Agent Skills

safari-mcp

Native Safari browser automation for AI agents. 98 tools via AppleScript โ€” zero overhead, keeps logins, runs silently in background. Drop-in alternative to Chrome DevTools MCP with 40-60% less CPU/heat on Apple Silicon.

Install

npx -y safari-mcp
README.md
Safari MCP Server โ€” 98 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

98 tools ยท No Chrome/Puppeteer/Playwright needed ยท ~5ms per command ยท 60% less CPU than Chrome

Quick Start ยท All 98 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. 98 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 ships an official Safari MCP โ€” safaridriver --mcp, first in Safari Technology Preview 247 (July 2026) and in stable Safari since 27.0. It drives an isolated automation session for debugging. safari-mcp drives the real Safari you're already logged into โ€” your cookies and sessions, in the background, with 98 tools. See the full comparison below.


Highlights

  • 98 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 โ†’ Settings โ†’ Developer โ†’ 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 โ†’ Settings โ†’ MCP Servers โ†’ Add 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:

Strategy Example Best for
CSS selector #login-btn, .submit Unique elements
Visible text "Sign In", "Submit" Buttons, links
Coordinates x: 100, y: 200 Canvas, custom widgets
Ref from snapshot ref: "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

Variable Default What it does
SAFARI_MCP_HTTP off Run one shared HTTP daemon instead of a process per client (see above).
SAFARI_MCP_HTTP_PORT 9225 Port for that daemon.
SAFARI_PROFILE unset Bind sessions to a named Safari profile. Unset = your ordinary windows.
SAFARI_MCP_ALLOW_USER_TABS off Let safari_switch_tab adopt a tab you already had open, instead of refusing it (see below).
MCP_MAX_TABS 6 Tabs one session may keep open. Opening one more closes that session's oldest tab, and safari_new_tab names it in evictedTab (its receipt).
SAFARI_MCP_RAISE_ON_NAVIGATE off Let navigation bring Safari to the front, and stop the focus guard from putting your previous app back.
SAFARI_MCP_SCREENSHOT_MAX_WIDTH unset Downscale every safari_screenshot to this pixel width (Retina captures are 2ร— the viewport). Per-call maxWidth overrides it.
SAFARI_MCP_KEEPALIVE_TAB off Keep one daemon-served page open in the profile window so Safari never parks the extension worker between commands.
SAFARI_MCP_OPEN_WINDOW_CMD unset Command 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 (98)

Click to expand the full tool list โ€” organized by category

Navigation (4)

Tool Description
safari_navigate Navigate to URL (auto HTTPS, wait for load)
safari_go_back Go back in history
safari_go_forward Go forward in history
safari_reload Reload page (optional hard reload)

Page Reading (3)

Tool Description
safari_read_page Get title, URL, and text content
safari_get_source Get full HTML source
safari_navigate_and_read Navigate + read in one call

Click & Interaction (6)

Tool Description
safari_click Click by CSS selector, visible text, or coordinates
safari_double_click Double-click (select word, etc.)
safari_right_click Right-click (context menu)
safari_hover Hover over element
safari_click_and_wait Click + wait for navigation
safari_click_and_read Click then return the updated page โ€” saves a round-trip (React Router + full loads)

Form Input (11)

Tool Description
safari_fill Fill input (React/Vue/Angular compatible)
safari_clear_field Clear input field
safari_select_option Select dropdown option
safari_fill_form Batch fill multiple fields
safari_fill_and_submit Fill form + submit in one call
safari_type_text Type real keystrokes (JS-based, no System Events)
safari_press_key Press key with modifiers
safari_react_select_set Set a react-select v5 value via React fiber โ€” bypasses the menu UI
safari_react_select_list_options List a react-select v5 dropdown's options without opening it
safari_replace_editor Replace all content in a code editor (Monaco, CodeMirror, Ace, ProseMirror)
safari_verify_state Verify an editor's framework-level state matches expected โ€” catch stale DOM before Submit

Screenshots & PDF (3)

Tool Description
safari_screenshot Screenshot as PNG (viewport or full page)
safari_screenshot_element Screenshot a specific element
safari_save_pdf Export page as PDF

Scroll (3)

Tool Description
safari_scroll Scroll up/down by pixels
safari_scroll_to Scroll to exact position
safari_scroll_to_element Smooth scroll to element

Tab Management (5)

