safari-mcp 2.11.0 → 2.11.1
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/index.js +19 -2
- package/package.json +1 -1
- package/safari.js +9 -0
package/index.js
CHANGED
|
@@ -127,6 +127,20 @@ function _isURLOwned(url) {
|
|
|
127
127
|
return false;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
// Sentinel persisted when a blank tab (about:blank) is opened by this session.
|
|
131
|
+
// A blank tab has no unique URL to own, but ownership must still survive an MCP
|
|
132
|
+
// process restart (_openedTabs is in-memory only) — otherwise reopening blank
|
|
133
|
+
// tabs falsely trips the "no tabs opened yet" guard. The sentinel is never a
|
|
134
|
+
// real tab URL, so it cannot falsely match a user's page in _isURLOwned().
|
|
135
|
+
const BLANK_TAB_SENTINEL = "__mcp-blank-tab__";
|
|
136
|
+
|
|
137
|
+
function _markBlankTabOpened() {
|
|
138
|
+
if (!_ownedTabURLs.has(BLANK_TAB_SENTINEL)) {
|
|
139
|
+
_ownedTabURLs.add(BLANK_TAB_SENTINEL);
|
|
140
|
+
_saveOwnershipFile(_ownedTabURLs);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
130
144
|
function _addOwnedURL(url) {
|
|
131
145
|
if (url && url !== 'about:blank' && url !== 'favorites://') {
|
|
132
146
|
_ownedTabURLs.add(url);
|
|
@@ -697,7 +711,7 @@ async function extensionOrFallback(extensionType, extensionPayload, fallbackFn)
|
|
|
697
711
|
throw new Error(msg);
|
|
698
712
|
} else if (currentUrl && !_isURLOwned(currentUrl)) {
|
|
699
713
|
// about:blank tabs are owned if we have any tracked tabs (new_tab creates them at about:blank)
|
|
700
|
-
const isBlankOwned = (currentUrl === 'about:blank' || currentUrl === 'missing value') && _openedTabs.size > 0;
|
|
714
|
+
const isBlankOwned = (currentUrl === 'about:blank' || currentUrl === 'missing value') && (_openedTabs.size > 0 || _ownedTabURLs.has(BLANK_TAB_SENTINEL));
|
|
701
715
|
if (!isBlankOwned) {
|
|
702
716
|
const msg = `⚠️ Tab safety: refusing "${extensionType}" — current tab (${currentUrl}) was not opened by this MCP session. Use safari_new_tab or safari_switch_tab to target your own tab.`;
|
|
703
717
|
console.error(`[Safari MCP] ${msg}`);
|
|
@@ -1354,6 +1368,9 @@ server.tool(
|
|
|
1354
1368
|
_addOwnedURL(trackUrl);
|
|
1355
1369
|
if (url && url !== trackUrl) _addOwnedURL(url); // also own the requested URL (handles www redirects)
|
|
1356
1370
|
}
|
|
1371
|
+
// Blank tab (no URL requested): persist a restart-surviving ownership marker.
|
|
1372
|
+
const _effectiveNewURL = (result?.url && result.url !== 'about:blank' && result.url !== 'missing value') ? result.url : url;
|
|
1373
|
+
if (!_effectiveNewURL) _markBlankTabOpened();
|
|
1357
1374
|
return { content: [{ type: "text", text: typeof rawResult === 'string' ? rawResult : JSON.stringify(result) }] };
|
|
1358
1375
|
}
|
|
1359
1376
|
);
|
|
@@ -1384,7 +1401,7 @@ server.tool(
|
|
|
1384
1401
|
const target = parsed.find(t => t.index === index);
|
|
1385
1402
|
if (target && target.url && !_isURLOwned(target.url)) {
|
|
1386
1403
|
// about:blank / missing value tabs are owned if tracked in _openedTabs
|
|
1387
|
-
const isBlankOwned = (target.url === 'about:blank' || target.url === 'missing value') && _openedTabs.has(index);
|
|
1404
|
+
const isBlankOwned = (target.url === 'about:blank' || target.url === 'missing value') && (_openedTabs.has(index) || _ownedTabURLs.has(BLANK_TAB_SENTINEL));
|
|
1388
1405
|
if (!isBlankOwned) {
|
|
1389
1406
|
const msg = `⚠️ Tab safety: refusing switch_tab to index ${index} (${target.url}) — not opened by this MCP session. Use safari_new_tab to open your own tab.`;
|
|
1390
1407
|
console.error(`[Safari MCP] ${msg}`);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "safari-mcp",
|
|
3
|
-
"version": "2.11.
|
|
3
|
+
"version": "2.11.1",
|
|
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
|
@@ -107,6 +107,15 @@ function cleanupHelper() {
|
|
|
107
107
|
// cleanupHelper runs via process.on("exit"), which fires when index.js calls process.exit().
|
|
108
108
|
process.on("exit", cleanupHelper);
|
|
109
109
|
process.on("uncaughtException", (err) => { console.error("Uncaught:", err); cleanupHelper(); process.exit(1); });
|
|
110
|
+
// Unhandled promise rejections must NOT terminate the server. A single failed
|
|
111
|
+
// async operation — e.g. a proxy fetch to the primary instance while it is
|
|
112
|
+
// mid-restart, or an aborted fetch timeout — would otherwise bubble to the
|
|
113
|
+
// uncaughtException handler above (Node's default for unhandled rejections) and
|
|
114
|
+
// exit the whole MCP process, disconnecting every concurrent session. Log and
|
|
115
|
+
// continue: the failed operation is localized, the process itself is healthy.
|
|
116
|
+
process.on("unhandledRejection", (reason) => {
|
|
117
|
+
console.error("[Safari MCP] Unhandled rejection (non-fatal, continuing):", (reason && reason.stack) || reason);
|
|
118
|
+
});
|
|
110
119
|
|
|
111
120
|
// ========== SAFARI RUNNING CHECK ==========
|
|
112
121
|
// Prevent AppleScript from auto-launching Safari when it's closed
|