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.
Files changed (3) hide show
  1. package/README.md +0 -12
  2. package/index.js +48 -28
  3. 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
- [![Star this repo](https://img.shields.io/github/stars/achiya-automation/safari-mcp?style=social)](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
- [![Star History Chart](https://api.star-history.com/svg?repos=achiya-automation/safari-mcp&type=Date)](https://star-history.com/#achiya-automation/safari-mcp&Date)
1005
-
1006
- ---
1007
-
1008
996
  ## Listed On
1009
997
 
1010
998
  [![Glama](https://glama.ai/mcp/servers/achiya-automation/safari-mcp/badges/score.svg)](https://glama.ai/mcp/servers/achiya-automation/safari-mcp) [![MCP Registry](https://img.shields.io/badge/MCP-Registry-purple)](https://registry.modelcontextprotocol.io/) [![MCP Scoreboard](https://mcpscoreboard.com/badge/05977769-8762-4e89-aff3-a0c5776843bb.svg)](https://mcpscoreboard.com/server/05977769-8762-4e89-aff3-a0c5776843bb/) [![CI](https://github.com/achiya-automation/safari-mcp/actions/workflows/ci.yml/badge.svg)](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
- let result = await extensionOrFallback(
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
- // A receipt is bound to the origin it was minted on, so a cross-origin navigation the
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.7",
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",