firefox-devtools-mcp 0.5.3 → 0.6.1

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/dist/index.js CHANGED
@@ -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
  }
@@ -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.5.3",
3
+ "version": "0.6.1",
4
4
  "description": "Model Context Protocol (MCP) server for Firefox DevTools automation",
5
5
  "author": "freema",
6
6
  "license": "MIT",
@@ -88,7 +88,8 @@
88
88
  "dist",
89
89
  "README.md",
90
90
  "LICENSE",
91
- "scripts"
91
+ "scripts",
92
+ "plugins"
92
93
  ],
93
94
  "repository": {
94
95
  "type": "git",
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "firefox-devtools",
3
+ "description": "Firefox browser automation via WebDriver BiDi. Automate Firefox for testing, scraping, form filling, screenshots, and debugging.",
4
+ "version": "0.6.0",
5
+ "author": {
6
+ "name": "Tomáš Grasl",
7
+ "url": "https://www.tomasgrasl.cz/"
8
+ },
9
+ "repository": "https://github.com/freema/firefox-devtools-mcp",
10
+ "license": "MIT"
11
+ }
@@ -0,0 +1,8 @@
1
+ {
2
+ "mcpServers": {
3
+ "firefox-devtools": {
4
+ "command": "npx",
5
+ "args": ["-y", "firefox-devtools-mcp@latest"]
6
+ }
7
+ }
8
+ }
@@ -0,0 +1,82 @@
1
+ # Firefox DevTools Plugin for Claude Code
2
+
3
+ Firefox browser automation via WebDriver BiDi. Navigate pages, fill forms, click elements, take screenshots, and monitor console/network activity.
4
+
5
+ ## What's Included
6
+
7
+ This plugin provides:
8
+
9
+ - **MCP Server** - Connects Claude Code to Firefox automation
10
+ - **Skills** - Auto-triggers for browser automation, testing, and scraping tasks
11
+ - **Agents** - Dedicated `e2e-tester` and `web-scraper` agents for focused tasks
12
+ - **Commands** - `/firefox:navigate`, `/firefox:screenshot`, `/firefox:debug`
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ claude plugin install firefox-devtools
18
+ ```
19
+
20
+ ## Commands
21
+
22
+ ### /firefox:navigate
23
+
24
+ Navigate to a URL and take a DOM snapshot:
25
+
26
+ ```
27
+ /firefox:navigate https://example.com
28
+ /firefox:navigate https://github.com/login
29
+ ```
30
+
31
+ ### /firefox:screenshot
32
+
33
+ Capture the current page or a specific element:
34
+
35
+ ```
36
+ /firefox:screenshot
37
+ /firefox:screenshot e15
38
+ ```
39
+
40
+ ### /firefox:debug
41
+
42
+ Show console errors and failed network requests:
43
+
44
+ ```
45
+ /firefox:debug
46
+ /firefox:debug console
47
+ /firefox:debug network
48
+ ```
49
+
50
+ ## Agents
51
+
52
+ Spawn agents to keep your main context clean:
53
+
54
+ ```
55
+ spawn e2e-tester to test the login flow on https://app.example.com
56
+ spawn web-scraper to extract product prices from https://shop.example.com
57
+ ```
58
+
59
+ ## Usage Examples
60
+
61
+ The plugin works automatically when you ask about browser tasks:
62
+
63
+ - "Navigate to example.com and take a screenshot"
64
+ - "Fill out the login form and submit"
65
+ - "Check for JavaScript errors on this page"
66
+ - "Scrape all product prices from this page"
67
+
68
+ ## Key Workflow
69
+
70
+ 1. `take_snapshot` - Creates DOM snapshot with UIDs (e.g., `e42`)
71
+ 2. Interact using UIDs - `click_by_uid`, `fill_by_uid`, etc.
72
+ 3. Re-snapshot after DOM changes
73
+
74
+ ## Requirements
75
+
76
+ - Firefox 120+
77
+ - Node.js 20.19.0+
78
+
79
+ ## Links
80
+
81
+ - [Repository](https://github.com/freema/firefox-devtools-mcp)
82
+ - [npm](https://www.npmjs.com/package/firefox-devtools-mcp)
@@ -0,0 +1,36 @@
1
+ ---
2
+ name: e2e-tester
3
+ description: Agent for running E2E tests on web applications. Navigates pages, fills forms, clicks buttons, and verifies results.
4
+ model: sonnet
5
+ ---
6
+
7
+ You are an E2E testing agent specializing in automated browser testing using Firefox DevTools MCP.
8
+
9
+ ## Your Task
10
+
11
+ When given a test scenario, execute it step-by-step using Firefox automation tools, verify the results, and report pass/fail status.
12
+
13
+ ## Process
14
+
15
+ 1. **Navigate to the target**: Use `navigate_page` to open the URL
16
+ 2. **Take snapshot**: Always call `take_snapshot` before interacting
17
+ 3. **Execute test steps**: Use `fill_by_uid`, `click_by_uid`, etc.
18
+ 4. **Re-snapshot after changes**: DOM updates require fresh snapshots
19
+ 5. **Verify results**: Check for expected elements, text, or states
20
+ 6. **Report outcome**: Clear pass/fail with evidence (screenshots if needed)
21
+
22
+ ## Available Tools
23
+
24
+ - `navigate_page` - Go to URL
25
+ - `take_snapshot` - Get DOM with UIDs
26
+ - `fill_by_uid` / `fill_form_by_uid` - Enter text
27
+ - `click_by_uid` - Click elements
28
+ - `screenshot_page` - Capture evidence
29
+ - `list_console_messages` - Check for JS errors
30
+
31
+ ## Guidelines
32
+
33
+ - Always snapshot before AND after interactions
34
+ - Take screenshots at key checkpoints
35
+ - Report console errors as test failures
36
+ - Be specific about what passed or failed
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: web-scraper
3
+ description: Agent for extracting structured data from web pages. Navigates, handles pagination, and extracts content.
4
+ model: sonnet
5
+ ---
6
+
7
+ You are a web scraping agent specializing in data extraction using Firefox DevTools MCP.
8
+
9
+ ## Your Task
10
+
11
+ When given a scraping task, navigate to pages, extract the requested data, handle pagination if needed, and return structured results.
12
+
13
+ ## Process
14
+
15
+ 1. **Navigate to source**: Use `navigate_page` to open the URL
16
+ 2. **Take snapshot**: Call `take_snapshot` to see page structure
17
+ 3. **Identify data elements**: Find UIDs for elements containing target data
18
+ 4. **Extract content**: The snapshot contains text content of elements
19
+ 5. **Handle pagination**: Click "next" buttons, re-snapshot, repeat
20
+ 6. **Structure output**: Return data in requested format (JSON, table, etc.)
21
+
22
+ ## Available Tools
23
+
24
+ - `navigate_page` - Go to URL
25
+ - `take_snapshot` - Get DOM with content and UIDs
26
+ - `click_by_uid` - Navigate pagination
27
+ - `list_network_requests` - Monitor API calls (sometimes easier to scrape)
28
+
29
+ ## Guidelines
30
+
31
+ - Snapshots contain element text - no need for separate "get text" calls
32
+ - Check network requests for API endpoints (often cleaner than DOM scraping)
33
+ - Respect rate limits - don't hammer the server
34
+ - Handle "load more" buttons and infinite scroll patterns
35
+ - Return structured data, not raw HTML
@@ -0,0 +1,32 @@
1
+ ---
2
+ description: Show console errors and failed network requests
3
+ argument-hint: [console|network|all]
4
+ ---
5
+
6
+ # /firefox:debug
7
+
8
+ Displays debugging information from the current page.
9
+
10
+ ## Usage
11
+
12
+ ```
13
+ /firefox:debug # Show all (console errors + failed requests)
14
+ /firefox:debug console # Console messages only
15
+ /firefox:debug network # Network requests only
16
+ ```
17
+
18
+ ## Examples
19
+
20
+ ```
21
+ /firefox:debug
22
+ /firefox:debug console
23
+ /firefox:debug network
24
+ ```
25
+
26
+ ## What Happens
27
+
28
+ - `console`: Calls `list_console_messages` with `level="error"`
29
+ - `network`: Calls `list_network_requests` with `status="failed"`
30
+ - `all` (default): Shows both console errors and failed network requests
31
+
32
+ Useful for debugging page issues, JavaScript errors, and API failures.
@@ -0,0 +1,31 @@
1
+ ---
2
+ description: Navigate Firefox to a URL and take a snapshot
3
+ argument-hint: <url>
4
+ ---
5
+
6
+ # /firefox:navigate
7
+
8
+ Opens a URL in Firefox and takes a DOM snapshot for interaction.
9
+
10
+ ## Usage
11
+
12
+ ```
13
+ /firefox:navigate <url>
14
+ ```
15
+
16
+ ## Examples
17
+
18
+ ```
19
+ /firefox:navigate https://example.com
20
+ /firefox:navigate https://github.com/login
21
+ /firefox:navigate file:///path/to/local.html
22
+ ```
23
+
24
+ ## What Happens
25
+
26
+ 1. Calls `navigate_page` with the URL
27
+ 2. Waits for page load
28
+ 3. Calls `take_snapshot` to create UID mappings
29
+ 4. Returns the DOM snapshot with interactive elements marked
30
+
31
+ After navigating, you can interact with elements using their UIDs (e.g., `e42`).
@@ -0,0 +1,30 @@
1
+ ---
2
+ description: Take a screenshot of the current page or element
3
+ argument-hint: [uid]
4
+ ---
5
+
6
+ # /firefox:screenshot
7
+
8
+ Captures a screenshot of the page or a specific element.
9
+
10
+ ## Usage
11
+
12
+ ```
13
+ /firefox:screenshot # Full page
14
+ /firefox:screenshot <uid> # Specific element
15
+ ```
16
+
17
+ ## Examples
18
+
19
+ ```
20
+ /firefox:screenshot
21
+ /firefox:screenshot e15
22
+ /firefox:screenshot e42
23
+ ```
24
+
25
+ ## What Happens
26
+
27
+ - Without UID: Calls `screenshot_page` for full page capture
28
+ - With UID: Calls `screenshot_by_uid` for element-specific capture
29
+
30
+ Screenshots are saved and displayed in the conversation.
@@ -0,0 +1,65 @@
1
+ ---
2
+ name: browser-automation
3
+ description: This skill should be used when the user asks about browser automation, testing web pages, scraping content, filling forms, taking screenshots, or monitoring console/network activity. Activates for E2E testing, web scraping, form automation, or debugging web applications.
4
+ ---
5
+
6
+ When the user asks about browser automation, use Firefox DevTools MCP to control a real Firefox browser.
7
+
8
+ ## When to Use This Skill
9
+
10
+ Activate this skill when the user:
11
+
12
+ - Wants to automate browser interactions ("Fill out this form", "Click the login button")
13
+ - Needs E2E testing ("Test the checkout flow", "Verify the login works")
14
+ - Requests web scraping ("Extract prices from this page", "Get all links")
15
+ - Needs screenshots ("Screenshot this page", "Capture the error state")
16
+ - Wants to debug ("Check for JS errors", "Show failed network requests")
17
+
18
+ ## Core Workflow
19
+
20
+ ### Step 1: Navigate and Snapshot
21
+
22
+ ```
23
+ navigate_page url="https://example.com"
24
+ take_snapshot
25
+ ```
26
+
27
+ The snapshot returns a DOM representation with UIDs (e.g., `e42`) for each interactive element.
28
+
29
+ ### Step 2: Interact with Elements
30
+
31
+ Use UIDs from the snapshot:
32
+
33
+ ```
34
+ fill_by_uid uid="e5" text="user@example.com"
35
+ click_by_uid uid="e8"
36
+ ```
37
+
38
+ ### Step 3: Re-snapshot After Changes
39
+
40
+ DOM changes invalidate UIDs. Always re-snapshot after:
41
+ - Page navigation
42
+ - Form submissions
43
+ - Dynamic content loads
44
+
45
+ ```
46
+ take_snapshot # Get fresh UIDs
47
+ ```
48
+
49
+ ## Quick Reference
50
+
51
+ | Task | Tools |
52
+ |------|-------|
53
+ | Navigate | `navigate_page` |
54
+ | See DOM | `take_snapshot` |
55
+ | Click | `click_by_uid` |
56
+ | Type | `fill_by_uid`, `fill_form_by_uid` |
57
+ | Screenshot | `screenshot_page`, `screenshot_by_uid` |
58
+ | Debug | `list_console_messages`, `list_network_requests` |
59
+
60
+ ## Guidelines
61
+
62
+ - **Always snapshot first**: UIDs only exist after `take_snapshot`
63
+ - **Re-snapshot after DOM changes**: UIDs become stale after interactions
64
+ - **Check for errors**: Use `list_console_messages level="error"` to catch JS issues
65
+ - **Firefox only**: This MCP controls Firefox, not Chrome or Safari