Repository Public Research preview
1# safari-mcp23An MCP server that lets an agent use **Safari on your Mac** through AppleScript: open pages, read4them, click, fill forms, attach files, download files with the page's own sign-in, take5screenshots.67Why Safari rather than a headless browser: it is the browser you are already signed in to. The8agent can read a page behind your university proxy or download a report from an internal tool9without ever seeing a password. That is also why this server is built defensively.1011## What it will and will not do1213- **Its own window.** The server opens a dedicated Safari window and works only there. Tabs you14 already had open are invisible to it — it cannot list, read, switch or close them — unless you15 start it with `SAFARI_MCP_SCOPE=all`.16- **Domain policy.** `SAFARI_MCP_ALLOW_DOMAINS` restricts it to named sites;17 `SAFARI_MCP_BLOCK_DOMAINS` excludes sites and wins over the allow list. The tab's current18 address is re-checked before every tool call, because pages redirect.19- **Secrets.** It never reads cookies, browser storage or saved passwords. Password, card and20 one-time-code fields are redacted in every output, and typing into them is refused unless the21 call carries `confirm_sensitive: true`.22- **No arbitrary script by default.** `evaluate_js` is off unless you set `SAFARI_MCP_ALLOW_EVAL=1`.23- **Files stay in one directory.** Uploads are read from, and downloads and screenshots written24 to, `SAFARI_MCP_FILE_ROOT` only. Nothing is overwritten.25- **Untrusted content.** Tool results carry a notice that page text is data, not instructions.26 This reduces the risk of prompt injection; it does not remove it. Use the allow list for27 anything unattended.2829It cannot stop an agent from doing something unwise that you allowed. `submit_form` and `click`30act with your identity on the sites you let it visit.3132## Requirements3334- macOS with Safari, Node.js 20 or newer.35- Safari → Settings → Advanced → "Show features for web developers", then36 Settings → Developer → **Allow JavaScript from Apple Events**.37- On first use macOS asks whether the app running the server (your terminal, Claude Code, …) may38 control Safari: allow it (System Settings → Privacy & Security → Automation).39- For `screenshot` only: Screen Recording permission for that same app.4041## Install4243```bash44skills download @spboucher/safari-mcp --extract ./safari-mcp # or download the archive and verify its SHA-25645cd safari-mcp46npm ci47npm test # offline tests; does not touch Safari48```4950```bash51# Claude Code — restricted to two sites, files under ~/Downloads/agent52claude mcp add --transport stdio \53 --env SAFARI_MCP_ALLOW_DOMAINS=example.org,intranet.example.com \54 --env SAFARI_MCP_FILE_ROOT=$HOME/Downloads/agent \55 safari -- node /abs/path/safari-mcp/server.mjs56```5758```toml59# Codex: ~/.codex/config.toml60[mcp_servers.safari]61command = "node"62args = ["/abs/path/safari-mcp/server.mjs"]63[mcp_servers.safari.env]64SAFARI_MCP_ALLOW_DOMAINS = "example.org"65```6667```json68// Cursor: .cursor/mcp.json69{ "mcpServers": { "safari": { "type": "stdio", "command": "node", "args": ["/abs/path/safari-mcp/server.mjs"], "env": { "SAFARI_MCP_ALLOW_DOMAINS": "example.org" } } } }70```7172| Variable | Effect | Default |73|---|---|---|74| `SAFARI_MCP_ALLOW_DOMAINS` | Only these sites (and subdomains) | any http(s) site |75| `SAFARI_MCP_BLOCK_DOMAINS` | Never these sites | none |76| `SAFARI_MCP_FILE_ROOT` | Directory for uploads, downloads, screenshots | working directory |77| `SAFARI_MCP_SCOPE=all` | Also see and act on tabs you opened | own window only |78| `SAFARI_MCP_ALLOW_EVAL=1` | Enable `evaluate_js` | off |79| `SAFARI_MCP_ALLOW_FILE_URLS=1` | Allow `file://` pages | off |80| `SAFARI_MCP_DIALOGS=dismiss` | Answer page `confirm()` with "cancel" | accept |8182## Tools8384| Group | Tools |85|---|---|86| Session | `safari_status`, `open_url`, `navigate` (goto / back / forward / reload), `list_tabs`, `select_tab`, `close_tab`, `resize_window` |87| Read | `page_info`, `snapshot`, `read_text`, `read_html`, `query_elements`, `list_links`, `extract_tables`, `list_forms` |88| Act | `click`, `hover`, `fill`, `fill_form`, `select_option`, `set_checked`, `press_key`, `submit_form`, `scroll`, `wait_for` |89| Files and data | `upload_file`, `download`, `fetch_text`, `screenshot` |90| Opt-in | `evaluate_js` |9192The usual loop is `open_url` → `snapshot` → act on a `ref` → `wait_for` / `snapshot`. Elements93can also be addressed by visible text, field label or CSS selector; when several match, the error94lists the candidates. Searches go through open shadow roots and same-origin iframes.9596## How it stays reliable9798- **Page readiness.** Safari reports a tab's destination address as soon as a navigation starts,99 while the old document still answers scripts. The server waits until the document's own address100 has caught up and it is fully loaded, so it never acts on the page being left.101- **Framework inputs.** Fields are set through the native value setter with `input` and `change`102 events, which React- and Vue-style pages observe.103- **No injection.** Scripts are fixed; every value travels as an argument to `osascript`, never104 pasted into AppleScript or JavaScript source. No shell is ever invoked.105- **Dialogs.** `alert`, `confirm` and `prompt` are intercepted and reported, so a page cannot106 freeze the session.107- **Downloads and uploads** move bytes through the page in chunks: fetch with the page's cookies108 for downloads, a constructed `File` for uploads — no system file dialog.109110## Verified111112`test/safari.test.mjs` drives the real Safari through the MCP protocol on a local fixture site:113navigation, redirects, history, tabs, late content, shadow DOM, iframes, ambiguous targets,114dialogs, a framework-style form, the sensitive-field guard and redaction, file upload (bytes115checked by hash), a cookie-protected download (checked by hash), same-origin enforcement,116screenshots, the domain policy applied to a page that moves, stale refs — and finally that the117user's own windows are exactly as they were. `test/realweb.test.mjs` repeats a read-only flow on118example.com and Wikipedia.119120## Limits121122- Tested on Safari 27.0 / macOS 27.0 only.123- Cross-origin iframes cannot be read or driven: open their URL in a tab.124- Events are synthetic: pages react, the browser does not (no shortcuts, no autofill), and some125 sites reject them.126- A dialog raised before the server first touches a page still blocks until dismissed by hand.127- Private windows, Reader, PDFs displayed in a tab and browser settings are not reachable.128- Uploads up to 20 MB, downloads up to 50 MB.129- The server takes no part in solving CAPTCHAs or bypassing bot checks.130- While it runs, a Safari window is visible on your screen. It is not brought to the front unless131 `select_tab` is called with `bring_to_front`.132