Tool Description
safari_list_tabs List all tabs (index, title, URL)
safari_new_tab Open new tab (background, no focus steal)
safari_close_tab Close tab
safari_switch_tab Switch to tab by index
safari_wait_for_new_tab Wait for a new tab (e.g. OAuth popup) and auto-switch to it

Wait (2)

Tool Description
safari_wait_for Wait for element, text, or URL change
safari_wait Wait for specified milliseconds

JavaScript (3)

Tool Description
safari_evaluate Execute arbitrary JavaScript, return result
safari_eval_file Execute JavaScript read from a file path (avoids huge inline scripts)
safari_list_frames List every document in the tab (main page + each iframe) with frameId, URL and text length โ€” pass a frameId to safari_evaluate's frame to run inside a cross-origin iframe

Element Inspection (4)

Tool Description
safari_get_element Element details (tag, rect, attrs, visibility)
safari_query_all Find all matching elements
safari_get_computed_style Computed CSS styles
safari_detect_forms Auto-detect all forms with field selectors

Accessibility (2)

Tool Description
safari_accessibility_snapshot Full a11y tree: roles, ARIA, focusable elements
safari_snapshot Accessibility tree with ref IDs for every interactive element โ€” preferred way to see page state

Drag & Drop (1)

Tool Description
safari_drag Drag between elements or coordinates

File Operations (2)

Tool Description
safari_upload_file Upload file via JS DataTransfer (no file dialog!)
safari_paste_image Paste image into editor (no clipboard touch!)

Dialog & Window (2)

Tool Description
safari_handle_dialog Handle alert/confirm/prompt
safari_resize Resize browser window

Device Emulation (2)

Tool Description
safari_emulate Emulate device (iPhone, iPad, Pixel, Galaxy)
safari_reset_emulation Reset to desktop

Cookies & Storage (11)

Tool Description
safari_get_cookies Get all cookies
safari_set_cookie Set cookie with all options
safari_delete_cookies Delete one or all cookies
safari_local_storage Read localStorage
safari_set_local_storage Write localStorage
safari_delete_local_storage Delete/clear localStorage
safari_session_storage Read sessionStorage
safari_set_session_storage Write sessionStorage
safari_delete_session_storage Delete/clear sessionStorage
safari_export_storage Export all storage as JSON (backup/restore sessions)
safari_import_storage Import storage state from JSON

Clipboard (2)

Tool Description
safari_clipboard_read Read clipboard text
safari_clipboard_write Write text to clipboard

Network (6)

Tool Description
safari_network Quick network requests via Performance API
safari_start_network_capture Start detailed capture (fetch + XHR)
safari_network_details Get captured requests with headers/timing
safari_clear_network Clear captured requests
safari_mock_route Mock network responses (intercept fetch/XHR)
safari_clear_mocks Remove all network mocks

Console (4)

Tool Description
safari_start_console Start capturing console messages
safari_get_console Get all captured messages
safari_clear_console Clear captured messages
safari_console_filter Filter by level (log/warn/error)

Performance (2)

Tool Description
safari_performance_metrics Navigation timing, Web Vitals, memory
safari_throttle_network Simulate slow-3g/fast-3g/4g/offline

Data Extraction (4)

Tool Description
safari_extract_tables Tables as structured JSON
safari_extract_meta All meta: OG, Twitter, JSON-LD, canonical
safari_extract_images Images with dimensions and loading info
safari_extract_links Links with rel, external/nofollow detection

Advanced (7)

Tool Description
safari_override_geolocation Override browser geolocation
safari_list_indexed_dbs List IndexedDB databases
safari_get_indexed_db Read IndexedDB records
safari_css_coverage Find unused CSS rules
safari_analyze_page Full page analysis in one call
safari_doctor Diagnose the macOS permission + daemon chain (Apple Events, Accessibility, Screen Recording, codesign) with per-failure fixes
safari_reload_extension Hot-reload the Safari MCP Bridge extension without a manual toggle

Automation (1)

Tool Description
safari_run_script Run multiple actions in a single call (batch)

Native Input โ€” CGEvent (4)

Tool Description
safari_native_click OS-level mouse click (CGEvent, isTrusted: true) โ€” bypasses WAF/bot detection when safari_click is blocked (405/403)
safari_native_hover OS-level cursor hover โ€” triggers real :hover/mouseenter for tooltips and obfuscated UIs
safari_native_type Insert text via the real paste pipeline โ€” ProseMirror/Slate/Draft.js process it natively so Submit sends real data
safari_native_keyboard OS-level keypress + modifiers to Safari, no focus steal โ€” reaches React trust-gated handlers (Discord/Slack send)

