safari-mcp 2.8.1 → 2.8.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 +5 -0
- package/package.json +1 -1
- package/safari.js +41 -1
package/README.md
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
[](https://registry.modelcontextprotocol.io/)
|
|
18
18
|
[](https://glama.ai/mcp/servers/g0upg7gyex)
|
|
19
19
|
[](https://github.com/punkpeye/awesome-mcp-servers)
|
|
20
|
+
[](https://hackernoon.com/i-had-to-reverse-engineer-react-shadow-dom-and-csp-to-automate-safari-without-chrome)
|
|
20
21
|
[](https://github.com/HKUDS/CLI-Anything/pull/212)
|
|
21
22
|
|
|
22
23
|
<a href="vscode: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-Install_Server-0078d4?logo=visual-studio-code&logoColor=white" alt="Install in VS Code"></a>
|
|
@@ -33,6 +34,8 @@
|
|
|
33
34
|
|
|
34
35
|
> **TL;DR:** Use your real Safari with all your logins, cookies, and sessions. No headless browsers, no Chrome, no Puppeteer. Just native AppleScript + JavaScript on macOS — 60% less CPU/heat on Apple Silicon.
|
|
35
36
|
|
|
37
|
+
> 📰 **Featured on HackerNoon:** [I Had to Reverse-Engineer React, Shadow DOM, and CSP to Automate Safari Without Chrome](https://hackernoon.com/i-had-to-reverse-engineer-react-shadow-dom-and-csp-to-automate-safari-without-chrome) — the three hardest technical problems behind Safari MCP and the code that solved them.
|
|
38
|
+
|
|
36
39
|
### 🤔 Why not just use Playwright or Chrome DevTools MCP?
|
|
37
40
|
|
|
38
41
|
| Problem | Safari MCP Solution |
|
|
@@ -136,6 +139,8 @@ cd safari-mcp && npm install
|
|
|
136
139
|
*4,000+ developers install Safari MCP every month. Less than 1% star it.*
|
|
137
140
|
*If it saves you Chrome heat, give it back a click.*
|
|
138
141
|
|
|
142
|
+
📰 **[Read the deep-dive on HackerNoon →](https://hackernoon.com/i-had-to-reverse-engineer-react-shadow-dom-and-csp-to-automate-safari-without-chrome)**
|
|
143
|
+
|
|
139
144
|
</div>
|
|
140
145
|
|
|
141
146
|
---
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "safari-mcp",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.3",
|
|
4
4
|
"mcpName": "io.github.achiya-automation/safari-mcp",
|
|
5
5
|
"description": "Safari browser automation for AI agents — native macOS, zero Chrome overhead. 80 tools via AppleScript + JavaScript.",
|
|
6
6
|
"type": "module",
|
package/safari.js
CHANGED
|
@@ -156,7 +156,8 @@ let _activeTabIndex = null; // null = use front document (default)
|
|
|
156
156
|
let _activeTabURL = null; // URL-based tracking (stable even when tabs shift)
|
|
157
157
|
let _lastResolveTime = 0; // Cache: skip resolve if verified recently
|
|
158
158
|
let _lastTabCount = null; // Track tab count for smart cache invalidation
|
|
159
|
-
|
|
159
|
+
let _activeTabMarker = null; // window.__mcpTabMarker — survives same-tab navigation, bulletproof tab identity
|
|
160
|
+
const RESOLVE_CACHE_MS = 100; // Brief cache — was 500, reduced to catch tabs added by user/popups (v2.8.3 fix)
|
|
160
161
|
|
|
161
162
|
// ========== DIAGNOSTIC LOG ==========
|
|
162
163
|
// File-based log for profile/focus issues — survives MCP restart, visible to user
|
|
@@ -331,7 +332,41 @@ export function setActiveTabURL(url) { _activeTabURL = url; _lastResolveTime = D
|
|
|
331
332
|
|
|
332
333
|
// Resolve our tracked URL to current tab index — single combined osascript call
|
|
333
334
|
async function resolveActiveTab() {
|
|
335
|
+
if (!_activeTabURL && !_activeTabMarker) return _activeTabIndex;
|
|
336
|
+
|
|
337
|
+
// Strategy 1: window.__mcpTabMarker (bulletproof — survives navigation, redirects, query changes)
|
|
338
|
+
// Only attempted if we have a marker AND a cached index hint
|
|
339
|
+
if (_activeTabMarker && _activeTabIndex) {
|
|
340
|
+
try {
|
|
341
|
+
const safeMarker = _activeTabMarker.replace(/'/g, "\\'");
|
|
342
|
+
// Single JS call: check cached index first, then walk all tabs to find marker
|
|
343
|
+
const checkScript = `(function(){return window.__mcpTabMarker==='${safeMarker}'?'1':'0'})()`;
|
|
344
|
+
const matchAtCached = await osascriptFast(
|
|
345
|
+
`tell application "Safari" to do JavaScript "${checkScript}" in tab ${_activeTabIndex} of ${getTargetWindowRef()}`
|
|
346
|
+
).catch(() => '0');
|
|
347
|
+
if (String(matchAtCached).trim() === '1') return _activeTabIndex;
|
|
348
|
+
// Cached index doesn't match — scan all tabs for marker
|
|
349
|
+
const tabCountStr = await osascriptFast(
|
|
350
|
+
`tell application "Safari" to return count of tabs of ${getTargetWindowRef()}`
|
|
351
|
+
).catch(() => '0');
|
|
352
|
+
const tabCount = Number(tabCountStr) || 0;
|
|
353
|
+
for (let i = tabCount; i >= 1; i--) {
|
|
354
|
+
const m = await osascriptFast(
|
|
355
|
+
`tell application "Safari" to do JavaScript "${checkScript}" in tab ${i} of ${getTargetWindowRef()}`
|
|
356
|
+
).catch(() => '0');
|
|
357
|
+
if (String(m).trim() === '1') {
|
|
358
|
+
_activeTabIndex = i;
|
|
359
|
+
_lastTabCount = tabCount;
|
|
360
|
+
return i;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// Marker not found anywhere — page reloaded or tab closed
|
|
364
|
+
_activeTabMarker = null;
|
|
365
|
+
} catch { /* fall through to URL strategy */ }
|
|
366
|
+
}
|
|
367
|
+
|
|
334
368
|
if (!_activeTabURL) return _activeTabIndex;
|
|
369
|
+
|
|
335
370
|
try {
|
|
336
371
|
const safeUrl = _activeTabURL.replace(/"/g, '\\"');
|
|
337
372
|
const domain = _activeTabURL.replace(/^https?:\/\//, '').split('/')[0].replace(/"/g, '\\"');
|
|
@@ -2112,6 +2147,11 @@ export async function newTab(url = "") {
|
|
|
2112
2147
|
_activeTabURL = url || null;
|
|
2113
2148
|
_lastResolveTime = Date.now();
|
|
2114
2149
|
_lastTabCount = Number(tabCount); // Update tab count cache
|
|
2150
|
+
// Set bulletproof tab marker — survives same-tab navigation, redirects, query changes (v2.8.3)
|
|
2151
|
+
_activeTabMarker = `MCP_${SESSION_ID}_${Date.now().toString(36)}_${Math.random().toString(36).slice(2, 8)}`;
|
|
2152
|
+
await osascriptFast(
|
|
2153
|
+
`tell application "Safari" to do JavaScript "window.__mcpTabMarker='${_activeTabMarker}'" in tab ${_activeTabIndex} of ${getTargetWindowRef()}`
|
|
2154
|
+
).catch(() => {});
|
|
2115
2155
|
// Wait for page load if URL given
|
|
2116
2156
|
if (url) {
|
|
2117
2157
|
try {
|