firefox-devtools-mcp 0.6.0 → 0.7.0

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/README.md CHANGED
@@ -99,9 +99,21 @@ You can pass flags or environment variables (names on the right):
99
99
  - Input: click/hover/fill/drag/upload/form fill
100
100
  - Network: list/get (ID‑first, filters, always‑on capture)
101
101
  - Console: list/clear
102
- - Screenshot: page/by uid
102
+ - Screenshot: page/by uid (with optional `saveTo` for CLI environments)
103
103
  - Utilities: accept/dismiss dialog, history back/forward, set viewport
104
104
 
105
+ ### Screenshot optimization for Claude Code
106
+
107
+ When using screenshots in Claude Code CLI, the base64 image data can consume significant context.
108
+ Use the `saveTo` parameter to save screenshots to disk instead:
109
+
110
+ ```
111
+ screenshot_page({ saveTo: "/tmp/page.png" })
112
+ screenshot_by_uid({ uid: "abc123", saveTo: "/tmp/element.png" })
113
+ ```
114
+
115
+ The file can then be viewed with Claude Code's `Read` tool without impacting context size.
116
+
105
117
  ## Local development
106
118
 
107
119
  ```bash
package/dist/index.js CHANGED
@@ -14072,7 +14072,7 @@ var init_protocol = __esm({
14072
14072
  return;
14073
14073
  }
14074
14074
  const pollInterval = task2.pollInterval ?? this._options?.defaultTaskPollInterval ?? 1e3;
14075
- await new Promise((resolve3) => setTimeout(resolve3, pollInterval));
14075
+ await new Promise((resolve4) => setTimeout(resolve4, pollInterval));
14076
14076
  options?.signal?.throwIfAborted();
14077
14077
  }
14078
14078
  } catch (error2) {
@@ -14089,7 +14089,7 @@ var init_protocol = __esm({
14089
14089
  */
14090
14090
  request(request, resultSchema, options) {
14091
14091
  const { relatedRequestId, resumptionToken, onresumptiontoken, task, relatedTask } = options ?? {};
14092
- return new Promise((resolve3, reject) => {
14092
+ return new Promise((resolve4, reject) => {
14093
14093
  const earlyReject = (error2) => {
14094
14094
  reject(error2);
14095
14095
  };
@@ -14167,7 +14167,7 @@ var init_protocol = __esm({
14167
14167
  if (!parseResult.success) {
14168
14168
  reject(parseResult.error);
14169
14169
  } else {
14170
- resolve3(parseResult.data);
14170
+ resolve4(parseResult.data);
14171
14171
  }
14172
14172
  } catch (error2) {
14173
14173
  reject(error2);
@@ -14428,12 +14428,12 @@ var init_protocol = __esm({
14428
14428
  }
14429
14429
  } catch {
14430
14430
  }
14431
- return new Promise((resolve3, reject) => {
14431
+ return new Promise((resolve4, reject) => {
14432
14432
  if (signal.aborted) {
14433
14433
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
14434
14434
  return;
14435
14435
  }
14436
- const timeoutId = setTimeout(resolve3, interval);
14436
+ const timeoutId = setTimeout(resolve4, interval);
14437
14437
  signal.addEventListener("abort", () => {
14438
14438
  clearTimeout(timeoutId);
14439
14439
  reject(new McpError(ErrorCode.InvalidRequest, "Request cancelled"));
@@ -17460,7 +17460,7 @@ var require_compile = __commonJS({
17460
17460
  const schOrFunc = root.refs[ref];
17461
17461
  if (schOrFunc)
17462
17462
  return schOrFunc;
17463
- let _sch = resolve3.call(this, root, ref);
17463
+ let _sch = resolve4.call(this, root, ref);
17464
17464
  if (_sch === void 0) {
17465
17465
  const schema = (_a2 = root.localRefs) === null || _a2 === void 0 ? void 0 : _a2[ref];
17466
17466
  const { schemaId } = this.opts;
@@ -17487,7 +17487,7 @@ var require_compile = __commonJS({
17487
17487
  function sameSchemaEnv(s1, s2) {
17488
17488
  return s1.schema === s2.schema && s1.root === s2.root && s1.baseId === s2.baseId;
17489
17489
  }
17490
- function resolve3(root, ref) {
17490
+ function resolve4(root, ref) {
17491
17491
  let sch;
17492
17492
  while (typeof (sch = this.refs[ref]) == "string")
17493
17493
  ref = sch;
@@ -18062,7 +18062,7 @@ var require_fast_uri = __commonJS({
18062
18062
  }
18063
18063
  return uri;
18064
18064
  }
18065
- function resolve3(baseURI, relativeURI, options) {
18065
+ function resolve4(baseURI, relativeURI, options) {
18066
18066
  const schemelessOptions = options ? Object.assign({ scheme: "null" }, options) : { scheme: "null" };
18067
18067
  const resolved = resolveComponent(parse3(baseURI, schemelessOptions), parse3(relativeURI, schemelessOptions), schemelessOptions, true);
18068
18068
  schemelessOptions.skipEscape = true;
@@ -18289,7 +18289,7 @@ var require_fast_uri = __commonJS({
18289
18289
  var fastUri = {
18290
18290
  SCHEMES,
18291
18291
  normalize,
18292
- resolve: resolve3,
18292
+ resolve: resolve4,
18293
18293
  resolveComponent,
18294
18294
  equal,
18295
18295
  serialize,
@@ -21944,12 +21944,12 @@ var init_stdio2 = __esm({
21944
21944
  this.onclose?.();
21945
21945
  }
21946
21946
  send(message) {
21947
- return new Promise((resolve3) => {
21947
+ return new Promise((resolve4) => {
21948
21948
  const json2 = serializeMessage(message);
21949
21949
  if (this._stdout.write(json2)) {
21950
- resolve3();
21950
+ resolve4();
21951
21951
  } else {
21952
- this._stdout.once("drain", resolve3);
21952
+ this._stdout.once("drain", resolve4);
21953
21953
  }
21954
21954
  });
21955
21955
  }
@@ -21963,7 +21963,7 @@ var init_constants = __esm({
21963
21963
  "src/config/constants.ts"() {
21964
21964
  "use strict";
21965
21965
  SERVER_NAME = "firefox-devtools";
21966
- SERVER_VERSION = "0.2.5";
21966
+ SERVER_VERSION = "0.7.0";
21967
21967
  }
21968
21968
  });
21969
21969
 
@@ -22791,7 +22791,7 @@ var init_dom = __esm({
22791
22791
  */
22792
22792
  async waitForEventsAfterAction() {
22793
22793
  await this.driver.executeScript("return new Promise(r => requestAnimationFrame(() => r()))");
22794
- await new Promise((resolve3) => setTimeout(resolve3, 50));
22794
+ await new Promise((resolve4) => setTimeout(resolve4, 50));
22795
22795
  }
22796
22796
  // ============================================================================
22797
22797
  // Screenshot
@@ -22820,7 +22820,7 @@ var init_dom = __esm({
22820
22820
  'arguments[0].scrollIntoView({block: "center", inline: "center"});',
22821
22821
  el
22822
22822
  );
22823
- await new Promise((resolve3) => setTimeout(resolve3, 100));
22823
+ await new Promise((resolve4) => setTimeout(resolve4, 100));
22824
22824
  return await el.takeScreenshot();
22825
22825
  }
22826
22826
  };
@@ -23301,12 +23301,12 @@ ${attemptedPaths.map((p) => ` - ${p}`).join("\n")}`
23301
23301
  * Take a snapshot of the current page