iOS & WebKit Validation (4)

Tool Description
safari_inspect_viewport Validate the <meta name=viewport> tag for iOS Safari (device-width, zoom/WCAG, viewport-fit)
safari_safe_area_insets Read live safe-area-inset values + viewport-fit / env() usage (notch / Dynamic Island)
safari_check_pwa Audit iOS "Add to Home Screen" / PWA readiness (apple-touch-icon, manifest, theme-color, splash)
safari_webkit_compat Check 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:

Aspect Detail
Network No remote connections โ€” all communication is local (stdio + localhost)
Permissions macOS system permissions required (Screen Recording for screenshots)
Data No telemetry, no analytics, no data sent anywhere
Extension Communicates only with the local profile bridges (localhost:9224/9228/9232/9236), validated by Safari
Code Fully open source (MIT) โ€” audit every line

Safari MCP vs Alternatives

Feature Safari MCP Chrome DevTools MCP Playwright MCP
CPU/Heat ๐ŸŸข Minimal ๐Ÿ”ด High ๐ŸŸก Medium
Your logins โœ… Yes โœ… Yes โŒ No
macOS native โœ… WebKit โŒ Chromium โŒ Chromium/WebKit
Browser dependencies None Chrome + debug port Playwright runtime
Tools 98 ~30 ~25
File upload JS (no dialog) CDP Playwright API
Image paste JS (no clipboard) CDP Playwright API
Focus steal โŒ Background โŒ Background โŒ Headless
Network mocking โœ… โŒ โœ…
Lighthouse โŒ โœ… โŒ
Performance trace โŒ โœ… โŒ

Tip: Use Safari MCP for daily browsing tasks (95% of work) and Chrome DevTools MCP only for Lighthouse/Performance audits.

vs Apple's Official Safari MCP (safaridriver)

In July 2026 Apple shipped an official Safari MCP server built on safaridriver โ€” first in Safari Technology Preview 247, and in stable Safari since 27.0. That's great validation for the category โ€” and it's built for a different job. Apple's server drives an isolated WebDriver automation session for debugging; safari-mcp drives the real Safari you're already logged into.

On macOS 27.0 with Safari 27.0, safaridriver --help lists --mcp and its tools/list answers with 17 entries (verified 2026-09-15); Safari 26.5.2 had no --mcp (verified 2026-07-23). Check your own machine with safaridriver --help | grep mcp.

Running both? Apple's setup guide registers its server under the name safari-mcp (claude mcp add safari-mcp -- /usr/bin/safaridriver --mcp) โ€” the same name as this package. Give one of them a different name, e.g. claude mcp add safari-webdriver -- /usr/bin/safaridriver --mcp, so your client doesn't overwrite one with the other or leave you guessing which server answered.

๐Ÿฆ safari-mcp (this repo) Apple safaridriver --mcp
Your real logins / cookies โœ… Your actual Safari โš ๏ธ Isolated automation session โ€” no access to AutoFill or browsing activity
Runs on โœ… Stable Safari, every Mac โš ๏ธ Safari 27 or later, or Safari Technology Preview 247+
Background (no focus steal) โœ… Yes โŒ Dedicated window with a "controlled by automation" banner
Page sees navigator.webdriver โœ… false โ€” an ordinary tab โš ๏ธ true โ€” required of every WebDriver session
Tools 98 17
Storage (cookies, localStorage, IndexedDB) โœ… 10 tools โŒ
Network mocking + throttling โœ… Yes โŒ Read-only network inspection
Device emulation (iPhone, iPad) โœ… Yes โš ๏ธ Viewport + media type only
Setup npx safari-mcp Enable "Allow remote automation and external agents" in Safari's Developer settings, then point your client at safaridriver --mcp
Official Apple support โŒ Community (MIT) โœ… Apple, WebDriver-standard

About that navigator.webdriver row: the WebDriver standard requires every remote-controlled session to report true, so Apple's server does. safari-mcp evaluates JavaScript in an ordinary tab, so it reads false (checked on Safari 27.0, 2026-09-17). That is the difference if a site refuses WebDriver traffic. It is not an anti-detection feature โ€” nothing else about the browser is disguised, and your real logins are in use, so treat every page as if it knows exactly who you are.

