SimDrive — MCP-native iOS simulator driver. Vision-first, agent-driven.
Project description
SpecterQA for iOS
Hand your iOS simulator to your agent.
SpecterQA for iOS (simdrive internally) is an MCP server for driving iOS simulators. Vision-first. No XCTest, no accessibility-tree query, no daemons. Your agent looks at a screenshot, picks a pixel, and SpecterQA taps it.
Why
You stay in your editor. Your agent drives the sim in the background. Taps don't steal focus, your keyboard doesn't get hijacked.
Automating an iOS simulator from inside an LLM session has historically required:
- A Swift XCTest runner that breaks every Xcode release
- An accessibility tree your agent has to mentally reconstruct from JSON dumps
- Bespoke selectors (
label:"Sign in") that drift with every UI change - Watchdogs killing your runner mid-test
SpecterQA for iOS replaces all of that with: screenshot in, click out. Your agent already understands screenshots — the LLM is the selector engine.
Install
pip install specterqa-ios
Requirements:
- macOS with Xcode + iOS Simulator (for native HID input)
- A booted simulator. SpecterQA will use a running one or boot one for you.
SpecterQA runs in the background by default — taps and keystrokes go straight to the simulator without raising its window or stealing your keyboard focus. Verify via session_status (mode: "background").
Wire into Claude
Add to your .mcp.json:
{
"mcpServers": {
"specterqa-ios": { "command": "specterqa-ios" }
}
}
Restart Claude Code. The 29 SpecterQA MCP tools are now available.
Existing
.mcp.jsonconfigs withcommand: simdrivekeep working — both console scripts (specterqa-iosandsimdrive) point at the same server.
Quickstart
You: open Settings on iPhone 17 Pro and turn on Airplane Mode.
Claude (using SpecterQA):
→ session_start({device: "iPhone 17 Pro", app_bundle_id: "com.apple.Preferences"})
→ observe() # screenshot + annotated copy with numbered marks
→ tap({text: "Airplane Mode"}) # by visible text
→ observe() # sees the toggle
→ tap({mark: 12}) # by mark number from the annotation
→ observe() # confirms it's green
You can also tap({x, y}) if you have specific pixel coords (great for replay). Pick whichever is lowest-friction per call:
| Form | Use it for |
|---|---|
{text: "..."} |
Buttons, labels, anything with visible text |
{mark: N} |
When the agent has just looked at the annotated screenshot |
{x, y} |
Replays, deterministic UI tests, icons without text |
That's the whole loop. No selectors. No waits. No XCTest.
Tool surface (29 MCP tools)
| Group | Tools |
|---|---|
| Lifecycle (3) | session_start, session_end, session_status |
| Observe (1) | observe |
| Act (5) | tap, swipe, type_text, press_key, clear_field |
| Record/Replay (5) | record_start, record_stop, replay, list_replays, validate_replay |
| Logs (1) | logs |
| Performance (4) | perf, perf_baseline, perf_compare, memory |
| Diagnostics (5) | doctor, app_state, apps, crashes, list_devices |
| Robustness (4) | dismiss_first_launch_alerts, pre_grant_permissions, set_appearance, dismiss_sheet |
| Version (1) | version |
Coordinates are always in screenshot pixel space — same pixels the agent sees in the most recent observe.
Recording + replay
record_start({name: "checkout-flow"})
... agent does the flow naturally, calling tap/swipe/type_text ...
record_stop() # writes ~/.simdrive/recordings/checkout-flow/recording.yaml
Later:
replay({name: "checkout-flow", on_drift: "halt"})
Each step is gated on visual similarity: if the live screen has drifted from the recorded pre-screenshot, the replay halts (halt), warns and continues (warn), or proceeds blind (force). The recording is a self-contained YAML+PNG bundle you can commit to your repo.
Testing
pip install specterqa-ios[dev]
pytest # 91 unit tests, no sim required
pytest -m live # 26 live tests against TestKitApp
Live tests boot a fresh TestKitApp session per test and exercise every tool: tap by text/mark/coords, type into focused fields, swipe-to-scroll, alert-while-focused dismissal (the iOS 26 case that defeated v15), record + replay with drift detection.
What this isn't
- Not a real-device tool. v0.1 is simulator-only. Real device support via
idb/devicectlis on the roadmap. - Not a CI replacement (yet). Designed for interactive Claude sessions; CI integration is a follow-up.
- Not a fork of XCTest. We deliberately avoid Apple's testing stack to stay durable across Xcode releases.
License
MIT. Built by SyncTek.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file simdrive-1.0.0a5.tar.gz.
File metadata
- Download URL: simdrive-1.0.0a5.tar.gz
- Upload date:
- Size: 222.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
271bc6d76dbccf266f96128673f12b42c2f175fada4ccfe124296b0cf2dca6ab
|
|
| MD5 |
dd15caf7b5628c575d407964dd99b3c8
|
|
| BLAKE2b-256 |
6f3155a062b00c9f23518a9a62a3da89a2ea7fe85dd3bf0391f9ea1d5eca979b
|
File details
Details for the file simdrive-1.0.0a5-py3-none-any.whl.
File metadata
- Download URL: simdrive-1.0.0a5-py3-none-any.whl
- Upload date:
- Size: 161.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.9.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d4e7329bf1a024a12ee6a0a59497eb315c93078ad55cd48e929733f1da8509e6
|
|
| MD5 |
15182bb68fb031de4b4f8ba65932713f
|
|
| BLAKE2b-256 |
6c7ec7037545d89a070233363a6892077fc32a7a94128801c25c9186cff3c25f
|