@extension.dev/mcp 10.2.0 → 10.3.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.
@@ -10,7 +10,7 @@
10
10
  "name": "extension-mcp",
11
11
  "source": "./",
12
12
  "description": "MCP tools for browser extension development: scaffold from 50+ templates, run the dev server with HMR, inspect the live DOM and logs, and publish store-ready builds for Chrome, Edge, and Firefox.",
13
- "version": "10.2.0",
13
+ "version": "10.3.0",
14
14
  "category": "development",
15
15
  "author": {
16
16
  "name": "Cezar Augusto"
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "extension-mcp",
3
3
  "description": "MCP tools for browser extension development: scaffold from 50+ templates, run the dev server with HMR, inspect the live DOM and logs, and publish store-ready builds for Chrome, Edge, and Firefox. Ships /extension, /extension-add, /extension-debug, and /extension-publish commands.",
4
- "version": "10.2.0",
4
+ "version": "10.3.0",
5
5
  "author": {
6
6
  "name": "Cezar Augusto",
7
7
  "email": "hello@extension.dev",
package/CHANGELOG.md CHANGED
@@ -1,5 +1,45 @@
1
1
  # Changelog
2
2
 
3
+ ## 10.3.0
4
+
5
+ Four fixes for places where a tool answered confidently about something it
6
+ had not actually checked: a login refusal that never said where the slugs
7
+ come from, a doctor report labelled with a browser that had no session, a
8
+ preview probe that certified whatever was listening on the port, and a
9
+ release gate that passed on a stale bundle.
10
+
11
+ - `extension_auth login` refused a malformed `project` without saying
12
+ where the two slugs come from. Both the refusal and the input schema now
13
+ name the console address bar as the source: an existing project's page is
14
+ `console.extension.dev/<workspace>/<project>`, and a project that does
15
+ not exist yet is created at extension.dev/new first.
16
+ - `extension_doctor` diagnoses the session that exists, not a hardcoded
17
+ default. The shared session resolver only counts contracts marked
18
+ "ready", which is exactly wrong for the tool you reach for when a session
19
+ failed: doctor fell back to chrome, labelled the report with it, read the
20
+ absent contract, and returned a healthy verdict over a session that had
21
+ errored. When there is no ready session, the browser with a contract on
22
+ disk wins, newest first and whatever its status.
23
+ - `extension_preview_web` certifies the probe against the build the call
24
+ just minted instead of against the port. Anything can be listening on the
25
+ local preview port, and the old probe read `hostReachable` and
26
+ `previewLoadable` as true off whatever JSON came back, then echoed that
27
+ server's `identifier`, name and version into the envelope unmarked, which
28
+ made any local server a text channel into the agent's context. The name
29
+ and version must now match the `dist` manifest read off disk, and the
30
+ identifier is echoed only when it matches the derivation the real
31
+ middleware uses. A mismatch returns the status
32
+ `host-serving-different-artifact` with `previewLoadable: false`, and
33
+ anything the host claimed that could not be confirmed locally travels
34
+ clipped under `probe.hostReported`, named as the host's claim.
35
+ - The release gate asserts the version the built bundle exports rather than
36
+ grepping the bundle for the version string. The grep greenwashed: the
37
+ bundle carries the pinned engine version and fifty-two `"version":"1.0.0"`
38
+ strings from the template corpus, so a release cut at any of those
39
+ numbers passed the gate over a stale `dist`. `dist/module.js` now exports
40
+ the manifest version it inlined at build time, and the gate reads it back
41
+ and compares.
42
+
3
43
  ## 10.2.0
4
44
 
5
45
  A production walk of the full share-and-publish loop, then a fix for every
package/dist/module.d.ts CHANGED
@@ -1 +1 @@
1
- export { startServer, runCli } from "./src/index";
1
+ export { startServer, runCli, version } from "./src/index";
package/dist/module.js CHANGED
@@ -582,7 +582,7 @@ __webpack_require__.d(wait_namespaceObject, {
582
582
  handler: ()=>wait_handler,
583
583
  schema: ()=>wait_schema
584
584
  });
585
- var package_namespaceObject = JSON.parse('{"rE":"10.2.0","El":{"OP":"4.0.20"}}');
585
+ var package_namespaceObject = JSON.parse('{"rE":"10.3.0","El":{"OP":"4.0.20"}}');
586
586
  const ENVELOPE_SCHEMA = 1;
587
587
  const collectWarnings = (warnings)=>{
588
588
  if (!warnings) return [];
@@ -6305,6 +6305,14 @@ function safeHostBase(raw) {
6305
6305
  base: trimmed
6306
6306
  };
6307
6307
  }
6308
+ const HOST_CLAIM_MAX_CHARS = 120;
6309
+ function clipHostClaim(value) {
6310
+ return value.length > HOST_CLAIM_MAX_CHARS ? value.slice(0, HOST_CLAIM_MAX_CHARS) : value;
6311
+ }
6312
+ function expectedPreviewIdentifier(name, distDir) {
6313
+ const base = (name ?? node_path.basename(distDir)).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "extension";
6314
+ return base.startsWith("local-") ? base : `local-${base}`;
6315
+ }
6308
6316
  const SURFACE = {
6309
6317
  defaultOrigin: DEFAULT_PREVIEW_DEV_URL,
6310
6318
  scheme: (encoded)=>`preview://build/${encoded}`,
@@ -6651,7 +6659,14 @@ async function preview_web_handler(args) {
6651
6659
  }
6652
6660
  });
6653
6661
  const contentType = res.headers.get("content-type") ?? "";
6654
- if (!res.ok || !contentType.includes("application/json")) return envelope_envelope({
6662
+ let raw = null;
6663
+ if (res.ok && contentType.includes("application/json")) try {
6664
+ raw = await res.json();
6665
+ } catch {
6666
+ raw = null;
6667
+ }
6668
+ const payload = raw && "object" == typeof raw && !Array.isArray(raw) ? raw : null;
6669
+ if (!payload) return envelope_envelope({
6655
6670
  ok: true,
6656
6671
  command: preview_web_COMMAND,
6657
6672
  status: "host-not-serving-preview",
@@ -6670,7 +6685,62 @@ async function preview_web_handler(args) {
6670
6685
  `${SURFACE.label} answered but not with a preview payload. On the deployed host ${SURFACE.fetchPath} does not exist, because it is dev-only. ${remedy}`
6671
6686
  ]
6672
6687
  });
6673
- const payload = await res.json();
6688
+ const localName = "string" == typeof manifest.name ? manifest.name : null;
6689
+ const localVersion = "string" == typeof manifest.version ? manifest.version : null;
6690
+ const remoteName = "string" == typeof payload.manifest?.name ? payload.manifest.name : null;
6691
+ const remoteVersion = "string" == typeof payload.version ? payload.version : null;
6692
+ const remoteIdentifier = "string" == typeof payload.identifier ? payload.identifier : null;
6693
+ const fileCount = Array.isArray(payload.files) ? payload.files.length : 0;
6694
+ const matchesDist = (null === localName || remoteName === localName) && (null === localVersion || remoteVersion === localVersion);
6695
+ if (!matchesDist) {
6696
+ const hostReported = {
6697
+ ...null !== remoteIdentifier ? {
6698
+ identifier: clipHostClaim(remoteIdentifier)
6699
+ } : {},
6700
+ ...null !== remoteName ? {
6701
+ name: clipHostClaim(remoteName)
6702
+ } : {},
6703
+ ...null !== remoteVersion ? {
6704
+ version: clipHostClaim(remoteVersion)
6705
+ } : {}
6706
+ };
6707
+ return envelope_envelope({
6708
+ ok: true,
6709
+ command: preview_web_COMMAND,
6710
+ status: "host-serving-different-artifact",
6711
+ value: {
6712
+ ...result,
6713
+ hostReachable: true,
6714
+ previewLoadable: false,
6715
+ probe: {
6716
+ method: "server-fetch",
6717
+ provesBrowserLoad: false,
6718
+ matchesDist: false,
6719
+ fileCount,
6720
+ ...Object.keys(hostReported).length ? {
6721
+ hostReported
6722
+ } : {}
6723
+ }
6724
+ },
6725
+ hint,
6726
+ warnings: [
6727
+ ...previewWarnings,
6728
+ `Something answered at ${hostBase}${SURFACE.fetchPath} but described a different artifact than the build this call minted, so previewLoadable stays false. probe.hostReported carries that host's own claims, clipped and unverified, not facts about your build; another local server on this port is the usual cause. ${remedy}`
6729
+ ]
6730
+ });
6731
+ }
6732
+ const identifierConfirmed = null !== remoteIdentifier && remoteIdentifier === expectedPreviewIdentifier(localName, distDir);
6733
+ const unconfirmed = {
6734
+ ...!identifierConfirmed && null !== remoteIdentifier ? {
6735
+ identifier: clipHostClaim(remoteIdentifier)
6736
+ } : {},
6737
+ ...null === localName && null !== remoteName ? {
6738
+ name: clipHostClaim(remoteName)
6739
+ } : {},
6740
+ ...null === localVersion && null !== remoteVersion ? {
6741
+ version: clipHostClaim(remoteVersion)
6742
+ } : {}
6743
+ };
6674
6744
  return envelope_envelope({
6675
6745
  ok: true,
6676
6746
  command: preview_web_COMMAND,
@@ -6681,12 +6751,22 @@ async function preview_web_handler(args) {
6681
6751
  previewLoadable: true,
6682
6752
  previewLoadableLane: "local-dev-host",
6683
6753
  probe: {
6684
- identifier: payload.identifier,
6685
- loadedName: payload.manifest?.name,
6686
- loadedVersion: payload.version,
6687
- fileCount: Array.isArray(payload.files) ? payload.files.length : 0,
6754
+ ...identifierConfirmed ? {
6755
+ identifier: remoteIdentifier
6756
+ } : {},
6757
+ ...null !== localName ? {
6758
+ loadedName: localName
6759
+ } : {},
6760
+ ...null !== localVersion ? {
6761
+ loadedVersion: localVersion
6762
+ } : {},
6763
+ fileCount,
6688
6764
  method: "server-fetch",
6689
- provesBrowserLoad: false
6765
+ provesBrowserLoad: false,
6766
+ matchesDist: true,
6767
+ ...Object.keys(unconfirmed).length ? {
6768
+ hostReported: unconfirmed
6769
+ } : {}
6690
6770
  }
6691
6771
  },
6692
6772
  hint,
@@ -11456,6 +11536,23 @@ function readContractForDiagnosis(projectPath, browser) {
11456
11536
  return null;
11457
11537
  }
11458
11538
  }
11539
+ function sightedContractBrowser(projectPath) {
11540
+ let dirs;
11541
+ try {
11542
+ dirs = node_fs.readdirSync(sessionArtifactsRootDir(projectPath));
11543
+ } catch {
11544
+ return null;
11545
+ }
11546
+ let best = null;
11547
+ for (const dir of dirs)try {
11548
+ const stat = node_fs.statSync(bridge_readyContractPath(projectPath, dir));
11549
+ if (!best || stat.mtimeMs > best.mtimeMs) best = {
11550
+ browser: dir,
11551
+ mtimeMs: stat.mtimeMs
11552
+ };
11553
+ } catch {}
11554
+ return best ? best.browser : null;
11555
+ }
11459
11556
  const doctor_schema = {
11460
11557
  name: "extension_doctor",
11461
11558
  description: "Diagnose a dev session end to end: ready contract, dev-server process, control-port agreement, control channel, eval token, executor, browser liveness. This returns one {check, status, detail, remediation?} per leg, in dependency order. Read a 'skip' as blocked, not as a pass: it names the check that blocked it. Run this first when any act tool (storage, reload, eval, open) errors unexpectedly. Call it with no projectPath for a pre-flight environment check (node, the Extension.js CLI, the template cache) before any project exists.",
@@ -11551,7 +11648,8 @@ function capabilityProbeChecks(parsed) {
11551
11648
  async function doctor_handler(args) {
11552
11649
  if (!args.projectPath) return environmentPreflight();
11553
11650
  const projectPath = args.projectPath;
11554
- const { browser } = resolveSessionBrowser(projectPath, args.browser);
11651
+ const resolved = resolveSessionBrowser(projectPath, args.browser);
11652
+ const browser = "fallback" === resolved.source ? sightedContractBrowser(projectPath) ?? resolved.browser : resolved.browser;
11555
11653
  const { code, stdout, stderr } = await runExtensionCli([
11556
11654
  "doctor",
11557
11655
  projectPath,
@@ -12321,7 +12419,7 @@ function resumePending(deviceCode, verificationUri) {
12321
12419
  }
12322
12420
  async function loginToProject(args) {
12323
12421
  const project = String(args.project || "").trim();
12324
- if (!/^[^/]+\/[^/]+$/.test(project)) return login_fail("BadRequest", "project must be in the form '<workspace>/<project>'.", "bad-request", "E_BAD_REQUEST");
12422
+ if (!/^[^/]+\/[^/]+$/.test(project)) return login_fail("BadRequest", "project must be in the form '<workspace>/<project>'. The slug pair is the console address bar: an existing project's page is console.extension.dev/<workspace>/<project>. If the project does not exist yet, create it at extension.dev/new, then log in with the slugs the console shows.", "bad-request", "E_BAD_REQUEST");
12325
12423
  const apiCheck = safeApiBase(resolveApiBase(args.api), args.api);
12326
12424
  if (!apiCheck.ok) return login_fail("LoginConfigError", apiCheck.message, "login-failed", "E_AUTH_FAILED");
12327
12425
  const apiBase = apiCheck.base;
@@ -12562,7 +12660,7 @@ const auth_schema = {
12562
12660
  },
12563
12661
  project: {
12564
12662
  type: "string",
12565
- description: "login: target project as '<workspace>/<project>'; the token is scoped to it."
12663
+ description: "login: target project as '<workspace>/<project>'; the token is scoped to it. The slug pair is the console address bar: an existing project's page is console.extension.dev/<workspace>/<project>. Create one at extension.dev/new if none exists yet."
12566
12664
  },
12567
12665
  deviceCode: {
12568
12666
  type: "string",
@@ -13461,4 +13559,5 @@ async function runCli(cmd, args) {
13461
13559
  log(`Unknown command: ${cmd}. Expected one of: login, logout, whoami, release.`);
13462
13560
  return 1;
13463
13561
  }
13464
- export { runCli, startServer };
13562
+ var version_0 = package_namespaceObject.rE;
13563
+ export { runCli, startServer, version_0 as version };
@@ -1,3 +1,5 @@
1
+ import { version } from "../package.json";
2
+ export { version };
1
3
  export interface ToolModule {
2
4
  schema: {
3
5
  name: string;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@extension.dev/mcp",
3
3
  "type": "module",
4
- "version": "10.2.0",
4
+ "version": "10.3.0",
5
5
  "description": "MCP server that lets AI agents (Claude Code, Claude Desktop, Cursor, Copilot, Codex) build, run, inspect, and publish browser extensions. 28 tools for scaffolding, live DOM inspection, log streaming, and store-ready builds across Chrome, Edge, Firefox, Safari, and every Chromium- or Gecko-based browser (Brave, Opera, Vivaldi, Yandex, Waterfox, LibreWolf). Powered by extension.dev and Extension.js.",
6
6
  "mcpName": "io.github.extensiondev/mcp",
7
7
  "license": "Apache-2.0",
package/server.json CHANGED
@@ -7,13 +7,13 @@
7
7
  "source": "github"
8
8
  },
9
9
  "websiteUrl": "https://extension.dev",
10
- "version": "10.2.0",
10
+ "version": "10.3.0",
11
11
  "packages": [
12
12
  {
13
13
  "registryType": "npm",
14
14
  "registryBaseUrl": "https://registry.npmjs.org",
15
15
  "identifier": "@extension.dev/mcp",
16
- "version": "10.2.0",
16
+ "version": "10.3.0",
17
17
  "runtimeHint": "npx",
18
18
  "transport": {
19
19
  "type": "stdio"