@extension.dev/mcp 10.3.0 → 10.3.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.
@@ -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.3.0",
13
+ "version": "10.3.1",
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.3.0",
4
+ "version": "10.3.1",
5
5
  "author": {
6
6
  "name": "Cezar Augusto",
7
7
  "email": "hello@extension.dev",
package/CHANGELOG.md CHANGED
@@ -1,5 +1,18 @@
1
1
  # Changelog
2
2
 
3
+ ## 10.3.1
4
+
5
+ The 10.3.0 tarball shipped two comments a stranger could read: an HTML
6
+ comment in the popup markup naming a private package, and an eight line
7
+ internal roadmap comment plus two inline script comments in the live
8
+ preview sandbox page. Both were removed at source right after that
9
+ release. This release exists to take them off the registry, because a
10
+ published tarball cannot be edited in place.
11
+
12
+ - The popup markup no longer names any private package.
13
+ - `extensions/live-preview/chromium/sandbox/page-0.html` ships exactly
14
+ the markup it runs, with no comments at all.
15
+
3
16
  ## 10.3.0
4
17
 
5
18
  Four fixes for places where a tool answered confidently about something it
package/README.md CHANGED
@@ -127,7 +127,7 @@ cp node_modules/@extension.dev/mcp/claude/commands/*.md ~/my-extension/.claude/c
127
127
  | platform | `extension_shares` | List every link you have shared, and revoke one permanently |
128
128
  | platform | `extension_publish` | Publish a shareable preview to extension.dev |
129
129
  | platform | `extension_release_promote` | Promote a build to a release channel, headless |
130
- | platform | `extension_submit` | Submit for store review: Chrome, Firefox, Edge, Safari, through extension.dev |
130
+ | platform | `extension_submit` | Submit for store review: Chrome, Firefox and Edge, through extension.dev |
131
131
  | platform | `extension_release_status` | Read release channels, recent builds, and store submission and review state |
132
132
 
133
133
  Browser-launching tools (`dev`, `start`) shell out to the `extension` CLI, the project's own `node_modules/.bin/extension` when present, otherwise `npx extension@<pinned>` at the version this package is verified against; everything else runs in-process.
@@ -142,7 +142,7 @@ That is a different job from shipping. Use `share` for the build you are holding
142
142
 
143
143
  ## From preview to store
144
144
 
145
- The platform tools connect agents to [extension.dev](https://extension.dev): `extension_auth` runs extension.dev's own device flow (you approve the code at [extension.dev/device](https://extension.dev/device), and GitHub is federated server-side, so no GitHub token ever reaches your machine) and stores a project-scoped token locally (never returned to the agent), `extension_publish` turns a build your project has already published into a shareable URL, and `extension_release_promote` promotes a tested build to a release channel from CI or an agent session, no browser required. `extension_submit` submits a built extension to the Chrome Web Store, Edge Add-ons, Firefox AMO, and the App Store for Safari through extension.dev, which holds your store credentials and dispatches the release from your project's mirror CI, it defaults to a dry run and store credentials are never tool arguments. The two verbs are not interchangeable: `extension_publish` pushes to the extension.dev platform, `extension_submit` sends the build into a store's review queue, which is irreversible. After a real submission, `extension_release_status` reads the recorded outcome, per-store credential health, and review state from the project's public registry, so agents and CI can answer "was it approved?" without a console visit. Access tokens live at most 7 days; CI pipelines re-mint them from the console's Access tokens page.
145
+ The platform tools connect agents to [extension.dev](https://extension.dev): `extension_auth` runs extension.dev's own device flow (you approve the code at [extension.dev/device](https://extension.dev/device), and GitHub is federated server-side, so no GitHub token ever reaches your machine) and stores a project-scoped token locally (never returned to the agent), `extension_publish` turns a build your project has already published into a shareable URL, and `extension_release_promote` promotes a tested build to a release channel from CI or an agent session, no browser required. `extension_submit` submits a built extension to the Chrome Web Store, Edge Add-ons, and Firefox AMO through extension.dev, which holds your store credentials and dispatches the release from your project's mirror CI, it defaults to a dry run and store credentials are never tool arguments. Safari and the App Store are one paid lane on the platform, so a free workspace is refused there and the other three stores are unaffected. The two verbs are not interchangeable: `extension_publish` pushes to the extension.dev platform, `extension_submit` sends the build into a store's review queue, which is irreversible. After a real submission, `extension_release_status` reads the recorded outcome, per-store credential health, and review state from the project's public registry, so agents and CI can answer "was it approved?" without a console visit. Access tokens live at most 7 days; CI pipelines re-mint them from the console's Access tokens page.
146
146
 
147
147
  ## The extension.dev stack
148
148
 
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.3.0","El":{"OP":"4.0.20"}}');
585
+ var package_namespaceObject = JSON.parse('{"rE":"10.3.1","El":{"OP":"4.0.20"}}');
586
586
  const ENVELOPE_SCHEMA = 1;
587
587
  const collectWarnings = (warnings)=>{
588
588
  if (!warnings) return [];
@@ -3530,6 +3530,26 @@ async function pollBootVerdict(projectPath, browser, options) {
3530
3530
  warnings: []
3531
3531
  };
3532
3532
  }
3533
+ const SYSTEM_PROFILE_ARG = "false";
3534
+ function holdsState(dir) {
3535
+ try {
3536
+ return node_fs.readdirSync(dir).length > 0;
3537
+ } catch {
3538
+ return false;
3539
+ }
3540
+ }
3541
+ function profileCarriesTabsOver(projectPath, browser, profileArg) {
3542
+ const raw = "string" == typeof profileArg ? profileArg.trim() : "";
3543
+ if (raw === SYSTEM_PROFILE_ARG) return true;
3544
+ if (raw) return holdsState(node_path.resolve(projectPath, raw));
3545
+ return holdsState(node_path.join(browserProfileRootDir(projectPath, browser), PERSISTED_PROFILE_DIR_NAME));
3546
+ }
3547
+ function sessionProfileReused(projectPath, browser) {
3548
+ return findSessionInfo(projectPath, browser)?.profileReused === true;
3549
+ }
3550
+ function restoredTabWarning(url) {
3551
+ return `No target was given and this session runs on a browser profile that already held a previous session's state, so ${url} may be a tab restored from that previous session rather than anything this run opened. Nothing here proves the page belongs to the extension you just started: pass url, or open a surface with extension_open, and inspect again.`;
3552
+ }
3533
3553
  function sweepCarriers(projectPaths) {
3534
3554
  const out = [];
3535
3555
  const seen = new Set();
@@ -3870,6 +3890,7 @@ function launchFlagArgs(args) {
3870
3890
  if (args.extensions?.length) cli.push("--extensions", args.extensions.join(","));
3871
3891
  return cli;
3872
3892
  }
3893
+ const REUSED_PROFILE_NOTE = "This session reuses a browser profile that already held a previous session's state, so the browser may restore that session's tabs alongside the extension. extension_inspect with no target ranks whatever is open and can land on one of them; it flags this session as profileReused for that reason. Pass url, or open the surface you mean with extension_open, before reading anything.";
3873
3894
  const dev_schema = {
3874
3895
  name: "extension_dev",
3875
3896
  description: "Run the extension while you edit it: dev build, hot module replacement, and a browser with the extension loaded. Reach for this first when the ask is \"run my extension\". ONLY this tool unlocks the control channel that extension_storage, extension_reload, extension_open and extension_dom_snapshot need (allowControl:true) and the eval channel that extension_eval needs (allowEval:true, which implies allowControl, so you never need to pass both). Use extension_start instead to run the production build in a browser. The result carries the process info that extension_wait and extension_inspect need.",
@@ -3954,6 +3975,7 @@ async function dev_handler(args) {
3954
3975
  }
3955
3976
  const carrier = args.carrier ? materializeCarrier(args.projectPath, browser) : null;
3956
3977
  const allowControl = Boolean(args.allowControl || args.allowEval);
3978
+ const profileReused = profileCarriesTabsOver(args.projectPath, browser, args.profile);
3957
3979
  const cliArgs = [
3958
3980
  "dev",
3959
3981
  args.projectPath,
@@ -3979,7 +4001,8 @@ async function dev_handler(args) {
3979
4001
  port: args.port,
3980
4002
  projectPath: args.projectPath,
3981
4003
  command: "dev",
3982
- noBrowser: Boolean(args.noBrowser)
4004
+ noBrowser: Boolean(args.noBrowser),
4005
+ profileReused
3983
4006
  });
3984
4007
  child.on("exit", ()=>{
3985
4008
  removeSession(args.projectPath, browser, pid);
@@ -4104,7 +4127,8 @@ async function dev_handler(args) {
4104
4127
  port: boundPort,
4105
4128
  projectPath: args.projectPath,
4106
4129
  command: "dev",
4107
- noBrowser: Boolean(args.noBrowser)
4130
+ noBrowser: Boolean(args.noBrowser),
4131
+ profileReused
4108
4132
  });
4109
4133
  const portReport = null !== boundPort ? {
4110
4134
  port: boundPort,
@@ -4138,6 +4162,7 @@ async function dev_handler(args) {
4138
4162
  },
4139
4163
  warnings: [
4140
4164
  portNote,
4165
+ profileReused && !args.noBrowser ? REUSED_PROFILE_NOTE : null,
4141
4166
  ...boot.warnings
4142
4167
  ],
4143
4168
  hint: args.noBrowser ? "Build-only session (noBrowser: true): no browser will launch, so no runtime will ever attach. extension_wait returns as soon as the first compile lands (compiled: true, browserAttached: false) instead of waiting out its budget; do not wait for a browser. The control verbs (storage/reload/open/dom_snapshot/eval) need a live browser and will not work against this session. When you are done, call extension_stop to shut down the dev server." : "Use extension_wait to check when the extension is fully loaded, then extension_inspect to inspect the live state. " + (allowControl ? `Control channel is ON: extension_${controlVerbs.split(", ").join("/extension_")}${args.allowEval ? "/extension_eval" : ""} will work against this session.` : "Control channel is OFF: extension_storage/reload/open/dom_snapshot need allowControl: true, and extension_eval needs allowEval: true (which also implies allowControl). To unlock them, call extension_dev again with the flag you need plus replace: true (it stops this session first); a plain second call is refused so the session does not fork.") + " When you are done, call extension_stop to shut down the dev server and browser."
@@ -4192,6 +4217,7 @@ async function start_handler(args) {
4192
4217
  if (args.noBrowser) cliArgs.push("--no-browser");
4193
4218
  cliArgs.push(...launchFlagArgs(args));
4194
4219
  const stale = removeCarrier(args.projectPath);
4220
+ const profileReused = profileCarriesTabsOver(args.projectPath, browser, args.profile);
4195
4221
  const spawnedAt = Date.now();
4196
4222
  const spawned = spawnExtensionCli(cliArgs, {
4197
4223
  projectDir: args.projectPath
@@ -4203,7 +4229,8 @@ async function start_handler(args) {
4203
4229
  pid,
4204
4230
  browser,
4205
4231
  projectPath: args.projectPath,
4206
- command
4232
+ command,
4233
+ profileReused
4207
4234
  });
4208
4235
  child.on("exit", ()=>{
4209
4236
  removeSession(args.projectPath, browser, pid);
@@ -5742,6 +5769,7 @@ async function publish(options = {}) {
5742
5769
  }
5743
5770
  const ARTIFACT_ID = /^gen_(?:[0-9a-f]{32}|[0-9a-f]{64})$/;
5744
5771
  const ARTIFACT_ID_CANDIDATE = /gen_[0-9a-f]+/;
5772
+ const ZIP_URL_REDIRECT_NOTE = "zipUrl does not serve the archive itself: it answers 302 with a short-lived presigned storage URL in Location. Follow redirects when you fetch it (curl -L; fetch and most HTTP clients already do), because a client that does not follow them reads 0 bytes and reports the share as empty when it is not.";
5745
5773
  function parseArtifactRef(input) {
5746
5774
  const raw = String(input ?? "").trim();
5747
5775
  if (!raw) return null;
@@ -6442,7 +6470,7 @@ async function buildShare(projectPath, distDir, manifest, browser, verifyInBrows
6442
6470
  browserLoadable: null
6443
6471
  },
6444
6472
  record,
6445
- note: "Anyone with this link can open the build you just made, running in the emulator. No install, no sign-in, no dev server. They can also download the whole build as a zip from zipUrl, so the link hands over the built code. It stays live until expiresAt; DELETE revokeUrl with the same token to kill it sooner, and a revoked link stays dead. revokeUrl is the handle that pulls this link early. Re-sharing an unchanged build returns this same link rather than a second one, and only a revoked link is replaced by a different one, so " + (record.recorded ? `it was also written to ${record.path} (record.path), which lists every share from this project.` : `keep it: ${record.note}`) + " To find this link again later, or to pull it back once it has left this conversation, run extension_shares: it lists every link this token has shared with its live or dead state, and revokes one by artifactId or by pasting any of its URLs." + (record.warning ? ` ${record.warning}` : "")
6473
+ note: "Anyone with this link can open the build you just made, running in the emulator. No install, no sign-in, no dev server. They can also download the whole build as a zip from zipUrl, so the link hands over the built code. It stays live until expiresAt; DELETE revokeUrl with the same token to kill it sooner, and a revoked link stays dead. revokeUrl is the handle that pulls this link early. Re-sharing an unchanged build returns this same link rather than a second one, and only a revoked link is replaced by a different one, so " + (record.recorded ? `it was also written to ${record.path} (record.path), which lists every share from this project.` : `keep it: ${record.note}`) + " To find this link again later, or to pull it back once it has left this conversation, run extension_shares: it lists every link this token has shared with its live or dead state, and revokes one by artifactId or by pasting any of its URLs." + (result.data.zipUrl ? ` ${ZIP_URL_REDIRECT_NOTE}` : "") + (record.warning ? ` ${record.warning}` : "")
6446
6474
  };
6447
6475
  }
6448
6476
  function detectSurfaces(manifest) {
@@ -7010,6 +7038,7 @@ async function listShares(args) {
7010
7038
  } : {},
7011
7039
  status: localOnlyStatus(entry, completeness, liveFiltered, now)
7012
7040
  }));
7041
+ const handsOutAZip = shares.some((share)=>Boolean(share.zipUrl)) || localOnly.some((entry)=>Boolean(entry.zipUrl));
7013
7042
  const liveCount = shares.filter((share)=>share.live).length;
7014
7043
  const ownership = {
7015
7044
  project: shares.filter((s)=>"project" === s.attribution.ownership).length,
@@ -7046,6 +7075,7 @@ async function listShares(args) {
7046
7075
  warnings: [
7047
7076
  "previewUrl and zipUrl are null for a share that is no longer live, because a revoked or expired link cannot resolve for anyone. revokeUrl stays on every row. Revocation is permanent: a revoked id is burned, and re-sharing that build mints a different link. Re-sharing a build that was NOT revoked returns its existing link instead.",
7048
7077
  "attribution.ownership says who the share belongs to and therefore who may revoke it: project means the owning workspace holds it and any member can pull it back, personal means one person holds it alone. attribution.credit names the publisher and is attribution only, granting and restricting nothing. A credit of \"CLI token ...\" means the platform could not resolve which human minted that token, and a credit of \"not recorded\" means it never knew; neither is a name, and neither should be reported as one.",
7078
+ handsOutAZip ? ZIP_URL_REDIRECT_NOTE : null,
7049
7079
  truncatedNote
7050
7080
  ]
7051
7081
  });
@@ -9252,6 +9282,10 @@ async function inspectViaBridge(args, browser, include, maxBytes) {
9252
9282
  if (jsLooking.length) probeWarning = `Probes are CSS selectors run through querySelectorAll against the live page, NOT JavaScript expressions. ${jsLooking.map((s)=>`"${s}"`).join(", ")} parsed as selectors and will match nothing. To evaluate JS, use extension_eval.`;
9253
9283
  }
9254
9284
  const urlFilter = args.url ?? ("string" == typeof value.meta?.url ? value.meta.url : void 0);
9285
+ if (!args.url && sessionProfileReused(args.projectPath, browser)) {
9286
+ result.profileReused = true;
9287
+ notes.push(restoredTabWarning(urlFilter ?? "the tab this read landed on"));
9288
+ }
9255
9289
  if (include.has("console")) await collectGeckoConsole(args, browser, urlFilter, result, notes);
9256
9290
  if (args.deepDom) {
9257
9291
  const cap = maxBytes > 0 ? maxBytes : 65536;
@@ -9351,9 +9385,13 @@ async function inspect_handler(args) {
9351
9385
  }
9352
9386
  toolchainWarning = isEngineCompanionUrl(documentUrl) || isEngineCompanionUrl(target.url) ? `Inspected ${target.url}, which is rendered by the Extension.js toolchain's own companion extension, not by this project. Pass url, or open one of the extension's surfaces first (extension_open), then inspect again.` : `No url was given and only override pages were open, so this inspected ${target.url}. Unless this extension provides that override itself, this is the toolchain's welcome surface, not your extension: pass url or open a surface with extension_open.`;
9353
9387
  }
9388
+ const profileReused = !args.url && sessionProfileReused(args.projectPath, browser);
9354
9389
  const result = {
9355
9390
  cdpPort,
9356
9391
  browser,
9392
+ ...profileReused ? {
9393
+ profileReused: true
9394
+ } : {},
9357
9395
  target: {
9358
9396
  id: target.id,
9359
9397
  url: target.url,
@@ -9415,7 +9453,8 @@ async function inspect_handler(args) {
9415
9453
  value: result,
9416
9454
  warnings: [
9417
9455
  "string" == typeof probeWarning ? probeWarning : null,
9418
- toolchainWarning
9456
+ toolchainWarning,
9457
+ profileReused ? restoredTabWarning(documentUrl || target.url) : null
9419
9458
  ]
9420
9459
  });
9421
9460
  } catch (err) {
@@ -11555,7 +11594,7 @@ function sightedContractBrowser(projectPath) {
11555
11594
  }
11556
11595
  const doctor_schema = {
11557
11596
  name: "extension_doctor",
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.",
11597
+ 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. A session started without allowControl comes back ok:true with status 'read-only', not as an error: its control channel is off by choice. 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.",
11559
11598
  inputSchema: {
11560
11599
  type: "object",
11561
11600
  properties: {
@@ -11643,7 +11682,14 @@ function projectEngineVersion(projectPath) {
11643
11682
  }
11644
11683
  }
11645
11684
  function capabilityProbeChecks(parsed) {
11646
- return isEnvelope(parsed) ? parsed.value?.checks : parsed;
11685
+ if (!isEnvelope(parsed)) return parsed;
11686
+ const value = parsed.value;
11687
+ if (Array.isArray(value)) return value;
11688
+ return value?.checks;
11689
+ }
11690
+ const CONTROL_OFF_BY_CHOICE = /\bwas not started with --allow-control\b/i;
11691
+ function controlOffByChoiceLeg(checks) {
11692
+ return checks.find((leg)=>"control-channel" === leg.check && "fail" === leg.status && "string" == typeof leg.detail && CONTROL_OFF_BY_CHOICE.test(leg.detail)) ?? null;
11647
11693
  }
11648
11694
  async function doctor_handler(args) {
11649
11695
  if (!args.projectPath) return environmentPreflight();
@@ -11674,8 +11720,10 @@ async function doctor_handler(args) {
11674
11720
  const out = stdout.trim();
11675
11721
  try {
11676
11722
  const parsed = JSON.parse(out);
11677
- const checks = capabilityProbeChecks(parsed);
11678
- if (!Array.isArray(checks)) throw new Error("not a check array");
11723
+ const probed = capabilityProbeChecks(parsed);
11724
+ if (!Array.isArray(probed)) throw new Error("not a check array");
11725
+ const checks = probed;
11726
+ const readOnlyLeg = controlOffByChoiceLeg(checks);
11679
11727
  for (const check of checks){
11680
11728
  if ("string" == typeof check.detail) check.detail = toMcpSpeak(check.detail);
11681
11729
  if ("string" == typeof check.remediation) check.remediation = toMcpSpeak(check.remediation);
@@ -11717,17 +11765,30 @@ async function doctor_handler(args) {
11717
11765
  } : {}
11718
11766
  });
11719
11767
  }
11768
+ const failures = checks.filter((leg)=>"fail" === leg.status);
11769
+ const readOnly = null !== readOnlyLeg && 1 === failures.length && failures[0] === readOnlyLeg;
11770
+ if (readOnly && readOnlyLeg) {
11771
+ readOnlyLeg.status = "warn";
11772
+ readOnlyLeg.detail = `read-only by choice: ${readOnlyLeg.detail}`;
11773
+ readOnlyLeg.remediation = "Nothing failed. To unlock the control verbs, call extension_dev again with allowControl: true (or allowEval: true) plus replace: true, which stops this session first; a plain second call is refused so the session does not fork.";
11774
+ }
11720
11775
  return envelope_envelope({
11721
- ok: healthy,
11776
+ ok: healthy || readOnly,
11722
11777
  command: doctor_schema.name,
11723
- status: healthy ? "healthy" : "unhealthy",
11778
+ status: healthy ? "healthy" : readOnly ? "read-only" : "unhealthy",
11724
11779
  value: {
11725
11780
  browser,
11726
11781
  ...engineVersion ? {
11727
11782
  engineVersion
11728
11783
  } : {},
11784
+ ...readOnly ? {
11785
+ readOnly: true
11786
+ } : {},
11729
11787
  checks
11730
- }
11788
+ },
11789
+ ...readOnly ? {
11790
+ hint: "This session is read-only because it was started without allowControl, not because anything is wrong: logs, inspect, wait and doctor all work, while storage, reload, open, dom_snapshot and eval stay locked."
11791
+ } : {}
11731
11792
  });
11732
11793
  } catch {
11733
11794
  const message = stderr.trim() || `extension exited with code ${code}`;
@@ -13,6 +13,7 @@ export interface ArtifactPublisher {
13
13
  project: string | null;
14
14
  tokenId: string | null;
15
15
  }
16
+ export declare const ZIP_URL_REDIRECT_NOTE = "zipUrl does not serve the archive itself: it answers 302 with a short-lived presigned storage URL in Location. Follow redirects when you fetch it (curl -L; fetch and most HTTP clients already do), because a client that does not follow them reads 0 bytes and reports the share as empty when it is not.";
16
17
  export interface ListedArtifact {
17
18
  artifactId: string;
18
19
  kind?: string;
@@ -0,0 +1,4 @@
1
+ export declare const SYSTEM_PROFILE_ARG = "false";
2
+ export declare function profileCarriesTabsOver(projectPath: string, browser: string, profileArg?: string): boolean;
3
+ export declare function sessionProfileReused(projectPath: string, browser: string): boolean;
4
+ export declare function restoredTabWarning(url: string): string;
@@ -71,5 +71,6 @@ export interface ProcessInfo {
71
71
  projectPath: string;
72
72
  command: "dev" | "start" | "preview";
73
73
  noBrowser?: boolean;
74
+ profileReused?: boolean;
74
75
  }
75
76
  export type BrowserType = "chrome" | "edge" | "firefox" | "chromium-based" | "gecko-based";
@@ -1,9 +1,6 @@
1
1
  <!DOCTYPE html><html lang="en"><head>
2
2
  <meta charset="utf-8">
3
3
  <meta name="viewport" content="width=device-width, initial-scale=1">
4
- <!-- Dark-only surface: signals the popup chrome up front so there is no
5
- white flash before styles.css loads. The brand tokens live in
6
- ./styles.css (synced with @extensiondev/ui), not inline here. -->
7
4
  <meta name="color-scheme" content="dark">
8
5
  <title>Extension.dev Live Preview</title>
9
6
  <link rel="stylesheet" href="/action/index.css"></head>
@@ -3,19 +3,8 @@
3
3
  <title>Extension preview host</title>
4
4
  </head>
5
5
  <body>
6
- <!--
7
- Sandbox execution surface for guest extension code.
8
- Declared in manifest.json under chromium:sandbox.pages with a CSP
9
- that allows unsafe-eval / blob: so guest code can run for real inside this
10
- installed extension. This file exists so the sandbox reference resolves and
11
- the build has a host page to mount; the full executor wiring (receiving a
12
- guest PreviewSession and running its surfaces here) is the next increment.
13
- Until then, guest surfaces still render through the creator-preview iframes.
14
- -->
15
6
  <div id="preview-host-root"></div>
16
7
  <script>
17
- // Signal the parent (the newtab runtime) that the sandbox host is mounted
18
- // and ready to receive a guest preview to execute.
19
8
  if (window.parent) {
20
9
  window.parent.postMessage(
21
10
  { type: "EXTENSION_PREVIEW_HOST_READY" },
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@extension.dev/mcp",
3
3
  "type": "module",
4
- "version": "10.3.0",
4
+ "version": "10.3.1",
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.3.0",
10
+ "version": "10.3.1",
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.3.0",
16
+ "version": "10.3.1",
17
17
  "runtimeHint": "npx",
18
18
  "transport": {
19
19
  "type": "stdio"