safari-mcp 2.22.9 → 2.22.11
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/index.js +25 -7
- package/node-compat.js +22 -0
- package/package.json +2 -1
package/index.js
CHANGED
|
@@ -37,6 +37,12 @@ import {
|
|
|
37
37
|
readProfileIdentities,
|
|
38
38
|
describeProfileIdentities,
|
|
39
39
|
} from "./worker-ledger.js";
|
|
40
|
+
import { Socket } from "node:net";
|
|
41
|
+
import { ignoreTypeOfServiceEinval } from "./node-compat.js";
|
|
42
|
+
|
|
43
|
+
// Before the first fetch: a secondary's /proxy-check to a primary that just went away
|
|
44
|
+
// must reject, not end the process (#140).
|
|
45
|
+
ignoreTypeOfServiceEinval(Socket.prototype);
|
|
40
46
|
|
|
41
47
|
const MAX_BODY_SIZE = 10 * 1024 * 1024; // 10 MB cap on POST body — prevents DoS
|
|
42
48
|
|
|
@@ -1767,7 +1773,7 @@ const _nullMeansFailure = new Set([
|
|
|
1767
1773
|
"read_page", "get_source", "snapshot", "get_element", "query_all",
|
|
1768
1774
|
"scroll", "scroll_to",
|
|
1769
1775
|
// NOTE: "evaluate" intentionally NOT here — null is a valid return value.
|
|
1770
|
-
// CSP fallback is handled separately
|
|
1776
|
+
// Its CSP fallback is handled separately, by the ladder's terminal marker (hardCspBlock).
|
|
1771
1777
|
]);
|
|
1772
1778
|
|
|
1773
1779
|
// Operations that don't need tab ownership (read-only or tab management)
|
|
@@ -2453,16 +2459,18 @@ async function extensionOrFallback(extensionType, extensionPayload, fallbackFn)
|
|
|
2453
2459
|
// The ladder's own terminal marker. It was not in this list, so the fallback the
|
|
2454
2460
|
// marker's text promises never actually ran and its text reached the caller as
|
|
2455
2461
|
// the tool's value instead of as a failure (#106).
|
|
2456
|
-
|
|
2457
|
-
//
|
|
2458
|
-
//
|
|
2459
|
-
|
|
2460
|
-
|
|
2462
|
+
// It is the whole reply of an evaluate, so it is matched as that and nowhere else. A
|
|
2463
|
+
// result is page text as often as not: matched anywhere in it, CSP wording read a page
|
|
2464
|
+
// that mentions Trusted Types as a blocked command, which a profile session then
|
|
2465
|
+
// refused and any other session ran a second time through AppleScript. Every other
|
|
2466
|
+
// command's failure arrives as a rejection, never as its result.
|
|
2467
|
+
hardCspBlock = extensionType === "evaluate" && typeof result === 'string' && result.startsWith('Error: CSP blocked all strategies');
|
|
2468
|
+
const isPermissionDenied = result === '__SCREENSHOT_PERMISSION_DENIED__';
|
|
2461
2469
|
const isElementMiss = typeof result === 'string' && result.startsWith('Element not found');
|
|
2462
2470
|
const isFailed = result === null || isElementMiss;
|
|
2463
2471
|
if (isPermissionDenied) {
|
|
2464
2472
|
console.error(`[Safari MCP] ${extensionType} permission denied (${Date.now() - t0}ms) — falling back to AppleScript`);
|
|
2465
|
-
} else if (
|
|
2473
|
+
} else if (hardCspBlock) {
|
|
2466
2474
|
console.error(`[Safari MCP] ${extensionType} CSP blocked: ${result?.substring(0, 100)} (${Date.now() - t0}ms) — falling back to AppleScript`);
|
|
2467
2475
|
} else if (isFailed && _nullMeansFailure.has(extensionType)) {
|
|
2468
2476
|
if (_preferAppleScript && isElementMiss) {
|
|
@@ -2505,6 +2513,16 @@ async function extensionOrFallback(extensionType, extensionPayload, fallbackFn)
|
|
|
2505
2513
|
}
|
|
2506
2514
|
if (!usedExtension) {
|
|
2507
2515
|
if (_preferAppleScript) {
|
|
2516
|
+
// The extension answered here, so naming it unavailable sent callers after a bridge
|
|
2517
|
+
// that worked (copilot.microsoft.com, 4-5.10.26: Trusted Types turn away its policy).
|
|
2518
|
+
if (hardCspBlock) {
|
|
2519
|
+
throw new Error(
|
|
2520
|
+
"safari_evaluate: this page's Content-Security-Policy refused every way to run a script string " +
|
|
2521
|
+
"(no eval, no inline script the extension can add), so the script did not run. A profile-scoped " +
|
|
2522
|
+
"session never falls back to AppleScript. Tools that run no script string still work here: " +
|
|
2523
|
+
"safari_read_page, safari_snapshot, safari_get_element, safari_click, safari_fill."
|
|
2524
|
+
);
|
|
2525
|
+
}
|
|
2508
2526
|
throw new Error(`Safari profile extension unavailable for "${extensionType}"; refusing AppleScript fallback`);
|
|
2509
2527
|
}
|
|
2510
2528
|
const t0 = Date.now();
|
package/node-compat.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Node 24's bundled undici (7.x) calls socket.setTypeOfService() on every HTTP/1.1
|
|
2
|
+
// write without a catch. On macOS, a socket whose peer has just reset makes that call
|
|
3
|
+
// throw EINVAL from inside an I/O callback, where no try/catch around fetch() can reach
|
|
4
|
+
// it, so the whole process exits (#140: a secondary died this way exactly when its
|
|
5
|
+
// primary went away, instead of taking the bridge port over). undici 8.8.0 ignores
|
|
6
|
+
// these errors (nodejs/undici#5547, Node 26.5.1+); Node 24 never got the fix.
|
|
7
|
+
// Swallowing only EINVAL leaves the request to fail the ordinary way, as a rejected
|
|
8
|
+
// fetch. Type of service is a QoS hint, so skipping it changes nothing else.
|
|
9
|
+
export function ignoreTypeOfServiceEinval(proto) {
|
|
10
|
+
const original = proto.setTypeOfService;
|
|
11
|
+
if (typeof original !== "function" || original.ignoresEinval) return;
|
|
12
|
+
const wrapped = function (...args) {
|
|
13
|
+
try {
|
|
14
|
+
return original.apply(this, args);
|
|
15
|
+
} catch (err) {
|
|
16
|
+
if (err?.code === "EINVAL") return this;
|
|
17
|
+
throw err;
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
wrapped.ignoresEinval = true;
|
|
21
|
+
proto.setTypeOfService = wrapped;
|
|
22
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "safari-mcp",
|
|
3
|
-
"version": "2.22.
|
|
3
|
+
"version": "2.22.11",
|
|
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",
|
|
@@ -71,6 +71,7 @@
|
|
|
71
71
|
"ownership-state.js",
|
|
72
72
|
"image-size.js",
|
|
73
73
|
"worker-ledger.js",
|
|
74
|
+
"node-compat.js",
|
|
74
75
|
"mcp-helpers.js",
|
|
75
76
|
"injected-validators.js",
|
|
76
77
|
"injected-escape.js",
|