Install
$ agentstack add mcp-erwinzhang7-safari-mcp ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo 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 No
- ✓ 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.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
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 →About
Safari MCP
A native Model Context Protocol server that lets an MCP client (e.g. Claude Code) drive Safari on macOS — navigate, read, click, type, run JS, take screenshots, do screenshot-driven mouse/keyboard control, upload files, and capture console/network traffic. The Safari equivalent of a "browser in your agent," built native with zero runtime dependencies.
Two complementary halves:
- Native driver (
driver/) — a single Swift binary that speaks MCP over stdio and drives
Safari via AppleScript (do JavaScript), CoreGraphics (screencapture, CGEvent), and NSPasteboard. Hand-rolled JSON-RPC and WebSocket — no SDK, no ws, no Node.
- Safari extension (
extension/) — a vanilla-JS MV3 extension whose only job is to capture
the current site's console + network and stream it to the driver over a loopback WebSocket. Its popup is a small status panel (bridge health, capture state, event counts, and per-site permission management). No API key, no embedded chat.
MCP client ──stdio(MCP)──▶ safari-mcp (Swift) ──AppleScript/CGEvent──▶ Safari ──▶ pages
▲
│ ws://127.0.0.1:8787 (console/network telemetry)
│
Safari extension (MV3) ──▶ status + permission popup
Why this shape
Safari's extension model is hostile to silent automation (per-site prompts, sandboxing, unsigned-extension resets). So the heavy lifting is native (AppleScript + CoreGraphics), which needs only one-time macOS permission grants — and the extension stays tiny, doing the one thing native automation can't easily do: capture console/network from inside the page. See RESEARCH.md for the verified platform constraints and PLAN.md for the design.
Requirements
- macOS with Safari, and Xcode (for building/installing the extension).
- A Swift toolchain (
swift), Python 3, and Node (for the test runners only). - A code-signing identity. A stable identity (Apple Development or Developer ID) is strongly
recommended so macOS permission grants persist across rebuilds.
Build & install
Quick start (build the driver and register it globally in one command):
# Optional but recommended: a stable signing identity so macOS permission grants persist.
export SMCP_SIGN_IDENTITY="Apple Development: you@example.com"
make install # builds + signs the driver, registers it as a global MCP server
make build-extension # generates the Xcode project; then build/run it once (step 2 below)
Then restart your MCP client and finish the Safari steps below. The two halves in detail:
1. Driver
# Signs with $SMCP_SIGN_IDENTITY (defaults to ad-hoc "-"). Set it to a stable identity so the
# macOS Automation grant sticks across rebuilds, e.g. SMCP_SIGN_IDENTITY="Apple Development: you@example.com".
./scripts/build-driver.sh release
# Register it (the script prints the exact binary path; `make install` does this for you):
claude mcp add safari --scope user -- "$(cd driver && swift build -c release --show-bin-path)/safari-mcp"
One-time macOS grants (per the signing identity):
- Approve the Automation prompt for Safari the first time a tool runs
(System Settings ▸ Privacy & Security ▸ Automation).
- Safari ▸ Settings ▸ Advanced ▸ Show features for web developers, then
Develop ▸ Allow JavaScript from Apple Events.
- For
safari_screenshot: Screen Recording permission (prompted on first use). - For
safari_computerwithreal: true: Accessibility permission (prompted on first use).
2. Extension
./scripts/build-extension.sh # converts extension/ → an Xcode project under app/
open app/SafariMCP/SafariMCP.xcodeproj
In Xcode: set your signing Team on both targets (a free Apple ID works), then Product ▸ Run to install. In Safari: Develop ▸ Allow Unsigned Extensions (resets each launch unless the extension is signed by a real Developer ID), Settings ▸ Extensions ▸ enable Safari MCP, then grant a site capture via the toolbar popup. extension/ is the source of truth; app/ is a regenerated build artifact (gitignored) — re-run build-extension.sh after editing extension/.
> Driver changes require a fresh MCP-client session to load (the server set is fixed at session > start). Extension changes hot-reload in Safari, but reinstalling the app can disable the > extension — re-enable it in Settings ▸ Extensions if so.
Per-site permissions
Read-only tools (list tabs, read page text/DOM, screenshots, console/network) work anywhere. Anything that acts on a page or runs JS (click, type, set value, scroll, run_js, navigate, reload, computer-use input, storage writes, file upload) is gated per origin:
- The first time the agent acts on an unauthorized site, the driver pops a native Allow / Deny
dialog. Allow adds the origin to a persistent allowlist; the agent can never self-authorize.
- Grant/revoke any time from the extension popup. Capture is folded into the same auth: a site
must be allowed before console/network stream.
MCP tools
| Tool | What it does | |---|---| | safari_list_tabs | List all tabs across windows (with stable tabIds) | | safari_navigate | Open a URL in a tab (waits for load) | | safari_new_tab / safari_close_tab / safari_activate_tab | Tab management (new tabs open in the background) | | safari_reload / safari_back / safari_forward | History navigation | | safari_resize_window | Resize a window (responsive testing) | | safari_get_page_text | Visible page text (innerText) | | safari_read_page | Snapshot interactive elements with stable refs | | safari_find | Find elements by CSS selector or visible text | | safari_inspect | Computed styles, bounding box, attributes, outerHTML | | safari_click / safari_type / safari_set_value | Interact with elements by ref / selector / text | | safari_scroll | Scroll by direction/amount, or a ref/selector into view | | safari_wait_for | Wait for a selector / text / network-idle (with timeout) | | safari_run_js | Run arbitrary JS, return its result (escape hatch) | | safari_screenshot | PNG of a Safari window — works on background/occluded windows | | safari_computer | Screenshot-driven mouse/keyboard control with an on-page cursor overlay | | safari_upload | Upload a local file into a file input / drop zone (no native picker) | | safari_clipboard | Read/write the macOS clipboard (copy/paste workflows) | | safari_storage | Read/write localStorage, sessionStorage, cookies | | safari_read_console / safari_read_network | Captured console / network, filterable (needs the extension) | | safari_bridge_status | Is the extension connected? how much is buffered? | | safari_permissions | List or revoke per-site action permissions |
Notes:
- Most tools take an optional
tabId(fromsafari_list_tabs); omit for the active tab. Tabs in
non-front and occluded windows are addressed reliably and driven in the background.
safari_computerdefaults to DOM-synthesized input dispatched inside the target tab — it
works in the background, needs no Accessibility grant, and draws a cursor/click overlay. Set real: true for true OS-level CGEvent input (needs the window frontmost + Accessibility) for sites that reject synthetic events.
safari_read_networkcaptures request/response headers and (for JSON/text responses) bodies up
to 16KB; sensitive headers are redacted unless includeSecrets: true.
Tests
make test # runs both self-tests (neither needs Safari)
protocol_selftest.py— MCP protocol layer (initialize, tools/list, tools/call, errors).bridge_selftest.mjs— the console/network bridge, with a Node WS client standing in for the
extension.
Repo layout
driver/ Swift package — the native MCP driver
extension/ MV3 Safari extension source (the single source of truth)
scripts/ build-driver.sh, build-extension.sh, patch-xcodeproj.py, make-icons.py
app/ generated Xcode project (gitignored build artifact)
PLAN.md architecture; RESEARCH.md verified platform findings
License
Apache License 2.0 — see [LICENSE](LICENSE).
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: erwinzhang7
- Source: erwinzhang7/safari-mcp
- License: Apache-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.