Async fixes + injection hardening + tab safety
Release history
achiya-automation/safari-mcp releases
Native Safari browser automation for AI agents with 80+ tools. No Chrome dependency, optimized for Apple Silicon with 60% less CPU overhead.
All releases
70 shown
Fixed `safari_fill` dropping middle paragraphs in ProseMirror editors and prevented duplicate text on synthetic paste.
Full changelog
Fixed
safari_fill no longer loses middle paragraphs in ProseMirror/Tiptap editors with no view access (Hashnode-style)
When a ProseMirror-based editor doesn't expose its view via pmViewDesc or React Fiber walk (Hashnode's contenteditable has neither), safari_fill previously fell through to char-by-char beforeinput + execCommand('insertText') per line. On multi-paragraph content with markdown-like first characters (>, **, [), the editor dropped middle paragraphs silently — the result was the first paragraph + a chain of empty paragraphs + the last paragraph.
The fix verifies actual content length after the char-by-char pass; if the rendered text is less than 60% of the expected value length, it clears the editor and re-fills via execCommand('insertHTML') with paragraph-wrapped HTML. The new return marker Filled CE (ProseMirror insertHTML fallback, <actual>/<expected>) makes the path observable.
Originally surfaced cross-posting a dev.to article body to a Hashnode draft — char-by-char filled 446 chars out of an expected 6800+, retaining only the first and last paragraphs.
safari_fill synthetic-paste path now explicitly clears pre-existing content before paste
X's tweetTextarea_0 at /intent/post?text=... pre-fills the textarea with the URL's text parameter before the page settles. Previous fill logic positioned cursor at the end of the existing content (via selectNodeContents + collapse) but didn't delete the selection before dispatching the synthetic paste event — most editors replace selection on paste, but X's React handler appended, resulting in duplicated text (<URL prefill> <safari_fill value>).
The fix adds an explicit document.execCommand('delete', false, null) after the selection is set and before the ClipboardEvent('paste') dispatch. Affects any contenteditable whose initial content needs to be replaced via the synthetic-paste path (Quill in some configurations, X composer with URL-prefilled text, similar React-driven editors).
Install: npx safari-mcp (auto-updates) or pin npx [email protected]
Source: https://github.com/achiya-automation/safari-mcp
Routine maintenance release for achiya-automation/safari-mcp.
Changelog
Full Changelog: https://github.com/achiya-automation/safari-mcp/compare/v2.10.2...v2.10.3
Routine maintenance release for achiya-automation/safari-mcp.
Changelog
Full Changelog: https://github.com/achiya-automation/safari-mcp/compare/v2.10.1...v2.10.2
Routine maintenance release for achiya-automation/safari-mcp.
Changelog
Full Changelog: https://github.com/achiya-automation/safari-mcp/compare/v2.10.0...v2.10.1
Routine maintenance release for achiya-automation/safari-mcp.
Changelog
Full Changelog: https://github.com/achiya-automation/safari-mcp/compare/v2.9.3...v2.10.0
Fixed safari_fill functionality inside dialog-hosted rich editors (LinkedIn share composer) by routing through native Cmd+V pipeline.
Full changelog
Fixed
-
safari_fill now works inside dialog-hosted rich editors (LinkedIn share composer). Three issues previously made LinkedIn posting impossible:
- The contenteditable fallback dispatched a blur event at the end — LinkedIn's composer listens for focusout and dismisses the dialog.
- When ProseMirror was detected but its EditorView could not be reached via pmViewDesc or the React Fiber walk, fill fell back to beforeinput+execCommand char-by-char (isTrusted:false, rejected by ProseMirror).
- The synthetic ClipboardEvent('paste') returned 'Filled CE (synthetic paste)' even when ProseMirror called preventDefault without accepting the payload.
Fix: when the contenteditable sits inside a [role="dialog"], route through the existing CGEvent Cmd+V pipeline (_nativeTypeViaClipboard). Real paste, isTrusted:true, window-targeted so no focus steal. The blur dispatch is removed — React detects the change from input alone.
Routine maintenance release for achiya-automation/safari-mcp.
Changelog
Full Changelog: https://github.com/achiya-automation/safari-mcp/compare/v2.9.1...v2.9.2
Routine maintenance release for achiya-automation/safari-mcp.
Changelog
Full Changelog: https://github.com/achiya-automation/safari-mcp/compare/v2.9.0...v2.9.1
Routine maintenance release for achiya-automation/safari-mcp.
Changelog
Full Changelog: https://github.com/achiya-automation/safari-mcp/compare/v2.8.9...v2.9.0
Routine maintenance release for achiya-automation/safari-mcp.
Changelog
Full Changelog: https://github.com/achiya-automation/safari-mcp/compare/v2.8.8...v2.8.9
Minor fixes and improvements.
Full changelog
What's Changed
- ci: bump actions/setup-node from 6.3.0 to 6.4.0 by @dependabot[bot] in https://github.com/achiya-automation/safari-mcp/pull/24
Full Changelog: https://github.com/achiya-automation/safari-mcp/compare/v2.8.5...v2.8.8
Minor fixes and improvements.
Full changelog
Research-backed overhaul of the README hero section — no API/tool/code changes.
What changed
- Tagline shortened from 11 words to Serena-style category claim: "The browser for your coding agent." A second italic line carries the wedges: "Your real Safari, logged in — no Chrome, no heat, no headless."
- Badges reduced from ~10 above the fold to 4 core (npm version, downloads, MIT, macOS). Others moved to "Listed On" at the bottom.
- "❌ Without / ✅ With Safari MCP" block added immediately after the hero — Context7's killer pattern. Frames against Playwright, Chrome DevTools MCP, and headless scrapers.
- 80-tool list collapsed into
<details>— still fully documented, but no longer dominates the scroll. Top-level content now skimmable in under 30 seconds. - Star-scolding block removed — "Less than 1% star it" was an anti-pattern per every top README analyzed. Kept the silent star-history chart at the bottom.
- "What agents unlock with Safari MCP" new section — Serena-style framing of capabilities an LLM benefits from (authenticated sessions, framework-aware form setters, background operation, batched workflow calls).
- Community count corrected from "2,000+ monthly" → "6,000+ monthly" to match current npm downloads.
Reference
Patterns distilled from: Context7 (49K⭐), Serena (23K⭐), Desktop Commander (5.9K⭐), BrowserMCP (6K⭐).
- Upgrade immediately from v2.8.3; form‑driven workflows are broken due to the SESSION_ID ReferenceError.
Full changelog
Fixed
SESSION_ID is not definedruntime error onsafari_new_tab— introduced by v2.8.3's bulletproof tab marker. The marker code atsafari.js:2151referencesSESSION_ID, but that const was only declared inindex.js(not exported), so the separatesafari.jsES module threwReferenceError: SESSION_ID is not definedwhenever a new tab went through the marker path.
Symptom
safari_new_tab returned "SESSION_ID is not defined" but the tab still opened in Safari. Subsequent calls then failed the tab-ownership safety check, making the MCP unusable for form-driven workflows (HubSpot AEO, Semrush AI Visibility, GTmetrix, Mangools AI Search Grader, etc.).
Fix
Declare a local const SESSION_ID = randomUUID().slice(0, 8) in safari.js. Each ES module gets its own — the marker only needs per-process uniqueness.
How it was caught
Real /seo-geo run that needed 5 form-driven browser checks across HubSpot, Mangools, Semrush, GTmetrix, and Google Search Console. All 5 failed immediately after safari_new_tab.
Upgrade immediately if you were on v2.8.3 — form-driven workflows are broken on that version.
Fixed tab tracking bug where `safari_evaluate` could target the wrong tab after redirects or new tabs.
Full changelog
🐛 Bug fix: tab tracking via window.__mcpTabMarker
Discovered during the v2.8.2 launch campaign that `safari_evaluate` could land on the wrong tab when `safari_new_tab` and `safari_evaluate` were called more than ~500 ms apart and a popup/redirect added a new tab in the meantime.
Root cause
- `resolveActiveTab` relied on URL prefix matching, which fails when:
- The page redirects (e.g. LinkedIn `/feed/` → `/feed/?shareActive=true`)
- Query strings change (login redirects, share params)
- Multiple tabs share the same domain
- The 500 ms cache masked the race in many cases but not all.
Fix
- Every `safari_new_tab` now writes `window.__mcpTabMarker` = unique ID after the page loads. The marker survives same-tab navigation, redirects, and `pushState` changes — it's tied to the JS context, not the URL.
- `resolveActiveTab` tries the marker FIRST: it asks the cached tab "are you my marker?", and if not, walks all tabs in the profile window looking for the marker.
- Resolve cache reduced from 500 ms → 100 ms. The marker check is cheap (~5 ms), so the tighter cache adds negligible latency.
URL/domain matching stays as a fallback for tabs created before the marker was set, or when the marker is wiped by a hard reload.
Full diff: https://github.com/achiya-automation/safari-mcp/compare/v2.8.2...v2.8.3
Minor fixes and improvements.
Full changelog
The new HackerNoon technical deep-dive launches today.
📰 Featured Article
I Had to Reverse-Engineer React, Shadow DOM, and CSP to Automate Safari Without Chrome
The article walks through the three hardest problems behind safari-mcp:
- React's `_valueTracker` — why `input.value = "x"` doesn't fire onChange in React, and the tracker-reset + native property descriptor + event sequence fix
- Shadow DOM piercing — recursive multi-root collector with MutationObserver-backed caching (~15ms per query saved)
- CSP bypass — 4-strategy fallback chain: direct eval → Trusted Types script injection → Web Worker → AppleScript nuclear option
🎨 README Updates
- Prominent "Featured on HackerNoon" badge added to the badge row
- Quote linking to the article right under the TL;DR
- Deep-dive link in the star CTA section
Full diff: https://github.com/achiya-automation/safari-mcp/compare/v2.8.1...v2.8.2
- Automatic sync of server.json version to match package.json
- Download and use mcp-publisher binary in CI workflow
- Publish every GitHub release to both npm and registry.modelcontextprotocol.io
Full changelog
Patch release that wires up automatic publishing to registry.modelcontextprotocol.io on every GitHub release.
Why
Until now, `server.json` had to be bumped manually and `mcp-publisher` had to be run by hand for every release. As a result, versions 2.7.7 → 2.8.0 never made it to the MCP Registry — only 2.7.6 was visible there.
What changed
`.github/workflows/release.yml` now has three new steps after the existing npm publish:
- Sync server.json version to match `package.json` automatically (single source of truth).
- Download mcp-publisher binary from the official release.
- Login via GitHub OIDC and `mcp-publisher publish` to registry.modelcontextprotocol.io.
From v2.8.1 onward, every GitHub release lands on both npm and the MCP Registry automatically.
Full diff: https://github.com/achiya-automation/safari-mcp/compare/v2.8.0...v2.8.1
- Postinstall welcome banner (scripts/postinstall.cjs) with star CTA, silenced via SAFARI_MCP_SILENT_INSTALL=1
- Once‑per‑day startup banner in index.js printed to stderr, suppressed by SAFARI_MCP_QUIET=1
- Added smithery.yaml deployment configuration leveraging existing smithery-entry.js
Full changelog
This release is a focused push to convert Safari MCP's healthy ~4,300 npm downloads/month into actual GitHub stars and visible community signals.
✨ Added
- Postinstall welcome banner (`scripts/postinstall.cjs`) — printed once after `npm install` with next-step setup and a star CTA. Skipped silently in CI and when `SAFARI_MCP_SILENT_INSTALL=1`.
- Once-per-day startup banner in `index.js` — written to stderr (never stdout, MCP protocol untouched) on the first server start of the day. Includes version, capability summary, and a one-line star link. Suppressed by `SAFARI_MCP_QUIET=1`.
- `smithery.yaml` — Smithery deployment config (uses the existing `smithery-entry.js` to avoid top-level `await` issues).
🎨 Changed
README revamp for discoverability
- New social-proof badge row: MCP Registry, Glama, Awesome MCP, CLI-Anything.
- Prominent star CTA block right after Quick Start (was buried at line 568).
- New "Why Safari MCP and Not the Other Safari MCP Projects?" comparison table — clarifies how this project differs from `lxman/safari-mcp-server`, `Epistates/MCPSafari`, and `HayoDev/safari-devtools-mcp`.
- Removed duplicate "vs. Chrome DevTools MCP / Playwright MCP" section.
GitHub topics
Updated for better organic discovery — added `claude-code`, `safari-mcp`, `mcp` to the topic set.
🔇 Opt-out flags
- `SAFARI_MCP_SILENT_INSTALL=1` — skip postinstall banner
- `SAFARI_MCP_QUIET=1` — skip startup banner
Full diff: https://github.com/achiya-automation/safari-mcp/compare/v2.7.16...v2.8.0
- Removed the native `Enter` fallback that activated Safari; `safari_press_key enter` now dispatches a JavaScript keydown event and returns an `isTrusted:false` rejection instead of bringing Safari to foreground.
Full changelog
Changed
- Removed the native Enter fallback that activated Safari.
safari_press_key enteron contenteditable now dispatches the JS keydown and returns a clear message if the app didn't handle it (isTrusted:falserejection), instead of briefly activating Safari to send a real keystroke. This eliminates ALL focus-stealing from Safari MCP — no operation will ever bring Safari to the foreground.
Why
Discord/Slack require isTrusted:true on keyboard events, which is only possible when Safari is the frontmost app. The previous fallback (activate → keystroke → restore) worked but caused a visible ~130ms flash of Safari appearing. Users reported this as disruptive. The correct approach: MCP fills the editor in the background, then the user presses Enter in Safari when ready.
Full changelog: v2.7.15…v2.7.16
Fixed safari_press_key Enter to work on Discord, Slack, and other isTrusted-gated editors by falling back to a native keystroke.
Full changelog
Fixed
safari_press_key Enter now works on Discord, Slack, and other isTrusted-gated editors. When the JS keydown isn't handled (rejected as isTrusted:false), falls back to briefly activating Safari (~80ms), sending a real keystroke, then immediately restoring the previous app. Total flash <130ms.
Full Discord chain now works end-to-end:
- safari_new_tab → Discord channel
- safari_click → focus editor
- pbcopy + safari_press_key cmd+v → paste (Slate state-aware)
- safari_press_key enter → submit (native fallback, auto-restore)
- After `npm update`, re‑grant Automation permission using the provided `osascript` one‑liner if "Not authorized" errors appear.
- `com.apple.security.automation.apple-events` entitlement added to `safari-helper`
- Entitlement file now ships with npm package for source builds
Full changelog
Fixed
safari-helpernow includescom.apple.security.automation.apple-eventsentitlement. This helps macOS correctly identify the binary's intent and surface the TCC Automation prompt more reliably on first launch from an IDE. Previously, the ad-hoc signature had no entitlements, causing some setups to silently deny Apple Events without ever prompting.- Added troubleshooting note for "Not authorized" errors after
npm update— updating changes the binary's cdhash, which causes macOS to silently revoke Automation permission. Users need to re-grant via theosascriptone-liner. - Entitlements file (
safari-helper.entitlements) now ships with the npm package for users building from source.
Full Changelog: https://github.com/achiya-automation/safari-mcp/compare/v2.7.13...v2.7.14
Fixed Enter key behavior in contenteditable to let apps handle submission without inserting a line break.
Full changelog
Fixed
safari_press_key Enter on contenteditable no longer inserts a line break. Modern editors (Discord Slate, Slack, Notion) handle Enter in their own keydown listener to trigger submit/send. The old fallback execCommand('insertLineBreak') double-acted: the app tries to submit AND MCP adds a newline — corrupting editor state and preventing submission.
Now:
- INPUT → form submit (unchanged)
- TEXTAREA → insertLineBreak (unchanged)
- ContentEditable + Enter → keydown event only, let the app decide (fixed)
- ContentEditable + Shift+Enter → insertLineBreak (newline)
Full changelog: v2.7.12…v2.7.13
- `safari_native_type` inserts text via OS-level clipboard paste, integrating with ProseMirror/Slate/Draft.js models and preserving user clipboard
Full changelog
Added
safari_native_type— inserts text via OS-level clipboard paste (CGEvent Cmd+V targeted to Safari window). Goes through the real browser paste pipeline so ProseMirror/Slate/Draft.js update their internal model state. Afternative_type, pressing Enter vianative_keyboardactually submits the form. Saves and restores the user's clipboard. No focus stealing.
Why this matters
Closes the last gap in the Discord/Slack automation chain:
safari_hover→ find server by tooltipsafari_click→ enter channelsafari_native_type→ paste message (state-aware, not just DOM)safari_native_keyboard {key: "enter"}→ submit (no focus steal)
Previously step 3 used safari_fill which wrote to the DOM but not to Discord's ProseMirror internal state — leading to empty submissions on Enter.
Full changelog: v2.7.11…v2.7.12
- `safari_native_keyboard` provides OS-level keyboard events via macOS CGEvent to a specific Safari window, producing `isTrusted: true` without stealing focus
Full changelog
Added
safari_native_keyboard— OS-level keyboard event via macOS CGEvent, targeted to the Safari window ID without activating Safari. ProducesisTrusted: trueevents that bypass React trust checks in Discord ProseMirror, Slack virtualized editors, and similar trust-gated UIs. Supports all common keys (enter, tab, escape, arrows, letters, digits, punctuation) and modifiers (cmd, shift, alt, ctrl). No focus stealing — runs entirely in the background.
Why this matters
Operations that required a real keypress previously had no zero-focus-steal path. Pressing Enter in Discord, Slack, or any ProseMirror-backed editor forced a fallback to osascript "tell application \"Safari\" to activate" which brings Safari to the foreground and interrupts whatever the user is doing.
safari_native_keyboard closes that gap. It uses the same CGEvent path as safari_native_click and safari_native_hover — event is posted directly to the Safari process with the target window ID, so Safari stays in the background and the user's focus is never disturbed.
Full changelog: v2.7.10…v2.7.11
- `safari_native_hover` OS‑level mouse move via macOS CGEvent for real `:hover` handling
- Tab ownership persisted to `~/.safari-mcp/owned-tabs.json` with 30‑minute TTL across MCP restarts
- `safari-helper` rebuilt targeting macOS 12.0+ (arm64) fixing crashes on older macOS versions
Full changelog
Added
safari_native_hover— OS-level mouse move via macOS CGEvent. Triggers real:hover/mouseenterhandlers on obfuscated UIs (Discord server sidebars, portal-rendered tooltips, virtualized React components) where JS-dispatched events alone aren't enough to reveal tooltips. Dwells for a configurable duration so tooltips can render, then optionally restores the cursor position. Complementssafari_native_clickusing the same CGEvent path.
Fixed
- Tab ownership now persists across MCP process restarts. Previously, every time Claude Code (or any other MCP client) recycled the Safari MCP server, the in-memory
_ownedTabURLsset was wiped. The next tool call then failed with⚠️ Tab safety: no tabs opened yeteven though the previously-opened tab was still live in Safari. Ownership is now persisted to~/.safari-mcp/owned-tabs.jsonwith a 30-minute TTL and hydrated on startup. safari-helpernow targets macOS 12.0+ (root cause of #15). v2.7.9 shipped a helper compiled withminos 26.0becauseswiftcdefaults to the current SDK. That binary silently failed to launch on macOS 12–15, producing the exact crash loop that @alex-konkov reported (safari-helper crashed (restart #1, retrying in 500ms)...). The v2.7.10 helper is built withswiftc -O -target arm64-apple-macos12.0and runs on Monterey, Ventura, Sonoma, Sequoia, and Tahoe.
Why this matters
Both fixes remove real blockers for driving complex SPAs from an MCP client:
- Native hover unlocks Discord, Slack, and any UI that puts critical info (tooltips, reveal states, custom dropdowns) behind a CSS
:hoveror real pointer position. - Persistent tab ownership removes the "first tool call after any restart fails" paper cut that forced callers to preemptively re-open tabs and re-establish state.
- macOS 12+ helper closes the compatibility gap that made safari-mcp unusable on anything older than macOS 26 — which was most of the world, since macOS 26 is the current release.
Full changelog: v2.7.9…v2.7.10
- -E flag removed from `pgrep` invocation in memory monitor
Full changelog
Fixes
- #15
pgrep -Eflag removed — the memory monitor was emittingpgrep: illegal option -- Eon every check because macOS pgrep uses extended regex by default and the-Eflag doesn't exist. Thanks @alex-konkov for the report. - #18 Extension popup was stuck on "Checking..." because MV3
script-src 'self'CSP blocks inline scripts. The logic is now inpopup/popup.js— byte-for-byte identical to the previous inline code. Thanks @mikhailkogan17 for the fix. safari-helperdaemon re-signed with an explicit ad-hoc code signature so macOS TCC has a stable cdhash to track Automation permissions.
Documentation
- #16 Added Automation → Safari to the macOS Permissions table in the README, plus the
osascriptworkaround to register a parent IDE with TCC. Thanks @mikhailkogan17 for the diagnosis. - #17 Added
codesign --sign - --force --deepto the extension build instructions so Safari actually loads the.appbundle produced byxcodebuild. Thanks @mikhailkogan17 and @Lionad-Morotar for the workaround.
Security & infrastructure
- Pinned
hono/@hono/node-serverviapackage.jsonoverrides to silence 6 transitive dependabot advisories (not exploitable — safari-mcp only usesStdioServerTransport). - Added
.github/CODEOWNERSand expanded Dependabot to cover GitHub Actions. - Publishing to npm now uses OIDC Trusted Publisher — no long-lived
NPM_TOKEN. Releases gate through a manual-approvalnpm-publishenvironment. - Branch protection on
main: signed commits required, no force push, no deletion. - Outside collaborator workflows require approval before running.
- All action versions pinned to commit SHAs.
Full changelog: v2.7.8…v2.7.9
Fixed LinkedIn field filling and added beforeinput event handling for better contenteditable compatibility.
Full changelog
Improved
- ProseMirror/Tiptap fill: React Fiber walk to discover EditorView + char-by-char beforeinput fallback
- LinkedIn support: Fixes fill on Shadow DOM + Quill/ProseMirror editors
- beforeinput events: Added to execCommand fallback for better contenteditable compatibility
Fixed React Fiber traversal depth limit to support deep portal trees and enriched synthetic click events with additional methods.
Full changelog
Fixed
- React Fiber traversal depth increased from 10 to 20 — React portals (modals, dialogs, tooltips) can have deeply nested fiber trees. This fix ensures safari_click correctly triggers onClick handlers on portal buttons.
- Enriched synthetic click event — Added nativeEvent, isDefaultPrevented(), and isPropagationStopped() to the synthetic React event for better compatibility.
- Added Dockerfile for MCP introspection testing (Glama, Smithery)
Full changelog
- Added Dockerfile for MCP introspection testing (Glama, Smithery)
- Bumped ws to 8.20.0
- Bumped @modelcontextprotocol/sdk to 1.28.0
Fixed VS Code focus stealing from Safari by removing redundant daemon-level focus guard.
Full changelog
Bug Fixes
-
Fix focus stealing from Safari — The swift daemon (
safari-helper) was saving/restoring focus on every AppleScript call, including background checks every 3 seconds. This caused VS Code to steal focus whenever the user switched to Safari. Removed the daemon-level focus guard — the Node.js layer already handles focus preservation during tool calls only. -
Fix Google Workspace services breaking — The content script's
attachShadowmonkey-patch (for closed shadow DOM capture) was injected into all pages including Gmail, Calendar, Chat, and Meet. These services use closed shadow DOM heavily and the patch interfered with their initialization (Gmail error 1010). Addedexclude_matchesfor all affected Google Workspace domains.
What's Changed
Full Changelog: https://github.com/achiya-automation/safari-mcp/compare/v2.7.4...v2.7.5
- TypeScript type declarations (index.d.ts) added
Full changelog
What's Changed
- feat: add TypeScript type declarations (index.d.ts) by @mahek395 in https://github.com/achiya-automation/safari-mcp/pull/11
New Contributors
- @mahek395 made their first contribution in https://github.com/achiya-automation/safari-mcp/pull/11
Full Changelog: https://github.com/achiya-automation/safari-mcp/compare/v2.7.3...v2.7.4
Routine maintenance release for achiya-automation/safari-mcp.
Changelog
Full Changelog: https://github.com/achiya-automation/safari-mcp/compare/v2.7.1...v2.7.3
Fixed FIFO mismatch when helper queue callbacks time out.
Full changelog
- Clipboard double-release fix in _nativeTypeViaClipboard
- clipboardRead now acquires lock (prevents reading during restore)
- Helper queue: timed-out callbacks → no-op consumers (fixes FIFO mismatch)
- Native paste sleep: 300ms → 100ms
- screenshotElement: cropFile cleaned in error path
- Profile verification: JSON parse wrapped in try/catch
- onRemoved: now also cleans _sessionOwnedTabs
- Result delivery: logging instead of silent swallow
- Zero Focus Stealing: No operation steals focus or moves the mouse.
- Clipboard Protection: Mutex lock for concurrent operations and reduced restore window to 2 seconds with immediate native type restoration.
- Tab Ghost Prevention: Proactive tab count tracking with cache invalidation and smart 500 ms resolve caching.
Full changelog
Zero Focus Stealing
No operation in Safari MCP will ever steal your focus or move your mouse.
Clipboard Protection
- Mutex lock prevents concurrent clipboard operations
- Restore window reduced from 5s to 2s, native type restores immediately
Focus Elimination
- savePDF rewritten: screencapture + Python Quartz (no activate, no System Events)
- CGEvent windowId=0 fallback removed (moved mouse)
- All native operations throw if window ID unavailable
Tab Ghost Prevention
- Proactive tab count tracking with cache invalidation on open/close
- Smart 500ms resolve cache (was always re-resolving)
about:blank Dynamic Wait
- Dynamic polling (300ms × 10) instead of fixed 1s sleep
Memory Monitoring
- Warning at 70%, checks every 30s, closes up to 2 tabs
Extension Keepalive (3-Layer)
- fetch polling + storage heartbeat (20s) + alarms (60s)
Fixed tab ownership guard in AppleScript mode and allowed same-origin redirects to be recognized.
Full changelog
Bug Fixes
- Tab ownership guard broken in AppleScript mode —
safari_new_tabreturned a JSON string from AppleScript fallback, but the handler expected an object.result?.urlwas alwaysundefined, so_addOwnedURLwas never called. This caused every subsequentsafari_click,safari_fill,safari_evaluateetc. to be blocked with "no tabs opened yet" error. - Same-origin redirects rejected — When a page redirected after opening (e.g.
/login/device→/login/device/select_account), the ownership check rejected the new URL. Added subpath matching for same-origin URLs.
MCP Registry
- Published to the Official MCP Registry as
io.github.achiya-automation/safari-mcpv2.6.1 - PulseMCP ingests from this registry daily
npm
npx [email protected]
- Write operations (navigate, click, fill) are blocked on tabs not opened by MCP
Full changelog
Tab Ownership Guard
Prevents MCP from operating on tabs it didn't open.
How it works
- Tracks tabs opened via safari_new_tab
- Write operations (navigate, click, fill) blocked on non-owned tabs
- Read-only operations (screenshot, read_page, snapshot) allowed on any tab
- Dual enforcement: URL-based (index.js) + tab ID per session (extension)
Routine maintenance release for achiya-automation/safari-mcp.
Changelog
Full Changelog: https://github.com/achiya-automation/safari-mcp/compare/v2.5.2...v2.5.3
Routine maintenance release for achiya-automation/safari-mcp.
Changelog
Full Changelog: https://github.com/achiya-automation/safari-mcp/compare/v2.5.1...v2.5.2
- Click support in cross‑origin iframes via Extension allFrames fallback with zero focus stealing
Changelog
Click in cross-origin iframes via Extension allFrames fallback. Zero focus stealing.
- CGEvent native keyboard support in `performNativeKeyboard()` for typing/pasting into cross‑origin iframes without focus change
- Daemon JSON protocol now accepts a `"keyboard"` section with keyCode, flags, and windowId
- CLI shortcut `safari-helper --paste --window <WID>` added
Full changelog
What's New
CGEvent Native Keyboard — Type into Cross-Origin Iframes Without Stealing Focus
The Swift helper now supports CGEvent keyboard events, enabling:
- No focus stealing:
typeTextandpressKey(Cmd+V) into cross-origin iframes (Intercom, Zendesk, etc.) without activating Safari or interrupting the user's work - Background operation: Keystrokes are sent directly to the Safari window via PID targeting, same as how
nativeClickalready works
Technical Details
- New
performNativeKeyboard()insafari-helper.swift _nativeTypeViaClipboarduses CGEvent paste instead of System Events activate- Daemon JSON protocol:
{"keyboard": {"keyCode": 9, "flags": ["cmd"], "windowId": 1234}} - CLI shortcut:
safari-helper --paste --window WID
Full Changelog: https://github.com/achiya-automation/safari-mcp/compare/v2.4.0...v2.5.0
Fixed fill() to throw a clear error when called without selector or reference.
Full changelog
Highlights
- Deleted ~300 lines of dead code (
_LEGACY_INLINE_START) — 11KB savings per startup
Bug Fixes
fill()now throws clear error when called without selector/ref (was TypeError crash)_helperNativeClickstdin.writableguard (prevents permanently-pending promises)- Extension
go_back/go_forwardnow update session URL cache mcpIsActionablecheckspointer-events: none(elements visible but not clickable)- Extension click checks
visibility: collapse+ usesparseFloatfor opacity navigateAndReadsuppressesonbeforeunloaddialogs
Reliability
- Signal handlers deduplicated (prevents double
process.exiton rapid SIGTERM) MAX_BODY_SIZEmoved to module scope- Screenshot
.png→.jpguses regex anchor (/\.png$/)
- _validateFilePath used require() in ES module context, causing crashes on every upload/paste call
Full changelog
Critical
- BREAKING FIX: _validateFilePath used require() in ES module — crashed on every upload/paste call since v2.3.0
- DoS prevention: HTTP POST body size capped at 10MB
High
- Memory monitor non-functional: pgrep needs -E flag for regex alternation
- Memory monitor tab close: resolves by URL not stale index
- Proxy polling: stops self-scheduling when already hosting
Medium
- Snapshot __mcpRefsTime in AppleScript path, profile test tab leak fix, wait_for_new_tab state sync, signal handler consolidation, dialog dismiss order
- Fixed AppleScript injection by whitelisting profile name in verify-profile endpoint
- Blocked sensitive file paths in upload/paste tools via enhanced validation
- Resolved helper queue race condition by pushing only after confirming stdin writable
Full changelog
Security Fixes
- AppleScript injection — Profile name whitelisted in verify-profile endpoint
- File path validation — Block sensitive paths in upload/paste tools
- Helper queue race — Push only after confirming stdin writable
Bug Fixes
- Tab cleanup — Close by URL not stale index
- Closure editor fill — Escape backslashes in selector
- Full-page screenshot — Restore bounds via try/finally
- Import storage — runJSLarge for large sessions
- Snapshot generation — Fix double increment
- goBack/goForward/reload — Update _activeTabURL
- Drag and drop — Add dragstart/dragover/dragend + fix escaping
- handleDialog — Fix monkey-patch chain
- resetEmulation — Fix user-agent reset no-op
- Opacity/waitForTime/scroll_to_element — Various fixes
Reliability
- Helper daemon — Kill only after 3 consecutive timeouts
- waitForTabLoad — Check status before registering listener
- Server version — Read from package.json
Fixed cookie import for multi-cookie pages, corrected URL tracking in navigation and new tabs, unified toolbar height to 74px.
Full changelog
Bug Fixes
- Cookie import —
safari_import_storagenow sets cookies one at a time (was broken for multi-cookie pages) - navigateAndRead URL tracking —
safari_navigate_and_readnow correctly updates internal tab URL tracking - Snapshot ref consistency — Extension and AppleScript engines now use the same generation-based ref prefix
- New tab URL tracking — Extension correctly tracks requested URL when tab initially shows
about:blank - Wait for new tab —
safari_wait_for_new_tabdetectsabout:blanktabs (OAuth popups) - Toolbar height — Unified to 74px (was 52px in
screenshotElement) - Duplicate signal handlers — Consolidated shutdown handlers
- Command timeouts — Added missing
replace_editortimeout
Performance
- Extension deep query injection — Skips re-injection on already-injected tabs
- Memory monitor — Direct
pgrep+psinstead ofbash | grep | awk
Maintainability
- INJECT_MCP_HELPERS extracted to
mcp-helpers.js— proper JS file with syntax highlighting and linting
Routine maintenance release for achiya-automation/safari-mcp.
Changelog
Full Changelog: https://github.com/achiya-automation/safari-mcp/compare/v2.1.7...v2.1.8
Routine maintenance release for achiya-automation/safari-mcp.
Changelog
Full Changelog: https://github.com/achiya-automation/safari-mcp/compare/v2.1.6...v2.1.7
- Fixed path-to-regexp HIGH severity ReDoS vulnerability
Full changelog
Security
- npm tokens scoped to safari-mcp only (no more full-access tokens)
- Branch protection enabled on
main(no force push) - Dependabot enabled for dependency monitoring
- Vulnerability alerts enabled
- Fixed
path-to-regexpHIGH severity ReDoS vulnerability
Project Infrastructure
- GitHub Actions CI workflow (macOS-latest, Node 18)
- 5 practical usage examples in
examples/ CHANGELOG.md— full version history from 1.0.0CONTRIBUTING.md— setup & PR guidelines- Bug report and feature request issue templates
- 13 GitHub topics for discoverability
npm Install
npm install -g safari-mcp
Full Changelog: https://github.com/achiya-automation/safari-mcp/blob/main/CHANGELOG.md
- Synthetic ClipboardEvent paste fallback for modern contenteditable editors (Hashnode, HackerNoon, Notion) to enable correct handling by ProseMirror-based editors
Full changelog
Adds synthetic ClipboardEvent paste fallback for modern contenteditable editors (Hashnode, HackerNoon, Notion). The fill command now sends a proper paste event that ProseMirror-based editors handle correctly.
Fixed crash when two MCP instances start by only killing truly stale (>10 seconds) processes.
Full changelog
Problem: Claude Code VSCode starts 2 MCP instances simultaneously. Singleton detection killed the first, crashing both connections.
Fix: Only kill instances running >10s (truly stale). Fresh sibling instances preserved.
Fix: Kills stale MCP instances on startup to prevent zombie process accumulation.
Full changelog
Fix: Singleton — kill stale MCP instances on startup
מונע הצטברות של תהליכי זומבי כשנפתחים סשנים חדשים של Claude Code.
- On startup, the MCP server finds and kills any stale instances of itself
- Prevents memory bloat from zombie processes accumulating across sessions
- Uses
execFileSync("pgrep")for safe process detection (no shell injection)
Routine maintenance release for achiya-automation/safari-mcp.
Changelog
Full Changelog: https://github.com/achiya-automation/safari-mcp/compare/v2.1.1...v2.1.2
- Official MCP Registry at registry.modelcontextprotocol.io
- Cursor Directory listed as an MCP plugin
- Dual-engine architecture documented (Extension vs AppleScript)
Full changelog
What's New
- Official MCP Registry — Safari MCP is now published on
registry.modelcontextprotocol.io - npm package — install with
npm install safari-mcpornpx safari-mcp - Cursor Directory — listed as an MCP plugin
- Dual-engine architecture documented — README now explains Extension vs AppleScript
- Safari Extension docs — installation instructions with Xcode build
mcp.json+server.json— Open Plugins & MCP Registry manifests- Social preview — new project image
Extension Improvements
- Reconnect exponential backoff (prevents timer growth)
- 90s safety timeout on poll connections
- Overlay click pattern detection (transparent buttons over text labels)
evaluatewrapper fix for multi-statement scripts in Safari
Install
npm install safari-mcp
Or clone:
git clone https://github.com/achiya-automation/safari-mcp.git
cd safari-mcp && npm install
- 80+ tools for native Safari browser automation
- Zero Chrome overhead using AppleScript + JavaScript on macOS
- Dual engine: Safari Extension (~5-20ms) and AppleScript daemon fallback (~5ms)
Full changelog
Safari MCP Server v2.0.1
What's New
- Added
glama.jsonfor Glama directory listing - Minor stability improvements
Features
- 80+ tools for native Safari browser automation
- Zero Chrome overhead — uses AppleScript + JavaScript natively on macOS
- Keeps all your logins, cookies, and sessions
- Dual engine: Safari Extension (~5-20ms) + AppleScript daemon (~5ms fallback)
- Drop-in alternative to Chrome DevTools MCP with 40-60% less CPU/heat on Apple Silicon
- Requires macOS 13 (Ventura) or later
- Enable Safari Develop menu → Allow JavaScript from Apple Events
- Node.js version must be 18 or higher
- 80 tools covering navigation, clicks, forms, screenshots, JavaScript execution, storage management, network mocking, console logging, data extraction, and device emulation
- Zero overhead by using Apple's native WebKit via AppleScript—no Chromium or Playwright required
- Runs silently in the background without stealing focus and provides 40-60% lower CPU/heat on Apple Silicon
Full changelog
Safari MCP Server v1.0.0
Native Safari browser automation for AI agents via Model Context Protocol (MCP).
Highlights
- 80 tools — navigation, clicks, forms, screenshots, JavaScript execution, network mocking, storage management, and more
- Zero overhead — uses Apple's native WebKit via AppleScript. No Chromium, no Playwright, no headless browser
- Keeps your logins — works with your real Safari profile. Log in once, automate forever
- Runs silently — never steals focus or interrupts your work. Tab management happens in the background
- 40-60% less CPU/heat compared to Chrome DevTools MCP on Apple Silicon
Tools by Category
| Category | Count | Examples |
|----------|-------|---------|
| Navigation | 4 | navigate, go_back, reload |
| Click & Input | 9 | click, fill, type_text, press_key, double_click, right_click |
| Screenshots | 3 | screenshot, screenshot_element, save_pdf |
| Tab Management | 4 | list_tabs, new_tab, switch_tab, close_tab |
| JavaScript | 2 | evaluate, run_script (multi-step) |
| Storage | 9 | cookies, localStorage, sessionStorage, export/import |
| Network | 6 | capture, mock_route, throttle, performance_metrics |
| Console | 3 | start_console, get_console, filter |
| Data Extraction | 4 | extract_tables, extract_meta, extract_images, extract_links |
| Device Emulation | 2 | emulate (iPhone, iPad, Pixel), reset |
| Advanced | 10+ | accessibility_snapshot, css_coverage, detect_forms, geolocation override |
Quick Start
{
"mcpServers": {
"safari": {
"command": "node",
"args": ["/path/to/safari-mcp/index.js"]
}
}
}
Requirements
- macOS 13+ (Ventura or later)
- Safari → Develop → Allow JavaScript from Apple Events
- Node.js 18+
- Requires macOS with Safari
- Node.js 18+ is required
- Enable Safari → Develop → Allow JavaScript from Apple Events
- 80 tools spanning navigation, click/interaction, form input, screenshots/PDF, scroll, tab management, wait conditions, JavaScript execution, element inspection, accessibility, drag & drop, file operations, dialog/window handling, device emulation, cookies/storage, clipboard, network control, console logging, performance measurement, data extraction, and advanced automation
- Background operation without window stealing or focus changes
- Persistent osascript with ~5 ms per command latency
Full changelog
Safari MCP v2.0.0
Native Safari browser automation for AI agents — 80 tools, zero Chrome overhead.
Highlights
- 80 tools — the most comprehensive Safari automation MCP available
- Native macOS — AppleScript + JavaScript, no Chrome/Puppeteer/Playwright
- Your real browser — keeps all logins, cookies, and sessions
- Background operation — no window stealing, no focus changes
- Persistent osascript — ~5ms per command (vs ~80ms with process spawning)
- JS-first input — typing and clicking via JavaScript events (no keyboard conflicts)
- Framework-compatible — React, Vue, Angular form filling via native setters
Tool Categories
| Category | Count |
|----------|-------|
| Navigation | 4 |
| Page Reading | 3 |
| Click & Interaction | 5 |
| Form Input | 7 |
| Screenshots & PDF | 3 |
| Scroll | 3 |
| Tab Management | 4 |
| Wait | 2 |
| JavaScript | 1 |
| Element Inspection | 4 |
| Accessibility | 1 |
| Drag & Drop | 1 |
| File Operations | 2 |
| Dialog & Window | 2 |
| Device Emulation | 2 |
| Cookies & Storage | 11 |
| Clipboard | 2 |
| Network | 6 |
| Console | 4 |
| Performance | 2 |
| Data Extraction | 4 |
| Advanced | 5 |
| Automation | 1 |
Quick Start
git clone https://github.com/achiya-automation/safari-mcp.git
cd safari-mcp && npm install
Add to your MCP config (~/.mcp.json):
{
"mcpServers": {
"safari": {
"command": "node",
"args": ["/path/to/safari-mcp/index.js"]
}
}
}
Requirements
- macOS with Safari
- Node.js 18+
- Safari → Develop → Allow JavaScript from Apple Events