UNPKG

safari-mcp

Version:

Safari browser automation for AI agents — native macOS, zero Chrome overhead. 98 tools via AppleScript + JavaScript.

97 lines (87 loc) • 4.94 kB
#!/usr/bin/env node // Safari MCP — postinstall: codesign helper + welcome message // Skipped silently in CI and when stdout is not a TTY (npm install in scripts). const path = require("path"); const { execFileSync, spawnSync } = require("child_process"); const fs = require("fs"); const c = { reset: "\x1b[0m", bold: "\x1b[1m", dim: "\x1b[2m", cyan: "\x1b[36m", yellow: "\x1b[33m", magenta: "\x1b[35m", green: "\x1b[32m", red: "\x1b[31m", }; // Re-sign safari-helper with a stable identifier so macOS Accessibility approval persists. // The package ships with an adhoc-signed binary whose codesign Identifier is a one-off hash // (e.g. `safari-helper-555549441c166aa237e130ddbe3d95629266ecaf`). macOS TCC keys grants by // that identifier, so a fresh npm install or rebuild silently invalidates any previously-granted // Accessibility approval — the helper keeps running but CGEvent injections to non-frontmost Safari // windows stop reaching WebKit content (no isTrusted click events fire on the page). // Re-signing here with the fixed identifier `com.achiya-automation.safari-mcp` keeps the grant // stable across installs. const STABLE_ID = "com.achiya-automation.safari-mcp"; function readHelperId(helper) { try { // NOTE: the Identifier= line is only emitted with --verbose. Plain `codesign -d` // prints nothing matchable, so the old grep always failed → the early-return never // fired and any verify read came back empty (a false "re-sign failed" signal). const result = spawnSync("codesign", ["-d", "--verbose=2", "--", helper], { encoding: "utf8" }); const output = (result.stdout || "") + (result.stderr || ""); const match = output.split("\n").find((l) => l.startsWith("Identifier=")); return match ? match.trim() : ""; } catch { return ""; } } function ensureCodesign() { if (process.platform !== "darwin") return; const helper = path.join(__dirname, "..", "safari-helper"); if (!fs.existsSync(helper)) return; try { // Check current identifier; only re-sign if it doesn't already match. const current = readHelperId(helper); if (current.includes(STABLE_ID)) return; const entitlements = path.join(__dirname, "..", "safari-helper.entitlements"); const codesignArgs = ["-s", "-", "-f", "--identifier", STABLE_ID]; if (fs.existsSync(entitlements)) codesignArgs.push("--entitlements", entitlements); codesignArgs.push(helper); // NOTE: do NOT swallow codesign's stderr (the old `2>/dev/null` hid real failures). execFileSync("codesign", codesignArgs, { stdio: ["ignore", "ignore", "pipe"] }); // Verify the re-sign actually took. A SILENT failure here is the #1 cause of native // clicks "succeeding" while never reaching the page — the Accessibility grant is keyed // to the identifier, so a wrong/unstable identifier breaks it invisibly. Make it loud. const after = readHelperId(helper); if (!after.includes(STABLE_ID)) { process.stderr.write( `\n${c.yellow}⚠ Safari MCP: could not re-sign safari-helper with the stable identifier.${c.reset}\n` + ` Now: ${after || "(unknown)"}\n` + ` Native clicks may silently fail. Fix:\n` + ` ${c.dim}codesign -s - -f --identifier ${STABLE_ID} --entitlements safari-helper.entitlements "${helper}"${c.reset}\n` + ` then re-grant Accessibility, and run safari_doctor to verify.\n` ); } } catch (e) { process.stderr.write( `\n${c.yellow}⚠ Safari MCP: codesign step failed${c.reset} — ${(e.message || "").split("\n")[0]}\n` + ` The binary still runs ad-hoc-signed, but the macOS Accessibility grant may not persist across installs.\n` + ` Run safari_doctor after install to check; re-sign manually if needed.\n` ); } } ensureCodesign(); if (process.env.CI || process.env.SAFARI_MCP_SILENT_INSTALL === "1") process.exit(0); const msg = ` ${c.bold}${c.cyan}🦁 Safari MCP installed${c.reset} ${c.dim}— native browser tools for AI agents${c.reset} ${c.bold}Next steps:${c.reset} 1. Enable Safari → Settings → Developer → ${c.yellow}Allow JavaScript from Apple Events${c.reset} 2. Add to your MCP client config: ${c.dim}{ "mcpServers": { "safari": { "command": "npx", "args": ["safari-mcp"] } } }${c.reset} 3. ${c.bold}For native_click / native_keyboard${c.reset} (no focus stealing): System Settings → Privacy & Security → ${c.yellow}Accessibility${c.reset} ${c.dim}(macOS 27: Device Control and Data Access)${c.reset} → add ${c.dim}node_modules/safari-mcp/safari-helper${c.reset} ${c.dim}(or the global install path)${c.reset} ${c.bold}${c.magenta}⭐ Found this useful?${c.reset} A star helps others discover it: ${c.cyan}https://github.com/achiya-automation/safari-mcp${c.reset} ${c.dim}Docs · Examples · Issues → github.com/achiya-automation/safari-mcp${c.reset} `; try { process.stdout.write(msg); } catch { /* ignore */ }