@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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +40 -0
- package/dist/module.d.ts +1 -1
- package/dist/module.js +111 -12
- package/dist/src/index.d.ts +2 -0
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
6685
|
-
|
|
6686
|
-
|
|
6687
|
-
|
|
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
|
|
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
|
-
|
|
13562
|
+
var version_0 = package_namespaceObject.rE;
|
|
13563
|
+
export { runCli, startServer, version_0 as version };
|
package/dist/src/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@extension.dev/mcp",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "10.
|
|
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.
|
|
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.
|
|
16
|
+
"version": "10.3.0",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|