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). |