@mozilla/firefox-devtools-mcp 0.9.9 → 0.9.10

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
@@ -39,8 +39,19 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
39
39
  ));
40
40
 
41
41
  // src/cli.ts
42
+ import { createHash } from "crypto";
43
+ import { homedir } from "os";
44
+ import { join } from "path";
42
45
  import yargs from "yargs";
43
46
  import { hideBin } from "yargs/helpers";
47
+ function defaultProfileDir(firefoxPath) {
48
+ const base = join(homedir(), ".firefox-devtools-mcp");
49
+ if (!firefoxPath) {
50
+ return join(base, "profile");
51
+ }
52
+ const hash2 = createHash("sha1").update(firefoxPath).digest("hex").slice(0, 8);
53
+ return join(base, `profile-${hash2}`);
54
+ }
44
55
  function parsePrefs(prefs) {
45
56
  const result = {};
46
57
  if (!prefs || prefs.length === 0) {
@@ -122,6 +133,11 @@ var init_cli = __esm({
122
133
  type: "string",
123
134
  description: "Path to Firefox profile directory (optional, for persistent profile)"
124
135
  },
136
+ autoProfile: {
137
+ type: "boolean",
138
+ description: "Automatically use a persistent profile stored in ~/.firefox-devtools-mcp/. When --firefox-path is set, the profile is scoped to that binary so different Firefox builds stay isolated.",
139
+ default: (process.env.AUTO_PROFILE ?? "false") === "true"
140
+ },
125
141
  firefoxArg: {
126
142
  type: "array",
127
143
  description: "Additional arguments for Firefox. Only applies when Firefox is launched by firefox-devtools-mcp."
@@ -28076,8 +28092,8 @@ var SERVER_NAME, SERVER_VERSION;
28076
28092
  var init_constants = __esm({
28077
28093
  "src/config/constants.ts"() {
28078
28094
  "use strict";
28079
- SERVER_NAME = "firefox-devtools";
28080
- SERVER_VERSION = "0.7.1";
28095
+ SERVER_NAME = true ? "@mozilla/firefox-devtools-mcp" : "firefox-devtools";
28096
+ SERVER_VERSION = true ? "0.9.10" : "dev";
28081
28097
  }
28082
28098
  });
28083
28099
 
@@ -28157,12 +28173,12 @@ var init_logger = __esm({
28157
28173
 
28158
28174
  // src/firefox/profile.ts
28159
28175
  import { existsSync, mkdirSync, copyFileSync } from "fs";
28160
- import { join } from "path";
28176
+ import { join as join2 } from "path";
28161
28177
  function isFirefoxProfile(dir) {
28162
- return FIREFOX_PROFILE_INDICATORS.some((file2) => existsSync(join(dir, file2)));
28178
+ return FIREFOX_PROFILE_INDICATORS.some((file2) => existsSync(join2(dir, file2)));
28163
28179
  }
28164
28180
  function resolveProfilePath(parentPath) {
28165
- const mcpProfilePath = join(parentPath, MCP_PROFILE_DIR_NAME);
28181
+ const mcpProfilePath = join2(parentPath, MCP_PROFILE_DIR_NAME);
28166
28182
  let warning = null;
28167
28183
  if (isFirefoxProfile(parentPath)) {
28168
28184
  warning = `Warning: The path "${parentPath}" looks like an existing Firefox profile.
@@ -28177,9 +28193,9 @@ function resolveProfilePath(parentPath) {
28177
28193
  if (isNew) {
28178
28194
  mkdirSync(mcpProfilePath, { recursive: true });
28179
28195
  log(`Created MCP profile directory: ${mcpProfilePath}`);
28180
- const parentPrefs = join(parentPath, "prefs.js");
28196
+ const parentPrefs = join2(parentPath, "prefs.js");
28181
28197
  if (existsSync(parentPrefs)) {
28182
- copyFileSync(parentPrefs, join(mcpProfilePath, "prefs.js"));
28198
+ copyFileSync(parentPrefs, join2(mcpProfilePath, "prefs.js"));
28183
28199
  log(` Copied prefs.js from parent profile for initial preferences.`);
28184
28200
  }
28185
28201
  }
@@ -28199,14 +28215,14 @@ var init_profile = __esm({
28199
28215
  import { Builder, Browser, Capabilities } from "selenium-webdriver";
28200
28216
  import firefox from "selenium-webdriver/firefox.js";
28201
28217
  import { mkdirSync as mkdirSync2, openSync, closeSync, existsSync as existsSync2, readdirSync, statSync } from "fs";
28202
- import { homedir } from "os";
28203
- import { join as join2, delimiter } from "path";
28218
+ import { homedir as homedir2 } from "os";
28219
+ import { join as join3, delimiter } from "path";
28204
28220
  function findGeckodriverInPath(binaryName) {
28205
28221
  for (const dir of (process.env.PATH ?? "").split(delimiter)) {
28206
28222
  if (!dir) {
28207
28223
  continue;
28208
28224
  }
28209
- const candidate = join2(dir, binaryName);
28225
+ const candidate = join3(dir, binaryName);
28210
28226
  if (existsSync2(candidate)) {
28211
28227
  return candidate;
28212
28228
  }
@@ -28214,18 +28230,18 @@ function findGeckodriverInPath(binaryName) {
28214
28230
  return null;
28215
28231
  }
28216
28232
  function findGeckodriverInSeleniumCache(binaryName) {
28217
- const cacheBase = join2(homedir(), ".cache/selenium/geckodriver");
28233
+ const cacheBase = join3(homedir2(), ".cache/selenium/geckodriver");
28218
28234
  try {
28219
28235
  if (!existsSync2(cacheBase)) {
28220
28236
  return null;
28221
28237
  }
28222
28238
  for (const platformDir of readdirSync(cacheBase)) {
28223
- const platformPath = join2(cacheBase, platformDir);
28239
+ const platformPath = join3(cacheBase, platformDir);
28224
28240
  if (!statSync(platformPath).isDirectory()) {
28225
28241
  continue;
28226
28242
  }
28227
28243
  for (const versionDir of readdirSync(platformPath).sort().reverse()) {
28228
- const candidate = join2(platformPath, versionDir, binaryName);
28244
+ const candidate = join3(platformPath, versionDir, binaryName);
28229
28245
  if (existsSync2(candidate)) {
28230
28246
  return candidate;
28231
28247
  }
@@ -28315,10 +28331,10 @@ var init_core3 = __esm({
28315
28331
  if (this.options.logFile) {
28316
28332
  this.logFilePath = this.options.logFile;
28317
28333
  } else if (this.options.env && Object.keys(this.options.env).length > 0) {
28318
- const outputDir = join2(homedir(), ".firefox-devtools-mcp", "output");
28334
+ const outputDir = join3(homedir2(), ".firefox-devtools-mcp", "output");
28319
28335
  mkdirSync2(outputDir, { recursive: true });
28320
28336
  const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
28321
- this.logFilePath = join2(outputDir, `firefox-${timestamp}.log`);
28337
+ this.logFilePath = join3(outputDir, `firefox-${timestamp}.log`);
28322
28338
  }
28323
28339
  if (this.options.env) {
28324
28340
  for (const [key, value] of Object.entries(this.options.env)) {
@@ -33419,7 +33435,7 @@ async function getFirefox() {
33419
33435
  options = {
33420
33436
  firefoxPath: args.firefoxPath ?? void 0,
33421
33437
  headless: args.headless,
33422
- profilePath: args.profilePath ?? void 0,
33438
+ profilePath: args.profilePath ?? (args.autoProfile ? defaultProfileDir(args.firefoxPath) : void 0),
33423
33439
  viewport: args.viewport ?? void 0,
33424
33440
  args: args.firefoxArg ?? void 0,
33425
33441
  startUrl: args.startUrl ?? void 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mozilla/firefox-devtools-mcp",
3
- "version": "0.9.9",
3
+ "version": "0.9.10",
4
4
  "description": "Model Context Protocol (MCP) server for Firefox DevTools automation",
5
5
  "author": "Mozilla",
6
6
  "license": "MIT OR Apache-2.0",
@@ -15,6 +15,7 @@
15
15
  "build": "tsup",
16
16
  "build:moz": "node scripts/generate-moz-package.mjs && tsup --config tsup.config.moz.ts",
17
17
  "build:all": "npm run build && npm run build:moz",
18
+ "build:mcpb": "node scripts/build-mcpb.mjs",
18
19
  "publish:moz": "node scripts/publish-moz-package.mjs",
19
20
  "start": "node dist/index.js",
20
21
  "setup": "node scripts/setup-mcp-config.js",
@@ -75,6 +76,7 @@
75
76
  "yargs": "17.7.2"
76
77
  },
77
78
  "devDependencies": {
79
+ "@anthropic-ai/mcpb": "2.1.2",
78
80
  "@types/jsdom": "28.0.0",
79
81
  "@types/node": "24.1.0",
80
82
  "@types/selenium-webdriver": "4.35.1",
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Builds the firefox-devtools-mcp .mcpb bundle (Claude Desktop extension).
4
+ * Assembles a staging directory with dist/, production node_modules and
5
+ * manifest.json, then runs `mcpb pack` on it.
6
+ */
7
+
8
+ import { readFileSync, writeFileSync, existsSync, cpSync, mkdtempSync, rmSync } from 'node:fs';
9
+ import { execSync } from 'node:child_process';
10
+ import { resolve, dirname } from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+ import { tmpdir } from 'node:os';
13
+
14
+ const root = resolve(dirname(fileURLToPath(import.meta.url)), '..');
15
+ const pkg = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8'));
16
+ const manifest = JSON.parse(readFileSync(resolve(root, 'manifest.json'), 'utf8'));
17
+
18
+ console.log('Building server...');
19
+ execSync('npm run build', { cwd: root, stdio: 'inherit' });
20
+
21
+ const stagingDir = mkdtempSync(resolve(tmpdir(), 'firefox-devtools-mcpb-'));
22
+
23
+ try {
24
+ cpSync(resolve(root, 'dist'), resolve(stagingDir, 'dist'), { recursive: true });
25
+ for (const file of ['README.md', 'LICENSE-MIT', 'LICENSE-APACHE', 'package.json', 'package-lock.json']) {
26
+ cpSync(resolve(root, file), resolve(stagingDir, file));
27
+ }
28
+
29
+ console.log('Installing production dependencies...');
30
+ execSync('npm ci --omit=dev', { cwd: stagingDir, stdio: 'inherit' });
31
+
32
+ writeFileSync(
33
+ resolve(stagingDir, 'manifest.json'),
34
+ JSON.stringify({ ...manifest, version: pkg.version }, null, 2) + '\n'
35
+ );
36
+
37
+ const outDir = resolve(root, 'dist-mcpb');
38
+ if (!existsSync(outDir)) {
39
+ execSync(`mkdir -p ${outDir}`);
40
+ }
41
+ const outFile = resolve(outDir, `firefox-devtools-mcp-${pkg.version}.mcpb`);
42
+
43
+ console.log('Packing .mcpb bundle...');
44
+ execSync(`npx mcpb pack "${stagingDir}" "${outFile}"`, {
45
+ cwd: root,
46
+ stdio: 'inherit',
47
+ });
48
+
49
+ console.log(`Written ${outFile}`);
50
+ } finally {
51
+ rmSync(stagingDir, { recursive: true, force: true });
52
+ }