AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
MCP verified MIT Self-run

Opensafari

mcp-shaun0927-opensafari · by shaun0927

Smart. Fast. Parallel. iOS Safari automation MCP server via Xcode Simulator.

— No reviews yet
0 installs
34 views
0.0% view→install

Install

$ agentstack add mcp-shaun0927-opensafari

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • ✓ Prompt-injection patterns
  • ✓ Secret / credential exfiltration
  • ✓ Dangerous shell & filesystem operations
  • ✓ Untrusted network calls
  • ✓ Known-malicious package signatures

What it can access

  • ● Network access Used
  • ✓ Filesystem access No
  • ✓ Shell / process execution No
  • ● Environment & secrets Used
  • ✓ Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/mcp-shaun0927-opensafari)

Reliability & compatibility

✓ Security review passed
0 installs to date
— no reviews yet
● 3mo ago

Declared compatibility

Claude CodeClaude DesktopCursorWindsurf

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Opensafari? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

OpenSafari

Smart. Fast. Parallel. iOS Safari automation MCP server via Xcode Simulator.

Headless mobile QA automation — drive real Safari, Flutter, and native iOS apps on Xcode Simulator without stealing your mouse or requiring Simulator.app focus.


How OpenSafari compares

| | OpenSafari | Playwright WebKit | BrowserStack | Manual Testing | |---|:---:|:---:|:---:|:---:| | Engine | Real Safari (Xcode Sim) | Bundled WebKit (approximation) | Real devices (cloud) | Real devices | | Protocol | WebKit Remote Debugging (direct) | Playwright API (wrapper) | Proprietary | N/A | | iOS Fidelity | exact | close but diverges | exact | exact | | Parallel sessions | N simulators | N browsers | limited by plan | 1 device | | Login persistence | built-in (real Safari cookies) | manual | manual | manual | | LLM integration | MCP native | none | none | none | | Cost | free (Xcode) | free | $29+/mo | device cost | | iOS-specific QA | auto-detect (zoom, safe area, keyboard) | none | manual | manual |

> tl;dr — OpenSafari controls the real Safari inside Xcode Simulator via WebKit Remote Debugging Protocol — the same way OpenChrome controls real Chrome via CDP. No middleware, no bundled browsers. Just direct protocol access to the actual Safari.app.


Headless Capabilities

OpenSafari runs fully headless on CI — no display server, no mouse focus, no Simulator.app window required. See [docs/headless-architecture.md](docs/headless-architecture.md) for the full technical design. For Universal Link channel recipes (Notes paste-and-tap, captureLogs, channel matrix), see [docs/recipes/universal-link-channels.md](docs/recipes/universal-link-channels.md).

| Scenario | Query (AX Tree) | Input (Tap/Type) | Headless | Backend | |---|---|---|---|---| | Safari (Web) | ✅ | ✅ | ✅ | WebKit Remote Debug | | Flutter App | ✅ | ✅ | ✅ | FlutterVMInputBackend | | Native iOS App (Xcode ≤ 16) | ✅ | ✅ | ✅ | SimulatorKitHID (Tier 1) / simctl | | Native iOS App (Xcode 26+, element-targeted app_tap_element / app_type_element) | ✅ | ✅ | ✅ | AX-press (Tier 1.5) when the element advertises AXPress — see [docs/headless-architecture.md](docs/headless-architecture.md) | | Native iOS App (Xcode 26+, coordinate app_tap({x,y}) / app_swipe_native) | ✅ | ⚠️ Experimental (opt-in) | ⚠️ | PointerService opt-in via OPENSAFARI_ENABLE_POINTERSERVICE=1 — see #590; AppleScript fallback otherwise (#491) | | WebView in Native | ✅ | ⚠️ Partial | ⚠️ | Bundle metadata (appId|bundleId) requires a newer ios-webkit-debug-proxy build — older proxies fall back to URL-scheme heuristics. HTTPS WebViews (e.g. payment-return pages) must pass bundleId to app_webview_connect for bundle_match classification; without it they default to safari via url_scheme — see #592 |

> ✅ Supported and stable. ⚠️ Partially supported — see linked docs for current status and limitations.

