@extension.dev/mcp 4.2.0 → 4.2.2
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/CHANGELOG.md +42 -0
- package/README.md +1 -1
- package/dist/module.js +200 -91
- package/dist/src/lib/act.d.ts +12 -0
- package/dist/src/lib/cdp-port.d.ts +7 -0
- package/dist/src/lib/session-browser.d.ts +7 -0
- package/dist/src/tools/dom-inspect.d.ts +1 -1
- package/dist/src/tools/eval.d.ts +1 -1
- package/dist/src/tools/list-extensions.d.ts +1 -1
- package/dist/src/tools/logs-schema.d.ts +0 -1
- package/dist/src/tools/open.d.ts +1 -1
- package/dist/src/tools/reload.d.ts +1 -1
- package/dist/src/tools/source-inspect.d.ts +1 -1
- package/dist/src/tools/storage.d.ts +1 -1
- package/dist/src/tools/wait.d.ts +0 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,47 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.2.2
|
|
4
|
+
|
|
5
|
+
Agent-ergonomics release from the 4.2.1 fresh-eyes walk: the two changes
|
|
6
|
+
that removed nearly all friction a real MCP client hit.
|
|
7
|
+
|
|
8
|
+
- Session-aware browser default. Tools that target a running session
|
|
9
|
+
(`extension_logs`, `extension_reload`, `extension_eval`,
|
|
10
|
+
`extension_storage`, `extension_open`, `extension_dom_inspect`,
|
|
11
|
+
`extension_list_extensions`, `extension_source_inspect`,
|
|
12
|
+
`extension_wait`) no longer hard-default `browser` to a constant that
|
|
13
|
+
could disagree with the session `extension_dev` actually started.
|
|
14
|
+
Omitting `browser` now resolves to the active session's browser:
|
|
15
|
+
in-memory registry first, then the freshest live `ready.json` contract
|
|
16
|
+
on disk (dead pids ignored), then the old constant. Starting a session
|
|
17
|
+
with `browser: "chrome"` and calling `extension_logs` with no args now
|
|
18
|
+
just works instead of erroring about a missing chromium channel.
|
|
19
|
+
- Error hints speak the MCP tool surface, not the CLI. Act-verb error
|
|
20
|
+
prose is rewritten before returning: `` `extension dev
|
|
21
|
+
--browser=chromium --allow-control` `` becomes `extension_dev with
|
|
22
|
+
{ browser: "chromium", allowControl: true }`, and stray
|
|
23
|
+
`--allow-control` / `--allow-eval` / `--browser=<x>` mentions become
|
|
24
|
+
their tool-argument names. Result data is never touched — only
|
|
25
|
+
error/hint prose. Tool descriptions now name `allowControl` /
|
|
26
|
+
`allowEval` directly, so agents no longer discover the gates by
|
|
27
|
+
fuzzing the schema.
|
|
28
|
+
- The no-channel error now names the session that IS running ("Active
|
|
29
|
+
session browser(s) for this project: chrome — pass that as `browser`"),
|
|
30
|
+
so an agent retargets instead of spawning a second, conflicting
|
|
31
|
+
session. Same for the `extension_logs` follow miss.
|
|
32
|
+
- `extension_list_extensions` / `extension_source_inspect` accept
|
|
33
|
+
`browser: "chromium"` (the default dev target) instead of rejecting it
|
|
34
|
+
as non-Chromium.
|
|
35
|
+
- Tests: session-browser resolution + hint-translation suite (137 total).
|
|
36
|
+
|
|
37
|
+
## 4.2.1
|
|
38
|
+
|
|
39
|
+
`extension_build` failures no longer kill the MCP server process
|
|
40
|
+
(fatal-error path returned a rejected promise the server didn't catch).
|
|
41
|
+
CDP-dependent tools resolve the debug port from the session's ready
|
|
42
|
+
contract instead of assuming 9222 (plus a test-only engine pin
|
|
43
|
+
override, `EXTENSION_MCP_CLI_VERSION`).
|
|
44
|
+
|
|
3
45
|
## 4.2.0
|
|
4
46
|
|
|
5
47
|
Session lifecycle + determinism release. Tool count 27 -> 28.
|
package/README.md
CHANGED
|
@@ -96,7 +96,7 @@ cp node_modules/@extension.dev/mcp/claude/commands/*.md ~/my-extension/.claude/c
|
|
|
96
96
|
| see | `extension_dom_inspect` | CDP-free DOM snapshot |
|
|
97
97
|
| see | `extension_list_extensions` | List loaded extensions (Chromium) |
|
|
98
98
|
| see | `extension_logs` | Stream logs from every context |
|
|
99
|
-
| act | `extension_eval` | Evaluate in a context (
|
|
99
|
+
| act | `extension_eval` | Evaluate in a context (needs `allowEval: true` on `extension_dev`) |
|
|
100
100
|
| act | `extension_storage` | Read/write `chrome.storage` |
|
|
101
101
|
| act | `extension_reload` | Reload extension or tab |
|
|
102
102
|
| act | `extension_open` | Open a surface / trigger `action`, `command` |
|
package/dist/module.js
CHANGED
|
@@ -9,8 +9,8 @@ import node_os from "node:os";
|
|
|
9
9
|
import { extensionBuild } from "extension-develop";
|
|
10
10
|
import cross_spawn from "cross-spawn";
|
|
11
11
|
import { filterKeysForThisBrowser } from "browser-extension-manifest-fields";
|
|
12
|
-
import node_net from "node:net";
|
|
13
12
|
import ws from "ws";
|
|
13
|
+
import node_http from "node:http";
|
|
14
14
|
import { clearCredentials, exchangeAndPersist, fetchLoginConfig, pollForToken, publish, readCredentials, resolveApiBase, resolveToken, safeApiBase, startDeviceCode } from "@extension.dev/core";
|
|
15
15
|
import { extensionInstall, getManagedBrowsersCacheRoot } from "extension-install";
|
|
16
16
|
import { execFile } from "node:child_process";
|
|
@@ -183,7 +183,7 @@ __webpack_require__.d(whoami_namespaceObject, {
|
|
|
183
183
|
handler: ()=>whoami_handler,
|
|
184
184
|
schema: ()=>whoami_schema
|
|
185
185
|
});
|
|
186
|
-
var package_namespaceObject = JSON.parse('{"rE":"4.2.
|
|
186
|
+
var package_namespaceObject = JSON.parse('{"rE":"4.2.2","El":{"OP":"^4.0.8"}}');
|
|
187
187
|
const create_schema = {
|
|
188
188
|
name: "extension_create",
|
|
189
189
|
description: "Create a new browser extension project from a template in the extension.dev template catalog. Use extension_list_templates to see available options.",
|
|
@@ -401,7 +401,8 @@ async function build_handler(args) {
|
|
|
401
401
|
const summary = await extensionBuild(args.projectPath, {
|
|
402
402
|
browser: args.browser ?? "chrome",
|
|
403
403
|
zip: args.zip ?? false,
|
|
404
|
-
zipSource: args.zipSource ?? false
|
|
404
|
+
zipSource: args.zipSource ?? false,
|
|
405
|
+
exitOnError: false
|
|
405
406
|
});
|
|
406
407
|
return JSON.stringify({
|
|
407
408
|
success: true,
|
|
@@ -417,6 +418,10 @@ async function build_handler(args) {
|
|
|
417
418
|
}
|
|
418
419
|
}
|
|
419
420
|
const PINNED_CLI_VERSION = String(package_namespaceObject.El.OP ?? "latest").replace(/^[\^~]/, "");
|
|
421
|
+
function pinnedCliVersion() {
|
|
422
|
+
const override = String(process.env.EXTENSION_MCP_CLI_VERSION || "").trim();
|
|
423
|
+
return override || PINNED_CLI_VERSION;
|
|
424
|
+
}
|
|
420
425
|
function resolveExtensionInvocation(projectDir) {
|
|
421
426
|
if (projectDir) {
|
|
422
427
|
const bin = node_path.join(projectDir, "node_modules", ".bin", "win32" === process.platform ? "extension.cmd" : "extension");
|
|
@@ -431,7 +436,7 @@ function resolveExtensionInvocation(projectDir) {
|
|
|
431
436
|
return {
|
|
432
437
|
command: "npx",
|
|
433
438
|
prefixArgs: [
|
|
434
|
-
`extension@${
|
|
439
|
+
`extension@${pinnedCliVersion()}`
|
|
435
440
|
]
|
|
436
441
|
};
|
|
437
442
|
}
|
|
@@ -1571,6 +1576,110 @@ class CDPClient extends CDPConnection {
|
|
|
1571
1576
|
return result ?? null;
|
|
1572
1577
|
}
|
|
1573
1578
|
}
|
|
1579
|
+
async function resolveCdpPort(projectPath, browser, options) {
|
|
1580
|
+
const waitMs = options?.waitMs ?? 20000;
|
|
1581
|
+
const readyPath = node_path.resolve(projectPath, "dist", "extension-js", browser, "ready.json");
|
|
1582
|
+
const deadline = Date.now() + waitMs;
|
|
1583
|
+
let contractSeen = false;
|
|
1584
|
+
for(;;){
|
|
1585
|
+
try {
|
|
1586
|
+
const contract = JSON.parse(node_fs.readFileSync(readyPath, "utf8"));
|
|
1587
|
+
contractSeen = true;
|
|
1588
|
+
if ("number" == typeof contract.cdpPort) return {
|
|
1589
|
+
port: contract.cdpPort,
|
|
1590
|
+
source: "contract"
|
|
1591
|
+
};
|
|
1592
|
+
} catch {
|
|
1593
|
+
if (!contractSeen) break;
|
|
1594
|
+
}
|
|
1595
|
+
if (Date.now() >= deadline) break;
|
|
1596
|
+
await new Promise((resolve)=>setTimeout(resolve, 500));
|
|
1597
|
+
}
|
|
1598
|
+
if (!contractSeen && await isCdpEndpoint(9222)) return {
|
|
1599
|
+
port: 9222,
|
|
1600
|
+
source: "default-probe"
|
|
1601
|
+
};
|
|
1602
|
+
return null;
|
|
1603
|
+
}
|
|
1604
|
+
const CDP_PORT_MISSING_HINT = "The session's ready contract has no CDP port (the browser may still be binding its debug port, or was launched without one). Confirm the session with extension_wait, give it a moment, and retry.";
|
|
1605
|
+
function isCdpEndpoint(port) {
|
|
1606
|
+
return new Promise((resolve)=>{
|
|
1607
|
+
const req = node_http.get({
|
|
1608
|
+
host: "127.0.0.1",
|
|
1609
|
+
port,
|
|
1610
|
+
path: "/json/version",
|
|
1611
|
+
timeout: 1000
|
|
1612
|
+
}, (res)=>{
|
|
1613
|
+
res.resume();
|
|
1614
|
+
resolve(200 === res.statusCode);
|
|
1615
|
+
});
|
|
1616
|
+
req.on("error", ()=>resolve(false));
|
|
1617
|
+
req.on("timeout", ()=>{
|
|
1618
|
+
req.destroy();
|
|
1619
|
+
resolve(false);
|
|
1620
|
+
});
|
|
1621
|
+
});
|
|
1622
|
+
}
|
|
1623
|
+
function contractSightings(projectPath) {
|
|
1624
|
+
const root = node_path.resolve(projectPath, "dist", "extension-js");
|
|
1625
|
+
let dirs;
|
|
1626
|
+
try {
|
|
1627
|
+
dirs = node_fs.readdirSync(root);
|
|
1628
|
+
} catch {
|
|
1629
|
+
return [];
|
|
1630
|
+
}
|
|
1631
|
+
const sightings = [];
|
|
1632
|
+
for (const dir of dirs){
|
|
1633
|
+
const readyPath = node_path.join(root, dir, "ready.json");
|
|
1634
|
+
try {
|
|
1635
|
+
const stat = node_fs.statSync(readyPath);
|
|
1636
|
+
const contract = JSON.parse(node_fs.readFileSync(readyPath, "utf8"));
|
|
1637
|
+
if (contract?.status !== "ready") continue;
|
|
1638
|
+
sightings.push({
|
|
1639
|
+
browser: dir,
|
|
1640
|
+
mtimeMs: stat.mtimeMs,
|
|
1641
|
+
pid: "number" == typeof contract.pid ? contract.pid : void 0
|
|
1642
|
+
});
|
|
1643
|
+
} catch {}
|
|
1644
|
+
}
|
|
1645
|
+
return sightings;
|
|
1646
|
+
}
|
|
1647
|
+
function pidAlive(pid) {
|
|
1648
|
+
try {
|
|
1649
|
+
process.kill(pid, 0);
|
|
1650
|
+
return true;
|
|
1651
|
+
} catch {
|
|
1652
|
+
return false;
|
|
1653
|
+
}
|
|
1654
|
+
}
|
|
1655
|
+
function knownSessionBrowsers(projectPath) {
|
|
1656
|
+
const resolved = node_path.resolve(projectPath);
|
|
1657
|
+
const browsers = [];
|
|
1658
|
+
for (const session of listSessions())if (node_path.resolve(session.projectPath) === resolved) browsers.push(session.browser);
|
|
1659
|
+
for (const sighting of contractSightings(projectPath))if (void 0 === sighting.pid || pidAlive(sighting.pid)) browsers.push(sighting.browser);
|
|
1660
|
+
return Array.from(new Set(browsers));
|
|
1661
|
+
}
|
|
1662
|
+
function resolveSessionBrowser(projectPath, explicit, fallback = "chromium") {
|
|
1663
|
+
if (explicit) return {
|
|
1664
|
+
browser: explicit,
|
|
1665
|
+
source: "explicit"
|
|
1666
|
+
};
|
|
1667
|
+
const resolved = node_path.resolve(projectPath);
|
|
1668
|
+
const mine = listSessions().filter((s)=>node_path.resolve(s.projectPath) === resolved);
|
|
1669
|
+
if (mine.length > 0) return {
|
|
1670
|
+
browser: mine[mine.length - 1].browser,
|
|
1671
|
+
source: "session"
|
|
1672
|
+
};
|
|
1673
|
+
const sightings = contractSightings(projectPath).filter((s)=>void 0 === s.pid || pidAlive(s.pid)).sort((a, b)=>b.mtimeMs - a.mtimeMs);
|
|
1674
|
+
if (sightings.length > 0) return {
|
|
1675
|
+
browser: sightings[0].browser,
|
|
1676
|
+
source: "contract"
|
|
1677
|
+
};
|
|
1678
|
+
return {
|
|
1679
|
+
browser: fallback,
|
|
1680
|
+
source: "fallback"
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1574
1683
|
const source_inspect_schema = {
|
|
1575
1684
|
name: "extension_source_inspect",
|
|
1576
1685
|
description: "Inspect a running extension's live state via Chrome DevTools Protocol: full HTML (with shadow DOM), DOM structure, content script injection, console messages, and CSS selector queries. Requires an active dev or start session.",
|
|
@@ -1614,7 +1723,7 @@ const source_inspect_schema = {
|
|
|
1614
1723
|
},
|
|
1615
1724
|
browser: {
|
|
1616
1725
|
type: "string",
|
|
1617
|
-
|
|
1726
|
+
description: "Browser session to target. Defaults to the active dev session's browser for this project."
|
|
1618
1727
|
},
|
|
1619
1728
|
maxBytes: {
|
|
1620
1729
|
type: "number",
|
|
@@ -1632,30 +1741,8 @@ const source_inspect_schema = {
|
|
|
1632
1741
|
]
|
|
1633
1742
|
}
|
|
1634
1743
|
};
|
|
1635
|
-
async function findCdpPort(projectPath, browser) {
|
|
1636
|
-
const readyPath = node_path.resolve(projectPath, "dist", "extension-js", browser, "ready.json");
|
|
1637
|
-
try {
|
|
1638
|
-
const contract = JSON.parse(node_fs.readFileSync(readyPath, "utf8"));
|
|
1639
|
-
if ("number" == typeof contract.cdpPort) return contract.cdpPort;
|
|
1640
|
-
} catch {}
|
|
1641
|
-
const defaultPort = 9222;
|
|
1642
|
-
return new Promise((resolve)=>{
|
|
1643
|
-
const socket = new node_net.Socket();
|
|
1644
|
-
socket.setTimeout(1000);
|
|
1645
|
-
socket.on("connect", ()=>{
|
|
1646
|
-
socket.destroy();
|
|
1647
|
-
resolve(defaultPort);
|
|
1648
|
-
});
|
|
1649
|
-
socket.on("error", ()=>resolve(null));
|
|
1650
|
-
socket.on("timeout", ()=>{
|
|
1651
|
-
socket.destroy();
|
|
1652
|
-
resolve(null);
|
|
1653
|
-
});
|
|
1654
|
-
socket.connect(defaultPort, "127.0.0.1");
|
|
1655
|
-
});
|
|
1656
|
-
}
|
|
1657
1744
|
async function source_inspect_handler(args) {
|
|
1658
|
-
const browser = args.browser
|
|
1745
|
+
const { browser } = resolveSessionBrowser(args.projectPath, args.browser, "chrome");
|
|
1659
1746
|
const include = new Set(args.include ?? [
|
|
1660
1747
|
"summary",
|
|
1661
1748
|
"meta",
|
|
@@ -1664,18 +1751,20 @@ async function source_inspect_handler(args) {
|
|
|
1664
1751
|
const maxBytes = args.maxBytes ?? 262144;
|
|
1665
1752
|
const isChromium = [
|
|
1666
1753
|
"chrome",
|
|
1754
|
+
"chromium",
|
|
1667
1755
|
"edge",
|
|
1668
1756
|
"chromium-based"
|
|
1669
1757
|
].includes(browser);
|
|
1670
1758
|
if (!isChromium) return JSON.stringify({
|
|
1671
1759
|
error: `Source inspection for ${browser} uses RDP (Remote Debug Protocol). Currently only Chromium CDP is supported.`,
|
|
1672
|
-
hint:
|
|
1760
|
+
hint: 'Pass browser: "chrome" (against a Chromium-family dev session).'
|
|
1673
1761
|
});
|
|
1674
|
-
const
|
|
1675
|
-
if (!
|
|
1762
|
+
const resolved = await resolveCdpPort(args.projectPath, browser);
|
|
1763
|
+
if (!resolved) return JSON.stringify({
|
|
1676
1764
|
error: "No active dev session found. Cannot connect to Chrome DevTools Protocol.",
|
|
1677
|
-
hint:
|
|
1765
|
+
hint: `Start a dev session first with extension_dev, then use extension_wait to confirm it is ready. ${CDP_PORT_MISSING_HINT}`
|
|
1678
1766
|
});
|
|
1767
|
+
const cdpPort = resolved.port;
|
|
1679
1768
|
const cdp = new CDPClient();
|
|
1680
1769
|
try {
|
|
1681
1770
|
const allTargets = await CDPClient.discoverTargets(cdpPort);
|
|
@@ -1769,7 +1858,7 @@ const list_extensions_schema = {
|
|
|
1769
1858
|
},
|
|
1770
1859
|
browser: {
|
|
1771
1860
|
type: "string",
|
|
1772
|
-
|
|
1861
|
+
description: "Browser session to target. Defaults to the active dev session's browser for this project."
|
|
1773
1862
|
}
|
|
1774
1863
|
},
|
|
1775
1864
|
required: [
|
|
@@ -1778,26 +1867,38 @@ const list_extensions_schema = {
|
|
|
1778
1867
|
}
|
|
1779
1868
|
};
|
|
1780
1869
|
async function list_extensions_handler(args) {
|
|
1781
|
-
const browser = args.browser
|
|
1870
|
+
const { browser } = resolveSessionBrowser(args.projectPath, args.browser, "chrome");
|
|
1782
1871
|
const isChromium = [
|
|
1783
1872
|
"chrome",
|
|
1873
|
+
"chromium",
|
|
1784
1874
|
"edge",
|
|
1785
1875
|
"chromium-based"
|
|
1786
1876
|
].includes(browser);
|
|
1787
1877
|
if (!isChromium) return JSON.stringify({
|
|
1788
1878
|
error: `Listing extensions for ${browser} uses RDP (Remote Debug Protocol). Currently only Chromium CDP is supported.`,
|
|
1789
|
-
hint:
|
|
1879
|
+
hint: 'Pass browser: "chrome" (against a Chromium-family dev session).'
|
|
1790
1880
|
});
|
|
1791
|
-
const
|
|
1792
|
-
if (!
|
|
1881
|
+
const resolved = await resolveCdpPort(args.projectPath, browser);
|
|
1882
|
+
if (!resolved) return JSON.stringify({
|
|
1793
1883
|
error: "No active dev session found. Cannot connect to Chrome DevTools Protocol.",
|
|
1794
|
-
hint:
|
|
1884
|
+
hint: `Start a dev session first with extension_dev, then use extension_wait to confirm it is ready. ${CDP_PORT_MISSING_HINT}`
|
|
1795
1885
|
});
|
|
1886
|
+
const cdpPort = resolved.port;
|
|
1796
1887
|
const cdp = new CDPClient();
|
|
1797
1888
|
try {
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1889
|
+
let targets = null;
|
|
1890
|
+
let lastError = null;
|
|
1891
|
+
for(let attempt = 0; attempt < 3; attempt++)try {
|
|
1892
|
+
const browserWsUrl = await CDPClient.discoverBrowserWsUrl(cdpPort);
|
|
1893
|
+
await cdp.connect(browserWsUrl);
|
|
1894
|
+
targets = await cdp.getTargets();
|
|
1895
|
+
break;
|
|
1896
|
+
} catch (error) {
|
|
1897
|
+
lastError = error;
|
|
1898
|
+
cdp.disconnect();
|
|
1899
|
+
if (attempt < 2) await new Promise((resolve)=>setTimeout(resolve, 800));
|
|
1900
|
+
}
|
|
1901
|
+
if (!targets) throw lastError;
|
|
1801
1902
|
const byId = new Map();
|
|
1802
1903
|
for (const t of targets){
|
|
1803
1904
|
const url = String(t.url ?? "");
|
|
@@ -1849,28 +1950,6 @@ async function list_extensions_handler(args) {
|
|
|
1849
1950
|
cdp.disconnect();
|
|
1850
1951
|
}
|
|
1851
1952
|
}
|
|
1852
|
-
async function list_extensions_findCdpPort(projectPath, browser) {
|
|
1853
|
-
const readyPath = node_path.resolve(projectPath, "dist", "extension-js", browser, "ready.json");
|
|
1854
|
-
try {
|
|
1855
|
-
const contract = JSON.parse(node_fs.readFileSync(readyPath, "utf8"));
|
|
1856
|
-
if ("number" == typeof contract.cdpPort) return contract.cdpPort;
|
|
1857
|
-
} catch {}
|
|
1858
|
-
const defaultPort = 9222;
|
|
1859
|
-
return new Promise((resolve)=>{
|
|
1860
|
-
const socket = new node_net.Socket();
|
|
1861
|
-
socket.setTimeout(1000);
|
|
1862
|
-
socket.on("connect", ()=>{
|
|
1863
|
-
socket.destroy();
|
|
1864
|
-
resolve(defaultPort);
|
|
1865
|
-
});
|
|
1866
|
-
socket.on("error", ()=>resolve(null));
|
|
1867
|
-
socket.on("timeout", ()=>{
|
|
1868
|
-
socket.destroy();
|
|
1869
|
-
resolve(null);
|
|
1870
|
-
});
|
|
1871
|
-
socket.connect(defaultPort, "127.0.0.1");
|
|
1872
|
-
});
|
|
1873
|
-
}
|
|
1874
1953
|
const CONTROL_WS_PATH = "/extjs-control";
|
|
1875
1954
|
const LEVEL_ORDER = [
|
|
1876
1955
|
"error",
|
|
@@ -1933,8 +2012,7 @@ const logs_schema_schema = {
|
|
|
1933
2012
|
},
|
|
1934
2013
|
browser: {
|
|
1935
2014
|
type: "string",
|
|
1936
|
-
|
|
1937
|
-
description: "Which dist/extension-js/<browser>/ to read. Defaults to chromium (the default dev target)."
|
|
2015
|
+
description: "Which dist/extension-js/<browser>/ to read. Defaults to the active dev session's browser for this project (falls back to chromium)."
|
|
1938
2016
|
},
|
|
1939
2017
|
level: {
|
|
1940
2018
|
type: "string",
|
|
@@ -2075,10 +2153,14 @@ async function readFromFile(args, browser, limit) {
|
|
|
2075
2153
|
}
|
|
2076
2154
|
async function readFromStream(args, browser, limit) {
|
|
2077
2155
|
const ready = readReadyContract(args.projectPath, browser);
|
|
2078
|
-
if (!ready)
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2156
|
+
if (!ready) {
|
|
2157
|
+
const running = knownSessionBrowsers(args.projectPath).filter((b)=>b !== browser);
|
|
2158
|
+
const retarget = running.length ? `An active session exists for browser(s): ${running.join(", ")} — pass that as \`browser\`. Otherwise run` : "Run";
|
|
2159
|
+
return JSON.stringify({
|
|
2160
|
+
error: `No active control channel found for ${browser}.`,
|
|
2161
|
+
hint: `${retarget} extension_dev (browser: ${browser}) and wait for it to be ready, then retry. For past logs without a live channel, call without follow.`
|
|
2162
|
+
});
|
|
2163
|
+
}
|
|
2082
2164
|
const followMs = Math.min(Math.max(args.followMs ?? 4000, 500), 15000);
|
|
2083
2165
|
const matches = makeFilter(args);
|
|
2084
2166
|
const events = [];
|
|
@@ -2141,11 +2223,27 @@ async function readFromStream(args, browser, limit) {
|
|
|
2141
2223
|
});
|
|
2142
2224
|
}
|
|
2143
2225
|
async function logs_handler(args) {
|
|
2144
|
-
const browser = args.browser
|
|
2226
|
+
const { browser } = resolveSessionBrowser(args.projectPath, args.browser);
|
|
2145
2227
|
const limit = args.limit && args.limit > 0 ? args.limit : 200;
|
|
2146
2228
|
if (args.follow) return readFromStream(args, browser, limit);
|
|
2147
2229
|
return readFromFile(args, browser, limit);
|
|
2148
2230
|
}
|
|
2231
|
+
function toMcpSpeak(text) {
|
|
2232
|
+
return text.replace(/`?extension dev(?: [^\s`]*)? --browser[= ]([\w-]+) --allow-control`?/g, 'extension_dev with { browser: "$1", allowControl: true }').replace(/--allow-control/g, "allowControl: true (extension_dev)").replace(/--allow-eval/g, "allowEval: true (extension_dev)").replace(/--browser[= ]([\w-]+)/g, 'browser: "$1"').replace(/`extension dev`/g, "extension_dev").replace(/\bextension dev\b/g, "extension_dev");
|
|
2233
|
+
}
|
|
2234
|
+
function withSessionContext(message, projectPath) {
|
|
2235
|
+
if (!/no active control channel/i.test(message)) return message;
|
|
2236
|
+
const running = knownSessionBrowsers(projectPath);
|
|
2237
|
+
if (0 === running.length) return message;
|
|
2238
|
+
return `${message} Active session browser(s) for this project: ${running.join(", ")} — pass that as \`browser\`, or restart it via extension_dev with allowControl: true if the control channel is off.`;
|
|
2239
|
+
}
|
|
2240
|
+
function translateFrame(frame, projectPath) {
|
|
2241
|
+
if (!frame || false !== frame.ok) return frame;
|
|
2242
|
+
if (frame.error && "string" == typeof frame.error.message) frame.error.message = withSessionContext(toMcpSpeak(frame.error.message), projectPath);
|
|
2243
|
+
if ("string" == typeof frame.error?.hint) frame.error.hint = toMcpSpeak(frame.error.hint);
|
|
2244
|
+
if ("string" == typeof frame.hint) frame.hint = toMcpSpeak(frame.hint);
|
|
2245
|
+
return frame;
|
|
2246
|
+
}
|
|
2149
2247
|
async function runActVerb(args, projectPath, timeoutMs) {
|
|
2150
2248
|
const { code, stdout, stderr } = await runExtensionCli([
|
|
2151
2249
|
...args,
|
|
@@ -2157,14 +2255,16 @@ async function runActVerb(args, projectPath, timeoutMs) {
|
|
|
2157
2255
|
});
|
|
2158
2256
|
const out = stdout.trim();
|
|
2159
2257
|
if (out) try {
|
|
2160
|
-
JSON.parse(out);
|
|
2258
|
+
const frame = JSON.parse(out);
|
|
2259
|
+
if (frame && false === frame.ok) return JSON.stringify(translateFrame(frame, projectPath));
|
|
2161
2260
|
return out;
|
|
2162
2261
|
} catch {}
|
|
2262
|
+
const message = stderr.trim() || `extension exited with code ${code}`;
|
|
2163
2263
|
return JSON.stringify({
|
|
2164
2264
|
ok: false,
|
|
2165
2265
|
error: {
|
|
2166
2266
|
name: "CliError",
|
|
2167
|
-
message:
|
|
2267
|
+
message: withSessionContext(toMcpSpeak(message), projectPath)
|
|
2168
2268
|
}
|
|
2169
2269
|
});
|
|
2170
2270
|
}
|
|
@@ -2179,7 +2279,7 @@ function commonFlags(args) {
|
|
|
2179
2279
|
}
|
|
2180
2280
|
const eval_schema = {
|
|
2181
2281
|
name: "extension_eval",
|
|
2182
|
-
description: "Evaluate an expression in a running extension context (service worker, content script, popup, options, sidebar). Requires the dev session to be started with
|
|
2282
|
+
description: "Evaluate an expression in a running extension context (service worker, content script, popup, options, sidebar). Requires the dev session to be started with allowEval: true (extension_dev; writes a 0600 session token the CLI reads). Wraps `extension eval`.",
|
|
2183
2283
|
inputSchema: {
|
|
2184
2284
|
type: "object",
|
|
2185
2285
|
properties: {
|
|
@@ -2215,7 +2315,7 @@ const eval_schema = {
|
|
|
2215
2315
|
},
|
|
2216
2316
|
browser: {
|
|
2217
2317
|
type: "string",
|
|
2218
|
-
|
|
2318
|
+
description: "Browser session to target. Defaults to the active dev session's browser for this project."
|
|
2219
2319
|
},
|
|
2220
2320
|
timeout: {
|
|
2221
2321
|
type: "number",
|
|
@@ -2229,16 +2329,20 @@ const eval_schema = {
|
|
|
2229
2329
|
}
|
|
2230
2330
|
};
|
|
2231
2331
|
async function eval_handler(args) {
|
|
2332
|
+
const { browser } = resolveSessionBrowser(args.projectPath, args.browser);
|
|
2232
2333
|
return runActVerb([
|
|
2233
2334
|
"eval",
|
|
2234
2335
|
args.expression,
|
|
2235
2336
|
args.projectPath,
|
|
2236
|
-
...commonFlags(
|
|
2337
|
+
...commonFlags({
|
|
2338
|
+
...args,
|
|
2339
|
+
browser
|
|
2340
|
+
})
|
|
2237
2341
|
], args.projectPath, args.timeout);
|
|
2238
2342
|
}
|
|
2239
2343
|
const storage_schema = {
|
|
2240
2344
|
name: "extension_storage",
|
|
2241
|
-
description: "Read or write chrome.storage in a running extension. Requires the dev session to be started with
|
|
2345
|
+
description: "Read or write chrome.storage in a running extension. Requires the dev session to be started with allowControl: true (extension_dev). Wraps `extension storage get|set`.",
|
|
2242
2346
|
inputSchema: {
|
|
2243
2347
|
type: "object",
|
|
2244
2348
|
properties: {
|
|
@@ -2284,7 +2388,7 @@ const storage_schema = {
|
|
|
2284
2388
|
},
|
|
2285
2389
|
browser: {
|
|
2286
2390
|
type: "string",
|
|
2287
|
-
|
|
2391
|
+
description: "Browser session to target. Defaults to the active dev session's browser for this project."
|
|
2288
2392
|
},
|
|
2289
2393
|
timeout: {
|
|
2290
2394
|
type: "number",
|
|
@@ -2298,6 +2402,7 @@ const storage_schema = {
|
|
|
2298
2402
|
}
|
|
2299
2403
|
};
|
|
2300
2404
|
async function storage_handler(args) {
|
|
2405
|
+
const { browser } = resolveSessionBrowser(args.projectPath, args.browser);
|
|
2301
2406
|
const cli = [
|
|
2302
2407
|
"storage",
|
|
2303
2408
|
args.action,
|
|
@@ -2316,13 +2421,13 @@ async function storage_handler(args) {
|
|
|
2316
2421
|
cli.push("--value", JSON.stringify(args.value));
|
|
2317
2422
|
}
|
|
2318
2423
|
if (args.context) cli.push("--context", args.context);
|
|
2319
|
-
|
|
2424
|
+
cli.push("--browser", browser);
|
|
2320
2425
|
if (null != args.timeout) cli.push("--timeout", String(args.timeout));
|
|
2321
2426
|
return runActVerb(cli, args.projectPath, args.timeout);
|
|
2322
2427
|
}
|
|
2323
2428
|
const reload_schema = {
|
|
2324
2429
|
name: "extension_reload",
|
|
2325
|
-
description: "Reload a running extension (background) or a tab. Requires the dev session to be started with
|
|
2430
|
+
description: "Reload a running extension (background) or a tab. Requires the dev session to be started with allowControl: true (extension_dev). Wraps `extension reload`.",
|
|
2326
2431
|
inputSchema: {
|
|
2327
2432
|
type: "object",
|
|
2328
2433
|
properties: {
|
|
@@ -2345,7 +2450,7 @@ const reload_schema = {
|
|
|
2345
2450
|
},
|
|
2346
2451
|
browser: {
|
|
2347
2452
|
type: "string",
|
|
2348
|
-
|
|
2453
|
+
description: "Browser session to target. Defaults to the active dev session's browser for this project."
|
|
2349
2454
|
},
|
|
2350
2455
|
timeout: {
|
|
2351
2456
|
type: "number",
|
|
@@ -2358,15 +2463,19 @@ const reload_schema = {
|
|
|
2358
2463
|
}
|
|
2359
2464
|
};
|
|
2360
2465
|
async function reload_handler(args) {
|
|
2466
|
+
const { browser } = resolveSessionBrowser(args.projectPath, args.browser);
|
|
2361
2467
|
return runActVerb([
|
|
2362
2468
|
"reload",
|
|
2363
2469
|
args.projectPath,
|
|
2364
|
-
...commonFlags(
|
|
2470
|
+
...commonFlags({
|
|
2471
|
+
...args,
|
|
2472
|
+
browser
|
|
2473
|
+
})
|
|
2365
2474
|
], args.projectPath, args.timeout);
|
|
2366
2475
|
}
|
|
2367
2476
|
const open_schema = {
|
|
2368
2477
|
name: "extension_open",
|
|
2369
|
-
description: "Open an extension surface or replay an event in a running session. 'popup'/'options'/'sidebar' open UI surfaces. 'action' triggers the toolbar action: opens the action's popup, or (no popup) replays chrome.action.onClicked. 'command' replays a chrome.commands.onCommand keyboard shortcut (pass `name`). NOTE: action/command replay invokes your listener WITHOUT a user gesture, so the gesture-derived activeTab grant does not apply (the result includes gesture:false and a warning when activeTab is declared). Requires
|
|
2478
|
+
description: "Open an extension surface or replay an event in a running session. 'popup'/'options'/'sidebar' open UI surfaces. 'action' triggers the toolbar action: opens the action's popup, or (no popup) replays chrome.action.onClicked. 'command' replays a chrome.commands.onCommand keyboard shortcut (pass `name`). NOTE: action/command replay invokes your listener WITHOUT a user gesture, so the gesture-derived activeTab grant does not apply (the result includes gesture:false and a warning when activeTab is declared). Requires the dev session to be started with allowControl: true (extension_dev). Wraps `extension open`.",
|
|
2370
2479
|
inputSchema: {
|
|
2371
2480
|
type: "object",
|
|
2372
2481
|
properties: {
|
|
@@ -2391,7 +2500,7 @@ const open_schema = {
|
|
|
2391
2500
|
},
|
|
2392
2501
|
browser: {
|
|
2393
2502
|
type: "string",
|
|
2394
|
-
|
|
2503
|
+
description: "Browser session to target. Defaults to the active dev session's browser for this project."
|
|
2395
2504
|
},
|
|
2396
2505
|
timeout: {
|
|
2397
2506
|
type: "number",
|
|
@@ -2405,19 +2514,20 @@ const open_schema = {
|
|
|
2405
2514
|
}
|
|
2406
2515
|
};
|
|
2407
2516
|
async function open_handler(args) {
|
|
2517
|
+
const { browser } = resolveSessionBrowser(args.projectPath, args.browser);
|
|
2408
2518
|
const cli = [
|
|
2409
2519
|
"open",
|
|
2410
2520
|
args.surface,
|
|
2411
2521
|
args.projectPath
|
|
2412
2522
|
];
|
|
2413
2523
|
if ("command" === args.surface && args.name) cli.push("--name", args.name);
|
|
2414
|
-
|
|
2524
|
+
cli.push("--browser", browser);
|
|
2415
2525
|
if (null != args.timeout) cli.push("--timeout", String(args.timeout));
|
|
2416
2526
|
return runActVerb(cli, args.projectPath, args.timeout);
|
|
2417
2527
|
}
|
|
2418
2528
|
const dom_inspect_schema = {
|
|
2419
2529
|
name: "extension_dom_inspect",
|
|
2420
|
-
description: "Inspect a page/content-script DOM via the agent bridge (CDP-free, localhost). Returns a structured snapshot (counts, extension roots, open shadow roots, optional capped HTML). Requires
|
|
2530
|
+
description: "Inspect a page/content-script DOM via the agent bridge (CDP-free, localhost). Returns a structured snapshot (counts, extension roots, open shadow roots, optional capped HTML). Requires the dev session to be started with allowControl: true (extension_dev). For closed shadow roots or deep CDP inspection use extension_source_inspect. Wraps `extension inspect`.",
|
|
2421
2531
|
inputSchema: {
|
|
2422
2532
|
type: "object",
|
|
2423
2533
|
properties: {
|
|
@@ -2466,7 +2576,7 @@ const dom_inspect_schema = {
|
|
|
2466
2576
|
},
|
|
2467
2577
|
browser: {
|
|
2468
2578
|
type: "string",
|
|
2469
|
-
|
|
2579
|
+
description: "Browser session to target. Defaults to the active dev session's browser for this project."
|
|
2470
2580
|
},
|
|
2471
2581
|
timeout: {
|
|
2472
2582
|
type: "number",
|
|
@@ -2502,7 +2612,7 @@ async function dom_inspect_handler(args) {
|
|
|
2502
2612
|
if (args.include?.length) cli.push("--include", args.include.join(","));
|
|
2503
2613
|
if (null != args.maxBytes) cli.push("--max-bytes", String(args.maxBytes));
|
|
2504
2614
|
if (null != args.withConsole) cli.push("--with-console", String(args.withConsole));
|
|
2505
|
-
|
|
2615
|
+
cli.push("--browser", resolveSessionBrowser(args.projectPath, args.browser).browser);
|
|
2506
2616
|
if (null != args.timeout) cli.push("--timeout", String(args.timeout));
|
|
2507
2617
|
return runActVerb(cli, args.projectPath, args.timeout);
|
|
2508
2618
|
}
|
|
@@ -2659,8 +2769,7 @@ const wait_schema = {
|
|
|
2659
2769
|
},
|
|
2660
2770
|
browser: {
|
|
2661
2771
|
type: "string",
|
|
2662
|
-
|
|
2663
|
-
description: "Browser to check readiness for"
|
|
2772
|
+
description: "Browser to check readiness for. Defaults to the active dev session's browser for this project."
|
|
2664
2773
|
},
|
|
2665
2774
|
timeout: {
|
|
2666
2775
|
type: "number",
|
|
@@ -2674,7 +2783,7 @@ const wait_schema = {
|
|
|
2674
2783
|
}
|
|
2675
2784
|
};
|
|
2676
2785
|
async function wait_handler(args) {
|
|
2677
|
-
const browser = args.browser
|
|
2786
|
+
const { browser } = resolveSessionBrowser(args.projectPath, args.browser, "chrome");
|
|
2678
2787
|
const timeout = args.timeout ?? 60000;
|
|
2679
2788
|
const readyPath = node_path.resolve(args.projectPath, "dist", "extension-js", browser, "ready.json");
|
|
2680
2789
|
const start = Date.now();
|
|
@@ -3139,7 +3248,7 @@ async function install_browser_handler(args) {
|
|
|
3139
3248
|
status: "installed",
|
|
3140
3249
|
browser: args.browser,
|
|
3141
3250
|
duration: Date.now() - start,
|
|
3142
|
-
hint: `Browser "${args.browser}" is now available. Use extension_dev or extension_start with
|
|
3251
|
+
hint: `Browser "${args.browser}" is now available. Use extension_dev or extension_start with browser: "${args.browser}".`
|
|
3143
3252
|
});
|
|
3144
3253
|
} catch (err) {
|
|
3145
3254
|
return JSON.stringify({
|
package/dist/src/lib/act.d.ts
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Translate CLI-speak in an error message into the MCP tool surface
|
|
3
|
+
* (fresh-eyes walk, friction #2). The CLI's hints say things like
|
|
4
|
+
* "Run `extension dev --browser=chromium --allow-control` first" — correct
|
|
5
|
+
* for a human at a terminal, a dead end for an agent that only has the MCP
|
|
6
|
+
* tools. Rewrite flags to their tool-argument names so the hint is actionable
|
|
7
|
+
* on the surface the caller is actually using. Result data is never touched —
|
|
8
|
+
* only error/hint prose.
|
|
9
|
+
*/
|
|
10
|
+
export declare function toMcpSpeak(text: string): string;
|
|
1
11
|
/**
|
|
2
12
|
* Run an `extension <verb> … --output json` act command and return its result
|
|
3
13
|
* JSON as a string (the MCP tool payload). The CLI prints the control-channel
|
|
@@ -6,6 +16,8 @@
|
|
|
6
16
|
*
|
|
7
17
|
* Per lockstep invariant #1 the act tools wrap the CLI verb rather than talking
|
|
8
18
|
* to the control WS directly — so MCP behavior can never drift from the CLI.
|
|
19
|
+
* Error PROSE is the one exception: hints are rewritten from CLI flags to MCP
|
|
20
|
+
* tool arguments before returning (see toMcpSpeak).
|
|
9
21
|
*/
|
|
10
22
|
export declare function runActVerb(args: string[], projectPath: string, timeoutMs?: number): Promise<string>;
|
|
11
23
|
/** Shared input fields for act tools. */
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export declare function resolveCdpPort(projectPath: string, browser: string, options?: {
|
|
2
|
+
waitMs?: number;
|
|
3
|
+
}): Promise<{
|
|
4
|
+
port: number;
|
|
5
|
+
source: "contract" | "default-probe";
|
|
6
|
+
} | null>;
|
|
7
|
+
export declare const CDP_PORT_MISSING_HINT = "The session's ready contract has no CDP port (the browser may still be binding its debug port, or was launched without one). Confirm the session with extension_wait, give it a moment, and retry.";
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export interface ResolvedBrowser {
|
|
2
|
+
browser: string;
|
|
3
|
+
source: "explicit" | "session" | "contract" | "fallback";
|
|
4
|
+
}
|
|
5
|
+
/** Browsers with a live session for this project (registry + disk contracts). */
|
|
6
|
+
export declare function knownSessionBrowsers(projectPath: string): string[];
|
|
7
|
+
export declare function resolveSessionBrowser(projectPath: string, explicit: string | undefined, fallback?: string): ResolvedBrowser;
|
package/dist/src/tools/eval.d.ts
CHANGED
package/dist/src/tools/open.d.ts
CHANGED
package/dist/src/tools/wait.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@extension.dev/mcp",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "4.2.
|
|
4
|
+
"version": "4.2.2",
|
|
5
5
|
"description": "MCP server that lets AI agents build, run, inspect, and publish browser extensions. 28 tools for scaffolding, live DOM inspection, log streaming, and store-ready builds across Chrome, Edge, and Firefox.",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": {
|