Safari Web Inspector Bridge is an MCP server that gives AI agents the same capabilities a developer gets from Safari Web Inspector — inspect, observe, and automate WKWebViews running on connected iOS devices.
graph TD
Agent["AI Agent<br/>Claude, Codex, etc."]
Bridge["MCP Server:<br /><code>safari-web-inspector-bridge</code>"]
Proxy["managed child process: <code>ios-webkit-debug-proxy</code>"]
Device["iOS Device<br/>WKWebView"]
Agent <-->|"MCP <code>stdio</code>"| Bridge
Bridge <-->|"WebSocket<br/>WebKit Inspector Protocol"| Proxy
Proxy <-->|"usbmuxd<br/>(USB / Wi-Fi)"| Device
style Agent fill:#f0f0f0
style Bridge fill:#f0f0f0
style Proxy fill:#f0f0f0
style Device fill:#f0f0f0
The server spawns ios-webkit-debug-proxy as a child process, connects to the WebKit Inspector Protocol over WebSocket, and exposes everything as MCP tools. The proxy is managed for its full lifecycle -- started on init, health-checked, auto-restarted on crash, and killed on shutdown.
stateDiagram-v2
[*] --> Checking: Server starts
Checking --> Spawning: Binary found
Checking --> Error: Binary missing
Spawning --> HealthCheck: Process started
HealthCheck --> Ready: localhost:9221 responds
HealthCheck --> Error: Timeout (10s)
Ready --> Crashed: Process exits
Crashed --> Spawning: Restart (once)
Crashed --> Error: Already retried
Ready --> Stopped: Graceful shutdown
Stopped --> [*]
Discovery: list_devices | list_inspectable_pages | connect
Observation: get_url | get_dom | get_network_log | get_console_log | screenshot
Automation: navigate | execute_javascript | click_element | type_text | wait_for
macOSrequired forusbmuxdand iOS device connectivityios-webkit-debug-proxy:brew install ios-webkit-debug-proxyiOS: Settings › Safari › Advanced › Web Inspector: enabled- target app WKWebView must have
isInspectable = true
git clone https://github.com/andesco/safari-web-inspector-bridge.git
cd safari-web-inspector-bridge
npm install
npm run buildclaude mcp add safari-web-inspector-bridge node /path/to/safari-web-inspector-bridge/dist/index.jsAdd to your MCP configuration file:
{
"mcpServers": {
"safari-web-inspector-bridge": {
"command": "node",
"args": ["/path/to/safari-web-inspector-bridge/dist/index.js"],
"env": {
"SWIB_NETWORK_CAPTURE": "true",
"SWIB_CONSOLE_CAPTURE": "true"
}
}
}
}| Tool | Parameters | Returns |
|---|---|---|
list_devices |
(none) | [{ udid, name, os_version }] |
list_inspectable_pages |
device_udid? string -- filter to one device |
[{ page_id, title, url, app_bundle_id, device_udid }] |
connect |
page_id string -- from list_inspectable_pages |
{ connected, page_id, url, title, warnings? } |
| Tool | Parameters | Returns |
|---|---|---|
get_url |
(none) | { url } |
get_dom |
selector? string -- CSS selector (default: document.documentElement); outer_html? boolean (default: true) -- outerHTML vs textContent |
{ html } or { text } |
get_network_log |
clear? boolean (default: false); filter_url? string -- regex; filter_status? string -- e.g. "302", "4xx" |
[{ request_id, method, url, status, mime_type, response_headers, request_headers, redirected_from, redirected_to, timing, error }] |
get_console_log |
clear? boolean (default: false); level? "log" | "warn" | "error" | "info" |
[{ level, text, timestamp, source_url, line_number }] |
screenshot |
(none) | MCP image content (base64 PNG) |
| Tool | Parameters | Returns |
|---|---|---|
navigate |
url string |
{ url, title, status } |
execute_javascript |
expression string; await_promise? boolean (default: true) |
{ result } -- JSON-serialized return value |
click_element |
selector string -- CSS selector; index? number (default: 0) -- which match to click |
{ clicked, selector, tag_name } |
type_text |
text string; selector? string -- focus this element first |
{ typed: true } |
wait_for |
One of: selector? string, url_contains? string, network_idle? number (ms); plus timeout_ms? number (default: 10000) |
{ matched: true, elapsed_ms } |
| Env Var | Default | Description |
|---|---|---|
SWIB_AUTO_CONNECT |
false |
Auto-connect to the first inspectable page on startup |
SWIB_NETWORK_CAPTURE |
true |
Capture network requests on connect |
SWIB_CONSOLE_CAPTURE |
true |
Capture console messages on connect |
SWIB_PROXY_PORT |
9222 |
Starting port for ios-webkit-debug-proxy device ports |
Note
SWIB_NETWORK_CAPTURE and SWIB_CONSOLE_CAPTURE default to true — set to false to disable.
SWIB_AUTO_CONNECT defaults to false — set to true to enable.
sequenceDiagram
participant Agent as AI Agent
participant Bridge as MCP Server
participant Proxy as ios-webkit-debug-proxy
participant Device as iOS WKWebView
Agent->>Bridge: list_inspectable_pages()
Bridge->>Proxy: GET /json (devices + pages)
Proxy-->>Bridge: page_id: "1", title: "Banks"
Bridge-->>Agent: [{ page_id, title, url, app_bundle_id }]
Agent->>Bridge: connect({ page_id: "1" })
Bridge->>Proxy: WebSocket connect
Proxy->>Device: WebKit Inspector attach
Device-->>Proxy: Connected
Bridge-->>Agent: { connected: true }
Agent->>Bridge: get_network_log({ filter_url: "scotiabank" })
Bridge-->>Agent: [{ url, status: 302, redirected_to: "scotiabank://..." }]
Agent->>Bridge: execute_javascript("document.title")
Bridge->>Device: Runtime.evaluate
Device-->>Bridge: "Banks"
Bridge-->>Agent: { result: "Banks" }
Agent->>Bridge: click_element({ selector: "button.next" })
Bridge->>Device: Runtime.evaluate (querySelector + click)
Device-->>Bridge: clicked
Bridge-->>Agent: { clicked: true, tag_name: "button" }
Agent->>Bridge: wait_for({ url_contains: "/dashboard" })
loop Poll until match
Bridge->>Device: Runtime.evaluate (location.href)
Device-->>Bridge: current URL
end
Bridge-->>Agent: { matched: true, elapsed_ms: 1230 }
Agent->>Bridge: screenshot()
Bridge->>Device: Page.snapshotRect
Device-->>Bridge: base64 PNG
Bridge-->>Agent: image content
npm run build # Compile TypeScript to dist/
npm run dev # Watch mode (tsc --watch)
npm test # Run tests (vitest)
npm run test:watch # Watch mode tests
npm start # Run the MCP server (node dist/index.js)src/
index.ts # Entry point, server setup, lifecycle
types.ts # Interfaces and config loader
proxy-manager.ts # Spawns and manages ios-webkit-debug-proxy
device-discovery.ts # Queries proxy for devices and pages
webkit-connection.ts # WebSocket connection to WebKit Inspector Protocol
network-buffer.ts # Ring buffer for network request entries (1000 max)
tools/
device-tools.ts # list_devices, list_inspectable_pages, connect
observation-tools.ts # get_url, get_dom, get_network_log, get_console_log, screenshot
automation-tools.ts # navigate, execute_javascript, click_element, type_text, wait_for