safari-mcp 2.22.14 → 2.22.15
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/package.json +1 -1
- package/safari.js +17 -17
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "safari-mcp",
|
|
3
|
-
"version": "2.22.
|
|
3
|
+
"version": "2.22.15",
|
|
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",
|
package/safari.js
CHANGED
|
@@ -998,7 +998,7 @@ function _windowById(id) {
|
|
|
998
998
|
// Page JavaScript that answers "1" when the page carries exactly `marker`: in window.name, or in
|
|
999
999
|
// window.__mcpTabMarker once a page has taken window.name over.
|
|
1000
1000
|
function _markerCheckJS(marker) {
|
|
1001
|
-
const safeMarker = String(marker).replace(/'/g, "\\'");
|
|
1001
|
+
const safeMarker = String(marker).replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
1002
1002
|
return `(function(){try{return (window.name==='${safeMarker}'||window.__mcpTabMarker==='${safeMarker}')?'1':'0'}catch(e){return '0'}})()`;
|
|
1003
1003
|
}
|
|
1004
1004
|
|
|
@@ -3242,7 +3242,7 @@ export async function selectOption({ selector, value, ref }) {
|
|
|
3242
3242
|
const val = String(value).replace(/\\/g, "\\\\").replace(/'/g, "\\'");
|
|
3243
3243
|
let finder;
|
|
3244
3244
|
if (ref) {
|
|
3245
|
-
finder = `mcpFindRef('${
|
|
3245
|
+
finder = `mcpFindRef('${escJsSingleQuote(ref)}')`;
|
|
3246
3246
|
} else if (selector) {
|
|
3247
3247
|
const sel = escJsSingleQuote(selector);
|
|
3248
3248
|
finder = `(document.querySelector('${sel}')||mcpQuerySelectorDeep('${sel}'))`;
|
|
@@ -3264,7 +3264,7 @@ export async function reactSelectSet({ selector, ref, value }) {
|
|
|
3264
3264
|
if (value === undefined || value === null) throw new Error("reactSelectSet requires 'value' (option label)");
|
|
3265
3265
|
let finder;
|
|
3266
3266
|
if (ref) {
|
|
3267
|
-
const safeRef =
|
|
3267
|
+
const safeRef = escJsSingleQuote(ref);
|
|
3268
3268
|
finder = `mcpFindRef('${safeRef}')`;
|
|
3269
3269
|
} else if (selector) {
|
|
3270
3270
|
const sel = escJsSingleQuote(selector);
|
|
@@ -3281,7 +3281,7 @@ export async function reactSelectListOptions({ selector, ref }) {
|
|
|
3281
3281
|
await ensureHelpers();
|
|
3282
3282
|
let finder;
|
|
3283
3283
|
if (ref) {
|
|
3284
|
-
const safeRef =
|
|
3284
|
+
const safeRef = escJsSingleQuote(ref);
|
|
3285
3285
|
finder = `mcpFindRef('${safeRef}')`;
|
|
3286
3286
|
} else if (selector) {
|
|
3287
3287
|
const sel = escJsSingleQuote(selector);
|
|
@@ -3518,7 +3518,7 @@ export async function pressKey({ key, modifiers = [] }) {
|
|
|
3518
3518
|
|
|
3519
3519
|
// Non-modifier keys: pure JavaScript (no System Events)
|
|
3520
3520
|
const jsKey = jsKeyMap[k] || key;
|
|
3521
|
-
const safeKey = jsKey
|
|
3521
|
+
const safeKey = escJsSingleQuote(jsKey);
|
|
3522
3522
|
// W3C `code` values: special keys are NOT "Key"-prefixed ("Enter", "ArrowUp", ...);
|
|
3523
3523
|
// only letters are ("KeyA"), digits are "Digit1". Apps that route on event.code
|
|
3524
3524
|
// (Notion, Monaco, Google Docs) ignore a bogus "KeyEnter".
|
|
@@ -3533,7 +3533,7 @@ export async function pressKey({ key, modifiers = [] }) {
|
|
|
3533
3533
|
|| (/^[a-z]$/i.test(jsKey) ? "Key" + jsKey.toUpperCase()
|
|
3534
3534
|
: /^[0-9]$/.test(jsKey) ? "Digit" + jsKey
|
|
3535
3535
|
: jsKey);
|
|
3536
|
-
const safeCode = jsCode
|
|
3536
|
+
const safeCode = escJsSingleQuote(jsCode);
|
|
3537
3537
|
const shiftKey = hasShift;
|
|
3538
3538
|
const altKey = modifiers.some((m) => m.toLowerCase() === "alt");
|
|
3539
3539
|
|
|
@@ -3694,7 +3694,7 @@ export async function replaceEditorContent({ text }) {
|
|
|
3694
3694
|
// then call onChange(text) to sync React. Works for Airtable-style embeds
|
|
3695
3695
|
// and is a no-op on plain Monaco (VS Code web, GitHub) where setValue
|
|
3696
3696
|
// already covers the state.
|
|
3697
|
-
const preflightFirstLine = text.split('\n')[0].slice(0, 20)
|
|
3697
|
+
const preflightFirstLine = escJsSingleQuote(text.split('\n')[0].slice(0, 20));
|
|
3698
3698
|
const monacoPreflight = await runJS(
|
|
3699
3699
|
`(function(){
|
|
3700
3700
|
var m = (typeof monaco !== 'undefined') ? monaco : window.monaco;
|
|
@@ -5181,7 +5181,7 @@ export async function uploadFile({ selector, filePath, forceNative = false, veri
|
|
|
5181
5181
|
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
5182
5182
|
};
|
|
5183
5183
|
const mime = mimeMap[ext] || "application/octet-stream";
|
|
5184
|
-
const safeName = fileName
|
|
5184
|
+
const safeName = escJsSingleQuote(fileName);
|
|
5185
5185
|
|
|
5186
5186
|
// forceNative: skip synthetic injection entirely. Attempting it first would leave a stray
|
|
5187
5187
|
// media item behind on sites that register the pickup in their UI without ingesting it
|
|
@@ -5413,7 +5413,7 @@ export async function pasteImageFromFile({ filePath, locate = null }) {
|
|
|
5413
5413
|
// Read image as base64
|
|
5414
5414
|
const fileData = await readFile(filePath);
|
|
5415
5415
|
const base64 = fileData.toString("base64");
|
|
5416
|
-
const fileName = filePath.split("/").pop()
|
|
5416
|
+
const fileName = escJsSingleQuote(filePath.split("/").pop());
|
|
5417
5417
|
|
|
5418
5418
|
// Use runJSLarge — images are often >260KB as base64
|
|
5419
5419
|
const result = await runJSLarge(
|
|
@@ -5665,7 +5665,7 @@ export function getNextSnapshotGen() { return _snapshotGen++; }
|
|
|
5665
5665
|
export async function takeSnapshot({ selector, _gen } = {}) {
|
|
5666
5666
|
// Use provided gen (from tool path) or allocate a new one (direct call)
|
|
5667
5667
|
const gen = _gen != null ? _gen : _snapshotGen++;
|
|
5668
|
-
const root = selector ? `document.querySelector('${selector
|
|
5668
|
+
const root = selector ? `document.querySelector('${escJsSingleQuote(selector)}')` : "document.body";
|
|
5669
5669
|
|
|
5670
5670
|
const result = await runJS(
|
|
5671
5671
|
`(function(){
|
|
@@ -5897,7 +5897,7 @@ export async function runScript({ steps, onStep, actions: overrides = {} }) {
|
|
|
5897
5897
|
// ========== ACCESSIBILITY SNAPSHOT ==========
|
|
5898
5898
|
|
|
5899
5899
|
export async function getAccessibilityTree({ selector, maxDepth = 5 }) {
|
|
5900
|
-
const sel = selector ? `'${selector
|
|
5900
|
+
const sel = selector ? `'${escJsSingleQuote(selector)}'` : "null";
|
|
5901
5901
|
return runJS(
|
|
5902
5902
|
`(function(){
|
|
5903
5903
|
function buildTree(el, depth) {
|
|
@@ -6242,7 +6242,7 @@ export async function clearNetworkCapture() {
|
|
|
6242
6242
|
}
|
|
6243
6243
|
|
|
6244
6244
|
export async function getNetworkDetails({ limit = 50, filter } = {}) {
|
|
6245
|
-
const filterStr = filter ? `.filter(function(r){return r.url.includes('${filter
|
|
6245
|
+
const filterStr = filter ? `.filter(function(r){return r.url.includes('${escJsSingleQuote(filter)}')})` : "";
|
|
6246
6246
|
return runJS(
|
|
6247
6247
|
`JSON.stringify((window.__mcp_network||[])${filterStr}.slice(-${Number(limit)}))`
|
|
6248
6248
|
);
|
|
@@ -6350,7 +6350,7 @@ export async function throttleNetwork({ profile, latency, downloadKbps, uploadKb
|
|
|
6350
6350
|
// ========== CONSOLE FILTER ==========
|
|
6351
6351
|
|
|
6352
6352
|
export async function getConsoleByLevel({ level }) {
|
|
6353
|
-
const safeLevel = level
|
|
6353
|
+
const safeLevel = escJsSingleQuote(level);
|
|
6354
6354
|
return runJS(
|
|
6355
6355
|
`JSON.stringify((window.__mcp_console||[]).filter(function(m){return m.level==='${safeLevel}'}))`
|
|
6356
6356
|
);
|
|
@@ -6359,7 +6359,7 @@ export async function getConsoleByLevel({ level }) {
|
|
|
6359
6359
|
// ========== DATA EXTRACTION ==========
|
|
6360
6360
|
|
|
6361
6361
|
export async function extractTables({ selector, limit = 10 }) {
|
|
6362
|
-
const sel = selector ? `'${selector
|
|
6362
|
+
const sel = selector ? `'${escJsSingleQuote(selector)}'` : "'table'";
|
|
6363
6363
|
return runJS(
|
|
6364
6364
|
`(function(){
|
|
6365
6365
|
var tables = [...document.querySelectorAll(${sel})].slice(0, ${Number(limit)});
|
|
@@ -6424,7 +6424,7 @@ export async function extractImages({ limit = 50 }) {
|
|
|
6424
6424
|
|
|
6425
6425
|
export async function extractLinks({ limit = 100, filter }) {
|
|
6426
6426
|
const filterStr = filter
|
|
6427
|
-
? `.filter(function(a){return a.href.includes('${filter
|
|
6427
|
+
? `.filter(function(a){return a.href.includes('${escJsSingleQuote(filter)}')||a.textContent.includes('${escJsSingleQuote(filter)}')})`
|
|
6428
6428
|
: "";
|
|
6429
6429
|
return runJS(
|
|
6430
6430
|
`JSON.stringify([...document.querySelectorAll('a[href]')]${filterStr}.slice(0,${Number(limit)}).map(function(a){
|
|
@@ -6476,8 +6476,8 @@ export async function getComputedStyles({ selector, properties }) {
|
|
|
6476
6476
|
// ========== INDEXEDDB ==========
|
|
6477
6477
|
|
|
6478
6478
|
export async function getIndexedDB({ dbName, storeName, limit = 20 }) {
|
|
6479
|
-
const safeDb = dbName
|
|
6480
|
-
const safeStore = storeName
|
|
6479
|
+
const safeDb = escJsSingleQuote(dbName);
|
|
6480
|
+
const safeStore = escJsSingleQuote(storeName);
|
|
6481
6481
|
// `do JavaScript` can't await a Promise — route async work through the Node-side poller.
|
|
6482
6482
|
return _evaluateAsync(
|
|
6483
6483
|
`(async function(){
|