safari-mcp 2.15.2 → 2.15.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -10
- package/mcp-helpers.js +34 -20
- package/package.json +3 -2
- package/safari.js +414 -157
- package/session-context.js +17 -0
- package/transport.js +4 -2
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
<img src="social-preview.png" alt="Safari MCP Server —
|
|
3
|
+
<img src="social-preview.png" alt="Safari MCP Server — 97 native browser automation tools for AI agents on macOS" width="100%">
|
|
4
4
|
|
|
5
5
|
<br/>
|
|
6
6
|
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
<a href="https://insiders.vscode.dev/redirect?url=vscode-insiders:mcp/install?%7B%22safari-mcp%22%3A%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22safari-mcp%22%5D%7D%7D"><img src="https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?logo=visual-studio-code&logoColor=white" alt="Install in VS Code Insiders"></a>
|
|
25
25
|
<a href="cursor://anysphere.cursor-deeplink/mcp/install?name=safari-mcp&config=%7B%22command%22%3A%22npx%22%2C%22args%22%3A%5B%22safari-mcp%22%5D%7D"><img src="https://img.shields.io/badge/Cursor-Install_Server-f97316?logo=cursor&logoColor=white" alt="Install in Cursor"></a>
|
|
26
26
|
|
|
27
|
-
**
|
|
27
|
+
**97 tools** · **No Chrome/Puppeteer/Playwright needed** · **~5ms per command** · **60% less CPU than Chrome**
|
|
28
28
|
|
|
29
|
-
[Quick Start](#quick-start) · [All
|
|
29
|
+
[Quick Start](#quick-start) · [All 97 Tools](#tools-97) · [Examples](examples/) · [Why Safari MCP?](#safari-mcp-vs-alternatives) · [Architecture](#architecture) · [Changelog](CHANGELOG.md)
|
|
30
30
|
|
|
31
31
|

|
|
32
32
|
|
|
@@ -44,17 +44,17 @@ Your AI agent needs to browse. So it either:
|
|
|
44
44
|
|
|
45
45
|
Your AI drives the **Safari you're already logged into** — Gmail, GitHub, Ahrefs, Slack, banking.
|
|
46
46
|
|
|
47
|
-
Native WebKit. ~60% less CPU. Background operation.
|
|
47
|
+
Native WebKit. ~60% less CPU. Background operation. 97 tools. One `npx` command. macOS only.
|
|
48
48
|
|
|
49
49
|
> 📰 **Featured on freeCodeCamp:** [How to Connect Your AI Coding Agent to a Browser on macOS](https://www.freecodecamp.org/news/how-to-connect-your-ai-coding-agent-to-a-browser-on-macos/) · [HackerNoon: Reverse-Engineering React, Shadow DOM, and CSP](https://hackernoon.com/i-had-to-reverse-engineer-react-shadow-dom-and-csp-to-automate-safari-without-chrome)
|
|
50
50
|
|
|
51
|
-
> 🍎 **Apple shipped an official Safari MCP** (Safari Technology Preview 247, July 2026). It's built on `safaridriver` for isolated debugging sessions. safari-mcp drives the **real Safari you're already logged into** — on stable Safari, with
|
|
51
|
+
> 🍎 **Apple shipped an official Safari MCP** (Safari Technology Preview 247, July 2026). It's built on `safaridriver` for isolated debugging sessions. safari-mcp drives the **real Safari you're already logged into** — on stable Safari, with 97 tools. See the full comparison below.
|
|
52
52
|
|
|
53
53
|
---
|
|
54
54
|
|
|
55
55
|
## Highlights
|
|
56
56
|
|
|
57
|
-
- **
|
|
57
|
+
- **97 tools** — navigation, clicks, forms, screenshots, network, storage, accessibility, and more
|
|
58
58
|
- **Zero heat** — native WebKit on Apple Silicon, ~60% less CPU than Chrome
|
|
59
59
|
- **Your real browser** — keeps all logins, cookies, sessions (Gmail, GitHub, Ahrefs, etc.)
|
|
60
60
|
- **Background operation** — Safari stays in the background, no window stealing
|
|
@@ -304,7 +304,7 @@ The recommended pattern for AI agents using Safari MCP:
|
|
|
304
304
|
|
|
305
305
|
---
|
|
306
306
|
|
|
307
|
-
## Tools (
|
|
307
|
+
## Tools (97)
|
|
308
308
|
|
|
309
309
|
<details>
|
|
310
310
|
<summary><b>Click to expand the full tool list — organized by category</b></summary>
|
|
@@ -382,6 +382,7 @@ The recommended pattern for AI agents using Safari MCP:
|
|
|
382
382
|
| Tool | Description |
|
|
383
383
|
|------|-------------|
|
|
384
384
|
| `safari_evaluate` | Execute arbitrary JavaScript, return result |
|
|
385
|
+
| `safari_eval_file` | Execute JavaScript read from a file path (avoids huge inline scripts) |
|
|
385
386
|
|
|
386
387
|
### Element Inspection (4)
|
|
387
388
|
| Tool | Description |
|
|
@@ -531,7 +532,7 @@ Safari MCP runs locally on your Mac with minimal attack surface:
|
|
|
531
532
|
| Your logins | ✅ Yes | ✅ Yes | ❌ No |
|
|
532
533
|
| macOS native | ✅ WebKit | ❌ Chromium | ❌ Chromium/WebKit |
|
|
533
534
|
| Browser dependencies | None | Chrome + debug port | Playwright runtime |
|
|
534
|
-
| Tools |
|
|
535
|
+
| Tools | 97 | ~30 | ~25 |
|
|
535
536
|
| File upload | JS (no dialog) | CDP | Playwright API |
|
|
536
537
|
| Image paste | JS (no clipboard) | CDP | Playwright API |
|
|
537
538
|
| Focus steal | ❌ Background | ❌ Background | ❌ Headless |
|
|
@@ -550,7 +551,7 @@ In Safari Technology Preview 247 (July 2026), Apple shipped an **official** Safa
|
|
|
550
551
|
| **Your real logins / cookies** | ✅ Your actual Safari | ⚠️ Isolated automation session — no access to AutoFill or browsing activity |
|
|
551
552
|
| **Runs on** | ✅ Stable Safari, every Mac | ❌ Safari Technology Preview 247 only |
|
|
552
553
|
| **Background (no focus steal)** | ✅ Yes | ❌ Dedicated window with a "controlled by automation" banner |
|
|
553
|
-
| **Tools** | **
|
|
554
|
+
| **Tools** | **97** | ~17 |
|
|
554
555
|
| **Storage** (cookies, localStorage, IndexedDB) | ✅ 10 tools | ❌ |
|
|
555
556
|
| **Network mocking + throttling** | ✅ Yes | ❌ Read-only network inspection |
|
|
556
557
|
| **Device emulation** (iPhone, iPad) | ✅ Yes | ⚠️ Viewport + media type only |
|
|
@@ -565,7 +566,7 @@ There are several "safari-mcp" projects floating around. Here's how they compare
|
|
|
565
566
|
|
|
566
567
|
| Feature | **🦁 safari-mcp** *(this repo)* | [lxman/safari-mcp-server](https://github.com/lxman/safari-mcp-server) | [Epistates/MCPSafari](https://github.com/Epistates/MCPSafari) | [HayoDev/safari-devtools-mcp](https://github.com/HayoDev/safari-devtools-mcp) |
|
|
567
568
|
|---------|:------------------------------:|:----------------------:|:------------:|:----------------------:|
|
|
568
|
-
| **Tools** | **
|
|
569
|
+
| **Tools** | **97** | ~10 | 23 | ~15 |
|
|
569
570
|
| **Install** | `npx safari-mcp` | Manual | Binary | `npx` |
|
|
570
571
|
| **Engine** | **Dual** (Extension + AppleScript) | WebDriver | Extension only | DevTools Protocol |
|
|
571
572
|
| **Keeps your real Safari logins** | ✅ Yes | ⚠️ Limited | ✅ Yes | ❌ Debug session |
|
|
@@ -847,3 +848,7 @@ If Safari MCP saves you from Chrome overhead, **a star helps others discover it:
|
|
|
847
848
|
## License
|
|
848
849
|
|
|
849
850
|
MIT — use it however you want.
|
|
851
|
+
|
|
852
|
+
---
|
|
853
|
+
|
|
854
|
+
Built by **[Achiya Automation](https://achiya-automation.com)** — automation & AI agents for business.
|
package/mcp-helpers.js
CHANGED
|
@@ -144,28 +144,42 @@ if (window.__mcpVersion !== 6) {
|
|
|
144
144
|
var beforeUrl = location.href;
|
|
145
145
|
var anchor = target.closest ? target.closest('a[href]') : null;
|
|
146
146
|
var href = anchor && anchor.href && !anchor.href.startsWith('javascript:') ? anchor.href : '';
|
|
147
|
+
// Dispatch from the LEAF under the click point, not from the actionable ancestor.
|
|
148
|
+
// A real click's `event.target` is the topmost node at that pixel — the inner label —
|
|
149
|
+
// while `currentTarget` is the <a>/<button>. Handlers that gate on e.target (Facebook's
|
|
150
|
+
// rel="dialog" links, most SPA routers) see a target they never expected when the event
|
|
151
|
+
// is fired straight at the <a>, bail out, and the click silently degrades into a plain
|
|
152
|
+
// navigation. Firing from the leaf reproduces the real bubble path exactly.
|
|
153
|
+
var from = window.mcpElementFromPoint(x, y);
|
|
154
|
+
if (!from || !(target === from || target.contains(from))) from = target;
|
|
147
155
|
var s = { bubbles: true, cancelable: true, composed: true, view: window, clientX: x, clientY: y, button: 0, detail: 1 };
|
|
148
156
|
var p = { ...s, pointerId: 1, pointerType: 'mouse', isPrimary: true, width: 1, height: 1, pressure: 0.5 };
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
+
from.dispatchEvent(new PointerEvent('pointerover', { ...p, buttons: 0 }));
|
|
158
|
+
from.dispatchEvent(new MouseEvent('mouseover', { ...s, buttons: 0 }));
|
|
159
|
+
from.dispatchEvent(new PointerEvent('pointerenter', { ...p, buttons: 0 }));
|
|
160
|
+
from.dispatchEvent(new MouseEvent('mouseenter', { ...s, buttons: 0 }));
|
|
161
|
+
from.dispatchEvent(new PointerEvent('pointermove', { ...p, buttons: 0 }));
|
|
162
|
+
from.dispatchEvent(new MouseEvent('mousemove', { ...s, buttons: 0 }));
|
|
163
|
+
from.dispatchEvent(new PointerEvent('pointerdown', { ...p, buttons: 1 }));
|
|
164
|
+
from.dispatchEvent(new MouseEvent('mousedown', { ...s, buttons: 1 }));
|
|
165
|
+
// Focus the actionable element, not the leaf — a <span> inside a button isn't focusable.
|
|
157
166
|
if (target.focus) { try { target.focus({ preventScroll: true }); } catch (e) { try { target.focus(); } catch (_) {} } }
|
|
158
|
-
|
|
159
|
-
|
|
167
|
+
from.dispatchEvent(new PointerEvent('pointerup', { ...p, buttons: 0, pressure: 0 }));
|
|
168
|
+
from.dispatchEvent(new MouseEvent('mouseup', { ...s, buttons: 0 }));
|
|
169
|
+
// .click() on an anchor (or a node inside one) triggers the browser's own navigation
|
|
170
|
+
// with target === the anchor, which is the exact shape the gate rejects. For anchors,
|
|
171
|
+
// let the dispatched event below carry the default action instead.
|
|
160
172
|
try {
|
|
161
|
-
if (typeof
|
|
162
|
-
target.click();
|
|
163
|
-
if (href && href !== beforeUrl) {
|
|
164
|
-
location.href = href;
|
|
165
|
-
}
|
|
166
|
-
}
|
|
173
|
+
if (!href && typeof from.click === 'function') from.click();
|
|
167
174
|
} catch (e) {}
|
|
168
|
-
|
|
175
|
+
var clickEv = new MouseEvent('click', { ...s, buttons: 0 });
|
|
176
|
+
var notPrevented = from.dispatchEvent(clickEv);
|
|
177
|
+
// Navigate only if nothing claimed the click. A handler that called preventDefault owns
|
|
178
|
+
// it — it just opened a dialog or pushed an SPA route, and forcing location.href here
|
|
179
|
+
// would tear that down. (dispatchEvent returns false iff preventDefault was called.)
|
|
180
|
+
if (href && notPrevented && location.href === beforeUrl) {
|
|
181
|
+
location.href = href;
|
|
182
|
+
}
|
|
169
183
|
// Vue v-model / Lit @change / framework-agnostic toggle sync — for checkbox/radio,
|
|
170
184
|
// Vue 3 listens for `change` via v-model; some Vue components subscribe to `input` instead.
|
|
171
185
|
// Native target.click() fires both, but in production-stripped Vue apps (no fiber/proxy
|
|
@@ -178,9 +192,9 @@ if (window.__mcpVersion !== 6) {
|
|
|
178
192
|
target.dispatchEvent(new Event('input', toggleOpts));
|
|
179
193
|
target.dispatchEvent(new Event('change', toggleOpts));
|
|
180
194
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
195
|
+
// (Navigation is handled above, gated on notPrevented. A second unconditional
|
|
196
|
+
// `location.href = href` used to live here and fired even when a handler had called
|
|
197
|
+
// preventDefault — tearing down the dialog/route it had just opened.)
|
|
184
198
|
var form = target.closest ? target.closest('form') : null;
|
|
185
199
|
if (form && (target.type === 'submit' || (target.tagName === 'BUTTON' && target.type !== 'button' && target.type !== 'reset'))) {
|
|
186
200
|
try { form.requestSubmit ? form.requestSubmit(target.type === 'submit' ? target : undefined) : form.submit(); } catch (e) {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "safari-mcp",
|
|
3
|
-
"version": "2.15.
|
|
3
|
+
"version": "2.15.3",
|
|
4
4
|
"mcpName": "io.github.achiya-automation/safari-mcp",
|
|
5
5
|
"description": "Safari browser automation for AI agents — native macOS, zero Chrome overhead. 96 tools via AppleScript + JavaScript.",
|
|
6
6
|
"type": "module",
|
|
@@ -66,6 +66,7 @@
|
|
|
66
66
|
"transport.js",
|
|
67
67
|
"response.js",
|
|
68
68
|
"safari.js",
|
|
69
|
+
"session-context.js",
|
|
69
70
|
"ownership-match.js",
|
|
70
71
|
"ownership-state.js",
|
|
71
72
|
"mcp-helpers.js",
|
|
@@ -95,6 +96,6 @@
|
|
|
95
96
|
"globals": "^17.6.0",
|
|
96
97
|
"jsdom": "^29.1.1",
|
|
97
98
|
"prettier": "^3.8.4",
|
|
98
|
-
"typescript": "^
|
|
99
|
+
"typescript": "^7.0.2"
|
|
99
100
|
}
|
|
100
101
|
}
|