When Apple's server is the right pick: you specifically want a clean-room, WebDriver-standard session for compatibility debugging on Safari 27 or later. For everything else โ€” daily automation on the browser you're already signed into, in the background โ€” safari-mcp is built for exactly that.

Why Safari MCP and Not the Other Safari MCP Projects?

There are several "safari-mcp" projects floating around. Here's how they compare:

Feature ๐Ÿฆ safari-mcp (this repo) lxman/safari-mcp-server Epistates/MCPSafari HayoDev/safari-devtools-mcp
Tools 98 ~10 23 ~15
Install npx safari-mcp Manual Binary npx
Engine Dual (Extension + AppleScript) WebDriver Extension only DevTools Protocol
Keeps your real Safari logins โœ… Yes โš ๏ธ Limited โœ… Yes โŒ Debug session
Background (no focus steal) โœ… Yes โŒ No โš ๏ธ Sometimes โœ… Yes
Storage tools (cookies, localStorage, IndexedDB) 10 0 0 2
Data extraction (tables, meta, images, links) 4 0 0 0
Network mocking โœ… Yes โŒ No โŒ No โŒ No
Device emulation (iPhone, iPad, Pixel) โœ… Yes โŒ No โŒ No โŒ No
File upload (no dialog) โœ… JS DataTransfer โŒ No โŒ No โŒ No
Image paste (no clipboard touch) โœ… Yes โŒ No โŒ No โŒ No
PDF export โœ… Yes โŒ No โŒ No โŒ No
Console capture 4 tools 0 1 1
Performance metrics + Web Vitals โœ… Yes โŒ No โŒ No โš ๏ธ Partial
Active maintenance โœ… Multiple releases/week ๐ŸŸก Sporadic ๐ŸŸก Slow ๐ŸŸก Slow
License MIT MIT None specified MIT
In MCP Registry โœ… โŒ โŒ โœ…
In Awesome MCP โœ… โŒ โŒ โŒ

TL;DR โ€” if you want the most complete Safari MCP with the smoothest install, the most tools, and active maintenance, this is the one.


Architecture

Safari MCP uses a dual-engine architecture โ€” the Extension is preferred for speed and advanced capabilities, with AppleScript as an always-available fallback:

Claude/Cursor/AI Agent
        โ†“ MCP Protocol (stdio)
   Safari MCP Server (Node.js)
        โ†“                    โ†“
   Extension (HTTP)     AppleScript + Swift daemon
   (~5-20ms/cmd)        (~5ms/cmd, always available)
        โ†“                    โ†“
   Content Script       do JavaScript in tab N
        โ†“                    โ†“
   Page DOM โ†โ†โ†โ†โ†โ†โ†โ†โ†โ† Page DOM

Key design decisions:

  • Dual engine with automatic fallback โ€” Extension is preferred; if not connected, AppleScript handles everything seamlessly
  • Persistent Swift helper โ€” one long-running process instead of spawning per command (16x faster)
  • Tab-indexed operations โ€” all JS runs on a specific tab by index, never steals visual focus
  • JS-first approach โ€” typing, clicking, file upload all use JavaScript events (no System Events keyboard conflicts)
  • No activate โ€” Safari is never brought to foreground

Safari Extension (Optional)

The Safari MCP Extension is optional but recommended. Without it, ~80% of functionality works via AppleScript alone. The extension adds capabilities that AppleScript cannot provide:

What the Extension Adds

Capability With Extension AppleScript Only
Closed Shadow DOM (Reddit, Web Components) โœ… Full access โŒ Invisible
Strict CSP sites โœ… Bypasses via MAIN world โŒ Often blocked
React/Vue/Angular state manipulation โœ… Deep (Fiber, ProseMirror) โš ๏ธ Basic
Loading state detection (spinners, skeletons) โœ… Smart detection โŒ No
Dialog handling (alert/confirm) โŒ โœ… Only AppleScript
Native OS-level click (CGEvent) โŒ โœ… Only AppleScript
PDF export โŒ โœ… Only AppleScript

When do you need the extension? If you're automating modern SPAs with closed shadow DOM (e.g., Reddit), sites with strict Content Security Policy, or framework-heavy editors (Draft.js, ProseMirror, Slate).

Installing the Extension

The extension requires a one-time build with Xcode (free, included with macOS).

