@extension.dev/mcp 10.10.7 → 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.
- package/.claude-plugin/marketplace.json +1 -1
- package/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +38 -0
- package/dist/module.js +422 -14
- package/dist/src/lib/rdp.d.ts +28 -0
- package/dist/src/tools/dom-snapshot.d.ts +1 -0
- package/package.json +2 -2
- 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.10.
|
|
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.
|
|
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,43 @@
|
|
|
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
|
+
|
|
3
41
|
## 10.10.7
|
|
4
42
|
|
|
5
43
|
- A share revoke now waits for a person by default, like a real store
|
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";
|
|
@@ -92,6 +92,7 @@ var dom_snapshot_namespaceObject = {};
|
|
|
92
92
|
__webpack_require__.r(dom_snapshot_namespaceObject);
|
|
93
93
|
__webpack_require__.d(dom_snapshot_namespaceObject, {
|
|
94
94
|
handler: ()=>dom_snapshot_handler,
|
|
95
|
+
protocolSnapshotExpression: ()=>protocolSnapshotExpression,
|
|
95
96
|
schema: ()=>dom_snapshot_schema
|
|
96
97
|
});
|
|
97
98
|
var eval_namespaceObject = {};
|
|
@@ -242,7 +243,7 @@ __webpack_require__.d(workspace_create_namespaceObject, {
|
|
|
242
243
|
handler: ()=>workspace_create_handler,
|
|
243
244
|
schema: ()=>workspace_create_schema
|
|
244
245
|
});
|
|
245
|
-
var package_namespaceObject = JSON.parse('{"rE":"10.10.
|
|
246
|
+
var package_namespaceObject = JSON.parse('{"rE":"10.10.8","El":{"OP":"4.1.31"}}');
|
|
246
247
|
const ENVELOPE_SCHEMA = 1;
|
|
247
248
|
const collectWarnings = (warnings)=>{
|
|
248
249
|
if (!warnings) return [];
|
|
@@ -13609,6 +13610,223 @@ async function rdpCollectConsoleMessages(port, options) {
|
|
|
13609
13610
|
return messages;
|
|
13610
13611
|
});
|
|
13611
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
|
+
}
|
|
13612
13830
|
const inspect_schema_schema = {
|
|
13613
13831
|
name: "extension_inspect",
|
|
13614
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.",
|
|
@@ -14443,7 +14661,7 @@ function readRelayFrame(value) {
|
|
|
14443
14661
|
}
|
|
14444
14662
|
const eval_schema = {
|
|
14445
14663
|
name: "extension_eval",
|
|
14446
|
-
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}.",
|
|
14447
14665
|
inputSchema: {
|
|
14448
14666
|
type: "object",
|
|
14449
14667
|
properties: {
|
|
@@ -14764,6 +14982,7 @@ async function evaluateThroughRelay(args, browser, context) {
|
|
|
14764
14982
|
], args.projectPath, args.timeout, eval_schema.name);
|
|
14765
14983
|
let raw = await run(relaySafeExpression(args.expression, token));
|
|
14766
14984
|
let parsed = tryParseFrame(raw);
|
|
14985
|
+
if (isGeckoFamily(browser) && cspRefusedFrame(parsed)) return evaluatePastSurfaceCsp(args, context, run);
|
|
14767
14986
|
if (!parsed || true !== parsed.ok) return raw;
|
|
14768
14987
|
let frame = readRelayFrame(parsed.value);
|
|
14769
14988
|
if (!frame) return raw;
|
|
@@ -14810,21 +15029,28 @@ async function evaluateThroughRelay(args, browser, context) {
|
|
|
14810
15029
|
return actFrameJson(parsed);
|
|
14811
15030
|
}
|
|
14812
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
|
+
}
|
|
14813
15043
|
function explainCspRefusal(raw, context) {
|
|
14814
15044
|
const parsed = tryParseFrame(raw);
|
|
14815
|
-
if (!parsed ||
|
|
14816
|
-
const code = "string" == typeof parsed.error?.code ? parsed.error.code : "";
|
|
14817
|
-
const message = String(parsed.error?.message ?? "");
|
|
14818
|
-
if ("E_EVAL" !== code || !CSP_EVAL_REFUSAL.test(message)) return null;
|
|
15045
|
+
if (!parsed || !cspRefusedFrame(parsed)) return null;
|
|
14819
15046
|
parsed.error.name = "CspBlocksEval";
|
|
14820
|
-
parsed.hint =
|
|
15047
|
+
parsed.hint = cspRefusalHint(context);
|
|
14821
15048
|
if ("string" == typeof parsed.error.hint) delete parsed.error.hint;
|
|
14822
15049
|
return actFrameJson(parsed);
|
|
14823
15050
|
}
|
|
14824
15051
|
const NO_SCRIPTING_API = /scripting is not available/i;
|
|
14825
15052
|
function pageEvalRefusedByCsp(parsed) {
|
|
14826
|
-
|
|
14827
|
-
return "E_EVAL" === code && CSP_EVAL_REFUSAL.test(String(parsed.error?.message ?? ""));
|
|
15053
|
+
return cspRefusedFrame(parsed);
|
|
14828
15054
|
}
|
|
14829
15055
|
function isSingleExpression(source) {
|
|
14830
15056
|
try {
|
|
@@ -14834,6 +15060,110 @@ function isSingleExpression(source) {
|
|
|
14834
15060
|
return false;
|
|
14835
15061
|
}
|
|
14836
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
|
+
}
|
|
14837
15167
|
async function evaluateThroughExecuteScript(args, browser, context, raw) {
|
|
14838
15168
|
if ("page" !== context && "content" !== context) return null;
|
|
14839
15169
|
if (isChromiumFamily(browser)) return null;
|
|
@@ -14928,6 +15258,12 @@ async function eval_handler(args) {
|
|
|
14928
15258
|
}
|
|
14929
15259
|
return raw;
|
|
14930
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
|
+
}
|
|
14931
15267
|
const declared = declaredSurfaces(args.projectPath, browser) ?? [];
|
|
14932
15268
|
return envelope_envelope({
|
|
14933
15269
|
ok: false,
|
|
@@ -14955,6 +15291,9 @@ async function eval_handler(args) {
|
|
|
14955
15291
|
], args.projectPath, args.timeout, eval_schema.name);
|
|
14956
15292
|
const mv2Fallback = await evaluateThroughExecuteScript(args, browser, context, raw);
|
|
14957
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();
|
|
14958
15297
|
const cspRefusal = explainCspRefusal(raw, context);
|
|
14959
15298
|
if (null !== cspRefusal) return cspRefusal;
|
|
14960
15299
|
const trustedTypes = tryParseFrame(raw);
|
|
@@ -15191,6 +15530,72 @@ const dom_snapshot_schema = {
|
|
|
15191
15530
|
]
|
|
15192
15531
|
}
|
|
15193
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
|
+
}
|
|
15194
15599
|
async function cdpPortOrError(projectPath, browser, feature) {
|
|
15195
15600
|
if (!isChromiumFamily(browser)) return {
|
|
15196
15601
|
error: envelope_envelope({
|
|
@@ -15433,7 +15838,10 @@ async function dom_snapshot_handler(args) {
|
|
|
15433
15838
|
if (null != withConsole) cli.push("--with-console", String(withConsole));
|
|
15434
15839
|
cli.push("--browser", resolveSessionBrowser(args.projectPath, args.browser).browser);
|
|
15435
15840
|
if (null != args.timeout) cli.push("--timeout", String(args.timeout));
|
|
15436
|
-
const
|
|
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;
|
|
15437
15845
|
if (!resolvedTarget) return raw;
|
|
15438
15846
|
try {
|
|
15439
15847
|
const parsed = JSON.parse(raw);
|
|
@@ -16005,7 +16413,7 @@ async function readStores(args) {
|
|
|
16005
16413
|
const healthUrl = registryFileUrl(ref, "stores/health.json");
|
|
16006
16414
|
const statusUrl = registryFileUrl(ref, "stores/status.json");
|
|
16007
16415
|
const submissionsUrl = registryFileUrl(ref, "stores/submissions.json");
|
|
16008
|
-
const consoleStoresUrl = consoleProjectUrl(ref,
|
|
16416
|
+
const consoleStoresUrl = consoleProjectUrl(ref, ConsoleProjectPage.submissions, args.api);
|
|
16009
16417
|
const [healthRes, statusRes, submissionsRes] = await Promise.all([
|
|
16010
16418
|
fetchRegistryJson(healthUrl, fetch, {
|
|
16011
16419
|
ref,
|
|
@@ -16035,7 +16443,7 @@ async function readStores(args) {
|
|
|
16035
16443
|
project: ref.project
|
|
16036
16444
|
}
|
|
16037
16445
|
});
|
|
16038
|
-
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
|
|
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", {
|
|
16039
16447
|
workspace: ref.workspace,
|
|
16040
16448
|
project: ref.project,
|
|
16041
16449
|
registryUrls: {
|
|
@@ -16447,7 +16855,7 @@ async function submit_handler(args) {
|
|
|
16447
16855
|
let statusNote = null;
|
|
16448
16856
|
if (dryRun) {
|
|
16449
16857
|
const ref = resolveProjectRef();
|
|
16450
|
-
const consoleStoresUrl = consoleProjectUrl(ref,
|
|
16858
|
+
const consoleStoresUrl = consoleProjectUrl(ref, ConsoleProjectPage.submissions, args.api);
|
|
16451
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}.`;
|
|
16452
16860
|
let health = null;
|
|
16453
16861
|
let healthUnreadable = null;
|
package/dist/src/lib/rdp.d.ts
CHANGED
|
@@ -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 {};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@extension.dev/mcp",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "10.10.
|
|
4
|
+
"version": "10.10.8",
|
|
5
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",
|
|
@@ -92,7 +92,7 @@
|
|
|
92
92
|
"extension.js"
|
|
93
93
|
],
|
|
94
94
|
"dependencies": {
|
|
95
|
-
"@extension.dev/urls": "^0.8.
|
|
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
|
@@ -7,13 +7,13 @@
|
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
9
|
"websiteUrl": "https://extension.dev",
|
|
10
|
-
"version": "10.10.
|
|
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.
|
|
16
|
+
"version": "10.10.8",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|