ScreenSync MCP guide

Testing Android Deep Links and App Links with Claude Code and MCP

Automate Android Deep Links and App Links testing on physical devices with Claude Code and ScreenSync MCP. Verify intent filters, backstacks, and URI routing.

A glowing purple stream of digital data connects an open laptop and a smartphone on a dark desk.

Validating Android Deep Links and Android App Links on physical hardware represents one of the most frustrating bottlenecks in mobile software engineering. While universal URI routing and deep links provide seamless user acquisition, marketing attribution, and authentication handoffs, verifying their execution across real devices has historically demanded tedious manual testing or fragile shell scripts. A single malformed query parameter, an unverified assetlinks.json domain record, or a misconfigured launchMode in AndroidManifest.xml can silently route users to broken fallback screens or trigger unwanted system disambiguation dialogs. By pairing autonomous AI coding agents like Claude Code with the Model Context Protocol (MCP) through ScreenSync MCP, engineering teams can now automate comprehensive deep-link verification cycles, validate backstack integrity, and triage Intent routing failures directly on physical Android devices.

Key Takeaways

  • Universal Intent verification: ScreenSync MCP empowers Claude Code to dispatch, inspect, and validate both custom URI schemes (myapp://checkout) and verified Android App Links (https://domain.com/path) on real Android devices without synthetic emulators.
  • Non-invasive display perception: Real-time display streaming operates through Android's native MediaProjection API (Android 7.0+, zero root, zero ADB overhead), maintaining sub-100 millisecond visual latency over local area networks (LAN).
  • Deterministic Intent and input injection: Device control tools such as control_open_url, control_tap, and control_type execute deterministically via Android Debug Bridge (ADB) over USB or wireless debugging.
  • Dual-layer DOM and semantics inspection: Claude Code resolves destination screens and catches silent routing errors by pairing structural node hierarchy extraction from get_ui_hierarchy with visual pixel verification from get_latest_screenshot.
  • Deep backstack and cold-start triage: The agent validates synthetic task backstacks (TaskStackBuilder) and monitors system intent resolution, activity recreations, and unhandled bundle exceptions in real time using get_logcat.
  • Cross-surface web-to-app handoff: ScreenSync MCP unifies native Android surfaces with active desktop browser sessions across 226 total MCP tools (29 mobile/desktop tools and 197 browser tools), enabling end-to-end verification of OAuth redirects and web checkout bridges.

Why Testing Android Deep Links with MCP Solves Mobile Routing Flakiness

Testing Android deep links with the Model Context Protocol solves mobile routing flakiness by automating intent execution and destination verification across live Android runtimes. Autonomous agents inspect accessibility trees and visual frames simultaneously, verifying that complex URI parameters, authentication tokens, and backstack hierarchies reconstruct properly on physical hardware without brittle manual verification or mocked intent test runners.

Modern Android development relies on deep links to transition users seamlessly into specific application views. Whether handling a password-reset email, an e-commerce promotional discount, a push notification action, or a cross-platform OAuth2 redirect, incoming URIs must be parsed by the Android operating system, mapped against registered <intent-filter> declarations in AndroidManifest.xml, and dispatched to the appropriate Activity or Jetpack Compose NavHost destination. When validating URI handoffs triggered from system alert cards, developers can also apply testing Android push notifications with Claude Code and MCP to verify the full delivery and notification shade lifecycle.

However, deep link testing has remained stubbornly brittle in automated test suites:

  1. The Disambiguation Dialog Trap: If an Android App Link fails Digital Asset Links verification, Android falls back to displaying a system "Open with..." dialog. Traditional instrumentation tests running inside an isolated app process cannot detect or interact with system UI overlays, causing automated tests to hang or fail silently.
  2. Backstack Corruption: When an app is cold-started via a deep link, pressing the system Back button should navigate users up the application hierarchy rather than immediately dumping them back onto the mobile home screen or external browser. Verifying synthetic backstack generation (TaskStackBuilder) requires full-device interaction that mocks cannot replicate.
  3. Complex Parameter Parsing: Modern deep links carry dynamic payloads—nested JSON objects, JWT authentication tokens, affiliate tracking cookies, and URL-encoded query strings. Minor parsing discrepancies between debug and release builds often result in blank screens, silent null-pointer exceptions, or infinite splash-screen loading loops.
  4. OEM Behavior Fragmentation: Different Android manufacturers (Samsung One UI, Xiaomi HyperOS, Google Pixel) implement custom Intent resolvers, background process limiters, and default browser handlers that alter how links open in the wild.

The emergence of terminal-based AI coding agents like Claude Code transforms this verification pipeline. Rather than writing brittle procedural test cases in Espresso or Appium, developers can instruct Claude Code to execute real URIs on a physical smartphone, observe the rendered composables, inspect system logs, and modify the underlying Kotlin or manifest code when routing discrepancies are found. ScreenSync MCP serves as the bridge that gives Claude Code this real-time perception and execution power.

Deep Link Verification Dimension Traditional Espresso / JUnit Tests Cloud Device Farms (Appium / BrowserStack) ScreenSync MCP + Claude Code (Physical Device)
Execution Surface Mocked Intent stubs in-process Cloud-hosted virtual or remote devices Real local physical device on developer desk
System UI Interaction Impossible (sandboxed inside test APK) Brittle XPath selectors; high latency Native inspection via get_ui_hierarchy & ADB
Visual Mirror Latency No live visual mirror 800 ms – 3,000 ms cloud stream lag ~94 ms median local LAN MediaProjection
Domain Verification Triage Cannot test live assetlinks.json checks Slow manual terminal tunneling Direct adb shell pm get-app-links execution
Backstack Validation Limited to in-app synthetic transitions Flaky multi-activity orchestration Full hardware back-button navigation loop
Cross-Surface Web Handoff Isolated to webview mocks Fragmented web/mobile test sessions Unified browser extension + Android hub (226 tools)
Developer Feedback Loop 3–8 minute Gradle test rebuild cycle 5–15 minute queue and cloud run cycle Instant iterative prompt-driven verification

Deep Linking Architecture: Custom Schemes vs Android App Links

Android deep linking architecture consists of three distinct mechanisms: standard deep links, web links, and Android App Links. Android App Links provide verified, secure, first-party routing by matching public HTTPS URLs to a cryptographic SHA-256 fingerprint hosted in an assetlinks.json file on the developer's web domain.

To test deep links effectively, developers and AI agents must understand how Android categorizes incoming URIs across three key routing paths:

URI Category URI Pattern Example Resolution Authority User Experience on Click
Custom URI Schemes screensync://navigate?id=4920 Local Intent Filter registry Displays system disambiguation dialog ("Open with...") if multiple apps register the scheme
Web Links (Unverified) https://screensync.tech/p/4920 System default browser Typically opens inside Chrome or external browser unless manually configured by user
Android App Links https://www.screensyncmcp.tech/... assetlinks.json SHA-256 validation Bypasses browser entirely; launches target Android Activity instantly with zero dialogs

1. Standard Deep Links (Custom Schemes)

Standard deep links use custom URI schemes such as myapp://details?id=4920. While simple to configure, custom schemes carry severe security vulnerabilities: any malicious application installed on the user's device can declare the exact same scheme in its AndroidManifest.xml. When a user clicks a custom scheme, Android displays a system disambiguation dialog asking the user which application should handle the request.

2. Web Links (Unverified HTTP/HTTPS)

Web links use standard HTTP or HTTPS schemes (such as https://example.com/products/4920). If an app registers an intent filter for this domain without auto-verification, Android still treats it as ambiguous, often opening the URL directly in Chrome or displaying a chooser dialog.

3. Android App Links (Verified HTTPS)

Introduced in Android 6.0 (API 23) and strictly enforced in Android 12+, Android App Links turn web links into seamless direct-to-app launches. When android:autoVerify="true" is declared on an intent filter, the Android OS contacts the domain's .well-known/assetlinks.json endpoint upon APK installation. If the SHA-256 certificate fingerprint matches the installed app's signing key, Android marks the domain as verified. Subsequent clicks on that URL bypass the browser and open the application immediately without showing a disambiguation dialog.

Here is a production-grade AndroidManifest.xml configuration demonstrating both custom scheme fallbacks and verified Android App Links:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.screensync.deeplink">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.ScreenSyncDeepLink">

        <!-- Deep-Link Entry Point Activity -->
        <activity
            android:name=".DeepLinkActivity"
            android:exported="true"
            android:launchMode="singleTask">

            <!-- 1. Custom URI Scheme Filter (Internal & Legacy Redirects) -->
            <intent-filter>
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <data
                    android:scheme="screensync"
                    android:host="navigate" />
            </intent-filter>

            <!-- 2. Verified Android App Link Filter (Production Universal Links) -->
            <intent-filter android:autoVerify="true">
                <action android:name="android.intent.action.VIEW" />
                <category android:name="android.intent.category.DEFAULT" />
                <category android:name="android.intent.category.BROWSABLE" />

                <data
                    android:scheme="https"
                    android:host="www.screensyncmcp.tech"
                    android:pathPrefix="/products" />
                <data
                    android:scheme="https"
                    android:host="www.screensyncmcp.tech"
                    android:pathPrefix="/auth/callback" />
            </intent-filter>
        </activity>

        <activity
            android:name=".MainActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>
</manifest>

When building applications with Jetpack Compose Navigation, deep links are declared directly within composable routes using navDeepLink:

package com.example.screensync.deeplink.ui

import androidx.compose.runtime.Composable
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController
import androidx.navigation.navArgument
import androidx.navigation.navDeepLink

@Composable
fun AppNavHost() {
    val navController = rememberNavController()
    val baseUri = "https://www.screensyncmcp.tech"

    NavHost(navController = navController, startDestination = "home") {
        composable("home") {
            HomeScreen(onNavigateToProduct = { id -> navController.navigate("product/$id") })
        }

        composable(
            route = "product/{productId}",
            arguments = listOf(
                navArgument("productId") { type = NavType.StringType }
            ),
            deepLinks = listOf(
                navDeepLink { uriPattern = "$baseUri/products/{productId}" },
                navDeepLink { uriPattern = "screensync://navigate?productId={productId}" }
            )
        ) { backStackEntry ->
            val productId = backStackEntry.arguments?.getString("productId") ?: "unknown"
            ProductDetailScreen(
                productId = productId,
                onBackClicked = { navController.popBackStack() }
            )
        }
    }
}

How ScreenSync MCP Bridges Claude Code to Physical Intent Routing

ScreenSync MCP bridges Claude Code to physical Android intent routing through a dual-channel architecture. Display frames stream from the Android app to the desktop hub via MediaProjection without ADB overhead, while intent dispatches, URL launches, and touch validations execute through ADB over USB or wireless debugging.

Understanding the mechanics of ScreenSync MCP explains why it outperforms legacy mobile automation tools for agentic workflows across three decoupled architectural planes:

Architectural Plane Connection Channel Protocol / Interface Function in Deep Link Testing
Agent Control Plane Local stdio Pipe JSON-RPC (MCP) Claude Code sends structured tool calls (control_open_url, get_ui_hierarchy) to the desktop hub
Media Streaming Plane Local Subnet (LAN) Hardware MediaProjection Android app streams low-latency display frames (~94 ms) to hub port 3000 without ADB saturation
Device Execution Plane USB or Wireless Debugging ADB Daemon (am start) Desktop hub injects deterministic intents, keycodes, and logcat queries directly into Android OS

Display Perception: Zero-Root MediaProjection

The ScreenSync MCP mobile application runs on Android 7.0 or newer and initializes a native MediaProjection display capture session upon interactive user consent. Screen buffers are captured directly from Android's hardware surface compositor, compressed into low-overhead video frames, and transmitted over your local Wi-Fi subnet to the desktop hub on port 3000. This achieves a median visual mirror latency of approximately 94 milliseconds. Critically, screen capture requires no root access and does not compete for ADB bandwidth.

Input & Intent Execution: ADB Bridge

While display streaming runs over LAN via MediaProjection, actionable commands execute through ADB. When Claude Code decides to fire an intent, it invokes ScreenSync MCP's control_open_url or executes raw ADB shell commands. The desktop hub translates the JSON-RPC tool call into an am start command:

adb shell am start -W -a android.intent.action.VIEW -d "https://www.screensyncmcp.tech/products/mcp-hub-adapter" com.example.screensync.deeplink

The -W flag forces the ActivityManager to wait for the target activity to complete its launch sequence and returns execution metrics (such as TotalTime, WaitTime, and launch status). Once launched, Claude Code immediately queries get_ui_hierarchy to confirm that the expected UI nodes have populated.

The Complete 226-Tool Ecosystem

ScreenSync MCP exposes 226 tools across both mobile hardware and desktop browser environments:

  • 29 Phone & Desktop Tools: Including 12 control_* tools (control_tap, control_swipe, control_type, control_open_url, control_launch_app, control_key), 3 os_* tools (os_mouse_click, os_type, os_hotkey), and 14 capture and telemetry tools (get_latest_screenshot, get_ui_hierarchy, get_logcat, record_screen, get_device_status).
  • 197 Browser Tools: The web_* suite operates through an authenticated Chrome/Edge MV3 browser extension, allowing Claude Code to drive live desktop browser tabs, click promotional links, verify web OAuth login flows, and monitor deep link redirects back into the Android app.

Step-by-Step Setup: Configuring Hardware and MCP Clients

Configuring physical Android hardware and connecting Claude Code to ScreenSync MCP requires four sequential steps: launching the desktop hub, registering the MCP server, installing the Android client, and authorizing debugging permissions. This pipeline establishes an isolated, private testing environment on your local network.

Step 1: Workstation and Hardware Prerequisites

Ensure your development environment meets the following specifications:

  1. Workstation: Windows 10/11, macOS, or Linux running Node.js 18 or higher.
  2. Android Hardware: A physical smartphone or tablet running Android 7.0 (API 24) or later connected to the same local Wi-Fi network as the workstation.
  3. Android Platform Tools: adb configured in your workstation PATH.
  4. Target Application: Debug APK installed on the device with your intent filters compiled.

Step 2: Launch the ScreenSync MCP Desktop Hub

Download the desktop hub archive from screensyncmcp.tech. Unpack the folder and execute the startup launcher:

# On Windows (PowerShell)
cd C:\tools\screensync-hub
.\start-hub.bat

# On macOS or Linux (Terminal)
cd ~/tools/screensync-hub
chmod +x start-hub.sh
./start-hub.sh

The desktop hub starts an HTTP server on port 3000 to receive MediaProjection video frames and initializes a JSON-RPC MCP server over stdio. The console displays your bearer token (SCREEN_SYNC_TOKEN) and an ASCII pairing QR code.

Step 3: Register ScreenSync MCP in Claude Code

In your workstation terminal, register the MCP server with the Claude Code CLI:

claude mcp add screensync node "/path/to/screensync-hub/screensync-hub.js"

If configuring Claude Desktop, open claude_desktop_config.json (Settings > Developer > Edit Config) and add the server configuration:

{
  "mcpServers": {
    "screensync": {
      "command": "node",
      "args": ["/path/to/screensync-hub/screensync-hub.js"],
      "env": {
        "SCREEN_SYNC_TOKEN": "your_hub_token_here"
      }
    }
  }
}

(Note: ChatGPT's web client relies strictly on remote cloud endpoints and does not support local stdio MCP servers. ScreenSync MCP is built specifically for local coding agents including Claude Code, Claude Desktop, Cursor, and VS Code).

Step 4: Install the Android App and Authorize Debugging

On your physical smartphone:

  1. Download the free ScreenSync MCP app (com.screensync.mcp) from Google Play.
  2. Open the app and allow mDNS discovery to locate your desktop hub, or scan the QR code displayed in the hub console.
  3. Tap Start Projection and confirm the system MediaProjection capture prompt.
  4. Enable Developer Options (Settings > About Phone > tap Build Number 7 times) and turn on USB Debugging or Wireless Debugging. If using wireless mode, follow our complete guide on pairing Android wireless debugging to an MCP server without USB.
  5. Verify connectivity by executing adb devices in your workstation terminal. For additional setup guidance, review the official ScreenSync MCP setup documentation.

Practical Deep Link QA Workflows: App Links, Backstack, and Web Handoff

Practical deep link QA workflows with Claude Code validate routing accuracy across three critical scenarios: auto-verification of Android App Links, synthetic backstack navigation, and cross-surface browser-to-native app handoffs. Claude Code leverages ScreenSync MCP's 226 tools to dispatch intents, assert accessibility hierarchies, and capture visual proof at each phase.

Workflow 1: Validating Android App Link Auto-Verification and Query Parsing

When launching an App Link, the operating system must verify that the target domain is approved. If verification fails, Android displays a disambiguation picker rather than launching the application directly. Claude Code can verify domain status via ADB and test intent execution:

Developer Prompt:
"Claude, verify that https://www.screensyncmcp.tech is verified for package com.example.screensync.deeplink, then dispatch a deep link for product ID 'mcp-pro-hub' with promotional query 'DISCOUNT20'. Confirm that the ProductDetailScreen opens and the discount banner is visible."

Step 1: Claude Code checks domain verification status

Claude Code first executes a diagnostic command to inspect the Android package manager's domain verification table:

adb shell pm get-app-links com.example.screensync.deeplink

Output:

com.example.screensync.deeplink:
    ID: d7f02b3c-9182-4c28-98e2-f81829e12345
    Signatures: [A4:82:19:...]
    Domain verification state:
      www.screensyncmcp.tech: verified

If the state returns 1024 (asking/unverified), Claude Code can simulate user approval during development:

adb shell pm set-app-links-user-selection --user 0 --package com.example.screensync.deeplink true www.screensyncmcp.tech

Step 2: Claude Code dispatches the deep link

Next, Claude Code invokes the control_open_url MCP tool:

{
  "name": "control_open_url",
  "arguments": {
    "url": "https://www.screensyncmcp.tech/products/mcp-pro-hub?promo=DISCOUNT20"
  }
}

Step 3: Structural and Visual Verification

Claude Code calls get_ui_hierarchy to assert that the destination composable loaded with the correct parameters:

{
  "name": "get_ui_hierarchy",
  "arguments": {}
}

The returned accessibility tree contains the target elements:

<node text="MCP Pro Hub" resource-id="text_product_title" bounds="[60,320][1020,440]" />
<node text="Promo Applied: DISCOUNT20 (-20%)" resource-id="banner_promo_applied" bounds="[60,460][1020,580]" />
<node text="Checkout Now" resource-id="btn_checkout" bounds="[60,2100][1020,2240]" />

To complete the audit, Claude Code invokes get_latest_screenshot to confirm that the promotional banner rendered with the expected purple accent styling and no layout clipping.

Workflow 2: Validating Navigation Component Backstack and Synthetic Up Navigation

A critical defect in deep link implementations is broken backstack behavior. When an application is launched via an external intent while already running or cold-started, tapping the back button must navigate up to the parent catalog screen rather than exiting to the launcher.

Developer Prompt:
"Claude, cold-start our app using the deep link 'screensync://navigate?productId=sensor-pack-9', verify the product screen appears, press the device back button, and verify that the app navigates back to HomeScreen instead of quitting."

Step 1: Force stop the application to simulate cold launch

Claude Code ensures a clean state by issuing an ADB force-stop:

adb shell am force-stop com.example.screensync.deeplink

Step 2: Dispatch deep link and verify landing page

Claude Code calls control_open_url with the custom scheme:

{
  "name": "control_open_url",
  "arguments": {
    "url": "screensync://navigate?productId=sensor-pack-9"
  }
}

Claude Code calls get_ui_hierarchy and confirms text_product_title contains "Sensor Pack 9".

Step 3: Trigger the Back keycode

Claude Code invokes the MCP key tool:

{
  "name": "control_key",
  "arguments": {
    "keycode": 4
  }
}

(Keycode 4 corresponds to KEYCODE_BACK in Android).

Step 4: Validate parent destination

Claude Code calls get_ui_hierarchy once more:

{
  "name": "get_ui_hierarchy",
  "arguments": {}
}

The accessibility dump reveals that HomeScreen is active with resource-id="feed_category_header". If the back press had exited the application, the root node would belong to com.google.android.apps.nexuslauncher or com.sec.android.app.launcher. By asserting the active package and layout nodes, Claude Code proves that the navigation backstack was synthesized properly.

Workflow 3: Cross-Surface Browser OAuth to Native App Handoff

In modern authentication workflows, mobile apps redirect users to an external browser tab for federated OAuth (Google, GitHub, or Okta), which redirects back to the mobile app via a custom scheme callback (myapp://auth/callback?code=xyz). ScreenSync MCP's combination of 197 browser tools and 29 mobile tools makes testing this cross-platform loop straightforward.

Developer Prompt:
"Claude, launch our Android app, tap 'Sign in with Web', open our browser extension tab, complete the OAuth approval, and verify that the browser redirects back into the Android app with an authenticated session."
  1. Native Mobile Step: Claude Code calls control_launch_app to launch com.example.screensync.deeplink.
  2. Trigger Web Flow: Claude Code calls control_tap on the btn_oauth_login composable. The app launches Chrome with the OAuth authorization URL.
  3. Browser Automation: Claude Code switches to the web_* suite, finding the active tab with web_tabs, focusing the authorization button with web_click, and confirming permissions.
  4. Deep Link Handoff: The web authorization server issues a 302 Found redirect to https://www.screensyncmcp.tech/auth/callback?code=auth_sec_token_9981. Android intercepts the verified App Link, returning the user to DeepLinkActivity.
  5. Final Assertion: Claude Code calls get_ui_hierarchy on the mobile device, confirming that text_user_profile displays "Authenticated as Developer".
QA Intent Test Scenario ScreenSync MCP Tools Used Primary Assertion Mechanism Common Defect Detected
Android App Link Direct Launch control_open_url, get_ui_hierarchy Accessibility Node resource-id Domain verification missing in assetlinks.json
Custom URI Parameter Injection control_open_url, get_latest_screenshot Multimodal Visual Comparison Malformed URL encoding causing null string crashes
Cold-Start Backstack Validation control_key (Back), get_ui_hierarchy Active Activity / Composable Hierarchy App exits to launcher instead of parent route
Disambiguation Dialog Detection get_ui_hierarchy, get_latest_screenshot System Package (com.android.internal.app) Multiple apps registering the same custom scheme
OAuth Browser-to-App Handoff web_click, control_open_url, get_logcat Cross-surface token lifecycle Redirect stripped by mobile browser security policy
Notification Deep-Link Tap control_swipe, control_tap, get_ui_hierarchy Notification shade node interaction PendingIntent flags missing FLAG_IMMUTABLE

Triaging Common Deep Link Pitfalls and Logcat Diagnostics

Triaging deep link failures requires inspecting system-level activity lifecycle events and package manager verifications. Developers leverage get_logcat to diagnose ActivityNotFoundException, duplicate task allocations, and unhandled intent extras directly from terminal sessions.

1. The ActivityNotFoundException Trap

When an agent or external app fires a deep link for an unmapped URI, Android throws an ActivityNotFoundException. This commonly happens when trailing slashes or path prefixes in AndroidManifest.xml do not match incoming URLs:

<!-- Mismatched Path: Only matches exact /products, fails on /products/482 -->
<data android:scheme="https" android:host="www.screensyncmcp.tech" android:path="/products" />

<!-- Corrected Path: Matches all child routes -->
<data android:scheme="https" android:host="www.screensyncmcp.tech" android:pathPrefix="/products" />

When this error occurs, Claude Code invokes get_logcat with lines: 100:

E AndroidRuntime: FATAL EXCEPTION: main
E AndroidRuntime: android.content.ActivityNotFoundException: No Activity found to handle Intent { act=android.intent.action.VIEW dat=https://www.screensyncmcp.tech/products/482 }

Claude Code identifies the exact missing route pattern in AndroidManifest.xml, applies the patch directly in your repository, recompiles the build, and re-tests the intent.

2. Disambiguation Chooser Dialogs on Android 12+

Starting with Android 12 (API 31), Android strictly requires web links to pass domain verification. If your domain has not been verified or if android:autoVerify="true" is omitted, tapping the link inside Gmail or messaging apps will open Chrome instead of your native app.

To triage domain verification failures, Claude Code runs:

adb shell pm get-app-links --user 0 com.example.screensync.deeplink

If the state shows legacy_failure or verifier_timeout, verify that:

  1. Your domain serves https://www.screensyncmcp.tech/.well-known/assetlinks.json with Content-Type application/json.
  2. The endpoint returns HTTP status 200 OK without intermediate HTTP-to-HTTPS redirects.
  3. The sha256_cert_fingerprint matches the keystore certificate used to sign the installed APK.
[
  {
    "relation": ["delegate_permission/common.handle_all_urls"],
    "target": {
      "namespace": "android_app",
      "package_name": "com.example.screensync.deeplink",
      "sha256_cert_fingerprints": [
        "14:6D:E9:01:07:49:7A:A2:57:C3:04:A2:B8:3B:EE:71:95:C0:A4:77:2A:00:00:84:34:82:12:29:40:E1:9F:22"
      ]
    }
  }
]

3. LaunchMode and SingleTask State Loss

When an Activity has android:launchMode="singleTask" or "singleTop", launching a deep link while the app is running does not create a new Activity instance. Instead, Android delivers the intent to onNewIntent(intent: Intent?). If developers fail to override onNewIntent or do not call setIntent(intent), the Activity continues displaying its previous state, ignoring the incoming deep link parameters:

// In DeepLinkActivity.kt
override fun onNewIntent(intent: Intent?) {
    super.onNewIntent(intent)
    setIntent(intent) // Essential: Updates the stored intent so composables receive new extras
    intent?.data?.let { uri ->
        viewModel.handleIncomingDeepLink(uri)
    }
}

Claude Code diagnoses this issue by comparing get_ui_hierarchy before and after firing control_open_url. If the UI state remains unchanged despite a successful am start response, Claude Code navigates to the target Activity and checks for missing onNewIntent overrides.

Implementation Checklist for Mobile Engineering Teams

  • [ ] Ensure host workstation runs Node.js 18 or higher with adb added to system environment variables.
  • [ ] Unpack the ScreenSync MCP desktop hub and launch start-hub.bat (Windows) or start-hub.sh (macOS/Linux).
  • [ ] Register the MCP server in Claude Code CLI via claude mcp add screensync node "/path/to/screensync-hub.js".
  • [ ] Install the ScreenSync MCP Android client (version 2.5.7+) from Google Play on physical Android 7.0+ hardware.
  • [ ] Connect the Android device and workstation to the same local Wi-Fi subnet; complete mDNS pairing or scan the console QR code.
  • [ ] Authorize the native MediaProjection display recording prompt on the phone for local sub-100ms video streaming.
  • [ ] Enable USB Debugging or Wireless Debugging in Android Developer Options; confirm active connection with adb devices.
  • [ ] Verify that AndroidManifest.xml declares android:autoVerify="true" for all production HTTPS domain intent filters.
  • [ ] Validate that the target web server hosts /.well-known/assetlinks.json with the matching SHA-256 APK signing fingerprint.
  • [ ] Confirm that onNewIntent() is implemented in deep-link activities with setIntent(intent) to handle singleTask intent updates.
  • [ ] Test cold-start synthetic backstacks to ensure hardware back-button navigation returns to the parent route rather than exiting.
Deep Link Component Target Standard Verification Command / Tool Success Criteria
Android App Links HTTPS Universal URIs adb shell pm get-app-links State: verified for all declared domains
AssetLinks Endpoint JSON-LD Cryptographic Link curl -sI https://domain/.well-known/assetlinks.json HTTP 200 OK, Content-Type: application/json
Custom URI Schemes Internal App Protocol control_open_url Correct Activity launches without disambiguation
Intent Parameter Parser Query strings & path tokens get_ui_hierarchy Target composables reflect query values
Visual Rendering Pixel fidelity & styling get_latest_screenshot No cropped text, layout shifts, or clipping
Backstack Synthesis Parent hierarchical routing control_key (Back, Keycode 4) Transitions to parent screen, not home launcher
System Diagnostics Runtime exceptions & logs get_logcat Zero ActivityNotFoundException or dropped frames

Conclusion

Automating Android deep link and App Link validation with Claude Code and ScreenSync MCP eliminates one of the most error-prone manual bottlenecks in mobile app development. By pairing high-speed, local display streaming via Android's native MediaProjection API with deterministic ADB intent execution, engineering teams can validate universal URLs, query parameters, backstack hierarchies, and cross-surface OAuth handoffs in seconds.

ScreenSync MCP provides the ScreenSync MCP Android app, ScreenSync MCP hub, and ScreenSync MCP browser extension for development teams worldwide. Explore our technical documentation at https://www.screensyncmcp.tech, contact our engineering group directly at [email protected], and Get it on Google Play to start automating real physical Android hardware today.

FAQ

Questions, answered

What is the difference between an Android Deep Link and an Android App Link?

Android Deep Links use custom URI schemes that prompt an operating system disambiguation dialog when multiple applications declare identical schemes. Android App Links are verified HTTPS URLs tied to a cryptographic SHA-256 certificate fingerprint in an assetlinks.json file, allowing Android to bypass the browser and launch the destination activity directly.

Does ScreenSync MCP require root access or ADB to stream the Android screen?

ScreenSync MCP captures display frames through Android's native MediaProjection API after one-time interactive user consent, requiring neither device root privileges nor ADB overhead. However, executing input actions such as tapping, swiping, and dispatching deep-link URLs requires an active ADB connection over USB or authenticated wireless debugging.

How does Claude Code verify that an Android App Link domain is properly verified?

Claude Code queries the Android package manager by executing the shell command adb shell pm get-app-links for the target package. The command returns the domain verification table, confirming whether the domain state is marked as verified or has failed due to certificate mismatches or missing assetlinks.json endpoints.

Can ScreenSync MCP test deep links when an application is cold-started?

Yes. ScreenSync MCP can issue an ADB force-stop command to terminate the target application process, dispatch the intent using the control_open_url tool, and inspect the resulting accessibility hierarchy with get_ui_hierarchy. This enables testing of initial bundle parsing, splash-screen transitions, and synthetic backstack generation on physical hardware.

How does Claude Code validate the Android navigation backstack after a deep link?

Claude Code dispatches a deep link to open a nested destination, validates screen elements using get_ui_hierarchy, and triggers keycode 4 with control_key to simulate the hardware back button. The agent then dumps the hierarchy again to verify navigation returned to the parent screen rather than exiting to the launcher.

What happens if an activity with singleTask launchMode receives a deep link while running?

When an activity configured with singleTask receives a new intent while active, Android routes the intent to onNewIntent without recreating the activity. Developers must implement onNewIntent and invoke setIntent to update intent extras; otherwise, the view hierarchy will fail to update with incoming deep-link parameters.

Can ScreenSync MCP automate cross-surface OAuth redirects between desktop browsers and Android?

Yes. ScreenSync MCP unifies 29 mobile and desktop tools with 197 browser tools through a Chrome or Edge extension. Claude Code can initiate an authentication flow on the phone, automate web consent in an authenticated browser tab, and verify the resulting deep-link redirect back into the mobile app.

Which MCP clients are compatible with ScreenSync MCP for mobile automated testing?

ScreenSync MCP connects to any MCP client supporting local stdio process execution, including Claude Code, Claude Desktop, Cursor, and VS Code agent mode. The ChatGPT web application is unsupported because ChatGPT requires remote HTTPS endpoints rather than local stdio processes running on your developer workstation.

Written by

ScreenSync MCP team

The ScreenSync MCP team at EpsolDev builds the Android app, the desktop MCP hub and the browser extension that let AI agents see and control a real phone. These guides come out of that work.