@extension.dev/mcp 10.10.2 → 10.10.4
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 +55 -0
- package/README.md +94 -3
- package/bin/extension-mcp.js +15 -4
- package/dist/clients.js +157 -0
- package/dist/module.d.ts +1 -0
- package/dist/module.js +451 -72
- package/dist/src/clients/index.d.ts +31 -0
- package/dist/src/clients/readme.d.ts +4 -0
- package/dist/src/index.d.ts +3 -2
- package/dist/src/lib/approval-gate.d.ts +5 -1
- package/dist/src/lib/artifacts-api.d.ts +1 -0
- package/dist/src/lib/credentials.d.ts +4 -0
- package/dist/src/lib/envelope.d.ts +1 -1
- package/dist/src/lib/tool-policy.d.ts +39 -0
- package/package.json +9 -4
- 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.4",
|
|
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.4",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cezar Augusto",
|
|
7
7
|
"email": "hello@extension.dev",
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 10.10.4
|
|
4
|
+
|
|
5
|
+
- `--project <workspace>/<project>` (env `EXTENSION_DEV_PROJECT`) pins a
|
|
6
|
+
server to one project. Every unnamed credential read takes that
|
|
7
|
+
project's login instead of the most recent one, an agent signed in to
|
|
8
|
+
several projects can no longer act on the wrong one, and a call that
|
|
9
|
+
names a different project is refused with `status: "project-pinned"`.
|
|
10
|
+
A pinned project with no stored login falls back to
|
|
11
|
+
`EXTENSION_DEV_TOKEN`, so CI keeps working.
|
|
12
|
+
- New `@extension.dev/mcp/clients` export: `CLIENTS` and
|
|
13
|
+
`buildRecipe({ client, reach, strictApproval, project })` build the
|
|
14
|
+
exact setup for Claude Code, Cursor, VS Code, Codex and any
|
|
15
|
+
`mcpServers` client. The README's Setup section is generated from it
|
|
16
|
+
(`pnpm readme:clients`, checked by a spec), and the extension.dev
|
|
17
|
+
console's Connect dialog renders the same recipes, so the instructions
|
|
18
|
+
cannot drift. It never emits a setting that turns approvals off.
|
|
19
|
+
|
|
20
|
+
## 10.10.3
|
|
21
|
+
|
|
22
|
+
- The engine pin moves off the canary to Extension.js `4.1.31` stable:
|
|
23
|
+
`extension_dev`, `extension_start` and `extension_build` spawn that
|
|
24
|
+
release when a project has no engine of its own, and the nightly
|
|
25
|
+
engine-pin check reads it against `latest` again.
|
|
26
|
+
|
|
27
|
+
- A human now approves a real store submission and a promotion to stable
|
|
28
|
+
before either runs, by default. The first call answers
|
|
29
|
+
`approval-required` with an `approvalUrl` on extension.dev
|
|
30
|
+
(`/device/approve/<id>`) and an `approvalId`; once a workspace member
|
|
31
|
+
approves (an owner, for a submission), the same call with that
|
|
32
|
+
`approvalId` runs exactly that action, once. Promotions to other channels
|
|
33
|
+
and share revokes stay ungated unless `EXTENSION_DEV_APPROVAL_GATE=1`;
|
|
34
|
+
`EXTENSION_DEV_APPROVAL_GATE=0` turns the default off. Whatever the
|
|
35
|
+
setting, a platform that answers `APPROVAL_REQUIRED` now gets the same
|
|
36
|
+
approval flow instead of a bare failure.
|
|
37
|
+
|
|
38
|
+
- Two server flags narrow what an agent can reach. `--features=local`
|
|
39
|
+
or `--features=platform` (env `EXTENSION_DEV_FEATURES`) lists one tool
|
|
40
|
+
group; `local` alone is about 40% smaller. `--no-ship` (env
|
|
41
|
+
`EXTENSION_DEV_NO_SHIP`) hides `extension_publish` and
|
|
42
|
+
`extension_release_promote` and refuses a real submit, a share link and
|
|
43
|
+
a share revoke, while dry runs and listing keep working. A refused call
|
|
44
|
+
answers the new code `E_TOOL_DISABLED` with the flag to change. Both
|
|
45
|
+
default to everything on, so no existing setup changes.
|
|
46
|
+
- Every tool now carries MCP annotations (`readOnlyHint`,
|
|
47
|
+
`destructiveHint`, `idempotentHint`, `openWorldHint`) from one policy
|
|
48
|
+
table, so clients can auto-approve reads and ask before the rest. A
|
|
49
|
+
merged tool takes its worst action: `extension_shares` is destructive
|
|
50
|
+
because revoke is.
|
|
51
|
+
- A device-flow step waiting on a human (`extension_auth` login,
|
|
52
|
+
`extension_project_create`, `extension_workspace_create`) now answers
|
|
53
|
+
`ok: true` with `status: "authorization-pending"` and no error, instead
|
|
54
|
+
of `ok: false` with `E_AUTH_PENDING`, so clients stop rendering the
|
|
55
|
+
first login step as a failure. The approval link and code stay in
|
|
56
|
+
`hint` and `value`; `E_AUTH_PENDING` is retired.
|
|
57
|
+
|
|
3
58
|
## 10.10.2
|
|
4
59
|
|
|
5
60
|
- The engine pin moves to the Extension.js canary
|
package/README.md
CHANGED
|
@@ -45,10 +45,12 @@ Built on [Extension.js](https://extension.js.org), the open-source cross-browser
|
|
|
45
45
|
|
|
46
46
|
## Setup
|
|
47
47
|
|
|
48
|
+
<!-- setup:start (generated from src/clients, run pnpm readme:clients) -->
|
|
49
|
+
|
|
48
50
|
### Claude Code
|
|
49
51
|
|
|
50
52
|
```bash
|
|
51
|
-
claude mcp add extension-dev npx @extension.dev/mcp
|
|
53
|
+
claude mcp add extension-dev -- npx @extension.dev/mcp
|
|
52
54
|
```
|
|
53
55
|
|
|
54
56
|
Or install it as a plugin, the MCP server plus the `/extension`, `/extension-add`, `/extension-debug`, and `/extension-publish` commands in one step:
|
|
@@ -62,14 +64,103 @@ Or install it as a plugin, the MCP server plus the `/extension`, `/extension-add
|
|
|
62
64
|
|
|
63
65
|
[](https://cursor.com/en/install-mcp?name=extension-dev&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyJAZXh0ZW5zaW9uLmRldi9tY3AiXX0%3D)
|
|
64
66
|
|
|
65
|
-
|
|
67
|
+
`.cursor/mcp.json`:
|
|
68
|
+
|
|
69
|
+
```json
|
|
70
|
+
{
|
|
71
|
+
"mcpServers": {
|
|
72
|
+
"extension-dev": {
|
|
73
|
+
"command": "npx",
|
|
74
|
+
"args": [
|
|
75
|
+
"@extension.dev/mcp"
|
|
76
|
+
]
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### VS Code (Also GitHub Copilot)
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
code --add-mcp '{"name":"extension-dev","command":"npx","args":["@extension.dev/mcp"]}'
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`.vscode/mcp.json`:
|
|
89
|
+
|
|
90
|
+
```json
|
|
91
|
+
{
|
|
92
|
+
"servers": {
|
|
93
|
+
"extension-dev": {
|
|
94
|
+
"type": "stdio",
|
|
95
|
+
"command": "npx",
|
|
96
|
+
"args": [
|
|
97
|
+
"@extension.dev/mcp"
|
|
98
|
+
]
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### Codex
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
codex mcp add extension-dev -- npx @extension.dev/mcp
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
`~/.codex/config.toml`:
|
|
111
|
+
|
|
112
|
+
```toml
|
|
113
|
+
[mcp_servers.extension-dev]
|
|
114
|
+
command = "npx"
|
|
115
|
+
args = ["@extension.dev/mcp"]
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Other clients (Claude Desktop and .mcp.json)
|
|
119
|
+
|
|
120
|
+
`.mcp.json`:
|
|
121
|
+
|
|
122
|
+
```json
|
|
123
|
+
{
|
|
124
|
+
"mcpServers": {
|
|
125
|
+
"extension-dev": {
|
|
126
|
+
"command": "npx",
|
|
127
|
+
"args": [
|
|
128
|
+
"@extension.dev/mcp"
|
|
129
|
+
]
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Signing in and pinning a project
|
|
136
|
+
|
|
137
|
+
The platform tools need a login. Sign in once per project, then add `--project <workspace>/<project>` to the server's arguments (or set `EXTENSION_DEV_PROJECT`) so that server only ever acts on that project, however many logins this machine holds. The console's Connect dialog fills both in for your project.
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
npx @extension.dev/mcp login --project <workspace>/<project>
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
<!-- setup:end -->
|
|
144
|
+
|
|
145
|
+
### Choosing what the agent can reach
|
|
146
|
+
|
|
147
|
+
Two flags (or environment variables) narrow the server before an agent sees it:
|
|
148
|
+
|
|
149
|
+
- `--features=local` exposes only the tools that work on this machine (create, run, inspect, build), which also cuts the tool list by about 40%. `--features=platform` exposes only the extension.dev account, share, release and store tools. Both are on by default. Env: `EXTENSION_DEV_FEATURES`.
|
|
150
|
+
- `--no-ship` keeps everything that stays on this machine and refuses every call that reaches other people: `extension_publish`, `extension_release_promote`, `extension_submit` with `dryRun: false`, `extension_preview_web` with `share: true`, and a share revoke. Dry runs and share listing still work. Env: `EXTENSION_DEV_NO_SHIP=1`.
|
|
151
|
+
|
|
152
|
+
A refused call answers `E_TOOL_DISABLED` with the flag to change.
|
|
153
|
+
|
|
154
|
+
A real store submission and a promotion to stable also wait for a person by default: the first call answers `approval-required` with a link on extension.dev, a workspace member approves exactly that action, and the same call with the returned `approvalId` runs it once. `EXTENSION_DEV_APPROVAL_GATE=1` extends this to every promotion and share revoke; `EXTENSION_DEV_APPROVAL_GATE=0` turns it off.
|
|
155
|
+
|
|
156
|
+
Every tool also carries MCP annotations (`readOnlyHint`, `destructiveHint`, `idempotentHint`, `openWorldHint`), so clients can auto-approve reads and ask before the rest.
|
|
66
157
|
|
|
67
158
|
```json
|
|
68
159
|
{
|
|
69
160
|
"mcpServers": {
|
|
70
161
|
"extension-dev": {
|
|
71
162
|
"command": "npx",
|
|
72
|
-
"args": ["@extension.dev/mcp"]
|
|
163
|
+
"args": ["@extension.dev/mcp", "--no-ship"]
|
|
73
164
|
}
|
|
74
165
|
}
|
|
75
166
|
}
|
package/bin/extension-mcp.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// Apache License 2.0 (c) 2026 Cezar Augusto and the extension.dev collaborators
|
|
9
9
|
|
|
10
10
|
import {createRequire} from 'node:module'
|
|
11
|
-
import {startServer, runCli} from '../dist/module.js'
|
|
11
|
+
import {startServer, runCli, isServerFlag, resolveServerOptions} from '../dist/module.js'
|
|
12
12
|
|
|
13
13
|
const require = createRequire(import.meta.url)
|
|
14
14
|
const {version} = require('../package.json')
|
|
@@ -16,18 +16,29 @@ const {version} = require('../package.json')
|
|
|
16
16
|
const usage = `@extension.dev/mcp ${version}
|
|
17
17
|
|
|
18
18
|
Usage:
|
|
19
|
-
extension-mcp
|
|
19
|
+
extension-mcp [flags] Start the MCP server on stdio
|
|
20
20
|
extension-mcp login [flags] Device login at extension.dev
|
|
21
21
|
extension-mcp logout Remove the stored credentials
|
|
22
22
|
extension-mcp whoami Show the stored identity
|
|
23
23
|
extension-mcp release [...] Headless release commands
|
|
24
24
|
extension-mcp --version Print the version
|
|
25
|
+
|
|
26
|
+
Server flags:
|
|
27
|
+
--features=local,platform Tool groups to expose (default: both)
|
|
28
|
+
--no-ship Refuse publish, promote, real submits,
|
|
29
|
+
share links and share revokes
|
|
30
|
+
--project <workspace>/<proj> Use only this project's login
|
|
25
31
|
`
|
|
26
32
|
|
|
27
33
|
const [, , cmd, ...rest] = process.argv
|
|
28
34
|
|
|
29
|
-
if (cmd === undefined) {
|
|
30
|
-
|
|
35
|
+
if (cmd === undefined || isServerFlag(cmd)) {
|
|
36
|
+
const resolved = resolveServerOptions(process.argv.slice(2), process.env)
|
|
37
|
+
if (!resolved.ok) {
|
|
38
|
+
process.stderr.write(`${resolved.message}\n\n${usage}`)
|
|
39
|
+
process.exit(1)
|
|
40
|
+
}
|
|
41
|
+
startServer(resolved.options)
|
|
31
42
|
} else if (['login', 'logout', 'whoami', 'release'].includes(cmd)) {
|
|
32
43
|
runCli(cmd, rest)
|
|
33
44
|
.then((code) => process.exit(code))
|
package/dist/clients.js
ADDED
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
const CLIENTS = [
|
|
2
|
+
{
|
|
3
|
+
id: "claude-code",
|
|
4
|
+
label: "Claude Code"
|
|
5
|
+
},
|
|
6
|
+
{
|
|
7
|
+
id: "cursor",
|
|
8
|
+
label: "Cursor"
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
id: "vscode",
|
|
12
|
+
label: "VS Code",
|
|
13
|
+
subtitle: "Also GitHub Copilot"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
id: "codex",
|
|
17
|
+
label: "Codex"
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
id: "json",
|
|
21
|
+
label: "Other clients",
|
|
22
|
+
subtitle: "Claude Desktop and .mcp.json"
|
|
23
|
+
}
|
|
24
|
+
];
|
|
25
|
+
const SERVER_NAME = "extension-dev";
|
|
26
|
+
const PACKAGE = "@extension.dev/mcp";
|
|
27
|
+
function serverArgs(input) {
|
|
28
|
+
const args = [
|
|
29
|
+
PACKAGE
|
|
30
|
+
];
|
|
31
|
+
if ("local" !== input.reach && input.project) args.push("--project", input.project);
|
|
32
|
+
if ("no-ship" === input.reach) args.push("--no-ship");
|
|
33
|
+
if ("local" === input.reach) args.push("--features=local");
|
|
34
|
+
return args;
|
|
35
|
+
}
|
|
36
|
+
function serverEnv(input) {
|
|
37
|
+
return "everything" === input.reach && input.strictApproval ? {
|
|
38
|
+
EXTENSION_DEV_APPROVAL_GATE: "1"
|
|
39
|
+
} : {};
|
|
40
|
+
}
|
|
41
|
+
const shellQuote = (value)=>/^[A-Za-z0-9_@%+=:,./-]+$/.test(value) ? value : `'${value.replace(/'/g, "'\\''")}'`;
|
|
42
|
+
const base64 = (text)=>"function" == typeof btoa ? btoa(text) : globalThis.Buffer.from(text).toString("base64");
|
|
43
|
+
const tomlString = (value)=>JSON.stringify(value);
|
|
44
|
+
function loginCommand(project) {
|
|
45
|
+
return `npx ${PACKAGE} login --project ${shellQuote(project)}`;
|
|
46
|
+
}
|
|
47
|
+
function buildRecipe(input) {
|
|
48
|
+
const args = serverArgs(input);
|
|
49
|
+
const env = serverEnv(input);
|
|
50
|
+
const envEntries = Object.entries(env);
|
|
51
|
+
const stdio = {
|
|
52
|
+
command: "npx",
|
|
53
|
+
args,
|
|
54
|
+
...envEntries.length ? {
|
|
55
|
+
env
|
|
56
|
+
} : {}
|
|
57
|
+
};
|
|
58
|
+
const login = loginCommand(input.project);
|
|
59
|
+
const argLine = args.map(shellQuote).join(" ");
|
|
60
|
+
switch(input.client){
|
|
61
|
+
case "claude-code":
|
|
62
|
+
return {
|
|
63
|
+
client: "claude-code",
|
|
64
|
+
command: [
|
|
65
|
+
"claude mcp add",
|
|
66
|
+
SERVER_NAME,
|
|
67
|
+
...envEntries.map(([k, v])=>`-e ${k}=${shellQuote(v)}`),
|
|
68
|
+
"-- npx",
|
|
69
|
+
argLine
|
|
70
|
+
].join(" "),
|
|
71
|
+
login
|
|
72
|
+
};
|
|
73
|
+
case "cursor":
|
|
74
|
+
return {
|
|
75
|
+
client: "cursor",
|
|
76
|
+
deeplink: `https://cursor.com/en/install-mcp?name=${SERVER_NAME}&config=${encodeURIComponent(base64(JSON.stringify(stdio)))}`,
|
|
77
|
+
config: {
|
|
78
|
+
language: "json",
|
|
79
|
+
path: ".cursor/mcp.json",
|
|
80
|
+
text: JSON.stringify({
|
|
81
|
+
mcpServers: {
|
|
82
|
+
[SERVER_NAME]: stdio
|
|
83
|
+
}
|
|
84
|
+
}, null, 2)
|
|
85
|
+
},
|
|
86
|
+
login
|
|
87
|
+
};
|
|
88
|
+
case "vscode":
|
|
89
|
+
{
|
|
90
|
+
const named = {
|
|
91
|
+
name: SERVER_NAME,
|
|
92
|
+
...stdio
|
|
93
|
+
};
|
|
94
|
+
return {
|
|
95
|
+
client: "vscode",
|
|
96
|
+
command: `code --add-mcp ${shellQuote(JSON.stringify(named))}`,
|
|
97
|
+
deeplink: `vscode:mcp/install?${encodeURIComponent(JSON.stringify(named))}`,
|
|
98
|
+
config: {
|
|
99
|
+
language: "json",
|
|
100
|
+
path: ".vscode/mcp.json",
|
|
101
|
+
text: JSON.stringify({
|
|
102
|
+
servers: {
|
|
103
|
+
[SERVER_NAME]: {
|
|
104
|
+
type: "stdio",
|
|
105
|
+
...stdio
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}, null, 2)
|
|
109
|
+
},
|
|
110
|
+
login
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
case "codex":
|
|
114
|
+
{
|
|
115
|
+
const lines = [
|
|
116
|
+
`[mcp_servers.${SERVER_NAME}]`,
|
|
117
|
+
'command = "npx"',
|
|
118
|
+
`args = [${args.map(tomlString).join(", ")}]`
|
|
119
|
+
];
|
|
120
|
+
if (envEntries.length) {
|
|
121
|
+
lines.push("", `[mcp_servers.${SERVER_NAME}.env]`);
|
|
122
|
+
for (const [k, v] of envEntries)lines.push(`${k} = ${tomlString(v)}`);
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
client: "codex",
|
|
126
|
+
command: [
|
|
127
|
+
"codex mcp add",
|
|
128
|
+
SERVER_NAME,
|
|
129
|
+
...envEntries.map(([k, v])=>`--env ${k}=${shellQuote(v)}`),
|
|
130
|
+
"-- npx",
|
|
131
|
+
argLine
|
|
132
|
+
].join(" "),
|
|
133
|
+
config: {
|
|
134
|
+
language: "toml",
|
|
135
|
+
path: "~/.codex/config.toml",
|
|
136
|
+
text: lines.join("\n")
|
|
137
|
+
},
|
|
138
|
+
login
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
case "json":
|
|
142
|
+
return {
|
|
143
|
+
client: "json",
|
|
144
|
+
config: {
|
|
145
|
+
language: "json",
|
|
146
|
+
path: ".mcp.json",
|
|
147
|
+
text: JSON.stringify({
|
|
148
|
+
mcpServers: {
|
|
149
|
+
[SERVER_NAME]: stdio
|
|
150
|
+
}
|
|
151
|
+
}, null, 2)
|
|
152
|
+
},
|
|
153
|
+
login
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
export { CLIENTS, PACKAGE, SERVER_NAME, buildRecipe, loginCommand, serverArgs, serverEnv };
|
package/dist/module.d.ts
CHANGED
package/dist/module.js
CHANGED
|
@@ -236,7 +236,7 @@ __webpack_require__.d(workspace_create_namespaceObject, {
|
|
|
236
236
|
handler: ()=>workspace_create_handler,
|
|
237
237
|
schema: ()=>workspace_create_schema
|
|
238
238
|
});
|
|
239
|
-
var package_namespaceObject = JSON.parse('{"rE":"10.10.
|
|
239
|
+
var package_namespaceObject = JSON.parse('{"rE":"10.10.4","El":{"OP":"4.1.31"}}');
|
|
240
240
|
const ENVELOPE_SCHEMA = 1;
|
|
241
241
|
const collectWarnings = (warnings)=>{
|
|
242
242
|
if (!warnings) return [];
|
|
@@ -509,8 +509,20 @@ function readCredentialStore() {
|
|
|
509
509
|
entries
|
|
510
510
|
};
|
|
511
511
|
}
|
|
512
|
+
const PROJECT_PIN_ENV = "EXTENSION_DEV_PROJECT";
|
|
513
|
+
function pinnedProject() {
|
|
514
|
+
return String(process.env[PROJECT_PIN_ENV] || "").trim().toLowerCase();
|
|
515
|
+
}
|
|
516
|
+
function isProjectRef(value) {
|
|
517
|
+
return /^[a-z0-9][a-z0-9-_.]*\/[a-z0-9][a-z0-9-_.]*$/i.test(value);
|
|
518
|
+
}
|
|
519
|
+
function sameProject(named, pinned) {
|
|
520
|
+
const a = named.trim().toLowerCase();
|
|
521
|
+
if (!a || !pinned) return true;
|
|
522
|
+
return a.includes("/") ? a === pinned : a === pinned.split("/")[1];
|
|
523
|
+
}
|
|
512
524
|
function selectEntry(store, selector) {
|
|
513
|
-
const wanted = String(selector?.project ?? "").trim().toLowerCase();
|
|
525
|
+
const wanted = (String(selector?.project ?? "").trim() || pinnedProject()).toLowerCase();
|
|
514
526
|
if (!wanted) return store.active && store.entries[store.active] || Object.values(store.entries)[0] || null;
|
|
515
527
|
if (wanted.includes("/")) return store.entries[wanted] ?? null;
|
|
516
528
|
const bySlug = Object.values(store.entries).filter((entry)=>entry.projectSlug.toLowerCase() === wanted);
|
|
@@ -1986,13 +1998,9 @@ function pendingEnvelope(start) {
|
|
|
1986
1998
|
const hasCompleteLink = complete.length > 0 && complete !== start.verificationUri;
|
|
1987
1999
|
const message = hasCompleteLink ? `Open ${complete} and approve creating the project (code ${start.userCode} is pre-filled), then call extension_project_create again with this deviceCode and the same arguments. If the page asks for a code, enter ${start.userCode} at ${start.verificationUri}.` : `Open ${start.verificationUri}, enter code ${start.userCode}, approve creating the project, then call extension_project_create again with this deviceCode and the same arguments.`;
|
|
1988
2000
|
return envelope_envelope({
|
|
1989
|
-
ok:
|
|
2001
|
+
ok: true,
|
|
1990
2002
|
command: project_create_COMMAND,
|
|
1991
2003
|
status: "authorization-pending",
|
|
1992
|
-
error: {
|
|
1993
|
-
code: "E_AUTH_PENDING",
|
|
1994
|
-
message
|
|
1995
|
-
},
|
|
1996
2004
|
value: {
|
|
1997
2005
|
userCode: start.userCode,
|
|
1998
2006
|
verificationUri: start.verificationUri,
|
|
@@ -2131,17 +2139,14 @@ async function project_create_handler(args) {
|
|
|
2131
2139
|
async function finishFromPoll(poll, ctx) {
|
|
2132
2140
|
if (!poll.ok) {
|
|
2133
2141
|
if ("pending" === poll.reason) return envelope_envelope({
|
|
2134
|
-
ok:
|
|
2142
|
+
ok: true,
|
|
2135
2143
|
command: project_create_COMMAND,
|
|
2136
2144
|
status: "authorization-pending",
|
|
2137
|
-
error: {
|
|
2138
|
-
code: "E_AUTH_PENDING",
|
|
2139
|
-
message: `Still waiting for approval at ${ctx.verificationUri}. Approve there, then call extension_project_create again with this same deviceCode.`
|
|
2140
|
-
},
|
|
2141
2145
|
value: {
|
|
2142
2146
|
verificationUri: ctx.verificationUri,
|
|
2143
2147
|
deviceCode: ctx.deviceCode
|
|
2144
|
-
}
|
|
2148
|
+
},
|
|
2149
|
+
hint: `Still waiting for approval at ${ctx.verificationUri}. Approve there, then call extension_project_create again with this same deviceCode.`
|
|
2145
2150
|
});
|
|
2146
2151
|
if ("denied" === poll.reason) return fail("CreateDenied", "Creating the project was denied at extension.dev/device.", "create-denied", "E_AUTH_DENIED");
|
|
2147
2152
|
if ("expired" === poll.reason) return fail("CreateExpired", "The device code expired. Run extension_project_create again to restart.", "create-expired", "E_AUTH_EXPIRED");
|
|
@@ -2332,13 +2337,9 @@ function workspace_create_pendingEnvelope(start) {
|
|
|
2332
2337
|
const hasCompleteLink = complete.length > 0 && complete !== start.verificationUri;
|
|
2333
2338
|
const message = hasCompleteLink ? `Open ${complete} and approve creating the workspace (code ${start.userCode} is pre-filled), then call ${workspace_create_COMMAND} again with this deviceCode and the same arguments. If the page asks for a code, enter ${start.userCode} at ${start.verificationUri}. The GitHub account that approves becomes the workspace owner.` : `Open ${start.verificationUri}, enter code ${start.userCode}, approve creating the workspace, then call ${workspace_create_COMMAND} again with this deviceCode and the same arguments. The GitHub account that approves becomes the workspace owner.`;
|
|
2334
2339
|
return envelope_envelope({
|
|
2335
|
-
ok:
|
|
2340
|
+
ok: true,
|
|
2336
2341
|
command: workspace_create_COMMAND,
|
|
2337
2342
|
status: "authorization-pending",
|
|
2338
|
-
error: {
|
|
2339
|
-
code: "E_AUTH_PENDING",
|
|
2340
|
-
message
|
|
2341
|
-
},
|
|
2342
2343
|
value: {
|
|
2343
2344
|
userCode: start.userCode,
|
|
2344
2345
|
verificationUri: start.verificationUri,
|
|
@@ -2419,17 +2420,14 @@ async function workspace_create_handler(args) {
|
|
|
2419
2420
|
async function workspace_create_finishFromPoll(poll, ctx) {
|
|
2420
2421
|
if (!poll.ok) {
|
|
2421
2422
|
if ("pending" === poll.reason) return envelope_envelope({
|
|
2422
|
-
ok:
|
|
2423
|
+
ok: true,
|
|
2423
2424
|
command: workspace_create_COMMAND,
|
|
2424
2425
|
status: "authorization-pending",
|
|
2425
|
-
error: {
|
|
2426
|
-
code: "E_AUTH_PENDING",
|
|
2427
|
-
message: `Still waiting for approval at ${ctx.verificationUri}. Approve there, then call ${workspace_create_COMMAND} again with this same deviceCode.`
|
|
2428
|
-
},
|
|
2429
2426
|
value: {
|
|
2430
2427
|
verificationUri: ctx.verificationUri,
|
|
2431
2428
|
deviceCode: ctx.deviceCode
|
|
2432
|
-
}
|
|
2429
|
+
},
|
|
2430
|
+
hint: `Still waiting for approval at ${ctx.verificationUri}. Approve there, then call ${workspace_create_COMMAND} again with this same deviceCode.`
|
|
2433
2431
|
});
|
|
2434
2432
|
if ("denied" === poll.reason) return workspace_create_fail("CreateDenied", "Creating the workspace was denied at extension.dev/device.", "create-denied", "E_AUTH_DENIED");
|
|
2435
2433
|
if ("expired" === poll.reason) return workspace_create_fail("CreateExpired", `The device code expired. Run ${workspace_create_COMMAND} again to restart.`, "create-expired", "E_AUTH_EXPIRED");
|
|
@@ -7935,12 +7933,15 @@ async function openSidebarThroughGesture(projectPath, browser, refusal) {
|
|
|
7935
7933
|
return actFrameJson(refusal);
|
|
7936
7934
|
}
|
|
7937
7935
|
function resolveToken(options = {}) {
|
|
7938
|
-
const
|
|
7936
|
+
const named = String(options.project ?? "").trim();
|
|
7937
|
+
const project = named || pinnedProject();
|
|
7939
7938
|
if (project) {
|
|
7940
7939
|
const creds = readValidCredentials(void 0, {
|
|
7941
7940
|
project
|
|
7942
7941
|
});
|
|
7943
|
-
|
|
7942
|
+
if (creds?.token) return String(creds.token).trim();
|
|
7943
|
+
if (named) return "";
|
|
7944
|
+
return String(process.env.EXTENSION_DEV_TOKEN || "").trim();
|
|
7944
7945
|
}
|
|
7945
7946
|
const fromEnv = String(process.env.EXTENSION_DEV_TOKEN || "").trim();
|
|
7946
7947
|
if (fromEnv) return fromEnv;
|
|
@@ -8204,6 +8205,9 @@ async function revokeArtifact(options) {
|
|
|
8204
8205
|
error: {
|
|
8205
8206
|
name: "SharesRevokeError",
|
|
8206
8207
|
status: res.status,
|
|
8208
|
+
..."string" == typeof data?.code ? {
|
|
8209
|
+
code: data.code
|
|
8210
|
+
} : {},
|
|
8207
8211
|
message: `Revoking ${options.artifactId} failed (${res.status}): ${data?.message || "unknown error"}`
|
|
8208
8212
|
}
|
|
8209
8213
|
};
|
|
@@ -9176,9 +9180,29 @@ const APPROVAL_GATE_ENV = "EXTENSION_DEV_APPROVAL_GATE";
|
|
|
9176
9180
|
const APPROVAL_REQUIRED_STATUS = "approval-required";
|
|
9177
9181
|
const APPROVAL_PENDING_STATUS = "approval-pending";
|
|
9178
9182
|
const APPROVAL_REJECTED_STATUS = "approval-rejected";
|
|
9179
|
-
function approvalGateEnabled() {
|
|
9183
|
+
function approvalGateEnabled(defaultOn = false) {
|
|
9180
9184
|
const raw = String(process.env[APPROVAL_GATE_ENV] || "").trim().toLowerCase();
|
|
9181
|
-
|
|
9185
|
+
if ("" === raw) return defaultOn;
|
|
9186
|
+
return "0" !== raw && "false" !== raw && "off" !== raw;
|
|
9187
|
+
}
|
|
9188
|
+
function platformRequiresApproval(res, data) {
|
|
9189
|
+
return 403 === res.status && !!data && "object" == typeof data && "APPROVAL_REQUIRED" === data.code;
|
|
9190
|
+
}
|
|
9191
|
+
async function requestApprovalAfterRefusal(input) {
|
|
9192
|
+
const gate = await evaluateApproval({
|
|
9193
|
+
...input,
|
|
9194
|
+
approvalId: void 0,
|
|
9195
|
+
enabled: true
|
|
9196
|
+
});
|
|
9197
|
+
return gate.blocked ? gate.envelope : envelope_envelope({
|
|
9198
|
+
ok: false,
|
|
9199
|
+
command: input.command,
|
|
9200
|
+
status: APPROVAL_REQUIRED_STATUS,
|
|
9201
|
+
error: {
|
|
9202
|
+
code: "E_APPROVAL_REQUIRED",
|
|
9203
|
+
message: "The platform requires an approval for this action."
|
|
9204
|
+
}
|
|
9205
|
+
});
|
|
9182
9206
|
}
|
|
9183
9207
|
function canonicalScope(scope) {
|
|
9184
9208
|
return Object.keys(scope).sort().map((key)=>{
|
|
@@ -9666,18 +9690,19 @@ async function revokeShare(args) {
|
|
|
9666
9690
|
const token = resolveToken({
|
|
9667
9691
|
project: args.project
|
|
9668
9692
|
});
|
|
9693
|
+
const gateInput = {
|
|
9694
|
+
command: "extension_shares",
|
|
9695
|
+
action: "extension_shares.revoke",
|
|
9696
|
+
scope: {
|
|
9697
|
+
artifactId: ref
|
|
9698
|
+
},
|
|
9699
|
+
description: `Permanently revoke share ${ref}, killing the link for everyone with no way to restore it.`,
|
|
9700
|
+
approvalId: args.approvalId,
|
|
9701
|
+
token: token || "",
|
|
9702
|
+
api: args.api
|
|
9703
|
+
};
|
|
9669
9704
|
if (token) {
|
|
9670
|
-
const gate = await evaluateApproval(
|
|
9671
|
-
command: "extension_shares",
|
|
9672
|
-
action: "extension_shares.revoke",
|
|
9673
|
-
scope: {
|
|
9674
|
-
artifactId: ref
|
|
9675
|
-
},
|
|
9676
|
-
description: `Permanently revoke share ${ref}, killing the link for everyone with no way to restore it.`,
|
|
9677
|
-
approvalId: args.approvalId,
|
|
9678
|
-
token,
|
|
9679
|
-
api: args.api
|
|
9680
|
-
});
|
|
9705
|
+
const gate = await evaluateApproval(gateInput);
|
|
9681
9706
|
if (gate.blocked) return gate.envelope;
|
|
9682
9707
|
}
|
|
9683
9708
|
const result = await revokeArtifact({
|
|
@@ -9703,6 +9728,7 @@ async function revokeShare(args) {
|
|
|
9703
9728
|
artifactId: ref
|
|
9704
9729
|
}
|
|
9705
9730
|
});
|
|
9731
|
+
if (token && !args.approvalId && 403 === result.error.status && "APPROVAL_REQUIRED" === result.error.code) return requestApprovalAfterRefusal(gateInput);
|
|
9706
9732
|
const isAuth = "SharesAuthError" === result.error.name;
|
|
9707
9733
|
return envelope_envelope({
|
|
9708
9734
|
ok: false,
|
|
@@ -15393,7 +15419,7 @@ async function release_promote_handler(args) {
|
|
|
15393
15419
|
const url = `${apiCheck.base}/api/cli/release/promote`;
|
|
15394
15420
|
const sourceChannel = args.sourceChannel ? String(args.sourceChannel).trim() : "";
|
|
15395
15421
|
const gateBrowsers = (Array.isArray(args.browsers) ? args.browsers : []).map((b)=>String(b).trim()).filter(Boolean);
|
|
15396
|
-
const
|
|
15422
|
+
const gateInput = {
|
|
15397
15423
|
command: "extension_release_promote",
|
|
15398
15424
|
action: "extension_release_promote",
|
|
15399
15425
|
scope: {
|
|
@@ -15412,6 +15438,10 @@ async function release_promote_handler(args) {
|
|
|
15412
15438
|
approvalId: args.approvalId,
|
|
15413
15439
|
token,
|
|
15414
15440
|
api: args.api
|
|
15441
|
+
};
|
|
15442
|
+
const gate = await evaluateApproval({
|
|
15443
|
+
...gateInput,
|
|
15444
|
+
enabled: approvalGateEnabled("stable" === channel)
|
|
15415
15445
|
});
|
|
15416
15446
|
if (gate.blocked) return gate.envelope;
|
|
15417
15447
|
const body = {
|
|
@@ -15456,6 +15486,7 @@ async function release_promote_handler(args) {
|
|
|
15456
15486
|
buildId
|
|
15457
15487
|
}
|
|
15458
15488
|
});
|
|
15489
|
+
if (!args.approvalId && platformRequiresApproval(res, data)) return requestApprovalAfterRefusal(gateInput);
|
|
15459
15490
|
const code = "string" == typeof data?.code ? data.code : void 0;
|
|
15460
15491
|
const enrich = {};
|
|
15461
15492
|
let hint = "";
|
|
@@ -16090,21 +16121,25 @@ async function submit_handler(args) {
|
|
|
16090
16121
|
};
|
|
16091
16122
|
if (args.channel) body.channel = String(args.channel).trim();
|
|
16092
16123
|
if (args.version) body.version = String(args.version).trim();
|
|
16124
|
+
const gateInput = {
|
|
16125
|
+
command: "extension_submit",
|
|
16126
|
+
action: "extension_submit",
|
|
16127
|
+
scope: {
|
|
16128
|
+
browsers: [
|
|
16129
|
+
...browsers
|
|
16130
|
+
].sort(),
|
|
16131
|
+
buildSha: buildSha.toLowerCase(),
|
|
16132
|
+
channel
|
|
16133
|
+
},
|
|
16134
|
+
description: `Submit build ${buildSha} to ${browsers.join(", ")} for store review.`,
|
|
16135
|
+
approvalId: args.approvalId,
|
|
16136
|
+
token,
|
|
16137
|
+
api: args.api
|
|
16138
|
+
};
|
|
16093
16139
|
if (!dryRun) {
|
|
16094
16140
|
const gate = await evaluateApproval({
|
|
16095
|
-
|
|
16096
|
-
|
|
16097
|
-
scope: {
|
|
16098
|
-
browsers: [
|
|
16099
|
-
...browsers
|
|
16100
|
-
].sort(),
|
|
16101
|
-
buildSha: buildSha.toLowerCase(),
|
|
16102
|
-
channel
|
|
16103
|
-
},
|
|
16104
|
-
description: `Submit build ${buildSha} to ${browsers.join(", ")} for store review.`,
|
|
16105
|
-
approvalId: args.approvalId,
|
|
16106
|
-
token,
|
|
16107
|
-
api: args.api
|
|
16141
|
+
...gateInput,
|
|
16142
|
+
enabled: approvalGateEnabled(true)
|
|
16108
16143
|
});
|
|
16109
16144
|
if (gate.blocked) return gate.envelope;
|
|
16110
16145
|
if (gate.approvalId) body.approvalId = gate.approvalId;
|
|
@@ -16144,6 +16179,7 @@ async function submit_handler(args) {
|
|
|
16144
16179
|
dryRun
|
|
16145
16180
|
}
|
|
16146
16181
|
});
|
|
16182
|
+
if (!dryRun && !args.approvalId && platformRequiresApproval(res, data)) return requestApprovalAfterRefusal(gateInput);
|
|
16147
16183
|
return submit_fail("SubmitError", `${dryRun ? "preflight" : "submit"} failed (${res.status}): ${data?.message || text || "unknown error"}`, "submit-failed", "E_PLATFORM");
|
|
16148
16184
|
}
|
|
16149
16185
|
const warnings = Array.isArray(data?.warnings) ? [
|
|
@@ -16793,13 +16829,9 @@ function login_pending(start) {
|
|
|
16793
16829
|
const hasCompleteLink = complete.length > 0 && complete !== start.verificationUri;
|
|
16794
16830
|
const message = hasCompleteLink ? `Open ${complete} and approve (code ${start.userCode} is pre-filled), then call extension_auth (action: login) again with this deviceCode and the same project. If the page asks for a code, enter ${start.userCode} at ${start.verificationUri}.` : `Open ${start.verificationUri} and enter code ${start.userCode}, then call extension_auth (action: login) again with this deviceCode and the same project.`;
|
|
16795
16831
|
return envelope_envelope({
|
|
16796
|
-
ok:
|
|
16832
|
+
ok: true,
|
|
16797
16833
|
command: "extension_auth",
|
|
16798
16834
|
status: "authorization-pending",
|
|
16799
|
-
error: {
|
|
16800
|
-
code: "E_AUTH_PENDING",
|
|
16801
|
-
message
|
|
16802
|
-
},
|
|
16803
16835
|
value: {
|
|
16804
16836
|
userCode: start.userCode,
|
|
16805
16837
|
verificationUri: start.verificationUri,
|
|
@@ -16818,13 +16850,9 @@ function login_pending(start) {
|
|
|
16818
16850
|
function resumePending(deviceCode, verificationUri) {
|
|
16819
16851
|
const message = `Still waiting for authorization. The one-click link and code from the previous response are still valid: open that link (or enter the code at ${verificationUri}), then call extension_auth (action: login) again with this same deviceCode and the same project.`;
|
|
16820
16852
|
return envelope_envelope({
|
|
16821
|
-
ok:
|
|
16853
|
+
ok: true,
|
|
16822
16854
|
command: "extension_auth",
|
|
16823
16855
|
status: "authorization-pending",
|
|
16824
|
-
error: {
|
|
16825
|
-
code: "E_AUTH_PENDING",
|
|
16826
|
-
message
|
|
16827
|
-
},
|
|
16828
16856
|
value: {
|
|
16829
16857
|
verificationUri,
|
|
16830
16858
|
deviceCode,
|
|
@@ -17843,6 +17871,334 @@ function inputValidationError(toolName, issues, inputSchema) {
|
|
|
17843
17871
|
}
|
|
17844
17872
|
});
|
|
17845
17873
|
}
|
|
17874
|
+
const FEATURE_GROUPS = [
|
|
17875
|
+
"local",
|
|
17876
|
+
"platform"
|
|
17877
|
+
];
|
|
17878
|
+
const FEATURES_ENV = "EXTENSION_DEV_FEATURES";
|
|
17879
|
+
const NO_SHIP_ENV = "EXTENSION_DEV_NO_SHIP";
|
|
17880
|
+
const reads = (openWorld = false)=>({
|
|
17881
|
+
readOnlyHint: true,
|
|
17882
|
+
destructiveHint: false,
|
|
17883
|
+
idempotentHint: true,
|
|
17884
|
+
openWorldHint: openWorld
|
|
17885
|
+
});
|
|
17886
|
+
const acts = (over = {})=>({
|
|
17887
|
+
readOnlyHint: false,
|
|
17888
|
+
destructiveHint: false,
|
|
17889
|
+
idempotentHint: false,
|
|
17890
|
+
openWorldHint: false,
|
|
17891
|
+
...over
|
|
17892
|
+
});
|
|
17893
|
+
const TOOL_POLICY = {
|
|
17894
|
+
extension_create: {
|
|
17895
|
+
group: "local",
|
|
17896
|
+
annotations: acts({
|
|
17897
|
+
openWorldHint: true
|
|
17898
|
+
})
|
|
17899
|
+
},
|
|
17900
|
+
extension_templates: {
|
|
17901
|
+
group: "local",
|
|
17902
|
+
annotations: reads(true)
|
|
17903
|
+
},
|
|
17904
|
+
extension_add_feature: {
|
|
17905
|
+
group: "local",
|
|
17906
|
+
annotations: reads()
|
|
17907
|
+
},
|
|
17908
|
+
extension_build: {
|
|
17909
|
+
group: "local",
|
|
17910
|
+
annotations: acts({
|
|
17911
|
+
idempotentHint: true
|
|
17912
|
+
})
|
|
17913
|
+
},
|
|
17914
|
+
extension_dev: {
|
|
17915
|
+
group: "local",
|
|
17916
|
+
annotations: acts()
|
|
17917
|
+
},
|
|
17918
|
+
extension_start: {
|
|
17919
|
+
group: "local",
|
|
17920
|
+
annotations: acts()
|
|
17921
|
+
},
|
|
17922
|
+
extension_wait: {
|
|
17923
|
+
group: "local",
|
|
17924
|
+
annotations: reads()
|
|
17925
|
+
},
|
|
17926
|
+
extension_stop: {
|
|
17927
|
+
group: "local",
|
|
17928
|
+
annotations: acts({
|
|
17929
|
+
idempotentHint: true
|
|
17930
|
+
})
|
|
17931
|
+
},
|
|
17932
|
+
extension_manifest_validate: {
|
|
17933
|
+
group: "local",
|
|
17934
|
+
annotations: reads()
|
|
17935
|
+
},
|
|
17936
|
+
extension_theme_verify: {
|
|
17937
|
+
group: "local",
|
|
17938
|
+
annotations: reads()
|
|
17939
|
+
},
|
|
17940
|
+
extension_analyze: {
|
|
17941
|
+
group: "local",
|
|
17942
|
+
annotations: reads()
|
|
17943
|
+
},
|
|
17944
|
+
extension_assert: {
|
|
17945
|
+
group: "local",
|
|
17946
|
+
annotations: acts({
|
|
17947
|
+
idempotentHint: true
|
|
17948
|
+
})
|
|
17949
|
+
},
|
|
17950
|
+
extension_inspect: {
|
|
17951
|
+
group: "local",
|
|
17952
|
+
annotations: acts({
|
|
17953
|
+
idempotentHint: true
|
|
17954
|
+
})
|
|
17955
|
+
},
|
|
17956
|
+
extension_dom_snapshot: {
|
|
17957
|
+
group: "local",
|
|
17958
|
+
annotations: reads()
|
|
17959
|
+
},
|
|
17960
|
+
extension_list_extensions: {
|
|
17961
|
+
group: "local",
|
|
17962
|
+
annotations: reads()
|
|
17963
|
+
},
|
|
17964
|
+
extension_logs: {
|
|
17965
|
+
group: "local",
|
|
17966
|
+
annotations: reads()
|
|
17967
|
+
},
|
|
17968
|
+
extension_doctor: {
|
|
17969
|
+
group: "local",
|
|
17970
|
+
annotations: reads()
|
|
17971
|
+
},
|
|
17972
|
+
extension_eval: {
|
|
17973
|
+
group: "local",
|
|
17974
|
+
annotations: acts({
|
|
17975
|
+
destructiveHint: true,
|
|
17976
|
+
openWorldHint: true
|
|
17977
|
+
})
|
|
17978
|
+
},
|
|
17979
|
+
extension_storage: {
|
|
17980
|
+
group: "local",
|
|
17981
|
+
annotations: acts({
|
|
17982
|
+
destructiveHint: true
|
|
17983
|
+
})
|
|
17984
|
+
},
|
|
17985
|
+
extension_reload: {
|
|
17986
|
+
group: "local",
|
|
17987
|
+
annotations: acts({
|
|
17988
|
+
idempotentHint: true
|
|
17989
|
+
})
|
|
17990
|
+
},
|
|
17991
|
+
extension_open: {
|
|
17992
|
+
group: "local",
|
|
17993
|
+
annotations: acts({
|
|
17994
|
+
openWorldHint: true
|
|
17995
|
+
})
|
|
17996
|
+
},
|
|
17997
|
+
extension_browsers: {
|
|
17998
|
+
group: "local",
|
|
17999
|
+
annotations: acts({
|
|
18000
|
+
destructiveHint: true,
|
|
18001
|
+
openWorldHint: true
|
|
18002
|
+
})
|
|
18003
|
+
},
|
|
18004
|
+
extension_auth: {
|
|
18005
|
+
group: "platform",
|
|
18006
|
+
annotations: acts({
|
|
18007
|
+
openWorldHint: true
|
|
18008
|
+
})
|
|
18009
|
+
},
|
|
18010
|
+
extension_workspace_create: {
|
|
18011
|
+
group: "platform",
|
|
18012
|
+
annotations: acts({
|
|
18013
|
+
openWorldHint: true
|
|
18014
|
+
})
|
|
18015
|
+
},
|
|
18016
|
+
extension_project_create: {
|
|
18017
|
+
group: "platform",
|
|
18018
|
+
annotations: acts({
|
|
18019
|
+
openWorldHint: true
|
|
18020
|
+
})
|
|
18021
|
+
},
|
|
18022
|
+
extension_preview_web: {
|
|
18023
|
+
group: "platform",
|
|
18024
|
+
annotations: acts({
|
|
18025
|
+
openWorldHint: true
|
|
18026
|
+
}),
|
|
18027
|
+
ships: (args)=>true === args.share
|
|
18028
|
+
},
|
|
18029
|
+
extension_shares: {
|
|
18030
|
+
group: "platform",
|
|
18031
|
+
annotations: acts({
|
|
18032
|
+
destructiveHint: true,
|
|
18033
|
+
openWorldHint: true
|
|
18034
|
+
}),
|
|
18035
|
+
ships: (args)=>"revoke" === args.action
|
|
18036
|
+
},
|
|
18037
|
+
extension_publish: {
|
|
18038
|
+
group: "platform",
|
|
18039
|
+
annotations: acts({
|
|
18040
|
+
openWorldHint: true
|
|
18041
|
+
}),
|
|
18042
|
+
ships: "always"
|
|
18043
|
+
},
|
|
18044
|
+
extension_release_status: {
|
|
18045
|
+
group: "platform",
|
|
18046
|
+
annotations: reads(true)
|
|
18047
|
+
},
|
|
18048
|
+
extension_release_promote: {
|
|
18049
|
+
group: "platform",
|
|
18050
|
+
annotations: acts({
|
|
18051
|
+
destructiveHint: true,
|
|
18052
|
+
openWorldHint: true
|
|
18053
|
+
}),
|
|
18054
|
+
ships: "always"
|
|
18055
|
+
},
|
|
18056
|
+
extension_submit: {
|
|
18057
|
+
group: "platform",
|
|
18058
|
+
annotations: acts({
|
|
18059
|
+
destructiveHint: true,
|
|
18060
|
+
openWorldHint: true
|
|
18061
|
+
}),
|
|
18062
|
+
ships: (args)=>false === args.dryRun
|
|
18063
|
+
}
|
|
18064
|
+
};
|
|
18065
|
+
const DEFAULT_SERVER_OPTIONS = {
|
|
18066
|
+
features: [
|
|
18067
|
+
...FEATURE_GROUPS
|
|
18068
|
+
],
|
|
18069
|
+
noShip: false
|
|
18070
|
+
};
|
|
18071
|
+
const truthy = (raw)=>{
|
|
18072
|
+
const value = String(raw ?? "").trim().toLowerCase();
|
|
18073
|
+
return "" !== value && "0" !== value && "false" !== value && "off" !== value;
|
|
18074
|
+
};
|
|
18075
|
+
function isServerFlag(arg) {
|
|
18076
|
+
return "--no-ship" === arg || "--features" === arg || arg.startsWith("--features=") || "--project" === arg || arg.startsWith("--project=");
|
|
18077
|
+
}
|
|
18078
|
+
function resolveServerOptions(argv, env) {
|
|
18079
|
+
let rawFeatures = env[FEATURES_ENV];
|
|
18080
|
+
let noShip = truthy(env[NO_SHIP_ENV]);
|
|
18081
|
+
let project = String(env[PROJECT_PIN_ENV] || "").trim().toLowerCase();
|
|
18082
|
+
for(let i = 0; i < argv.length; i++){
|
|
18083
|
+
const arg = argv[i];
|
|
18084
|
+
if ("--no-ship" === arg) noShip = true;
|
|
18085
|
+
else if ("--features" === arg) rawFeatures = argv[++i] ?? "";
|
|
18086
|
+
else if (arg.startsWith("--features=")) rawFeatures = arg.slice(11);
|
|
18087
|
+
else if ("--project" === arg) project = String(argv[++i] ?? "").trim().toLowerCase();
|
|
18088
|
+
else {
|
|
18089
|
+
if (!arg.startsWith("--project=")) return {
|
|
18090
|
+
ok: false,
|
|
18091
|
+
message: `Unknown flag "${arg}".`
|
|
18092
|
+
};
|
|
18093
|
+
project = arg.slice(10).trim().toLowerCase();
|
|
18094
|
+
}
|
|
18095
|
+
}
|
|
18096
|
+
if (project && !isProjectRef(project)) return {
|
|
18097
|
+
ok: false,
|
|
18098
|
+
message: `--project takes "<workspace>/<project>", got "${project}".`
|
|
18099
|
+
};
|
|
18100
|
+
const pin = project ? {
|
|
18101
|
+
project
|
|
18102
|
+
} : {};
|
|
18103
|
+
if (void 0 === rawFeatures || "" === rawFeatures.trim()) return {
|
|
18104
|
+
ok: true,
|
|
18105
|
+
options: {
|
|
18106
|
+
features: [
|
|
18107
|
+
...FEATURE_GROUPS
|
|
18108
|
+
],
|
|
18109
|
+
noShip,
|
|
18110
|
+
...pin
|
|
18111
|
+
}
|
|
18112
|
+
};
|
|
18113
|
+
const names = rawFeatures.split(",").map((name)=>name.trim().toLowerCase()).filter(Boolean);
|
|
18114
|
+
const unknown = names.filter((name)=>!FEATURE_GROUPS.includes(name));
|
|
18115
|
+
if (unknown.length) return {
|
|
18116
|
+
ok: false,
|
|
18117
|
+
message: `Unknown feature group ${unknown.map((n)=>`"${n}"`).join(", ")}. Use ${FEATURE_GROUPS.join(" or ")}, comma-separated.`
|
|
18118
|
+
};
|
|
18119
|
+
const features = FEATURE_GROUPS.filter((group)=>names.includes(group));
|
|
18120
|
+
return {
|
|
18121
|
+
ok: true,
|
|
18122
|
+
options: {
|
|
18123
|
+
features,
|
|
18124
|
+
noShip,
|
|
18125
|
+
...pin
|
|
18126
|
+
}
|
|
18127
|
+
};
|
|
18128
|
+
}
|
|
18129
|
+
function isToolListed(name, options) {
|
|
18130
|
+
const policy = TOOL_POLICY[name];
|
|
18131
|
+
if (!policy) return false;
|
|
18132
|
+
if (!options.features.includes(policy.group)) return false;
|
|
18133
|
+
return !(options.noShip && "always" === policy.ships);
|
|
18134
|
+
}
|
|
18135
|
+
function disabledToolEnvelope(name, args, options) {
|
|
18136
|
+
const policy = TOOL_POLICY[name];
|
|
18137
|
+
if (!policy) return null;
|
|
18138
|
+
if (!options.features.includes(policy.group)) return envelope_envelope({
|
|
18139
|
+
ok: false,
|
|
18140
|
+
command: name,
|
|
18141
|
+
status: "tool-disabled",
|
|
18142
|
+
error: {
|
|
18143
|
+
code: "E_TOOL_DISABLED",
|
|
18144
|
+
message: `${name} is in the "${policy.group}" feature group, which this server was started without.`
|
|
18145
|
+
},
|
|
18146
|
+
value: {
|
|
18147
|
+
group: policy.group,
|
|
18148
|
+
features: options.features
|
|
18149
|
+
},
|
|
18150
|
+
hint: `Add ${policy.group} to --features (or ${FEATURES_ENV}) in this server's MCP config, or ask the user to.`
|
|
18151
|
+
});
|
|
18152
|
+
const ships = "always" === policy.ships || "function" == typeof policy.ships && policy.ships(args);
|
|
18153
|
+
if (options.noShip && ships) return envelope_envelope({
|
|
18154
|
+
ok: false,
|
|
18155
|
+
command: name,
|
|
18156
|
+
status: "tool-disabled",
|
|
18157
|
+
error: {
|
|
18158
|
+
code: "E_TOOL_DISABLED",
|
|
18159
|
+
message: `${name} with these arguments reaches people outside this machine, and this server was started in no-ship mode.`
|
|
18160
|
+
},
|
|
18161
|
+
value: {
|
|
18162
|
+
noShip: true
|
|
18163
|
+
},
|
|
18164
|
+
hint: `Dry runs, previews without share, and listing still work. To ship, the user removes --no-ship (or ${NO_SHIP_ENV}) from this server's MCP config.`
|
|
18165
|
+
});
|
|
18166
|
+
return null;
|
|
18167
|
+
}
|
|
18168
|
+
function pinProjectArgs(name, args, inputSchema, options) {
|
|
18169
|
+
const pinned = options.project;
|
|
18170
|
+
if (!pinned) return {
|
|
18171
|
+
args
|
|
18172
|
+
};
|
|
18173
|
+
const named = "string" == typeof args.project ? args.project : "";
|
|
18174
|
+
if (named && !sameProject(named, pinned)) return {
|
|
18175
|
+
refused: envelope_envelope({
|
|
18176
|
+
ok: false,
|
|
18177
|
+
command: name,
|
|
18178
|
+
status: "project-pinned",
|
|
18179
|
+
error: {
|
|
18180
|
+
code: "E_TOOL_DISABLED",
|
|
18181
|
+
message: `This server is pinned to ${pinned} and was asked to act on ${named}.`
|
|
18182
|
+
},
|
|
18183
|
+
value: {
|
|
18184
|
+
pinned,
|
|
18185
|
+
named
|
|
18186
|
+
},
|
|
18187
|
+
hint: `Work on ${named} through an MCP server configured with --project ${named}, or change this server's --project (or ${PROJECT_PIN_ENV}).`
|
|
18188
|
+
})
|
|
18189
|
+
};
|
|
18190
|
+
const properties = inputSchema.properties ?? {};
|
|
18191
|
+
const statusOnly = "extension_auth" === name && "status" === args.action;
|
|
18192
|
+
if (named || statusOnly || !("project" in properties)) return {
|
|
18193
|
+
args
|
|
18194
|
+
};
|
|
18195
|
+
return {
|
|
18196
|
+
args: {
|
|
18197
|
+
...args,
|
|
18198
|
+
project: pinned
|
|
18199
|
+
}
|
|
18200
|
+
};
|
|
18201
|
+
}
|
|
17846
18202
|
const tools = [
|
|
17847
18203
|
create_namespaceObject,
|
|
17848
18204
|
templates_namespaceObject,
|
|
@@ -17898,8 +18254,8 @@ function toolResultFrame(result) {
|
|
|
17898
18254
|
}
|
|
17899
18255
|
const toolMap = new Map();
|
|
17900
18256
|
for (const tool of tools)toolMap.set(tool.schema.name, tool);
|
|
17901
|
-
const SERVER_INSTRUCTIONS = "extension-dev runs, inspects, drives, builds and publishes browser extensions (Chrome, Edge, Firefox, Safari and the other Chromium and Gecko browsers) through Extension.js and extension.dev.\nWhen the ask is to run, start, wait for, watch, inspect, drive, test, debug or build a browser extension, search this server first and use its tools: extension_dev starts the dev session (allowEval: true also turns on control), extension_wait blocks until it is ready, extension_logs streams its console, extension_open opens a surface or a url, extension_dom_snapshot and extension_inspect read a live page, extension_eval runs code in a context, extension_build makes a store-ready bundle, extension_stop ends the session.\nThese replace hand-rolled ps, curl, remote-debugging-port lookups and CDP or Playwright scripts: the server already holds the session's debug port, the extension id and the session token.\nEvery tool answers one JSON envelope {ok, status, value, error, hint, warnings}; read hint and warnings before choosing the next call, and treat ok: false as the answer, not a transport error.";
|
|
17902
|
-
function createServer() {
|
|
18257
|
+
const SERVER_INSTRUCTIONS = "extension-dev runs, inspects, drives, builds and publishes browser extensions (Chrome, Edge, Firefox, Safari and the other Chromium and Gecko browsers) through Extension.js and extension.dev.\nWhen the ask is to run, start, wait for, watch, inspect, drive, test, debug or build a browser extension, search this server first and use its tools: extension_dev starts the dev session (allowEval: true also turns on control), extension_wait blocks until it is ready, extension_logs streams its console, extension_open opens a surface or a url, extension_dom_snapshot and extension_inspect read a live page, extension_eval runs code in a context, extension_build makes a store-ready bundle, extension_stop ends the session.\nThese replace hand-rolled ps, curl, remote-debugging-port lookups and CDP or Playwright scripts: the server already holds the session's debug port, the extension id and the session token.\nEvery tool answers one JSON envelope {ok, status, value, error, hint, warnings}; read hint and warnings before choosing the next call, and treat ok: false as the answer, not a transport error. status authorization-pending is ok: true and means a human approves at the link in hint before you call again; it is not done yet.";
|
|
18258
|
+
function createServer(options = DEFAULT_SERVER_OPTIONS) {
|
|
17903
18259
|
const server = new Server({
|
|
17904
18260
|
name: "extension-dev",
|
|
17905
18261
|
version: package_namespaceObject.rE
|
|
@@ -17910,10 +18266,11 @@ function createServer() {
|
|
|
17910
18266
|
instructions: SERVER_INSTRUCTIONS
|
|
17911
18267
|
});
|
|
17912
18268
|
server.setRequestHandler(ListToolsRequestSchema, async ()=>({
|
|
17913
|
-
tools: tools.map((t)=>({
|
|
18269
|
+
tools: tools.filter((t)=>isToolListed(t.schema.name, options)).map((t)=>({
|
|
17914
18270
|
name: t.schema.name,
|
|
17915
18271
|
description: t.schema.description,
|
|
17916
|
-
inputSchema: t.schema.inputSchema
|
|
18272
|
+
inputSchema: t.schema.inputSchema,
|
|
18273
|
+
annotations: TOOL_POLICY[t.schema.name].annotations
|
|
17917
18274
|
}))
|
|
17918
18275
|
}));
|
|
17919
18276
|
server.setRequestHandler(CallToolRequestSchema, async (request)=>{
|
|
@@ -17932,14 +18289,35 @@ function createServer() {
|
|
|
17932
18289
|
message: `Unknown tool: ${name}`
|
|
17933
18290
|
},
|
|
17934
18291
|
value: {
|
|
17935
|
-
availableTools: tools.map((t)=>t.schema.name)
|
|
18292
|
+
availableTools: tools.map((t)=>t.schema.name).filter((toolName)=>isToolListed(toolName, options))
|
|
17936
18293
|
}
|
|
17937
18294
|
})
|
|
17938
18295
|
}
|
|
17939
18296
|
],
|
|
17940
18297
|
isError: true
|
|
17941
18298
|
};
|
|
17942
|
-
const
|
|
18299
|
+
const aliasedArgs = normalizeArgAliases(tool.schema.inputSchema, args ?? {});
|
|
18300
|
+
const disabled = disabledToolEnvelope(name, aliasedArgs, options);
|
|
18301
|
+
if (disabled) return {
|
|
18302
|
+
content: [
|
|
18303
|
+
{
|
|
18304
|
+
type: "text",
|
|
18305
|
+
text: disabled
|
|
18306
|
+
}
|
|
18307
|
+
],
|
|
18308
|
+
isError: true
|
|
18309
|
+
};
|
|
18310
|
+
const pinned = pinProjectArgs(name, aliasedArgs, tool.schema.inputSchema, options);
|
|
18311
|
+
if ("refused" in pinned) return {
|
|
18312
|
+
content: [
|
|
18313
|
+
{
|
|
18314
|
+
type: "text",
|
|
18315
|
+
text: pinned.refused
|
|
18316
|
+
}
|
|
18317
|
+
],
|
|
18318
|
+
isError: true
|
|
18319
|
+
};
|
|
18320
|
+
const normalizedArgs = pinned.args;
|
|
17943
18321
|
const issues = validateToolInput(tool.schema.inputSchema, normalizedArgs);
|
|
17944
18322
|
if (issues.length) return {
|
|
17945
18323
|
content: [
|
|
@@ -17976,9 +18354,10 @@ function createServer() {
|
|
|
17976
18354
|
});
|
|
17977
18355
|
return server;
|
|
17978
18356
|
}
|
|
17979
|
-
async function startServer() {
|
|
18357
|
+
async function startServer(options = DEFAULT_SERVER_OPTIONS) {
|
|
17980
18358
|
installCarrierExitCleanup();
|
|
17981
|
-
|
|
18359
|
+
if (options.project) process.env.EXTENSION_DEV_PROJECT = options.project;
|
|
18360
|
+
const server = createServer(options);
|
|
17982
18361
|
const transport = new StdioServerTransport();
|
|
17983
18362
|
await server.connect(transport);
|
|
17984
18363
|
}
|
|
@@ -18074,4 +18453,4 @@ async function runCli(cmd, args) {
|
|
|
18074
18453
|
return 1;
|
|
18075
18454
|
}
|
|
18076
18455
|
var version_0 = package_namespaceObject.rE;
|
|
18077
|
-
export { runCli, startServer, version_0 as version };
|
|
18456
|
+
export { isServerFlag, resolveServerOptions, runCli, startServer, version_0 as version };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export type ClientId = "claude-code" | "cursor" | "vscode" | "codex" | "json";
|
|
2
|
+
export type Reach = "everything" | "no-ship" | "local";
|
|
3
|
+
export interface ClientInfo {
|
|
4
|
+
id: ClientId;
|
|
5
|
+
label: string;
|
|
6
|
+
subtitle?: string;
|
|
7
|
+
}
|
|
8
|
+
export declare const CLIENTS: readonly ClientInfo[];
|
|
9
|
+
export interface Recipe {
|
|
10
|
+
client: ClientId;
|
|
11
|
+
command?: string;
|
|
12
|
+
config?: {
|
|
13
|
+
language: "json" | "toml";
|
|
14
|
+
path: string;
|
|
15
|
+
text: string;
|
|
16
|
+
};
|
|
17
|
+
deeplink?: string;
|
|
18
|
+
login: string;
|
|
19
|
+
}
|
|
20
|
+
export interface RecipeInput {
|
|
21
|
+
client: ClientId;
|
|
22
|
+
reach: Reach;
|
|
23
|
+
strictApproval: boolean;
|
|
24
|
+
project: string;
|
|
25
|
+
}
|
|
26
|
+
export declare const SERVER_NAME = "extension-dev";
|
|
27
|
+
export declare const PACKAGE = "@extension.dev/mcp";
|
|
28
|
+
export declare function serverArgs(input: Omit<RecipeInput, "client">): string[];
|
|
29
|
+
export declare function serverEnv(input: Omit<RecipeInput, "client">): Record<string, string>;
|
|
30
|
+
export declare function loginCommand(project: string): string;
|
|
31
|
+
export declare function buildRecipe(input: RecipeInput): Recipe;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const README_SETUP_START = "<!-- setup:start (generated from src/clients, run pnpm readme:clients) -->";
|
|
2
|
+
export declare const README_SETUP_END = "<!-- setup:end -->";
|
|
3
|
+
export declare function renderReadmeSetup(): string;
|
|
4
|
+
export declare function spliceReadmeSetup(readme: string): string;
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
2
|
import { version } from "../package.json";
|
|
3
3
|
export { version };
|
|
4
|
+
import { type ServerOptions } from "./lib/tool-policy";
|
|
4
5
|
export interface ToolModule {
|
|
5
6
|
schema: {
|
|
6
7
|
name: string;
|
|
@@ -18,6 +19,6 @@ export declare function toolResultFrame(result: string): {
|
|
|
18
19
|
isError?: boolean;
|
|
19
20
|
};
|
|
20
21
|
export declare const SERVER_INSTRUCTIONS: string;
|
|
21
|
-
export declare function createServer(): Server;
|
|
22
|
-
export declare function startServer(): Promise<void>;
|
|
22
|
+
export declare function createServer(options?: ServerOptions): Server;
|
|
23
|
+
export declare function startServer(options?: ServerOptions): Promise<void>;
|
|
23
24
|
export declare function runCli(cmd: string, args: string[]): Promise<number>;
|
|
@@ -2,7 +2,11 @@ export declare const APPROVAL_GATE_ENV = "EXTENSION_DEV_APPROVAL_GATE";
|
|
|
2
2
|
export declare const APPROVAL_REQUIRED_STATUS = "approval-required";
|
|
3
3
|
export declare const APPROVAL_PENDING_STATUS = "approval-pending";
|
|
4
4
|
export declare const APPROVAL_REJECTED_STATUS = "approval-rejected";
|
|
5
|
-
export declare function approvalGateEnabled(): boolean;
|
|
5
|
+
export declare function approvalGateEnabled(defaultOn?: boolean): boolean;
|
|
6
|
+
export declare function platformRequiresApproval(res: {
|
|
7
|
+
status: number;
|
|
8
|
+
}, data: unknown): boolean;
|
|
9
|
+
export declare function requestApprovalAfterRefusal(input: ApprovalGateInput): Promise<string>;
|
|
6
10
|
export type ApprovalScope = Record<string, string | string[]>;
|
|
7
11
|
export declare function actionFingerprint(action: string, scope: ApprovalScope): string;
|
|
8
12
|
export interface ApprovalGateInput {
|
|
@@ -18,6 +18,10 @@ export interface CredentialSelector {
|
|
|
18
18
|
}
|
|
19
19
|
export declare function credentialKey(workspaceSlug: string, projectSlug: string): string;
|
|
20
20
|
export declare function readCredentialStore(): CredentialStore | null;
|
|
21
|
+
export declare const PROJECT_PIN_ENV = "EXTENSION_DEV_PROJECT";
|
|
22
|
+
export declare function pinnedProject(): string;
|
|
23
|
+
export declare function isProjectRef(value: string): boolean;
|
|
24
|
+
export declare function sameProject(named: string, pinned: string): boolean;
|
|
21
25
|
export declare function readCredentials(selector?: CredentialSelector): StoredCredentials | null;
|
|
22
26
|
export declare function listCredentials(): Array<StoredCredentials & {
|
|
23
27
|
key: string;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const ENVELOPE_SCHEMA: 1;
|
|
2
2
|
export declare const DECISION_D6 = "The MCP tool name owns the envelope `command` key; forking the key to `tool` would fork the schema the checksum test pins.";
|
|
3
|
-
export type ErrorCode = "E_AMBIGUOUS_TARGET" | "E_APPROVAL_PENDING" | "E_APPROVAL_REJECTED" | "E_APPROVAL_REQUIRED" | "E_AUTH_DENIED" | "E_AUTH_EXPIRED" | "E_AUTH_FAILED" | "
|
|
3
|
+
export type ErrorCode = "E_AMBIGUOUS_TARGET" | "E_APPROVAL_PENDING" | "E_APPROVAL_REJECTED" | "E_APPROVAL_REQUIRED" | "E_AUTH_DENIED" | "E_AUTH_EXPIRED" | "E_AUTH_FAILED" | "E_AUTH_REQUIRED" | "E_BAD_HOST_URL" | "E_BAD_MANIFEST" | "E_BAD_REQUEST" | "E_BRIDGE" | "E_BROWSER_EXITED" | "E_BROWSER_INSTALL" | "E_BROWSER_UNINSTALL" | "E_BUILD_FAILED" | "E_CARRIER_IN_DIST" | "E_CDP" | "E_CLI" | "E_COMPILE" | "E_CONFIG" | "E_CONTRACT_ERROR" | "E_CONTROL_CHANNEL" | "E_CONTROL_ENVELOPE" | "E_ENGINE_TOO_OLD" | "E_ENTRYPOINT_MISSING" | "E_FIRST_COMPILE" | "E_INPUT_VALIDATION" | "E_INTERNAL" | "E_INVALID_BUNDLE_ID" | "E_LOGS_MISSING" | "E_MANIFEST_BLOCKING" | "E_MANIFEST_NOT_FOUND" | "E_NAVIGATE_ERROR" | "E_NAVIGATE_FAILED" | "E_NETWORK" | "E_NO_CONTROL_CHANNEL" | "E_NO_DIST" | "E_NO_EXTENSION_ID" | "E_NO_MATCHING_TARGET" | "E_NO_REFERENCE_TEMPLATE" | "E_NO_SESSION" | "E_NO_SURFACE_DOCUMENT" | "E_NO_TARGET" | "E_NOT_ATTACHED" | "E_PARSE" | "E_PLATFORM" | "E_PREVIEW_HOST_UNREACHABLE" | "E_PROFILE_LOCKED" | "E_RDP" | "E_SAFARI_ONLY_OPTION" | "E_EVAL" | "E_SCAFFOLD_FAILED" | "E_SCAFFOLD_INCOMPLETE" | "E_SESSION_EXISTS" | "E_SESSION_EXITED" | "E_STALE_CONTRACT" | "E_SURFACE_DID_NOT_OPEN" | "E_TEMPLATE_FETCH" | "E_TEMPLATE_NOT_FOUND" | "E_TOOL_DISABLED" | "E_UNKNOWN_COMMAND" | "E_UNKNOWN_TOOL" | "E_UNSUPPORTED_BROWSER" | "E_WAIT_TIMEOUT";
|
|
4
4
|
export declare const ERROR_CODES: ErrorCode[];
|
|
5
5
|
export interface EnvelopeError {
|
|
6
6
|
code: ErrorCode | string;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type FeatureGroup = "local" | "platform";
|
|
2
|
+
export declare const FEATURE_GROUPS: FeatureGroup[];
|
|
3
|
+
export declare const FEATURES_ENV = "EXTENSION_DEV_FEATURES";
|
|
4
|
+
export declare const NO_SHIP_ENV = "EXTENSION_DEV_NO_SHIP";
|
|
5
|
+
export interface ToolAnnotations {
|
|
6
|
+
readOnlyHint: boolean;
|
|
7
|
+
destructiveHint: boolean;
|
|
8
|
+
idempotentHint: boolean;
|
|
9
|
+
openWorldHint: boolean;
|
|
10
|
+
}
|
|
11
|
+
type Args = Record<string, unknown>;
|
|
12
|
+
export interface ToolPolicy {
|
|
13
|
+
group: FeatureGroup;
|
|
14
|
+
annotations: ToolAnnotations;
|
|
15
|
+
ships?: "always" | ((args: Args) => boolean);
|
|
16
|
+
}
|
|
17
|
+
export declare const TOOL_POLICY: Record<string, ToolPolicy>;
|
|
18
|
+
export interface ServerOptions {
|
|
19
|
+
features: FeatureGroup[];
|
|
20
|
+
noShip: boolean;
|
|
21
|
+
project?: string;
|
|
22
|
+
}
|
|
23
|
+
export declare const DEFAULT_SERVER_OPTIONS: ServerOptions;
|
|
24
|
+
export declare function isServerFlag(arg: string): boolean;
|
|
25
|
+
export declare function resolveServerOptions(argv: string[], env: Record<string, string | undefined>): {
|
|
26
|
+
ok: true;
|
|
27
|
+
options: ServerOptions;
|
|
28
|
+
} | {
|
|
29
|
+
ok: false;
|
|
30
|
+
message: string;
|
|
31
|
+
};
|
|
32
|
+
export declare function isToolListed(name: string, options: ServerOptions): boolean;
|
|
33
|
+
export declare function disabledToolEnvelope(name: string, args: Args, options: ServerOptions): string | null;
|
|
34
|
+
export declare function pinProjectArgs(name: string, args: Args, inputSchema: Record<string, unknown>, options: ServerOptions): {
|
|
35
|
+
args: Args;
|
|
36
|
+
} | {
|
|
37
|
+
refused: string;
|
|
38
|
+
};
|
|
39
|
+
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.4",
|
|
5
5
|
"description": "MCP server that lets AI agents (Claude Code, Claude Desktop, Cursor, Copilot, Codex) build, run, inspect, and publish browser extensions. 31 tools for scaffolding, live DOM inspection, log streaming, and store-ready builds across Chrome, Edge, Firefox, Safari, and every Chromium- or Gecko-based browser (Brave, Opera, Vivaldi, Yandex, Waterfox, LibreWolf). Powered by extension.dev and Extension.js.",
|
|
6
6
|
"mcpName": "io.github.extensiondev/mcp",
|
|
7
7
|
"license": "Apache-2.0",
|
|
@@ -26,6 +26,10 @@
|
|
|
26
26
|
".": {
|
|
27
27
|
"types": "./dist/module.d.ts",
|
|
28
28
|
"import": "./dist/module.js"
|
|
29
|
+
},
|
|
30
|
+
"./clients": {
|
|
31
|
+
"types": "./dist/src/clients/index.d.ts",
|
|
32
|
+
"import": "./dist/clients.js"
|
|
29
33
|
}
|
|
30
34
|
},
|
|
31
35
|
"main": "./dist/module.js",
|
|
@@ -46,6 +50,7 @@
|
|
|
46
50
|
],
|
|
47
51
|
"scripts": {
|
|
48
52
|
"clean": "rm -rf dist",
|
|
53
|
+
"readme:clients": "UPDATE_README=1 vitest run src/__tests__/readme-clients.test.ts",
|
|
49
54
|
"watch": "rslib build --watch",
|
|
50
55
|
"compile": "rslib build",
|
|
51
56
|
"build": "pnpm run compile",
|
|
@@ -91,9 +96,9 @@
|
|
|
91
96
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
92
97
|
"browser-extension-manifest-fields": "^2.2.9",
|
|
93
98
|
"cross-spawn": "^7.0.6",
|
|
94
|
-
"extension-create": "4.1.31
|
|
95
|
-
"extension-develop": "4.1.31
|
|
96
|
-
"extension-install": "4.1.31
|
|
99
|
+
"extension-create": "4.1.31",
|
|
100
|
+
"extension-develop": "4.1.31",
|
|
101
|
+
"extension-install": "4.1.31",
|
|
97
102
|
"ws": "^8.20.0"
|
|
98
103
|
},
|
|
99
104
|
"devDependencies": {
|
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.4",
|
|
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.4",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|