@smbcloud / cli / commits / 4830243

Send the app id with xcrs input_tap and input_swipe

The ControlKit runner now refuses a tap or swipe when the app it resolves is not running. Without a bundleId it resolves its own host app, which is never launched, so both tools failed with "Application the app under test is not running". input_tap and input_swipe take app_id (or the bundle_id alias) and pass it as bundleId. Apple targets without one get a clear error; Android ignores the field. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>

Seto Elkahfi committed Oct 27, 2026 at 11:15 UTC 483024322196d7f0071f10ba89c732f5fe06da86
2 files changed +20 -5
crates/xcrs/src/mcp.rs
+18 -3
@@ -673,6 +673,12 @@ pub struct TapArgs {
673 /// Android device serial from `adb devices -l`.
674 #[serde(default)]
675 pub android_serial: Option<String>,
676 + /// Bundle identifier of the app to tap in. Required on Apple targets, ignored on Android.
677 + #[serde(default)]
678 + pub app_id: Option<String>,
679 + /// Backward-compatible alias for `app_id`.
680 + #[serde(default)]
681 + pub bundle_id: Option<String>,
682 /// Horizontal screen coordinate in points (Apple) or pixels (Android).
683 pub x: f64,
684 /// Vertical screen coordinate in points (Apple) or pixels (Android).
@@ -719,6 +725,12 @@ pub struct SwipeArgs {
725 /// Android device serial from `adb devices -l`.
726 #[serde(default)]
727 pub android_serial: Option<String>,
728 + /// Bundle identifier of the app to tap in. Required on Apple targets, ignored on Android.
729 + #[serde(default)]
730 + pub app_id: Option<String>,
731 + /// Backward-compatible alias for `app_id`.
732 + #[serde(default)]
733 + pub bundle_id: Option<String>,
734 /// Swipe start horizontal coordinate.
735 pub x1: f64,
736 /// Swipe start vertical coordinate.
@@ -1758,7 +1770,7 @@ macro_rules! xcrs_mcp_tools {
1770 name = $input_tap_name,
1771 title = "Tap (iOS/tvOS/Android)",
1772 annotations(title = "Tap (iOS/tvOS/Android)", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
1761 - description = "Purpose: tap the resolved target at screen coordinates. When to use vs siblings: use input_click for macOS and input_spatial_tap for visionOS instead; read coordinates from ui_element_list or ui_describe first. Behavior: for an Apple target, calls ControlKit `device.io.tap`; for Android, runs `adb shell input tap`, rounding x/y to the nearest pixel. Prerequisites: a reachable ControlKit endpoint (Apple) or adb connection (Android). Failure modes: errors if no target can be resolved, if x or y is not a finite number, or if the underlying call fails."
1773 + description = "Purpose: tap the resolved target at screen coordinates. When to use vs siblings: use input_click for macOS and input_spatial_tap for visionOS instead; read coordinates from ui_element_list or ui_describe first. Behavior: for an Apple target, calls ControlKit `device.io.tap`; for Android, runs `adb shell input tap`, rounding x/y to the nearest pixel. Prerequisites: a reachable ControlKit endpoint (Apple, with the app running and its identifier passed as app_id) or adb connection (Android). Failure modes: errors if no target can be resolved, if an Apple target has no app_id, if x or y is not a finite number, or if the underlying call fails."
1774 )]
1775 async fn input_tap(
1776 &self,
@@ -1798,11 +1810,12 @@ macro_rules! xcrs_mcp_tools {
1810 device.serial
1811 }
1812 _ => {
1813 + let app_id = Self::require_app_id("input_tap", args.app_id, args.bundle_id)?;
1814 let (simulator, controlkit) = Self::controlkit_for_target(&target)?;
1815 controlkit
1816 .call(
1817 "device.io.tap",
1805 - ::serde_json::json!({ "x": args.x, "y": args.y }),
1818 + ::serde_json::json!({ "bundleId": app_id, "x": args.x, "y": args.y }),
1819 )
1820 .await
1821 .map_err(|error| {
@@ -1878,7 +1891,7 @@ macro_rules! xcrs_mcp_tools {
1891 name = $input_swipe_name,
1892 title = "Swipe",
1893 annotations(title = "Swipe", read_only_hint = false, destructive_hint = false, idempotent_hint = false),
1881 - description = "Purpose: swipe between two screen coordinates on the resolved target, e.g. to scroll. When to use vs siblings: use input_tap for a single point of contact instead. Behavior: for an Apple target, calls ControlKit `device.io.swipe`; for Android, runs `adb shell input swipe` with duration_ms (default 300, rounding x/y to the nearest pixel). Prerequisites: a reachable ControlKit endpoint (Apple) or adb connection (Android). Failure modes: errors if no target can be resolved, if any coordinate is not a finite number, or (Android) if duration_ms exceeds 10000."
1894 + description = "Purpose: swipe between two screen coordinates on the resolved target, e.g. to scroll. When to use vs siblings: use input_tap for a single point of contact instead. Behavior: for an Apple target, calls ControlKit `device.io.swipe`; for Android, runs `adb shell input swipe` with duration_ms (default 300, rounding x/y to the nearest pixel). Prerequisites: a reachable ControlKit endpoint (Apple, with the app running and its identifier passed as app_id) or adb connection (Android). Failure modes: errors if no target can be resolved, if an Apple target has no app_id, if any coordinate is not a finite number, or (Android) if duration_ms exceeds 10000."
1895 )]
1896 async fn input_swipe(
1897 &self,
@@ -1926,11 +1939,13 @@ macro_rules! xcrs_mcp_tools {
1939 device.serial
1940 }
1941 _ => {
1942 + let app_id = Self::require_app_id("input_swipe", args.app_id, args.bundle_id)?;
1943 let (simulator, controlkit) = Self::controlkit_for_target(&target)?;
1944 controlkit
1945 .call(
1946 "device.io.swipe",
1947 ::serde_json::json!({
1948 + "bundleId": app_id,
1949 "x1": args.x1,
1950 "y1": args.y1,
1951 "x2": args.x2,
docs/controlkit.md
+2 -2
@@ -111,9 +111,9 @@ expose the same unprefixed tool names.
111 | `device_capabilities` | All platforms | Read platform and capability information. |
112 | `input_click` | macOS | Click at screen coordinates. |
113 | `input_spatial_tap` | visionOS | Perform a spatial tap. |
114 -| `input_tap` | iOS / tvOS / watchOS, simulator or physical | Perform a touch tap. |
114 +| `input_tap` | iOS / tvOS / watchOS, simulator or physical | Perform a touch tap. Apple targets need `app_id`, the running app to tap in. |
115 | `input_text` | iOS / tvOS, simulator or physical | Type into the focused field. |
116 -| `input_swipe` | iOS / tvOS, simulator or physical | Swipe between coordinates. |
116 +| `input_swipe` | iOS / tvOS, simulator or physical | Swipe between coordinates. Apple targets need `app_id`, the running app to swipe in. |
117 | `input_button` | iOS / tvOS, simulator or physical | Press a Home or tvOS remote button. |
118 | `app_launch`/`app_terminate` | simulator or physical | Launch/terminate an app by bundle ID. On a physical device this calls the runner's `device.apps.launch`/`device.apps.terminate` RPC methods instead of `simctl`. |
119 | `screen_capture` | Simulator or physical | Capture a PNG screenshot. Uses `simctl` for a simulator, or `devicectl device capture screenshot` when a `device` identifier is given. `display` (`outer`, `inner`, `all` or a screen ID) picks a display of a foldable simulator; see [iPhone Duo](#iphone-duo-foldable-simulators). |