Sandbox
@achiya-automation/safari-mcp

Safari MCP server for browser automation in AI agents

Safari MCP connects an MCP client to the real Safari browser on macOS. It uses a Safari bridge extension, AppleScript, and a native helper to let agents navigate pages, fill forms, inspect the DOM, capture screenshots, and read cookies or network activity without launching Chrome or Playwright. It is built for local use and keeps your existing logins and sessions. The server also supports background operation, tab ownership guards, and a shared HTTP daemon mode for multiple agent sessions.

183 stars26 forksJavaScriptUpdated 7d ago
Who it's for

Builders who want their agent to browse, test, and fill forms inside their existing Safari session on macOS.

What it delivers

You can let an agent use your logged-in Safari tabs instead of setting up a separate browser session every time.

What it does

Native Safari control

Drives the real Safari app on macOS through AppleScript and a bridge extension.

97 browser tools

Includes navigation, clicking, forms, screenshots, tabs, waits, JavaScript, storage, network, console, and data extraction tools.

Logged-in sessions

Keeps your existing Safari cookies, logins, and profile state instead of starting fresh in headless Chrome.

Background operation

Lets Safari stay in the background so the agent can work without taking focus unless you opt in.

Shared HTTP daemon mode

Can run one daemon for multiple sessions, with per-session tab ownership and isolation.

Native input fallback

Provides CGEvent-based click, hover, typing, and keyboard actions for sites that block normal DOM events.

WebKit validation tools

Adds checks for viewport, safe areas, PWA readiness, and Safari/WebKit compatibility.

How to get it

  1. 1Run
    npx safari-mcp
  2. 2That's it — no global install needed. Or install permanently
    npm install -g safari-mcp
  3. 3All clients run Safari MCP the same way — npx safari-mcp. Pick your editor
    claude mcp add safari -- npx safari-mcp
  4. 4Run
    brew install achiya-automation/tap/safari-mcp
  5. 5Run
    git clone https://github.com/achiya-automation/safari-mcp.git
    cd safari-mcp && npm install
  6. 6The recommended pattern for AI agents using Safari MCP
    1. safari_snapshot        → Get page state (accessibility tree)
    2. safari_click/fill/...  → Interact with elements by ref
    3. safari_snapshot        → Verify the result

README

Safari MCP Server — 97 native browser automation tools for AI agents on macOS

🦁 Safari MCP

The browser for your coding agent.

Your real Safari, logged in — no Chrome, no heat, no headless.

npm version npm downloads License: MIT macOS

Install in VS Code · VS Code Insiders · Install in Cursor

97 tools · No Chrome/Puppeteer/Playwright needed · ~5ms per command · 60% less CPU than Chrome

Quick Start · All 97 Tools · Examples · Why Safari MCP? · Architecture · Changelog

Safari MCP Demo

❌ Without Safari MCP

Your AI agent needs to browse. So it either:

  • Spins up Chromium via Playwright — with no logins, no cookies, no sessions
  • Uses Chrome DevTools MCP — and melts your fan running a second browser
  • Relies on headless scrapers — blocked by Cloudflare, reCAPTCHA, and bot detection

✅ With Safari MCP

Your AI drives the Safari you're already logged into — Gmail, GitHub, Ahrefs, Slack, banking.

Native WebKit. ~60% less CPU. Background operation. 97 tools. One npx command. macOS only.

📰 Featured on freeCodeCamp: How to Connect Your AI Coding Agent to a Browser on macOS · HackerNoon: Reverse-Engineering React, Shadow DOM, and CSP

🍎 Apple shipped an official Safari MCP (July 2026 — Safari Technology Preview 247+ and the Safari 27 beta). It's built on safaridriver for isolated debugging sessions. safari-mcp drives the real Safari you're already logged into — on the stable Safari that ships with macOS today, with 97 tools. See the full comparison below.


