ControlKit runners
Generated from docs/controlkit.md in
smbcloud-cli . Edit it there.
ControlKit is the native XCTest runner used by the CLI’s Apple-platform MCP
tools. The public runner project is
xcrs-controlkit. It uses
one Xcode project generated by XcodeGen, with platform-specific targets and
schemes:
| Platform | Scheme | App product |
|---|---|---|
| iOS / iPadOS | ControlKit-iOS | ControlKit.app |
| tvOS | ControlKit-tvOS | ControlKit.app |
| visionOS | ControlKit-visionOS | ControlKit.app |
| watchOS | ControlKit-watchOS | ControlKit.app |
| macOS | ControlKit-macOS | ControlKit.app |
The scheme and target names keep their platform suffixes so Xcode can select
the correct SDK, while each built app is named ControlKit.
Build a runner
Install XcodeGen once, clone the runner project, and generate the Xcode project:
brew install xcodegen
cd <path-to-xcrs-controlkit>/Runners
xcodegen generateBuild the simulator runner for the platform you need:
xcodebuild -project XCRSControlKitRunner.xcodeproj \
-scheme ControlKit-<platform> \
-destination 'generic/platform=<platform> Simulator' \
build-for-testingReplace <platform> with iOS, tvOS, watchOS, or visionOS. macOS is
built with -destination 'platform=macOS' and runs on the local Mac rather
than a simulator.
Start the runner
Boot a compatible simulator and run the UI-test host:
xcodebuild -project XCRSControlKitRunner.xcodeproj \
-scheme ControlKit-<platform> \
-destination 'id=<simulator-udid>' \
test-without-buildingThe shared XCTest runner starts a local ControlKit JSON-RPC server on
127.0.0.1:12004. Keep that test process running while calling the MCP tools.
The macOS UI-test target includes the network-server entitlement required to
bind the local port.
Run on a physical device
The same runner also works installed on a real iOS, tvOS, watchOS, or
visionOS device, not just a simulator. Build and start it against the
device’s UDID (find it with xcrun devicectl list devices) instead of a
simulator destination:
xcodebuild -project XCRSControlKitRunner.xcodeproj \
-scheme ControlKit-<platform> \
-destination 'id=<device-udid>' \
build-for-testing
TEST_RUNNER_CONTROLKIT_LISTEN_HOST=0.0.0.0 xcodebuild \
-project XCRSControlKitRunner.xcodeproj \
-scheme ControlKit-<platform> \
-destination 'id=<device-udid>' \
test-without-buildingThe CONTROLKIT_LISTEN_HOST environment variable controls what the
in-process RPC server binds to (Shared/ControlKitRPCServer.swift) — it
defaults to 127.0.0.1, which is loopback-only on the device itself and
unreachable from your Mac. xcodebuild forwards any TEST_RUNNER_-prefixed
environment variable into the test process with the prefix stripped, so
setting TEST_RUNNER_CONTROLKIT_LISTEN_HOST=0.0.0.0 makes the server bind on
every interface, including the one reachable over your LAN. Find the device’s
address with its Bonjour hostname (<device-name>.local, discoverable via
dns-sd -B _airplay._tcp local for an Apple TV) or its CoreDevice tunnel
address (xcrun devicectl device info details --device <udid>, under
Tunnel IP Address).
Connect through the automation MCP profile
Start either automation server:
xcrs --mcp
# or
smb --mcp --scope automationThe runner tools accept either simulator_name or simulator_udid for a
simulator, or host (plus controlkit_port, defaulting to 12004) for a
physical device or any other remote runner. Call device_select once to
remember the target so later tools need no target arguments. Both commands
expose the same unprefixed tool names.
| Tool | Runner | Purpose |
|---|---|---|
device_select | All platforms | Remember the active simulator or device for later tools. |
device_capabilities | All platforms | Read platform and capability information. |
input_click | macOS | Click at screen coordinates. |
input_spatial_tap | visionOS | Perform a spatial tap. |
input_tap | iOS / tvOS / watchOS, simulator or physical | Perform a touch tap. |
input_text | iOS / tvOS, simulator or physical | Type into the focused field. |
input_swipe | iOS / tvOS, simulator or physical | Swipe between coordinates. |
input_button | iOS / tvOS, simulator or physical | Press a Home or tvOS remote button. |
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. |
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. |
ui_describe | All UI-test runners | Read the accessibility hierarchy. |
ui_element_list | All UI-test runners | Read only actionable elements with tap coordinates. |
For a local macOS runner, omit simulator fields and pass host and
controlkit_port when needed:
{
"host": "127.0.0.1",
"controlkit_port": 12004,
"x": 400,
"y": 300
}For a physical device, pass its Bonjour hostname or tunnel address as host
instead:
{
"host": "<device-name>.local",
"controlkit_port": 12004,
"button": "select"
}Platform-specific operations intentionally reject unsupported input. For example, visionOS rejects raw touch, watchOS rejects pointer and spatial input, and macOS rejects touch and Home-button requests.