Skip to content

Connecting over MCP

31 tools and 5 resources, over stdio or HTTP.

Without a clone, from PyPI. Build WebDriverAgent once, into ~/Library/Application Support/ios-mcp/wda, then point any client at uvx:

Terminal window
uvx ios-mcp prepare-wda simulator # about 20 seconds, once
uvx ios-mcp doctor # says what, if anything, is missing
{ "mcpServers": { "ios": { "command": "uvx", "args": ["ios-mcp"] } } }

From a clone, which is what the rest of this README assumes, every client starts it the same way: uv run --directory <path to this clone> ios-mcp serve. Use the absolute path; desktop apps do not start in your project directory. The clone’s build in vendor/wda is found from there.

Claude Code. Inside this repository it is already set up, through .mcp.json. From anywhere else:

Terminal window
claude mcp add ios -s user -- uv run --directory /path/to/ios-agent ios-mcp serve

Cursor (~/.cursor/mcp.json, or .cursor/mcp.json in a project) and Claude Desktop (~/Library/Application Support/Claude/claude_desktop_config.json) take the same block:

{
"mcpServers": {
"ios": {
"command": "uv",
"args": ["run", "--directory", "/path/to/ios-agent", "ios-mcp", "serve"]
}
}
}

VS Code (.vscode/mcp.json) calls the key servers:

{
"servers": {
"ios": {
"type": "stdio",
"command": "uv",
"args": ["run", "--directory", "/path/to/ios-agent", "ios-mcp", "serve"]
}
}
}

A client that supports MCP elicitation asks you directly before anything the approval gate stops; one that does not gets the decision handed back with a signature to pass on retry. See Approvals and secrets.

Then ask for what you want in plain language. The server ships an ios_operator prompt that teaches the observe/act/verify loop, so clients do not have to reinvent it. For a client that speaks HTTP rather than stdio, ios-mcp serve --transport http --port 8765 serves on this machine only: it has no authentication, so it checks where each request came from and will not bind another address without --allow-remote. See the threat model.

Or skip the protocol and import the library:

outcome = await run_goal(session, "turn on bold text")