Highlights

  • 97 tools — navigation, clicks, forms, screenshots, network, storage, accessibility, and more
  • Zero heat — native WebKit on Apple Silicon, ~60% less CPU than Chrome
  • Your real browser — keeps all logins, cookies, sessions (Gmail, GitHub, Ahrefs, etc.)
  • Background operation — Safari stays in the background, no window stealing
  • No browser dependencies — no Puppeteer, no Playwright, no WebDriver, no Chrome
  • Persistent process — reuses a single osascript process (~5ms per command vs ~80ms)
  • Framework-compatible — React, Vue, Angular, Svelte form filling via native setters

In users' own words

Not solicited testimonials — quotes lifted from the public issue tracker, each linked to the thread it came from.

"I run multiple Pi sessions/subagents against my normal Safari profile in parallel. Sharing its cookies and logins is intentional." — @maxim, on running concurrent agents against a real browser

"The server has a deliberate tab-ownership model … the code is careful about this, and for the default case that's the right safety posture." — @turner-moore, on why the guards refuse to touch your tabs

"Direct local validation from the package: Safari MCP doctor 6/6." — @jrepp, who found and fixed a queue-alignment bug in the focus helper


Quick Start

Prerequisites

  • macOS (any version with Safari)
  • Node.js 20+
  • Safari → Settings → Advanced → Show features for web developers
  • Safari → Develop → Allow JavaScript from Apple Events

Install (one command)

npx safari-mcp

That's it — no global install needed. Or install permanently:

npm install -g safari-mcp

Configure your MCP client

All clients run Safari MCP the same way — npx safari-mcp. Pick your editor:

Claude Code
claude mcp add safari -- npx safari-mcp

Or edit ~/.mcp.json:

{
  "mcpServers": {
    "safari": {
      "command": "npx",
      "args": ["safari-mcp"]
    }
  }
}
Claude Desktop

Edit ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "safari": {
      "command": "npx",
      "args": ["safari-mcp"]
    }
  }
}

Restart Claude Desktop after saving.

Cursor

One-click: Install in Cursor

Or edit .cursor/mcp.json in your project:

{
  "mcpServers": {
    "safari": {
      "command": "npx",
      "args": ["safari-mcp"]
    }
  }
}
VS Code / VS Code Insiders

One-click: Install in VS Code

Or edit .vscode/mcp.json:

{
  "servers": {
    "safari": {
      "type": "stdio",
      "command": "npx",
      "args": ["safari-mcp"]
    }
  }
}
Windsurf

Edit .windsurf/mcp.json in your project (or ~/.codeium/windsurf/mcp_config.json globally):

{
  "mcpServers": {
    "safari": {
      "command": "npx",
      "args": ["safari-mcp"]
    }
  }
}
Cline

Open Cline in VS Code → click the MCP icon → Edit MCP Settings → add:

{
  "mcpServers": {
    "safari": {
      "command": "npx",
      "args": ["safari-mcp"]
    }
  }
}
Continue

Edit ~/.continue/config.yaml (or .continue/config.yaml in workspace):

mcpServers:
  - name: safari
    command: npx
    args:
      - safari-mcp
Goose

Edit ~/.config/goose/config.yaml:

extensions:
  safari:
    name: safari
    type: stdio
    cmd: npx
    args:
      - safari-mcp
    enabled: true
LM Studio

Open LM Studio → SettingsMCP ServersAdd Server:

  • Name: safari
  • Command: npx
  • Args: safari-mcp
Zed

Open Zed → Settings → search for "Context Servers" and add:

{
  "context_servers": {
    "safari": {
      "command": {
        "path": "npx",
        "args": ["safari-mcp"]
      }
    }
  }
}
Alternative: Homebrew
brew install achiya-automation/tap/safari-mcp
Alternative: from source
git clone https://github.com/achiya-automation/safari-mcp.git
cd safari-mcp && npm install

Usage Workflow

The recommended pattern for AI agents using Safari MCP:

1. safari_snapshot        → Get page state (accessibility tree)
2. safari_click/fill/...  → Interact with elements by ref
3. safari_snapshot        → Verify the result

