safari-mcp 2.22.7 → 2.22.8
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 +0 -12
- package/index.js +48 -28
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -993,18 +993,6 @@ Other macOS MCP servers that complement Safari MCP:
|
|
|
993
993
|
|
|
994
994
|
---
|
|
995
995
|
|
|
996
|
-
## Like it? Give it a ⭐
|
|
997
|
-
|
|
998
|
-
If Safari MCP saves you from Chrome overhead, **a star helps others discover it:**
|
|
999
|
-
|
|
1000
|
-
[](https://github.com/achiya-automation/safari-mcp)
|
|
1001
|
-
|
|
1002
|
-
[Share on Twitter/X](https://twitter.com/intent/tweet?text=Safari%20MCP%20%E2%80%94%20Stop%20running%20Chrome%20just%20so%20your%20AI%20agent%20can%20browse.%2098%20tools%2C%20native%20Safari%2C%2060%25%20less%20CPU.&url=https%3A%2F%2Fgithub.com%2Fachiya-automation%2Fsafari-mcp) · [Share on LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fgithub.com%2Fachiya-automation%2Fsafari-mcp) · [Write about it](https://dev.to/)
|
|
1003
|
-
|
|
1004
|
-
[](https://star-history.com/#achiya-automation/safari-mcp&Date)
|
|
1005
|
-
|
|
1006
|
-
---
|
|
1007
|
-
|
|
1008
996
|
## Listed On
|
|
1009
997
|
|
|
1010
998
|
[](https://glama.ai/mcp/servers/achiya-automation/safari-mcp) [](https://registry.modelcontextprotocol.io/) [](https://mcpscoreboard.com/server/05977769-8762-4e89-aff3-a0c5776843bb/) [](https://github.com/achiya-automation/safari-mcp/actions/workflows/ci.yml)
|
package/index.js
CHANGED
|
@@ -1983,6 +1983,39 @@ function _sanitizeTabResult(value) {
|
|
|
1983
1983
|
};
|
|
1984
1984
|
}
|
|
1985
1985
|
|
|
1986
|
+
// A receipt is bound to the origin it was minted on, so a cross-origin navigation the
|
|
1987
|
+
// caller itself asked for used to strand every following call ("not valid for this
|
|
1988
|
+
// origin", ~80×/week). The caller chose the destination, so rotating here is safe —
|
|
1989
|
+
// hand back the new receipt instead of letting the caller discover the trap. Every
|
|
1990
|
+
// navigation a caller asks for goes through here: safari_navigate, safari_navigate_and_read
|
|
1991
|
+
// and run_script's navigate and navigateAndRead steps. `usedReceipt` is the receipt the
|
|
1992
|
+
// navigation carried, read before it waited: a call alongside that names another tab makes
|
|
1993
|
+
// that tab the session's current one, and the rotation has to name the tab that navigated.
|
|
1994
|
+
// navigate_and_read answers with its page as a JSON string; a rotated result is an object.
|
|
1995
|
+
async function _rotateReceiptAfterNavigate(result, usedReceipt, oldUrl) {
|
|
1996
|
+
let page = result;
|
|
1997
|
+
if (typeof page === "string") {
|
|
1998
|
+
try { page = JSON.parse(page); } catch { return result; }
|
|
1999
|
+
}
|
|
2000
|
+
const landed = page && typeof page === "object" ? page.url : "";
|
|
2001
|
+
// The origin of the receipt the navigation carried: extensionOrFallback sends the newest one.
|
|
2002
|
+
if (!landed || !usedReceipt || _originOf(landed) === (_receiptOrigins.get(_receiptToken(usedReceipt)) || _originOf(oldUrl))) {
|
|
2003
|
+
return result;
|
|
2004
|
+
}
|
|
2005
|
+
try {
|
|
2006
|
+
// Named for the alias below, as run_script getReceipt names it (see _receiptAliases).
|
|
2007
|
+
const fresh = _sanitizeTabResult(await extensionOrFallback(
|
|
2008
|
+
"get_tab_receipt", { ..._explicitReceipt({ receipt: usedReceipt }) }, () => null
|
|
2009
|
+
));
|
|
2010
|
+
if (fresh?.receipt) {
|
|
2011
|
+
_aliasReceipt(_receiptToken(usedReceipt), fresh.receipt); // the receipt extensionOrFallback sent
|
|
2012
|
+
_setActiveReceipt(fresh.receipt);
|
|
2013
|
+
return { ...page, receipt: fresh.receipt };
|
|
2014
|
+
}
|
|
2015
|
+
} catch { /* the navigation itself succeeded — a failed rotation must not fail the tool */ }
|
|
2016
|
+
return result;
|
|
2017
|
+
}
|
|
2018
|
+
|
|
1986
2019
|
function _isBatchSemanticFailure(result) {
|
|
1987
2020
|
if (Array.isArray(result)) return result.some(_isBatchSemanticFailure);
|
|
1988
2021
|
if (result && typeof result === "object") return result.ok === false;
|
|
@@ -2137,22 +2170,26 @@ async function _runExtensionBatchAction(action, args = {}) {
|
|
|
2137
2170
|
case "navigate": {
|
|
2138
2171
|
const url = String(args.url || "");
|
|
2139
2172
|
if (!url) throw new Error("navigate requires url");
|
|
2173
|
+
const oldUrl = safari.getActiveTabURL();
|
|
2174
|
+
const usedReceipt = _getActiveReceipt();
|
|
2140
2175
|
const raw = await extensionOrFallback(
|
|
2141
|
-
"navigate", { url, timeout: args.timeout },
|
|
2176
|
+
"navigate", { url, timeout: args.timeout, ..._explicitReceipt({ receipt: usedReceipt }) },
|
|
2142
2177
|
() => safari.navigate(url)
|
|
2143
2178
|
);
|
|
2144
|
-
return syncResultUrl(raw, url);
|
|
2179
|
+
return _rotateReceiptAfterNavigate(syncResultUrl(raw, url), usedReceipt, oldUrl);
|
|
2145
2180
|
}
|
|
2146
2181
|
|
|
2147
2182
|
case "navigateAndRead": {
|
|
2148
2183
|
const url = String(args.url || "");
|
|
2149
2184
|
if (!url) throw new Error("navigateAndRead requires url");
|
|
2185
|
+
const oldUrl = safari.getActiveTabURL();
|
|
2186
|
+
const usedReceipt = _getActiveReceipt();
|
|
2150
2187
|
const raw = await extensionOrFallback(
|
|
2151
|
-
"navigate_and_read", { url, maxLength: args.maxLength, timeout: args.timeout },
|
|
2188
|
+
"navigate_and_read", { url, maxLength: args.maxLength, timeout: args.timeout, ..._explicitReceipt({ receipt: usedReceipt }) },
|
|
2152
2189
|
async () => { await safari.navigate(url); return safari.readPage({ maxLength: args.maxLength }); }
|
|
2153
2190
|
);
|
|
2154
2191
|
safari.setActiveTabURL(url);
|
|
2155
|
-
return normalize(raw);
|
|
2192
|
+
return _rotateReceiptAfterNavigate(normalize(raw), usedReceipt, oldUrl);
|
|
2156
2193
|
}
|
|
2157
2194
|
|
|
2158
2195
|
case "readPage":
|
|
@@ -2569,32 +2606,13 @@ server.tool(
|
|
|
2569
2606
|
// Read once, before anything waits: a call alongside that names another tab makes that tab the
|
|
2570
2607
|
// session's current one, and the rotation below has to name the tab this call navigated.
|
|
2571
2608
|
const usedReceipt = _receiptToken(receipt || _getActiveReceipt());
|
|
2572
|
-
|
|
2609
|
+
const result = await extensionOrFallback(
|
|
2573
2610
|
"navigate", { url, ..._explicitReceipt({ receipt: usedReceipt }) },
|
|
2574
2611
|
() => safari.navigate(url)
|
|
2575
2612
|
);
|
|
2576
2613
|
// Tab kept its identity, just changed URL — drop the stale old URL from ownership.
|
|
2577
2614
|
if (oldUrl && oldUrl !== url && oldUrl !== 'about:blank') _removeOwnedURL(oldUrl);
|
|
2578
|
-
|
|
2579
|
-
// caller itself asked for used to strand every following call ("not valid for this
|
|
2580
|
-
// origin", ~80×/week). The caller chose the destination, so rotating here is safe —
|
|
2581
|
-
// hand back the new receipt instead of letting the caller discover the trap.
|
|
2582
|
-
const landed = result && typeof result === "object" ? result.url : "";
|
|
2583
|
-
// The origin of the receipt the navigation carried: extensionOrFallback sends the newest one.
|
|
2584
|
-
if (landed && _originOf(landed) !== (_receiptOrigins.get(_receiptToken(usedReceipt)) || _originOf(oldUrl)) && usedReceipt) {
|
|
2585
|
-
try {
|
|
2586
|
-
// Named for the alias below, as run_script getReceipt names it (see _receiptAliases).
|
|
2587
|
-
const fresh = _sanitizeTabResult(await extensionOrFallback(
|
|
2588
|
-
"get_tab_receipt", { ..._explicitReceipt({ receipt: usedReceipt }) }, () => null
|
|
2589
|
-
));
|
|
2590
|
-
if (fresh?.receipt) {
|
|
2591
|
-
_aliasReceipt(_receiptToken(usedReceipt), fresh.receipt); // the receipt extensionOrFallback sent
|
|
2592
|
-
_setActiveReceipt(fresh.receipt);
|
|
2593
|
-
result = { ...result, receipt: fresh.receipt };
|
|
2594
|
-
}
|
|
2595
|
-
} catch { /* the navigation itself succeeded — a failed rotation must not fail the tool */ }
|
|
2596
|
-
}
|
|
2597
|
-
return textResult(result);
|
|
2615
|
+
return textResult(await _rotateReceiptAfterNavigate(result, usedReceipt, oldUrl));
|
|
2598
2616
|
}
|
|
2599
2617
|
);
|
|
2600
2618
|
|
|
@@ -2678,7 +2696,7 @@ server.tool(
|
|
|
2678
2696
|
|
|
2679
2697
|
server.tool(
|
|
2680
2698
|
"safari_navigate_and_read",
|
|
2681
|
-
"Navigate 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.",
|
|
2699
|
+
"Navigate 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 `receipt` when the origin changed; use that one from then on.",
|
|
2682
2700
|
{
|
|
2683
2701
|
receipt: z.string().optional().describe("Tab receipt from safari_new_tab — pins this call to that tab (survives reconnects/subagents)"),
|
|
2684
2702
|
url: z.string().describe("URL to navigate to"),
|
|
@@ -2687,15 +2705,17 @@ server.tool(
|
|
|
2687
2705
|
},
|
|
2688
2706
|
async ({ url, maxLength, timeout, receipt }) => {
|
|
2689
2707
|
const oldUrl = safari.getActiveTabURL();
|
|
2708
|
+
// Read before anything waits, as safari_navigate does: the rotation names the tab that navigated.
|
|
2709
|
+
const usedReceipt = _receiptToken(receipt || _getActiveReceipt());
|
|
2690
2710
|
const result = await extensionOrFallback(
|
|
2691
|
-
"navigate_and_read", { url, maxLength, timeout, ..._explicitReceipt({ receipt }) },
|
|
2711
|
+
"navigate_and_read", { url, maxLength, timeout, ..._explicitReceipt({ receipt: usedReceipt }) },
|
|
2692
2712
|
async () => {
|
|
2693
2713
|
await safari.navigate(url);
|
|
2694
2714
|
return safari.readPage({ maxLength });
|
|
2695
2715
|
}
|
|
2696
2716
|
);
|
|
2697
2717
|
if (oldUrl && oldUrl !== url && oldUrl !== 'about:blank') _removeOwnedURL(oldUrl);
|
|
2698
|
-
return textResult(result);
|
|
2718
|
+
return textResult(await _rotateReceiptAfterNavigate(result, usedReceipt, oldUrl));
|
|
2699
2719
|
}
|
|
2700
2720
|
);
|
|
2701
2721
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "safari-mcp",
|
|
3
|
-
"version": "2.22.
|
|
3
|
+
"version": "2.22.8",
|
|
4
4
|
"mcpName": "io.github.achiya-automation/safari-mcp",
|
|
5
5
|
"description": "Safari browser automation for AI agents — native macOS, zero Chrome overhead. 98 tools via AppleScript + JavaScript.",
|
|
6
6
|
"type": "module",
|