@extension.dev/mcp 3.17.0-canary.1779910200.ddcd9eb → 3.17.0
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 +48 -0
- package/LICENSE +21 -0
- package/README.md +52 -10
- package/bin/extension-mcp.js +15 -2
- package/claude/CLAUDE.md +8 -0
- package/claude/commands/extension-debug.md +12 -2
- package/claude/rules/mcp-tools.md +7 -0
- package/dist/module.d.ts +1 -1
- package/dist/module.js +614 -25
- package/dist/src/index.d.ts +8 -0
- package/dist/src/lib/credentials.d.ts +24 -0
- package/dist/src/lib/github-device.d.ts +40 -0
- package/dist/src/lib/login-flow.d.ts +25 -0
- package/dist/src/tools/list-extensions.d.ts +22 -0
- package/dist/src/tools/login.d.ts +27 -0
- package/dist/src/tools/logout.d.ts +9 -0
- package/dist/src/tools/open.d.ts +5 -0
- package/dist/src/tools/publish.d.ts +2 -0
- package/dist/src/tools/whoami.d.ts +9 -0
- package/package.json +28 -21
- package/dist/rslib.config.d.ts +0 -2
- package/dist/vitest.config.d.ts +0 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,18 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 3.17.0
|
|
4
|
+
|
|
5
|
+
First stable release on npm. The registry previously carried only canary
|
|
6
|
+
builds (3.17.0-canary.*), so `npx @extension.dev/mcp` resolved a canary;
|
|
7
|
+
this release graduates that line to stable and becomes `latest`.
|
|
8
|
+
|
|
9
|
+
- 26 tools across scaffolding, build/dev/preview, live inspection (CDP +
|
|
10
|
+
agent bridge), act tools (eval/storage/reload/open), browser management,
|
|
11
|
+
and platform auth/publish.
|
|
12
|
+
- Claude Code integration assets (CLAUDE.md, slash commands, rules) and the
|
|
13
|
+
@extension.dev/skill pairing.
|
|
14
|
+
- MIT license shipped; repository moved to extensiondev/mcp.
|
|
15
|
+
|
|
1
16
|
# @extension.dev/mcp — Changelog
|
|
2
17
|
|
|
3
18
|
## Unreleased — agent-bridge tools
|
|
@@ -34,3 +49,36 @@ New tools (22 total):
|
|
|
34
49
|
Internal: `lib/act` (CLI shell-out helper), `lib/exec.runExtensionCli` (capture),
|
|
35
50
|
`lib/cdp.getClosedShadowRoots`. Test infra aligned to the workspace vitest
|
|
36
51
|
catalog.
|
|
52
|
+
|
|
53
|
+
## Unreleased — login (auth tools)
|
|
54
|
+
|
|
55
|
+
Adds the missing `login` flow so `extension_publish` no longer requires the user
|
|
56
|
+
to mint and export `EXTENSION_DEV_TOKEN` by hand. Auth stays auth-AWARE: the
|
|
57
|
+
token lives in a local credentials file, never in the MCP process state or logs.
|
|
58
|
+
|
|
59
|
+
New tools (25 total):
|
|
60
|
+
|
|
61
|
+
- **`extension_login`** — GitHub **device-code** flow (no local server; works
|
|
62
|
+
headless). Two-phase: call with `project` (`<workspace>/<project>`) to get a
|
|
63
|
+
code + URL, call again with the returned `deviceCode` to finish. On success it
|
|
64
|
+
writes a project-scoped token to the credentials file. Never returns the token.
|
|
65
|
+
- **`extension_whoami`** — report the stored workspace/project and token expiry
|
|
66
|
+
without revealing the token.
|
|
67
|
+
- **`extension_logout`** — delete the local credentials file.
|
|
68
|
+
|
|
69
|
+
Token resolution for publish is now `EXTENSION_DEV_TOKEN` env **>** the
|
|
70
|
+
credentials file (expired file tokens are ignored).
|
|
71
|
+
|
|
72
|
+
Credentials file (versioned, `0600`): `$XDG_CONFIG_HOME/extension-dev/auth.json`
|
|
73
|
+
(or `~/.config/...`; `%APPDATA%\extension-dev\auth.json` on Windows).
|
|
74
|
+
|
|
75
|
+
Platform endpoints this depends on (in `apps/www.extension.dev`):
|
|
76
|
+
|
|
77
|
+
- `GET /api/cli/login/config` — public GitHub OAuth client id + scope.
|
|
78
|
+
- `POST /api/cli/login/exchange` — trades a GitHub **user** token for a
|
|
79
|
+
project-scoped access token after checking workspace membership. Modeled on
|
|
80
|
+
`/api/oidc/exchange`; tokens are recorded so they stay revocable.
|
|
81
|
+
|
|
82
|
+
> ⚠️ **Ops:** the device flow requires **device flow enabled** on the GitHub
|
|
83
|
+
> OAuth App behind `WWW_GITHUB_OAUTH_CLIENT_ID`. Until then, `extension_login`
|
|
84
|
+
> can't complete and users fall back to a dashboard-minted `EXTENSION_DEV_TOKEN`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Extension Dev
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,27 +1,46 @@
|
|
|
1
|
+
[npm-version-image]: https://img.shields.io/npm/v/%40extension.dev%2Fmcp
|
|
2
|
+
[npm-version-url]: https://www.npmjs.com/package/@extension.dev/mcp
|
|
3
|
+
[action-image]: https://github.com/extensiondev/mcp/actions/workflows/ci.yml/badge.svg?branch=main
|
|
4
|
+
[action-url]: https://github.com/extensiondev/mcp/actions
|
|
5
|
+
|
|
6
|
+
[![Version][npm-version-image]][npm-version-url] [![workflow][action-image]][action-url]
|
|
7
|
+
|
|
1
8
|
# @extension.dev/mcp
|
|
2
9
|
|
|
3
|
-
|
|
10
|
+
Give your AI agent hands for browser extension development. One command connects Claude Code, Claude Desktop, Cursor, or any MCP client to 26 tools that scaffold, run, inspect, debug, and publish cross-browser extensions:
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
claude mcp add extension-dev npx @extension.dev/mcp
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Extensions fail silently: content scripts that never inject, panels that never open, permissions that return `undefined` with no error. These tools give agents eyes on the live browser (DOM probes, unified logs from every context, storage access, event replay) so they debug from evidence instead of guessing. Scaffolding draws on the 60+ template catalog of the [extension.dev](https://extension.dev) platform, built on [Extension.js](https://extension.js.org).
|
|
4
17
|
|
|
5
18
|
## What's in this package
|
|
6
19
|
|
|
7
20
|
```
|
|
8
21
|
@extension.dev/mcp
|
|
9
|
-
src/ MCP server source (
|
|
22
|
+
src/ MCP server source (26 tools)
|
|
10
23
|
claude/ Claude Code integration (CLAUDE.md, slash commands, rules)
|
|
11
24
|
bin/ CLI entrypoint
|
|
12
25
|
```
|
|
13
26
|
|
|
14
|
-
**MCP server
|
|
27
|
+
**MCP server**: programmatic bridge between AI assistants and the extension.dev platform. Imports from published npm packages:
|
|
15
28
|
|
|
16
|
-
- `extension-create
|
|
17
|
-
- `extension-develop
|
|
18
|
-
- `extension-install
|
|
29
|
+
- `extension-create`: project scaffolding
|
|
30
|
+
- `extension-develop`: build/dev/preview
|
|
31
|
+
- `extension-install`: managed browser binaries
|
|
19
32
|
|
|
20
|
-
**Claude Code integration
|
|
33
|
+
**Claude Code integration**: drop-in instructions, slash commands, and rules for Claude Code:
|
|
21
34
|
|
|
22
|
-
- `claude/CLAUDE.md
|
|
23
|
-
- `claude/commands
|
|
24
|
-
- `claude/rules
|
|
35
|
+
- `claude/CLAUDE.md`: project-level instructions for any extension project
|
|
36
|
+
- `claude/commands/`: slash commands (`/extension`, `/extension-add`, `/extension-debug`, `/extension-publish`)
|
|
37
|
+
- `claude/rules/`: rules for extension development, cross-browser compat, and MCP tools
|
|
38
|
+
|
|
39
|
+
**Agent Skill**: the knowledge companion to this server lives in [`@extension.dev/skill`](https://npmjs.com/package/@extension.dev/skill): a portable [Agent Skills](https://agentskills.io)-format skill (SKILL.md + references) covering cross-browser rules, silent-failure gotchas, debugging playbooks, and store publishing. This server gives agents hands; the skill gives them judgment. Pair them:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
mkdir -p .claude/skills && cp -R node_modules/@extension.dev/skill/skills/extension-dev .claude/skills/
|
|
43
|
+
```
|
|
25
44
|
|
|
26
45
|
Browser-launching tools (`dev`, `start`, `preview`) shell out to the `extension` CLI since they require the full browser launcher infrastructure.
|
|
27
46
|
|
|
@@ -73,11 +92,22 @@ cp node_modules/@extension.dev/mcp/claude/commands/*.md ~/my-extension/.claude/c
|
|
|
73
92
|
| 2 | `extension_manifest_validate` | native | Cross-browser manifest validation |
|
|
74
93
|
| 2 | `extension_inspect` | native | Build output analysis |
|
|
75
94
|
| 2 | `extension_source_inspect` | CDP WebSocket | Live DOM inspection |
|
|
95
|
+
| 2 | `extension_dom_inspect` | agent bridge | CDP-free DOM snapshot |
|
|
96
|
+
| 2 | `extension_list_extensions` | CDP WebSocket | List loaded extensions (Chromium) |
|
|
97
|
+
| 2 | `extension_logs` | agent bridge | Stream logs from every context |
|
|
76
98
|
| 2 | `extension_wait` | native | Poll ready.json contract |
|
|
77
99
|
| 2 | `extension_add_feature` | native | Add sidebar/popup/content script |
|
|
100
|
+
| act | `extension_eval` | agent bridge | Eval in a context (`--allow-eval`) |
|
|
101
|
+
| act | `extension_storage` | agent bridge | Read/write `chrome.storage` |
|
|
102
|
+
| act | `extension_reload` | agent bridge | Reload extension or tab |
|
|
103
|
+
| act | `extension_open` | agent bridge | Open surface / trigger `action`,`command` |
|
|
78
104
|
| 3 | `extension_install_browser` | `extensionInstall()` | Install managed browser |
|
|
79
105
|
| 3 | `extension_list_browsers` | `getManagedBrowsersCacheRoot()` | List managed browsers |
|
|
80
106
|
| 3 | `extension_detect_browsers` | native | System browser detection |
|
|
107
|
+
| auth | `extension_login` | platform | GitHub device-code → stored token |
|
|
108
|
+
| auth | `extension_whoami` | native | Show stored login (no token) |
|
|
109
|
+
| auth | `extension_logout` | native | Remove stored credentials |
|
|
110
|
+
| auth | `extension_publish` | platform | Publish to extension.dev (token) |
|
|
81
111
|
|
|
82
112
|
## Development
|
|
83
113
|
|
|
@@ -96,6 +126,18 @@ NPM_TOKEN=<token> pnpm publish
|
|
|
96
126
|
|
|
97
127
|
Uses `prepublishOnly` to run tests and compile before publishing.
|
|
98
128
|
|
|
129
|
+
## The extension.dev open source stack
|
|
130
|
+
|
|
131
|
+
| Package | Use it to |
|
|
132
|
+
| --- | --- |
|
|
133
|
+
| [`@extension.dev/skill`](https://github.com/extensiondev/skill) | Teach agents the cross-browser rules and silent-failure gotchas |
|
|
134
|
+
| [`@extension.dev/deploy`](https://github.com/extensiondev/deploy) | Ship to Chrome, Firefox, and Edge stores from CI |
|
|
135
|
+
| [`@extension.dev/artifact-integrity`](https://github.com/extensiondev/artifact-integrity) | Gate releases on artifact verification |
|
|
136
|
+
| [`@extension.dev/compiler`](https://github.com/extensiondev/compiler) | Build extensions in the browser with esbuild-wasm |
|
|
137
|
+
| [`@extension.dev/core`](https://github.com/extensiondev/core) | Authenticate and publish to the extension.dev platform |
|
|
138
|
+
|
|
139
|
+
All of it rides on [Extension.js](https://github.com/extension-js/extension.js), the open-source cross-browser extension framework.
|
|
140
|
+
|
|
99
141
|
## License
|
|
100
142
|
|
|
101
143
|
MIT
|
package/bin/extension-mcp.js
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {startServer} from '../dist/module.js'
|
|
3
|
-
|
|
2
|
+
import {startServer, runCli} from '../dist/module.js'
|
|
3
|
+
|
|
4
|
+
const [, , cmd, ...rest] = process.argv
|
|
5
|
+
|
|
6
|
+
if (cmd && ['login', 'logout', 'whoami'].includes(cmd)) {
|
|
7
|
+
runCli(cmd, rest)
|
|
8
|
+
.then((code) => process.exit(code))
|
|
9
|
+
.catch((err) => {
|
|
10
|
+
process.stderr.write(`${err?.message || String(err)}\n`)
|
|
11
|
+
process.exit(1)
|
|
12
|
+
})
|
|
13
|
+
} else {
|
|
14
|
+
// No subcommand: run as the MCP stdio server (the default and primary mode).
|
|
15
|
+
startServer()
|
|
16
|
+
}
|
package/claude/CLAUDE.md
CHANGED
|
@@ -242,6 +242,14 @@ npm run dev -- --logs info --log-url "example.com"
|
|
|
242
242
|
- Use `--wait` flag to check if dev session is ready (outputs ready.json contract)
|
|
243
243
|
- Use `npm run start` to test production builds (builds first, then launches)
|
|
244
244
|
|
|
245
|
+
### Triggering events without clicking (requires `--allow-control`)
|
|
246
|
+
|
|
247
|
+
- `extension open action` — fire the toolbar action (opens its popup, or replays `chrome.action.onClicked`).
|
|
248
|
+
- `extension open command --name <cmd>` — replay a `chrome.commands.onCommand` keyboard shortcut.
|
|
249
|
+
- `extension_list_extensions` (MCP tool; no CLI verb) — list extensions with a live context in the browser (Chromium, read-only).
|
|
250
|
+
|
|
251
|
+
These replay your captured listeners, so they work on Chrome and Firefox — but carry **no user gesture**, so `activeTab` is not granted (the result reports `gesture: false`, plus a `warning` when the manifest declares `activeTab`). If you need a genuine-gesture click (activeTab granted), use [chrome-devtools-mcp](https://github.com/ChromeDevTools/chrome-devtools-mcp)'s `trigger_extension_action` (Chromium only) alongside this server.
|
|
252
|
+
|
|
245
253
|
## Contributing templates to the examples repo
|
|
246
254
|
|
|
247
255
|
If you create a new extension pattern worth sharing:
|
|
@@ -24,7 +24,17 @@ Debug the currently running extension dev session. The user said: $ARGUMENTS
|
|
|
24
24
|
- Read `dist/extension-js/chrome/ready.json` to get the CDP port
|
|
25
25
|
- Report the port and suggest Chrome DevTools inspection
|
|
26
26
|
|
|
27
|
-
3. **
|
|
27
|
+
3. **Exercise event handlers (when the bug is in an action / command / shortcut)**
|
|
28
|
+
If the session was started with `--allow-control`, fire the events a user would, without clicking:
|
|
29
|
+
- `extension_open` with `surface: "action"` — triggers the toolbar action (opens its popup, or replays `chrome.action.onClicked`).
|
|
30
|
+
- `extension_open` with `surface: "command"` and `name: "<cmd>"` — replays a `chrome.commands.onCommand` keyboard shortcut.
|
|
31
|
+
- Then re-inspect or read `extension_logs` to see what the handler did.
|
|
32
|
+
|
|
33
|
+
**Caveat:** replay carries no user gesture, so `activeTab` is NOT granted. If the handler depends on `activeTab` (e.g. `chrome.scripting.executeScript` on the active tab, `captureVisibleTab`), the result includes `gesture: false` and a `warning`, and behavior differs from a real click. For a genuine-gesture click (activeTab granted), use chrome-devtools-mcp's `trigger_extension_action` (Chromium only) alongside this server.
|
|
34
|
+
|
|
35
|
+
To see what else is loaded in the browser (Chromium): `extension_list_extensions`.
|
|
36
|
+
|
|
37
|
+
4. **Diagnose common issues**
|
|
28
38
|
Based on what you find, check for:
|
|
29
39
|
- **"It didn't load"**: Check extension root count. If 0, content scripts may not be injecting. Check manifest `content_scripts` matches patterns and the target URL.
|
|
30
40
|
- **Console errors**: Report top errors. Common ones:
|
|
@@ -34,7 +44,7 @@ Debug the currently running extension dev session. The user said: $ARGUMENTS
|
|
|
34
44
|
- **Wrong page**: Check if the content script `matches` pattern in manifest covers the target URL
|
|
35
45
|
- **Shadow DOM empty**: Extension root exists but shadow content is empty — likely a CSS or framework mounting issue
|
|
36
46
|
|
|
37
|
-
|
|
47
|
+
5. **Suggest fixes** based on the diagnosis
|
|
38
48
|
|
|
39
49
|
## Examples
|
|
40
50
|
|
|
@@ -719,8 +719,15 @@ The `similarTemplates` field lists templates from the catalog with similar surfa
|
|
|
719
719
|
| `extension_manifest_validate` | `programs/develop` | `plugin-web-extension` | `templates-meta.json` for similar templates | Extract validation logic |
|
|
720
720
|
| `extension_inspect` | `programs/develop` | `--source` flag logic | — | Extract into callable API |
|
|
721
721
|
| `extension_source_inspect` | `programs/extension` | CDP client / RDP transport | Live browser via debugging protocol | Wire to running session |
|
|
722
|
+
| `extension_list_extensions` | MCP `lib/cdp` | `Extensions.getExtensionInfo` (read-only) | Live browser via CDP (Chromium) | MCP tool (no CLI verb) |
|
|
722
723
|
| `extension_wait` | `programs/extension` | `dev-wait.ts` | `ready.json` contract file | Thin wrapper (exists in CLI) |
|
|
723
724
|
| `extension_add_feature` | New | `extension_get_template_source` | examples repo patterns | Codegen from examples |
|
|
725
|
+
| **Agent bridge — act / triggers** | | | | |
|
|
726
|
+
| `extension_eval` | `programs/extension` | bridge control channel | Live extension context | Wraps `extension eval` (`--allow-eval`) |
|
|
727
|
+
| `extension_storage` | `programs/extension` | bridge control channel | `chrome.storage` | Wraps `extension storage` |
|
|
728
|
+
| `extension_reload` | `programs/extension` | bridge control channel | Live extension | Wraps `extension reload` |
|
|
729
|
+
| `extension_open` | `programs/extension` | bridge control channel | Surfaces + `action`/`command` replay | Wraps `extension open` |
|
|
730
|
+
| `extension_logs` | `programs/extension` | bridge log/control channel | `logs.ndjson` + live channel | Wraps `extension logs` |
|
|
724
731
|
| **Tier 3 — Browser management** | | | | |
|
|
725
732
|
| `extension_install_browser` | `programs/install` | `extensionInstall()` | — | Thin wrapper only |
|
|
726
733
|
| `extension_list_browsers` | `programs/install` | `getManagedBrowsersCacheRoot()` | — | Thin wrapper only |
|
package/dist/module.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export { startServer } from "./src/index";
|
|
1
|
+
export { startServer, runCli } from "./src/index";
|
package/dist/module.js
CHANGED
|
@@ -78,12 +78,30 @@ __webpack_require__.d(list_browsers_namespaceObject, {
|
|
|
78
78
|
handler: ()=>list_browsers_handler,
|
|
79
79
|
schema: ()=>list_browsers_schema
|
|
80
80
|
});
|
|
81
|
+
var list_extensions_namespaceObject = {};
|
|
82
|
+
__webpack_require__.r(list_extensions_namespaceObject);
|
|
83
|
+
__webpack_require__.d(list_extensions_namespaceObject, {
|
|
84
|
+
handler: ()=>list_extensions_handler,
|
|
85
|
+
schema: ()=>list_extensions_schema
|
|
86
|
+
});
|
|
81
87
|
var list_templates_namespaceObject = {};
|
|
82
88
|
__webpack_require__.r(list_templates_namespaceObject);
|
|
83
89
|
__webpack_require__.d(list_templates_namespaceObject, {
|
|
84
90
|
handler: ()=>list_templates_handler,
|
|
85
91
|
schema: ()=>list_templates_schema
|
|
86
92
|
});
|
|
93
|
+
var login_namespaceObject = {};
|
|
94
|
+
__webpack_require__.r(login_namespaceObject);
|
|
95
|
+
__webpack_require__.d(login_namespaceObject, {
|
|
96
|
+
handler: ()=>login_handler,
|
|
97
|
+
schema: ()=>login_schema
|
|
98
|
+
});
|
|
99
|
+
var logout_namespaceObject = {};
|
|
100
|
+
__webpack_require__.r(logout_namespaceObject);
|
|
101
|
+
__webpack_require__.d(logout_namespaceObject, {
|
|
102
|
+
handler: ()=>logout_handler,
|
|
103
|
+
schema: ()=>logout_schema
|
|
104
|
+
});
|
|
87
105
|
var logs_namespaceObject = {};
|
|
88
106
|
__webpack_require__.r(logs_namespaceObject);
|
|
89
107
|
__webpack_require__.d(logs_namespaceObject, {
|
|
@@ -112,6 +130,7 @@ var publish_namespaceObject = {};
|
|
|
112
130
|
__webpack_require__.r(publish_namespaceObject);
|
|
113
131
|
__webpack_require__.d(publish_namespaceObject, {
|
|
114
132
|
handler: ()=>publish_handler,
|
|
133
|
+
resolveToken: ()=>resolveToken,
|
|
115
134
|
schema: ()=>publish_schema
|
|
116
135
|
});
|
|
117
136
|
var reload_namespaceObject = {};
|
|
@@ -144,6 +163,12 @@ __webpack_require__.d(wait_namespaceObject, {
|
|
|
144
163
|
handler: ()=>wait_handler,
|
|
145
164
|
schema: ()=>wait_schema
|
|
146
165
|
});
|
|
166
|
+
var whoami_namespaceObject = {};
|
|
167
|
+
__webpack_require__.r(whoami_namespaceObject);
|
|
168
|
+
__webpack_require__.d(whoami_namespaceObject, {
|
|
169
|
+
handler: ()=>whoami_handler,
|
|
170
|
+
schema: ()=>whoami_schema
|
|
171
|
+
});
|
|
147
172
|
const schema = {
|
|
148
173
|
name: "extension_create",
|
|
149
174
|
description: "Create a new browser extension project from a template in the extension.dev template catalog. Use extension_list_templates to see available options.",
|
|
@@ -1546,6 +1571,120 @@ async function source_inspect_handler(args) {
|
|
|
1546
1571
|
cdp.disconnect();
|
|
1547
1572
|
}
|
|
1548
1573
|
}
|
|
1574
|
+
const list_extensions_schema = {
|
|
1575
|
+
name: "extension_list_extensions",
|
|
1576
|
+
description: "List the extensions with a live context in the running dev browser via Chrome DevTools Protocol. Returns each extension's id, name, version, and live contexts (service worker, page). Identity is read read-only via the Extensions domain — other extensions' contexts are never attached to or evaluated in. A dormant MV3 service worker with no open page may be absent until it wakes. Chromium only (Firefox uses RDP, not yet supported). Requires an active dev or start session.",
|
|
1577
|
+
inputSchema: {
|
|
1578
|
+
type: "object",
|
|
1579
|
+
properties: {
|
|
1580
|
+
projectPath: {
|
|
1581
|
+
type: "string",
|
|
1582
|
+
description: "Path to the extension project root (must have an active dev session)"
|
|
1583
|
+
},
|
|
1584
|
+
browser: {
|
|
1585
|
+
type: "string",
|
|
1586
|
+
default: "chrome"
|
|
1587
|
+
}
|
|
1588
|
+
},
|
|
1589
|
+
required: [
|
|
1590
|
+
"projectPath"
|
|
1591
|
+
]
|
|
1592
|
+
}
|
|
1593
|
+
};
|
|
1594
|
+
async function list_extensions_handler(args) {
|
|
1595
|
+
const browser = args.browser ?? "chrome";
|
|
1596
|
+
const isChromium = [
|
|
1597
|
+
"chrome",
|
|
1598
|
+
"edge",
|
|
1599
|
+
"chromium-based"
|
|
1600
|
+
].includes(browser);
|
|
1601
|
+
if (!isChromium) return JSON.stringify({
|
|
1602
|
+
error: `Listing extensions for ${browser} uses RDP (Remote Debug Protocol). Currently only Chromium CDP is supported.`,
|
|
1603
|
+
hint: "Use --browser=chrome."
|
|
1604
|
+
});
|
|
1605
|
+
const cdpPort = await list_extensions_findCdpPort(args.projectPath, browser);
|
|
1606
|
+
if (!cdpPort) return JSON.stringify({
|
|
1607
|
+
error: "No active dev session found. Cannot connect to Chrome DevTools Protocol.",
|
|
1608
|
+
hint: "Start a dev session first with extension_dev, then use extension_wait to confirm it is ready."
|
|
1609
|
+
});
|
|
1610
|
+
const cdp = new CDPClient();
|
|
1611
|
+
try {
|
|
1612
|
+
const browserWsUrl = await CDPClient.discoverBrowserWsUrl(cdpPort);
|
|
1613
|
+
await cdp.connect(browserWsUrl);
|
|
1614
|
+
const targets = await cdp.getTargets();
|
|
1615
|
+
const byId = new Map();
|
|
1616
|
+
for (const t of targets){
|
|
1617
|
+
const url = String(t.url ?? "");
|
|
1618
|
+
if (!url.startsWith("chrome-extension://")) continue;
|
|
1619
|
+
const id = url.slice(19).split("/")[0];
|
|
1620
|
+
if (!id) continue;
|
|
1621
|
+
const list = byId.get(id) ?? [];
|
|
1622
|
+
list.push({
|
|
1623
|
+
type: String(t.type ?? ""),
|
|
1624
|
+
url
|
|
1625
|
+
});
|
|
1626
|
+
byId.set(id, list);
|
|
1627
|
+
}
|
|
1628
|
+
const extensions = [];
|
|
1629
|
+
for (const [id, ctxTargets] of byId){
|
|
1630
|
+
const entry = {
|
|
1631
|
+
id,
|
|
1632
|
+
contexts: ctxTargets.map((c)=>({
|
|
1633
|
+
type: c.type,
|
|
1634
|
+
url: c.url
|
|
1635
|
+
})),
|
|
1636
|
+
source: "target-only"
|
|
1637
|
+
};
|
|
1638
|
+
try {
|
|
1639
|
+
const info = await cdp.sendCommand("Extensions.getExtensionInfo", {
|
|
1640
|
+
extensionId: id
|
|
1641
|
+
});
|
|
1642
|
+
if (info?.extensionInfo) {
|
|
1643
|
+
entry.name = info.extensionInfo.name;
|
|
1644
|
+
entry.version = info.extensionInfo.version;
|
|
1645
|
+
entry.source = "extensions-domain";
|
|
1646
|
+
}
|
|
1647
|
+
} catch {}
|
|
1648
|
+
extensions.push(entry);
|
|
1649
|
+
}
|
|
1650
|
+
extensions.sort((a, b)=>(a.name ?? a.id).localeCompare(b.name ?? b.id));
|
|
1651
|
+
return JSON.stringify({
|
|
1652
|
+
cdpPort,
|
|
1653
|
+
browser,
|
|
1654
|
+
count: extensions.length,
|
|
1655
|
+
extensions,
|
|
1656
|
+
note: "Lists extensions that currently have at least one live context (service worker or open page). An MV3 service worker that has gone dormant with no open page may be absent until it wakes. Identity is read read-only via the Extensions domain; other extensions' contexts are never attached to or evaluated in."
|
|
1657
|
+
});
|
|
1658
|
+
} catch (error) {
|
|
1659
|
+
return JSON.stringify({
|
|
1660
|
+
error: `Failed to list extensions: ${error.message}`
|
|
1661
|
+
});
|
|
1662
|
+
} finally{
|
|
1663
|
+
cdp.disconnect();
|
|
1664
|
+
}
|
|
1665
|
+
}
|
|
1666
|
+
async function list_extensions_findCdpPort(projectPath, browser) {
|
|
1667
|
+
const readyPath = node_path.resolve(projectPath, "dist", "extension-js", browser, "ready.json");
|
|
1668
|
+
try {
|
|
1669
|
+
const contract = JSON.parse(node_fs.readFileSync(readyPath, "utf8"));
|
|
1670
|
+
if ("number" == typeof contract.cdpPort) return contract.cdpPort;
|
|
1671
|
+
} catch {}
|
|
1672
|
+
const defaultPort = 9222;
|
|
1673
|
+
return new Promise((resolve)=>{
|
|
1674
|
+
const socket = new node_net.Socket();
|
|
1675
|
+
socket.setTimeout(1000);
|
|
1676
|
+
socket.on("connect", ()=>{
|
|
1677
|
+
socket.destroy();
|
|
1678
|
+
resolve(defaultPort);
|
|
1679
|
+
});
|
|
1680
|
+
socket.on("error", ()=>resolve(null));
|
|
1681
|
+
socket.on("timeout", ()=>{
|
|
1682
|
+
socket.destroy();
|
|
1683
|
+
resolve(null);
|
|
1684
|
+
});
|
|
1685
|
+
socket.connect(defaultPort, "127.0.0.1");
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1549
1688
|
const CONTROL_ENVELOPE_VERSION = 1;
|
|
1550
1689
|
const CONTROL_WS_PATH = "/extjs-control";
|
|
1551
1690
|
const DEFAULT_LIMIT = 200;
|
|
@@ -2046,7 +2185,7 @@ async function reload_handler(args) {
|
|
|
2046
2185
|
}
|
|
2047
2186
|
const open_schema = {
|
|
2048
2187
|
name: "extension_open",
|
|
2049
|
-
description: "Open an extension surface
|
|
2188
|
+
description: "Open an extension surface or replay an event in a running session. 'popup'/'options'/'sidebar' open UI surfaces. 'action' triggers the toolbar action: opens the action's popup, or (no popup) replays chrome.action.onClicked. 'command' replays a chrome.commands.onCommand keyboard shortcut (pass `name`). NOTE: action/command replay invokes your listener WITHOUT a user gesture, so the gesture-derived activeTab grant does not apply (the result includes gesture:false and a warning when activeTab is declared). Requires --allow-control. Wraps `extension open`.",
|
|
2050
2189
|
inputSchema: {
|
|
2051
2190
|
type: "object",
|
|
2052
2191
|
properties: {
|
|
@@ -2059,9 +2198,15 @@ const open_schema = {
|
|
|
2059
2198
|
enum: [
|
|
2060
2199
|
"popup",
|
|
2061
2200
|
"options",
|
|
2062
|
-
"sidebar"
|
|
2201
|
+
"sidebar",
|
|
2202
|
+
"action",
|
|
2203
|
+
"command"
|
|
2063
2204
|
],
|
|
2064
|
-
description: "Which surface to open"
|
|
2205
|
+
description: "Which surface to open or event to replay. 'action' triggers the toolbar action; 'command' replays a keyboard-shortcut command (requires `name`)."
|
|
2206
|
+
},
|
|
2207
|
+
name: {
|
|
2208
|
+
type: "string",
|
|
2209
|
+
description: "For surface 'command': the chrome.commands name to trigger."
|
|
2065
2210
|
},
|
|
2066
2211
|
browser: {
|
|
2067
2212
|
type: "string",
|
|
@@ -2084,6 +2229,7 @@ async function open_handler(args) {
|
|
|
2084
2229
|
args.surface,
|
|
2085
2230
|
args.projectPath
|
|
2086
2231
|
];
|
|
2232
|
+
if ("command" === args.surface && args.name) cli.push("--name", args.name);
|
|
2087
2233
|
if (args.browser) cli.push("--browser", args.browser);
|
|
2088
2234
|
if (null != args.timeout) cli.push("--timeout", String(args.timeout));
|
|
2089
2235
|
return runActVerb(cli, args.projectPath, args.timeout);
|
|
@@ -2179,9 +2325,73 @@ async function dom_inspect_handler(args) {
|
|
|
2179
2325
|
if (null != args.timeout) cli.push("--timeout", String(args.timeout));
|
|
2180
2326
|
return runActVerb(cli, args.projectPath, args.timeout);
|
|
2181
2327
|
}
|
|
2328
|
+
function credentialsPath() {
|
|
2329
|
+
if ("win32" === process.platform) {
|
|
2330
|
+
const base = process.env.APPDATA || process.env.LOCALAPPDATA || node_path.join(node_os.homedir(), "AppData", "Roaming");
|
|
2331
|
+
return node_path.join(base, "extension-dev", "auth.json");
|
|
2332
|
+
}
|
|
2333
|
+
const xdg = String(process.env.XDG_CONFIG_HOME || "").trim();
|
|
2334
|
+
const base = xdg || node_path.join(node_os.homedir(), ".config");
|
|
2335
|
+
return node_path.join(base, "extension-dev", "auth.json");
|
|
2336
|
+
}
|
|
2337
|
+
function readCredentials() {
|
|
2338
|
+
try {
|
|
2339
|
+
const raw = node_fs.readFileSync(credentialsPath(), "utf8");
|
|
2340
|
+
const data = JSON.parse(raw);
|
|
2341
|
+
if (!data || "object" != typeof data) return null;
|
|
2342
|
+
if (1 !== data.version) return null;
|
|
2343
|
+
const token = String(data.token || "").trim();
|
|
2344
|
+
if (!token) return null;
|
|
2345
|
+
return {
|
|
2346
|
+
version: 1,
|
|
2347
|
+
token,
|
|
2348
|
+
workspaceSlug: String(data.workspaceSlug || ""),
|
|
2349
|
+
projectSlug: String(data.projectSlug || ""),
|
|
2350
|
+
expiresAt: Number(data.expiresAt || 0),
|
|
2351
|
+
api: String(data.api || "")
|
|
2352
|
+
};
|
|
2353
|
+
} catch {
|
|
2354
|
+
return null;
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
function writeCredentials(creds) {
|
|
2358
|
+
const file = credentialsPath();
|
|
2359
|
+
node_fs.mkdirSync(node_path.dirname(file), {
|
|
2360
|
+
recursive: true
|
|
2361
|
+
});
|
|
2362
|
+
node_fs.writeFileSync(file, JSON.stringify(creds, null, 2) + "\n", {
|
|
2363
|
+
mode: 384
|
|
2364
|
+
});
|
|
2365
|
+
try {
|
|
2366
|
+
node_fs.chmodSync(file, 384);
|
|
2367
|
+
} catch {}
|
|
2368
|
+
return file;
|
|
2369
|
+
}
|
|
2370
|
+
function clearCredentials() {
|
|
2371
|
+
const file = credentialsPath();
|
|
2372
|
+
try {
|
|
2373
|
+
node_fs.unlinkSync(file);
|
|
2374
|
+
return {
|
|
2375
|
+
cleared: true,
|
|
2376
|
+
path: file
|
|
2377
|
+
};
|
|
2378
|
+
} catch {
|
|
2379
|
+
return {
|
|
2380
|
+
cleared: false,
|
|
2381
|
+
path: file
|
|
2382
|
+
};
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
function readValidCredentials(nowSeconds = Math.floor(Date.now() / 1000)) {
|
|
2386
|
+
const creds = readCredentials();
|
|
2387
|
+
if (!creds) return null;
|
|
2388
|
+
if (creds.expiresAt && creds.expiresAt <= nowSeconds) return null;
|
|
2389
|
+
return creds;
|
|
2390
|
+
}
|
|
2391
|
+
const DEFAULT_API = "https://www.extension.dev";
|
|
2182
2392
|
const publish_schema = {
|
|
2183
2393
|
name: "extension_publish",
|
|
2184
|
-
description: "Publish a project to extension.dev and return a shareable URL. Auth-gated: requires EXTENSION_DEV_TOKEN (a workspace/project access token) in the environment.
|
|
2394
|
+
description: "Publish a project to extension.dev and return a shareable URL. Auth-gated: requires EXTENSION_DEV_TOKEN (a workspace/project access token) in the environment. Posts to the platform's CLI publish endpoint directly. This is the only tool that talks to the hosted platform rather than the local browser.",
|
|
2185
2395
|
inputSchema: {
|
|
2186
2396
|
type: "object",
|
|
2187
2397
|
properties: {
|
|
@@ -2207,32 +2417,54 @@ const publish_schema = {
|
|
|
2207
2417
|
]
|
|
2208
2418
|
}
|
|
2209
2419
|
};
|
|
2210
|
-
|
|
2211
|
-
const
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
if (null != args.ttlHours) cli.push("--ttl", String(args.ttlHours));
|
|
2218
|
-
if (args.buildSha) cli.push("--build-sha", args.buildSha);
|
|
2219
|
-
if (args.api) cli.push("--api", args.api);
|
|
2220
|
-
const { code, stdout, stderr } = await runExtensionCli(cli, {
|
|
2221
|
-
cwd: args.projectPath
|
|
2222
|
-
});
|
|
2223
|
-
const out = stdout.trim();
|
|
2224
|
-
if (out) try {
|
|
2225
|
-
JSON.parse(out);
|
|
2226
|
-
return out;
|
|
2227
|
-
} catch {}
|
|
2420
|
+
function resolveToken() {
|
|
2421
|
+
const fromEnv = String(process.env.EXTENSION_DEV_TOKEN || "").trim();
|
|
2422
|
+
if (fromEnv) return fromEnv;
|
|
2423
|
+
const creds = readValidCredentials();
|
|
2424
|
+
return creds?.token ? String(creds.token).trim() : "";
|
|
2425
|
+
}
|
|
2426
|
+
function fail(name, message) {
|
|
2228
2427
|
return JSON.stringify({
|
|
2229
2428
|
ok: false,
|
|
2230
2429
|
error: {
|
|
2231
|
-
name
|
|
2232
|
-
message
|
|
2430
|
+
name,
|
|
2431
|
+
message
|
|
2233
2432
|
}
|
|
2234
2433
|
});
|
|
2235
2434
|
}
|
|
2435
|
+
async function publish_handler(args) {
|
|
2436
|
+
const token = resolveToken();
|
|
2437
|
+
if (!token) return fail("PublishAuthError", "No token. Run extension_login, or set EXTENSION_DEV_TOKEN (create one in the extension.dev dashboard).");
|
|
2438
|
+
const base = String(args.api || process.env.EXTENSION_DEV_API_URL || DEFAULT_API).replace(/\/+$/, "");
|
|
2439
|
+
const url = `${base}/api/cli/publish`;
|
|
2440
|
+
const body = {};
|
|
2441
|
+
if (null != args.ttlHours) body.ttlHours = Number(args.ttlHours);
|
|
2442
|
+
if (args.buildSha) body.buildSha = args.buildSha;
|
|
2443
|
+
let res;
|
|
2444
|
+
try {
|
|
2445
|
+
res = await fetch(url, {
|
|
2446
|
+
method: "POST",
|
|
2447
|
+
headers: {
|
|
2448
|
+
authorization: `Bearer ${token}`,
|
|
2449
|
+
"content-type": "application/json"
|
|
2450
|
+
},
|
|
2451
|
+
body: JSON.stringify(body)
|
|
2452
|
+
});
|
|
2453
|
+
} catch (err) {
|
|
2454
|
+
return fail("PublishNetworkError", `Could not reach ${url}: ${err?.message || err}`);
|
|
2455
|
+
}
|
|
2456
|
+
const text = await res.text();
|
|
2457
|
+
let data;
|
|
2458
|
+
try {
|
|
2459
|
+
data = JSON.parse(text);
|
|
2460
|
+
} catch {
|
|
2461
|
+
data = {
|
|
2462
|
+
message: text
|
|
2463
|
+
};
|
|
2464
|
+
}
|
|
2465
|
+
if (!res.ok) return fail("PublishError", `publish failed (${res.status}): ${data?.message || text || "unknown error"}`);
|
|
2466
|
+
return JSON.stringify(data);
|
|
2467
|
+
}
|
|
2236
2468
|
const wait_schema = {
|
|
2237
2469
|
name: "extension_wait",
|
|
2238
2470
|
description: "Wait for a running dev or start session to be ready. Polls the ready.json contract file and returns structured status.",
|
|
@@ -2528,6 +2760,303 @@ async function add_feature_handler(args) {
|
|
|
2528
2760
|
hint: conflicts.length ? `Warning: ${conflicts.length} file(s) already exist and would be overwritten.` : "No conflicts detected. Safe to create all files."
|
|
2529
2761
|
});
|
|
2530
2762
|
}
|
|
2763
|
+
const GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
|
|
2764
|
+
const GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token";
|
|
2765
|
+
const DEVICE_GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code";
|
|
2766
|
+
const defaultSleep = (ms)=>new Promise((resolve)=>setTimeout(resolve, ms));
|
|
2767
|
+
async function startDeviceCode(args) {
|
|
2768
|
+
const doFetch = args.fetchImpl ?? fetch;
|
|
2769
|
+
const res = await doFetch(GITHUB_DEVICE_CODE_URL, {
|
|
2770
|
+
method: "POST",
|
|
2771
|
+
headers: {
|
|
2772
|
+
accept: "application/json",
|
|
2773
|
+
"content-type": "application/json"
|
|
2774
|
+
},
|
|
2775
|
+
body: JSON.stringify({
|
|
2776
|
+
client_id: args.clientId,
|
|
2777
|
+
scope: args.scope || "read:user"
|
|
2778
|
+
})
|
|
2779
|
+
});
|
|
2780
|
+
const data = await res.json().catch(()=>({}));
|
|
2781
|
+
if (!res.ok || data.error) throw new Error(`GitHub device-code request failed: ${data.error_description || data.error || res.status}`);
|
|
2782
|
+
return {
|
|
2783
|
+
deviceCode: String(data.device_code || ""),
|
|
2784
|
+
userCode: String(data.user_code || ""),
|
|
2785
|
+
verificationUri: String(data.verification_uri || "https://github.com/login/device"),
|
|
2786
|
+
interval: Number(data.interval || 5),
|
|
2787
|
+
expiresIn: Number(data.expires_in || 900)
|
|
2788
|
+
};
|
|
2789
|
+
}
|
|
2790
|
+
async function pollForToken(args) {
|
|
2791
|
+
const doFetch = args.fetchImpl ?? fetch;
|
|
2792
|
+
const sleep = args.sleepImpl ?? defaultSleep;
|
|
2793
|
+
let intervalMs = 1000 * Math.max(1, args.interval);
|
|
2794
|
+
const deadline = Date.now() + Math.max(0, args.budgetMs);
|
|
2795
|
+
while(Date.now() < deadline){
|
|
2796
|
+
await sleep(intervalMs);
|
|
2797
|
+
const res = await doFetch(GITHUB_TOKEN_URL, {
|
|
2798
|
+
method: "POST",
|
|
2799
|
+
headers: {
|
|
2800
|
+
accept: "application/json",
|
|
2801
|
+
"content-type": "application/json"
|
|
2802
|
+
},
|
|
2803
|
+
body: JSON.stringify({
|
|
2804
|
+
client_id: args.clientId,
|
|
2805
|
+
device_code: args.deviceCode,
|
|
2806
|
+
grant_type: DEVICE_GRANT_TYPE
|
|
2807
|
+
})
|
|
2808
|
+
});
|
|
2809
|
+
const data = await res.json().catch(()=>({}));
|
|
2810
|
+
const token = String(data.access_token || "").trim();
|
|
2811
|
+
if (token) return {
|
|
2812
|
+
ok: true,
|
|
2813
|
+
githubToken: token
|
|
2814
|
+
};
|
|
2815
|
+
const error = String(data.error || "");
|
|
2816
|
+
if ("authorization_pending" === error) continue;
|
|
2817
|
+
if ("slow_down" === error) {
|
|
2818
|
+
intervalMs = Math.max(intervalMs + 5000, 1000 * Number(data.interval || 0));
|
|
2819
|
+
continue;
|
|
2820
|
+
}
|
|
2821
|
+
if ("expired_token" === error) return {
|
|
2822
|
+
ok: false,
|
|
2823
|
+
reason: "expired"
|
|
2824
|
+
};
|
|
2825
|
+
if ("access_denied" === error) return {
|
|
2826
|
+
ok: false,
|
|
2827
|
+
reason: "denied"
|
|
2828
|
+
};
|
|
2829
|
+
}
|
|
2830
|
+
return {
|
|
2831
|
+
ok: false,
|
|
2832
|
+
reason: "pending"
|
|
2833
|
+
};
|
|
2834
|
+
}
|
|
2835
|
+
const login_flow_DEFAULT_API = "https://www.extension.dev";
|
|
2836
|
+
function resolveApiBase(api) {
|
|
2837
|
+
return String(api || process.env.EXTENSION_DEV_API_URL || login_flow_DEFAULT_API).replace(/\/+$/, "");
|
|
2838
|
+
}
|
|
2839
|
+
async function fetchLoginConfig(apiBase, fetchImpl = fetch) {
|
|
2840
|
+
const override = String(process.env.EXTENSION_DEV_GITHUB_CLIENT_ID || "").trim();
|
|
2841
|
+
if (override) return {
|
|
2842
|
+
clientId: override,
|
|
2843
|
+
scope: "read:user"
|
|
2844
|
+
};
|
|
2845
|
+
const res = await fetchImpl(`${apiBase}/api/cli/login/config`, {
|
|
2846
|
+
headers: {
|
|
2847
|
+
accept: "application/json"
|
|
2848
|
+
}
|
|
2849
|
+
});
|
|
2850
|
+
if (!res.ok) throw new Error(`Could not fetch login config from ${apiBase} (${res.status}).`);
|
|
2851
|
+
const data = await res.json().catch(()=>({}));
|
|
2852
|
+
const clientId = String(data.githubClientId || "").trim();
|
|
2853
|
+
if (!clientId) throw new Error("Login is not configured on the server (no GitHub client id). Set EXTENSION_DEV_GITHUB_CLIENT_ID to override.");
|
|
2854
|
+
return {
|
|
2855
|
+
clientId,
|
|
2856
|
+
scope: String(data.scope || "read:user")
|
|
2857
|
+
};
|
|
2858
|
+
}
|
|
2859
|
+
async function exchangeAndPersist(args) {
|
|
2860
|
+
const doFetch = args.fetchImpl ?? fetch;
|
|
2861
|
+
const res = await doFetch(`${args.apiBase}/api/cli/login/exchange`, {
|
|
2862
|
+
method: "POST",
|
|
2863
|
+
headers: {
|
|
2864
|
+
"content-type": "application/json",
|
|
2865
|
+
accept: "application/json"
|
|
2866
|
+
},
|
|
2867
|
+
body: JSON.stringify({
|
|
2868
|
+
githubToken: args.githubToken,
|
|
2869
|
+
project: args.project
|
|
2870
|
+
})
|
|
2871
|
+
});
|
|
2872
|
+
const text = await res.text();
|
|
2873
|
+
let data;
|
|
2874
|
+
try {
|
|
2875
|
+
data = JSON.parse(text);
|
|
2876
|
+
} catch {
|
|
2877
|
+
data = {
|
|
2878
|
+
message: text
|
|
2879
|
+
};
|
|
2880
|
+
}
|
|
2881
|
+
if (!res.ok) throw new Error(`Login exchange failed (${res.status}): ${data.message || "unknown error"}`);
|
|
2882
|
+
const token = String(data.token || "").trim();
|
|
2883
|
+
if (!token) throw new Error("Login exchange returned no token.");
|
|
2884
|
+
const creds = {
|
|
2885
|
+
version: 1,
|
|
2886
|
+
token,
|
|
2887
|
+
workspaceSlug: String(data.workspaceSlug || ""),
|
|
2888
|
+
projectSlug: String(data.projectSlug || ""),
|
|
2889
|
+
expiresAt: Number(data.expiresAt || 0),
|
|
2890
|
+
api: args.apiBase
|
|
2891
|
+
};
|
|
2892
|
+
writeCredentials(creds);
|
|
2893
|
+
return creds;
|
|
2894
|
+
}
|
|
2895
|
+
const login_schema = {
|
|
2896
|
+
name: "extension_login",
|
|
2897
|
+
description: "Authenticate to extension.dev via GitHub device-code and store a project-scoped access token locally so extension_publish can use it. Two-phase: call with `project` to get a code + URL for the user to authorize, then call again with the returned `deviceCode` to finish. Never returns the token. This is the only tool besides extension_publish that talks to the hosted platform.",
|
|
2898
|
+
inputSchema: {
|
|
2899
|
+
type: "object",
|
|
2900
|
+
properties: {
|
|
2901
|
+
project: {
|
|
2902
|
+
type: "string",
|
|
2903
|
+
description: "Target project as '<workspace>/<project>' (the token is scoped to it)"
|
|
2904
|
+
},
|
|
2905
|
+
deviceCode: {
|
|
2906
|
+
type: "string",
|
|
2907
|
+
description: "Resume token from a prior call's `deviceCode`; omit on the first call"
|
|
2908
|
+
},
|
|
2909
|
+
api: {
|
|
2910
|
+
type: "string",
|
|
2911
|
+
description: "Platform base URL (defaults to https://www.extension.dev or EXTENSION_DEV_API_URL)"
|
|
2912
|
+
}
|
|
2913
|
+
},
|
|
2914
|
+
required: [
|
|
2915
|
+
"project"
|
|
2916
|
+
]
|
|
2917
|
+
}
|
|
2918
|
+
};
|
|
2919
|
+
const FIRST_CALL_BUDGET_MS = 8000;
|
|
2920
|
+
const RESUME_BUDGET_MS = 22000;
|
|
2921
|
+
function login_fail(name, message) {
|
|
2922
|
+
return JSON.stringify({
|
|
2923
|
+
ok: false,
|
|
2924
|
+
error: {
|
|
2925
|
+
name,
|
|
2926
|
+
message
|
|
2927
|
+
}
|
|
2928
|
+
});
|
|
2929
|
+
}
|
|
2930
|
+
function success(creds) {
|
|
2931
|
+
return JSON.stringify({
|
|
2932
|
+
ok: true,
|
|
2933
|
+
status: "logged-in",
|
|
2934
|
+
workspaceSlug: creds.workspaceSlug,
|
|
2935
|
+
projectSlug: creds.projectSlug,
|
|
2936
|
+
expiresAt: creds.expiresAt ? new Date(1000 * creds.expiresAt).toISOString() : null,
|
|
2937
|
+
message: `Logged in to ${creds.workspaceSlug}/${creds.projectSlug}. extension_publish can now use the stored token.`
|
|
2938
|
+
});
|
|
2939
|
+
}
|
|
2940
|
+
function login_pending(start) {
|
|
2941
|
+
return JSON.stringify({
|
|
2942
|
+
ok: false,
|
|
2943
|
+
status: "authorization_pending",
|
|
2944
|
+
userCode: start.userCode,
|
|
2945
|
+
verificationUri: start.verificationUri,
|
|
2946
|
+
deviceCode: start.deviceCode,
|
|
2947
|
+
message: `Open ${start.verificationUri} and enter code ${start.userCode}, then call extension_login again with this deviceCode (and the same project).`
|
|
2948
|
+
});
|
|
2949
|
+
}
|
|
2950
|
+
async function login_handler(args) {
|
|
2951
|
+
const project = String(args.project || "").trim();
|
|
2952
|
+
if (!/^[^/]+\/[^/]+$/.test(project)) return login_fail("BadRequest", "project must be in the form '<workspace>/<project>'.");
|
|
2953
|
+
const apiBase = resolveApiBase(args.api);
|
|
2954
|
+
let config;
|
|
2955
|
+
try {
|
|
2956
|
+
config = await fetchLoginConfig(apiBase);
|
|
2957
|
+
} catch (err) {
|
|
2958
|
+
return login_fail("LoginConfigError", err?.message || "Could not load login config.");
|
|
2959
|
+
}
|
|
2960
|
+
if (args.deviceCode) {
|
|
2961
|
+
const poll = await pollForToken({
|
|
2962
|
+
clientId: config.clientId,
|
|
2963
|
+
deviceCode: String(args.deviceCode),
|
|
2964
|
+
interval: 5,
|
|
2965
|
+
budgetMs: RESUME_BUDGET_MS
|
|
2966
|
+
});
|
|
2967
|
+
if (!poll.ok) {
|
|
2968
|
+
if ("expired" === poll.reason) return login_fail("LoginExpired", "The device code expired. Run extension_login again to restart.");
|
|
2969
|
+
if ("denied" === poll.reason) return login_fail("LoginDenied", "Authorization was denied on GitHub.");
|
|
2970
|
+
return login_pending({
|
|
2971
|
+
deviceCode: String(args.deviceCode),
|
|
2972
|
+
userCode: "(see the previous response)",
|
|
2973
|
+
verificationUri: "https://github.com/login/device"
|
|
2974
|
+
});
|
|
2975
|
+
}
|
|
2976
|
+
try {
|
|
2977
|
+
const creds = await exchangeAndPersist({
|
|
2978
|
+
apiBase,
|
|
2979
|
+
githubToken: poll.githubToken,
|
|
2980
|
+
project
|
|
2981
|
+
});
|
|
2982
|
+
return success(creds);
|
|
2983
|
+
} catch (err) {
|
|
2984
|
+
return login_fail("LoginExchangeError", err?.message || "Token exchange failed.");
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2987
|
+
let start;
|
|
2988
|
+
try {
|
|
2989
|
+
start = await startDeviceCode({
|
|
2990
|
+
clientId: config.clientId,
|
|
2991
|
+
scope: config.scope
|
|
2992
|
+
});
|
|
2993
|
+
} catch (err) {
|
|
2994
|
+
return login_fail("LoginStartError", err?.message || "Could not start device flow.");
|
|
2995
|
+
}
|
|
2996
|
+
const poll = await pollForToken({
|
|
2997
|
+
clientId: config.clientId,
|
|
2998
|
+
deviceCode: start.deviceCode,
|
|
2999
|
+
interval: start.interval,
|
|
3000
|
+
budgetMs: FIRST_CALL_BUDGET_MS
|
|
3001
|
+
});
|
|
3002
|
+
if (poll.ok) try {
|
|
3003
|
+
const creds = await exchangeAndPersist({
|
|
3004
|
+
apiBase,
|
|
3005
|
+
githubToken: poll.githubToken,
|
|
3006
|
+
project
|
|
3007
|
+
});
|
|
3008
|
+
return success(creds);
|
|
3009
|
+
} catch (err) {
|
|
3010
|
+
return login_fail("LoginExchangeError", err?.message || "Token exchange failed.");
|
|
3011
|
+
}
|
|
3012
|
+
if (!poll.ok && "denied" === poll.reason) return login_fail("LoginDenied", "Authorization was denied on GitHub.");
|
|
3013
|
+
return login_pending(start);
|
|
3014
|
+
}
|
|
3015
|
+
const whoami_schema = {
|
|
3016
|
+
name: "extension_whoami",
|
|
3017
|
+
description: "Report the locally stored extension.dev login (workspace/project and token expiry) without revealing the token. Returns logged-out status when no credentials are stored.",
|
|
3018
|
+
inputSchema: {
|
|
3019
|
+
type: "object",
|
|
3020
|
+
properties: {}
|
|
3021
|
+
}
|
|
3022
|
+
};
|
|
3023
|
+
async function whoami_handler() {
|
|
3024
|
+
const creds = readCredentials();
|
|
3025
|
+
if (!creds) return JSON.stringify({
|
|
3026
|
+
ok: true,
|
|
3027
|
+
status: "logged-out",
|
|
3028
|
+
message: "No stored credentials. Run extension_login to authenticate."
|
|
3029
|
+
});
|
|
3030
|
+
const now = Math.floor(Date.now() / 1000);
|
|
3031
|
+
const expired = Boolean(creds.expiresAt && creds.expiresAt <= now);
|
|
3032
|
+
return JSON.stringify({
|
|
3033
|
+
ok: true,
|
|
3034
|
+
status: expired ? "expired" : "logged-in",
|
|
3035
|
+
workspaceSlug: creds.workspaceSlug,
|
|
3036
|
+
projectSlug: creds.projectSlug,
|
|
3037
|
+
api: creds.api,
|
|
3038
|
+
expiresAt: creds.expiresAt ? new Date(1000 * creds.expiresAt).toISOString() : null,
|
|
3039
|
+
expiresInSeconds: creds.expiresAt ? creds.expiresAt - now : null,
|
|
3040
|
+
expired,
|
|
3041
|
+
message: expired ? "Stored token has expired. Run extension_login to refresh it." : `Logged in to ${creds.workspaceSlug}/${creds.projectSlug}.`
|
|
3042
|
+
});
|
|
3043
|
+
}
|
|
3044
|
+
const logout_schema = {
|
|
3045
|
+
name: "extension_logout",
|
|
3046
|
+
description: "Delete the locally stored extension.dev credentials. Does not revoke the token server-side (revoke from the dashboard if needed); only removes it from this machine.",
|
|
3047
|
+
inputSchema: {
|
|
3048
|
+
type: "object",
|
|
3049
|
+
properties: {}
|
|
3050
|
+
}
|
|
3051
|
+
};
|
|
3052
|
+
async function logout_handler() {
|
|
3053
|
+
const result = clearCredentials();
|
|
3054
|
+
return JSON.stringify({
|
|
3055
|
+
ok: true,
|
|
3056
|
+
cleared: result.cleared,
|
|
3057
|
+
message: result.cleared ? "Local credentials removed." : "No stored credentials to remove."
|
|
3058
|
+
});
|
|
3059
|
+
}
|
|
2531
3060
|
const install_browser_schema = {
|
|
2532
3061
|
name: "extension_install_browser",
|
|
2533
3062
|
description: "Install a managed browser binary for extension testing. Useful in CI, Docker, or fresh environments where browsers are not pre-installed.",
|
|
@@ -2867,6 +3396,7 @@ const tools = [
|
|
|
2867
3396
|
manifest_validate_namespaceObject,
|
|
2868
3397
|
inspect_namespaceObject,
|
|
2869
3398
|
source_inspect_namespaceObject,
|
|
3399
|
+
list_extensions_namespaceObject,
|
|
2870
3400
|
logs_namespaceObject,
|
|
2871
3401
|
eval_namespaceObject,
|
|
2872
3402
|
storage_namespaceObject,
|
|
@@ -2876,6 +3406,9 @@ const tools = [
|
|
|
2876
3406
|
publish_namespaceObject,
|
|
2877
3407
|
wait_namespaceObject,
|
|
2878
3408
|
add_feature_namespaceObject,
|
|
3409
|
+
login_namespaceObject,
|
|
3410
|
+
whoami_namespaceObject,
|
|
3411
|
+
logout_namespaceObject,
|
|
2879
3412
|
install_browser_namespaceObject,
|
|
2880
3413
|
list_browsers_namespaceObject,
|
|
2881
3414
|
detect_browsers_namespaceObject
|
|
@@ -2941,4 +3474,60 @@ async function startServer() {
|
|
|
2941
3474
|
const transport = new StdioServerTransport();
|
|
2942
3475
|
await server.connect(transport);
|
|
2943
3476
|
}
|
|
2944
|
-
|
|
3477
|
+
async function runCli(cmd, args) {
|
|
3478
|
+
const log = (msg)=>process.stderr.write(`${msg}\n`);
|
|
3479
|
+
const flag = (name)=>{
|
|
3480
|
+
const i = args.indexOf(`--${name}`);
|
|
3481
|
+
return i >= 0 ? args[i + 1] : void 0;
|
|
3482
|
+
};
|
|
3483
|
+
if ("whoami" === cmd) {
|
|
3484
|
+
log(await whoami_handler());
|
|
3485
|
+
return 0;
|
|
3486
|
+
}
|
|
3487
|
+
if ("logout" === cmd) {
|
|
3488
|
+
log(await logout_handler());
|
|
3489
|
+
return 0;
|
|
3490
|
+
}
|
|
3491
|
+
if ("login" === cmd) {
|
|
3492
|
+
const project = String(flag("project") || "").trim();
|
|
3493
|
+
if (!/^[^/]+\/[^/]+$/.test(project)) {
|
|
3494
|
+
log("Usage: extension-mcp login --project <workspace>/<project> [--api <url>]");
|
|
3495
|
+
return 1;
|
|
3496
|
+
}
|
|
3497
|
+
const apiBase = resolveApiBase(flag("api"));
|
|
3498
|
+
try {
|
|
3499
|
+
const config = await fetchLoginConfig(apiBase);
|
|
3500
|
+
const start = await startDeviceCode({
|
|
3501
|
+
clientId: config.clientId,
|
|
3502
|
+
scope: config.scope
|
|
3503
|
+
});
|
|
3504
|
+
log("");
|
|
3505
|
+
log(` Open ${start.verificationUri} and enter code: ${start.userCode}`);
|
|
3506
|
+
log("");
|
|
3507
|
+
log(" Waiting for authorization...");
|
|
3508
|
+
const poll = await pollForToken({
|
|
3509
|
+
clientId: config.clientId,
|
|
3510
|
+
deviceCode: start.deviceCode,
|
|
3511
|
+
interval: start.interval,
|
|
3512
|
+
budgetMs: 1000 * start.expiresIn
|
|
3513
|
+
});
|
|
3514
|
+
if (!poll.ok) {
|
|
3515
|
+
log("denied" === poll.reason ? "Authorization was denied on GitHub." : "Timed out waiting for authorization. Run login again.");
|
|
3516
|
+
return 1;
|
|
3517
|
+
}
|
|
3518
|
+
const creds = await exchangeAndPersist({
|
|
3519
|
+
apiBase,
|
|
3520
|
+
githubToken: poll.githubToken,
|
|
3521
|
+
project
|
|
3522
|
+
});
|
|
3523
|
+
log(`Logged in to ${creds.workspaceSlug}/${creds.projectSlug}.`);
|
|
3524
|
+
return 0;
|
|
3525
|
+
} catch (err) {
|
|
3526
|
+
log(err instanceof Error ? err.message : String(err));
|
|
3527
|
+
return 1;
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
log(`Unknown command: ${cmd}. Expected one of: login, logout, whoami.`);
|
|
3531
|
+
return 1;
|
|
3532
|
+
}
|
|
3533
|
+
export { runCli, startServer };
|
package/dist/src/index.d.ts
CHANGED
|
@@ -1 +1,9 @@
|
|
|
1
1
|
export declare function startServer(): Promise<void>;
|
|
2
|
+
/**
|
|
3
|
+
* Human-facing subcommands for `extension-mcp <cmd>`. Unlike the MCP tools
|
|
4
|
+
* (which must return promptly and so resume across calls), the bin blocks on
|
|
5
|
+
* the device flow because there is a person watching the terminal. Returns a
|
|
6
|
+
* process exit code. All console output goes to stderr so it never pollutes a
|
|
7
|
+
* machine-readable stdout if a script captures it.
|
|
8
|
+
*/
|
|
9
|
+
export declare function runCli(cmd: string, args: string[]): Promise<number>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface StoredCredentials {
|
|
2
|
+
version: 1;
|
|
3
|
+
/** The HMAC access token the publish path sends as `Bearer`. */
|
|
4
|
+
token: string;
|
|
5
|
+
workspaceSlug: string;
|
|
6
|
+
projectSlug: string;
|
|
7
|
+
/** Token expiry as unix epoch seconds (0 if unknown). */
|
|
8
|
+
expiresAt: number;
|
|
9
|
+
/** Platform base URL the token was minted against. */
|
|
10
|
+
api: string;
|
|
11
|
+
}
|
|
12
|
+
export declare function credentialsPath(): string;
|
|
13
|
+
export declare function readCredentials(): StoredCredentials | null;
|
|
14
|
+
export declare function writeCredentials(creds: StoredCredentials): string;
|
|
15
|
+
export declare function clearCredentials(): {
|
|
16
|
+
cleared: boolean;
|
|
17
|
+
path: string;
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Return stored credentials only if the token has not expired. Used by the
|
|
21
|
+
* publish token resolution so an expired local token falls through cleanly to
|
|
22
|
+
* "no token" instead of producing a 401 from the platform.
|
|
23
|
+
*/
|
|
24
|
+
export declare function readValidCredentials(nowSeconds?: number): StoredCredentials | null;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface DeviceCodeStart {
|
|
2
|
+
deviceCode: string;
|
|
3
|
+
userCode: string;
|
|
4
|
+
verificationUri: string;
|
|
5
|
+
/** Minimum seconds to wait between polls. */
|
|
6
|
+
interval: number;
|
|
7
|
+
/** Seconds until the device code expires. */
|
|
8
|
+
expiresIn: number;
|
|
9
|
+
}
|
|
10
|
+
export type PollResult = {
|
|
11
|
+
ok: true;
|
|
12
|
+
githubToken: string;
|
|
13
|
+
} | {
|
|
14
|
+
ok: false;
|
|
15
|
+
reason: "pending" | "expired" | "denied";
|
|
16
|
+
error?: string;
|
|
17
|
+
};
|
|
18
|
+
type FetchImpl = typeof fetch;
|
|
19
|
+
type SleepImpl = (ms: number) => Promise<void>;
|
|
20
|
+
export declare function startDeviceCode(args: {
|
|
21
|
+
clientId: string;
|
|
22
|
+
scope?: string;
|
|
23
|
+
fetchImpl?: FetchImpl;
|
|
24
|
+
}): Promise<DeviceCodeStart>;
|
|
25
|
+
/**
|
|
26
|
+
* Poll for the access token until the budget elapses. A budget shorter than
|
|
27
|
+
* the device-code lifetime lets a caller (e.g. an MCP tool that must return
|
|
28
|
+
* promptly) poll in bounded slices and report "pending" between calls; a long
|
|
29
|
+
* budget (a blocking CLI) waits the whole time. Returns `pending` only on
|
|
30
|
+
* budget exhaustion; `expired`/`denied` are terminal.
|
|
31
|
+
*/
|
|
32
|
+
export declare function pollForToken(args: {
|
|
33
|
+
clientId: string;
|
|
34
|
+
deviceCode: string;
|
|
35
|
+
interval: number;
|
|
36
|
+
budgetMs: number;
|
|
37
|
+
fetchImpl?: FetchImpl;
|
|
38
|
+
sleepImpl?: SleepImpl;
|
|
39
|
+
}): Promise<PollResult>;
|
|
40
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type StoredCredentials } from "./credentials";
|
|
2
|
+
type FetchImpl = typeof fetch;
|
|
3
|
+
export declare function resolveApiBase(api?: string): string;
|
|
4
|
+
export interface LoginConfig {
|
|
5
|
+
clientId: string;
|
|
6
|
+
scope: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Resolve the GitHub OAuth client id the device flow authenticates against.
|
|
10
|
+
* EXTENSION_DEV_GITHUB_CLIENT_ID overrides (useful when pointing at a self-
|
|
11
|
+
* hosted platform); otherwise it comes from the platform's public config.
|
|
12
|
+
*/
|
|
13
|
+
export declare function fetchLoginConfig(apiBase: string, fetchImpl?: FetchImpl): Promise<LoginConfig>;
|
|
14
|
+
/**
|
|
15
|
+
* Trade a verified GitHub user token for a project-scoped access token and
|
|
16
|
+
* write it to the local credentials file. Returns the stored credentials
|
|
17
|
+
* (token included for the caller's in-memory use; never logged).
|
|
18
|
+
*/
|
|
19
|
+
export declare function exchangeAndPersist(args: {
|
|
20
|
+
apiBase: string;
|
|
21
|
+
githubToken: string;
|
|
22
|
+
project: string;
|
|
23
|
+
fetchImpl?: FetchImpl;
|
|
24
|
+
}): Promise<StoredCredentials>;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
};
|
|
15
|
+
};
|
|
16
|
+
required: string[];
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
export declare function handler(args: {
|
|
20
|
+
projectPath: string;
|
|
21
|
+
browser?: string;
|
|
22
|
+
}): Promise<string>;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export declare const schema: {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: "object";
|
|
6
|
+
properties: {
|
|
7
|
+
project: {
|
|
8
|
+
type: string;
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
deviceCode: {
|
|
12
|
+
type: string;
|
|
13
|
+
description: string;
|
|
14
|
+
};
|
|
15
|
+
api: {
|
|
16
|
+
type: string;
|
|
17
|
+
description: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
required: string[];
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export declare function handler(args: {
|
|
24
|
+
project: string;
|
|
25
|
+
deviceCode?: string;
|
|
26
|
+
api?: string;
|
|
27
|
+
}): Promise<string>;
|
package/dist/src/tools/open.d.ts
CHANGED
|
@@ -14,6 +14,10 @@ export declare const schema: {
|
|
|
14
14
|
enum: string[];
|
|
15
15
|
description: string;
|
|
16
16
|
};
|
|
17
|
+
name: {
|
|
18
|
+
type: string;
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
17
21
|
browser: {
|
|
18
22
|
type: string;
|
|
19
23
|
default: string;
|
|
@@ -28,4 +32,5 @@ export declare const schema: {
|
|
|
28
32
|
};
|
|
29
33
|
export declare function handler(args: ActArgs & {
|
|
30
34
|
surface: string;
|
|
35
|
+
name?: string;
|
|
31
36
|
}): Promise<string>;
|
|
@@ -24,6 +24,8 @@ export declare const schema: {
|
|
|
24
24
|
required: string[];
|
|
25
25
|
};
|
|
26
26
|
};
|
|
27
|
+
/** Resolve the access token: EXTENSION_DEV_TOKEN env first, then the login creds file. */
|
|
28
|
+
export declare function resolveToken(): string;
|
|
27
29
|
export declare function handler(args: {
|
|
28
30
|
projectPath: string;
|
|
29
31
|
ttlHours?: number;
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@extension.dev/mcp",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "3.17.0
|
|
5
|
-
"description": "MCP server
|
|
4
|
+
"version": "3.17.0",
|
|
5
|
+
"description": "MCP server that lets AI agents build, run, inspect, and publish browser extensions. 26 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",
|
|
@@ -11,15 +11,16 @@
|
|
|
11
11
|
},
|
|
12
12
|
"repository": {
|
|
13
13
|
"type": "git",
|
|
14
|
-
"url": "git+https://github.com/extensiondev/
|
|
14
|
+
"url": "git+https://github.com/extensiondev/mcp.git"
|
|
15
15
|
},
|
|
16
16
|
"bugs": {
|
|
17
|
-
"url": "https://github.com/extensiondev/
|
|
17
|
+
"url": "https://github.com/extensiondev/mcp/issues"
|
|
18
18
|
},
|
|
19
|
-
"homepage": "https://github.com/extensiondev/
|
|
19
|
+
"homepage": "https://github.com/extensiondev/mcp#readme",
|
|
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"
|
|
@@ -46,15 +56,20 @@
|
|
|
46
56
|
"keywords": [
|
|
47
57
|
"mcp",
|
|
48
58
|
"model-context-protocol",
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"browser-extension-development",
|
|
52
|
-
"extension-dev",
|
|
59
|
+
"ai-agent",
|
|
60
|
+
"agents",
|
|
53
61
|
"claude",
|
|
54
62
|
"claude-code",
|
|
55
|
-
"
|
|
63
|
+
"claude-desktop",
|
|
64
|
+
"cursor",
|
|
65
|
+
"browser-extension",
|
|
66
|
+
"chrome-extension",
|
|
67
|
+
"firefox-addon",
|
|
68
|
+
"manifest-v3",
|
|
56
69
|
"webextension",
|
|
57
|
-
"cross-browser"
|
|
70
|
+
"cross-browser",
|
|
71
|
+
"developer-tools",
|
|
72
|
+
"extension.dev"
|
|
58
73
|
],
|
|
59
74
|
"dependencies": {
|
|
60
75
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
@@ -68,14 +83,6 @@
|
|
|
68
83
|
"@types/node": "^25.2.0",
|
|
69
84
|
"@types/ws": "^8.18.1",
|
|
70
85
|
"typescript": "6.0.2",
|
|
71
|
-
"vitest": "
|
|
72
|
-
},
|
|
73
|
-
"scripts": {
|
|
74
|
-
"clean": "rm -rf dist",
|
|
75
|
-
"watch": "rslib build --watch",
|
|
76
|
-
"compile": "rslib build",
|
|
77
|
-
"build": "pnpm run compile",
|
|
78
|
-
"start": "node bin/extension-mcp.js",
|
|
79
|
-
"test": "vitest run"
|
|
86
|
+
"vitest": "^4.1.3"
|
|
80
87
|
}
|
|
81
|
-
}
|
|
88
|
+
}
|
package/dist/rslib.config.d.ts
DELETED
package/dist/vitest.config.d.ts
DELETED