safari-mcp 2.22.6 → 2.22.7

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 (2) hide show
  1. package/index.js +24 -8
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1861,10 +1861,16 @@ function _safeUrlForOutput(rawUrl) {
1861
1861
  // Receipts this daemon rotated on the caller's behalf (a cross-origin safari_navigate) stay
1862
1862
  // usable under their old name: callers routinely keep the first receipt they saw and ignore
1863
1863
  // the fresh one in the navigate result (nine consecutive "not valid for this origin" failures
1864
- // in one session on 4.9.26). Whoever held the old capability held the tab.
1864
+ // in one session on 4.9.26). Whoever held the old capability held the tab. Every session that
1865
+ // held it follows the alias, other clients of an HTTP daemon included (_getActiveReceipt), so an
1866
+ // alias may only join two receipts of one tab: each rotation names the receipt it replaces.
1865
1867
  const _receiptAliases = new Map(); // old token → newer token
1866
1868
  function _aliasReceipt(oldToken, newToken) {
1867
1869
  if (!oldToken || !newToken || oldToken === newToken) return;
1870
+ // Every older name of the tab points straight at its newest receipt. As a chain of one link per
1871
+ // rotation, a receipt stopped resolving after eight (_receiptToken's hop limit): a handful of
1872
+ // cross-origin navigations in one tab.
1873
+ for (const [older, newer] of _receiptAliases) if (newer === oldToken) _receiptAliases.set(older, newToken);
1868
1874
  _receiptAliases.set(oldToken, newToken);
1869
1875
  if (_receiptAliases.size > 500) _receiptAliases.delete(_receiptAliases.keys().next().value);
1870
1876
  }
@@ -1920,8 +1926,10 @@ const _activeReceipts = new Map();
1920
1926
  function _receiptSessionKey() {
1921
1927
  return `${SESSION_ID}:${currentSessionId()}`;
1922
1928
  }
1929
+ // Resolved on every read: another client of an HTTP daemon can rotate this session's receipt
1930
+ // after it was stored, and the extension then knows only the new one.
1923
1931
  function _getActiveReceipt() {
1924
- return _activeReceipts.get(_receiptSessionKey()) || "";
1932
+ return _receiptToken(_activeReceipts.get(_receiptSessionKey()));
1925
1933
  }
1926
1934
  function _setActiveReceipt(receipt) {
1927
1935
  _activeReceipts.set(_receiptSessionKey(), _receiptToken(receipt));
@@ -2089,7 +2097,7 @@ async function _runExtensionBatchAction(action, args = {}) {
2089
2097
  if (safeValue?.receipt) {
2090
2098
  // Rotation retires the old token. Keep it resolving to the new one, as navigate does,
2091
2099
  // so the tab stays reachable — and closable by the tab cap — by the name it was opened with.
2092
- _aliasReceipt(previous, safeValue.receipt);
2100
+ _aliasReceipt(_receiptToken(previous), safeValue.receipt); // the receipt extensionOrFallback sent
2093
2101
  _setActiveReceipt(safeValue.receipt);
2094
2102
  }
2095
2103
  return safeValue;
@@ -2388,9 +2396,13 @@ async function extensionOrFallback(extensionType, extensionPayload, fallbackFn)
2388
2396
  const reloadHandoff = extensionType === "reload_extension" && _isExtensionHost
2389
2397
  ? _prepareReloadHttpWorkerHandoff()
2390
2398
  : null;
2399
+ const namedReceipt = _receiptToken(extensionPayload.receipt);
2391
2400
  const payload = {
2392
2401
  ...(attachActiveReceipt && activeReceipt ? { receipt: activeReceipt } : {}),
2393
2402
  ...extensionPayload,
2403
+ // A receipt named when the call started follows a rotation of its tab made while the call
2404
+ // waited above (by another client of an HTTP daemon): an alias joins receipts of one tab.
2405
+ ...(namedReceipt ? { receipt: namedReceipt } : {}),
2394
2406
  ...(reloadHandoff ? { reloadHandoff: reloadHandoff.token } : {}),
2395
2407
  sessionId: `${SESSION_ID}:${currentSessionId()}`,
2396
2408
  };
@@ -2554,8 +2566,11 @@ server.tool(
2554
2566
  }
2555
2567
  const oldUrl = safari.getActiveTabURL();
2556
2568
  // extensionOrFallback claims the destination for this session once its guard passes.
2569
+ // Read once, before anything waits: a call alongside that names another tab makes that tab the
2570
+ // session's current one, and the rotation below has to name the tab this call navigated.
2571
+ const usedReceipt = _receiptToken(receipt || _getActiveReceipt());
2557
2572
  let result = await extensionOrFallback(
2558
- "navigate", { url, ..._explicitReceipt({ receipt }) },
2573
+ "navigate", { url, ..._explicitReceipt({ receipt: usedReceipt }) },
2559
2574
  () => safari.navigate(url)
2560
2575
  );
2561
2576
  // Tab kept its identity, just changed URL — drop the stale old URL from ownership.
@@ -2565,14 +2580,15 @@ server.tool(
2565
2580
  // origin", ~80×/week). The caller chose the destination, so rotating here is safe —
2566
2581
  // hand back the new receipt instead of letting the caller discover the trap.
2567
2582
  const landed = result && typeof result === "object" ? result.url : "";
2568
- const usedReceipt = _receiptToken(receipt || _getActiveReceipt());
2569
- if (landed && _originOf(landed) !== (_receiptOrigins.get(usedReceipt) || _originOf(oldUrl)) && usedReceipt) {
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) {
2570
2585
  try {
2586
+ // Named for the alias below, as run_script getReceipt names it (see _receiptAliases).
2571
2587
  const fresh = _sanitizeTabResult(await extensionOrFallback(
2572
- "get_tab_receipt", { ..._explicitReceipt({ receipt }) }, () => null
2588
+ "get_tab_receipt", { ..._explicitReceipt({ receipt: usedReceipt }) }, () => null
2573
2589
  ));
2574
2590
  if (fresh?.receipt) {
2575
- _aliasReceipt(usedReceipt, fresh.receipt);
2591
+ _aliasReceipt(_receiptToken(usedReceipt), fresh.receipt); // the receipt extensionOrFallback sent
2576
2592
  _setActiveReceipt(fresh.receipt);
2577
2593
  result = { ...result, receipt: fresh.receipt };
2578
2594
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "safari-mcp",
3
- "version": "2.22.6",
3
+ "version": "2.22.7",
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",