firefox-devtools-mcp 0.6.1 → 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.
Files changed (3) hide show
  1. package/README.md +13 -1
  2. package/dist/index.js +60 -35
  3. package/package.json +1 -1
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
  };
@@ -24933,33 +24933,48 @@ var init_input = __esm({
24933
24933
  });
24934
24934
 
24935
24935
  // src/tools/screenshot.ts
24936
- function buildScreenshotResponse(base64Png, label) {
24937
- const sizeKB = Math.round(base64Png.length / 1024);
24938
- if (base64Png.length > TOKEN_LIMITS.MAX_SCREENSHOT_CHARS) {
24939
- const truncatedData = base64Png.slice(0, TOKEN_LIMITS.MAX_SCREENSHOT_CHARS);
24940
- return successResponse(`\u{1F4F8} ${label} (${sizeKB}KB) [truncated]
24941
- ${truncatedData}`);
24942
- }
24943
- const warn = base64Png.length > TOKEN_LIMITS.WARNING_THRESHOLD_CHARS ? " [large]" : "";
24944
- return successResponse(`\u{1F4F8} ${label} (${sizeKB}KB)${warn}
24945
- ${base64Png}`);
24946
- }
24947
- 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) {
24948
24959
  try {
24960
+ const { saveTo } = args2 ?? {};
24949
24961
  const { getFirefox: getFirefox2 } = await init_index().then(() => index_exports);
24950
24962
  const firefox3 = await getFirefox2();
24951
24963
  const base64Png = await firefox3.takeScreenshotPage();
24952
24964
  if (!base64Png || typeof base64Png !== "string") {
24953
24965
  throw new Error("Invalid screenshot data");
24954
24966
  }
24955
- return buildScreenshotResponse(base64Png, "page");
24967
+ if (saveTo) {
24968
+ return await saveScreenshot(base64Png, saveTo);
24969
+ }
24970
+ return imageResponse(base64Png);
24956
24971
  } catch (error2) {
24957
24972
  return errorResponse(error2);
24958
24973
  }
24959
24974
  }
24960
24975
  async function handleScreenshotByUid(args2) {
24961
24976
  try {
24962
- const { uid } = args2;
24977
+ const { uid, saveTo } = args2;
24963
24978
  if (!uid || typeof uid !== "string") {
24964
24979
  throw new Error("uid required");
24965
24980
  }
@@ -24970,7 +24985,10 @@ async function handleScreenshotByUid(args2) {
24970
24985
  if (!base64Png || typeof base64Png !== "string") {
24971
24986
  throw new Error("Invalid screenshot data");
24972
24987
  }
24973
- return buildScreenshotResponse(base64Png, uid);
24988
+ if (saveTo) {
24989
+ return await saveScreenshot(base64Png, saveTo);
24990
+ }
24991
+ return imageResponse(base64Png);
24974
24992
  } catch (error2) {
24975
24993
  throw handleUidError(error2, uid);
24976
24994
  }
@@ -24978,18 +24996,24 @@ async function handleScreenshotByUid(args2) {
24978
24996
  return errorResponse(error2);
24979
24997
  }
24980
24998
  }
24981
- var screenshotPageTool, screenshotByUidTool;
24999
+ var SAVE_TO_SCHEMA, screenshotPageTool, screenshotByUidTool;
24982
25000
  var init_screenshot = __esm({
24983
25001
  "src/tools/screenshot.ts"() {
24984
25002
  "use strict";
24985
25003
  init_response_helpers();
24986
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
+ };
24987
25009
  screenshotPageTool = {
24988
25010
  name: "screenshot_page",
24989
25011
  description: "Capture page screenshot as base64 PNG.",
24990
25012
  inputSchema: {
24991
25013
  type: "object",
24992
- properties: {}
25014
+ properties: {
25015
+ saveTo: SAVE_TO_SCHEMA
25016
+ }
24993
25017
  }
24994
25018
  };
24995
25019
  screenshotByUidTool = {
@@ -25001,7 +25025,8 @@ var init_screenshot = __esm({
25001
25025
  uid: {
25002
25026
  type: "string",
25003
25027
  description: "Element UID from snapshot"
25004
- }
25028
+ },
25029
+ saveTo: SAVE_TO_SCHEMA
25005
25030
  },
25006
25031
  required: ["uid"]
25007
25032
  }
@@ -25595,7 +25620,7 @@ __export(index_exports, {
25595
25620
  });
25596
25621
  import { version as version2 } from "process";
25597
25622
  import { fileURLToPath as fileURLToPath2 } from "url";
25598
- import { resolve as resolve2 } from "path";
25623
+ import { resolve as resolve3 } from "path";
25599
25624
  import { realpathSync } from "fs";
25600
25625
  function resetFirefox() {
25601
25626
  if (firefox2) {
@@ -25776,7 +25801,7 @@ var init_index = __esm({
25776
25801
  setViewportSizeTool
25777
25802
  ];
25778
25803
  modulePath = fileURLToPath2(import.meta.url);
25779
- scriptPath = process.argv[1] ? resolve2(process.argv[1]) : "";
25804
+ scriptPath = process.argv[1] ? resolve3(process.argv[1]) : "";
25780
25805
  isMainModule = false;
25781
25806
  try {
25782
25807
  const realModulePath = realpathSync(modulePath);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "firefox-devtools-mcp",
3
- "version": "0.6.1",
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",