23302
23302
  * Returns text and JSON with snapshotId, no DOM mutations
23303
23303
  */
23304
- async takeSnapshot() {
23304
+ async takeSnapshot(options) {
23305
23305
  const snapshotId = ++this.currentSnapshotId;
23306
23306
  this.resolver.setSnapshotId(snapshotId);
23307
23307
  this.resolver.clear();
23308
23308
  logDebug(`Taking snapshot (ID: ${snapshotId})...`);
23309
- const result = await this.executeInjectedScript(snapshotId);
23309
+ const result = await this.executeInjectedScript(snapshotId, options);
23310
23310
  logDebug(
23311
23311
  `Snapshot executeScript result: hasResult=${!!result}, hasTree=${!!result?.tree}, truncated=${result?.truncated || false}`
23312
23312
  );
@@ -23319,6 +23319,10 @@ ${attemptedPaths.map((p) => ` - ${p}`).join("\n")}`
23319
23319
  logDebug(` ... and ${result.debugLog.length - 20} more`);
23320
23320
  }
23321
23321
  }
23322
+ if (result?.selectorError) {
23323
+ logDebug(`Snapshot generation failed: ${result.selectorError}`);
23324
+ throw new Error(result.selectorError);
23325
+ }
23322
23326
  if (!result?.tree) {
23323
23327
  const errorMsg = "Unknown error";
23324
23328
  logDebug(`Snapshot generation failed: ${errorMsg}`);
@@ -23362,7 +23366,7 @@ ${attemptedPaths.map((p) => ` - ${p}`).join("\n")}`
23362
23366
  /**
23363
23367
  * Execute bundled injected snapshot script
23364
23368
  */
23365
- async executeInjectedScript(snapshotId) {
23369
+ async executeInjectedScript(snapshotId, options) {
23366
23370
  const scriptSource = this.getInjectedScript();
23367
23371
  const result = await this.driver.executeScript(
23368
23372
  `
@@ -23374,10 +23378,11 @@ ${attemptedPaths.map((p) => ` - ${p}`).join("\n")}`
23374
23378
  window.__createSnapshot = __SnapshotInjected.createSnapshot;
23375
23379
  }
23376
23380
  }
23377
- // Call it
23378
- return window.__createSnapshot(arguments[0]);
23381
+ // Call it with options
23382
+ return window.__createSnapshot(arguments[0], arguments[1]);
23379
23383
  `,
23380
- snapshotId
23384
+ snapshotId,
23385
+ options || {}
23381
23386
  );
23382
23387
  return result;
23383
23388
  }
@@ -23649,11 +23654,11 @@ var init_firefox = __esm({
23649
23654
  // ============================================================================
23650
23655
  // Snapshot
23651
23656
  // ============================================================================
23652
- async takeSnapshot() {
23657
+ async takeSnapshot(options) {
23653
23658
  if (!this.snapshot) {
23654
23659
  throw new Error("Not connected");
23655
23660
  }
23656
- return await this.snapshot.takeSnapshot();
23661
+ return await this.snapshot.takeSnapshot(options);
23657
23662
  }
23658
23663
  resolveUidToSelector(uid) {
23659
23664
  if (!this.snapshot) {
@@ -24506,13 +24511,24 @@ async function handleTakeSnapshot(args2) {
24506
24511
  maxLines: requestedMaxLines = DEFAULT_SNAPSHOT_LINES,
24507
24512
  includeAttributes = false,
24508
24513
  includeText = true,
24509
- maxDepth
24514
+ maxDepth,
24515
+ includeAll = false,
24516
+ selector
24510
24517
  } = args2 || {};
24511
24518
  const maxLines = Math.min(Math.max(1, requestedMaxLines), TOKEN_LIMITS.MAX_SNAPSHOT_LINES_CAP);
24512
24519
  const wasCapped = requestedMaxLines > TOKEN_LIMITS.MAX_SNAPSHOT_LINES_CAP;
24513
24520
  const { getFirefox: getFirefox2 } = await init_index().then(() => index_exports);
24514
24521
  const firefox3 = await getFirefox2();
24515
- const snapshot = await firefox3.takeSnapshot();
24522
+ const snapshotOptions = {};
24523
+ if (includeAll) {
24524
+ snapshotOptions.includeAll = includeAll;
24525
+ }
24526
+ if (selector) {
24527
+ snapshotOptions.selector = selector;
24528
+ }
24529
+ const snapshot = await firefox3.takeSnapshot(
24530
+ Object.keys(snapshotOptions).length > 0 ? snapshotOptions : void 0
24531
+ );
24516
24532
  const { formatSnapshotTree: formatSnapshotTree2 } = await Promise.resolve().then(() => (init_formatter(), formatter_exports));
24517
24533
  const options = {
24518
24534
  includeAttributes,
@@ -24526,6 +24542,12 @@ async function handleTakeSnapshot(args2) {
24526
24542
  const truncated = lines.length > maxLines;
24527
24543
  const displayLines = truncated ? lines.slice(0, maxLines) : lines;
24528
24544
  let output = `\u{1F4F8} Snapshot (id=${snapshot.json.snapshotId})`;
24545
+ if (selector) {
24546
+ output += ` [selector: ${selector}]`;
24547
+ }
24548
+ if (includeAll) {
24549
+ output += " [includeAll: true]";
24550
+ }
24529
24551
  if (wasCapped) {
24530
24552
  output += ` [maxLines capped: ${TOKEN_LIMITS.MAX_SNAPSHOT_LINES_CAP}]`;
24531
24553
  }
@@ -24606,6 +24628,14 @@ var init_snapshot2 = __esm({
24606
24628
  maxDepth: {
24607
24629
  type: "number",
24608
24630
  description: "Max tree depth"
24631
+ },
24632
+ includeAll: {
24633
+ type: "boolean",
24634
+ description: "Include all visible elements without relevance filtering. Useful for Vue/Livewire apps (default: false)"
24635
+ },
24636
+ selector: {
24637
+ type: "string",
24638
+ description: 'CSS selector to scope snapshot to specific element (e.g., "#app")'
24609
24639
  }
24610
24640
  }
24611
24641
  }
@@ -24903,33 +24933,48 @@ var init_input = __esm({
24903
24933
  });
24904
24934
 
24905
24935
  // src/tools/screenshot.ts
24906
- function buildScreenshotResponse(base64Png, label) {
24907
- const sizeKB = Math.round(base64Png.length / 1024);
24908
- if (base64Png.length > TOKEN_LIMITS.MAX_SCREENSHOT_CHARS) {
24909
- const truncatedData = base64Png.slice(0, TOKEN_LIMITS.MAX_SCREENSHOT_CHARS);
24910
- return successResponse(`\u{1F4F8} ${label} (${sizeKB}KB) [truncated]
24911
- ${truncatedData}`);
24912
- }
24913
- const warn = base64Png.length > TOKEN_LIMITS.WARNING_THRESHOLD_CHARS ? " [large]" : "";
24914
- return successResponse(`\u{1F4F8} ${label} (${sizeKB}KB)${warn}
24915
- ${base64Png}`);
24916
- }
24917
- async function handleScreenshotPage(_args) {
24936
+ import { writeFile, mkdir } from "fs/promises";
24937
+ import { resolve as resolve2, dirname as dirname2 } from "path";
24938
+ async function saveScreenshot(base64Png, saveTo) {
24939
+ const buffer = Buffer.from(base64Png, "base64");
24940
+ const resolvedPath = resolve2(saveTo);
24941
+ await mkdir(dirname2(resolvedPath), { recursive: true });
24942
+ await writeFile(resolvedPath, buffer);
24943
+ return successResponse(
24944
+ `Screenshot saved to: ${resolvedPath} (${(buffer.length / 1024).toFixed(1)}KB)`
24945
+ );
24946
+ }
24947
+ function imageResponse(base64Png) {
24948
+ return {
24949
+ content: [
24950
+ {
24951
+ type: "image",
24952
+ data: base64Png,
24953
+ mimeType: "image/png"
24954
+ }
24955
+ ]
24956
+ };
24957
+ }
24958
+ async function handleScreenshotPage(args2) {
24918
24959
  try {
24960
+ const { saveTo } = args2 ?? {};
24919
24961
  const { getFirefox: getFirefox2 } = await init_index().then(() => index_exports);
24920
24962
  const firefox3 = await getFirefox2();
24921
24963
  const base64Png = await firefox3.takeScreenshotPage();
24922
24964
  if (!base64Png || typeof base64Png !== "string") {
24923
24965
  throw new Error("Invalid screenshot data");
24924
24966
  }
24925
- return buildScreenshotResponse(base64Png, "page");
24967
+ if (saveTo) {
24968
+ return await saveScreenshot(base64Png, saveTo);
24969
+ }
24970
+ return imageResponse(base64Png);
24926
24971
  } catch (error2) {
24927
24972
  return errorResponse(error2);
24928
24973
  }
24929
24974
  }
24930
24975
  async function handleScreenshotByUid(args2) {
24931
24976
  try {
24932
- const { uid } = args2;
24977
+ const { uid, saveTo } = args2;
24933
24978
  if (!uid || typeof uid !== "string") {
24934
24979
  throw new Error("uid required");
24935
24980
  }
@@ -24940,7 +24985,10 @@ async function handleScreenshotByUid(args2) {
24940
24985
  if (!base64Png || typeof base64Png !== "string") {
24941
24986
  throw new Error("Invalid screenshot data");
24942
24987
  }
24943
- return buildScreenshotResponse(base64Png, uid);
24988
+ if (saveTo) {
24989
+ return await saveScreenshot(base64Png, saveTo);
24990
+ }
24991
+ return imageResponse(base64Png);
24944
24992
  } catch (error2) {
24945
24993
  throw handleUidError(error2, uid);
24946
24994
  }
@@ -24948,18 +24996,24 @@ async function handleScreenshotByUid(args2) {
24948
24996
  return errorResponse(error2);
24949
24997
  }
24950
24998
  }
24951
- var screenshotPageTool, screenshotByUidTool;
24999
+ var SAVE_TO_SCHEMA, screenshotPageTool, screenshotByUidTool;
24952
25000
  var init_screenshot = __esm({
24953
25001
  "src/tools/screenshot.ts"() {
24954
25002
  "use strict";
24955
25003
  init_response_helpers();
24956
25004
  init_uid_helpers();
25005
+ SAVE_TO_SCHEMA = {
25006
+ type: "string",
25007
+ description: "Optional absolute file path to save the screenshot to instead of returning it as image data in the response. Use this in CLI environments (e.g. Claude Code) to avoid filling up the context window with large base64 image data. Example: '/tmp/screenshot.png'"
25008
+ };
24957
25009
  screenshotPageTool = {
24958
25010
  name: "screenshot_page",
24959
25011
  description: "Capture page screenshot as base64 PNG.",
24960
25012
  inputSchema: {
24961
25013
  type: "object",
24962
- properties: {}
25014
+ properties: {
25015
+ saveTo: SAVE_TO_SCHEMA
25016
+ }
24963
25017
  }
24964
25018
  };
24965
25019
  screenshotByUidTool = {
@@ -24971,7 +25025,8 @@ var init_screenshot = __esm({
24971
25025
  uid: {
24972
25026
  type: "string",
24973
25027
  description: "Element UID from snapshot"
24974
- }
25028
+ },
25029
+ saveTo: SAVE_TO_SCHEMA
24975
25030
  },
24976
25031
  required: ["uid"]
24977
25032
  }
@@ -25565,7 +25620,7 @@ __export(index_exports, {
25565
25620
  });
25566
25621
  import { version as version2 } from "process";
25567
25622
  import { fileURLToPath as fileURLToPath2 } from "url";
25568
- import { resolve as resolve2 } from "path";
25623
+ import { resolve as resolve3 } from "path";
25569
25624
  import { realpathSync } from "fs";
25570
25625
  function resetFirefox() {
25571
25626
  if (firefox2) {
@@ -25746,7 +25801,7 @@ var init_index = __esm({
25746
25801
  setViewportSizeTool
25747
25802
  ];
25748
25803
  modulePath = fileURLToPath2(import.meta.url);
25749
- scriptPath = process.argv[1] ? resolve2(process.argv[1]) : "";
25804
+ scriptPath = process.argv[1] ? resolve3(process.argv[1]) : "";
25750
25805
  isMainModule = false;
25751
25806
  try {
25752
25807
  const realModulePath = realpathSync(modulePath);
@@ -1 +1 @@
1
- "use strict";var __SnapshotInjected=(()=>{var h=Object.defineProperty;var W=Object.getOwnPropertyDescriptor;var V=Object.getOwnPropertyNames;var U=Object.prototype.hasOwnProperty;var q=(t,r)=>{for(var e in r)h(t,e,{get:r[e],enumerable:!0})},z=(t,r,e,i)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of V(r))!U.call(t,o)&&o!==e&&h(t,o,{get:()=>r[o],enumerable:!(i=W(r,o))||i.enumerable});return t};var B=t=>z(h({},"__esModule",{value:!0}),t);var rt={};q(rt,{createSnapshot:()=>P});var v=["a","button","input","select","textarea","img","video","audio","iframe"],J=["nav","main","section","article","header","footer","form"],K=["div","span","p","li","ul","ol"];function L(t){if(!t||t.nodeType!==Node.ELEMENT_NODE)return!1;try{let e=window.getComputedStyle(t);if(e.display==="none"||e.visibility==="hidden"||e.opacity==="0")return!1}catch{return!1}let r=t.tagName.toLowerCase();if(v.indexOf(r)!==-1||t.hasAttribute("role")||t.hasAttribute("aria-label")||/^h[1-6]$/.test(r)||J.indexOf(r)!==-1)return!0;if(K.indexOf(r)!==-1){let e=(t.textContent||"").trim();if(e.length>0&&e.length<500||t.id||t.className)return!0}return!1}function M(t){if(t.tabIndex>=0)return!0;let e=t.tagName.toLowerCase();return["a","button","input","select","textarea"].indexOf(e)!==-1}function O(t){let r=t.tagName.toLowerCase();if(v.indexOf(r)!==-1)return!0;let e=t.getAttribute("role");return!!(e&&["button","link","menuitem","tab"].indexOf(e)!==-1||t.hasAttribute("onclick"))}var Q=100;function k(t){if(t.hasAttribute("aria-label"))return t.getAttribute("aria-label")||void 0;let e=t.id;if(e){let o=document.querySelector(`label[for="${e}"]`);if(o?.textContent)return o.textContent.trim()}if(t.hasAttribute("placeholder"))return t.getAttribute("placeholder")||void 0;if(t.hasAttribute("title"))return t.getAttribute("title")||void 0;if(t.hasAttribute("alt"))return t.getAttribute("alt")||void 0;let i=t.tagName.toLowerCase();if(["button","a","h1","h2","h3","h4","h5","h6"].indexOf(i)!==-1)return g(t)}function g(t){let r="";for(let i=0;i<t.childNodes.length;i++){let o=t.childNodes[i];o&&o.nodeType===Node.TEXT_NODE&&(r+=o.textContent||"")}let e=r.trim();if(e)return e.substring(0,Q)}function $(t){let r={},e=!1,i=["disabled","hidden","selected","expanded"];for(let a of i){let n=t.getAttribute(`aria-${a}`);n!==null&&(r[a]=n==="true",e=!0)}let o=["checked","pressed"];for(let a of o){let n=t.getAttribute(`aria-${a}`);n!==null&&(n==="mixed"?r[a]="mixed":r[a]=n==="true",e=!0)}let l=["autocomplete","haspopup","invalid","label","labelledby","describedby","controls"];for(let a of l){let n=t.getAttribute(`aria-${a}`);n&&(r[a]=n,e=!0)}let s=t.getAttribute("aria-level");if(s){let a=parseInt(s,10);isNaN(a)||(r.level=a,e=!0)}return e?r:void 0}function I(t){let r={};try{let e=window.getComputedStyle(t);r.visible=e.display!=="none"&&e.visibility!=="hidden"&&e.opacity!=="0"}catch{r.visible=!1}return r.accessible=r.visible&&!t.getAttribute("aria-hidden"),r.focusable=M(t),r.interactive=O(t),r}var Y=["id","data-testid","data-test-id"];function R(t){let r=[],e=t;for(;e&&e.nodeType===Node.ELEMENT_NODE;){let i=e.nodeName.toLowerCase(),o=!1;for(let n of Y){let u=e.getAttribute(n);if(u){n==="id"?i+="#"+CSS.escape(u):i+=`[${n}="${X(u)}"]`,r.unshift(i),o=!0;break}}if(o)break;let l=e.getAttribute("aria-label"),s=e.getAttribute("role");if(l&&s){i+=`[role="${s}"][aria-label="${X(l)}"]`,r.unshift(i),e=e.parentElement;continue}let a=e.parentElement?.children;if(a&&a.length>1){let n=1;for(let u=0;u<a.length;u++){let d=a[u];if(d){if(d===e)break;d.nodeName===e.nodeName&&n++}}(n>1||a.length>1&&a[0]!==e)&&(i+=`:nth-of-type(${n})`)}if(r.unshift(tt(i)),e=e.parentElement,e&&e.nodeName.toLowerCase()==="body"){r.unshift("body");break}}return r.join(" > ")}function G(t){let r=t.id;if(r)return`//*[@id="${Z(r)}"]`;let e=[],i=t;for(;i&&i.nodeType===Node.ELEMENT_NODE;){let o=i.nodeName.toLowerCase(),l=1,s=i.previousElementSibling;for(;s;)s.nodeName.toLowerCase()===o&&l++,s=s.previousElementSibling;let a=i.parentElement,n=!1;a&&(n=Array.from(a.children).filter(E=>E.nodeName.toLowerCase()===o).length>1);let u=n?`${o}[${l}]`:o;if(e.unshift(u),i=i.parentElement,i&&i.nodeName.toLowerCase()==="html"){e.unshift("html");break}}return"/"+e.join("/")}function X(t){return t.replace(/"/g,'\\"').substring(0,64)}function Z(t){return t.indexOf('"')===-1||t.indexOf("'")===-1?t:`concat(${t.split('"').map((e,i,o)=>i===o.length-1?e?`"${e}"`:"":e?`"${e}",'"'`:`"'"`).filter(e=>e).join(",")})`}function tt(t){return t.length<=64?t:t.substring(0,64)}var et=10,H=1e3;function D(t,r,e=!0){let i=0,o=[],l=!1;function s(n,u){if(u>et||i>=H)return l=!0,null;let d=n.tagName.toLowerCase();if(!(d==="body"||d==="html")&&!L(n))return null;let A=`${r}_${i++}`,j=R(n),F=G(n);o.push({uid:A,css:j,xpath:F});let b=n,N=n.getAttribute("role"),T=k(n),y=g(n),x=b.value,C=b.href,S=b.src,w=$(n),_=I(n),c={uid:A,tag:d,...N&&{role:N},...T&&{name:T},...x&&{value:x},...C&&{href:C},...S&&{src:S},...y&&{text:y},...w&&{aria:w},..._&&{computed:_},children:[]};if(d==="iframe"&&e){try{let f=n,p=f.contentDocument||f.contentWindow?.document;if(p?.body){let m=s(p.body,u+1);m&&(m.isIframe=!0,m.frameSrc=f.src,c.children.push(m))}else c.isIframe=!0,c.frameSrc=f.src,c.crossOrigin=!0}catch{c.isIframe=!0,c.frameSrc=n.src,c.crossOrigin=!0}return c}for(let f=0;f<n.children.length;f++){if(i>=H){l=!0;break}let p=n.children[f];if(!p)continue;let m=s(p,u+1);m&&c.children.push(m)}return c}return{tree:s(t,0),uidMap:o,truncated:l}}function P(t){try{let r=D(document.body,t,!0);if(!r.tree)throw new Error("Failed to generate tree");return r}catch{return{tree:null,uidMap:[],truncated:!1}}}typeof window<"u"&&(window.__createSnapshot=P);return B(rt);})();
1
+ "use strict";var __SnapshotInjected=(()=>{var N=Object.defineProperty;var B=Object.getOwnPropertyDescriptor;var J=Object.getOwnPropertyNames;var K=Object.prototype.hasOwnProperty;var Q=(e,n)=>{for(var t in n)N(e,t,{get:n[t],enumerable:!0})},Y=(e,n,t,r)=>{if(n&&typeof n=="object"||typeof n=="function")for(let i of J(n))!K.call(e,i)&&i!==t&&N(e,i,{get:()=>n[i],enumerable:!(r=B(n,i))||r.enumerable});return e};var Z=e=>Y(N({},"__esModule",{value:!0}),e);var ue={};Q(ue,{createSnapshot:()=>U});var T=["a","button","input","select","textarea","img","video","audio","iframe"],ee=["nav","main","section","article","header","footer","form"],te=["div","span","p","li","ul","ol"];function y(e){if(!e||e.nodeType!==Node.ELEMENT_NODE)return!1;let n=e;for(;n&&n!==document.documentElement;){try{let t=window.getComputedStyle(n),r=parseFloat(t.opacity);if(t.display==="none"||t.visibility==="hidden"||r===0||isNaN(r))return!1}catch{return!1}n=n.parentElement}return!0}function ne(e){let n="";for(let t=0;t<e.childNodes.length;t++){let r=e.childNodes[t];r&&r.nodeType===Node.TEXT_NODE&&(n+=r.textContent||"")}return n.trim()}function re(e){for(let n=0;n<e.children.length;n++){let t=e.children[n];if(t){let r=t.tagName.toLowerCase();if(T.indexOf(r)!==-1||t.hasAttribute("role"))return!0}}return!1}function R(e){if(!e||e.nodeType!==Node.ELEMENT_NODE||!y(e))return!1;let n=e.tagName.toLowerCase();if(T.indexOf(n)!==-1||e.hasAttribute("role")||e.hasAttribute("aria-label")||/^h[1-6]$/.test(n)||ee.indexOf(n)!==-1)return!0;if(te.indexOf(n)!==-1){let t=ne(e);if(t.length>0&&t.length<500||e.id||e.className||re(e))return!0}return!1}function $(e){if(e.tabIndex>=0)return!0;let t=e.tagName.toLowerCase();return["a","button","input","select","textarea"].indexOf(t)!==-1}function D(e){let n=e.tagName.toLowerCase();if(T.indexOf(n)!==-1)return!0;let t=e.getAttribute("role");return!!(t&&["button","link","menuitem","tab"].indexOf(t)!==-1||e.hasAttribute("onclick"))}var ie=100;function X(e){if(e.hasAttribute("aria-label"))return e.getAttribute("aria-label")||void 0;let t=e.id;if(t){let i=document.querySelector(`label[for="${t}"]`);if(i?.textContent)return i.textContent.trim()}if(e.hasAttribute("placeholder"))return e.getAttribute("placeholder")||void 0;if(e.hasAttribute("title"))return e.getAttribute("title")||void 0;if(e.hasAttribute("alt"))return e.getAttribute("alt")||void 0;let r=e.tagName.toLowerCase();if(["button","a","h1","h2","h3","h4","h5","h6"].indexOf(r)!==-1)return x(e)}function x(e){let n="";for(let r=0;r<e.childNodes.length;r++){let i=e.childNodes[r];i&&i.nodeType===Node.TEXT_NODE&&(n+=i.textContent||"")}let t=n.trim();if(t)return t.substring(0,ie)}function W(e){let n={},t=!1,r=["disabled","hidden","selected","expanded"];for(let o of r){let a=e.getAttribute(`aria-${o}`);a!==null&&(n[o]=a==="true",t=!0)}let i=["checked","pressed"];for(let o of i){let a=e.getAttribute(`aria-${o}`);a!==null&&(a==="mixed"?n[o]="mixed":n[o]=a==="true",t=!0)}let l=["autocomplete","haspopup","invalid","label","labelledby","describedby","controls"];for(let o of l){let a=e.getAttribute(`aria-${o}`);a&&(n[o]=a,t=!0)}let u=e.getAttribute("aria-level");if(u){let o=parseInt(u,10);isNaN(o)||(n.level=o,t=!0)}return t?n:void 0}function G(e){let n={};try{let t=window.getComputedStyle(e),r=parseFloat(t.opacity);n.visible=t.display!=="none"&&t.visibility!=="hidden"&&r!==0&&!isNaN(r)}catch{n.visible=!1}return n.accessible=n.visible&&!e.getAttribute("aria-hidden"),n.focusable=$(e),n.interactive=D(e),n}var oe=["id","data-testid","data-test-id"];function P(e){let n=[],t=e;for(;t&&t.nodeType===Node.ELEMENT_NODE;){let r=t.nodeName.toLowerCase(),i=!1;for(let a of oe){let d=t.getAttribute(a);if(d){a==="id"?r+="#"+CSS.escape(d):r+=`[${a}="${H(d)}"]`,n.unshift(r),i=!0;break}}if(i)break;let l=t.getAttribute("aria-label"),u=t.getAttribute("role");if(l&&u){r+=`[role="${u}"][aria-label="${H(l)}"]`,n.unshift(r),t=t.parentElement;continue}let o=t.parentElement?.children;if(o&&o.length>1){let a=1;for(let d=0;d<o.length;d++){let s=o[d];if(s){if(s===t)break;s.nodeName===t.nodeName&&a++}}(a>1||o.length>1&&o[0]!==t)&&(r+=`:nth-of-type(${a})`)}if(n.unshift(se(r)),t=t.parentElement,t&&t.nodeName.toLowerCase()==="body"){n.unshift("body");break}}return n.join(" > ")}function F(e){let n=e.id;if(n)return`//*[@id="${ae(n)}"]`;let t=[],r=e;for(;r&&r.nodeType===Node.ELEMENT_NODE;){let i=r.nodeName.toLowerCase(),l=1,u=r.previousElementSibling;for(;u;)u.nodeName.toLowerCase()===i&&l++,u=u.previousElementSibling;let o=r.parentElement,a=!1;o&&(a=Array.from(o.children).filter(h=>h.nodeName.toLowerCase()===i).length>1);let d=a?`${i}[${l}]`:i;if(t.unshift(d),r=r.parentElement,r&&r.nodeName.toLowerCase()==="html"){t.unshift("html");break}}return"/"+t.join("/")}function H(e){return e.replace(/"/g,'\\"').substring(0,64)}function ae(e){return e.indexOf('"')===-1||e.indexOf("'")===-1?e:`concat(${e.split('"').map((t,r,i)=>r===i.length-1?t?`"${t}"`:"":t?`"${t}",'"'`:`"'"`).filter(t=>t).join(",")})`}function se(e){return e.length<=64?e:e.substring(0,64)}var le=10,j=1e3;function V(e,n,t={}){let{includeAll:r=!1,includeIframes:i=!0}=t,l=0,u=[],o=!1;function a(s,h){if(h>le)return o=!0,{node:null,relevantChildren:[]};if(l>=j)return o=!0,{node:null,relevantChildren:[]};let b=s.tagName.toLowerCase(),C=b==="body"||b==="html",g;r?g=C||y(s):g=C||R(s);let E=[];if(b==="iframe"&&i&&g)try{let c=s,p=c.contentDocument||c.contentWindow?.document;if(p?.body){let f=a(p.body,h+1);f.node&&(f.node.isIframe=!0,f.node.frameSrc=c.src,E.push(f.node))}}catch{}else for(let c=0;c<s.children.length;c++){if(l>=j){o=!0;break}let p=s.children[c];if(!p)continue;let f=a(p,h+1);f.node?E.push(f.node):f.relevantChildren.length>0&&E.push(...f.relevantChildren)}if(!g)return{node:null,relevantChildren:E};let S=`${n}_${l++}`,q=P(s),z=F(s);u.push({uid:S,css:q,xpath:z});let A=s,v=s.getAttribute("role"),O=X(s),_=x(s),w=A.value,M=A.href,L=A.src,I=W(s),k=G(s),m={uid:S,tag:b,...v&&{role:v},...O&&{name:O},...w&&{value:w},...M&&{href:M},...L&&{src:L},..._&&{text:_},...I&&{aria:I},...k&&{computed:k},children:E};if(b==="iframe"&&i)try{let c=s;(c.contentDocument||c.contentWindow?.document)?.body||(m.isIframe=!0,m.frameSrc=c.src,m.crossOrigin=!0)}catch{m.isIframe=!0,m.frameSrc=s.src,m.crossOrigin=!0}return{node:m,relevantChildren:[]}}return{tree:a(e,0).node,uidMap:u,truncated:o}}function U(e,n){try{let t=document.body;if(n?.selector)try{let l=document.querySelector(n.selector);if(!l)return{tree:null,uidMap:[],truncated:!1,selectorError:`Selector "${n.selector}" not found`};t=l}catch{return{tree:null,uidMap:[],truncated:!1,selectorError:`Invalid selector syntax: "${n.selector}"`}}let r={includeIframes:n?.includeIframes??!0};n?.includeAll!==void 0&&(r.includeAll=n.includeAll);let i=V(t,e,r);if(!i.tree)throw new Error("Failed to generate tree");return i}catch{return{tree:null,uidMap:[],truncated:!1}}}typeof window<"u"&&(window.__createSnapshot=U);return Z(ue);})();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firefox-devtools-mcp",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "description": "Model Context Protocol (MCP) server for Firefox DevTools automation",
5
5
  "author": "freema",
6
6
  "license": "MIT",