@extension.dev/mcp 10.10.6 → 10.10.8

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.10.6",
13
+ "version": "10.10.8",
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.10.6",
4
+ "version": "10.10.8",
5
5
  "author": {
6
6
  "name": "Cezar Augusto",
7
7
  "email": "hello@extension.dev",
package/CHANGELOG.md CHANGED
@@ -1,5 +1,58 @@
1
1
  # Changelog
2
2
 
3
+ ## 10.10.8
4
+
5
+ - On Firefox, `extension_eval` now reads an extension whose content
6
+ security policy forbids eval, which is every MV3 extension page
7
+ (BUGS_TO_FIX_MCP.md entry 20). Extension.js 4.1.31 evaluates such a
8
+ document over the debugger protocol, but the wrapper this server put
9
+ around a popup, options, sidebar or override-page expression called
10
+ eval itself, so those contexts still answered "blocked by CSP" on an
11
+ engine that could read them. A refused surface is now asked again with
12
+ the bare expression, which the engine takes over the protocol and
13
+ awaits there. A project pinned below 4.1.31 keeps the refusal, and its
14
+ hint now names that floor.
15
+ - `extension_eval` with `context: "page"` and a `moz-extension://` url
16
+ reaches a page the manifest declares as no surface (`pages/*`), through
17
+ the console actor of the tab showing it. It used to answer
18
+ `E_NO_SURFACE_DOCUMENT`. Open the page with `extension_open` first; a
19
+ page no tab shows answers `E_NO_MATCHING_TARGET`.
20
+ - `extension_dom_snapshot` on a `moz-extension://` url reads the page the
21
+ same way when Firefox refuses the injection with "Missing host
22
+ permission for the tab", and returns the engine's own snapshot fields.
23
+ - `extension_eval` with `context: "page"` on a site whose policy forbids
24
+ eval now works on an MV3 Firefox build, over the debugger protocol,
25
+ when `url` names the tab. An MV2 build keeps its `tabs.executeScript`
26
+ route, which had gone dead on Extension.js 4.1.31 because that engine
27
+ renamed the refusal to `E_CSP_BLOCKS_EVAL`; both spellings are read now.
28
+ - The protocol takes one expression. A statement list sent to a
29
+ policy-locked Firefox document answers `E_BAD_REQUEST` with the way to
30
+ wrap it, where the background used to answer a control-channel error
31
+ carrying "SyntaxError: expected expression".
32
+ - Still refused, by name: a string evaluated in the content-script world
33
+ of an extension whose policy forbids eval. Use `context: "page"` or
34
+ `extension_dom_snapshot` there.
35
+
36
+ - `extension_submit` and `extension_store_status` link to the console's
37
+ Submissions tab at `/<workspace>/<project>/submissions`, where the page
38
+ moved; the old `/stores` address still redirects. `@extension.dev/urls`
39
+ moves to `^0.8.2`, which carries the new paths.
40
+
41
+ ## 10.10.7
42
+
43
+ - A share revoke now waits for a person by default, like a real store
44
+ submission and a promotion to stable: the first `extension_shares`
45
+ revoke answers `approval-required` with a link, and the same call with
46
+ the returned `approvalId` runs it once. A revoke cannot be undone, so
47
+ it is the one outward call that should never run on an agent's word
48
+ alone. `EXTENSION_DEV_APPROVAL_GATE=0` still turns the gate off.
49
+ - New tool `extension_docs_search`: keyword search over the Extension.js
50
+ and extension.dev docs, returning up to 8 pages with a title, URL and
51
+ an excerpt of at most 400 characters. It calls
52
+ `/api/docs/search` on extension.dev, which ranks with no model call, so
53
+ it is free, needs no login and is in the `local` group. Its
54
+ description is kept short on purpose so it costs little context.
55
+
3
56
  ## 10.10.6
4
57
 
5
58
  - Tools that read a page or an extension (`extension_logs`,
package/README.md CHANGED
@@ -7,7 +7,7 @@
7
7
 
8
8
  # @extension.dev/mcp [![Version][npm-version-image]][npm-version-url] [![Downloads][npm-downloads-image]][npm-downloads-url] [![Discord][discord-image]][discord-url]
9
9
 
10
- > Give your AI agent hands for browser extension development. 31 MCP tools that scaffold, run, inspect, debug, and publish cross-browser extensions.
10
+ > Give your AI agent hands for browser extension development. 32 MCP tools that scaffold, run, inspect, debug, and publish cross-browser extensions.
11
11
 
12
12
  <img alt="Logo" align="right" src="https://media.extension.land/brand/extension-dev/logo-dock.png" width="20.7%" />
13
13
 
@@ -151,7 +151,7 @@ Two flags (or environment variables) narrow the server before an agent sees it:
151
151
 
152
152
  A refused call answers `E_TOOL_DISABLED` with the flag to change.
153
153
 
154
- A real store submission and a promotion to stable also wait for a person by default: the first call answers `approval-required` with a link on extension.dev, a workspace member approves exactly that action, and the same call with the returned `approvalId` runs it once. `EXTENSION_DEV_APPROVAL_GATE=1` extends this to every promotion and share revoke; `EXTENSION_DEV_APPROVAL_GATE=0` turns it off.
154
+ A real store submission, a promotion to stable and a share revoke also wait for a person by default: the first call answers `approval-required` with a link on extension.dev, a workspace member approves exactly that action, and the same call with the returned `approvalId` runs it once. `EXTENSION_DEV_APPROVAL_GATE=1` extends this to every promotion; `EXTENSION_DEV_APPROVAL_GATE=0` turns it off.
155
155
 
156
156
  Every tool also carries MCP annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`), so clients can auto-approve reads and ask before the rest.
157
157
 
@@ -196,6 +196,7 @@ cp node_modules/@extension.dev/mcp/claude/commands/*.md ~/my-extension/.claude/c
196
196
  | ---- | ---- | ----------- |
197
197
  | build | `extension_create` | Scaffold from a template |
198
198
  | build | `extension_templates` | Browse 50+ templates (`list`) and read one's source (`source`) |
199
+ | build | `extension_docs_search` | Search the Extension.js and extension.dev docs by keyword |
199
200
  | build | `extension_add_feature` | Add sidebar/popup/content script |
200
201
  | build | `extension_build` | Build for production |
201
202
  | run | `extension_dev` | Dev server with HMR |
package/dist/module.js CHANGED
@@ -9,7 +9,7 @@ import node_os from "node:os";
9
9
  import cross_spawn from "cross-spawn";
10
10
  import { PROD_ORIGINS, resolveOrigins } from "@extension.dev/urls/origins";
11
11
  import node_crypto, { createHash, randomUUID } from "node:crypto";
12
- import { consoleProjectPath, wwwNewPath } from "@extension.dev/urls/paths";
12
+ import { ConsoleProjectPage, consoleProjectPath, wwwNewPath } from "@extension.dev/urls/paths";
13
13
  import { UserlandProjectPage, userlandUrl } from "@extension.dev/urls/userland";
14
14
  import { CLOSE_BAD_HELLO, CLOSE_BAD_INSTANCE, CLOSE_CONTROL_UNAVAILABLE, CLOSE_SLOW_CONSUMER, CONTROL_ENVELOPE_VERSION, CONTROL_WS_PATH, actionsPath, browserArtifactsDir, buildSummaryPath, eventsPath, logsPath, matchesLogQuery, readLogEvents, readReadyContract, readyContractPath as bridge_readyContractPath, sessionArtifactsRootDir, sessionStateDir as bridge_sessionStateDir } from "extension-develop/bridge";
15
15
  import { fileURLToPath } from "node:url";
@@ -75,6 +75,12 @@ __webpack_require__.d(dev_namespaceObject, {
75
75
  handler: ()=>dev_handler,
76
76
  schema: ()=>dev_schema
77
77
  });
78
+ var docs_search_namespaceObject = {};
79
+ __webpack_require__.r(docs_search_namespaceObject);
80
+ __webpack_require__.d(docs_search_namespaceObject, {
81
+ handler: ()=>docs_search_handler,
82
+ schema: ()=>docs_search_schema
83
+ });
78
84
  var doctor_namespaceObject = {};
79
85
  __webpack_require__.r(doctor_namespaceObject);
80
86
  __webpack_require__.d(doctor_namespaceObject, {
@@ -86,6 +92,7 @@ var dom_snapshot_namespaceObject = {};
86
92
  __webpack_require__.r(dom_snapshot_namespaceObject);
87
93
  __webpack_require__.d(dom_snapshot_namespaceObject, {
88
94
  handler: ()=>dom_snapshot_handler,
95
+ protocolSnapshotExpression: ()=>protocolSnapshotExpression,
89
96
  schema: ()=>dom_snapshot_schema
90
97
  });
91
98
  var eval_namespaceObject = {};
@@ -236,7 +243,7 @@ __webpack_require__.d(workspace_create_namespaceObject, {
236
243
  handler: ()=>workspace_create_handler,
237
244
  schema: ()=>workspace_create_schema
238
245
  });
239
- var package_namespaceObject = JSON.parse('{"rE":"10.10.6","El":{"OP":"4.1.31"}}');
246
+ var package_namespaceObject = JSON.parse('{"rE":"10.10.8","El":{"OP":"4.1.31"}}');
240
247
  const ENVELOPE_SCHEMA = 1;
241
248
  const collectWarnings = (warnings)=>{
242
249
  if (!warnings) return [];
@@ -9930,7 +9937,10 @@ async function revokeShare(args) {
9930
9937
  api: args.api
9931
9938
  };
9932
9939
  if (token) {
9933
- const gate = await evaluateApproval(gateInput);
9940
+ const gate = await evaluateApproval({
9941
+ ...gateInput,
9942
+ enabled: approvalGateEnabled(true)
9943
+ });
9934
9944
  if (gate.blocked) return gate.envelope;
9935
9945
  }
9936
9946
  const result = await revokeArtifact({
@@ -13600,6 +13610,223 @@ async function rdpCollectConsoleMessages(port, options) {
13600
13610
  return messages;
13601
13611
  });
13602
13612
  }
13613
+ const RDP_EVAL_SLOT = "__extensionDevRdpEval";
13614
+ const RDP_FRAME_WAIT_MS = 5000;
13615
+ const RDP_FRAME_POLL_MS = 50;
13616
+ const RDP_RESULT_POLL_MS = 100;
13617
+ function rdpStartExpression(expression, token) {
13618
+ const key = JSON.stringify(token);
13619
+ return `(function () {
13620
+ var store = globalThis.${RDP_EVAL_SLOT} = globalThis.${RDP_EVAL_SLOT} || {};
13621
+ store[${key}] = { state: "pending" };
13622
+ function settle(next) { store[${key}] = next; }
13623
+ function encode(value) {
13624
+ if (value === undefined) return { state: "value" };
13625
+ var json;
13626
+ try { json = JSON.stringify(value); } catch (error) { json = undefined; }
13627
+ return { state: "value", json: json === undefined ? JSON.stringify(String(value)) : json };
13628
+ }
13629
+ function thrown(error) {
13630
+ return { state: "throw", name: (error && error.name) || "EvalError", message: (error && error.message) || String(error) };
13631
+ }
13632
+ try {
13633
+ Promise.resolve((${expression}
13634
+ )).then(function (value) { settle(encode(value)); }, function (error) { settle(thrown(error)); });
13635
+ } catch (error) {
13636
+ settle(thrown(error));
13637
+ }
13638
+ return "started";
13639
+ })()`;
13640
+ }
13641
+ function rdpPollExpression(token) {
13642
+ const key = JSON.stringify(token);
13643
+ return `(function () {
13644
+ var store = globalThis.${RDP_EVAL_SLOT};
13645
+ var entry = (store && store[${key}]) || null;
13646
+ if (entry && entry.state !== "pending") {
13647
+ delete store[${key}];
13648
+ if (Object.keys(store).length === 0) { try { delete globalThis.${RDP_EVAL_SLOT}; } catch (error) {} }
13649
+ }
13650
+ var encoded = JSON.stringify(entry);
13651
+ return encoded;
13652
+ })()`;
13653
+ }
13654
+ function readRdpEvalSlot(raw) {
13655
+ if ("string" != typeof raw) return "lost";
13656
+ let slot;
13657
+ try {
13658
+ slot = JSON.parse(raw);
13659
+ } catch {
13660
+ return "lost";
13661
+ }
13662
+ if (!slot || "object" != typeof slot) return "lost";
13663
+ if ("pending" === slot.state) return "pending";
13664
+ if ("throw" === slot.state) return {
13665
+ ok: false,
13666
+ name: "string" == typeof slot.name && slot.name ? slot.name : "EvalError",
13667
+ message: "string" == typeof slot.message && slot.message ? slot.message : "the expression threw inside the document"
13668
+ };
13669
+ if ("value" !== slot.state) return "lost";
13670
+ if ("string" != typeof slot.json) return {
13671
+ ok: true,
13672
+ value: null
13673
+ };
13674
+ try {
13675
+ return {
13676
+ ok: true,
13677
+ value: JSON.parse(slot.json)
13678
+ };
13679
+ } catch {
13680
+ return {
13681
+ ok: true,
13682
+ value: slot.json
13683
+ };
13684
+ }
13685
+ }
13686
+ function exceptionText(packet) {
13687
+ const message = packet.exceptionMessage;
13688
+ if ("string" == typeof message) return message;
13689
+ const initial = message?.initial;
13690
+ if ("string" == typeof initial) return initial;
13691
+ return true === packet.hasException || void 0 !== packet.exception && null !== packet.exception ? "the expression threw inside the document" : "";
13692
+ }
13693
+ async function readStringResult(session, result, timeoutMs) {
13694
+ if (!result || "object" != typeof result) return result;
13695
+ const grip = result;
13696
+ if ("longString" !== grip.type || "string" != typeof grip.actor) return result;
13697
+ const length = "number" == typeof grip.length ? grip.length : 0;
13698
+ const reply = await session.request(grip.actor, {
13699
+ type: "substring",
13700
+ start: 0,
13701
+ end: length
13702
+ }, timeoutMs);
13703
+ return "string" == typeof reply.substring ? reply.substring : grip.initial;
13704
+ }
13705
+ async function consoleEvaluate(session, consoleActor, text, timeoutMs) {
13706
+ const results = [];
13707
+ let wake = null;
13708
+ const untap = session.tap((packet)=>{
13709
+ if (packet.from !== consoleActor || "evaluationResult" !== packet.type) return;
13710
+ results.push(packet);
13711
+ wake?.();
13712
+ });
13713
+ try {
13714
+ const reply = await session.request(consoleActor, {
13715
+ type: "evaluateJSAsync",
13716
+ text
13717
+ }, timeoutMs);
13718
+ const resultId = String(reply.resultID ?? "");
13719
+ const deadline = Date.now() + timeoutMs;
13720
+ for(;;){
13721
+ const hit = results.find((p)=>String(p.resultID ?? "") === resultId);
13722
+ if (hit) return {
13723
+ result: await readStringResult(session, hit.result, timeoutMs),
13724
+ threw: exceptionText(hit)
13725
+ };
13726
+ const left = deadline - Date.now();
13727
+ if (left <= 0) throw new Error(`Firefox sent no evaluation result within ${timeoutMs}ms`);
13728
+ await new Promise((resolve)=>{
13729
+ const timer = setTimeout(resolve, Math.min(left, 250));
13730
+ wake = ()=>{
13731
+ clearTimeout(timer);
13732
+ resolve();
13733
+ };
13734
+ });
13735
+ wake = null;
13736
+ }
13737
+ } finally{
13738
+ untap();
13739
+ }
13740
+ }
13741
+ function topFrameConsoleActor(frames) {
13742
+ const usable = frames.filter((frame)=>"string" == typeof frame.consoleActor);
13743
+ const top = usable.find((frame)=>true === frame.isTopLevelTarget) ?? usable[0];
13744
+ return top ? String(top.consoleActor) : void 0;
13745
+ }
13746
+ async function rdpEvaluateInTab(port, options) {
13747
+ const timeoutMs = options.timeoutMs ?? 10000;
13748
+ return withSession(port, timeoutMs, async (session)=>{
13749
+ const listed = await session.request("root", {
13750
+ type: "listTabs"
13751
+ }, timeoutMs);
13752
+ const tab = options.select(listed.tabs ?? []);
13753
+ if (!tab?.actor) return {
13754
+ ok: false,
13755
+ name: "TargetNotFound",
13756
+ message: "no open tab matches"
13757
+ };
13758
+ const watcher = await session.request(String(tab.actor), {
13759
+ type: "getWatcher",
13760
+ isServerTargetSwitchingEnabled: true
13761
+ }, timeoutMs);
13762
+ const watcherActor = String(watcher.actor ?? "");
13763
+ if (!watcherActor) return {
13764
+ ok: false,
13765
+ name: "Unsupported",
13766
+ message: "this Firefox build exposes no watcher actor for a tab, so the tab cannot be evaluated over the debugger protocol"
13767
+ };
13768
+ const frames = [];
13769
+ const untap = session.tap((packet)=>{
13770
+ if ("target-available-form" !== packet.type) return;
13771
+ const target = packet.target;
13772
+ if (target && "object" == typeof target) frames.push(target);
13773
+ });
13774
+ try {
13775
+ await session.request(watcherActor, {
13776
+ type: "watchTargets",
13777
+ targetType: "frame"
13778
+ }, timeoutMs);
13779
+ const frameDeadline = Date.now() + Math.min(timeoutMs, RDP_FRAME_WAIT_MS);
13780
+ let consoleActor = topFrameConsoleActor(frames);
13781
+ while(!consoleActor && Date.now() < frameDeadline){
13782
+ await new Promise((resolve)=>setTimeout(resolve, RDP_FRAME_POLL_MS));
13783
+ consoleActor = topFrameConsoleActor(frames);
13784
+ }
13785
+ if (!consoleActor) return {
13786
+ ok: false,
13787
+ name: "TargetNotFound",
13788
+ message: "the tab announced no document to evaluate in (it may still be loading)"
13789
+ };
13790
+ const token = `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
13791
+ const started = await consoleEvaluate(session, consoleActor, rdpStartExpression(options.expression, token), timeoutMs);
13792
+ if (started.threw) return {
13793
+ ok: false,
13794
+ name: "EvalError",
13795
+ message: started.threw
13796
+ };
13797
+ const resultDeadline = Date.now() + timeoutMs;
13798
+ for(;;){
13799
+ const polled = await consoleEvaluate(session, consoleActor, rdpPollExpression(token), timeoutMs);
13800
+ const settled = readRdpEvalSlot(polled.result);
13801
+ if ("lost" === settled) return {
13802
+ ok: false,
13803
+ name: "EvalLost",
13804
+ message: "the document reloaded or navigated before the expression settled, so its result is gone"
13805
+ };
13806
+ if ("pending" !== settled) return settled.ok ? {
13807
+ ok: true,
13808
+ value: settled.value,
13809
+ tab: {
13810
+ url: String(tab.url ?? ""),
13811
+ title: String(tab.title ?? "")
13812
+ }
13813
+ } : settled;
13814
+ if (Date.now() >= resultDeadline) return {
13815
+ ok: false,
13816
+ name: "Timeout",
13817
+ message: `the expression did not settle within ${timeoutMs}ms`
13818
+ };
13819
+ await new Promise((resolve)=>setTimeout(resolve, RDP_RESULT_POLL_MS));
13820
+ }
13821
+ } finally{
13822
+ untap();
13823
+ session.request(watcherActor, {
13824
+ type: "unwatchTargets",
13825
+ targetType: "frame"
13826
+ }, 1000).catch(()=>{});
13827
+ }
13828
+ });
13829
+ }
13603
13830
  const inspect_schema_schema = {
13604
13831
  name: "extension_inspect",
13605
13832
  description: "Inspect a running extension deeply over the browser's debugger protocol: full HTML including shadow DOM, DOM structure, content-script injection, console messages, and CSS selector queries through `probe`. This is the ONLY tool that pierces closed shadow roots (deepDom), runs selector probes, and navigates a tab to `url` before reading it. It reads a web or override page and picks the first inspectable target, or the first whose url contains `url`; it cannot address an extension surface by name and takes no chrome.tabs id. Use extension_dom_snapshot to choose which tab or which open surface (popup, options, sidebar, devtools) to read, or to enumerate what is open. Use extension_analyze for a built extension's files and sizes on disk. Chromium rides the Chrome DevTools Protocol and needs the session's debug port, not allowControl. Firefox is fully paired: summary, meta, html, dom_snapshot, extension_roots and probes ride the agent bridge and need allowEval:true, console rides the RDP watcher replay on engine 4.0.15 and later, and deepDom needs an MV2 session with host permissions for the target url, because the Firefox MV3 background CSP blocks bridge evals. This requires an active dev or start session.",
@@ -14434,7 +14661,7 @@ function readRelayFrame(value) {
14434
14661
  }
14435
14662
  const eval_schema = {
14436
14663
  name: "extension_eval",
14437
- description: "Evaluate an expression in a running extension context. Start the session with allowEval:true (extension_dev), which writes a 0600 session token. Context defaults to 'background', except on a Chromium MV3 session (the default template) where it defaults to 'page', the active tab, because the MV3 service worker CSP blocks eval; pass context:'background' to target the worker anyway and get that explanation back. For content and page, pass `url` to pick the tab, or omit both `url` and `tab` for the active tab; a numeric `tab` only disambiguates. Extension surfaces (popup, options, sidebar, devtools) and override pages (newtab, history, bookmarks) need no tab id but must already be open: open one with extension_open first, because a closed one returns an explicit error. On a Chromium MV3 session those pages, and context:'page' with a chrome-extension:// url, evaluate over CDP, the inspector path the extension page CSP does not govern; elsewhere they evaluate over the in-bundle relay. Call extension_dom_snapshot with listTabs:true to enumerate {tabId, url, title}.",
14664
+ description: "Evaluate an expression in a running extension context. Start the session with allowEval:true (extension_dev), which writes a 0600 session token. Context defaults to 'background', except on a Chromium MV3 session (the default template) where it defaults to 'page', the active tab, because the MV3 service worker CSP blocks eval; pass context:'background' to target the worker anyway and get that explanation back. For content and page, pass `url` to pick the tab, or omit both `url` and `tab` for the active tab; a numeric `tab` only disambiguates. Extension surfaces (popup, options, sidebar, devtools) and override pages (newtab, history, bookmarks) need no tab id but must already be open: open one with extension_open first, because a closed one returns an explicit error. On a Chromium MV3 session those pages, and context:'page' with a chrome-extension:// url, evaluate over CDP, the inspector path the extension page CSP does not govern; elsewhere they evaluate over the in-bundle relay. On Firefox a document whose content security policy forbids eval (the extension's own pages, or a site's) is evaluated over the debugger protocol instead, which takes one expression; a page inside the extension that is no declared surface (pages/*) is reached the same way by context:'page' and its moz-extension:// url once a tab shows it. Call extension_dom_snapshot with listTabs:true to enumerate {tabId, url, title}.",
14438
14665
  inputSchema: {
14439
14666
  type: "object",
14440
14667
  properties: {
@@ -14755,6 +14982,7 @@ async function evaluateThroughRelay(args, browser, context) {
14755
14982
  ], args.projectPath, args.timeout, eval_schema.name);
14756
14983
  let raw = await run(relaySafeExpression(args.expression, token));
14757
14984
  let parsed = tryParseFrame(raw);
14985
+ if (isGeckoFamily(browser) && cspRefusedFrame(parsed)) return evaluatePastSurfaceCsp(args, context, run);
14758
14986
  if (!parsed || true !== parsed.ok) return raw;
14759
14987
  let frame = readRelayFrame(parsed.value);
14760
14988
  if (!frame) return raw;
@@ -14801,21 +15029,28 @@ async function evaluateThroughRelay(args, browser, context) {
14801
15029
  return actFrameJson(parsed);
14802
15030
  }
14803
15031
  const CSP_EVAL_REFUSAL = /blocked by CSP|call to eval|unsafe-eval|Content Security Policy/i;
15032
+ function cspRefusedFrame(parsed) {
15033
+ if (!parsed || false !== parsed.ok) return false;
15034
+ const code = "string" == typeof parsed.error?.code ? parsed.error.code : "";
15035
+ if ("E_CSP_BLOCKS_EVAL" === code) return true;
15036
+ return "E_EVAL" === code && CSP_EVAL_REFUSAL.test(String(parsed.error?.message ?? ""));
15037
+ }
15038
+ function cspRefusalHint(context) {
15039
+ if ("page" === context) return "This page's own Content-Security-Policy forbids eval, so the in-page executor cannot run any expression in it; this is the site's policy, not a fault in the expression. Pass url so the tab is evaluated through the browser's debugger, which the policy does not govern (CDP on Chromium, the debugger protocol on Firefox), or read the page with extension_dom_snapshot or extension_inspect.";
15040
+ if ("content" === context) return "The extension's content_security_policy forbids eval in its content-script world on this engine, so no string runs there; this is the extension's policy, not a fault in the expression. Evaluate the page itself with context: \"page\" and a url, or read the DOM the content script sees with extension_dom_snapshot or extension_inspect.";
15041
+ return `The extension's content_security_policy (or the MV3 default) forbids eval in its own ${context ?? "background"} context, so the in-page executor cannot run any expression there; this is the extension's policy, not a fault in the expression. Read the page instead with extension_dom_snapshot or extension_inspect, evaluate a web page with context: \"page\" and a url, or read the extension's console with extension_logs. On a Chromium MV3 session the extension's own pages evaluate over CDP, which the page policy does not govern. On Firefox, Extension.js 4.1.31 and later evaluate them over the debugger protocol instead: if this session runs an older engine, upgrade the project's extension dependency and restart it.`;
15042
+ }
14804
15043
  function explainCspRefusal(raw, context) {
14805
15044
  const parsed = tryParseFrame(raw);
14806
- if (!parsed || false !== parsed.ok) return null;
14807
- const code = "string" == typeof parsed.error?.code ? parsed.error.code : "";
14808
- const message = String(parsed.error?.message ?? "");
14809
- if ("E_EVAL" !== code || !CSP_EVAL_REFUSAL.test(message)) return null;
15045
+ if (!parsed || !cspRefusedFrame(parsed)) return null;
14810
15046
  parsed.error.name = "CspBlocksEval";
14811
- parsed.hint = `The extension's content_security_policy (or the MV3 default) forbids eval in its own ${context ?? "background"} context, so the in-page executor cannot run any expression there; this is the extension's policy, not a fault in the expression. Read the page instead with extension_dom_snapshot or extension_inspect, evaluate a web page with context: \"page\" and a url, or read the extension's console with extension_logs. On a Chromium MV3 session the extension's own pages evaluate over CDP, which the page policy does not govern.`;
15047
+ parsed.hint = cspRefusalHint(context);
14812
15048
  if ("string" == typeof parsed.error.hint) delete parsed.error.hint;
14813
15049
  return actFrameJson(parsed);
14814
15050
  }
14815
15051
  const NO_SCRIPTING_API = /scripting is not available/i;
14816
15052
  function pageEvalRefusedByCsp(parsed) {
14817
- const code = "string" == typeof parsed.error?.code ? parsed.error.code : "";
14818
- return "E_EVAL" === code && CSP_EVAL_REFUSAL.test(String(parsed.error?.message ?? ""));
15053
+ return cspRefusedFrame(parsed);
14819
15054
  }
14820
15055
  function isSingleExpression(source) {
14821
15056
  try {
@@ -14825,6 +15060,110 @@ function isSingleExpression(source) {
14825
15060
  return false;
14826
15061
  }
14827
15062
  }
15063
+ function notOneExpression() {
15064
+ return envelope_envelope({
15065
+ ok: false,
15066
+ command: eval_schema.name,
15067
+ status: "bad-request",
15068
+ error: {
15069
+ code: "E_BAD_REQUEST",
15070
+ name: "NotOneExpression",
15071
+ message: "This document's content security policy forbids eval, so Firefox evaluates it over the debugger protocol, which takes one expression, and this input is a statement list."
15072
+ },
15073
+ hint: "Pass one expression. Wrap statements in a function that returns the value, for example (() => { const a = 20; return a + 1; })()."
15074
+ });
15075
+ }
15076
+ async function evaluatePastSurfaceCsp(args, context, run) {
15077
+ if (!isSingleExpression(args.expression)) return notOneExpression();
15078
+ const direct = await run(args.expression);
15079
+ const frame = tryParseFrame(direct);
15080
+ if (!frame) return direct;
15081
+ if (cspRefusedFrame(frame)) return explainCspRefusal(direct, context) ?? direct;
15082
+ if (true === frame.ok && void 0 === frame.value) {
15083
+ frame.value = null;
15084
+ return actFrameJson(frame);
15085
+ }
15086
+ return direct;
15087
+ }
15088
+ const sameDocumentUrl = (a, b)=>String(a ?? "").replace(/[?#].*$/, "") === b.replace(/[?#].*$/, "");
15089
+ function tabByUrl(tabs, url) {
15090
+ const exact = tabs.filter((tab)=>sameDocumentUrl(tab.url, url));
15091
+ const covered = exact.length ? exact : tabs.filter((tab)=>matchPatternCovers(url, String(tab.url ?? "")));
15092
+ const candidates = covered.length ? covered : tabs.filter((tab)=>String(tab.url ?? "").includes(url));
15093
+ return candidates.find((tab)=>true === tab.selected) ?? candidates[0];
15094
+ }
15095
+ async function evaluateInGeckoTab(args, browser, select, missing) {
15096
+ const resolved = await resolveRdpPort(args.projectPath, browser, {
15097
+ waitMs: 3000,
15098
+ graceMs: 1000
15099
+ });
15100
+ if (!resolved) return null;
15101
+ let outcome;
15102
+ try {
15103
+ outcome = await rdpEvaluateInTab(resolved.port, {
15104
+ select,
15105
+ expression: args.expression,
15106
+ timeoutMs: args.timeout ?? 10000
15107
+ });
15108
+ } catch {
15109
+ return null;
15110
+ }
15111
+ if (outcome.ok) return envelope_envelope({
15112
+ ok: true,
15113
+ command: eval_schema.name,
15114
+ status: "ok",
15115
+ value: void 0 === outcome.value ? null : outcome.value
15116
+ });
15117
+ if ("TargetNotFound" === outcome.name) return envelope_envelope({
15118
+ ok: false,
15119
+ command: eval_schema.name,
15120
+ status: "no-matching-target",
15121
+ error: {
15122
+ code: "E_NO_MATCHING_TARGET",
15123
+ name: "NoMatchingTarget",
15124
+ message: missing
15125
+ },
15126
+ hint: "Open the page first with extension_open and its url, then retry; extension_dom_snapshot with listTabs: true lists what is open."
15127
+ });
15128
+ if ("Timeout" === outcome.name) return envelope_envelope({
15129
+ ok: false,
15130
+ command: eval_schema.name,
15131
+ status: "eval-pending",
15132
+ error: {
15133
+ code: "E_WAIT_TIMEOUT",
15134
+ name: "EvalPending",
15135
+ message: outcome.message
15136
+ },
15137
+ hint: "The expression returned a promise that is still pending; pass a larger timeout to wait for it."
15138
+ });
15139
+ return envelope_envelope({
15140
+ ok: false,
15141
+ command: eval_schema.name,
15142
+ status: "eval-failed",
15143
+ error: {
15144
+ code: "E_EVAL",
15145
+ name: outcome.name,
15146
+ message: outcome.message
15147
+ },
15148
+ hint: "The expression threw inside the document. It ran over the debugger protocol, which the document's content security policy does not govern, so this is the expression's own error."
15149
+ });
15150
+ }
15151
+ function backgroundRouteRefusedStatements(args, browser, context, raw) {
15152
+ if (!isGeckoFamily(browser) || (context ?? "background") !== "background") return false;
15153
+ const failed = tryParseFrame(raw);
15154
+ if (!failed || false !== failed.ok) return false;
15155
+ if (failed.error?.name !== "Unavailable") return false;
15156
+ if (!/SyntaxError/.test(String(failed.error?.message ?? ""))) return false;
15157
+ return !isSingleExpression(args.expression);
15158
+ }
15159
+ async function evaluatePagePastSiteCsp(args, browser, context, raw) {
15160
+ if ("page" !== context || !isGeckoFamily(browser)) return null;
15161
+ if (!cspRefusedFrame(tryParseFrame(raw))) return null;
15162
+ if (!args.url && null != args.tab) return null;
15163
+ if (!isSingleExpression(args.expression)) return notOneExpression();
15164
+ const url = args.url;
15165
+ return evaluateInGeckoTab(args, browser, url ? (tabs)=>tabByUrl(tabs, url) : (tabs)=>tabs.find((tab)=>true === tab.selected), url ? `No open tab matches url: ${url}` : "No tab is selected in the dev browser.");
15166
+ }
14828
15167
  async function evaluateThroughExecuteScript(args, browser, context, raw) {
14829
15168
  if ("page" !== context && "content" !== context) return null;
14830
15169
  if (isChromiumFamily(browser)) return null;
@@ -14919,6 +15258,12 @@ async function eval_handler(args) {
14919
15258
  }
14920
15259
  return raw;
14921
15260
  }
15261
+ if (isGeckoFamily(browser)) {
15262
+ if (!isSingleExpression(args.expression)) return notOneExpression();
15263
+ const url = args.url;
15264
+ const overProtocol = await evaluateInGeckoTab(args, browser, (tabs)=>tabs.find((tab)=>sameDocumentUrl(tab.url, url)), `No open tab shows ${url}.`);
15265
+ if (null !== overProtocol) return overProtocol;
15266
+ }
14922
15267
  const declared = declaredSurfaces(args.projectPath, browser) ?? [];
14923
15268
  return envelope_envelope({
14924
15269
  ok: false,
@@ -14946,6 +15291,9 @@ async function eval_handler(args) {
14946
15291
  ], args.projectPath, args.timeout, eval_schema.name);
14947
15292
  const mv2Fallback = await evaluateThroughExecuteScript(args, browser, context, raw);
14948
15293
  if (null !== mv2Fallback) return mv2Fallback;
15294
+ const pastSiteCsp = await evaluatePagePastSiteCsp(args, browser, context, raw);
15295
+ if (null !== pastSiteCsp) return pastSiteCsp;
15296
+ if (backgroundRouteRefusedStatements(args, browser, context, raw)) return notOneExpression();
14949
15297
  const cspRefusal = explainCspRefusal(raw, context);
14950
15298
  if (null !== cspRefusal) return cspRefusal;
14951
15299
  const trustedTypes = tryParseFrame(raw);
@@ -15182,6 +15530,72 @@ const dom_snapshot_schema = {
15182
15530
  ]
15183
15531
  }
15184
15532
  };
15533
+ const GECKO_EXTENSION_URL = /^moz-extension:\/\//;
15534
+ const dom_snapshot_sameDocumentUrl = (a, b)=>String(a ?? "").replace(/[?#].*$/, "") === b.replace(/[?#].*$/, "");
15535
+ function protocolSnapshotExpression(wantHtml, maxBytes) {
15536
+ return `(function () {
15537
+ var doc = document;
15538
+ function countShadow(root) {
15539
+ var n = 0, els = root.querySelectorAll("*");
15540
+ for (var i = 0; i < els.length; i++) { if (els[i].shadowRoot) { n++; n += countShadow(els[i].shadowRoot); } }
15541
+ return n;
15542
+ }
15543
+ var html = doc.documentElement.outerHTML;
15544
+ var snap = {
15545
+ context: "page",
15546
+ url: String(location.href),
15547
+ title: doc.title,
15548
+ summary: {
15549
+ htmlLength: html.length,
15550
+ scriptCount: doc.querySelectorAll("script").length,
15551
+ styleCount: doc.querySelectorAll("style,link[rel=stylesheet]").length,
15552
+ extensionRootCount: doc.querySelectorAll("#extension-root,[data-extension-root]").length,
15553
+ openShadowRoots: countShadow(doc),
15554
+ bodyChildCount: doc.body ? doc.body.children.length : 0
15555
+ }
15556
+ };
15557
+ if (${wantHtml ? "true" : "false"}) {
15558
+ var cap = ${Math.max(0, Math.floor(maxBytes))};
15559
+ if (cap > 0 && html.length > cap) { snap.html = html.slice(0, cap); snap.htmlTruncated = true; }
15560
+ else { snap.html = html; }
15561
+ }
15562
+ return snap;
15563
+ })()`;
15564
+ }
15565
+ async function snapshotExtensionPageOverProtocol(args, browser, url, raw, consoleAsked) {
15566
+ if (!url || !isGeckoFamily(browser) || !GECKO_EXTENSION_URL.test(url)) return null;
15567
+ try {
15568
+ const refused = JSON.parse(raw);
15569
+ if (!refused || false !== refused.ok) return null;
15570
+ } catch {
15571
+ return null;
15572
+ }
15573
+ const resolved = await resolveRdpPort(args.projectPath, browser, {
15574
+ waitMs: 3000,
15575
+ graceMs: 1000
15576
+ });
15577
+ if (!resolved) return null;
15578
+ const wantHtml = args.include?.includes("html") === true;
15579
+ try {
15580
+ const outcome = await rdpEvaluateInTab(resolved.port, {
15581
+ select: (tabs)=>tabs.find((tab)=>dom_snapshot_sameDocumentUrl(tab.url, url)),
15582
+ expression: protocolSnapshotExpression(wantHtml, args.maxBytes ?? 262144),
15583
+ timeoutMs: args.timeout ?? 10000
15584
+ });
15585
+ if (!outcome.ok) return null;
15586
+ return envelope_envelope({
15587
+ ok: true,
15588
+ command: dom_snapshot_schema.name,
15589
+ status: "ok",
15590
+ value: outcome.value,
15591
+ warnings: [
15592
+ `${url} is a page inside the extension, which Firefox lets no script be injected into, so this snapshot was read over the debugger protocol from the tab showing it.` + (consoleAsked ? " Console lines are not collected on this path; read them with extension_logs." : "")
15593
+ ]
15594
+ });
15595
+ } catch {
15596
+ return null;
15597
+ }
15598
+ }
15185
15599
  async function cdpPortOrError(projectPath, browser, feature) {
15186
15600
  if (!isChromiumFamily(browser)) return {
15187
15601
  error: envelope_envelope({
@@ -15424,7 +15838,10 @@ async function dom_snapshot_handler(args) {
15424
15838
  if (null != withConsole) cli.push("--with-console", String(withConsole));
15425
15839
  cli.push("--browser", resolveSessionBrowser(args.projectPath, args.browser).browser);
15426
15840
  if (null != args.timeout) cli.push("--timeout", String(args.timeout));
15427
- const raw = await runActVerb(cli, args.projectPath, args.timeout, dom_snapshot_schema.name);
15841
+ const sessionBrowser = resolveSessionBrowser(args.projectPath, args.browser).browser;
15842
+ const asked = await runActVerb(cli, args.projectPath, args.timeout, dom_snapshot_schema.name);
15843
+ const overProtocol = await snapshotExtensionPageOverProtocol(args, sessionBrowser, targetUrl ?? ("string" == typeof resolvedTarget?.url ? resolvedTarget.url : void 0), asked, null != withConsole);
15844
+ const raw = overProtocol ?? asked;
15428
15845
  if (!resolvedTarget) return raw;
15429
15846
  try {
15430
15847
  const parsed = JSON.parse(raw);
@@ -15996,7 +16413,7 @@ async function readStores(args) {
15996
16413
  const healthUrl = registryFileUrl(ref, "stores/health.json");
15997
16414
  const statusUrl = registryFileUrl(ref, "stores/status.json");
15998
16415
  const submissionsUrl = registryFileUrl(ref, "stores/submissions.json");
15999
- const consoleStoresUrl = consoleProjectUrl(ref, "stores", args.api);
16416
+ const consoleStoresUrl = consoleProjectUrl(ref, ConsoleProjectPage.submissions, args.api);
16000
16417
  const [healthRes, statusRes, submissionsRes] = await Promise.all([
16001
16418
  fetchRegistryJson(healthUrl, fetch, {
16002
16419
  ref,
@@ -16026,7 +16443,7 @@ async function readStores(args) {
16026
16443
  project: ref.project
16027
16444
  }
16028
16445
  });
16029
- if (!healthRes.ok && !statusRes.ok && !submissionsRes.ok) return store_status_fail("StoreStatusNotFound", `No store data on the registry for ${ref.workspace}/${ref.project} (${healthRes.message}). The project may have no stores configured yet, be private (private registry data needs a share token), or the workspace/project slugs may be wrong. Configure stores at ${consoleStoresUrl}/new; the console Stores page is the authoritative view: ${consoleStoresUrl}`, "unavailable", "E_PLATFORM", {
16446
+ if (!healthRes.ok && !statusRes.ok && !submissionsRes.ok) return store_status_fail("StoreStatusNotFound", `No store data on the registry for ${ref.workspace}/${ref.project} (${healthRes.message}). The project may have no stores configured yet, be private (private registry data needs a share token), or the workspace/project slugs may be wrong. Configure stores at ${consoleStoresUrl}/new; the console Submissions page is the authoritative view: ${consoleStoresUrl}`, "unavailable", "E_PLATFORM", {
16030
16447
  workspace: ref.workspace,
16031
16448
  project: ref.project,
16032
16449
  registryUrls: {
@@ -16438,7 +16855,7 @@ async function submit_handler(args) {
16438
16855
  let statusNote = null;
16439
16856
  if (dryRun) {
16440
16857
  const ref = resolveProjectRef();
16441
- const consoleStoresUrl = consoleProjectUrl(ref, "stores", args.api);
16858
+ const consoleStoresUrl = consoleProjectUrl(ref, ConsoleProjectPage.submissions, args.api);
16442
16859
  const storeModeNote = `Store publish mode (draft / skip-publish / live) is not readable with the CLI token, so it cannot be verified from here; check per-store settings at ${consoleStoresUrl}.`;
16443
16860
  let health = null;
16444
16861
  let healthUnreadable = null;
@@ -17939,6 +18356,95 @@ async function browsers_handler(args) {
17939
18356
  });
17940
18357
  return detectBrowsers(args.browsers);
17941
18358
  }
18359
+ const docs_search_COMMAND = "extension_docs_search";
18360
+ const docs_search_schema = {
18361
+ name: docs_search_COMMAND,
18362
+ description: "Find pages in the Extension.js and extension.dev docs by keyword, each with a short excerpt. Use it before answering from memory on anything version-specific: a CLI flag, a manifest field across browsers, a store submission rule. Free and needs no login.",
18363
+ inputSchema: {
18364
+ type: "object",
18365
+ properties: {
18366
+ query: {
18367
+ type: "string",
18368
+ description: "What to look up, in a few words."
18369
+ },
18370
+ limit: {
18371
+ type: "number",
18372
+ minimum: 1,
18373
+ maximum: 8,
18374
+ default: 5,
18375
+ description: "How many pages to return, 1 to 8."
18376
+ },
18377
+ api: API_BASE
18378
+ },
18379
+ required: [
18380
+ "query"
18381
+ ]
18382
+ }
18383
+ };
18384
+ async function docs_search_handler(args) {
18385
+ const query = String(args.query || "").trim();
18386
+ if (!query) return envelope_envelope({
18387
+ ok: false,
18388
+ command: docs_search_COMMAND,
18389
+ status: "bad-request",
18390
+ error: {
18391
+ code: "E_BAD_REQUEST",
18392
+ message: "query is required."
18393
+ }
18394
+ });
18395
+ const base = mcpOrigins(args.api).www.replace(/\/+$/, "");
18396
+ const limit = Math.min(8, Math.max(1, Math.floor(Number(args.limit) || 5)));
18397
+ const url = `${base}/api/docs/search?q=${encodeURIComponent(query)}&limit=${limit}`;
18398
+ let res;
18399
+ try {
18400
+ res = await fetch(url, {
18401
+ headers: {
18402
+ accept: "application/json"
18403
+ }
18404
+ });
18405
+ } catch (err) {
18406
+ return envelope_envelope({
18407
+ ok: false,
18408
+ command: docs_search_COMMAND,
18409
+ status: "network-failed",
18410
+ error: {
18411
+ code: "E_NETWORK",
18412
+ message: `Could not reach ${base}: ${err instanceof Error ? err.message : String(err)}`
18413
+ }
18414
+ });
18415
+ }
18416
+ let body = {};
18417
+ try {
18418
+ body = await res.json();
18419
+ } catch {
18420
+ body = {};
18421
+ }
18422
+ if (!res.ok) return envelope_envelope({
18423
+ ok: false,
18424
+ command: docs_search_COMMAND,
18425
+ status: 429 === res.status ? "rate-limited" : "search-failed",
18426
+ error: {
18427
+ code: "E_PLATFORM",
18428
+ message: body.message || `Docs search answered ${res.status}.`
18429
+ },
18430
+ ...429 === res.status ? {
18431
+ hint: `Wait ${body.retryAfterSeconds ?? 60} seconds, or read the docs at https://extension.js.org/docs.`
18432
+ } : {}
18433
+ });
18434
+ const results = Array.isArray(body.results) ? body.results : [];
18435
+ return envelope_envelope({
18436
+ ok: true,
18437
+ command: docs_search_COMMAND,
18438
+ status: results.length ? "found" : "no-match",
18439
+ value: {
18440
+ query,
18441
+ results
18442
+ },
18443
+ ...results.length ? {} : {
18444
+ hint: "No page matched. Try fewer or different words, or browse https://extension.js.org/docs."
18445
+ }
18446
+ });
18447
+ }
17942
18448
  function typeOf(value) {
17943
18449
  if (Array.isArray(value)) return "array";
17944
18450
  if (null === value) return "null";
@@ -18195,6 +18701,10 @@ const TOOL_POLICY = {
18195
18701
  group: "local",
18196
18702
  annotations: reads(true)
18197
18703
  },
18704
+ extension_docs_search: {
18705
+ group: "local",
18706
+ annotations: reads(true)
18707
+ },
18198
18708
  extension_add_feature: {
18199
18709
  group: "local",
18200
18710
  annotations: reads()
@@ -18535,7 +19045,8 @@ const tools = [
18535
19045
  project_create_namespaceObject,
18536
19046
  workspace_create_namespaceObject,
18537
19047
  browsers_namespaceObject,
18538
- doctor_namespaceObject
19048
+ doctor_namespaceObject,
19049
+ docs_search_namespaceObject
18539
19050
  ];
18540
19051
  function toolResultFrame(result, untrusted = false) {
18541
19052
  let refused;
@@ -47,4 +47,32 @@ export declare function rdpCollectConsoleMessages(port: number, options?: {
47
47
  timeoutMs?: number;
48
48
  settleMs?: number;
49
49
  }): Promise<ConsoleMessage[]>;
50
+ export type RdpEvalOutcome = {
51
+ ok: true;
52
+ value: unknown;
53
+ tab: {
54
+ url: string;
55
+ title: string;
56
+ };
57
+ } | {
58
+ ok: false;
59
+ name: string;
60
+ message: string;
61
+ };
62
+ export declare function rdpStartExpression(expression: string, token: string): string;
63
+ export declare function rdpPollExpression(token: string): string;
64
+ type RdpSettled = {
65
+ ok: true;
66
+ value: unknown;
67
+ } | {
68
+ ok: false;
69
+ name: string;
70
+ message: string;
71
+ };
72
+ export declare function readRdpEvalSlot(raw: unknown): RdpSettled | "pending" | "lost";
73
+ export declare function rdpEvaluateInTab(port: number, options: {
74
+ select: (tabs: RdpTab[]) => RdpTab | null | undefined;
75
+ expression: string;
76
+ timeoutMs?: number;
77
+ }): Promise<RdpEvalOutcome>;
50
78
  export {};
@@ -0,0 +1,30 @@
1
+ export declare const schema: {
2
+ name: string;
3
+ description: string;
4
+ inputSchema: {
5
+ type: "object";
6
+ properties: {
7
+ query: {
8
+ type: string;
9
+ description: string;
10
+ };
11
+ limit: {
12
+ type: string;
13
+ minimum: number;
14
+ maximum: number;
15
+ default: number;
16
+ description: string;
17
+ };
18
+ api: {
19
+ readonly type: "string";
20
+ readonly description: "Platform base URL (default EXTENSION_DEV_API_URL, else https://www.extension.dev)";
21
+ };
22
+ };
23
+ required: string[];
24
+ };
25
+ };
26
+ export declare function handler(args: {
27
+ query: string;
28
+ limit?: number;
29
+ api?: string;
30
+ }): Promise<string>;
@@ -66,6 +66,7 @@ export declare const schema: {
66
66
  required: string[];
67
67
  };
68
68
  };
69
+ export declare function protocolSnapshotExpression(wantHtml: boolean, maxBytes: number): string;
69
70
  export declare function handler(args: ActArgs & {
70
71
  tab?: number;
71
72
  url?: string;
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@extension.dev/mcp",
3
3
  "type": "module",
4
- "version": "10.10.6",
5
- "description": "MCP server that lets AI agents (Claude Code, Claude Desktop, Cursor, Copilot, Codex) build, run, inspect, and publish browser extensions. 31 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.",
4
+ "version": "10.10.8",
5
+ "description": "MCP server that lets AI agents (Claude Code, Claude Desktop, Cursor, Copilot, Codex) build, run, inspect, and publish browser extensions. 32 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",
8
8
  "author": {
@@ -92,7 +92,7 @@
92
92
  "extension.js"
93
93
  ],
94
94
  "dependencies": {
95
- "@extension.dev/urls": "^0.8.1",
95
+ "@extension.dev/urls": "^0.8.2",
96
96
  "@modelcontextprotocol/sdk": "^1.12.1",
97
97
  "browser-extension-manifest-fields": "^2.2.9",
98
98
  "cross-spawn": "^7.0.6",
package/server.json CHANGED
@@ -1,19 +1,19 @@
1
1
  {
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-09-29/server.schema.json",
3
3
  "name": "io.github.extensiondev/mcp",
4
- "description": "Build, run, inspect, and publish browser extensions from any MCP client. 31 tools, 11 browsers.",
4
+ "description": "Build, run, inspect, and publish browser extensions from any MCP client. 32 tools, 11 browsers.",
5
5
  "repository": {
6
6
  "url": "https://github.com/extensiondev/mcp",
7
7
  "source": "github"
8
8
  },
9
9
  "websiteUrl": "https://extension.dev",
10
- "version": "10.10.6",
10
+ "version": "10.10.8",
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.10.6",
16
+ "version": "10.10.8",
17
17
  "runtimeHint": "npx",
18
18
  "transport": {
19
19
  "type": "stdio"