SafariBLOCK
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.
Overview
From the repository's own README, as read at the audited commit. Badges and raw HTML are left out.
The browser for your coding agent.
Your real Safari, logged in — no Chrome, no heat, no headless.
[](https://www.npmjs.com/package/safari-mcp) [](https://www.npmjs.com/package/safari-mcp) [](https://opensource.org/licenses/MIT) [](https://www.apple.com/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
❌ 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, Ahref
03544cc6d600OBSERVED · 2026-10-06Connect
Built from this server's own package name, version and transport as found in its source — not copied from anyone's documentation, so it cannot drift against a page we do not control.
claude mcp add safari-mcp -- npx -y [email protected]
Exposed tools (99)
76 read · 15 write · 8 destructive. Blast radius: 8 tools can delete or overwrite — an agent that can be talked into calling a tool can be talked into calling this one.
| Tool | Risk | Description |
|---|---|---|
ping | read | returns pong |
safari_accessibility_snapshot | read | Get the accessibility tree of the page (roles, ARIA labels, focusable elements, form states). Essential for a11y auditing. |
safari_analyze_page | read | Full page analysis in ONE call: title, URL, meta tags, OG, headings, link stats, image stats, forms, and text preview. Perfect for SEO/audit. |
safari_check_pwa | read | Audit the page for iOS |
safari_clear_console | destructive | Clear all captured console messages |
safari_clear_field | destructive | Clear an input field |
safari_clear_mocks | destructive | Remove all network route mocks (restore real network behavior) |
safari_clear_network | destructive | Clear all captured network requests |
safari_click | read | Click element. Use ref (from snapshot), selector, text, or x/y. Works on React/Airtable/virtual DOM apps via full PointerEvent+MouseEvent sequence + React Fiber fallback. Pure JS — never touches user |
safari_click_and_read | read | Click an element then return the updated page — saves 1 full round-trip vs separate click+read_page. Handles both React Router navigation and full page loads. |
safari_click_and_wait | read | Click an element AND wait for the result (page load or element). Use instead of click + wait_for separately. |
safari_clipboard_read | read | Read the current clipboard content (text) |
safari_clipboard_write | write | Write text to the system clipboard |
safari_close_tab | write | Close the current tab. After a daemon/session restart, pass the opaque receipt returned by safari_new_tab or safari_list_tabs. |
safari_console_filter | read | Get console messages filtered by level (must call safari_start_console first) |
safari_css_coverage | read | Analyze CSS coverage: find unused CSS rules across all stylesheets. Shows coverage percentage per stylesheet. |
safari_delete_cookies | destructive | Delete a specific cookie or all cookies for the current page |
safari_delete_local_storage | destructive | Delete a localStorage key, or clear all localStorage (omit key to clear all) |
safari_delete_session_storage | destructive | Delete a sessionStorage key, or clear all sessionStorage (omit key to clear all) |
safari_detect_forms | read | Auto-detect all forms on the page with their fields, types, selectors, and submit buttons. Great for automated form filling. |
safari_doctor | read | Diagnose the macOS permission + daemon chain in one shot: Safari running, Apple Events/Automation, native helper daemon, Accessibility (native clicks), Screen Recording, and the helper |
safari_double_click | read | Double-click an element by CSS selector or x/y coordinates (e.g. to select a word in text) |
safari_drag | read | Drag an element to another element or position. Use CSS selectors or x/y coordinates. |
safari_emulate | read | Emulate a mobile device: sizes the Safari window to the device |
safari_eval_file | write | Execute JavaScript read from a FILE path (avoids passing huge scripts inline / manual copy). Same engine as safari_evaluate: extension-first (no focus steal), AppleScript fallback. Use to upload binary via a generated .js containing base64. |
safari_evaluate | read | |
safari_export_storage | read | Export all storage state (cookies + localStorage + sessionStorage) as JSON — useful for saving and restoring login sessions |
safari_extract_images | read | Extract all images with src, alt, dimensions, loading strategy, viewport visibility |
safari_extract_links | read | Extract all links with href, text, rel, target, external/nofollow detection |
safari_extract_meta | read | Extract all meta tags: title, description, canonical, OG tags, Twitter cards, JSON-LD, alternate languages, RSS feeds |
safari_extract_tables | read | Extract HTML tables as structured JSON (headers + rows). Perfect for scraping data tables. |
safari_fill | read | |
safari_fill_and_submit | write | Fill a form AND submit it in one operation. Finds submit button automatically if not specified. |
safari_fill_form | read | Fill multiple form fields at once |
safari_get_computed_style | read | Get computed CSS styles for an element. Optionally filter specific properties. |
safari_get_console | read | Get captured console messages (must call safari_start_console first) |
safari_get_cookies | read | Get cookies for the current page |
safari_get_element | read | Get detailed info about an element (tag, text, rect, attributes, visibility) |
safari_get_indexed_db | read | Read records from an IndexedDB database store |
safari_get_source | read | Get HTML source of current page |
safari_go_back | read | Go back in browser history |
safari_go_forward | read | Go forward in browser history |
safari_handle_dialog | write | Set up handler for the next alert/confirm/prompt dialog |
safari_hover | read | Hover over element. Use ref, selector, or x/y |
safari_import_storage | write | Import storage state from JSON (as exported by safari_export_storage) — restores cookies, localStorage, sessionStorage |
safari_inspect_viewport | read | Validate the page |
safari_list_frames | read | List every document in the tab — the main page plus each iframe — with its frameId, URL and text length. Use when a page |
safari_list_indexed_dbs | read | List all IndexedDB databases on the current page |
safari_list_tabs | read | List every tab in this session |
safari_local_storage | read | Get localStorage data for the current page |
safari_mock_route | read | Intercept network requests matching a URL pattern and return a mock response. Works with both fetch and XHR. Useful for testing API error states, offline behavior, or replacing API responses. |
safari_native_click | read | |
safari_native_hover | read | OS-level mouse hover via macOS CGEvent — moves the real cursor to an element to trigger native :hover / mouseenter handlers. Use for obfuscated UIs where JS-dispatched mouseenter isn |
safari_native_keyboard | read | OS-level keyboard event via macOS CGEvent — sends a real keypress (with optional modifiers) to the Safari window WITHOUT activating Safari or stealing focus. Use when safari_press_key |
safari_native_type | read | |
safari_navigate | read | Navigate this session |
safari_navigate_and_read | read | Navigate to a URL and return the page content in one step — saves 1 full round-trip vs navigate+read_page. Use instead of safari_navigate + safari_read_page. Like safari_navigate, returns a fresh |
safari_network | read | Quick network overview via Performance API (no setup needed). Shows URLs and timing for resources loaded by the page. For detailed request/response info (headers, status codes, POST bodies), use safari_start_network_capture + safari_network_details instead. |
safari_network_details | read | Get captured network requests with full details (must call safari_start_network_capture first) |
safari_new_tab | read | Open a new background tab (never steals focus), optionally with a URL. Returns {tabIndex, safeUrl, receipt}. KEEP THE RECEIPT and pass it as |
safari_override_geolocation | read | Override the browser |
safari_paste_image | read | Paste an image from a local file into the focused element via JS DataTransfer (no clipboard, no focus steal). Works on Medium, dev.to, HackerNoon, TOI, etc. |
safari_performance_metrics | read | Get detailed performance metrics: navigation timing, Web Vitals (FCP, LCP, CLS), resource breakdown, memory usage |
safari_press_key | read | Press a keyboard key (enter, tab, escape, arrows, etc). Supports modifiers (cmd, shift, alt, ctrl). |
safari_query_all | read | Find all elements matching a CSS selector (returns tag, text, href, value). x/y are relative to each element |
safari_react_select_list_options | read | List available options of a react-select v5 dropdown without opening the menu. Returns JSON {ok, total, options:[{label,value}...]}. Useful when safari_react_select_set returns |
safari_react_select_set | write | |
safari_read_page | read | Read page text content (title, URL, body text). Use for reading article text or page content. For interacting with elements, prefer safari_snapshot (gives ref IDs). Use selector to read specific element. Use maxLength to limit output. |
safari_reload | read | Reload the current page |
safari_reload_extension | read | |
safari_replace_editor | read | |
safari_reset_emulation | destructive | Reset device emulation: restores the window size from before safari_emulate and removes its navigator overrides (no reload) |
safari_resize | read | Resize the Safari window |
safari_right_click | read | Right-click (context menu) an element by CSS selector or x/y coordinates |
safari_run_script | write | |
safari_safe_area_insets | read | Read the live CSS safe-area-inset values (top/right/bottom/left) as the page sees them, whether viewport-fit=cover is set, and whether env(safe-area-inset-*) is used in any stylesheet. For notch / Dynamic Island layout debugging. |
safari_save_pdf | write | Save the current page as a PDF file. Uses screencapture + PDF rendering (no Safari UI interaction needed). |
safari_screenshot | read | Take a visual screenshot (base64 JPEG). EXPENSIVE — use safari_snapshot instead for most tasks. Only use screenshot when you need to verify visual layout, styling, images, or colors that snapshot can |
safari_screenshot_element | read | Take a screenshot of a specific element (by CSS selector). Returns a base64 image. |
safari_scroll | read | Scroll the page up or down by a specified amount |
safari_scroll_to | read | Scroll to a specific position on the page |
safari_scroll_to_element | read | Scroll to element by CSS selector OR text. For virtual DOM (Airtable) use text — scrolls down until text appears in DOM. |
safari_select_option | read | Select an option in a native <select> dropdown. Sets .value and dispatches change event. Pass |
safari_session_storage | read | Get sessionStorage data for the current page |
safari_set_cookie | write | Set a cookie on the current page |
safari_set_local_storage | write | Set a value in localStorage |
safari_set_session_storage | write | Set a value in sessionStorage |
safari_snapshot | read | PREFERRED way to see page state. Returns accessibility tree with ref IDs for every interactive element. Use refs with click/fill/type instead of CSS selectors. Workflow: snapshot → see refs → click({ref: |
safari_start_console | write | Start capturing console messages (log, warn, error, info). Call once per page. |
safari_start_network_capture | write | Start capturing detailed network requests (fetch + XHR) with headers, status, timing. Call once per page. Intercepts fetch/XHR — captures requests AFTER this call only. For quick overview of already-loaded resources, use safari_network instead. |
safari_switch_tab | read | Switch the MCP session |
safari_throttle_network | read | Simulate slow network conditions. Profiles: slow-3g, fast-3g, 4g, offline. Or custom latency/speed. Call with no args to reset. |
safari_type_text | read | |
safari_upload_file | write | Upload a file to a <input type= |
safari_verify_state | read | Verify the framework-level state of an editor/input matches the expected value. Returns JSON {match, mode, actual, expected, hint?}. Modern editors (ProseMirror, Lexical, Closure, React-controlled inputs) maintain state separately from the DOM — |
safari_wait | read | Wait for a fixed time in milliseconds. Use only when you need a brief pause between actions. PREFER safari_wait_for (waits for element/text to appear) — it |
safari_wait_for | read | Wait for an element or text to appear on the page |
safari_wait_for_new_tab | read | Wait for a new tab to appear (e.g. after OAuth login click opens popup). Automatically switches to the new tab. |
safari_webkit_compat | read | Check every CSS property used on the page against THIS Safari via CSS.supports() — reports unsupported properties, properties that need a -webkit- prefix, and known Safari rendering quirks (e.g. position:sticky inside overflow ancestors). Tested in the live engine, so no false positives. |
Trust audit
BLOCKgrade F · trust 57/100 Do not install this without reading the findings. The audit found something that could harm you or your machine.
| Layer | What it checks | Result |
|---|---|---|
| L0 | Provenance & inventory | WARN |
| L1 | Static analysis of the code | FAIL |
| L2 | Instruction surface (what it tells the agent) | PASS |
| L3 | Class-specific surface | WARN |
| L4 | Behavioural (sandbox) | SKIPPED |
What the source does
- Filesystem
- declared (2 observation(s))
- Network
- declared (8 observation(s))
- Shell
- declared (2 observation(s))
- Dependencies
- not all pinned
- Secrets in source
- found
Findings (25)
const blocked = ['.ssh', '.gnupg', '.aws', '.config/gcloud', 'credentials', '.env', '.npmrc', '.netrc', 'id_rsa', 'id_ed25519', '.keychain'];
safari-helper
let HTTP_URL = `http://127.0.0.1:${BRIDGE_PORTS[0]}`;...BRIDGE_PORTS.map((port) => `http://127.0.0.1:${port}`),const connectUrl = new URL(req.url, `http://127.0.0.1:${HTTP_PORT}`);const token = "opaque_receipt_token_1234567890";
const token = "receipt_token_abcdefghijklmnopqrstuvwxyz";
const TOKEN = "Receipt_ABCDEF1234567890abcdef";
const token = "Receipt_ABCDEF123456789012345678";
const token = "Receipt_ABCDEF123456789012345678";
safari_clear_console, safari_clear_field, safari_clear_mocks, safari_clear_network, safari_delete_cookies, safari_delete_local_storage, safari_delete_session_storage, safari_reset_emulation
.prettierignore
...new Function("browser", "_mintMcpTabMarker", body)(const fn = new Function(
return new Function(
return new Function(...Object.keys(deps), `${indexParts}\nreturn { _closeTrackedTab, _cleanupTabs };`)(...Object.values(deps));const resolveReceipt = new Function(
- **The HTTP transport refuses DNS-rebinding requests.** With `SAFARI_MCP_HTTP=1` the server bound to 127.0.0.1 and checked nothing else, and a loopback bind does not stop a web page: a page whose hos
{ "mcpServers": { "safari-mcp": { "type": "http", "url": "http://127.0.0.1:9225/mcp" } } }const bytes = Uint8Array.from(atob(dataUrl.slice(dataUrl.indexOf(",") + 1)), (c) => c.charCodeAt(0));var binary = atob(b64);
var binary = atob(b64);
@modelcontextprotocol/sdk, ws, @eslint/js, @types/node, eslint, globals, jsdom, prettier
- **The HTTP transport refuses DNS-rebinding requests.** With `SAFARI_MCP_HTTP=1` the server bound to 127.0.0.1 and checked nothing else, and a loopback bind does not stop a web page: a page whose hos
- **Shutdown cleanup no longer closes a tab of the user's that happens to sit on the URL our tab started from ([#112](https://github.com/achiya-automation/safari-mcp/issues/112)).** `_cleanupTabs()` c
Gates applied: no_behavioural_pass.
03544cc6d600full audit observations/trust-audit/mcp-server/achiya-automation__safari.json · Report an issue / request a re-scanAudit history
Every audit this server has had. A grade with a past is a grade somebody is still checking.
| Date | Source | Verdict | Grade | Score | Change |
|---|---|---|---|---|---|
| 2026-10-06 | 03544cc6d600 | BLOCK | F | 57 | first audit |
Questions
What is the Safari MCP server?
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.
What tools does Safari expose?
99 in total: 76 read-only, 15 that write, and 8 that can delete or overwrite (safari_clear_console, safari_clear_field, safari_clear_mocks, safari_clear_network, safari_delete_cookies). Every one is listed on this page with its risk.
Is Safari safe to connect to an agent?
No — not without reading the findings first. The audit graded it F (57/100) and found 1 critical or high issue in the source. Each one is listed on this page with the file and line it is on. Separately from the audit: 8 of its tools can destroy data, so scope the token you give it to what you actually need.
What credentials does Safari need?
No credential environment variables were found in its source, so it appears to need none.
How does Safari run?
It speaks stdio and streamable-http, so it runs as a local process your client starts. It is published on npm as safari-mcp at 2.22.11.
How current is this page?
The grade is for one exact copy of the source (03544cc6d600), read on 2026-10-06. The repository is watched and re-audited when it changes.