Note for npm users: The xcode/ directory is not included in the npm package. Clone the GitHub repository to build from source.

Prerequisites: Xcode (install from App Store โ€” free)

# 1. Clone the repo (the npm package does not include the Xcode project)
git clone https://github.com/achiya-automation/safari-mcp.git
cd safari-mcp

# 2. Build a clean, Apple Development-signed app
# If needed, first choose your Team in Xcode โ†’ Signing & Capabilities.
xcodebuild -project "xcode/Safari MCP/Safari MCP.xcodeproj" \
  -scheme "Safari MCP (macOS)" -configuration Release \
  -allowProvisioningUpdates clean build

# 3. Locate the signed app
APP_PATH=$(find ~/Library/Developer/Xcode/DerivedData/Safari_MCP-*/Build/Products/Release -name "Safari MCP.app" -maxdepth 2 | head -1)

# 4. Re-sign safari-helper with the Apple Events entitlement
# (helps macOS surface the TCC Automation prompt reliably)
codesign --sign - --force --entitlements safari-helper.entitlements safari-helper

# 5. Open the app (needed once so Safari registers the extension)
open "$APP_PATH"

Do not ad-hoc re-sign Safari MCP.app with codesign --deep --sign -. That replaces the Apple Development identity on the wrapper and embedded extension; current Safari can then silently disable or mark the extension as removed. If a previous build was ad-hoc signed, run the clean build command above again.

Alternatively, open xcode/Safari MCP/Safari MCP.xcodeproj directly in Xcode, select your Apple ID under Signing & Capabilities, and click Run. A free personal Apple Developer account is sufficient for local use.

Then in Safari:

  1. Safari โ†’ Settings โ†’ Advanced โ†’ enable Show features for web developers
  2. Safari โ†’ Settings โ†’ Developer โ†’ Allow unsigned extensions (required each Safari restart)
  3. Safari โ†’ Settings โ†’ Extensions โ†’ enable Safari MCP Bridge

The extension connects automatically to the first local bridge whose declared Safari profile matches its own. The default bridge ports are 9224, 9228, 9232, and 9236; a single-profile setup normally uses 9224.

Note: "Allow unsigned extensions" resets every time Safari restarts. You'll need to re-enable it in Safari โ†’ Settings โ†’ Developer after each restart. The extension itself stays installed.

Toolbar icon status:

  • ON โ€” connected to MCP server
  • OFF โ€” manually disabled via popup
  • (no badge) โ€” server not running, will auto-reconnect

macOS Permissions

Safari MCP needs these one-time permissions:

Permission Where Why
JavaScript from Apple Events Safari โ†’ Settings โ†’ Developer Required for do JavaScript
Automation โ†’ Safari System Settings โ†’ Privacy & Security โ†’ Automation Required for all AppleScript-backed tools
Screen Recording System Settings โ†’ Privacy & Security โ†’ Screen & System Audio Recording Required for safari_screenshot without the extension, and for safari_save_pdf
Accessibility (safari-helper) System Settings โ†’ Privacy & Security โ†’ Accessibility (named Device Control and Data Access on macOS 27) Required for safari_native_click, safari_native_keyboard, safari_native_hover and safari_save_pdf

Granting Accessibility to safari-helper (required for safari_native_*)

The safari_native_click, safari_native_keyboard and safari_native_hover tools inject OS-level CGEvent events into Safari without stealing focus. macOS requires the underlying helper binary to be approved in Accessibility before those events can reach a non-frontmost window.

  1. Open System Settings โ†’ Privacy & Security โ†’ Accessibility (on macOS 27 the pane is named Device Control and Data Access).
  2. Click + (unlock with your password if needed).
  3. Navigate to the helper binary and add it:
    • npm global install: $(npm root -g)/safari-mcp/safari-helper
    • npx / project install: ./node_modules/safari-mcp/safari-helper
    • From source clone: /path/to/safari-mcp/safari-helper
  4. Make sure the toggle next to it is ON.

The postinstall script re-signs the helper with a stable identifier (com.achiya-automation.safari-mcp) so this permission survives future upgrades โ€” without that step, every npm update would silently revoke approval because the binary's adhoc-signed identifier changes per build.

If safari_native_click reports success but the page doesn't react (no isTrusted: true click events fire), the helper is most likely missing this approval. The safari_* (non-native_) tools don't need it.

Granting Automation โ†’ Safari (important for IDE users)

