v9.0.0
Every client pays for this server's tool list at the start of every session,
whether or not the user ever touches an extension. That list was 36 tools and
52,403 bytes on the wire (roughly 13,100 tokens). It is now 28 tools and
43,214 bytes (roughly 10,800 tokens), a 17.5% cut, with no capability removed.
Eleven tools folded into the four that already owned their resource,
extension_preview folded into extension_start, and the prose was tightened
everywhere it repeated the schema or a parameter name.
9.0.0 lands close behind 8.0.0 on purpose. 8.0.0 renamed four tools for
disambiguation; this release cuts what the surface costs. Both are breaking,
adoption is still low, and doing them as one migration is cheaper for early
users than spacing them out.
Migration
| Old tool | New call |
|---|---|
extension_detect_browsers({ browsers }) |
extension_browsers({ action: "detect", browsers }) |
extension_list_browsers() |
extension_browsers({ action: "list" }) |
extension_install_browser({ browser }) |
extension_browsers({ action: "install", browser }) |
extension_uninstall_browser({ browser, all }) |
extension_browsers({ action: "uninstall", browser, all }) |
extension_login({ project, deviceCode, api }) |
extension_auth({ action: "login", project, deviceCode, api }) |
extension_whoami() |
extension_auth({ action: "status" }) |
extension_logout() |
extension_auth({ action: "logout" }) |
extension_list_templates({ surface, framework, tags, featured, query }) |
extension_templates({ action: "list", surface, framework, tags, featured, query }) |
extension_get_template_source({ slug, files }) |
extension_templates({ action: "source", slug, files }) |
extension_release_list({ workspace, project, api }) |
extension_release_status({ include: ["releases"], workspace, project, api }) |
extension_store_status({ workspace, project, api }) |
extension_release_status({ include: ["stores"], workspace, project, api }) |
extension_preview({ projectPath, browser, port, noBrowser, ...launch }) |
extension_start({ projectPath, build: false, browser, port, noBrowser, ...launch }) |
Every argument keeps its name and its meaning. action defaults to the most
common case (detect, status, list), so extension_browsers({}) scans,
extension_auth({}) reports the login, and extension_templates({}) lists.
extension_release_status returns both sections by default and nests each
under releases and stores; the old flat bodies are unchanged inside them.
The CLI is untouched: extension-mcp login|logout|whoami|release still work
exactly as before.
extension_submit, extension_publish, extension_analyze,
extension_inspect and extension_dom_snapshot were deliberately NOT merged.
8.0.0 separated them because agents confused them; folding them behind an
action parameter would hide that ambiguity rather than remove it.
Upgrading from 7.0.0
Most installs are still on 7.0.0 and two majors have landed on top of it. Do
both in one pass: apply the 8.0.0 renames, then the 9.0.0 merges above. 7.0.0
advertised 36 tools; 9.0.0 advertises 28, and every capability survived.
| 7.0.0 call | 9.0.0 call | Landed in |
|---|---|---|
extension_deploy(...) |
extension_submit(...) |
8.0.0 |
extension_inspect({ projectPath }) |
extension_analyze({ projectPath }) |
8.0.0 |
extension_source_inspect(...) |
extension_inspect(...) |
8.0.0 |
extension_dom_inspect(...) |
extension_dom_snapshot(...) |
8.0.0 |
extension_detect_browsers({ browsers }) |
extension_browsers({ action: "detect", browsers }) |
9.0.0 |
extension_list_browsers() |
extension_browsers({ action: "list" }) |
9.0.0 |
extension_install_browser({ browser }) |
extension_browsers({ action: "install", browser }) |
9.0.0 |
extension_uninstall_browser({ browser, all }) |
extension_browsers({ action: "uninstall", browser, all }) |
9.0.0 |
extension_login({ project, deviceCode, api }) |
extension_auth({ action: "login", project, deviceCode, api }) |
9.0.0 |
extension_whoami() |
extension_auth({ action: "status" }) |
9.0.0 |
extension_logout() |
extension_auth({ action: "logout" }) |
9.0.0 |
extension_list_templates({ surface, framework, tags, featured, query }) |
extension_templates({ action: "list", surface, framework, tags, featured, query }) |
9.0.0 |
extension_get_template_source({ slug, files }) |
extension_templates({ action: "source", slug, files }) |
9.0.0 |
extension_release_list({ workspace, project, api }) |
extension_release_status({ include: ["releases"], workspace, project, api }) |
9.0.0 |
extension_store_status({ workspace, project, api }) |
extension_release_status({ include: ["stores"], workspace, project, api }) |
9.0.0 |
extension_preview({ projectPath, browser, port, noBrowser, ...launch }) |
extension_start({ projectPath, build: false, browser, port, noBrowser, ...launch }) |
9.0.0 |
Read the extension_inspect row before any of the others. That name exists in
both versions and does not mean the same thing in each. In 7.0.0 it read a
BUILT extension's files off disk. In 9.0.0 it reads a RUNNING extension over
the browser's debugger protocol and needs a live extension_dev or
extension_start session. A 7.0.0 call left alone does not fail with an
unknown-tool error, it silently reaches the wrong tool and reports no dev
session instead of the file sizes you asked for. The disk reader is
extension_analyze now. Every call that passed a bare projectPath and
expected sizes, permissions and store-readiness back has to move.
extension_deploy carried its error names with it into extension_submit:
DeployAuthError, DeployInputError, DeployConfigError,
DeployNetworkError and DeployError are now SubmitAuthError,
SubmitInputError, SubmitConfigError, SubmitNetworkError and
SubmitError. Anything branching on those strings has to move with them.
Every argument keeps its name and its meaning across both majors, with two
exceptions:
extension_release_statusnests what the two 7.0.0 tools returned flat,
underreleasesandstores. The bodies inside are byte-for-byte the old
ones. Omittingincludereturns both sections.extension_startgainedbuild, defaulting totrue.build: falseis
whatextension_previewwas.
Nothing else moved. extension_publish, extension_preview_web,
extension_shares, extension_release_promote, extension_dev,
extension_build, extension_create, extension_add_feature,
extension_wait, extension_stop, extension_logs, extension_eval,
extension_storage, extension_reload, extension_open,
extension_list_extensions, extension_manifest_validate,
extension_theme_verify and extension_doctor are unchanged in name and in
arguments, and the CLI (extension-mcp login|logout|whoami|release) never
moved at all.
Merged
- Four browser tools are one.
extension_browsersdetects, lists,
installs, and uninstalls.detectandlistwere the confusable pair: both
answered "what browsers do I have", and telling them apart took a sentence of
prose in each description. An action enum settles it in the schema. - Three auth tools are one.
extension_authsigns in, reports the stored
login, and clears it. They were a lifecycle triad that each re-explained the
same token model. - Two template tools are one.
extension_templatessearches the catalog
and reads a template's source. The slug you read comes from the list you just
searched, so the pair is one resource. - The two read-only release tools are one.
extension_release_status
returns release channels and recent builds, browser-store submissions and
review state, or both. They took identical arguments and read the same
registry.extension_release_promotestays separate on purpose: it is the
only verb that writes, and putting a write behind the sameaction
parameter as a read is how an agent promotes a build it meant to list. extension_previewfolded intoextension_start. Both answered "run the
production build in a browser"; the only difference was whether a build ran
first. That is nowbuild, defaulting totrue, which matches
extension_preview_web, wherebuild: falsealready means the same thing.
Sharpened
extension_devandextension_startnow say which one to pick.
They are not merged:devis the only tool that can unlock the control
channel (allowControl,allowEval) thatextension_storage,
extension_reload,extension_open,extension_dom_snapshotand
extension_evalneed, andstartruns a production build with none of it.
Amodeparameter would have made those flags look valid on a session that
cannot honor them. Instead each description now opens with the thing that
decides between them and names the other tool.- Descriptions no longer repeat the schema. The biggest cuts, in bytes of
description:extension_shares1,661 to 1,250,extension_preview_web
1,151 to 639,extension_submit1,478 to 1,194,extension_eval1,128 to
831,extension_wait985 to 784,extension_list_extensions944 to 696,
extension_dom_snapshot965 to 854. What was cut was prose that restated a
parameter name, repeated a property's own description, or explained the
response shape the response already carries. What was kept is anything that
stops a tool being misused: theactiveTabgesture warning on
extension_open, the MV3 service-worker CSP note onextension_eval, the
profile-lock explanation onextension_dev, and the irreversibility of
extension_submitand of revoking a share. - Repeated property schemas are shared.
projectPath, the session
browser, the calltimeout, the platformapibase and the launch browser
enum are defined once insrc/lib/common-schema.tsinstead of being
re-typed per tool.
Considered and rejected
- A smaller default surface with the rest opt-in. The platform cluster
(extension_auth,extension_publish,extension_submit,
extension_release_status,extension_release_promote,extension_shares,
extension_preview_web) is 13 KB, about 30% of what is left, and is dead
weight for anyone building an extension locally without an extension.dev
account. Hiding it behind an env flag would cut the default surface by
roughly a third. It was not shipped because a hidden tool is an invisible
capability: an agent asked to publish would report that it cannot, which is
worse than the tokens. The version worth building expands the surface once a
login exists and announces it withnotifications/tools/list_changed, and
that needs a client-by-client compatibility check first.
Added
pnpm exec node scripts/tool-surface-size.mjsstarts the server, calls
tools/list, and reports exactly what a client receives: bytes per tool
split into description and schema, and the total.--jsonfor the raw rows.
Before this, the cost of the tool surface was never measured, only guessed.