SPB Git

Repository Public Research preview

README.md 6.7 KB

# safari-mcp

An MCP server that lets an agent use Safari on your Mac through AppleScript: open pages, read them, click, fill forms, attach files, download files with the page's own sign-in, take screenshots.

Why Safari rather than a headless browser: it is the browser you are already signed in to. The agent can read a page behind your university proxy or download a report from an internal tool without ever seeing a password. That is also why this server is built defensively.

# What it will and will not do

  • Its own window. The server opens a dedicated Safari window and works only there. Tabs you already had open are invisible to it — it cannot list, read, switch or close them — unless you start it with SAFARI_MCP_SCOPE=all.
  • Domain policy. SAFARI_MCP_ALLOW_DOMAINS restricts it to named sites; SAFARI_MCP_BLOCK_DOMAINS excludes sites and wins over the allow list. The tab's current address is re-checked before every tool call, because pages redirect.
  • Secrets. It never reads cookies, browser storage or saved passwords. Password, card and one-time-code fields are redacted in every output, and typing into them is refused unless the call carries confirm_sensitive: true.
  • No arbitrary script by default. evaluate_js is off unless you set SAFARI_MCP_ALLOW_EVAL=1.
  • Files stay in one directory. Uploads are read from, and downloads and screenshots written to, SAFARI_MCP_FILE_ROOT only. Nothing is overwritten.
  • Untrusted content. Tool results carry a notice that page text is data, not instructions. This reduces the risk of prompt injection; it does not remove it. Use the allow list for anything unattended.

It cannot stop an agent from doing something unwise that you allowed. submit_form and click act with your identity on the sites you let it visit.

# Requirements

  • macOS with Safari, Node.js 20 or newer.
  • Safari → Settings → Advanced → "Show features for web developers", then Settings → Developer → Allow JavaScript from Apple Events.
  • On first use macOS asks whether the app running the server (your terminal, Claude Code, …) may control Safari: allow it (System Settings → Privacy & Security → Automation).
  • For screenshot only: Screen Recording permission for that same app.

# Install

bash
skills download @spboucher/safari-mcp --extract ./safari-mcp   # or download the archive and verify its SHA-256
cd safari-mcp
npm ci
npm test        # offline tests; does not touch Safari
bash
# Claude Code — restricted to two sites, files under ~/Downloads/agent
claude mcp add --transport stdio \
  --env SAFARI_MCP_ALLOW_DOMAINS=example.org,intranet.example.com \
  --env SAFARI_MCP_FILE_ROOT=$HOME/Downloads/agent \
  safari -- node /abs/path/safari-mcp/server.mjs
toml
# Codex: ~/.codex/config.toml
[mcp_servers.safari]
command = "node"
args = ["/abs/path/safari-mcp/server.mjs"]
[mcp_servers.safari.env]
SAFARI_MCP_ALLOW_DOMAINS = "example.org"
json
// Cursor: .cursor/mcp.json
{ "mcpServers": { "safari": { "type": "stdio", "command": "node", "args": ["/abs/path/safari-mcp/server.mjs"], "env": { "SAFARI_MCP_ALLOW_DOMAINS": "example.org" } } } }
Variable Effect Default
SAFARI_MCP_ALLOW_DOMAINS Only these sites (and subdomains) any http(s) site
SAFARI_MCP_BLOCK_DOMAINS Never these sites none
SAFARI_MCP_FILE_ROOT Directory for uploads, downloads, screenshots working directory
SAFARI_MCP_SCOPE=all Also see and act on tabs you opened own window only
SAFARI_MCP_ALLOW_EVAL=1 Enable evaluate_js off
SAFARI_MCP_ALLOW_FILE_URLS=1 Allow file:// pages off
SAFARI_MCP_DIALOGS=dismiss Answer page confirm() with "cancel" accept

# Tools

Group Tools
Session safari_status, open_url, navigate (goto / back / forward / reload), list_tabs, select_tab, close_tab, resize_window
Read page_info, snapshot, read_text, read_html, query_elements, list_links, extract_tables, list_forms
Act click, hover, fill, fill_form, select_option, set_checked, press_key, submit_form, scroll, wait_for
Files and data upload_file, download, fetch_text, screenshot
Opt-in evaluate_js

The usual loop is open_url → snapshot → act on a ref → wait_for / snapshot. Elements can also be addressed by visible text, field label or CSS selector; when several match, the error lists the candidates. Searches go through open shadow roots and same-origin iframes.

# How it stays reliable

  • Page readiness. Safari reports a tab's destination address as soon as a navigation starts, while the old document still answers scripts. The server waits until the document's own address has caught up and it is fully loaded, so it never acts on the page being left.
  • Framework inputs. Fields are set through the native value setter with input and change events, which React- and Vue-style pages observe.
  • No injection. Scripts are fixed; every value travels as an argument to osascript, never pasted into AppleScript or JavaScript source. No shell is ever invoked.
  • Dialogs. alert, confirm and prompt are intercepted and reported, so a page cannot freeze the session.
  • Downloads and uploads move bytes through the page in chunks: fetch with the page's cookies for downloads, a constructed File for uploads — no system file dialog.

# Verified

test/safari.test.mjs drives the real Safari through the MCP protocol on a local fixture site: navigation, redirects, history, tabs, late content, shadow DOM, iframes, ambiguous targets, dialogs, a framework-style form, the sensitive-field guard and redaction, file upload (bytes checked by hash), a cookie-protected download (checked by hash), same-origin enforcement, screenshots, the domain policy applied to a page that moves, stale refs — and finally that the user's own windows are exactly as they were. test/realweb.test.mjs repeats a read-only flow on example.com and Wikipedia.

# Limits

  • Tested on Safari 27.0 / macOS 27.0 only.
  • Cross-origin iframes cannot be read or driven: open their URL in a tab.
  • Events are synthetic: pages react, the browser does not (no shortcuts, no autofill), and some sites reject them.
  • A dialog raised before the server first touches a page still blocks until dismissed by hand.
  • Private windows, Reader, PDFs displayed in a tab and browser settings are not reachable.
  • Uploads up to 20 MB, downloads up to 50 MB.
  • The server takes no part in solving CAPTCHAs or bypassing bot checks.
  • While it runs, a Safari window is visible on your screen. It is not brought to the front unless select_tab is called with bring_to_front.
Files main
Menu

Theme