macOS TCC grants Automation permission to the parent process that spawns the MCP server, not to safari-mcp itself. So you need to grant Automation โ†’ Safari to the app that runs Claude Code / Cursor / Windsurf โ€” typically Visual Studio Code or Terminal.

If the permission dialog never appears automatically, run this command once from a Terminal that already has Automation permission:

osascript -e 'tell application "Safari" to get URL of current tab of window 1'

That call registers the Terminal app in the Automation database and then triggers the prompt for Safari. After you approve it, subsequent MCP calls from any child process chain will work.


Troubleshooting

Issue Fix
"AppleScript error" Enable "Allow JavaScript from Apple Events" in Safari โ†’ Settings โ†’ Developer
"Not authorized to send Apple events to Safari" Grant Automation โ†’ Safari to your IDE (see above)
"Not authorized" after npm update Updating changes the binary's cdhash โ€” macOS silently revokes Automation permission. Re-run the osascript one-liner above to re-grant it
safari_native_click reports success but page doesn't react Add safari-helper to System Settings โ†’ Privacy & Security โ†’ Accessibility (see Granting Accessibility above). Confirm by attaching a click listener with {capture:true} in the page console โ€” without the grant, no isTrusted: true event fires
Screenshots empty Grant Screen & System Audio Recording to Terminal/VS Code โ€” or, when safari-mcp runs as a LaunchAgent, to the node binary itself (a Homebrew node upgrade changes its path, so re-grant after upgrading). safari_doctor names the exact binary
Extension missing after enabling "Allow unsigned extensions" (macOS 27) Known macOS 27.0 issue (183044008): with the setting on, rebuild the extension app (see Installing the Extension) and it appears
Tab not found Call safari_list_tabs to refresh tab indices
Hebrew keyboard issues All typing uses JS events โ€” immune to keyboard layout
HTTPS blocked safari_navigate auto-tries HTTPS first, falls back to HTTP
Safari steals focus Ensure you're on latest version โ€” newTab restores your active tab

FAQ

Does it work with Safari Technology Preview?

No. Every AppleScript path targets application "Safari" and the native helper looks for the bundle id com.apple.Safari, so STP (com.apple.SafariTechnologyPreview) is a different application that safari-mcp never addresses. Run it against stable Safari.

Note that Apple's own safaridriver --mcp server is the one that needs STP 247+ (or Safari 27+) โ€” see Safari MCP vs Alternatives. The two are unrelated.

Can it handle private/incognito tabs?

Not as a supported mode, and it is untested. No tool opens a private window, and Safari disables extensions in Private Browsing unless you turn each one on explicitly, so the extension bridge โ€” and everything in the "With Extension" column of Safari Extension (Optional) โ€” is off there by default.

If you want an isolated identity, use a separate Safari profile instead: profile windows are a first-class concept here (see Running several agents at once).

Does it work with multiple Safari windows?

Yes. Tab lookups walk every window rather than assuming window 1, and a tab is pinned by its window's (index, tabCount) pair, because an index on its own is ambiguous once two windows are open. Profile windows are recognised by Safari's ProfileName โ€” Tab Title window naming.

The reliable handle is still the receipt that safari_new_tab returns: pass it to every later call and the tab is addressed directly, no matter how many windows move around it.

What macOS versions are supported?

Any macOS with Safari โ€” the package declares "os": ["darwin"] and no minimum OS, and there is no version gate in the code. Node.js 20+ is the hard requirement ("engines": { "node": ">=20" }).

Two places where the OS does matter in practice:

  • On macOS 27 the Accessibility pane is renamed Device Control and Data Access โ€” same grant, new name (see macOS Permissions).
  • Safari 26 and 27 changed several web-platform behaviours that specific tools account for; those are handled, not blocked.
Can I run multiple instances simultaneously?

Yes โ€” that is what Running several agents at once covers. Each session owns its own tabs, and ownership is tracked per session rather than by tab index, so one agent cannot act on (or close) a tab another agent owns.

One process binds the extension bridge port; the others proxy through it and take the port over if that process exits.

Do I need to keep Safari in the foreground?

No. safari_new_tab opens a background tab and never steals focus, and the few commands macOS forces an implicit activate on restore your previous frontmost app afterwards โ€” with a guard that leaves focus alone if you were typing in Safari yourself a moment earlier.

