Atlas / MCP servers / achiya-automation / safari-mcp

safari-mcpBLOCK

mcp/achiya-automation/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.

Verdict
BLOCK
Grade
F
Trust score
57 /100
Exposed tools
99 76r · 15w · 8d
Transport
stdio · streamable-http
License
MIT
Stars
210
01

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

Read from source at commit 88ce49c36309OBSERVED · 2026-10-07
02

Connect

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-code (npm)
claude mcp add safari-mcp -- npx -y [email protected]
03

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.

ToolRiskDescription
pingreadreturns pong
safari_accessibility_snapshotreadGet the accessibility tree of the page (roles, ARIA labels, focusable elements, form states). Essential for a11y auditing.
safari_analyze_pagereadFull 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_pwareadAudit the page for iOS
safari_clear_consoledestructiveClear all captured console messages
safari_clear_fielddestructiveClear an input field
safari_clear_mocksdestructiveRemove all network route mocks (restore real network behavior)
safari_clear_networkdestructiveClear all captured network requests
safari_clickreadClick 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_readreadClick 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_waitreadClick an element AND wait for the result (page load or element). Use instead of click + wait_for separately.
safari_clipboard_readreadRead the current clipboard content (text)
safari_clipboard_writewriteWrite text to the system clipboard
safari_close_tabwriteClose the current tab. After a daemon/session restart, pass the opaque receipt returned by safari_new_tab or safari_list_tabs.
safari_console_filterreadGet console messages filtered by level (must call safari_start_console first)
safari_css_coveragereadAnalyze CSS coverage: find unused CSS rules across all stylesheets. Shows coverage percentage per stylesheet.
safari_delete_cookiesdestructiveDelete a specific cookie or all cookies for the current page
safari_delete_local_storagedestructiveDelete a localStorage key, or clear all localStorage (omit key to clear all)
safari_delete_session_storagedestructiveDelete a sessionStorage key, or clear all sessionStorage (omit key to clear all)
safari_detect_formsreadAuto-detect all forms on the page with their fields, types, selectors, and submit buttons. Great for automated form filling.
safari_doctorreadDiagnose 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_clickreadDouble-click an element by CSS selector or x/y coordinates (e.g. to select a word in text)
safari_dragreadDrag an element to another element or position. Use CSS selectors or x/y coordinates.
safari_emulatereadEmulate a mobile device: sizes the Safari window to the device
safari_eval_filewriteExecute 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_evaluateread
safari_export_storagereadExport all storage state (cookies + localStorage + sessionStorage) as JSON — useful for saving and restoring login sessions
safari_extract_imagesreadExtract all images with src, alt, dimensions, loading strategy, viewport visibility
safari_extract_linksreadExtract all links with href, text, rel, target, external/nofollow detection
safari_extract_metareadExtract all meta tags: title, description, canonical, OG tags, Twitter cards, JSON-LD, alternate languages, RSS feeds
safari_extract_tablesreadExtract HTML tables as structured JSON (headers + rows). Perfect for scraping data tables.
safari_fillread
safari_fill_and_submitwriteFill a form AND submit it in one operation. Finds submit button automatically if not specified.
safari_fill_formreadFill multiple form fields at once
safari_get_computed_stylereadGet computed CSS styles for an element. Optionally filter specific properties.
safari_get_consolereadGet captured console messages (must call safari_start_console first)
safari_get_cookiesreadGet cookies for the current page
safari_get_elementreadGet detailed info about an element (tag, text, rect, attributes, visibility)
safari_get_indexed_dbreadRead records from an IndexedDB database store
safari_get_sourcereadGet HTML source of current page
safari_go_backreadGo back in browser history
safari_go_forwardreadGo forward in browser history
safari_handle_dialogwriteSet up handler for the next alert/confirm/prompt dialog
safari_hoverreadHover over element. Use ref, selector, or x/y
safari_import_storagewriteImport storage state from JSON (as exported by safari_export_storage) — restores cookies, localStorage, sessionStorage
safari_inspect_viewportreadValidate the page
safari_list_framesreadList 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_dbsreadList all IndexedDB databases on the current page
safari_list_tabsreadList every tab in this session
safari_local_storagereadGet localStorage data for the current page
safari_mock_routereadIntercept 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_clickread
safari_native_hoverreadOS-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_keyboardreadOS-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_typeread
safari_navigatereadNavigate this session
safari_navigate_and_readreadNavigate 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_networkreadQuick 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_detailsreadGet captured network requests with full details (must call safari_start_network_capture first)
safari_new_tabreadOpen a new background tab (never steals focus), optionally with a URL. Returns {tabIndex, safeUrl, receipt}. KEEP THE RECEIPT and pass it as
safari_override_geolocationreadOverride the browser
safari_paste_imagereadPaste 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_metricsreadGet detailed performance metrics: navigation timing, Web Vitals (FCP, LCP, CLS), resource breakdown, memory usage
safari_press_keyreadPress a keyboard key (enter, tab, escape, arrows, etc). Supports modifiers (cmd, shift, alt, ctrl).
safari_query_allreadFind all elements matching a CSS selector (returns tag, text, href, value). x/y are relative to each element
safari_react_select_list_optionsreadList 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_setwrite
safari_read_pagereadRead 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_reloadreadReload the current page
safari_reload_extensionread
safari_replace_editorread
safari_reset_emulationdestructiveReset device emulation: restores the window size from before safari_emulate and removes its navigator overrides (no reload)
safari_resizereadResize the Safari window
safari_right_clickreadRight-click (context menu) an element by CSS selector or x/y coordinates
safari_run_scriptwrite
safari_safe_area_insetsreadRead 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_pdfwriteSave the current page as a PDF file. Uses screencapture + PDF rendering (no Safari UI interaction needed).
safari_screenshotreadTake 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_elementreadTake a screenshot of a specific element (by CSS selector). Returns a base64 image.
safari_scrollreadScroll the page up or down by a specified amount
safari_scroll_toreadScroll to a specific position on the page
safari_scroll_to_elementreadScroll to element by CSS selector OR text. For virtual DOM (Airtable) use text — scrolls down until text appears in DOM.
safari_select_optionreadSelect an option in a native <select> dropdown. Sets .value and dispatches change event. Pass
safari_session_storagereadGet sessionStorage data for the current page
safari_set_cookiewriteSet a cookie on the current page
safari_set_local_storagewriteSet a value in localStorage
safari_set_session_storagewriteSet a value in sessionStorage
safari_snapshotreadPREFERRED 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_consolewriteStart capturing console messages (log, warn, error, info). Call once per page.
safari_start_network_capturewriteStart 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_tabreadSwitch the MCP session
safari_throttle_networkreadSimulate slow network conditions. Profiles: slow-3g, fast-3g, 4g, offline. Or custom latency/speed. Call with no args to reset.
safari_type_textread
safari_upload_filewriteUpload a file to a <input type=
safari_verify_statereadVerify 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_waitreadWait 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_forreadWait for an element or text to appear on the page
safari_wait_for_new_tabreadWait for a new tab to appear (e.g. after OAuth login click opens popup). Automatically switches to the new tab.
safari_webkit_compatreadCheck 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.
04

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.

