@extension.dev/mcp 4.1.2 → 4.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -0
- package/README.md +13 -2
- package/bin/extension-mcp.js +0 -0
- package/claude/rules/mcp-tools.md +42 -0
- package/dist/module.js +335 -77
- package/dist/src/index.d.ts +9 -0
- package/dist/src/lib/cdp-port.d.ts +7 -0
- package/dist/src/lib/exec.d.ts +16 -0
- package/dist/src/lib/types.d.ts +1 -1
- package/dist/src/lib/validate-input.d.ts +11 -0
- package/dist/src/tools/create.d.ts +5 -0
- package/dist/src/tools/stop.d.ts +29 -0
- package/package.json +13 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,32 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.2.0
|
|
4
|
+
|
|
5
|
+
Session lifecycle + determinism release. Tool count 27 -> 28.
|
|
6
|
+
|
|
7
|
+
- New tool `extension_stop`: terminates a dev/start/preview session (dev
|
|
8
|
+
server AND the browser it launched) via a process-group signal with
|
|
9
|
+
SIGTERM -> SIGKILL escalation. Finds the pid in the in-memory session
|
|
10
|
+
registry, falling back to the `ready.json` contract when the MCP server
|
|
11
|
+
restarted since the session began, and removes the stale contract so
|
|
12
|
+
`extension_wait` cannot report a dead session as ready. Supports
|
|
13
|
+
`all: true` to stop everything the server started.
|
|
14
|
+
- Sessions self-clean: dev/start/preview register an exit listener so a
|
|
15
|
+
session that dies on its own is no longer reported as stoppable.
|
|
16
|
+
`extension_preview` sessions are now registered (and stoppable) too.
|
|
17
|
+
- `extension_create` gains `parentDir`: control where the project lands
|
|
18
|
+
instead of inheriting the MCP server's working directory. `nextSteps`
|
|
19
|
+
now reports the full project path.
|
|
20
|
+
- CLI spawns are deterministic: dev/start/preview and the act tools now
|
|
21
|
+
prefer the project's own `node_modules/.bin/extension`, falling back to
|
|
22
|
+
`npx extension@<pinned>` where the pin derives from the vendored
|
|
23
|
+
`extension-develop` version — never a floating `latest`.
|
|
24
|
+
- Session registry keys are path-normalized, so a stop with an absolute
|
|
25
|
+
path matches a session registered with a relative one.
|
|
26
|
+
- Tests: registry suite now asserts against the exported `tools` array
|
|
27
|
+
(the old hand-maintained mirror had drifted to 26 while the server
|
|
28
|
+
registered 27); new stop + CLI-resolution suites (123 tests total).
|
|
29
|
+
|
|
3
30
|
## 4.1.2
|
|
4
31
|
|
|
5
32
|
README: restore the `@extension.dev/skill` pairing section (hands +
|
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.
|
|
10
|
+
> Give your AI agent hands for browser extension development. 28 MCP tools that scaffold, run, inspect, debug, and publish cross-browser extensions.
|
|
11
11
|
|
|
12
12
|
<img alt="Logo" align="right" src="https://avatars.githubusercontent.com/u/106714027" width="20%" />
|
|
13
13
|
|
|
@@ -89,6 +89,7 @@ cp node_modules/@extension.dev/mcp/claude/commands/*.md ~/my-extension/.claude/c
|
|
|
89
89
|
| run | `extension_start` | Build + preview |
|
|
90
90
|
| run | `extension_preview` | Preview the production build |
|
|
91
91
|
| run | `extension_wait` | Poll the dev-server ready contract |
|
|
92
|
+
| run | `extension_stop` | Stop a dev/start/preview session (server + browser) |
|
|
92
93
|
| see | `extension_manifest_validate` | Cross-browser manifest validation |
|
|
93
94
|
| see | `extension_inspect` | Build output analysis |
|
|
94
95
|
| see | `extension_source_inspect` | Live DOM inspection (CDP) |
|
|
@@ -108,12 +109,22 @@ cp node_modules/@extension.dev/mcp/claude/commands/*.md ~/my-extension/.claude/c
|
|
|
108
109
|
| platform | `extension_publish` | Publish a shareable preview to extension.dev |
|
|
109
110
|
| platform | `extension_release_promote` | Promote a build to a release channel, headless |
|
|
110
111
|
|
|
111
|
-
Browser-launching tools (`dev`, `start`, `preview`) shell out to the `extension` CLI; everything else runs in-process.
|
|
112
|
+
Browser-launching tools (`dev`, `start`, `preview`) shell out to the `extension` CLI — the project's own `node_modules/.bin/extension` when present, otherwise `npx extension@<pinned>` at the version this package is verified against; everything else runs in-process.
|
|
112
113
|
|
|
113
114
|
## From preview to store
|
|
114
115
|
|
|
115
116
|
The four platform tools connect agents to [extension.dev](https://extension.dev): `extension_login` runs a GitHub device-code flow and stores a project-scoped token locally (never returned to the agent), `extension_publish` turns a build into a shareable preview URL, and `extension_release_promote` promotes a tested build to a release channel from CI or an agent session, no browser required. To automate store submission itself (Chrome Web Store, Edge Add-ons, Firefox AMO), pair with [`@extension.dev/deploy`](https://www.npmjs.com/package/@extension.dev/deploy).
|
|
116
117
|
|
|
118
|
+
## The extension.dev stack
|
|
119
|
+
|
|
120
|
+
| Package | Use it to |
|
|
121
|
+
| --- | --- |
|
|
122
|
+
| [`@extension.dev/skill`](https://www.npmjs.com/package/@extension.dev/skill) | Teach AI agents the judgment half: cross-browser rules, gotchas, playbooks |
|
|
123
|
+
| [`@extension.dev/deploy`](https://www.npmjs.com/package/@extension.dev/deploy) | Ship to Chrome, Firefox, and Edge stores from CI |
|
|
124
|
+
| [`@extension.dev/core`](https://www.npmjs.com/package/@extension.dev/core) | Authenticate and publish to the extension.dev platform |
|
|
125
|
+
|
|
126
|
+
All of it rides on [Extension.js](https://github.com/extension-js/extension.js), the open-source cross-browser extension framework.
|
|
127
|
+
|
|
117
128
|
## Community
|
|
118
129
|
|
|
119
130
|
- Join the [Discord](https://discord.gg/v9h2RgeTSN) for help and feedback
|
package/bin/extension-mcp.js
CHANGED
|
File without changes
|
|
@@ -612,6 +612,47 @@ The `similarTemplates` field lists templates from the catalog with similar surfa
|
|
|
612
612
|
|
|
613
613
|
---
|
|
614
614
|
|
|
615
|
+
#### `extension_stop`
|
|
616
|
+
|
|
617
|
+
**Source:** MCP `lib/process-manager` + the `ready.json` contract (no CLI verb)
|
|
618
|
+
|
|
619
|
+
**Purpose:** Terminate a running dev/start/preview session — the dev server AND the browser it launched. The lifecycle counterpart to `extension_dev`/`extension_start`.
|
|
620
|
+
|
|
621
|
+
```json
|
|
622
|
+
{
|
|
623
|
+
"name": "extension_stop",
|
|
624
|
+
"description": "Stop a running dev, start, or preview session: terminates the dev server and the browser it launched.",
|
|
625
|
+
"inputSchema": {
|
|
626
|
+
"type": "object",
|
|
627
|
+
"properties": {
|
|
628
|
+
"projectPath": {
|
|
629
|
+
"type": "string",
|
|
630
|
+
"description": "Path to the extension project root"
|
|
631
|
+
},
|
|
632
|
+
"browser": {
|
|
633
|
+
"type": "string",
|
|
634
|
+
"default": "chrome",
|
|
635
|
+
"description": "Browser of the session to stop"
|
|
636
|
+
},
|
|
637
|
+
"all": {
|
|
638
|
+
"type": "boolean",
|
|
639
|
+
"default": false,
|
|
640
|
+
"description": "Stop every session this server started"
|
|
641
|
+
}
|
|
642
|
+
},
|
|
643
|
+
"required": []
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
```
|
|
647
|
+
|
|
648
|
+
**Returns:** `{ projectPath, browser, pid, stopped, detail }` (or `{ stopped: [...] }` with `all: true`).
|
|
649
|
+
|
|
650
|
+
**How it finds the process:** the in-memory session registry first; if the MCP server restarted since the session began, it falls back to the `pid` recorded in the `ready.json` contract. It signals the whole process group (sessions are spawned detached), escalates SIGTERM → SIGKILL, and removes the stale `ready.json` so a later `extension_wait` cannot report a dead session as ready.
|
|
651
|
+
|
|
652
|
+
**Why this matters for MCP:** without a stop tool, every `extension_dev` call leaks a dev server and a browser window that outlive the agent's task. Agents should stop sessions when verification is done.
|
|
653
|
+
|
|
654
|
+
---
|
|
655
|
+
|
|
615
656
|
### Tier 3 — Browser management tools
|
|
616
657
|
|
|
617
658
|
#### `extension_install_browser`
|
|
@@ -721,6 +762,7 @@ The `similarTemplates` field lists templates from the catalog with similar surfa
|
|
|
721
762
|
| `extension_source_inspect` | `programs/extension` | CDP client / RDP transport | Live browser via debugging protocol | Wire to running session |
|
|
722
763
|
| `extension_list_extensions` | MCP `lib/cdp` | `Extensions.getExtensionInfo` (read-only) | Live browser via CDP (Chromium) | MCP tool (no CLI verb) |
|
|
723
764
|
| `extension_wait` | `programs/extension` | `dev-wait.ts` | `ready.json` contract file | Thin wrapper (exists in CLI) |
|
|
765
|
+
| `extension_stop` | MCP `lib/process-manager` | session registry + group signal | Session registry + `ready.json` pid | MCP tool (no CLI verb) |
|
|
724
766
|
| `extension_add_feature` | New | `extension_get_template_source` | examples repo patterns | Codegen from examples |
|
|
725
767
|
| **Agent bridge — act / triggers** | | | | |
|
|
726
768
|
| `extension_eval` | `programs/extension` | bridge control channel | Live extension context | Wraps `extension eval` (`--allow-eval`) |
|
package/dist/module.js
CHANGED
|
@@ -2,15 +2,15 @@ import { __webpack_require__ } from "./rslib-runtime.js";
|
|
|
2
2
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
3
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
4
|
import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import node_path, { join } from "node:path";
|
|
5
6
|
import { extensionCreate } from "extension-create";
|
|
6
7
|
import node_fs, { existsSync, readdirSync, statSync } from "node:fs";
|
|
7
|
-
import node_path, { join } from "node:path";
|
|
8
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";
|
|
@@ -31,7 +31,7 @@ var create_namespaceObject = {};
|
|
|
31
31
|
__webpack_require__.r(create_namespaceObject);
|
|
32
32
|
__webpack_require__.d(create_namespaceObject, {
|
|
33
33
|
handler: ()=>create_handler,
|
|
34
|
-
schema: ()=>
|
|
34
|
+
schema: ()=>create_schema
|
|
35
35
|
});
|
|
36
36
|
var detect_browsers_namespaceObject = {};
|
|
37
37
|
__webpack_require__.r(detect_browsers_namespaceObject);
|
|
@@ -159,6 +159,12 @@ __webpack_require__.d(start_namespaceObject, {
|
|
|
159
159
|
handler: ()=>start_handler,
|
|
160
160
|
schema: ()=>start_schema
|
|
161
161
|
});
|
|
162
|
+
var stop_namespaceObject = {};
|
|
163
|
+
__webpack_require__.r(stop_namespaceObject);
|
|
164
|
+
__webpack_require__.d(stop_namespaceObject, {
|
|
165
|
+
handler: ()=>stop_handler,
|
|
166
|
+
schema: ()=>stop_schema
|
|
167
|
+
});
|
|
162
168
|
var storage_namespaceObject = {};
|
|
163
169
|
__webpack_require__.r(storage_namespaceObject);
|
|
164
170
|
__webpack_require__.d(storage_namespaceObject, {
|
|
@@ -177,10 +183,8 @@ __webpack_require__.d(whoami_namespaceObject, {
|
|
|
177
183
|
handler: ()=>whoami_handler,
|
|
178
184
|
schema: ()=>whoami_schema
|
|
179
185
|
});
|
|
180
|
-
var package_namespaceObject = {
|
|
181
|
-
|
|
182
|
-
};
|
|
183
|
-
const schema = {
|
|
186
|
+
var package_namespaceObject = JSON.parse('{"rE":"4.2.1","El":{"OP":"^4.0.8"}}');
|
|
187
|
+
const create_schema = {
|
|
184
188
|
name: "extension_create",
|
|
185
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.",
|
|
186
190
|
inputSchema: {
|
|
@@ -190,6 +194,10 @@ const schema = {
|
|
|
190
194
|
type: "string",
|
|
191
195
|
description: "Name of the extension project (used as directory name)"
|
|
192
196
|
},
|
|
197
|
+
parentDir: {
|
|
198
|
+
type: "string",
|
|
199
|
+
description: "Directory to create the project inside. Defaults to the MCP server's working directory, which may not be where you expect — pass this explicitly when you care where the project lands."
|
|
200
|
+
},
|
|
193
201
|
template: {
|
|
194
202
|
type: "string",
|
|
195
203
|
default: "typescript",
|
|
@@ -208,8 +216,9 @@ const schema = {
|
|
|
208
216
|
};
|
|
209
217
|
async function create_handler(args) {
|
|
210
218
|
const start = Date.now();
|
|
219
|
+
const projectInput = args.parentDir ? node_path.resolve(args.parentDir, args.projectName) : args.projectName;
|
|
211
220
|
try {
|
|
212
|
-
const result = await extensionCreate(
|
|
221
|
+
const result = await extensionCreate(projectInput, {
|
|
213
222
|
template: args.template ?? "typescript",
|
|
214
223
|
install: args.install ?? true,
|
|
215
224
|
logger: {
|
|
@@ -224,7 +233,7 @@ async function create_handler(args) {
|
|
|
224
233
|
depsInstalled: result.depsInstalled,
|
|
225
234
|
duration: Date.now() - start,
|
|
226
235
|
nextSteps: [
|
|
227
|
-
`cd ${result.
|
|
236
|
+
`cd ${result.projectPath}`,
|
|
228
237
|
"npm run dev"
|
|
229
238
|
]
|
|
230
239
|
});
|
|
@@ -392,7 +401,8 @@ async function build_handler(args) {
|
|
|
392
401
|
const summary = await extensionBuild(args.projectPath, {
|
|
393
402
|
browser: args.browser ?? "chrome",
|
|
394
403
|
zip: args.zip ?? false,
|
|
395
|
-
zipSource: args.zipSource ?? false
|
|
404
|
+
zipSource: args.zipSource ?? false,
|
|
405
|
+
exitOnError: false
|
|
396
406
|
});
|
|
397
407
|
return JSON.stringify({
|
|
398
408
|
success: true,
|
|
@@ -407,10 +417,34 @@ async function build_handler(args) {
|
|
|
407
417
|
});
|
|
408
418
|
}
|
|
409
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
|
+
}
|
|
425
|
+
function resolveExtensionInvocation(projectDir) {
|
|
426
|
+
if (projectDir) {
|
|
427
|
+
const bin = node_path.join(projectDir, "node_modules", ".bin", "win32" === process.platform ? "extension.cmd" : "extension");
|
|
428
|
+
try {
|
|
429
|
+
node_fs.accessSync(bin, node_fs.constants.X_OK);
|
|
430
|
+
return {
|
|
431
|
+
command: bin,
|
|
432
|
+
prefixArgs: []
|
|
433
|
+
};
|
|
434
|
+
} catch {}
|
|
435
|
+
}
|
|
436
|
+
return {
|
|
437
|
+
command: "npx",
|
|
438
|
+
prefixArgs: [
|
|
439
|
+
`extension@${pinnedCliVersion()}`
|
|
440
|
+
]
|
|
441
|
+
};
|
|
442
|
+
}
|
|
410
443
|
function runExtensionCli(args, options) {
|
|
444
|
+
const { command, prefixArgs } = resolveExtensionInvocation(options?.cwd);
|
|
411
445
|
return new Promise((resolve)=>{
|
|
412
|
-
const child = cross_spawn(
|
|
413
|
-
|
|
446
|
+
const child = cross_spawn(command, [
|
|
447
|
+
...prefixArgs,
|
|
414
448
|
...args
|
|
415
449
|
], {
|
|
416
450
|
cwd: options?.cwd,
|
|
@@ -443,8 +477,9 @@ function runExtensionCli(args, options) {
|
|
|
443
477
|
});
|
|
444
478
|
}
|
|
445
479
|
function spawnExtensionCli(args, options) {
|
|
446
|
-
const
|
|
447
|
-
|
|
480
|
+
const { command, prefixArgs } = resolveExtensionInvocation(options?.projectDir ?? options?.cwd);
|
|
481
|
+
const child = cross_spawn(command, [
|
|
482
|
+
...prefixArgs,
|
|
448
483
|
...args
|
|
449
484
|
], {
|
|
450
485
|
cwd: options?.cwd,
|
|
@@ -463,12 +498,21 @@ function spawnExtensionCli(args, options) {
|
|
|
463
498
|
child.unref();
|
|
464
499
|
return child;
|
|
465
500
|
}
|
|
466
|
-
const
|
|
501
|
+
const process_manager_sessions = new Map();
|
|
467
502
|
function sessionKey(projectPath, browser) {
|
|
468
|
-
return `${projectPath}::${browser}`;
|
|
503
|
+
return `${node_path.resolve(projectPath)}::${browser}`;
|
|
469
504
|
}
|
|
470
505
|
function registerSession(info) {
|
|
471
|
-
|
|
506
|
+
process_manager_sessions.set(sessionKey(info.projectPath, info.browser), info);
|
|
507
|
+
}
|
|
508
|
+
function getSession(projectPath, browser) {
|
|
509
|
+
return process_manager_sessions.get(sessionKey(projectPath, browser));
|
|
510
|
+
}
|
|
511
|
+
function removeSession(projectPath, browser) {
|
|
512
|
+
process_manager_sessions.delete(sessionKey(projectPath, browser));
|
|
513
|
+
}
|
|
514
|
+
function listSessions() {
|
|
515
|
+
return Array.from(process_manager_sessions.values());
|
|
472
516
|
}
|
|
473
517
|
const dev_schema = {
|
|
474
518
|
name: "extension_dev",
|
|
@@ -528,7 +572,9 @@ async function dev_handler(args) {
|
|
|
528
572
|
if (args.noBrowser) cliArgs.push("--no-browser");
|
|
529
573
|
if (args.allowControl) cliArgs.push("--allow-control");
|
|
530
574
|
if (args.allowEval) cliArgs.push("--allow-eval");
|
|
531
|
-
const child = spawnExtensionCli(cliArgs
|
|
575
|
+
const child = spawnExtensionCli(cliArgs, {
|
|
576
|
+
projectDir: args.projectPath
|
|
577
|
+
});
|
|
532
578
|
const pid = child.pid;
|
|
533
579
|
registerSession({
|
|
534
580
|
pid,
|
|
@@ -537,6 +583,7 @@ async function dev_handler(args) {
|
|
|
537
583
|
projectPath: args.projectPath,
|
|
538
584
|
command: "dev"
|
|
539
585
|
});
|
|
586
|
+
child.on("exit", ()=>removeSession(args.projectPath, browser));
|
|
540
587
|
let earlyOutput = "";
|
|
541
588
|
const collector = (data)=>{
|
|
542
589
|
earlyOutput += data.toString();
|
|
@@ -552,7 +599,7 @@ async function dev_handler(args) {
|
|
|
552
599
|
port: args.port ?? 8080,
|
|
553
600
|
projectPath: args.projectPath,
|
|
554
601
|
status: "started",
|
|
555
|
-
hint: "Use extension_wait to check when the extension is fully loaded, then extension_source_inspect to inspect the live state.",
|
|
602
|
+
hint: "Use extension_wait to check when the extension is fully loaded, then extension_source_inspect to inspect the live state. When you are done, call extension_stop to shut down the dev server and browser.",
|
|
556
603
|
earlyOutput: earlyOutput.slice(0, 500)
|
|
557
604
|
});
|
|
558
605
|
}
|
|
@@ -597,7 +644,9 @@ async function start_handler(args) {
|
|
|
597
644
|
browser
|
|
598
645
|
];
|
|
599
646
|
if (false === args.polyfill) cliArgs.push("--no-polyfill");
|
|
600
|
-
const child = spawnExtensionCli(cliArgs
|
|
647
|
+
const child = spawnExtensionCli(cliArgs, {
|
|
648
|
+
projectDir: args.projectPath
|
|
649
|
+
});
|
|
601
650
|
const pid = child.pid;
|
|
602
651
|
registerSession({
|
|
603
652
|
pid,
|
|
@@ -605,6 +654,7 @@ async function start_handler(args) {
|
|
|
605
654
|
projectPath: args.projectPath,
|
|
606
655
|
command: "start"
|
|
607
656
|
});
|
|
657
|
+
child.on("exit", ()=>removeSession(args.projectPath, browser));
|
|
608
658
|
let earlyOutput = "";
|
|
609
659
|
const collector = (data)=>{
|
|
610
660
|
earlyOutput += data.toString();
|
|
@@ -619,7 +669,7 @@ async function start_handler(args) {
|
|
|
619
669
|
browser,
|
|
620
670
|
projectPath: args.projectPath,
|
|
621
671
|
status: "started",
|
|
622
|
-
hint: "Use extension_wait to check when the build and browser launch are complete.",
|
|
672
|
+
hint: "Use extension_wait to check when the build and browser launch are complete. When you are done, call extension_stop to shut down the session.",
|
|
623
673
|
earlyOutput: earlyOutput.slice(0, 500)
|
|
624
674
|
});
|
|
625
675
|
}
|
|
@@ -658,13 +708,134 @@ async function preview_handler(args) {
|
|
|
658
708
|
"--browser",
|
|
659
709
|
browser
|
|
660
710
|
];
|
|
661
|
-
const child = spawnExtensionCli(cliArgs
|
|
711
|
+
const child = spawnExtensionCli(cliArgs, {
|
|
712
|
+
projectDir: args.projectPath
|
|
713
|
+
});
|
|
714
|
+
const pid = child.pid;
|
|
715
|
+
registerSession({
|
|
716
|
+
pid,
|
|
717
|
+
browser,
|
|
718
|
+
projectPath: args.projectPath,
|
|
719
|
+
command: "preview"
|
|
720
|
+
});
|
|
721
|
+
child.on("exit", ()=>removeSession(args.projectPath, browser));
|
|
662
722
|
return JSON.stringify({
|
|
663
|
-
pid
|
|
723
|
+
pid,
|
|
664
724
|
browser,
|
|
665
725
|
projectPath: args.projectPath,
|
|
666
|
-
status: "launched"
|
|
726
|
+
status: "launched",
|
|
727
|
+
hint: "Call extension_stop when you are done to close the preview browser."
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
const stop_schema = {
|
|
731
|
+
name: "extension_stop",
|
|
732
|
+
description: "Stop a running dev, start, or preview session: terminates the dev server and the browser it launched. Counterpart to extension_dev/extension_start. Call it when you are done verifying so sessions do not accumulate.",
|
|
733
|
+
inputSchema: {
|
|
734
|
+
type: "object",
|
|
735
|
+
properties: {
|
|
736
|
+
projectPath: {
|
|
737
|
+
type: "string",
|
|
738
|
+
description: "Path to the extension project root"
|
|
739
|
+
},
|
|
740
|
+
browser: {
|
|
741
|
+
type: "string",
|
|
742
|
+
default: "chrome",
|
|
743
|
+
description: "Browser of the session to stop (matches the browser passed to extension_dev/extension_start)"
|
|
744
|
+
},
|
|
745
|
+
all: {
|
|
746
|
+
type: "boolean",
|
|
747
|
+
default: false,
|
|
748
|
+
description: "Stop every session this server started, across projects and browsers. When true, projectPath/browser are ignored."
|
|
749
|
+
}
|
|
750
|
+
},
|
|
751
|
+
required: []
|
|
752
|
+
}
|
|
753
|
+
};
|
|
754
|
+
function isAlive(pid) {
|
|
755
|
+
try {
|
|
756
|
+
process.kill(pid, 0);
|
|
757
|
+
return true;
|
|
758
|
+
} catch {
|
|
759
|
+
return false;
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
function signal(pid, sig) {
|
|
763
|
+
try {
|
|
764
|
+
process.kill(-pid, sig);
|
|
765
|
+
return true;
|
|
766
|
+
} catch {
|
|
767
|
+
try {
|
|
768
|
+
process.kill(pid, sig);
|
|
769
|
+
return true;
|
|
770
|
+
} catch {
|
|
771
|
+
return false;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
}
|
|
775
|
+
function readyJsonPath(projectPath, browser) {
|
|
776
|
+
return node_path.resolve(projectPath, "dist", "extension-js", browser, "ready.json");
|
|
777
|
+
}
|
|
778
|
+
function pidFromReadyContract(projectPath, browser) {
|
|
779
|
+
try {
|
|
780
|
+
const raw = node_fs.readFileSync(readyJsonPath(projectPath, browser), "utf8");
|
|
781
|
+
const contract = JSON.parse(raw);
|
|
782
|
+
return "number" == typeof contract.pid ? contract.pid : null;
|
|
783
|
+
} catch {
|
|
784
|
+
return null;
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
async function stopOne(projectPath, browser) {
|
|
788
|
+
const session = getSession(projectPath, browser);
|
|
789
|
+
const pid = session?.pid ?? pidFromReadyContract(projectPath, browser);
|
|
790
|
+
if (null == pid) return {
|
|
791
|
+
projectPath,
|
|
792
|
+
browser,
|
|
793
|
+
pid: null,
|
|
794
|
+
stopped: false,
|
|
795
|
+
detail: "No known session for this project/browser (nothing registered in this server and no ready.json contract found)."
|
|
796
|
+
};
|
|
797
|
+
let detail;
|
|
798
|
+
if (isAlive(pid)) {
|
|
799
|
+
signal(pid, "SIGTERM");
|
|
800
|
+
await new Promise((resolve)=>setTimeout(resolve, 1500));
|
|
801
|
+
if (isAlive(pid)) {
|
|
802
|
+
signal(pid, "SIGKILL");
|
|
803
|
+
await new Promise((resolve)=>setTimeout(resolve, 250));
|
|
804
|
+
}
|
|
805
|
+
detail = isAlive(pid) ? "Sent SIGTERM and SIGKILL but the process still reports alive; it may be exiting." : "Terminated.";
|
|
806
|
+
} else detail = "Process was already gone; cleaned up session records.";
|
|
807
|
+
removeSession(projectPath, browser);
|
|
808
|
+
try {
|
|
809
|
+
node_fs.rmSync(readyJsonPath(projectPath, browser), {
|
|
810
|
+
force: true
|
|
811
|
+
});
|
|
812
|
+
} catch {}
|
|
813
|
+
return {
|
|
814
|
+
projectPath,
|
|
815
|
+
browser,
|
|
816
|
+
pid,
|
|
817
|
+
stopped: !isAlive(pid),
|
|
818
|
+
detail
|
|
819
|
+
};
|
|
820
|
+
}
|
|
821
|
+
async function stop_handler(args) {
|
|
822
|
+
if (args.all) {
|
|
823
|
+
const sessions = listSessions();
|
|
824
|
+
if (0 === sessions.length) return JSON.stringify({
|
|
825
|
+
stopped: [],
|
|
826
|
+
message: "No sessions registered in this server."
|
|
827
|
+
});
|
|
828
|
+
const outcomes = [];
|
|
829
|
+
for (const s of sessions)outcomes.push(await stopOne(s.projectPath, s.browser));
|
|
830
|
+
return JSON.stringify({
|
|
831
|
+
stopped: outcomes
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
if (!args.projectPath) return JSON.stringify({
|
|
835
|
+
error: "projectPath is required unless all=true. Pass the same projectPath used with extension_dev/extension_start."
|
|
667
836
|
});
|
|
837
|
+
const outcome = await stopOne(args.projectPath, args.browser ?? "chrome");
|
|
838
|
+
return JSON.stringify(outcome);
|
|
668
839
|
}
|
|
669
840
|
const RAW_BASE = "https://raw.githubusercontent.com/extension-js/examples/main/examples";
|
|
670
841
|
const get_template_source_schema = {
|
|
@@ -1405,6 +1576,50 @@ class CDPClient extends CDPConnection {
|
|
|
1405
1576
|
return result ?? null;
|
|
1406
1577
|
}
|
|
1407
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
|
+
}
|
|
1408
1623
|
const source_inspect_schema = {
|
|
1409
1624
|
name: "extension_source_inspect",
|
|
1410
1625
|
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.",
|
|
@@ -1466,28 +1681,6 @@ const source_inspect_schema = {
|
|
|
1466
1681
|
]
|
|
1467
1682
|
}
|
|
1468
1683
|
};
|
|
1469
|
-
async function findCdpPort(projectPath, browser) {
|
|
1470
|
-
const readyPath = node_path.resolve(projectPath, "dist", "extension-js", browser, "ready.json");
|
|
1471
|
-
try {
|
|
1472
|
-
const contract = JSON.parse(node_fs.readFileSync(readyPath, "utf8"));
|
|
1473
|
-
if ("number" == typeof contract.cdpPort) return contract.cdpPort;
|
|
1474
|
-
} catch {}
|
|
1475
|
-
const defaultPort = 9222;
|
|
1476
|
-
return new Promise((resolve)=>{
|
|
1477
|
-
const socket = new node_net.Socket();
|
|
1478
|
-
socket.setTimeout(1000);
|
|
1479
|
-
socket.on("connect", ()=>{
|
|
1480
|
-
socket.destroy();
|
|
1481
|
-
resolve(defaultPort);
|
|
1482
|
-
});
|
|
1483
|
-
socket.on("error", ()=>resolve(null));
|
|
1484
|
-
socket.on("timeout", ()=>{
|
|
1485
|
-
socket.destroy();
|
|
1486
|
-
resolve(null);
|
|
1487
|
-
});
|
|
1488
|
-
socket.connect(defaultPort, "127.0.0.1");
|
|
1489
|
-
});
|
|
1490
|
-
}
|
|
1491
1684
|
async function source_inspect_handler(args) {
|
|
1492
1685
|
const browser = args.browser ?? "chrome";
|
|
1493
1686
|
const include = new Set(args.include ?? [
|
|
@@ -1505,11 +1698,12 @@ async function source_inspect_handler(args) {
|
|
|
1505
1698
|
error: `Source inspection for ${browser} uses RDP (Remote Debug Protocol). Currently only Chromium CDP is supported.`,
|
|
1506
1699
|
hint: "Use --browser=chrome for source inspection, or use the CLI: npx extension dev --source"
|
|
1507
1700
|
});
|
|
1508
|
-
const
|
|
1509
|
-
if (!
|
|
1701
|
+
const resolved = await resolveCdpPort(args.projectPath, browser);
|
|
1702
|
+
if (!resolved) return JSON.stringify({
|
|
1510
1703
|
error: "No active dev session found. Cannot connect to Chrome DevTools Protocol.",
|
|
1511
|
-
hint:
|
|
1704
|
+
hint: `Start a dev session first with extension_dev, then use extension_wait to confirm it is ready. ${CDP_PORT_MISSING_HINT}`
|
|
1512
1705
|
});
|
|
1706
|
+
const cdpPort = resolved.port;
|
|
1513
1707
|
const cdp = new CDPClient();
|
|
1514
1708
|
try {
|
|
1515
1709
|
const allTargets = await CDPClient.discoverTargets(cdpPort);
|
|
@@ -1622,16 +1816,27 @@ async function list_extensions_handler(args) {
|
|
|
1622
1816
|
error: `Listing extensions for ${browser} uses RDP (Remote Debug Protocol). Currently only Chromium CDP is supported.`,
|
|
1623
1817
|
hint: "Use --browser=chrome."
|
|
1624
1818
|
});
|
|
1625
|
-
const
|
|
1626
|
-
if (!
|
|
1819
|
+
const resolved = await resolveCdpPort(args.projectPath, browser);
|
|
1820
|
+
if (!resolved) return JSON.stringify({
|
|
1627
1821
|
error: "No active dev session found. Cannot connect to Chrome DevTools Protocol.",
|
|
1628
|
-
hint:
|
|
1822
|
+
hint: `Start a dev session first with extension_dev, then use extension_wait to confirm it is ready. ${CDP_PORT_MISSING_HINT}`
|
|
1629
1823
|
});
|
|
1824
|
+
const cdpPort = resolved.port;
|
|
1630
1825
|
const cdp = new CDPClient();
|
|
1631
1826
|
try {
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1827
|
+
let targets = null;
|
|
1828
|
+
let lastError = null;
|
|
1829
|
+
for(let attempt = 0; attempt < 3; attempt++)try {
|
|
1830
|
+
const browserWsUrl = await CDPClient.discoverBrowserWsUrl(cdpPort);
|
|
1831
|
+
await cdp.connect(browserWsUrl);
|
|
1832
|
+
targets = await cdp.getTargets();
|
|
1833
|
+
break;
|
|
1834
|
+
} catch (error) {
|
|
1835
|
+
lastError = error;
|
|
1836
|
+
cdp.disconnect();
|
|
1837
|
+
if (attempt < 2) await new Promise((resolve)=>setTimeout(resolve, 800));
|
|
1838
|
+
}
|
|
1839
|
+
if (!targets) throw lastError;
|
|
1635
1840
|
const byId = new Map();
|
|
1636
1841
|
for (const t of targets){
|
|
1637
1842
|
const url = String(t.url ?? "");
|
|
@@ -1683,28 +1888,6 @@ async function list_extensions_handler(args) {
|
|
|
1683
1888
|
cdp.disconnect();
|
|
1684
1889
|
}
|
|
1685
1890
|
}
|
|
1686
|
-
async function list_extensions_findCdpPort(projectPath, browser) {
|
|
1687
|
-
const readyPath = node_path.resolve(projectPath, "dist", "extension-js", browser, "ready.json");
|
|
1688
|
-
try {
|
|
1689
|
-
const contract = JSON.parse(node_fs.readFileSync(readyPath, "utf8"));
|
|
1690
|
-
if ("number" == typeof contract.cdpPort) return contract.cdpPort;
|
|
1691
|
-
} catch {}
|
|
1692
|
-
const defaultPort = 9222;
|
|
1693
|
-
return new Promise((resolve)=>{
|
|
1694
|
-
const socket = new node_net.Socket();
|
|
1695
|
-
socket.setTimeout(1000);
|
|
1696
|
-
socket.on("connect", ()=>{
|
|
1697
|
-
socket.destroy();
|
|
1698
|
-
resolve(defaultPort);
|
|
1699
|
-
});
|
|
1700
|
-
socket.on("error", ()=>resolve(null));
|
|
1701
|
-
socket.on("timeout", ()=>{
|
|
1702
|
-
socket.destroy();
|
|
1703
|
-
resolve(null);
|
|
1704
|
-
});
|
|
1705
|
-
socket.connect(defaultPort, "127.0.0.1");
|
|
1706
|
-
});
|
|
1707
|
-
}
|
|
1708
1891
|
const CONTROL_WS_PATH = "/extjs-control";
|
|
1709
1892
|
const LEVEL_ORDER = [
|
|
1710
1893
|
"error",
|
|
@@ -3269,6 +3452,70 @@ async function detect_browsers_handler(args) {
|
|
|
3269
3452
|
hint: missing.length ? `Missing browser(s): ${missing.map((d)=>d.browser).join(", ")}. Use extension_install_browser to install them.` : "All requested browsers are available."
|
|
3270
3453
|
});
|
|
3271
3454
|
}
|
|
3455
|
+
function typeOf(value) {
|
|
3456
|
+
if (Array.isArray(value)) return "array";
|
|
3457
|
+
if (null === value) return "null";
|
|
3458
|
+
return typeof value;
|
|
3459
|
+
}
|
|
3460
|
+
function checkPrimitive(path, value, schema, issues) {
|
|
3461
|
+
if (schema.type && [
|
|
3462
|
+
"string",
|
|
3463
|
+
"number",
|
|
3464
|
+
"boolean"
|
|
3465
|
+
].includes(schema.type)) {
|
|
3466
|
+
if (typeOf(value) !== schema.type) return void issues.push({
|
|
3467
|
+
path,
|
|
3468
|
+
message: `expected ${schema.type}, got ${typeOf(value)}`
|
|
3469
|
+
});
|
|
3470
|
+
}
|
|
3471
|
+
if ("array" === schema.type) {
|
|
3472
|
+
if (!Array.isArray(value)) return void issues.push({
|
|
3473
|
+
path,
|
|
3474
|
+
message: `expected array, got ${typeOf(value)}`
|
|
3475
|
+
});
|
|
3476
|
+
const itemSchema = schema.items;
|
|
3477
|
+
if (itemSchema) value.forEach((item, i)=>checkPrimitive(`${path}[${i}]`, item, itemSchema, issues));
|
|
3478
|
+
return;
|
|
3479
|
+
}
|
|
3480
|
+
if (Array.isArray(schema.enum) && schema.enum.length) {
|
|
3481
|
+
if (!schema.enum.includes(value)) issues.push({
|
|
3482
|
+
path,
|
|
3483
|
+
message: `must be one of: ${schema.enum.map(String).join(", ")}`
|
|
3484
|
+
});
|
|
3485
|
+
}
|
|
3486
|
+
}
|
|
3487
|
+
function validateToolInput(inputSchema, args) {
|
|
3488
|
+
const schema = inputSchema;
|
|
3489
|
+
const issues = [];
|
|
3490
|
+
const properties = schema.properties ?? {};
|
|
3491
|
+
for (const key of schema.required ?? [])if (void 0 === args[key]) issues.push({
|
|
3492
|
+
path: key,
|
|
3493
|
+
message: "required argument is missing"
|
|
3494
|
+
});
|
|
3495
|
+
for (const [key, value] of Object.entries(args)){
|
|
3496
|
+
const propSchema = properties[key];
|
|
3497
|
+
if (!propSchema) {
|
|
3498
|
+
const known = Object.keys(properties);
|
|
3499
|
+
issues.push({
|
|
3500
|
+
path: key,
|
|
3501
|
+
message: `unknown argument${known.length ? ` (known: ${known.join(", ")})` : ""}`
|
|
3502
|
+
});
|
|
3503
|
+
continue;
|
|
3504
|
+
}
|
|
3505
|
+
if (void 0 !== value) checkPrimitive(key, value, propSchema, issues);
|
|
3506
|
+
}
|
|
3507
|
+
return issues;
|
|
3508
|
+
}
|
|
3509
|
+
function inputValidationError(toolName, issues) {
|
|
3510
|
+
return JSON.stringify({
|
|
3511
|
+
ok: false,
|
|
3512
|
+
error: {
|
|
3513
|
+
name: "InputValidationError",
|
|
3514
|
+
message: `Invalid arguments for ${toolName}: ${issues.map((i)=>`${i.path}: ${i.message}`).join("; ")}`,
|
|
3515
|
+
issues
|
|
3516
|
+
}
|
|
3517
|
+
});
|
|
3518
|
+
}
|
|
3272
3519
|
const tools = [
|
|
3273
3520
|
create_namespaceObject,
|
|
3274
3521
|
list_templates_namespaceObject,
|
|
@@ -3276,6 +3523,7 @@ const tools = [
|
|
|
3276
3523
|
dev_namespaceObject,
|
|
3277
3524
|
start_namespaceObject,
|
|
3278
3525
|
preview_namespaceObject,
|
|
3526
|
+
stop_namespaceObject,
|
|
3279
3527
|
get_template_source_namespaceObject,
|
|
3280
3528
|
manifest_validate_namespaceObject,
|
|
3281
3529
|
inspect_namespaceObject,
|
|
@@ -3331,6 +3579,16 @@ async function startServer() {
|
|
|
3331
3579
|
],
|
|
3332
3580
|
isError: true
|
|
3333
3581
|
};
|
|
3582
|
+
const issues = validateToolInput(tool.schema.inputSchema, args ?? {});
|
|
3583
|
+
if (issues.length) return {
|
|
3584
|
+
content: [
|
|
3585
|
+
{
|
|
3586
|
+
type: "text",
|
|
3587
|
+
text: inputValidationError(name, issues)
|
|
3588
|
+
}
|
|
3589
|
+
],
|
|
3590
|
+
isError: true
|
|
3591
|
+
};
|
|
3334
3592
|
try {
|
|
3335
3593
|
const result = await tool.handler(args ?? {});
|
|
3336
3594
|
return {
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
export interface ToolModule {
|
|
2
|
+
schema: {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: Record<string, unknown>;
|
|
6
|
+
};
|
|
7
|
+
handler: (args: any) => Promise<string>;
|
|
8
|
+
}
|
|
9
|
+
export declare const tools: ToolModule[];
|
|
1
10
|
export declare function startServer(): Promise<void>;
|
|
2
11
|
/**
|
|
3
12
|
* Human-facing subcommands for `extension-mcp <cmd>`. Unlike the MCP 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.";
|
package/dist/src/lib/exec.d.ts
CHANGED
|
@@ -4,10 +4,26 @@ export interface CliResult {
|
|
|
4
4
|
stdout: string;
|
|
5
5
|
stderr: string;
|
|
6
6
|
}
|
|
7
|
+
/**
|
|
8
|
+
* Resolve how to invoke the `extension` CLI for a given project.
|
|
9
|
+
*
|
|
10
|
+
* Preference order:
|
|
11
|
+
* 1. The project's own `node_modules/.bin/extension` — the version the project
|
|
12
|
+
* pinned is the single source of behavior for that project (lockstep
|
|
13
|
+
* invariant), and spawning it needs no network.
|
|
14
|
+
* 2. `npx extension@<pinned>` — pinned to the extension-develop version this
|
|
15
|
+
* package vendors, never a floating `latest`, so MCP behavior stays
|
|
16
|
+
* reproducible even without a project-local install.
|
|
17
|
+
*/
|
|
18
|
+
export declare function resolveExtensionInvocation(projectDir?: string): {
|
|
19
|
+
command: string;
|
|
20
|
+
prefixArgs: string[];
|
|
21
|
+
};
|
|
7
22
|
export declare function runExtensionCli(args: string[], options?: {
|
|
8
23
|
cwd?: string;
|
|
9
24
|
timeoutMs?: number;
|
|
10
25
|
}): Promise<CliResult>;
|
|
11
26
|
export declare function spawnExtensionCli(args: string[], options?: {
|
|
12
27
|
cwd?: string;
|
|
28
|
+
projectDir?: string;
|
|
13
29
|
}): ChildProcess;
|
package/dist/src/lib/types.d.ts
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface InputIssue {
|
|
2
|
+
path: string;
|
|
3
|
+
message: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Validate tools/call arguments against a tool inputSchema. Returns an empty
|
|
7
|
+
* array when the args are acceptable.
|
|
8
|
+
*/
|
|
9
|
+
export declare function validateToolInput(inputSchema: Record<string, unknown>, args: Record<string, unknown>): InputIssue[];
|
|
10
|
+
/** Frozen-style error envelope for input validation failures. */
|
|
11
|
+
export declare function inputValidationError(toolName: string, issues: InputIssue[]): string;
|
|
@@ -8,6 +8,10 @@ export declare const schema: {
|
|
|
8
8
|
type: string;
|
|
9
9
|
description: string;
|
|
10
10
|
};
|
|
11
|
+
parentDir: {
|
|
12
|
+
type: string;
|
|
13
|
+
description: string;
|
|
14
|
+
};
|
|
11
15
|
template: {
|
|
12
16
|
type: string;
|
|
13
17
|
default: string;
|
|
@@ -24,6 +28,7 @@ export declare const schema: {
|
|
|
24
28
|
};
|
|
25
29
|
export declare function handler(args: {
|
|
26
30
|
projectName: string;
|
|
31
|
+
parentDir?: string;
|
|
27
32
|
template?: string;
|
|
28
33
|
install?: boolean;
|
|
29
34
|
}): Promise<string>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export declare const schema: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object";
|
|
6
|
+
properties: {
|
|
7
|
+
projectPath: {
|
|
8
|
+
type: string;
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
browser: {
|
|
12
|
+
type: string;
|
|
13
|
+
default: string;
|
|
14
|
+
description: string;
|
|
15
|
+
};
|
|
16
|
+
all: {
|
|
17
|
+
type: string;
|
|
18
|
+
default: boolean;
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
21
|
+
};
|
|
22
|
+
required: never[];
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
export declare function handler(args: {
|
|
26
|
+
projectPath?: string;
|
|
27
|
+
browser?: string;
|
|
28
|
+
all?: boolean;
|
|
29
|
+
}): Promise<string>;
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@extension.dev/mcp",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "4.1
|
|
5
|
-
"description": "MCP server that lets AI agents build, run, inspect, and publish browser extensions.
|
|
4
|
+
"version": "4.2.1",
|
|
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": {
|
|
8
8
|
"name": "Cezar Augusto",
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
"engines": {
|
|
21
21
|
"node": ">=20.18"
|
|
22
22
|
},
|
|
23
|
+
"packageManager": "pnpm@10.19.0",
|
|
23
24
|
"exports": {
|
|
24
25
|
".": {
|
|
25
26
|
"types": "./dist/module.d.ts",
|
|
@@ -39,6 +40,15 @@
|
|
|
39
40
|
"CHANGELOG.md",
|
|
40
41
|
"LICENSE"
|
|
41
42
|
],
|
|
43
|
+
"scripts": {
|
|
44
|
+
"clean": "rm -rf dist",
|
|
45
|
+
"watch": "rslib build --watch",
|
|
46
|
+
"compile": "rslib build",
|
|
47
|
+
"build": "pnpm run compile",
|
|
48
|
+
"start": "node bin/extension-mcp.js",
|
|
49
|
+
"test": "vitest run",
|
|
50
|
+
"prepublishOnly": "pnpm run test && pnpm run compile"
|
|
51
|
+
},
|
|
42
52
|
"publishConfig": {
|
|
43
53
|
"access": "public",
|
|
44
54
|
"registry": "https://registry.npmjs.org"
|
|
@@ -77,13 +87,5 @@
|
|
|
77
87
|
"@types/ws": "^8.18.1",
|
|
78
88
|
"typescript": "6.0.2",
|
|
79
89
|
"vitest": "^4.1.3"
|
|
80
|
-
},
|
|
81
|
-
"scripts": {
|
|
82
|
-
"clean": "rm -rf dist",
|
|
83
|
-
"watch": "rslib build --watch",
|
|
84
|
-
"compile": "rslib build",
|
|
85
|
-
"build": "pnpm run compile",
|
|
86
|
-
"start": "node bin/extension-mcp.js",
|
|
87
|
-
"test": "vitest run"
|
|
88
90
|
}
|
|
89
|
-
}
|
|
91
|
+
}
|