safari-mcp 2.21.4 → 2.21.6
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/extension/background.js +22 -5
- package/index.js +19 -1
- package/package.json +2 -2
package/extension/background.js
CHANGED
|
@@ -2883,11 +2883,30 @@ function _withBrowserEpochStorageLock(operation) {
|
|
|
2883
2883
|
return current;
|
|
2884
2884
|
}
|
|
2885
2885
|
|
|
2886
|
+
/**
|
|
2887
|
+
* Adopt `epoch` as the live browser-session identity. Storage is the authority, so a
|
|
2888
|
+
* value that disagrees with the cache is not an error — it is a rotation, and the only
|
|
2889
|
+
* correct response is to take the new one. Bumping the generation is what tells every
|
|
2890
|
+
* operation that captured the previous one to abort instead of writing a record stamped
|
|
2891
|
+
* with an epoch that is no longer current.
|
|
2892
|
+
*/
|
|
2893
|
+
function _adoptBrowserSessionEpoch(epoch) {
|
|
2894
|
+
if (_browserSessionEpoch && _browserSessionEpoch !== epoch) _browserEpochGeneration++;
|
|
2895
|
+
_browserSessionEpoch = epoch;
|
|
2896
|
+
return epoch;
|
|
2897
|
+
}
|
|
2898
|
+
|
|
2886
2899
|
async function _ensureBrowserSessionEpoch() {
|
|
2887
2900
|
if (!_browserSessionStorageAvailable) {
|
|
2888
2901
|
throw new Error("Browser session storage is unavailable; refusing durable tab authority");
|
|
2889
2902
|
}
|
|
2890
|
-
|
|
2903
|
+
// Deliberately no cache short-circuit. `storage.session` is the authority and it can
|
|
2904
|
+
// rotate under a worker that stays alive — a Safari session teardown clears it, and a
|
|
2905
|
+
// racing worker start can replace it. Returning the cached value forever meant every
|
|
2906
|
+
// receipt minted after such a rotation carried the *old* epoch, was handed to the
|
|
2907
|
+
// client as valid, and was then dropped wholesale by the next cold `_hydrateOwnedTabs`,
|
|
2908
|
+
// which does read storage. Re-reading here costs one local get per ownership mutation
|
|
2909
|
+
// and is what makes the generation guards below reachable at all (#105).
|
|
2891
2910
|
if (!_browserEpochInitializationPromise) {
|
|
2892
2911
|
const generation = _browserEpochGeneration;
|
|
2893
2912
|
_browserEpochInitializationPromise = _withBrowserEpochStorageLock(async () => {
|
|
@@ -2905,8 +2924,7 @@ async function _ensureBrowserSessionEpoch() {
|
|
|
2905
2924
|
if (generation !== _browserEpochGeneration) {
|
|
2906
2925
|
throw new Error("Browser-session identity changed during initialization");
|
|
2907
2926
|
}
|
|
2908
|
-
|
|
2909
|
-
return persisted;
|
|
2927
|
+
return _adoptBrowserSessionEpoch(persisted);
|
|
2910
2928
|
}
|
|
2911
2929
|
const epoch = _mintMcpTabMarker();
|
|
2912
2930
|
try {
|
|
@@ -2929,8 +2947,7 @@ async function _ensureBrowserSessionEpoch() {
|
|
|
2929
2947
|
if (generation !== _browserEpochGeneration) {
|
|
2930
2948
|
throw new Error("Browser-session identity changed during initialization");
|
|
2931
2949
|
}
|
|
2932
|
-
|
|
2933
|
-
return confirmedEpoch;
|
|
2950
|
+
return _adoptBrowserSessionEpoch(confirmedEpoch);
|
|
2934
2951
|
});
|
|
2935
2952
|
}
|
|
2936
2953
|
const initialization = _browserEpochInitializationPromise;
|
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
|
-
|
|
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,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "safari-mcp",
|
|
3
|
-
"version": "2.21.
|
|
3
|
+
"version": "2.21.6",
|
|
4
4
|
"mcpName": "io.github.achiya-automation/safari-mcp",
|
|
5
|
-
"description": "Safari browser automation for AI agents
|
|
5
|
+
"description": "Safari browser automation for AI agents \u2014 native macOS, zero Chrome overhead. 97 tools via AppleScript + JavaScript.",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"main": "index.js",
|
|
8
8
|
"bin": {
|