The two exceptions are the deliberately native tools โ€” safari_native_click, safari_native_keyboard, safari_native_hover โ€” which post real OS events and therefore land wherever the frontmost window is. Do not use those while you are working in another Safari tab.

How does it interact with Safari extensions?

Your existing extensions keep running normally; safari-mcp drives the same Safari you use, so content blockers and password managers behave exactly as they do for you.

safari-mcp also ships its own optional extension. Without it roughly 80% of the tools still work over AppleScript alone; with it you additionally get closed shadow DOM, strict-CSP sites, deep framework state and loading-state detection. Dialog handling, native clicks and PDF export go the other way โ€” those are AppleScript-only. The full split is in Safari Extension (Optional).

What happens if "Allow JavaScript from Apple Events" is not enabled?

Anything that evaluates JavaScript fails โ€” safari_evaluate, safari_read_page, form filling, extraction โ€” while pure AppleScript actions such as opening or switching tabs still work. That is the usual cause of "it opens the tab but reads nothing".

Turn it on in Safari โ†’ Settings โ†’ Advanced โ†’ Show features for web developers, then Safari โ†’ Settings โ†’ Developer โ†’ Allow JavaScript from Apple Events. Both are listed under Prerequisites. safari_doctor walks the rest of the chain (Automation, native helper, Accessibility, Screen Recording) and names this setting in its fix hint โ€” but its Apple Events check only counts windows, which Automation permission alone satisfies, so it will not fail on this one for you.

Works With

Safari MCP works with any MCP-compatible client:

Client Status
Claude Code โœ… Tested daily
Claude Desktop โœ… Tested
Cursor โœ… Tested
Windsurf โœ… Compatible
VS Code + Continue โœ… Compatible

Contributing

PRs welcome! See CONTRIBUTING.md for setup instructions.

The codebase is two files:

  • safari.js โ€” Safari automation layer (AppleScript + JavaScript)
  • index.js โ€” MCP server with tool definitions

Sponsors

Safari MCP is free and open source. If it saves you time or CPU cycles, consider supporting its development:

Sponsor via PayPal

Your support funds:

  • ๐Ÿงช Testing across macOS versions and Safari releases
  • ๐Ÿ› ๏ธ New tools and features
  • ๐Ÿ“– Documentation and examples

Become the first sponsor!


Commercial Support

Need Safari MCP integrated into your product or agent stack? Achiya Automation offers:

  • Priority bug fixes and custom tool development for your use case
  • Integration consulting โ€” wiring Safari MCP into production agent systems (Claude, Cursor, n8n, custom)
  • Private deployment support โ€” multi-user Safari MCP, non-standard macOS environments, CI/CD
  • Training workshops for engineering teams adopting MCP-based automation

Built by the author of Safari MCP. Start a conversation โ†’


What agents unlock with Safari MCP

When an AI agent drives Safari MCP, it gets things a headless browser can't:

  • Real authenticated sessions โ€” Gmail, GitHub, Ahrefs, Slack, banking dashboards are all already logged in
  • Framework-aware form filling โ€” safari_fill_and_submit calls React/Vue/Angular setters natively, no guessing whether input events fired
  • Background operation โ€” the agent works in parallel while you keep using your Mac
  • One MCP call per workflow โ€” safari_run_script batches navigation + clicks + extraction into a single roundtrip

The pattern holds across models: drive the browser the human already trusts โ€” you inherit logins, cookies, extensions, and the user's exact environment in one step.


Community

6,000+ monthly npm downloads โ€” developers are building AI agents on macOS with Safari MCP.

  • GitHub Discussions โ€” ask questions, share use cases
  • Issues โ€” bug reports and feature requests
  • Good First Issues โ€” start contributing

Ecosystem

Other macOS MCP servers that complement Safari MCP:

Project What it does When to use
mcp-server-macos-use OS-level macOS automation (accessibility, screen control) System-wide interactions beyond Safari
chrome-devtools-mcp Chrome DevTools Protocol Lighthouse audits, Chrome-specific performance traces

Using Safari MCP alongside Chrome DevTools MCP? Safari handles 95% of daily browsing (zero overhead), Chrome handles the 5% that needs Lighthouse or Chrome-specific traces.


Listed On

Glama MCP Registry MCP Scoreboard CI


License

MIT โ€” use it however you want.


Built by Achiya Automation โ€” automation & AI agents for business.

Search skills and MCP servers

Fuzzy search across 23,137 skills and servers