safari-mcp 2.21.4 → 2.21.5

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 +19 -1
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -1924,6 +1924,10 @@ async function extensionOrFallback(extensionType, extensionPayload, fallbackFn)
1924
1924
 
1925
1925
  let result;
1926
1926
  let usedExtension = false;
1927
+ // The extension's evaluate ladder has a terminal state: every injection strategy was
1928
+ // refused. It announces itself as "Falling back to AppleScript", so the caller must
1929
+ // never be handed an empty value as if the script had simply returned nothing (#106).
1930
+ let hardCspBlock = false;
1927
1931
  try {
1928
1932
  if (_extensionConnected && (!_preferAppleScript || _profileExtensionVerified)) {
1929
1933
  try {
@@ -1952,7 +1956,11 @@ async function extensionOrFallback(extensionType, extensionPayload, fallbackFn)
1952
1956
  if (reloadHandoff) _cancelReloadHttpWorkerHandoff(reloadHandoff);
1953
1957
  throw error;
1954
1958
  }
1955
- const isCspError = typeof result === 'string' && (result.includes('unsafe-eval') || result.includes('trusted-types') || result.includes('Trusted Type') || result.includes('Content Security Policy'));
1959
+ // The ladder's own terminal marker. It was not in this list, so the fallback the
1960
+ // marker's text promises never actually ran and its text reached the caller as
1961
+ // the tool's value instead of as a failure (#106).
1962
+ hardCspBlock = typeof result === 'string' && result.includes('CSP blocked all strategies');
1963
+ const isCspError = hardCspBlock || (typeof result === 'string' && (result.includes('unsafe-eval') || result.includes('trusted-types') || result.includes('Trusted Type') || result.includes('Content Security Policy')));
1956
1964
  const isPermissionDenied = typeof result === 'string' && result.includes('__SCREENSHOT_PERMISSION_DENIED__');
1957
1965
  const isElementMiss = typeof result === 'string' && result.startsWith('Element not found');
1958
1966
  const isFailed = result === null || isElementMiss;
@@ -1998,6 +2006,16 @@ async function extensionOrFallback(extensionType, extensionPayload, fallbackFn)
1998
2006
  const t0 = Date.now();
1999
2007
  result = await fallbackFn();
2000
2008
  console.error(`[Safari MCP] ${extensionType} via AppleScript (${Date.now() - t0}ms)`);
2009
+ // Both paths are now exhausted. An empty value here means the script never ran,
2010
+ // which is byte-identical to a script that legitimately returned nothing — and an
2011
+ // agent reads that as "no such element" and keeps building on it. Fail instead.
2012
+ if (hardCspBlock && (result === undefined || result === null)) {
2013
+ throw new Error(
2014
+ `safari_${extensionType}: this page refused every JavaScript injection strategy (CSP), ` +
2015
+ "and the AppleScript fallback returned nothing — the script did not run. " +
2016
+ "Reading the page still works here: safari_read_page, safari_snapshot, safari_get_element, safari_extract_links."
2017
+ );
2018
+ }
2001
2019
  }
2002
2020
  } finally {
2003
2021
  safari.setFocusGuard(false);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "safari-mcp",
3
- "version": "2.21.4",
3
+ "version": "2.21.5",
4
4
  "mcpName": "io.github.achiya-automation/safari-mcp",
5
5
  "description": "Safari browser automation for AI agents — native macOS, zero Chrome overhead. 97 tools via AppleScript + JavaScript.",
6
6
  "type": "module",