@extension.dev/mcp 10.9.0 → 10.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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.9.0",
13
+ "version": "10.10.1",
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.9.0",
4
+ "version": "10.10.1",
5
5
  "author": {
6
6
  "name": "Cezar Augusto",
7
7
  "email": "hello@extension.dev",
package/CHANGELOG.md CHANGED
@@ -1,5 +1,155 @@
1
1
  # Changelog
2
2
 
3
+ ## 10.10.1
4
+
5
+ Five Gecko findings from one agent session on Firefox and Waterfox
6
+ (BUGS_TO_FIX_MCP.md entries 5 to 9), each replayed against a live Firefox
7
+ Nightly before and after the fix where the shape allowed it, plus four more
8
+ that followed them the same week, and the engine release that carries the
9
+ root-cause fix for the first of them.
10
+
11
+ - `extension-create`, `extension-develop` and `extension-install` move from
12
+ 4.1.29 to 4.1.30. That engine settles a promise before a surface relay
13
+ replies to an eval (the root cause behind the first entry below, filed
14
+ from this repo as extension.js 512), names a missing tab, a refused url,
15
+ a closed surface and a browser exit on every act verb instead of a
16
+ generic refusal, and lets `reload` resolve a surface's tab. The relay
17
+ wrapper below stays: a project that pins an older engine in its own
18
+ node_modules still drives that engine.
19
+
20
+ - `extension_eval` on a surface that answers through the in-bundle relay
21
+ (every Gecko session, and Chromium MV2) never hands the relay a promise.
22
+ The relay evaluates synchronously and passes the raw value to
23
+ `sendResponse`; a promise is not cloneable, Firefox reports that to the
24
+ sender as no reply, and the bridge then called the surface "not open"
25
+ while the page was still running the expression. The wrapper now settles
26
+ a thenable inside the page under a token, and the tool polls until it is
27
+ done: an async expression returns its value, a rejection answers `E_EVAL`
28
+ with the page's own message, and one that never settles answers
29
+ `E_WAIT_TIMEOUT` naming where the result will land. The engine's "open it
30
+ first: extension open newtab" hint is now spoken as the tool call it means.
31
+ - `extension_inspect` on Gecko reads a page inside the extension through
32
+ its surface relay: a url that matches a declared surface document (the
33
+ full `moz-extension://` address, the document path, or its file name)
34
+ routes to that context instead of the tab injection, which Firefox
35
+ refuses into extension pages with "Missing host permission for the tab".
36
+ An extension url that matches no declared surface says so and names the
37
+ ones the manifest declares.
38
+ - `extension_stop` reaps the browser the launcher recorded in `ready.json`
39
+ (`browserPid`, `launcherPid`, and any process holding `profilePath`), so
40
+ a Firefox started on a custom profile, or one whose argv never names the
41
+ project, no longer survives a `stopped` answer with `reaped: []`.
42
+ - `extension_doctor` no longer calls a Gecko session unhealthy over a
43
+ browser exit that the executor outlived: Firefox hands a fresh profile to
44
+ a relaunched process and the first one exits 0, which the engine's browser
45
+ leg reports as a failure while storage probes and evals keep answering.
46
+ That leg becomes a warning that says so, and any other failure still
47
+ counts.
48
+ - `extension_browsers` detect finds a managed binary as deep as the cache
49
+ writes it (a Firefox Nightly sits six levels down inside its app bundle),
50
+ so it no longer reports the system Firefox as available while
51
+ `extension_dev` launches the cached Nightly. When both exist, the entry
52
+ carries `systemBinaryPath` and a `devLaunches` line saying which one dev
53
+ starts and how to choose the other. `extension_wait` reports `browserPid`
54
+ and `profilePath` from the contract.
55
+ - `extension_open` with surface `sidebar` on Gecko no longer stops at the
56
+ engine's "sidePanel not available", a Chromium API named on a Firefox
57
+ engine. It asks the sidebar relay whether the `sidebar_action` panel is
58
+ open and reports `status: "already-open"` when it is (Firefox opens the
59
+ panel at install, so it usually is); when it is not, the document is
60
+ rendered as a tab with the gesture rule stated (Firefox opens the panel
61
+ only from the toolbar button or View > Sidebar, Bugzilla 1392624).
62
+ - `extension_eval` with context `page` and a `moz-extension://` url on
63
+ Gecko evaluates through the surface relay of the document that url names,
64
+ with a warning naming the context to pass next time, instead of "chrome.
65
+ scripting is not available ... use context background". An extension url
66
+ that matches no declared surface says which surfaces the manifest
67
+ declares. The url-to-surface mapping now lives in one place for inspect
68
+ and eval.
69
+ - `extension_dev` with `port: 0` no longer warns "Requested port 0 was not
70
+ available": 0 asks the engine for any free port, so the note now says
71
+ which port it picked, and the collision wording stays for a numbered port
72
+ the server could not bind.
73
+
74
+ ## 10.10.0
75
+
76
+ Safari was the one engine this server treated as a dead end, while the
77
+ Extension.js it shells out to had grown a working Safari dev loop. The client
78
+ now rides that loop, and its pinned CLI packages move to the release that
79
+ has it.
80
+
81
+ - `extension-create`, `extension-develop` and `extension-install` move from
82
+ 4.1.2 to 4.1.29, the stable release that carries the `navigate` verb
83
+ (first shipped in a 4.1.28 canary). On Safari that engine reloads the extension
84
+ on every save through the extension's own bridge, streams background and
85
+ content lines into the session's log file, and points a tab at a url
86
+ without eval; a project with no local Extension.js now gets all of that
87
+ from the pin.
88
+ - The bridge tools work on a Safari dev session started with
89
+ `allowControl` or `allowEval`: `extension_storage`, `extension_reload`,
90
+ `extension_open` for surfaces, `extension_dom_snapshot` by tab id,
91
+ `extension_logs`, and the assertions `content-script-injected`,
92
+ `background-worker-booted`, `storage-key-present` and
93
+ `console-errors-empty`, measured live on Safari 27. `extension_eval` in
94
+ `content` or `page` needs a tab already open at the url, and Safari's MV3
95
+ background CSP blocks eval in `background` and the bridge's `url`
96
+ navigation, which the engine reports by name. `surface-rendered` and
97
+ `extension_inspect` still need a target list Safari does not expose, and
98
+ say so.
99
+ - `extension_browsers` reports Safari's version and an `automation` block
100
+ (the safaridriver beside it and whether it speaks `--mcp` and `--bidi`),
101
+ and `extension_doctor` with no `projectPath` gains a `safari-agent` leg,
102
+ so an agent learns when Apple's Safari MCP server can pair with this one.
103
+ The packaged CLAUDE.md and `/extension-debug` describe that pairing.
104
+ - `extension_open` with `url` asks the engine's `navigate` verb first, a
105
+ static tabs call inside the extension that works where an MV3 background
106
+ refuses eval, which is every Safari session. Only an engine that does not
107
+ know the verb yet falls back to the background eval, and that fallback
108
+ names the upgrade when Safari's CSP refuses it.
109
+ - If a dev session records a safaridriver session in `ready.json`
110
+ (`webdriverPort`, `webdriverSessionId`), `extension_eval` with context
111
+ `page` and `extension_open` with `url` use it for the page's main world,
112
+ and `extension_doctor` adds a `safari-window` leg; with no such record
113
+ the leg is a skip and both tools take the bridge.
114
+
115
+ Four defects agents met while driving the server on real extension work
116
+ (BUGS_TO_FIX_MCP.md entries 1 to 4) close in the same release.
117
+
118
+ - The initialize result now carries `instructions`: four lines that name
119
+ the moments (run, wait, inspect, drive, build an extension) and the tools
120
+ that own them, for clients that hide tool descriptions behind a search
121
+ step and would otherwise show the model only a server name and a tool
122
+ count. `createServer()` is exported so a client can initialize against
123
+ the same server object the stdio path connects.
124
+ - `extension_open` with surface `newtab`, `history` or `bookmarks` resolves
125
+ the `chrome_url_overrides` page itself and opens it by url. The engine's
126
+ `open` verb never knew those surfaces and answered `E_ARGS` "unknown
127
+ surface" to exactly what the tool description promised; an override page
128
+ is only ever a tab, so this is the surface, not a fallback, and the hint
129
+ says so.
130
+ - `extension_open` with surface `sidebar` on Chromium, when Chrome refuses
131
+ `sidePanel.open()` for lack of a user gesture, opens the extension's own
132
+ sidebar page in a tab, dispatches a synthetic click on it over CDP, calls
133
+ `chrome.sidePanel.open` from inside that click and closes the tab. The
134
+ panel that opens is the real one; the result reports `gesture:
135
+ "synthetic-click"` and a warning that the toolbar wiring was not
136
+ exercised. If the click does not open it either, the sidebar document is
137
+ rendered as a tab with the reason in a warning, and if even that fails
138
+ the refusal gains a hint naming the url to read instead of ending the
139
+ road. Headless sessions keep their tab fallback and never click.
140
+ - `extension_eval` on a Chromium MV3 session evaluates extension pages over
141
+ CDP: contexts `popup`, `options`, `sidebar`, `newtab`, `history` and
142
+ `bookmarks`, and context `page` with a `chrome-extension://` url. The
143
+ inspector path is not governed by the extension page CSP that blocks the
144
+ in-bundle relay's string eval, and script injection could never reach an
145
+ extension page at all, which is where the misleading "Extension manifest
146
+ must request permission to access this host" came from. A page that is
147
+ not open answers `E_NO_TARGET` with the `extension_open` call that opens
148
+ it; a thrown expression answers `E_EVAL` with the exception text; a bare
149
+ top-level `await` parses, as in the DevTools console; MV2 Chromium and
150
+ Gecko sessions keep the relay. Both this path and the sidebar gesture were
151
+ measured live on Chrome for Testing 151 before release.
152
+
3
153
  ## 10.9.0
4
154
 
5
155
  The client ran its browser work through CLI packages pinned three minor
package/README.md CHANGED
@@ -173,6 +173,40 @@ check registry, so a document from one lane can never be mistaken for the
173
173
  other's. Each check here names the preview check it is the live-browser
174
174
  counterpart of, and a contract test holds the two grammars together.
175
175
 
176
+ ## Safari
177
+
178
+ A Safari dev session runs on the same bridge as every other engine. On
179
+ Extension.js 4.1.28 or newer, `extension_dev --browser=safari` (macOS with
180
+ Xcode) builds the app, opens it, and, once you enable the extension in
181
+ Safari > Settings > Extensions, reloads it on every save through the
182
+ extension's own socket to the dev server and streams its background and
183
+ content lines into the session's log file. With `allowControl` or
184
+ `allowEval`, the control channel is on too: `extension_storage`,
185
+ `extension_reload`, `extension_dom_snapshot` (by tab id), `extension_open`
186
+ for surfaces, `extension_logs`, and the assertions `content-script-injected`
187
+ (on a content line at the url), `background-worker-booted`,
188
+ `storage-key-present` and `console-errors-empty` all work against it.
189
+ `extension_eval` in `content` or `page` needs a tab already open at the url,
190
+ and `extension_open` with `url` cannot open one on Safari: the bridge
191
+ navigates through a background eval, and Safari's MV3 background CSP blocks
192
+ eval, which also blocks `extension_eval` in `background`. Open the page in
193
+ Safari by hand, then read it. Safari has no CDP or RDP, so
194
+ `extension_inspect` has no Safari path and `surface-rendered` answers
195
+ inconclusive, pointing at `extension_dom_snapshot` with a `context`.
196
+
197
+ Safari 27 and Safari Technology Preview 247 also ship Apple's own MCP server
198
+ inside `safaridriver` (enable Safari > Settings > Developer > "Allow remote
199
+ automation and external agents", then
200
+ `claude mcp add safari-mcp -- "/usr/bin/safaridriver" --mcp`). It drives an
201
+ isolated automation window with page-level tools and has no extension-aware
202
+ tool. It runs beside a Safari dev session, because this server never opens
203
+ an automation session of its own: if a dev session ever records one in
204
+ `ready.json` (`webdriverPort`, `webdriverSessionId`), `extension_eval` with
205
+ context `page` and `extension_open` with `url` use it for the page's main
206
+ world, and `extension_doctor` shows it as a `safari-window` leg; today no
207
+ Extension.js release records one, and that leg reads `skip`.
208
+ `extension_browsers` reports whether the machine's safaridriver has `--mcp`.
209
+
176
210
  ## Sharing a build in progress
177
211
 
178
212
  An unpacked extension is unusually hard to hand to someone: the only way to look at a colleague's work-in-progress has been to take their zip and run untrusted code with real browser permissions on your own machine. `extension_preview_web` with `share: true` uploads the `dist/` it just built and returns a link that renders those exact bytes in the emulator. Whoever opens it installs nothing and signs in to nothing, which is what lets a designer, a PM, or a reviewer into the loop at all. Those bytes run in an isolated sandbox origin or they do not run at all: preview refuses a shared build rather than serving it in its own renderer. Sharing needs auth (`extension_auth` or `EXTENSION_DEV_TOKEN`), the link lives 30 days, and `DELETE`ing the returned `revokeUrl` with the same token kills it early. Re-sharing an unchanged build returns that same link rather than a second one, and only a revoked link is replaced by a different one, because revocation is permanent: the address is burned and never resolves again. That makes `revokeUrl` the handle to the link you just made, so every share is also appended to `.extension.dev/shared-previews.json` in the project (gitignored) so it survives losing the tool output. The upload holds up to 2,000 files and about 64MB of text, or roughly 48MB when the build is mostly images, fonts or wasm, which travel base64-encoded. Without `share`, the tool returns a local-only deep link and uploads nothing.
@@ -16,10 +16,7 @@ examples repo (GitHub)
16
16
  │ Generated by: scripts/generate-templates-meta.mjs
17
17
  │ Published as: GitHub release asset (nightly tag)
18
18
  │ Contains: slug, surfaces, framework, permissions, files, downloads, integrity
19
- │
20
- ├── public/<slug>/ Staged sources + pre-built distributions
21
- │ ├── src/ Committed source for website consumption
22
- │ └── dist/<browser>/ Pre-built .zip and .xpi files
19
+ │ File paths are repo-relative: examples/<slug>/...
23
20
  │
24
21
  └── artifacts/ Build pipeline output
25
22
  └── <slug>.<browser>.zip Distributable archives
package/claude/CLAUDE.md CHANGED
@@ -228,6 +228,7 @@ npm run dev -- --logs info --log-url "example.com"
228
228
  ### Other debugging tools
229
229
 
230
230
  - Use `--browser=firefox` to test cross-browser compatibility
231
+ - **Safari (macOS).** On Extension.js 4.1.28 or newer, `--browser=safari` builds the app, opens it, and after you enable the extension in Safari > Settings > Extensions it reloads on every save through the extension's bridge and streams background and content lines into the session log. Start it with `allowEval: true` and the bridge tools work: `extension_storage`, `extension_reload`, `extension_open` for surfaces, `extension_dom_snapshot` by tab id, `extension_logs`, and the assertions except `surface-rendered`. `extension_eval` in `content` or `page` needs a tab already open at the url, which you open in Safari by hand: `extension_open` with `url` and `extension_eval` in `background` are blocked by Safari's MV3 background CSP, and the engine says so. Safari has no CDP or RDP, so `extension_inspect` has no Safari path. Apple's Safari MCP server (`claude mcp add safari-mcp -- "/usr/bin/safaridriver" --mcp`, Safari 27+, after enabling Safari > Settings > Developer > "Allow remote automation and external agents") reads a page in its own isolated window with no extension-aware tool, and runs beside a dev session, since this server opens no automation session of its own.
231
232
  - Check `dist/<browser>/` for build output
232
233
  - Use `--wait` flag to check if dev session is ready (outputs ready.json contract)
233
234
  - Use `npm run start` to test production builds (builds first, then launches)
@@ -240,6 +241,10 @@ npm run dev -- --logs info --log-url "example.com"
240
241
 
241
242
  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.
242
243
 
244
+ Two surfaces the engine's `open` verb cannot serve are handled by the MCP tool itself. `extension_open` with `surface: "newtab"`, `"history"` or `"bookmarks"` resolves the `chrome_url_overrides` page and opens it in a tab, which is the only place the browser renders it. `extension_open` with `surface: "sidebar"` on Chromium, when Chrome refuses `sidePanel.open()` for lack of a user gesture, opens the extension's own sidebar page in a tab, dispatches a synthetic click on it over CDP, calls `chrome.sidePanel.open` from inside that click and closes the tab: the real panel opens, the result says `gesture: "synthetic-click"`, and a warning notes that the toolbar wiring was not exercised. If that fails too, the sidebar document is rendered as a tab and the warning names the reason.
245
+
246
+ On a Chromium MV3 session, `extension_eval` reaches extension pages over CDP rather than the in-bundle relay: contexts `popup`, `options`, `sidebar`, `newtab`, `history`, `bookmarks`, and `context: "page"` with a `chrome-extension://` url. The MV3 extension CSP blocks the relay's string eval and script injection cannot reach an extension page at all, while the inspector path is governed by neither. The page must already be open (`extension_open` first); a closed one answers `E_NO_TARGET` with the call that opens it.
247
+
243
248
  ## Contributing templates to the examples repo
244
249
 
245
250
  If you create a new extension pattern worth sharing:
@@ -33,6 +33,8 @@ Debug the currently running extension dev session. The user said: $ARGUMENTS
33
33
 
34
34
  To see what else is loaded in the browser (Chromium): `extension_list_extensions`.
35
35
 
36
+ **Safari sessions** (Extension.js 4.1.28+) have no CDP, but the bridge works once the extension is enabled in Safari: `extension_logs`, `extension_storage`, `extension_reload`, `extension_open` for surfaces, `extension_dom_snapshot` by tab id, and the assertions except `surface-rendered`. `extension_eval` in `content` or `page` needs a tab the user already opened at the url; `extension_open` with `url` and `extension_eval` in `background` are blocked by Safari's MV3 background CSP. `extension_inspect` has no Safari path. Apple's `safari-mcp` can read a page in its own window beside the dev session; neither it nor this server can open the popup or background page, which stay Web Inspector, attended.
37
+
36
38
  4. **Diagnose common issues**
37
39
  Based on what you find, check for:
38
40
  - **"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.
@@ -761,6 +761,20 @@ is carried as `value.sessionCommand`.
761
761
  "engine": "gecko",
762
762
  "cdpSupport": false,
763
763
  "rdpSupport": true
764
+ },
765
+ {
766
+ "browser": "safari",
767
+ "binaryPath": "/Applications/Safari.app/Contents/MacOS/Safari",
768
+ "source": "system",
769
+ "engine": "webkit",
770
+ "version": "27.0",
771
+ "cdpSupport": false,
772
+ "rdpSupport": false,
773
+ "automation": {
774
+ "safaridriver": "/usr/bin/safaridriver",
775
+ "mcp": true,
776
+ "bidi": true
777
+ }
764
778
  }
765
779
  ],
766
780
  "managed": {
@@ -770,6 +784,8 @@ is carried as `value.sessionCommand`.
770
784
  }
771
785
  ```
772
786
 
787
+ `automation` appears on Safari only (macOS): whether the safaridriver beside it speaks `--mcp` (Apple's Safari MCP server, Safari 27+) and `--bidi`. The hint says how to pair that server when it is there.
788
+
773
789
  **Why this matters:** Before Claude runs `extension_dev --browser=firefox`, it should know if Firefox is actually installed. This prevents "browser not found" errors and lets Claude suggest `extension_browsers` when needed. Especially important for Docker/devcontainer environments.
774
790
 
775
791
  ---