@extension.dev/mcp 4.2.0 → 4.2.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.
File without changes
package/dist/module.js CHANGED
@@ -9,8 +9,8 @@ import node_os from "node:os";
9
9
  import { extensionBuild } from "extension-develop";
10
10
  import cross_spawn from "cross-spawn";
11
11
  import { filterKeysForThisBrowser } from "browser-extension-manifest-fields";
12
- import node_net from "node:net";
13
12
  import ws from "ws";
13
+ import node_http from "node:http";
14
14
  import { clearCredentials, exchangeAndPersist, fetchLoginConfig, pollForToken, publish, readCredentials, resolveApiBase, resolveToken, safeApiBase, startDeviceCode } from "@extension.dev/core";
15
15
  import { extensionInstall, getManagedBrowsersCacheRoot } from "extension-install";
16
16
  import { execFile } from "node:child_process";
@@ -183,7 +183,7 @@ __webpack_require__.d(whoami_namespaceObject, {
183
183
  handler: ()=>whoami_handler,
184
184
  schema: ()=>whoami_schema
185
185
  });
186
- var package_namespaceObject = JSON.parse('{"rE":"4.2.0","El":{"OP":"^4.0.8"}}');
186
+ var package_namespaceObject = JSON.parse('{"rE":"4.2.1","El":{"OP":"^4.0.8"}}');
187
187
  const create_schema = {
188
188
  name: "extension_create",
189
189
  description: "Create a new browser extension project from a template in the extension.dev template catalog. Use extension_list_templates to see available options.",
@@ -401,7 +401,8 @@ async function build_handler(args) {
401
401
  const summary = await extensionBuild(args.projectPath, {
402
402
  browser: args.browser ?? "chrome",
403
403
  zip: args.zip ?? false,
404
- zipSource: args.zipSource ?? false
404
+ zipSource: args.zipSource ?? false,
405
+ exitOnError: false
405
406
  });
406
407
  return JSON.stringify({
407
408
  success: true,
@@ -417,6 +418,10 @@ async function build_handler(args) {
417
418
  }
418
419
  }
419
420
  const PINNED_CLI_VERSION = String(package_namespaceObject.El.OP ?? "latest").replace(/^[\^~]/, "");
421
+ function pinnedCliVersion() {
422
+ const override = String(process.env.EXTENSION_MCP_CLI_VERSION || "").trim();
423
+ return override || PINNED_CLI_VERSION;
424
+ }
420
425
  function resolveExtensionInvocation(projectDir) {
421
426
  if (projectDir) {
422
427
  const bin = node_path.join(projectDir, "node_modules", ".bin", "win32" === process.platform ? "extension.cmd" : "extension");
@@ -431,7 +436,7 @@ function resolveExtensionInvocation(projectDir) {
431
436
  return {
432
437
  command: "npx",
433
438
  prefixArgs: [
434
- `extension@${PINNED_CLI_VERSION}`
439
+ `extension@${pinnedCliVersion()}`
435
440
  ]
436
441
  };
437
442
  }
@@ -1571,6 +1576,50 @@ class CDPClient extends CDPConnection {
1571
1576
  return result ?? null;
1572
1577
  }
1573
1578
  }
1579
+ async function resolveCdpPort(projectPath, browser, options) {
1580
+ const waitMs = options?.waitMs ?? 20000;
1581
+ const readyPath = node_path.resolve(projectPath, "dist", "extension-js", browser, "ready.json");
1582
+ const deadline = Date.now() + waitMs;
1583
+ let contractSeen = false;
1584
+ for(;;){
1585
+ try {
1586
+ const contract = JSON.parse(node_fs.readFileSync(readyPath, "utf8"));
1587
+ contractSeen = true;
1588
+ if ("number" == typeof contract.cdpPort) return {
1589
+ port: contract.cdpPort,
1590
+ source: "contract"
1591
+ };
1592
+ } catch {
1593
+ if (!contractSeen) break;
1594
+ }
1595
+ if (Date.now() >= deadline) break;
1596
+ await new Promise((resolve)=>setTimeout(resolve, 500));
1597
+ }
1598
+ if (!contractSeen && await isCdpEndpoint(9222)) return {
1599
+ port: 9222,
1600
+ source: "default-probe"
1601
+ };
1602
+ return null;
1603
+ }
1604
+ const CDP_PORT_MISSING_HINT = "The session's ready contract has no CDP port (the browser may still be binding its debug port, or was launched without one). Confirm the session with extension_wait, give it a moment, and retry.";
1605
+ function isCdpEndpoint(port) {
1606
+ return new Promise((resolve)=>{
1607
+ const req = node_http.get({
1608
+ host: "127.0.0.1",
1609
+ port,
1610
+ path: "/json/version",
1611
+ timeout: 1000
1612
+ }, (res)=>{
1613
+ res.resume();
1614
+ resolve(200 === res.statusCode);
1615
+ });
1616
+ req.on("error", ()=>resolve(false));
1617
+ req.on("timeout", ()=>{
1618
+ req.destroy();
1619
+ resolve(false);
1620
+ });
1621
+ });
1622
+ }
1574
1623
  const source_inspect_schema = {
1575
1624
  name: "extension_source_inspect",
1576
1625
  description: "Inspect a running extension's live state via Chrome DevTools Protocol: full HTML (with shadow DOM), DOM structure, content script injection, console messages, and CSS selector queries. Requires an active dev or start session.",
@@ -1632,28 +1681,6 @@ const source_inspect_schema = {
1632
1681
  ]
1633
1682
  }
1634
1683
  };
1635
- async function findCdpPort(projectPath, browser) {
1636
- const readyPath = node_path.resolve(projectPath, "dist", "extension-js", browser, "ready.json");
1637
- try {
1638
- const contract = JSON.parse(node_fs.readFileSync(readyPath, "utf8"));
1639
- if ("number" == typeof contract.cdpPort) return contract.cdpPort;
1640
- } catch {}
1641
- const defaultPort = 9222;
1642
- return new Promise((resolve)=>{
1643
- const socket = new node_net.Socket();
1644
- socket.setTimeout(1000);
1645
- socket.on("connect", ()=>{
1646
- socket.destroy();
1647
- resolve(defaultPort);
1648
- });
1649
- socket.on("error", ()=>resolve(null));
1650
- socket.on("timeout", ()=>{
1651
- socket.destroy();
1652
- resolve(null);
1653
- });
1654
- socket.connect(defaultPort, "127.0.0.1");
1655
- });
1656
- }
1657
1684
  async function source_inspect_handler(args) {
1658
1685
  const browser = args.browser ?? "chrome";
1659
1686
  const include = new Set(args.include ?? [
@@ -1671,11 +1698,12 @@ async function source_inspect_handler(args) {
1671
1698
  error: `Source inspection for ${browser} uses RDP (Remote Debug Protocol). Currently only Chromium CDP is supported.`,
1672
1699
  hint: "Use --browser=chrome for source inspection, or use the CLI: npx extension dev --source"
1673
1700
  });
1674
- const cdpPort = await findCdpPort(args.projectPath, browser);
1675
- if (!cdpPort) return JSON.stringify({
1701
+ const resolved = await resolveCdpPort(args.projectPath, browser);
1702
+ if (!resolved) return JSON.stringify({
1676
1703
  error: "No active dev session found. Cannot connect to Chrome DevTools Protocol.",
1677
- hint: "Start a dev session first with extension_dev, then use extension_wait to confirm it is ready."
1704
+ hint: `Start a dev session first with extension_dev, then use extension_wait to confirm it is ready. ${CDP_PORT_MISSING_HINT}`
1678
1705
  });
1706
+ const cdpPort = resolved.port;
1679
1707
  const cdp = new CDPClient();
1680
1708
  try {
1681
1709
  const allTargets = await CDPClient.discoverTargets(cdpPort);
@@ -1788,16 +1816,27 @@ async function list_extensions_handler(args) {
1788
1816
  error: `Listing extensions for ${browser} uses RDP (Remote Debug Protocol). Currently only Chromium CDP is supported.`,
1789
1817
  hint: "Use --browser=chrome."
1790
1818
  });
1791
- const cdpPort = await list_extensions_findCdpPort(args.projectPath, browser);
1792
- if (!cdpPort) return JSON.stringify({
1819
+ const resolved = await resolveCdpPort(args.projectPath, browser);
1820
+ if (!resolved) return JSON.stringify({
1793
1821
  error: "No active dev session found. Cannot connect to Chrome DevTools Protocol.",
1794
- hint: "Start a dev session first with extension_dev, then use extension_wait to confirm it is ready."
1822
+ hint: `Start a dev session first with extension_dev, then use extension_wait to confirm it is ready. ${CDP_PORT_MISSING_HINT}`
1795
1823
  });
1824
+ const cdpPort = resolved.port;
1796
1825
  const cdp = new CDPClient();
1797
1826
  try {
1798
- const browserWsUrl = await CDPClient.discoverBrowserWsUrl(cdpPort);
1799
- await cdp.connect(browserWsUrl);
1800
- const targets = await cdp.getTargets();
1827
+ let targets = null;
1828
+ let lastError = null;
1829
+ for(let attempt = 0; attempt < 3; attempt++)try {
1830
+ const browserWsUrl = await CDPClient.discoverBrowserWsUrl(cdpPort);
1831
+ await cdp.connect(browserWsUrl);
1832
+ targets = await cdp.getTargets();
1833
+ break;
1834
+ } catch (error) {
1835
+ lastError = error;
1836
+ cdp.disconnect();
1837
+ if (attempt < 2) await new Promise((resolve)=>setTimeout(resolve, 800));
1838
+ }
1839
+ if (!targets) throw lastError;
1801
1840
  const byId = new Map();
1802
1841
  for (const t of targets){
1803
1842
  const url = String(t.url ?? "");
@@ -1849,28 +1888,6 @@ async function list_extensions_handler(args) {
1849
1888
  cdp.disconnect();
1850
1889
  }
1851
1890
  }
1852
- async function list_extensions_findCdpPort(projectPath, browser) {
1853
- const readyPath = node_path.resolve(projectPath, "dist", "extension-js", browser, "ready.json");
1854
- try {
1855
- const contract = JSON.parse(node_fs.readFileSync(readyPath, "utf8"));
1856
- if ("number" == typeof contract.cdpPort) return contract.cdpPort;
1857
- } catch {}
1858
- const defaultPort = 9222;
1859
- return new Promise((resolve)=>{
1860
- const socket = new node_net.Socket();
1861
- socket.setTimeout(1000);
1862
- socket.on("connect", ()=>{
1863
- socket.destroy();
1864
- resolve(defaultPort);
1865
- });
1866
- socket.on("error", ()=>resolve(null));
1867
- socket.on("timeout", ()=>{
1868
- socket.destroy();
1869
- resolve(null);
1870
- });
1871
- socket.connect(defaultPort, "127.0.0.1");
1872
- });
1873
- }
1874
1891
  const CONTROL_WS_PATH = "/extjs-control";
1875
1892
  const LEVEL_ORDER = [
1876
1893
  "error",
@@ -0,0 +1,7 @@
1
+ export declare function resolveCdpPort(projectPath: string, browser: string, options?: {
2
+ waitMs?: number;
3
+ }): Promise<{
4
+ port: number;
5
+ source: "contract" | "default-probe";
6
+ } | null>;
7
+ export declare const CDP_PORT_MISSING_HINT = "The session's ready contract has no CDP port (the browser may still be binding its debug port, or was launched without one). Confirm the session with extension_wait, give it a moment, and retry.";
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@extension.dev/mcp",
3
3
  "type": "module",
4
- "version": "4.2.0",
4
+ "version": "4.2.1",
5
5
  "description": "MCP server that lets AI agents build, run, inspect, and publish browser extensions. 28 tools for scaffolding, live DOM inspection, log streaming, and store-ready builds across Chrome, Edge, and Firefox.",
6
6
  "license": "MIT",
7
7
  "author": {
@@ -20,6 +20,7 @@
20
20
  "engines": {
21
21
  "node": ">=20.18"
22
22
  },
23
+ "packageManager": "pnpm@10.19.0",
23
24
  "exports": {
24
25
  ".": {
25
26
  "types": "./dist/module.d.ts",
@@ -39,6 +40,15 @@
39
40
  "CHANGELOG.md",
40
41
  "LICENSE"
41
42
  ],
43
+ "scripts": {
44
+ "clean": "rm -rf dist",
45
+ "watch": "rslib build --watch",
46
+ "compile": "rslib build",
47
+ "build": "pnpm run compile",
48
+ "start": "node bin/extension-mcp.js",
49
+ "test": "vitest run",
50
+ "prepublishOnly": "pnpm run test && pnpm run compile"
51
+ },
42
52
  "publishConfig": {
43
53
  "access": "public",
44
54
  "registry": "https://registry.npmjs.org"
@@ -77,13 +87,5 @@
77
87
  "@types/ws": "^8.18.1",
78
88
  "typescript": "6.0.2",
79
89
  "vitest": "^4.1.3"
80
- },
81
- "scripts": {
82
- "clean": "rm -rf dist",
83
- "watch": "rslib build --watch",
84
- "compile": "rslib build",
85
- "build": "pnpm run compile",
86
- "start": "node bin/extension-mcp.js",
87
- "test": "vitest run"
88
90
  }
89
- }
91
+ }