LayerWhat it checksResult
L0Provenance & inventoryWARN
L1Static analysis of the codeFAIL
L2Instruction surface (what it tells the agent)PASS
L3Class-specific surfaceWARN
L4Behavioural (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)

HIGHFilesystem / path · fs.credential_store · CWE-22, CWE-59
safari.js:5067
const blocked = ['.ssh', '.gnupg', '.aws', '.config/gcloud', 'credentials', '.env', '.npmrc', '.netrc', 'id_rsa', 'id_ed25519', '.keychain'];
Why it matters. touches a credential store
MEDIUMInventory / provenance · inv.binary · CWE-1104
safari-helper
safari-helper
Why it matters. a compiled or binary member cannot be reviewed from source
Fix. ship source, or explain the binary in the README
MEDIUMNetwork egress · net.raw_ip · CWE-200, CWE-319
extension/background.js:9
let HTTP_URL = `http://127.0.0.1:${BRIDGE_PORTS[0]}`;
MEDIUMNetwork egress · net.raw_ip · CWE-200, CWE-319
extension/background.js:590
...BRIDGE_PORTS.map((port) => `http://127.0.0.1:${port}`),
MEDIUMNetwork egress · net.raw_ip · CWE-200, CWE-319
index.js:1118
const connectUrl = new URL(req.url, `http://127.0.0.1:${HTTP_PORT}`);
MEDIUMHard-coded secrets · secret.generic · CWE-798, CWE-321
test/close-tab-ownership.test.mjs:231
const token = "opaque_receipt_token_1234567890";
MEDIUMHard-coded secrets · secret.generic · CWE-798, CWE-321
test/profile-extension-routing.test.mjs:947
const token = "receipt_token_abcdefghijklmnopqrstuvwxyz";
MEDIUMHard-coded secrets · secret.generic · CWE-798, CWE-321
test/receipt-refusal-reason.test.mjs:30
const TOKEN = "Receipt_ABCDEF1234567890abcdef";
MEDIUMHard-coded secrets · secret.generic · CWE-798, CWE-321
test/stateless-batch.test.mjs:113
const token = "Receipt_ABCDEF123456789012345678";
MEDIUMHard-coded secrets · secret.generic · CWE-798, CWE-321
test/stateless-batch.test.mjs:139
const token = "Receipt_ABCDEF123456789012345678";
MEDIUMFilesystem / path · mcp.destructive_tools · CWE-22, CWE-59
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
Why it matters. 8 tool(s) can delete or overwrite
Fix. prefer a read-only mode or scoped tokens; the page states the blast radius
LOWInventory / provenance · inv.hidden_file · CWE-1104
.prettierignore
.prettierignore
Why it matters. hidden member outside the usual dotfiles
Fix. review its purpose
LOWCode injection · code.eval_exec · CWE-78, CWE-94, CWE-95
test/browser-epoch-rotation.test.mjs:68
...new Function("browser", "_mintMcpTabMarker", body)(
Why it matters. evaluates text as code
Fix. remove; use a parser or a dispatch table
LOWCode injection · code.eval_exec · CWE-78, CWE-94, CWE-95
test/cleanup-tab-identity.test.mjs:55
const fn = new Function(
Why it matters. evaluates text as code
Fix. remove; use a parser or a dispatch table
LOWCode injection · code.eval_exec · CWE-78, CWE-94, CWE-95
test/close-by-marker.test.mjs:120
return new Function(
Why it matters. evaluates text as code
Fix. remove; use a parser or a dispatch table
LOWCode injection · code.eval_exec · CWE-78, CWE-94, CWE-95
test/close-by-marker.test.mjs:147
return new Function(...Object.keys(deps), `${indexParts}\nreturn { _closeTrackedTab, _cleanupTabs };`)(...Object.values(deps));
Why it matters. evaluates text as code
Fix. remove; use a parser or a dispatch table
LOWCode injection · code.eval_exec · CWE-78, CWE-94, CWE-95
test/close-tab-moved-origin.test.mjs:73
const ext = new Function(
Why it matters. evaluates text as code
Fix. remove; use a parser or a dispatch table
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
CHANGELOG.md:116
- **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
LOWNetwork egress · net.raw_ip · CWE-200, CWE-319
README.md:328
{ "mcpServers": { "safari-mcp": { "type": "http", "url": "http://127.0.0.1:9225/mcp" } } }
LOWObfuscation / stealth · obf.decode_call · CWE-506, CWE-94
extension/background.js:4603
const bytes = Uint8Array.from(atob(dataUrl.slice(dataUrl.indexOf(",") + 1)), (c) => c.charCodeAt(0));
LOWObfuscation / stealth · obf.decode_call · CWE-506, CWE-94
safari.js:5204
var binary = atob(b64);
LOWObfuscation / stealth · obf.decode_call · CWE-506, CWE-94
safari.js:5419
var binary = atob(b64);
LOWSupply chain · supply.unpinned · CWE-829, CWE-1357
package.json
@modelcontextprotocol/sdk, ws, @eslint/js, @types/node, eslint, globals, jsdom, prettier
Why it matters. 9 dependency range(s) float
Fix. pin exact versions or ship a lockfile
LOWPrompt injection · prompt.transfer_instruction · CWE-94, CWE-1427
CHANGELOG.md:116
- **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
Why it matters. an instruction to move sensitive data to an outside destination
Fix. remove; a skill never needs the user's secrets off the machine
LOWPrompt injection · prompt.transfer_instruction · CWE-94, CWE-1427
CHANGELOG.md:136
- **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
Why it matters. an instruction to move sensitive data to an outside destination
Fix. remove; a skill never needs the user's secrets off the machine

Gates applied: no_behavioural_pass.

Audited 2026-10-07 · audit v0.4.1 · source sha 88ce49c36309full audit observations/trust-audit/mcp-server/achiya-automation__safari-mcp.json · Report an issue / request a re-scan
05

Audit history

Every audit this server has had. A grade with a past is a grade somebody is still checking.

DateSourceVerdictGradeScoreChange
2026-10-0788ce49c36309BLOCKF57first audit
06

Questions

What is the safari-mcp 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-mcp 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-mcp 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-mcp need?

No credential environment variables were found in its source, so it appears to need none.

How does safari-mcp 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.13.

How current is this page?

The grade is for one exact copy of the source (88ce49c36309), read on 2026-10-07. The repository is watched and re-audited when it changes.

Advertisement