safari-mcp 2.11.2 → 2.11.4

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 (3) hide show
  1. package/index.js +18 -3
  2. package/package.json +1 -1
  3. package/safari.js +16 -4
package/index.js CHANGED
@@ -473,6 +473,14 @@ try {
473
473
 
474
474
  // POST /proxy-command — secondary instances send commands through primary
475
475
  if (req.method === "POST" && req.url === "/proxy-command") {
476
+ // חוסם כש-SAFARI_PROFILE מוגדר — אחרת ה-extension עלול לפעול בפרופיל הלא נכון
477
+ if (process.env.SAFARI_PROFILE) {
478
+ res.writeHead(503, { "Content-Type": "application/json" });
479
+ res.end(JSON.stringify({
480
+ error: `Refusing /proxy-command: SAFARI_PROFILE="${process.env.SAFARI_PROFILE}" is set on the host instance. The Safari extension may be connected to a different profile window, which would execute commands in the wrong profile. Use the safari_* MCP tools instead — they route through AppleScript when SAFARI_PROFILE is set and stay within the configured profile window.`
481
+ }));
482
+ return;
483
+ }
476
484
  let body = "";
477
485
  req.on("data", (chunk) => { body += chunk; if (body.length > MAX_BODY_SIZE) { res.writeHead(413); res.end("Payload too large"); req.destroy(); } });
478
486
  req.on("end", async () => {
@@ -1080,12 +1088,19 @@ server.tool(
1080
1088
 
1081
1089
  server.tool(
1082
1090
  "safari_select_option",
1083
- "Select an option in a native <select> dropdown. Sets .value and dispatches change event. For custom dropdowns (React/LinkedIn), use safari_click on the dropdown trigger, then safari_click on the option instead.",
1091
+ "Select an option in a native <select> dropdown. Sets .value and dispatches change event. Pass `ref` (from safari_snapshot) for a select inside an iframe or shadow DOM — a plain `selector` only reaches the top document. For custom dropdowns (React/LinkedIn), use safari_click on the dropdown trigger, then safari_click on the option instead.",
1084
1092
  {
1085
- selector: z.string().describe("CSS selector of the select"),
1086
- value: z.string().describe("Option value to select"),
1093
+ selector: z.string().optional().describe("CSS selector of the select (top document only)"),
1094
+ ref: z.string().optional().describe("Ref ID from safari_snapshot — required for selects inside iframes/shadow DOM"),
1095
+ value: z.string().describe("Option value or visible label to select"),
1087
1096
  },
1088
1097
  async (args) => {
1098
+ // ref path: resolve via mcpFindRef (reaches iframes/shadow DOM) on the AppleScript
1099
+ // engine — the extension's select_option handler is selector-only.
1100
+ if (args.ref) {
1101
+ const refResult = await safari.selectOption({ ref: args.ref, value: args.value });
1102
+ return { content: [{ type: "text", text: typeof refResult === 'string' ? refResult : JSON.stringify(refResult) }] };
1103
+ }
1089
1104
  let result = await extensionOrFallback(
1090
1105
  "select_option", { selector: args.selector, value: args.value },
1091
1106
  () => safari.selectOption(args)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "safari-mcp",
3
- "version": "2.11.2",
3
+ "version": "2.11.4",
4
4
  "mcpName": "io.github.achiya-automation/safari-mcp",
5
5
  "description": "Safari browser automation for AI agents — native macOS, zero Chrome overhead. 80 tools via AppleScript + JavaScript.",
6
6
  "type": "module",
package/safari.js CHANGED
@@ -1905,11 +1905,23 @@ export async function clearField({ selector }) {
1905
1905
  );
1906
1906
  }
1907
1907
 
1908
- export async function selectOption({ selector, value }) {
1909
- const sel = selector.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1910
- const val = value.replace(/'/g, "\\'");
1908
+ export async function selectOption({ selector, value, ref }) {
1909
+ // ref/deep finder: native <select> elements inside same-origin iframes or shadow
1910
+ // DOM are invisible to a top-frame document.querySelector. mcpFindRef (snapshot ref)
1911
+ // and mcpQuerySelectorDeep traverse those roots — the same finders click() uses.
1912
+ await ensureHelpers();
1913
+ const val = String(value).replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1914
+ let finder;
1915
+ if (ref) {
1916
+ finder = `mcpFindRef('${String(ref).replace(/'/g, "\\'")}')`;
1917
+ } else if (selector) {
1918
+ const sel = selector.replace(/\\/g, "\\\\").replace(/'/g, "\\'");
1919
+ finder = `(document.querySelector('${sel}')||mcpQuerySelectorDeep('${sel}'))`;
1920
+ } else {
1921
+ throw new Error("selectOption requires 'ref' or 'selector'");
1922
+ }
1911
1923
  return runJS(
1912
- `(function(){var el=document.querySelector('${sel}');if(!el)return 'Element not found';el.focus();var t=el._valueTracker;if(t)t.setValue('');var d=Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype,'value');if(d&&d.set){d.set.call(el,'${val}');}else{el.value='${val}';}var m=false;for(var i=0;i<el.options.length;i++){if(el.options[i].value==='${val}'){el.selectedIndex=i;m=true;break;}}if(!m||el.value!=='${val}'){var norm=function(s){return s.replace(/[\\u200B-\\u200F\\u202A-\\u202E\\u2066-\\u2069\\uFEFF]/g,'').replace(/[\\u2010-\\u2015\\u2212\\uFE58\\uFE63\\uFF0D]/g,'-').replace(/\\s*-\\s*/g,'-').replace(/\\s+/g,' ').trim();};var cv=norm('${val}');for(var i=0;i<el.options.length;i++){if(norm(el.options[i].value)===cv||norm(el.options[i].text)===cv){el.selectedIndex=i;if(d&&d.set){d.set.call(el,el.options[i].value);}else{el.value=el.options[i].value;}m=true;break;}}if(!m){for(var i=0;i<el.options.length;i++){var nv=norm(el.options[i].value),nt=norm(el.options[i].text);if(nv.indexOf(cv)>=0||nt.indexOf(cv)>=0||cv.indexOf(nv)>=0||cv.indexOf(nt)>=0){if(i===0&&el.options.length>1)continue;el.selectedIndex=i;if(d&&d.set){d.set.call(el,el.options[i].value);}else{el.value=el.options[i].value;}m=true;break;}}}}el.dispatchEvent(new Event('input',{bubbles:true}));el.dispatchEvent(new Event('change',{bubbles:true}));el.dispatchEvent(new Event('blur',{bubbles:true}));return 'Selected: '+el.value+' (index '+el.selectedIndex+')';})()`
1924
+ `(function(){var el=${finder};if(!el)return 'Element not found';el.focus();var t=el._valueTracker;if(t)t.setValue('');var d=Object.getOwnPropertyDescriptor(HTMLSelectElement.prototype,'value');if(d&&d.set){d.set.call(el,'${val}');}else{el.value='${val}';}var m=false;for(var i=0;i<el.options.length;i++){if(el.options[i].value==='${val}'){el.selectedIndex=i;m=true;break;}}if(!m||el.value!=='${val}'){var norm=function(s){return s.replace(/[\\u200B-\\u200F\\u202A-\\u202E\\u2066-\\u2069\\uFEFF]/g,'').replace(/[\\u2010-\\u2015\\u2212\\uFE58\\uFE63\\uFF0D]/g,'-').replace(/\\s*-\\s*/g,'-').replace(/\\s+/g,' ').trim();};var cv=norm('${val}');for(var i=0;i<el.options.length;i++){if(norm(el.options[i].value)===cv||norm(el.options[i].text)===cv){el.selectedIndex=i;if(d&&d.set){d.set.call(el,el.options[i].value);}else{el.value=el.options[i].value;}m=true;break;}}if(!m){for(var i=0;i<el.options.length;i++){var nv=norm(el.options[i].value),nt=norm(el.options[i].text);if(nv.indexOf(cv)>=0||nt.indexOf(cv)>=0||cv.indexOf(nv)>=0||cv.indexOf(nt)>=0){if(i===0&&el.options.length>1)continue;el.selectedIndex=i;if(d&&d.set){d.set.call(el,el.options[i].value);}else{el.value=el.options[i].value;}m=true;break;}}}}el.dispatchEvent(new Event('input',{bubbles:true}));el.dispatchEvent(new Event('change',{bubbles:true}));el.dispatchEvent(new Event('blur',{bubbles:true}));return 'Selected: '+el.value+' (index '+el.selectedIndex+')';})()`
1913
1925
  );
1914
1926
  }
1915
1927