Element targeting — tools accept multiple targeting strategies:

StrategyExampleBest for
CSS selector#login-btn, .submitUnique elements
Visible text"Sign In", "Submit"Buttons, links
Coordinatesx: 100, y: 200Canvas, custom widgets
Ref from snapshotref: "e42"Any element from accessibility tree

Tip: Start with safari_snapshot to get element refs, then use refs for precise targeting. This is faster and more reliable than CSS selectors.


Running several agents at once

Multiple agents or subagents driving one Safari at the same time will fight over the active tab — unless you run them against a shared HTTP daemon instead of one process per client:

SAFARI_MCP_HTTP=1 SAFARI_MCP_HTTP_PORT=9225 npx safari-mcp

Then point every client at it:

{ "mcpServers": { "safari-mcp": { "type": "http", "url": "http://127.0.0.1:9225/mcp" } } }

One daemon, many sessions — and each session gets its own tab state. The server keys activeTabIndex, the ownership flag and a unique tab marker off the MCP session id, so session A physically cannot read or steer session B's tab.

Two properties make this safe rather than merely tidy:

  • Tab identity is a marker, not an index. Each session stamps a unique id into the page it opens, so ownership survives navigation and survives the user reordering or closing other tabs. An index alone would silently drift onto the wrong tab.

  • It fails closed. If a session's marked tab can't be re-found, every tool refuses instead of falling back to whatever tab is in front — because that tab is usually yours:

    Tab tracking lost — refusing to fall back to "current tab of window"
    (would target the user's active tab). Call safari_new_tab to reopen.
    

This also drops process count sharply: ~17 node processes for 17 concurrent sessions becomes 1.

SAFARI_PROFILE stays optional — leave it unset and sessions bind to your ordinary Safari windows, cookies and logins intact. Details in docs/http-transport-design.md.

Prefer stdio (one process per agent) over a persistent daemon? That works too — isolation then comes from the process boundary itself. One caveat: if your client multiplexes agents through mcporter, mcporter caches a single MCP client for all of them — whichever transport you pick — so the server never sees distinct sessions and per-session isolation can't engage. mcporter-lanes (a pi extension by @maxim, born out of #76) fixes this upstream: each agent session gets its own daemon dir — and therefore its own safari-mcp — with an idle timeout so processes don't pile up.


Acting on a tab you already have open

By default the server touches only tabs it opened itself. Point it at one of yours and it refuses:

Tab safety: refusing "click" — current tab (https://mail.example.com/inbox) was not
opened by this MCP session. Use safari_new_tab or safari_switch_tab to target your own tab.

That default exists because early versions did click into and close people's tabs. But "read the article I'm looking at" and "fill in the form on my screen" are real, and reopening the page loses the session state that made your tab worth using. Set SAFARI_MCP_ALLOW_USER_TABS=1 and an explicit safari_switch_tab adopts the tab instead of refusing it; from then on the session works in it like one of its own, and says so:

{ "tabIndex": 3, "safeUrl": "https://mail.example.com/inbox", "note": "(user tab, opted-in)" }

What the flag deliberately does not do:

  • It unlocks adoption, not the guards. Only safari_switch_tab adopts, and only the tab you named. An ordinary click or navigate still never lands on whatever tab happens to be in front — the server acts on the tab you pointed it at, not the one you wandered to.
  • safari_close_tab still refuses. Closing is the one action whose cost you cannot undo, so an adopted tab is writable, never disposable. Close it yourself.
  • Adoption is session-local. Nothing is written to the shared ownership file, so it ends with the session rather than leaking to the next process on the machine.

safari_doctor prints the flag's state, and every operation on an adopted tab logs (user tab, opted-in) — so "why did it touch my tab" has an answer instead of being a mystery. Default off; set it only for agents you want working inside your own browsing session. Designed in #92.


Environment variables

VariableDefaultWhat it does
SAFARI_MCP_HTTPoffRun one shared HTTP daemon instead of a process per client (see above).
SAFARI_MCP_HTTP_PORT9225Port for that daemon.
SAFARI_PROFILEunsetBind sessions to a named Safari profile. Unset = your ordinary windows.
SAFARI_MCP_ALLOW_USER_TABSoffLet safari_switch_tab adopt a tab you already had open, instead of refusing it (see below).
SAFARI_MCP_RAISE_ON_NAVIGATEoffLet navigation bring Safari to the front, and stop the focus guard from putting your previous app back.
SAFARI_MCP_SCREENSHOT_MAX_WIDTHunsetDownscale every safari_screenshot to this pixel width (Retina captures are 2× the viewport). Per-call maxWidth overrides it.
SAFARI_MCP_KEEPALIVE_TABoffKeep one daemon-served page open in the profile window so Safari never parks the extension worker between commands.
SAFARI_MCP_OPEN_WINDOW_CMDunsetCommand run with the profile name when the profile window is absent (e.g. after a reboot). Must open the window without focusing Safari.

SAFARI_MCP_RAISE_ON_NAVIGATE=1 is for agents whose whole point is showing you a page — a voice assistant answering "open YouTube", a demo driver. Everything else should leave it off: by default Safari MCP works in the background and hands focus back to whatever app you were using, so an agent can drive a page while you keep typing somewhere else.


Tools (97)

Click to expand the full tool list — organized by category

Navigation (4)

ToolDescription
safari_navigateNavigate to URL (auto HTTPS, wait for load)
safari_go_backGo back in history
safari_go_forwardGo forward in history
safari_reloadReload page (optional hard reload)

Page Reading (3)

ToolDescription
safari_read_pageGet title, URL, and text content
safari_get_sourceGet full HTML source
safari_navigate_and_readNavigate + read in one call

Click & Interaction (6)

ToolDescription
safari_clickClick by CSS selector, visible text, or coordinates
safari_double_clickDouble-click (select word, etc.)
safari_right_clickRight-click (context menu)
safari_hoverHover over element
safari_click_and_waitClick + wait for navigation
safari_click_and_readClick then return the updated page — saves a round-trip (React Router + full loads)

Form Input (11)

ToolDescription
safari_fillFill input (React/Vue/Angular compatible)
safari_clear_fieldClear input field
safari_select_optionSelect dropdown option
safari_fill_formBatch fill multiple fields
safari_fill_and_submitFill form + submit in one call
safari_type_textType real keystrokes (JS-based, no System Events)
safari_press_keyPress key with modifiers
safari_react_select_setSet a react-select v5 value via React fiber — bypasses the menu UI
safari_react_select_list_optionsList a react-select v5 dropdown's options without opening it
safari_replace_editorReplace all content in a code editor (Monaco, CodeMirror, Ace, ProseMirror)
safari_verify_stateVerify an editor's framework-level state matches expected — catch stale DOM before Submit

Screenshots & PDF (3)

ToolDescription
safari_screenshotScreenshot as PNG (viewport or full page)
safari_screenshot_elementScreenshot a specific element
safari_save_pdfExport page as PDF

Scroll (3)

ToolDescription
safari_scrollScroll up/down by pixels
safari_scroll_toScroll to exact position
safari_scroll_to_elementSmooth scroll to element

Tab Management (5)

ToolDescription
safari_list_tabsList all tabs (index, title, URL)
safari_new_tabOpen new tab (background, no focus steal)
safari_close_tabClose tab
safari_switch_tabSwitch to tab by index
safari_wait_for_new_tabWait for a new tab (e.g. OAuth popup) and auto-switch to it

Wait (2)

ToolDescription
safari_wait_forWait for element, text, or URL change
safari_waitWait for specified milliseconds

JavaScript (1)

ToolDescription
safari_evaluateExecute arbitrary JavaScript, return result
safari_eval_fileExecute JavaScript read from a file path (avoids huge inline scripts)

Element Inspection (4)

ToolDescription
safari_get_elementElement details (tag, rect, attrs, visibility)
safari_query_allFind all matching elements
safari_get_computed_styleComputed CSS styles
safari_detect_formsAuto-detect all forms with field selectors

Accessibility (2)

ToolDescription
safari_accessibility_snapshotFull a11y tree: roles, ARIA, focusable elements
safari_snapshotAccessibility tree with ref IDs for every interactive element — preferred way to see page state

Drag & Drop (1)

ToolDescription
safari_dragDrag between elements or coordinates

File Operations (2)

ToolDescription
safari_upload_fileUpload file via JS DataTransfer (no file dialog!)
safari_paste_imagePaste image into editor (no clipboard touch!)

Dialog & Window (2)

ToolDescription
safari_handle_dialogHandle alert/confirm/prompt
safari_resizeResize browser window

Device Emulation (2)

ToolDescription
safari_emulateEmulate device (iPhone, iPad, Pixel, Galaxy)
safari_reset_emulationReset to desktop

Cookies & Storage (11)

ToolDescription
safari_get_cookiesGet all cookies
safari_set_cookieSet cookie with all options
safari_delete_cookiesDelete one or all cookies
safari_local_storageRead localStorage
safari_set_local_storageWrite localStorage
safari_delete_local_storageDelete/clear localStorage
safari_session_storageRead sessionStorage
safari_set_session_storageWrite sessionStorage
safari_delete_session_storageDelete/clear sessionStorage
safari_export_storageExport all storage as JSON (backup/restore sessions)
safari_import_storageImport storage state from JSON

Clipboard (2)

ToolDescription
safari_clipboard_readRead clipboard text
safari_clipboard_writeWrite text to clipboard

Network (6)

ToolDescription
safari_networkQuick network requests via Performance API
safari_start_network_captureStart detailed capture (fetch + XHR)
safari_network_detailsGet captured requests with headers/timing
safari_clear_networkClear captured requests
safari_mock_routeMock network responses (intercept fetch/XHR)
safari_clear_mocksRemove all network mocks

Console (4)

ToolDescription
safari_start_consoleStart capturing console messages
safari_get_consoleGet all captured messages
safari_clear_consoleClear captured messages
safari_console_filterFilter by level (log/warn/error)

Performance (2)

ToolDescription
safari_performance_metricsNavigation timing, Web Vitals, memory
safari_throttle_networkSimulate slow-3g/fast-3g/4g/offline

Data Extraction (4)

ToolDescription
safari_extract_tablesTables as structured JSON
safari_extract_metaAll meta: OG, Twitter, JSON-LD, canonical
safari_extract_imagesImages with dimensions and loading info
safari_extract_linksLinks with rel, external/nofollow detection

Advanced (7)

ToolDescription
safari_override_geolocationOverride browser geolocation
safari_list_indexed_dbsList IndexedDB databases
safari_get_indexed_dbRead IndexedDB records
safari_css_coverageFind unused CSS rules
safari_analyze_pageFull page analysis in one call
safari_doctorDiagnose the macOS permission + daemon chain (Apple Events, Accessibility, Screen Recording, codesign) with per-failure fixes
safari_reload_extensionHot-reload the Safari MCP Bridge extension without a manual toggle

Automation (1)

ToolDescription
safari_run_scriptRun multiple actions in a single call (batch)

Native Input — CGEvent (4)

ToolDescription
safari_native_clickOS-level mouse click (CGEvent, isTrusted: true) — bypasses WAF/bot detection when safari_click is blocked (405/403)
safari_native_hoverOS-level cursor hover — triggers real :hover/mouseenter for tooltips and obfuscated UIs
safari_native_typeInsert text via the real paste pipeline — ProseMirror/Slate/Draft.js process it natively so Submit sends real data
safari_native_keyboardOS-level keypress + modifiers to Safari, no focus steal — reaches React trust-gated handlers (Discord/Slack send)

iOS & WebKit Validation (4)

ToolDescription
safari_inspect_viewportValidate the <meta name=viewport> tag for iOS Safari (device-width, zoom/WCAG, viewport-fit)
safari_safe_area_insetsRead live safe-area-inset values + viewport-fit / env() usage (notch / Dynamic Island)
safari_check_pwaAudit iOS "Add to Home Screen" / PWA readiness (apple-touch-icon, manifest, theme-color, splash)
safari_webkit_compatCheck page CSS against this Safari via CSS.supports() — unsupported props, missing -webkit- prefixes, known quirks

Security

Safari MCP runs locally on your Mac with minimal attack surface:

AspectDetail
NetworkNo remote connections — all communication is local (stdio + localhost)
PermissionsmacOS system permissions required (Screen Recording for screenshots)
DataNo telemetry, no analytics, no data sent anywhere
ExtensionCommunicates only with the local profile bridges (localhost:9224/9228/9232/9236), validated by Safari
CodeFully open source (MIT) — audit every line

Safari MCP vs Alternatives

FeatureSafari MCPChrome DevTools MCPPlaywright MCP
CPU/Heat🟢 Minimal🔴 High🟡 Medium
Your logins✅ Yes✅ Yes❌ No
macOS native✅ WebKit❌ Chromium❌ Chromium/WebKit
Browser dependenciesNoneChrome + debug portPlaywright r

Files in the repo

Repository payload52 top-level entries
  • .github
  • .launch
  • assets
  • docs
  • examples
  • extension
  • scripts
  • test
  • tests
  • xcode
  • .gitignore
  • .mcp.json
  • .prettierignore
  • .prettierrc
  • ai-call-cost-il-2026-09-10.html
  • CHANGELOG.md
  • CODE_OF_CONDUCT.md
  • CONTRIBUTING.md
  • cover-founder-led-2026-09-02.png
  • cover-il-economy-2026-08-26.png
  • Dockerfile
  • eslint.config.js
  • founder-led-2026-09-02.html
  • glama.json
  • hero-ai-call-cost-2026-09-10.jpg
  • il-economy-automation-2026-08-26.html
  • image-size.js
  • index.js
  • injected-escape.js
  • injected-validators.js
  • jsconfig.json
  • LICENSE
  • mcp-helpers.js
  • mcp.json
  • ownership-match.js
  • ownership-state.js
  • package-lock.json
  • package.json
  • README.md
  • response.js
  • safari-helper
  • safari-helper.entitlements
  • safari-helper.swift
  • safari.js
  • SECURITY.md
  • server.json
  • session-context.js
  • smithery-entry.js
  • smithery.yaml
  • social-preview.png
  • test-daemon-session-id.mjs
  • transport.js

Discussion (0)

Ask about usage, or say what you built with it

Sign in to join the discussion.

No comments yet. Be the first to say what this is good for.

More connectors

Real-time global intelligence dashboard. AI-powered news aggregation, geopolitical monitoring, and infrastructure tracking in a unified situational awareness interface

86k

High-performance code intelligence MCP server. Indexes codebases into a persistent knowledge graph — average repo in milliseconds. 158 languages, sub-ms queries, 99% fewer tokens. Single static binary, zero dependencies.

43k

Universal provider proxy for OpenAI Codex & Claude Code — use any LLM (Claude, Gemini, Grok, DeepSeek, Ollama…) with Codex CLI, App, SDK, and Claude Code

14k
okf-memory/
okf-agent-memory

Git-native persistent memory for AI coding agents. Implements Google OKF v0.2 with sub-300µs in-memory BM25 search, embedded MCP server, and progressive disclosure. Slashes token bloat by 80% with zero external databases or dependencies. Built in pure Go.

547
tirth8205/
code-review-graph

Local-first code intelligence graph for MCP and CLI. Builds a persistent map of your codebase so AI coding tools read only what matters, with benchmarked context reductions on reviews and large-repo workflows.

31k
2akouwu/
reverify

Stop your AI from making things up — it proposes, deterministic tools decide, every claim checked against ground truth with evidence. Grounded facts and context survive resets. Reverse engineering is the proving ground. MCP server + CLI.

1.1k