Stability commitments (stable vs opt-in vs experimental) are catalogued in [docs/simhid-ios26-investigation.md#stability-commitments](docs/simhid-ios26-investigation.md#stability-commitments).

Headless input vs other iOS automation tools

| | OpenSafari | Appium | idb | XCUITest | |---|:---:|:---:|:---:|:---:| | Headless native input (no mouse focus, no Simulator.app activation) | ✅ AX-press + SimulatorKit HID (element-targeted tap & keys/buttons headless on Xcode 26+; coordinate-only tap/swipe pending #491) | ❌ (XCUI focus) | ✅ FBSimulatorHID | ❌ | | Works on Xcode 26+ (after simctl io input removal) | ✅ Safari, Flutter, and element-targeted native taps; ⚠️ coordinate-only native tap/swipe pending | ⚠️ driver-dependent | ✅ | ✅ | | Flutter native taps (no OS-level input) | ✅ Dart VM PointerDataPacket | ⚠️ 3rd-party plugin | ❌ | ❌ | | MCP / LLM integration | ✅ native | ❌ | ❌ | ❌ | | Private API dependency | SimulatorKit (documented, sentinel-guarded) | UIAutomation / XCUI | SimulatorKit | none |

> See [docs/private-apis.md](docs/private-apis.md) for the SimulatorKit contract, the daily sentinel CI that detects BC breaks, and the rollback plan if Apple changes symbols.

For CI setup recipes (GitHub Actions, Buildkite, GitLab CI), see [docs/ci-recipes.md](docs/ci-recipes.md).

Long-running MCP sessions are soak-tested nightly — see [memory-soak workflow](.github/workflows/memory-soak.yml).


What is OpenSafari?

Imagine testing your e-commerce site on iPhone 17e, iPhone 17, iPhone 17 Pro Max, and iPad — all at the same time, already logged in, with an AI agent that automatically finds iOS-specific bugs. That's OpenSafari.

You: Check our checkout flow for mobile issues across all iPhone sizes

AI:  [4 parallel simulators, all devices simultaneously]
     iPhone 17e:      ⚠ Credit card input triggers iOS auto-zoom (font-size: 14px)
     iPhone 17:       ✓ Layout OK
     iPhone 17 PM:    ⚠ "Place Order" button only 38×32px (below 44px touch target)
     iPad:            ⚠ Shipping form hidden behind keyboard when focused

     Time: 8s | All screenshots captured and analyzed.

| | Manual QA | OpenSafari | |---|:---:|:---:| | 4-device test | ~30 min | ~10s (parallel) | | Login | Each device, each time | Never (persisted) | | iOS bug detection | Human eye | Automatic (LLM vision) | | Consistency | Varies by tester | Deterministic |


Core Architecture

OpenSafari follows the same direct-protocol philosophy as OpenChrome:

OpenChrome:  CDPClient → Chrome DevTools Protocol → Real Chrome
OpenSafari:  SafariClient → WebKit Remote Debugging Protocol → Real Safari in Simulator

No middleware. No bundled browsers. Direct connection.

Claude Code / AI Agent (MCP Client)
    │
    │  JSON-RPC (stdio / HTTP)
    ▼
┌─────────────────────────────────────┐
│         OpenSafari MCP Server       │
│                                     │
│  ┌─────────────┐  ┌──────────────┐  │
│  │ Simulator   │  │ Safari       │  │
│  │ Manager     │  │ Client       │  │
│  │ (simctl)    │  │ (WebKit      │  │
│  │             │  │  Protocol)   │  │
│  └──────┬──────┘  └──────┬───────┘  │
│         │                │          │
│    boot/shutdown    navigate/click   │
│    rotate/appear    screenshot       │
│    multi-device     DOM/JS/cookies   │
│         │                │          │
│  ┌──────▼────────────────▼───────┐  │
│  │     Xcode Simulator(s)       │  │
│  │  ┌────────┐  ┌────────┐      │  │
│  │  │ iPhone │  │ iPhone │ ...  │  │
│  │  │ SE     │  │ 16 PM  │      │  │
│  │  │ Safari │  │ Safari │      │  │
│  │  └────────┘  └────────┘      │  │
│  └───────────────────────────────┘  │
│                                     │
│  ┌─────────────────────────────┐    │
│  │  Shared Infrastructure      │    │
│  │  (from OpenChrome)          │    │
│  │  • Security (sanitizer,     │    │
│  │    domain guard, audit)     │    │
│  │  • Watchdog (event loop,    │    │
│  │    disk, health endpoint)   │    │
│  │  • Orchestration (workflow  │    │
│  │    engine, parallel workers)│    │
│  │  • Session persistence      │    │
│  └─────────────────────────────┘    │
└─────────────────────────────────────┘

Key Features

1. Real Safari, Real Bugs

OpenSafari controls the actual Safari.app inside Xcode Simulator via WebKit Remote Debugging Protocol — not a bundled approximation. Every iOS-specific quirk is faithfully reproduced:

  • iOS auto-zoom on inputs with font-size elements with font-size unless you intentionally pass --http-insecure-local for local-only testing. Provide the token with OPENSAFARIHTTPTOKEN or --http-token`; OpenSafari never prints the token value.

Browser CORS for /mcp is restricted to local origins (localhost, 127.0.0.1, ::1) plus any comma-separated origins passed with --http-allow-origin. Use --http-host only when you intentionally need a non-loopback bind.

HTTP mode also blocks high-risk MCP tools that execute page/app code or move authentication material: javascript, flutter_evaluate, auth_save, auth_restore, and cookies. Stdio mode is unchanged. To intentionally expose those tools over HTTP, start with --http-enable-high-risk-tools or set OPENSAFARI_HTTP_ENABLE_HIGH_RISK_TOOLS=1; allowed and blocked high-risk HTTP calls are audit-logged with sensitive arguments redacted.

OPENSAFARI_HTTP_TOKEN="$OPENSAFARI_HTTP_TOKEN" opensafari serve --http 3100
curl -H "Authorization: Bearer $OPENSAFARI_HTTP_TOKEN" \
  -H "Content-Type: application/json" \
  http://127.0.0.1:3100/mcp \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'

MCP Client Configuration

// Claude Code: .mcp.json
{
  "mcpServers": {
    "opensafari": {
      "command": "npx",
      "args": ["-y", "opensafari-mcp", "serve"]
    }
  }
}
// Claude Desktop: claude_desktop_config.json
{
  "mcpServers": {
    "opensafari": {
      "command": "npx",
      "args": ["-y", "opensafari-mcp", "serve", "--all-tools"]
    }
  }
}

Tool Tiers

Tools are organized into 3 tiers for progressive disclosure. The default surface is Tier 1 (12 tools, ~4.5 KB of schema); higher tiers are opt-in so the tools/list payload stays small for MCP clients that load every tool schema into model context.

| Tier | Tools | Access | |------|-------|--------| | Tier 1 | navigate, screenshot, click, type, scroll, readpage, querydom, javascript, cookies, deviceboot, deviceshutdown, diagnose | Default | | Tier 2 | inspect, waitfor, press, swipe, longpress, devicelist, app native tools, flutter tools, qa detectors, network tools | OPENSAFARI_TOOL_TIER=2 or setTier(2) | | Tier 3 | authsave, authrestore, authlist, batch, workflow, qafullaudit, crossviewportcompare | --all-tools or OPENSAFARI_TOOL_TIER=3 |

Every registered tool must have an explicit entry in src/config/tool-tiers.ts; tools without one fall back to Tier 3 (enforced by tests/unit/tool-tier-drift.test.ts).

> Migration note (0.7.x): earlier releases exposed Tier 2 (132 tools, ~22k tokens of schema) by default. If you relied on Tier 2 tools being listed without configuration, set OPENSAFARI_TOOL_TIER=2 in the server environment or pass --all-tools.


Programmatic API

import { createServer } from 'opensafari-mcp';

// Start with the default Tier 1 tool surface over stdio
await createServer();

// Expose all tool tiers immediately (same as opensafari serve --all-tools)
await createServer({ allTools: true });

// Or start with HTTP transport
await createServer({
  transport: 'http',
  port: 3100,
  authToken: process.env.OPENSAFARI_HTTP_TOKEN,
});

WebKitClient

Direct WebKit protocol access for custom automation:

import { WebKitClient } from 'opensafari-mcp';

const client = new WebKitClient({ host: 'localhost', port: 9322 });
await client.connect({ retries: 5, retryDelay: 2000 });

// Navigate and evaluate
await client.navigate({ url: 'https://example.com', waitUntil: 'load' });
const title = await client.evaluate('document.title');

// Screenshot (returns PNG buffer)
const png = await client.screenshot();

// Cookies
const cookies = await client.getCookies();
await client.setCookies([{ name: 'key', value: 'val', domain: '.example.com',
  path: '/', expires: -1, httpOnly: false, secure: false }]);

// DOM interaction
await client.click('#submit-btn');
await client.type('#email-input', 'user@example.com');

await client.disconnect();

SimulatorManager

Programmatic simulator lifecycle control:

import { SimulatorManager } from 'opensafari-mcp';

const manager = new SimulatorManager();

// Boot a device
const device = await manager.boot('iPhone 17 Pro');
console.log(device.udid, device.state); // "XXXX-..." "Booted"

// Open Safari
await manager.openUrl(device.udid, 'https://example.com');

// List booted devices
const booted = await manager.listBooted();

// Shutdown
await manager.shutdown(device.udid);

Requirements

  • macOS (Xcode Simulator is macOS only)
  • Xcode with iOS Simulator runtime installed
  • Node.js >= 18
  • ios-webkit-debug-proxy — brew install ios-webkit-debug-proxy

WebInspector Proxy Configuration

OpenSafari uses ios_webkit_debug_proxy to bridge WebKit Remote Debugging from Xcode Simulator. The proxy is auto-started by the device_boot tool — no manual setup is needed in most cases.

Default Ports

| Port | Purpose | |------|---------| | 9321 | Device list (HTML) — serves the proxy's device listing page | | 9322 | Device connection (JSON) — WebKit debugging targets for connected simulators |

Port 9322 is deliberately offset from Chrome DevTools (9222) so OpenSafari and OpenChrome can run simultaneously.

Custom Port

Set the OPENSAFARI_PROXY_PORT environment variable to use a different device port:

# Use port 9500 instead of the default 9322
OPENSAFARI_PROXY_PORT=9500 opensafari serve

Port resolution order:

  1. Explicit port option (programmatic use)
  2. OPENSAFARI_PROXY_PORT environment variable
  3. Default: 9322

Multi-Session Usage

Multiple Claude Code sessions can share the same proxy. When a session detects a healthy proxy already running on its target port, it reuses it instead of starting a new one. When the owning session exits, only its own proxy is terminated — other sessions' proxies remain unaffected.


Input Backend Selection

OpenSafari dispatches native input (app_tap, app_swipe_native, app_scroll_native, app_double_tap, app_type_text, app_key_input) through a 5-tier fallback chain and surfaces the selected path in each tool result via a backend field. Coordinate and element-targeted tap tools also surface whether the interaction was verified by a post-action AX-tree check. Transport success alone is no longer treated as interaction success when no observable UI effect can be confirmed. app_tap_element now applies the same contract after it falls back from ax-press to a coordinate backend: callers get verified: false with effect: "verification_unavailable" when the AX proof is unavailable, or a typed TAP_NO_EFFECT result when the UI stays unchanged after the dispatched tap.

| Tier | Backend | Identifier | Headless? | When used | |------|---------|------------|-----------|-----------| | 0 | FlutterVMInputBackend | flutter-vm | Yes | Flutter app reachable over Dart VM Service + DDS | | 1 | SimulatorKitHIDInputBackend | simhid | Yes | Any app — sim-hid-bridge resolves and SimulatorKit.framework loads (covers Xcode 26+) | | 2 | SimctlInputBackend | simctl | Yes | Xcode ≤16 legacy path (where simctl io input is still available) | | 3 | WebKitInputBackend | webkit | Yes | Xcode 26+ with an active Safari / WebView connection | | 4 | AppleScriptInputBackend | applescript | No | Opt-in only — moves the mouse cursor and activates Simulator.app |

See [docs/headless-architecture.md](docs/headless-architecture.md) for the decision flowchart and the full scenario matrix. Tool responses also include _meta: { backendKind, headless, deviceId } so CI can assert _meta.headless === true.

Raw bridge consumers can now ask for the same foreground diagnostics directly:

  • dist/ax-bridge context --device [--expect-bundle ] [--require-match true]
  • dist/sim-hid-bridge context [--expect-bundle ] [--require-match true]

dist/sim-hid-bridge tap|swipe also appends classification, verified, frontmost, and `expectedBundle

…

Source & license

This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.