# Extension.dev platform documentation
> Extension.dev is the hosted platform that takes a finished browser extension from build to every browser's store. These docs cover the console, the publish rail, shared previews, and the agent tools: the MCP server, the agent skill, and the CLI.
Extension.dev sponsors Extension.js, the open source framework it builds on. The framework covers create, develop and build on your own machine, up to packaging, and is documented at https://extension.js.org. The platform begins where a workspace or an access token is minted, and that is what these docs describe.
extension.dev is not open to the public yet. Every page here is truthful about the platform. Signup and the hosted console answer that they are not open yet until it opens.
- [Overview](https://docs.extension.dev): What Extension.dev is, the map of the docs, and where to go next.
---
- [MCP server](https://docs.extension.dev/tools/mcp): Give a coding agent the build, preview, release and publish verbs.
# MCP server
`@extension.dev/mcp` gives a coding agent hands on a real browser and on the
platform. An agent editing extension files blind will happily fix a content
script that never injected without noticing it never injected. These tools let
it debug from evidence.
## Install
```bash
claude mcp add extension-dev -- npx @extension.dev/mcp --features=local,platform
```
The server has two tool groups. Without `--features` it starts with the
`local` group alone, the 23 tools that work on this machine; the 9 platform
tools (account, share, release and store) come on with
`--features=local,platform` or `EXTENSION_DEV_FEATURES=local,platform`.
Or as a plugin, which adds the server plus the `/extension`,
`/extension-add`, `/extension-debug` and `/extension-publish` commands (the
plugin starts the server with the default local group):
```
/plugin marketplace add extensiondev/mcp
/plugin install extension-mcp@extensiondev-mcp
```
The server is listed in the MCP registry as `io.github.extensiondev/mcp`. It
works with Claude Code, Claude Desktop, Cursor, and any MCP client; every
client's block, with a local-only variant, is on [Install in your client](/tools/mcp/clients).
The same shape works in any client configured by hand:
```json
{
"mcpServers": {
"extension-dev": {
"command": "npx",
"args": ["@extension.dev/mcp", "--features=local,platform"]
}
}
}
```
## How it works
The server does not reimplement Extension.js, it wraps the CLI and the local
[agent bridge](/tools/agent-bridge). Tools that run a build or launch a
browser shell out to the `extension` command, and so do the act verbs
(`eval`, `storage`, `reload`, `open`), `doctor`, `dom_snapshot` and
`assert`; the rest run in process and read the session's `ready.json`
contract and the bridge's local channel exactly as any other client would.
That means the assistant and the CLI always behave the same way, and your
assistant never holds anything the CLI does not.
## Session gates
`extension_dev` is the entry point, and the session it starts decides what
the act tools may do:
- `extension_storage`, `extension_reload`, `extension_open` and
`extension_dom_snapshot` ride the local control channel, which a dev
session carries by default. Start it with `allowControl: false` for a
read-only session (logs, inspect, wait, doctor).
- `extension_eval` is the one gate that stays off: it needs a session started
with `allowEval: true`, which writes a session token file with `0600`
permissions and implies control.
A session does not grow a gate on a second call: that call is refused so the
session does not fork. Call `extension_dev` again with the flag you need
plus `replace: true`, which stops the first session.
Reading is always free. Running code inside the extension is opt-in per
session; the control verbs act on the extension the same machine already
runs, which is why they are on unless you turn them off.
## Engine pinning for scaffolds
Projects scaffolded through the create tool declare the Extension.js engine
version inside the project itself. The project-local pin wins over the server
environment, so later sessions build with the version that the scaffold
declared.
## The tools
The server registers 32 tools, one page each, generated from the schemas the server registers so the pages cannot drift from the code. The 23 local tools need no account. The 9 platform tools come on with `--features=local,platform` and are described on [The platform lane](/tools/mcp/platform). Every page carries the inputs, whether the tool only reads, the gate it needs, and a prompt that reaches for it.
### Create and build
| Tool | What it does | |
| --- | --- | --- |
| [`extension_add_feature`](/tools/mcp/add-feature) | Plan a new feature surface for an existing extension. | reads |
| [`extension_build`](/tools/mcp/build) | Build a browser extension for production. | acts |
| [`extension_create`](/tools/mcp/create) | Create a browser extension project from a template in the extension.dev catalog. | acts |
| [`extension_docs_search`](/tools/mcp/docs-search) | Find the docs page to cite before answering any question about a WebExtension API, a manifest key, a permission, or a difference between Chrome, Firefox, Edge and Safari. | reads |
| [`extension_templates`](/tools/mcp/templates) | Browse the extension.dev template catalog. | reads |
### Run
| Tool | What it does | |
| --- | --- | --- |
| [`extension_dev`](/tools/mcp/dev) | Run the extension while you edit it: dev build, hot module replacement, and a browser with the extension loaded. | acts |
| [`extension_start`](/tools/mcp/start) | Run the PRODUCTION build in a browser: build the project, serve it, and launch. | acts |
| [`extension_stop`](/tools/mcp/stop) | Stop a session that extension_dev or extension_start is running: terminate the server and the browser it launched, and remove the live-preview carrier if extension_dev placed one. | acts |
| [`extension_wait`](/tools/mcp/wait) | Wait for a running dev or start session to be ready. | reads |
### See
| Tool | What it does | |
| --- | --- | --- |
| [`extension_analyze`](/tools/mcp/analyze) | Analyze a BUILT extension on disk: file sizes, declared entry points, permissions, bundle composition, and store-readiness checks. | reads |
| [`extension_doctor`](/tools/mcp/doctor) | Diagnose a dev session end to end: ready contract, dev-server process, control-port agreement, control channel, eval token, executor, browser liveness. | reads |
| [`extension_dom_snapshot`](/tools/mcp/dom-snapshot) | Take a shallow structured DOM snapshot of one chosen surface through the agent bridge (localhost only; the snapshot itself needs no CDP, but listTargets and `tabUrl` resolution... | reads |
| [`extension_inspect`](/tools/mcp/inspect) | Inspect a running extension deeply over the browser's debugger protocol: full HTML (open shadow roots of #extension-root and [data-extension-root] hosts inlined; other shadow ro... | acts |
| [`extension_list_extensions`](/tools/mcp/list-extensions) | List the extensions in the running dev browser: id, name, version, and, on Chromium, live contexts. | reads |
| [`extension_logs`](/tools/mcp/logs) | Read or stream logs from every context of a running dev session (service worker, content scripts, popup, options, sidebar, devtools, pages) in one ordered timeline. | reads |
| [`extension_manifest_validate`](/tools/mcp/manifest-validate) | Validate a manifest.json across browsers. | reads |
| [`extension_theme_verify`](/tools/mcp/theme-verify) | Verify a Chrome theme manifest before it ships. | reads |
### Test
| Tool | What it does | |
| --- | --- | --- |
| [`extension_assert`](/tools/mcp/assert) | Run a test stage against a live dev session: state expectations and read one verdict for each, instead of reading a blob and hand-rolling the judgement. | acts |
### Act
| Tool | What it does | |
| --- | --- | --- |
| [`extension_eval`](/tools/mcp/eval) | Evaluate an expression in a running extension context. | acts |
| [`extension_open`](/tools/mcp/open) | Open an extension surface, or replay an event, in a running session. | acts |
| [`extension_reload`](/tools/mcp/reload) | Reload a running extension's background context, or a tab. | acts |
| [`extension_storage`](/tools/mcp/storage) | Read or write chrome.storage in a running extension. | acts |
### Browsers
| Tool | What it does | |
| --- | --- | --- |
| [`extension_browsers`](/tools/mcp/browsers) | Find, install and remove the browsers Extension.js tooling can launch. | acts |
### Platform
| Tool | What it does | |
| --- | --- | --- |
| [`extension_auth`](/tools/mcp/auth) | Sign this machine in to extension.dev, report that login, or clear it. | acts |
| [`extension_preview_web`](/tools/mcp/preview-web) | Preview an in-progress extension in the web emulator, with no real browser. | acts |
| [`extension_project_create`](/tools/mcp/project-create) | Create an extension.dev project for an extension that does not have one yet, without opening the console. | acts |
| [`extension_publish`](/tools/mcp/publish) | Publish the project your stored token is scoped to (extension_auth, or EXTENSION_DEV_TOKEN) to extension.dev, and return its shareable URL. | acts |
| [`extension_release_promote`](/tools/mcp/release-promote) | Promote a built extension to a release channel (stable, preview, beta, …) on extension.dev, headless. | acts |
| [`extension_release_status`](/tools/mcp/release-status) | Read where a project stands on extension.dev, from the public registry (registry.extension.land). | reads |
| [`extension_shares`](/tools/mcp/shares) | List and revoke the public preview links this token has shared, which is what extension_preview_web share:true hands out. | acts |
| [`extension_submit`](/tools/mcp/submit) | Submit a built extension for store REVIEW through extension.dev, which holds your store credentials and dispatches from your project's mirror CI: the Chrome Web Store, Firefox A... | acts |
| [`extension_workspace_create`](/tools/mcp/workspace-create) | Create an extension.dev workspace that does not exist yet, without opening the console. | acts |
## The platform lane
The nine platform tools stay off until the server is started with `--features=local,platform`, and sharing, publishing, promoting and submitting need a login at [extension.dev/device](https://extension.dev/device). Login, sharing a build in progress, publishing versus submitting, and creating several projects under one approval are on [The platform lane](/tools/mcp/platform). The flags, the approvals that wait for a person and the one telemetry event are on [Server flags and telemetry](/tools/mcp/flags). What works against Safari is on [Safari](/tools/mcp/safari).
## Pair with the skill
The MCP server pairs with [`@extension.dev/skill`](/tools/skill), which
teaches the agent when to reach for each tool. The server without the skill
gives an agent hands with no instructions.
## When it fails
- A platform tool returns an auth error: the stored device login lives at
most 7 days. Log in again with `extension_auth`, or set
`EXTENSION_DEV_TOKEN`.
- The device page says the project does not exist: login mints a code for
any name, and the check happens at approval. Create the project at
[extension.dev/new](https://extension.dev/new), then log in again.
- `extension_eval` answers `eval-disabled`: the session was started without
`allowEval: true`, and no route of the tool runs without that token. On a
Chromium MV3 session the context defaults to `page`, the active tab; pass
`context: "background"` to reach the service worker over the debug port.
- An act tool refuses: eval and control are session opt-in. Call
`extension_dev` again with the gate you need plus `replace: true`, then
retry.
- `extension_list_extensions` misses an extension: a dormant MV3 service
worker with no open page is absent until it wakes.
## Next
- [Install in your client](/tools/mcp/clients), [Server flags and telemetry](/tools/mcp/flags), [The platform lane](/tools/mcp/platform), [Safari](/tools/mcp/safari)
- [Run beside Chrome DevTools MCP](/tools/chrome-devtools-mcp) says which server to reach for
- [Agent bridge](/tools/agent-bridge) is the capability behind the local tools
- [Agent logs](/tools/logs) and [Inspect and act](/tools/inspect-and-act) in depth
- [Publish from the CLI](/tools/publish) links a project's build page
- [Access tokens](/tools/access-tokens)
---
- [Server flags and telemetry](https://docs.extension.dev/tools/mcp/flags): The three flags that shape what the MCP server exposes, the approvals that wait for a person, the untrusted-data fence, and the one telemetry event.
# Server flags and telemetry
The server is one command with three flags. Each one is also an environment variable, so a client config and a CI job can set the same thing. The flags decide which tools are listed; the [session gates](/tools/mcp#session-gates) decide what a listed tool may do.
## The flags
Two tool groups, three flags, each also an environment variable:
- `--features` picks the groups. The default is `local`, the 23 tools that
work on this machine. `--features=local,platform` adds the 9 platform
tools (account, share, release and store); `--features=platform` exposes
only that group. Env: `EXTENSION_DEV_FEATURES`.
- `--no-ship` refuses the calls that put something in front of other people:
`extension_publish`, `extension_release_promote`, `extension_submit` with
`dryRun: false`, `extension_preview_web` with `share: true`, and a share
revoke. Dry runs, share listing, login, and workspace or project creation
still work. Env: `EXTENSION_DEV_NO_SHIP=1`.
- `--project /` pins the server to one project, however
many logins the machine holds; a call naming another project is refused.
Env: `EXTENSION_DEV_PROJECT`.
A refused call answers `E_TOOL_DISABLED` with the flag to change.
A real store submission, a promotion to stable, and a share revoke also wait
for a person by default: the first call answers `approval-required` with a
link on extension.dev, a workspace member approves exactly that action (a
store submission needs a workspace owner), and the same call with the
returned `approvalId` runs it once. `EXTENSION_DEV_APPROVAL_GATE=1` extends
this to every promotion; `EXTENSION_DEV_APPROVAL_GATE=0` turns it off.
Every tool carries MCP annotations (`readOnlyHint`, `destructiveHint`,
`idempotentHint`, `openWorldHint`), so clients can auto-approve reads and
ask before the rest.
Answers that can carry text a web page or an extension wrote (logs, DOM,
eval results, storage, titles, runtime errors) fence it between
`` and ``, with a fresh random `ID`
per call in `untrusted.boundary`. The page cannot predict the ID or close
the fence early, and the JSON still parses to the same fields. A fence is a
signal to the model, not a gate: pair it with `--no-ship` when the agent
reads pages you do not trust.
```json
{
"mcpServers": {
"extension-dev": {
"command": "npx",
"args": ["@extension.dev/mcp", "--features=local,platform", "--no-ship"]
}
}
}
```
## Telemetry
`extension_create` sends one event after a successful scaffold,
`draft_seeded`, to PostHog at `https://us.i.posthog.com/capture/` with a
public write-only project key. It carries the template slug, the template
source (`template` from this server; the field also admits `fork` and
`blank-init`), the template commit it resolved, a random per-process session
id that changes on every server start, the environment, and the fixed names
`source`, `entry` and `emitted_from`; never a path, a project name, a user
name, an address or anything read from your files. No other tool sends an
event; the platform calls (create, share, publish, submit, shares) carry a
per-install id header to extension.dev, rotated every 30 days. Set
`EXTENSION_DEV_NO_TELEMETRY=1` or `DO_NOT_TRACK=1` in the server's
environment to turn both off.
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients): every client's block with a local-only variant
- [The platform lane](/tools/mcp/platform): login, sharing, publishing and store submission
---
- [The platform lane](https://docs.extension.dev/tools/mcp/platform): Login, creating several projects under one approval, sharing a build in progress, and publishing versus submitting, for the nine platform tools.
# The platform lane
The nine platform tools connect an agent to [extension.dev](https://extension.dev). The platform is in private alpha: the tools stay off until the server is started with `--features=local,platform` (see [Server flags](/tools/mcp/flags)), and sharing, publishing, promoting and submitting need a login. The tools: [`extension_auth`](/tools/mcp/auth), [`extension_workspace_create`](/tools/mcp/workspace-create), [`extension_project_create`](/tools/mcp/project-create), [`extension_preview_web`](/tools/mcp/preview-web), [`extension_shares`](/tools/mcp/shares), [`extension_publish`](/tools/mcp/publish), [`extension_release_promote`](/tools/mcp/release-promote), [`extension_release_status`](/tools/mcp/release-status) and [`extension_submit`](/tools/mcp/submit).
## Login
Local tools need nothing. The platform tools read `EXTENSION_DEV_TOKEN`, a
workspace or project [access token](/tools/access-tokens), from the
environment, or use the device login:
`extension_auth` with `action: "login"` and
`project: "/"` returns a user code and a URL. The human
approves the code at [extension.dev/device](https://extension.dev/device).
GitHub is federated server side, so no GitHub token reaches the machine. The
resulting token is project scoped, stored locally, never returned to the
agent, and lives at most 7 days.
Login names a project that already exists on the platform, but the login
call does not check the name. A code is always minted, so nobody can use
this endpoint to probe which private projects exist. The check happens at
approval, where you are signed in and your own access is known. An
authorization for a project you cannot access fails there. A code nobody
approves simply expires. Bootstrap in the browser: import a repository or
a template at [extension.dev/new](https://extension.dev/new), then log in
again.
The same flow exists headless: `extension-mcp login`, `extension-mcp whoami`,
`extension-mcp logout`, and `extension-mcp release promote`. From the
package itself:
```bash
npx @extension.dev/mcp login --project /
```
CI re-mints from the console's Access tokens page, or sets
`EXTENSION_DEV_TOKEN`. Add `--project /` to the server's
arguments so that server only ever acts on that project; the console's
Connect dialog fills both in.
## Several projects under one approval
`extension_project_create` takes `projects`, a list of `{ project, repo }`
entries in one workspace, in place of `project` and `repo`: one approval
page lists every name, each project is created by its own request, and each
one's 7-day token is stored as that project's login. `extension_auth` with
`action: "login"` takes `projects`, a list of `/` names
of existing projects, and stores one token per project, which is also how
logins that expire together are renewed. Both lists: one workspace, 1 to 20
names, each by its exact slug (lowercase letters and digits joined by single
dashes, at most 48 characters), none twice. A create list is capped at 10,
the platform's limit per approving account per hour, and a longer list is
refused, never split. While projects remain the answer is
`status: "creating"` with the same `deviceCode` to call again, always with
one row per project, so a refusal on one never hides the others. A server
pinned with `--project` refuses a list that names any other project.
## Share a build in progress
`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, the bytes run in an
isolated sandbox origin or not at all, and the link also serves the build as
a zip, so it hands over the built code. The link lives for the workspace
plan's share window (30 days on Free, longer on Pro) and the answer carries
its exact `expiresAt` and a `revokeUrl`; re-sharing an unchanged build
returns the same link, a revoke is permanent, and every share is appended to
`.extension.dev/shared-previews.json` in the project (gitignored). The
upload holds up to 2,000 files and about 64MB of text, roughly 48MB when the
build is mostly images, fonts or wasm. Without `share`, the tool returns a
local-only deep link and uploads nothing.
`extension_shares` lists every link the token has shared, live and dead,
with each `previewUrl` and `revokeUrl`, revokes one by `artifactId` or any
of its URLs, and with `projectPath` reconciles against the project's own
record (`remoteOnly`, `localOnly`) without rewriting it.
## Publish is not submit
The shipping verbs do different things, so keep them apart.
`extension_publish` answers with the project's build page on its workspace
host: the canonical [public page](/share/public-pages) for a public project,
a time-limited `?share=` link (1 to 168 hours) for a private one. It uploads
nothing and never changes the project's visibility. The revocable preview
link is a different verb: `extension_preview_web` with `share: true`
uploads the local build and mints it
([Share an unpublished build for review](/share/unpublished-build-for-review)).
`extension_submit` sends a released build into a store's review
queue (Chrome Web Store, Edge Add-ons, Firefox AMO and the App Store for
Safari) through extension.dev, which holds your store credentials and
dispatches from your project's mirror CI; credentials are never tool
arguments. That is not reversible from the CLI; only Chrome accepts a
cancel. It defaults to a dry run, and `dryRun: false` needs the workspace
owner's token. Read the dry run before letting an agent submit. Safari and
the App Store are one paid lane, so a free workspace is refused there, and
the refusal covers the whole call: submit the other three stores with
`safari` left out of `browsers`.
`extension_release_promote` moves a release channel to a tested build,
headless, from CI or an agent session. After a real submission,
`extension_release_status` reads the recorded outcome, per-store credential
health, and review state from the project's public registry, so agents and
CI can answer "was it approved?" without a console visit.
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Server flags and telemetry](/tools/mcp/flags): `--features`, `--no-ship`, `--project` and the approvals
- [Access tokens](/tools/access-tokens) and [Publish from the CLI](/tools/publish)
---
- [Safari](https://docs.extension.dev/tools/mcp/safari): What the MCP server can and cannot do against a Safari dev session, and the Extension.js versions each leg needs.
# Safari
A Safari dev session runs on the same bridge as every other engine. On
Extension.js 4.1.21 or newer, `extension_dev` with `browser: "safari"`
(macOS with Xcode) builds the app, opens it, and, once you enable the
extension in Safari > Settings > Extensions, reloads it on every save and
streams its background and content lines into the session log. With the
control channel on (the default) or `allowEval`, `extension_storage`, `extension_reload`,
`extension_dom_snapshot` (by tab id), `extension_open` for surfaces,
`extension_logs`, and the assertions `content-script-injected` (on a content
line at the url), `background-worker-booted`, `storage-key-present` and
`console-errors-empty` work against it.
The limits: Safari's MV3 background CSP blocks eval, so `extension_eval` in
`background` fails. Safari has no CDP or RDP, so `extension_inspect` has no
Safari path; `surface-rendered` reads the surface through the relay and can
pass or fail. `extension_open` with `url` and `extension_eval` with context
`page` ride a safaridriver automation session, which the dev session opens
on Extension.js 4.1.32 or newer when Safari > Settings > Developer > "Allow
remote automation" (the Develop menu on older Safari) is on (`ready.json` records
`webdriverPort` and `webdriverSessionId`; `extension_doctor` shows it as a
`safari-window` leg, `skip` with the engine's own reason when it could not
open one). Without that session `extension_open` with `url` rides the
bridge's navigate verb instead (Extension.js 4.1.29 or newer; older engines
need a background eval that Safari's CSP blocks), and `extension_eval` in
`content` or `page` needs that tab already open at the url. Apple's own MCP server in
`safaridriver --mcp` has page-level tools and no extension-aware tool, and
runs beside a Safari dev session; `extension_browsers` reports whether the
machine's safaridriver has `--mcp`.
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [`extension_dev`](/tools/mcp/dev), [`extension_open`](/tools/mcp/open), [`extension_eval`](/tools/mcp/eval), [`extension_browsers`](/tools/mcp/browsers)
---
- [Agent skill](https://docs.extension.dev/tools/skill): The judgment half of the agent stack, one skill in the open Agent Skills format.
# Agent skill
The [MCP server](/tools/mcp) gives an agent hands. `@extension.dev/skill`
gives it judgment: when to reach for which tool, the cross browser rules,
the silent failure gotchas, and the publish checklist. The package ships one
agent skill in the open [Agent Skills](https://agentskills.io) format: plain
markdown, no login, no network, so it works with any agent that reads the
format.
## Install
Every lane installs the same skill; pick the one that fits your setup.
Any agent, one command. It installs into every agent it finds on your
machine; add `-g` for user scope instead of project scope, or `-a claude-code`
(or `-a cursor`, `-a copilot`) to target one agent:
```bash
npx skills add extensiondev/skill
```
Claude Code, through the plugin marketplace:
```
/plugin marketplace add extensiondev/skill
/plugin install extension-dev@extensiondev
```
GitHub CLI:
```bash
gh skill install extensiondev/skill extension-dev
```
npm, version locked to your dependency tree. The package declares the
`agents.skills` field, so skills-aware installers link it into your agents on
install; the manual fallback is a copy:
```bash
npm i -D @extension.dev/skill
cp -R node_modules/@extension.dev/skill/skills/extension-dev .claude/skills/
```
Agents without skills support take one line in `AGENTS.md` or `CLAUDE.md`:
```
For any browser-extension work, read .agents/skills/extension-dev/SKILL.md before writing code.
```
## Your first prompt
```
Create a browser extension that highlights every external link on a page.
It must work on Chrome and Firefox from a single codebase, and I want proof
it actually injects: inspect the live browser, do not just tell me it works.
```
A skill-equipped agent scaffolds from a template, writes one prefixed
manifest instead of two, and verifies injection against the live browser
instead of declaring victory.
## What is inside
`SKILL.md` carries the trigger metadata, a seven step workflow and the core
rules. The workflow: start from a [template](https://templates.extension.dev)
or migrate an existing build; treat `manifest.json` as the source of truth;
write cross browser by default with `chromium:` and `firefox:` prefixes;
develop with feedback, never faith, by reading logs and the live DOM; build
and verify per browser, then stop the session; show someone the build as a
link, not a file; keep publish and submit apart. The steps name the CLI
command beside the MCP tool wherever one exists, so the skill works without
the server and does more with it.
The skill is `@extension.dev/skill` 1.0.2. `SKILL.md` is 286 lines and always loads when the skill triggers; the agent then reads only the reference file that matches the task. The 14 references:
| File | Title | Read when | Lines |
| --- | --- | --- | --- |
| `templates.md` | Template Catalog | Choosing a starting point, learning a pattern from a working example | 99 |
| `project-structure.md` | Project Structure and File Patterns | Creating or reorganizing project files, HTML/mount patterns, config | 228 |
| `cross-browser.md` | Cross-Browser Rules | Anything touching Firefox, manifest prefixes, API namespace choices | 244 |
| `api-gotchas.md` | Runtime API Gotchas | Service worker, permissions, tabs, messaging, or runtime API bugs | 108 |
| `debugging.md` | Debugging: Eyes on the Live Browser | The extension misbehaves and you need eyes on the live browser | 252 |
| `publishing.md` | Building and Publishing | Building zips, store accounts and credentials, listings, publishing | 325 |
| `store-md.md` | STORE.md: One Store Metadata File for Every Store | Creating or updating STORE.md, store metadata, reviewer notes, rejections | 138 |
| `migrating.md` | Migrating an Existing Extension | Bringing an existing webpack, Vite, gulp or pug extension under the engine | 73 |
| `surface-popup.md` | Surface: toolbar popup | The extension's main surface is a toolbar popup (`action`) | 45 |
| `surface-content.md` | Surface: content script | The extension changes or reads web pages (`content_scripts`) | 55 |
| `surface-sidepanel.md` | Surface: side panel | The extension's main surface is a side panel or sidebar | 53 |
| `surface-devtools.md` | Surface: DevTools panel | The extension adds a DevTools panel (`devtools_page`) | 50 |
| `surface-newtab.md` | Surface: new tab page | The extension overrides the new tab page | 45 |
| `surface-options.md` | Surface: options page | The extension has an options page (`options_ui`) | 42 |
The budget follows the Agent Skills guidance: the frontmatter costs about a
hundred tokens at every turn, the body loads only when the skill triggers,
and a reference loads only when its row above matches the task.
## How the skill is checked
The package ships 4 graded tasks under `evals/`, each a prompt, the files it starts from, the expected outcome and the assertions a grader checks:
| Task | The prompt starts |
| --- | --- |
| `cross-browser-sidebar` | Create a browser extension project called notes-panel that shows a sidebar with a simple notes textarea that persists between sessions. |
| `injection-debugging` | I'm building a browser extension with extension.dev. |
| `service-worker-state` | Here is my extension's background service worker and manifest (in the input directory). |
| `store-readiness` | My Quote Keeper extension (in the input directory) is ready for users. |
In the shipped benchmark the first three tasks were run with identical
prompts: the skill run passed 15 of 15 graded assertions against 10 of 15
for a no-skill baseline, and the fourth task was run later as a single
sample at 5 of 5 against 4 of 5. The harness, fixtures and assertion sets
ship in the package, so the numbers are reproducible.
Four test suites and a spec check keep the words true. `skill-structure`
validates frontmatter, reference links, the progressive disclosure line
budget, version sync across the plugin manifests and copy conventions.
`conventions-sync` asserts the documented conventions against an Extension.js
checkout. `mcp-sync` asserts the stated tool count, every `extension_*` name,
and the CLI command and flag parity against the MCP package. `templates-sync`
asserts every template slug the skill recommends against the nightly
template catalog. `skills-ref validate` checks the skill against the open
specification. When Extension.js changes a documented convention, the sync
test fails and names the reference file to update, and the cross-browser
reference carries a "verified against Extension.js x.y.z" pin that is bumped
on re-verification.
```bash
npm test
npx skills-ref validate skills/extension-dev
```
## The skill in action
Asked about a content script that never shows up, with no error anywhere, a
skill-equipped agent reads the debugging reference and lays out the evidence
order before touching code:
```
My content script never shows up on example.com and there are no errors.
Where do I start?
```
## Why both
Extensions fail quietly. A content script that never injects, a panel that
never opens, a permission that returns `undefined` with no error. An agent
with tools but no judgment will call them, read a plausible result, and move
on.
| Package | Supplies |
| --- | --- |
| `@extension.dev/mcp` | The verbs: scaffold, run, inspect, act, ship |
| `@extension.dev/skill` | The rules: what to check, in what order, and when a result is a lie |
Pair them in one line:
```bash
claude mcp add extension-dev npx @extension.dev/mcp
```
Cross browser covers text too. For popup text that follows the browser
language, start from the
[Action Locales template](https://templates.extension.dev/action-locales).
## When it fails
- The skill never triggers: confirm it landed in
`.claude/skills/extension-dev` or your agent's skills directory. Agents
without skills support need the one line in `AGENTS.md` above.
- `npm i` linked nothing: your installer is not skills aware. Use the manual
copy above.
- A sync test fails after an Extension.js release: the failure names the
reference file; update it and bump its pin.
## Next
- [MCP server](/tools/mcp) for the tools it directs
- [CLI](/tools/cli) for the command line path it documents
---
- [Run beside Chrome DevTools MCP](https://docs.extension.dev/tools/chrome-devtools-mcp): Use Google's chrome-devtools-mcp and the extension.dev MCP server in one agent config, and know which one to reach for.
# Run beside Chrome DevTools MCP
[chrome-devtools-mcp](https://github.com/ChromeDevTools/chrome-devtools-mcp)
gives an agent raw control of a live Chrome instance, and recent versions added
extension tools (`install`, `list`, `reload`, `trigger` and `uninstall`, plus
service worker logs). [`@extension.dev/mcp`](/tools/mcp) covers the same
extension capabilities build-aware and across browsers, because it wraps an
[Extension.js](https://extension.js.org) dev session instead of a bare
browser.
They are complementary, not competing. Run both: let chrome-devtools-mcp drive
Chrome itself, and let the extension.dev server own everything that depends on
the project: the build, hot reload, the generated manifest, storage, logs
across every context, and the same flow on Firefox, Edge and Safari.
## When to use which
| Task | Use |
| --------------------------------------------------------------- | ---------------------------------------------------------- |
| Build, dev server, hot reload, production packaging | extension.dev (`extension_dev`, `extension_build`) |
| Read the generated, cross-browser manifest | extension.dev (`extension_manifest_validate`) |
| Replay the toolbar action handler, scriptably, no popup | extension.dev (`extension_open` with `surface: "action"`) |
| Replay a keyboard shortcut command handler | extension.dev (`extension_open` with `surface: "command"`) |
| List extensions with a live context in the dev browser | extension.dev (`extension_list_extensions`) |
| Logs across service worker, content scripts, popup, options | extension.dev (`extension_logs`) |
| Read or write `chrome.storage`, eval in any context | extension.dev (`extension_storage`, `extension_eval`) |
| Same flow on Firefox, Edge or Safari | extension.dev (cross-browser by design) |
| Drive an arbitrary web page, click, scroll, fill forms | chrome-devtools-mcp |
| Performance traces, Lighthouse audits, network inspection | chrome-devtools-mcp |
| Inspect an extension you did not build (no project) | chrome-devtools-mcp |
| Trigger the action with a **real gesture** (grants `activeTab`) | chrome-devtools-mcp (`trigger_extension_action`) |
Rule of thumb: if the task touches the **project** (source, build, manifest,
reload), reach for the extension.dev server. If it touches the **browser**
generically (pages, network, performance), reach for chrome-devtools-mcp.
## Install both
The extension.dev tools are namespaced `extension_*`, so they never collide
with chrome-devtools-mcp tools, and an agent fluent in one transfers to the
other.
::: code-group
```bash [Claude Code]
# extension.dev, build-aware extension control
claude mcp add extension-dev npx @extension.dev/mcp
# chrome-devtools-mcp, raw Chrome debugging (extension tools opt-in)
# `--` hands the flag to the server; without it `claude` parses it as its own
claude mcp add chrome-devtools -- npx chrome-devtools-mcp@latest --categoryExtensions
```
```json [Claude Desktop]
{
"mcpServers": {
"extension-dev": {
"command": "npx",
"args": ["@extension.dev/mcp"]
},
"chrome-devtools": {
"command": "npx",
"args": ["chrome-devtools-mcp@latest", "--categoryExtensions"]
}
}
}
```
```json [Cursor]
{
"mcpServers": {
"extension-dev": {
"command": "npx",
"args": ["@extension.dev/mcp"]
},
"chrome-devtools": {
"command": "npx",
"args": ["chrome-devtools-mcp@latest", "--categoryExtensions"]
}
}
}
```
:::
::: tip Pipe transport
The chrome-devtools-mcp extension tools (`--categoryExtensions`) need a pipe
connection. Attaching to an already running browser over a WebSocket endpoint
(`browserUrl` or `wsEndpoint`) is not supported for that category before
Chrome 149. The extension.dev server connects to the browser that the dev
session launched, so its extension tools work today.
:::
## Capability map
Every chrome-devtools-mcp extension verb has an extension.dev counterpart, plus
a build platform around it.
| chrome-devtools-mcp | extension.dev | Notes |
| -------------------------- | --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------ |
| `install_extension` | `extension_dev`, `extension_start` | Loading is tied to a real build and hot reload, not a static folder |
| `list_extensions` | `extension_list_extensions` | Lists extensions with a live context, read-only through the Extensions domain, Chromium only |
| `reload_extension` | `extension_reload` | Rebuild-aware, reloads the background or a tab |
| `trigger_extension_action` | `extension_open` (`surface: "action"`) | Portable replay of the `onClicked` handler, cross-browser, **no gesture**. For a genuine click that grants `activeTab`, use the left |
| none | `extension_open` (`surface: "command"`) | Replays a `chrome.commands.onCommand` keyboard shortcut handler, no chrome-devtools-mcp equivalent |
| `uninstall_extension` | managed by the dev session lifecycle | The dev session owns load and unload through `dev` and `preview` |
| service worker logs | `extension_logs` | One ordered timeline across every context, with `follow` |
### Action and command triggers, and their one caveat
chrome-devtools-mcp triggers an action by clicking the real toolbar button,
which needs a visible window and a genuine user gesture. Extension.js takes a
different approach: it captures the extension's `chrome.action.onClicked` and
`chrome.commands.onCommand` listeners at build time and **replays** them on
demand. That makes triggering scriptable and reproducible, the natural mode for
agentic testing, and because it only touches `addListener` it works on both
Chromium and Firefox. A single `background.service_worker` source works on
Firefox too: the framework translates it to a `background.scripts` event page
for the Firefox build.
::: warning No gesture on replay
Replay invokes the handler **without a user gesture**. A real toolbar click
grants temporary `activeTab` permission and satisfies gesture-gated APIs
(`chrome.permissions.request`, interactive `identity.getAuthToken`); a replay
does not. A handler that relies on `activeTab` (for example
`chrome.scripting.executeScript` on the active tab, or `captureVisibleTab`)
behaves differently than it does on a real click. The result includes
`gesture: false`, and a `warning` when the manifest declares `activeTab`.
When you need click fidelity, use chrome-devtools-mcp's
`trigger_extension_action`.
:::
### When you need a genuine gesture
- **`extension_open` (`surface: "action"`)**: the everyday path. Replays the
handler, cross-browser, headless, no gesture (`activeTab` not granted). Use
it for most handler testing.
- **chrome-devtools-mcp `trigger_extension_action`**: a real action invocation
through Puppeteer over the pipe transport, so `activeTab` is granted exactly
like a user click. Chromium only. Reach for it only when the handler depends
on the gesture.
### Safe by construction
The replay wraps are injected only into the dev build. The agent bridge is
gated on a control port that exists solely during `extension dev` and
`extension preview`, and nothing is added to a production build. The
`addListener` wraps delegate transparently to the originals, so the extension
behaves identically with or without the bridge. The framework side of this is
documented at
[extension.js.org/docs/debugging/trigger-actions-and-commands](https://extension.js.org/docs/debugging/trigger-actions-and-commands).
## Next
- [MCP server](/tools/mcp) is the tool reference
- [Agent skill](/tools/skill) teaches the agent which server to reach for
- [Inspect and act](/tools/inspect-and-act) covers the observe and act gates
- [Agent bridge](/tools/agent-bridge) is the local channel both the CLI and the server use
---
- [CLI](https://docs.extension.dev/tools/cli): The framework CLI, where it touches the platform, and the agent-facing package.
# CLI
Two packages, two jobs.
| Package | Job |
| --- | --- |
| `extension` | Create, run, debug and build an extension on your machine |
| `@extension.dev/mcp` | Give a coding agent the platform verbs ([MCP server](/tools/mcp)) |
## extension
The Extension.js framework CLI. It has no login and never needs an account.
Full documentation lives at [extension.js.org](https://extension.js.org).
```bash
npx extension@latest create my-extension --template=init
cd my-extension
npm install
npm run dev
```
The command surface is `create`, `dev`, `start`, `preview`, `build`, `logs`,
`doctor`, `capabilities`, `install`, `uninstall`, `eval`, `storage`, `reload`,
`inspect`, `navigate`, `open`, `publish`, and `telemetry`. Each is documented
in the
framework's
[commands reference](https://extension.js.org/docs/commands/create); this site
does not restate them. Only `publish` touches the platform.
A build often needs values that stay out of the source. To see how a build
reads environment values, start from the
[Content Env template](https://templates.extension.dev/content-env).
## Where it touches the platform
One place, explicit: `extension publish` asks the platform for the project's
build page URL. It uploads nothing: a public project answers with its public
page, a private one with the same address plus a `?share=` token that lives 1
to 168 hours (`--ttl`, default 24). The revocable preview link, which runs on
a much longer clock set by your plan, is a different verb, an upload, and it
is documented at
[Share an unpublished build for review](/share/unpublished-build-for-review).
`publish` reads its token from three places, in this order:
1. The `--token` flag.
2. `EXTENSION_DEV_TOKEN` in the environment.
3. The login stored by `npx @extension.dev/mcp login`, kept in
`~/.config/extension-dev/auth.json` (under `$XDG_CONFIG_HOME` if you set
one, `%APPDATA%\extension-dev\auth.json` on Windows).
The third path is why the command can succeed in a terminal where you set
nothing, and it is worth knowing before you trust the URL.
`publish` also needs the platform address. The CLI ships no default host and
prints none: pass `--api ` or set `EXTENSION_DEV_API_URL`, or the command
stops with "Publishing needs a platform URL" before it sends anything.
**The token decides which project answers, and the directory does not.** A
token is scoped to one project, and `publish` tells the platform nothing about
where you ran it. The CLI guards the stored login: when the folder you stand
in does not carry the name of the stored login's project, `publish` refuses
and names both projects, and `--project ` publishes the stored project
on purpose. A token you pass with `--token` or `EXTENSION_DEV_TOKEN` gets no
guard, so read the project slug in the URL before you send it to anyone. To
answer for a different project, pass that project's `--token`, or move the
stored login with
`npx @extension.dev/mcp login --project /`. See
[Publish from the CLI](/tools/publish).
Everything else runs on your machine and sends nothing to the platform.
## The platform from an agent
`@extension.dev/mcp` serves the [MCP tools](/tools/mcp) to a coding agent and
ships the `extension-mcp` headless commands. Store submission and release
promotion are platform actions: run them from the console's **Submissions**
tab, from an agent with `extension_submit` and `extension_release_promote`,
or over the [HTTP API](/automate/api).
## When it fails
- A command asks for a token: only `publish` does. Every other `extension`
command runs without an account.
- The URL names a project you did not expect: `publish` answered for the
project your token is scoped to, which can happen with a token you passed
by flag or environment. Pass the right `--token`, or run
`npx @extension.dev/mcp login --project /` and publish
again.
- `publish` refuses with "Publishing here would share X, not Y": the stored
login is scoped to another project. Run it inside that project, pass
`--project ` on purpose, or pass `--token` for this one.
- `eval`, `storage`, `reload`, `inspect`, `navigate`, `open` refuse: they
act on a live dev session and are gated. The gate is a flag on the session,
not on the verb: start `extension dev --allow-control` (or `--allow-eval`
for `eval`, which implies control), then retry.
- `publish` says "Publishing needs a platform URL": the CLI has no built-in
host. Pass `--api ` or set `EXTENSION_DEV_API_URL`.
- You are looking for a submit command: store submission is a platform
action, not a framework command. Use the console's **Submissions** tab, an
agent's `extension_submit`, or `POST /api/cli/stores/submit`. See
[Publishing](/publish/overview).
## Next
- [Publish from the CLI](/tools/publish) links a project's build page
- [Publishing](/publish/overview) for the store path
- [Access tokens](/tools/access-tokens) to authenticate from CI
---
- [Agent bridge](https://docs.extension.dev/tools/agent-bridge): The local channel that lets a human, a script or an AI agent observe and act on a running extension.
# Agent bridge
The agent bridge is an Extension.js framework feature. When `extension dev`
runs, the framework loads a privileged companion alongside your extension
and opens a channel on `127.0.0.1` that a human (the terminal), a script
(a file), or an AI agent (a live stream) uses to observe every extension
context and, when you opt in per session, act on it. It is local and open
source, needs no account, and sends nothing off your machine. The data
plane, the trust levels, and the honest limits live in the framework's
[Debugging section](https://extension.js.org/docs/debugging).
## What the platform adds
The [MCP server](/tools/mcp) reaches the same machinery from an agent. The
tools `extension_logs`, `extension_storage`, `extension_reload`,
`extension_open` and `extension_dom_snapshot` connect to the bridge's
channel exactly as any other client would, so the agent and the CLI see the
same thing. `extension_eval` rides the bridge where the extension's content
security policy allows it and the browser's debugger protocol where it does
not, and `extension_inspect` reads over that debugger protocol on Chromium
and over the bridge on Firefox. The platform stores none of it: bridge data
stays on the developer's machine.
## Next
- [Debugging](https://extension.js.org/docs/debugging) for the full story
- [Agent logs](/tools/logs) and [Inspect and act](/tools/inspect-and-act)
- [MCP server](/tools/mcp) wires the bridge into an agent
---
- [Agent logs](https://docs.extension.dev/tools/logs): One ordered stream of everything the extension does, across every context.
# Agent logs
Agent logs are an Extension.js framework feature. While `extension dev`
runs, the [agent bridge](/tools/agent-bridge) merges every context (service
worker, content scripts, popup, options, sidebar) into one ordered stream,
written to `dist/extension-js//logs.ndjson` and readable with
`extension logs`. It is local and needs no account. The line format and the
flags live in the framework's
[Debugging section](https://extension.js.org/docs/debugging) and the `logs`
reference it links. Rotation is not documented there yet: `extension logs`
announces a rotated file on stderr and names the sibling it moved to.
## What the platform adds
The [MCP server](/tools/mcp)'s `extension_logs` tool reads the same stream
from an agent, bounded by default so it does not flood the agent's context.
There is no platform log retention: logs stay on the developer's machine and
are never uploaded.
## Next
- [Debugging](https://extension.js.org/docs/debugging) for the full story
- [Inspect and act](/tools/inspect-and-act) reads the DOM and calls into the extension
- [MCP server](/tools/mcp) wires the stream into an agent
---
- [Inspect and act](https://docs.extension.dev/tools/inspect-and-act): Read the live DOM of any surface and run scoped operations in the exact context you care about.
# Inspect and act
Inspect and act is an Extension.js framework feature. Through the
[agent bridge](/tools/agent-bridge), `extension inspect` reads the live,
post injection DOM of any surface, and `extension eval`, `storage`, `reload`
and `open` run scoped operations inside the extension's own context, on
Chrome and Firefox. Acting is gated per session with `--allow-control` and
`--allow-eval`. All of it is local and needs no account. The addressing
model, the safety gates, and every flag live in the framework's
[Debugging section](https://extension.js.org/docs/debugging).
## What the platform adds
The [MCP server](/tools/mcp) reaches the same machinery from an agent:
`extension_dom_snapshot` reads over the bridge, `extension_inspect` reads
over the browser's debugger protocol on Chromium and over the bridge on
Firefox, and `extension_eval`, `extension_storage`, `extension_reload` and
`extension_open` act, under the same session gates, which `extension_dev`
names `allowControl` and `allowEval`. Nothing the agent reads
or touches here leaves the developer's machine.
## Next
- [Debugging](https://extension.js.org/docs/debugging) for the full story
- [Agent logs](/tools/logs) streams what act operations produce
- [MCP server](/tools/mcp) wires these into an agent
---
- ["extension_add_feature"](https://docs.extension.dev/tools/mcp/add-feature): "Plan a new feature surface for an existing extension."
# `extension_add_feature`
Plan a new feature surface for an existing extension. This returns step-by-step instructions, the manifest additions to make, and reference templates from the extension.dev catalog. It modifies no files: apply the returned plan yourself.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | Create and build |
| Needs | none |
| Read only | yes |
| Destructive | no |
| Idempotent | yes |
| Reaches the open web | no |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `projectPath` | string | yes | | Extension project root |
| `feature` | "sidebar" \| "popup" \| "options" \| "content-script" \| "background" \| "newtab" \| "devtools" | yes | | Feature surface to add |
| `framework` | "react" \| "vue" \| "svelte" \| "preact" \| "vanilla" | no | `"react"` | |
## Ask the agent
> Add an options page to this extension
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_build`](/tools/mcp/build): Build a browser extension for production.
- [`extension_create`](/tools/mcp/create): Create a browser extension project from a template in the extension.dev catalog.
- [`extension_docs_search`](/tools/mcp/docs-search): Find the docs page to cite before answering any question about a WebExtension API, a manifest key, a permission, or a difference between Chrome, Firefox, Edge and Safari.
- [`extension_templates`](/tools/mcp/templates): Browse the extension.dev template catalog.
---
- ["extension_analyze"](https://docs.extension.dev/tools/mcp/analyze): "Analyze a BUILT extension on disk: file sizes, declared entry points, permissions, bundle composition, and store-readiness checks."
# `extension_analyze`
Analyze a BUILT extension on disk: file sizes, declared entry points, permissions, bundle composition, and store-readiness checks. This is static only: it reads dist/<browser> from the filesystem and never touches a browser, so build first with extension_build. Use extension_inspect for a running extension's live DOM and console.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | See |
| Needs | none |
| Read only | yes |
| Destructive | no |
| Idempotent | yes |
| Reaches the open web | no |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `projectPath` | string | yes | | Extension project root |
| `browser` | string | no | `"chrome"` | Browser build to analyze |
| `format` | "summary" \| "tree" \| "json" | no | `"summary"` | |
## Ask the agent
> What is in the built extension and is anything too big for the store?
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_doctor`](/tools/mcp/doctor): Diagnose a dev session end to end: ready contract, dev-server process, control-port agreement, control channel, eval token, executor, browser liveness.
- [`extension_dom_snapshot`](/tools/mcp/dom-snapshot): Take a shallow structured DOM snapshot of one chosen surface through the agent bridge (localhost only; the snapshot itself needs no CDP, but listTargets and `tabUrl` resolution...
- [`extension_inspect`](/tools/mcp/inspect): Inspect a running extension deeply over the browser's debugger protocol: full HTML (open shadow roots of #extension-root and [data-extension-root] hosts inlined; other shadow ro...
- [`extension_list_extensions`](/tools/mcp/list-extensions): List the extensions in the running dev browser: id, name, version, and, on Chromium, live contexts.
- [`extension_logs`](/tools/mcp/logs): Read or stream logs from every context of a running dev session (service worker, content scripts, popup, options, sidebar, devtools, pages) in one ordered timeline.
- [`extension_manifest_validate`](/tools/mcp/manifest-validate): Validate a manifest.json across browsers.
- [`extension_theme_verify`](/tools/mcp/theme-verify): Verify a Chrome theme manifest before it ships.
---
- ["extension_assert"](https://docs.extension.dev/tools/mcp/assert): "Run a test stage against a live dev session: state expectations and read one verdict for each, instead of reading a blob and hand-rolling the judgement."
# `extension_assert`
Run a test stage against a live dev session: state expectations and read one verdict for each, instead of reading a blob and hand-rolling the judgement. Every expectation comes back pass, fail or inconclusive, where inconclusive means this platform cannot cover the question today and the verdict says what would settle it. An inconclusive check is never a pass. Start the session with extension_dev; use extension_inspect or extension_logs when you want the raw reading instead of a verdict.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | Test |
| Needs | a live dev or start session |
| Read only | no |
| Destructive | no |
| Idempotent | yes |
| Reaches the open web | no |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `projectPath` | string | yes | | Extension project root (needs a live dev session) |
| `expect` | array of object | yes | | One object per expectation, each { assert: <check id>, ...args }. background-worker-booted: no args. surface-rendered: surface (popup, options, sidebar, newtab, history, bookmarks), optional selector and minNodes. content-script-injected: url. storage-key-present: key, optional area (default local), equals, context. console-errors-empty: optional context (array), since (seq cursor), ignore (substrings). |
| `browser` | string | no | | Session browser; defaults to this project's live session |
| `timeout` | number | no | | Command timeout in ms. When omitted, the default depends on the route: 30000 through the dev session's control channel, 10000 over the Firefox debugger protocol, and 15000 per command over the Chromium debug port. |
## Ask the agent
> Check that the worker booted, the popup rendered and the console is clean
## The answer can carry page text
Part of this tool's answer is text a web page or the extension wrote. The server fences it between `` and `` with a fresh random `ID` per call, named in `untrusted.boundary`. A fence is a signal to the model, not a gate: pair it with `--no-ship` when the agent reads pages you do not trust. See [Server flags and telemetry](/tools/mcp/flags).
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
---
- ["extension_auth"](https://docs.extension.dev/tools/mcp/auth): "Sign this machine in to extension.dev, report that login, or clear it."
# `extension_auth`
Sign this machine in to extension.dev, report that login, or clear it. Pass action:'status' (the default) to name the workspace and project the stored token is scoped to and when it expires, never the token itself; that identity comes from the stored token alone, and does not change with the current working directory or whichever project folder you are in. Status also asks the platform's whoami endpoint whether that credential actually resolves there: the answer rides value.server.verdict as confirmed, refused or unavailable (when the server could not be reached), with status logged-in for the first and last and refused-by-server for the second, so a local file claiming a login the server refused is never reported as logged in. Pass action:'login' for a two-phase flow: call with `project` to get a code plus a URL the user authorizes at extension.dev/device, then call again with the returned `deviceCode`. GitHub federation happens server-side, so no GitHub token lands on this machine. Minted tokens live at most 7 days, server-enforced, so CI must re-mint before expiry on the console's project settings, Access tokens page. Pass action:'logout' to delete the local credentials only (with `project`, just that project's login); the token stays valid server-side until it is revoked at the URL the response returns. Several logins live side by side on one machine, one per workspace/project, and status lists them all under `logins`. To sign in to several existing projects of one workspace at once, pass `projects` instead of `project`: one approval, one stored token per project.
| | |
| --- | --- |
| Group | platform, on with `--features=local,platform` (private alpha) |
| Tier | Platform |
| Needs | the server started with --features=local,platform |
| Read only | no |
| Destructive | yes |
| Idempotent | no |
| Reaches the open web | yes |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `action` | "status" \| "login" \| "logout" | no | `"status"` | |
| `project` | string | no | | login: target project as '<workspace>/<project>'; the token is scoped to it. The slug pair is the console address bar: an existing project's page is console.extension.dev/<workspace>/<project>. Create one at extension.dev/new if none exists yet. Logins to several projects are all kept, the latest is the default; token-scoped tools take `project` to pick another. logout: remove only this project's login (omitted, every stored login goes). |
| `projects` | array of string | no | | login: sign in to several existing projects of one workspace with one approval, instead of one approval each. 1 to 20 names as '<workspace>/<project>', all in the same workspace, each by its exact slug (lowercase letters and digits joined by single dashes, at most 48 characters), none twice. Pass it instead of `project`, never beside it. The approval page lists every name; one missing project refuses the whole list and mints nothing. Resume with the returned `deviceCode` and the same list. Every token is stored as that project's own login and all expire within 7 days, so the same call renews them together. |
| `deviceCode` | string | no | | login: resume token from the prior call's `deviceCode`; omit on the first call. |
| `api` | string | no | | Platform base URL (default EXTENSION_DEV_API_URL, else https://www.extension.dev) |
## Ask the agent
> Log this machine in to the acme/newtab project on extension.dev
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_preview_web`](/tools/mcp/preview-web): Preview an in-progress extension in the web emulator, with no real browser.
- [`extension_project_create`](/tools/mcp/project-create): Create an extension.dev project for an extension that does not have one yet, without opening the console.
- [`extension_publish`](/tools/mcp/publish): Publish the project your stored token is scoped to (extension_auth, or EXTENSION_DEV_TOKEN) to extension.dev, and return its shareable URL.
- [`extension_release_promote`](/tools/mcp/release-promote): Promote a built extension to a release channel (stable, preview, beta, …) on extension.dev, headless.
- [`extension_release_status`](/tools/mcp/release-status): Read where a project stands on extension.dev, from the public registry (registry.extension.land).
- [`extension_shares`](/tools/mcp/shares): List and revoke the public preview links this token has shared, which is what extension_preview_web share:true hands out.
- [`extension_submit`](/tools/mcp/submit): Submit a built extension for store REVIEW through extension.dev, which holds your store credentials and dispatches from your project's mirror CI: the Chrome Web Store, Firefox A...
- [`extension_workspace_create`](/tools/mcp/workspace-create): Create an extension.dev workspace that does not exist yet, without opening the console.
---
- ["extension_browsers"](https://docs.extension.dev/tools/mcp/browsers): "Find, install and remove the browsers Extension.js tooling can launch."
# `extension_browsers`
Find, install and remove the browsers Extension.js tooling can launch. Pass action:'detect' (the default) to scan both system-installed and managed browsers, and report each one's binary path, version, engine and debugger support. Pass action:'list' for the managed cache this tool downloads into, with sizes on disk. Pass action:'install' to download a managed binary: several hundred MB in one blocking call, so allow a generous client timeout. Pass action:'uninstall' to remove managed binaries; it never touches a system install.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | Browsers |
| Needs | none |
| Read only | no |
| Destructive | yes |
| Idempotent | no |
| Reaches the open web | yes |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `action` | "detect" \| "list" \| "install" \| "uninstall" | no | `"detect"` | |
| `browsers` | array of "chrome" \| "chromium" \| "edge" \| "brave" \| "opera" \| "vivaldi" \| "yandex" \| "firefox" \| "waterfox" \| "librewolf" \| "zen" \| "floorp" \| "safari" | no | | detect: limit the scan to these. Omit to check all. |
| `browser` | "chrome" \| "chromium" \| "edge" \| "firefox" | no | | install/uninstall: which managed binary. Required for install. |
| `all` | boolean | no | `false` | uninstall: remove every managed binary. |
## Ask the agent
> Which browsers are installed here? Install Firefox if it is missing
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
---
- ["extension_build"](https://docs.extension.dev/tools/mcp/build): "Build a browser extension for production."
# `extension_build`
Build a browser extension for production. This tool always passes --browser (chrome unless you set it), so the output lands in dist/<browser>/ (dist/chrome by default) and the answer's outputPath always names the folder that was written (the engine's reported path, else dist/<browser> under the package root, the same folder this tool checked for the manifest), with zipPath beside it when a zip was asked for: read those paths instead of assuming one. To know what is in the build (sizes, entry points, the checks a store reviewer runs) call extension_analyze on the same projectPath and browser instead of listing or reading the output folder by hand; the answer's hint says so. A scaffolded project's own `npm run build` runs `extension build` with no --browser, which the engine defaults to chromium, so that script writes dist/chromium/ instead. Pass zip:true to also package a .zip for store submission. With browser:'safari' the build converts the extension into a macOS app through Xcode, and bundleId sets the identifier it ships under. The build refuses a manifest with build-blocking errors unless you pass skipValidation:true, because such a manifest yields a broken bundle the bundler itself never flags.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | Create and build |
| Needs | none |
| Read only | no |
| Destructive | no |
| Idempotent | yes |
| Reaches the open web | no |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `projectPath` | string | yes | | Extension project root |
| `browser` | "chrome" \| "chromium" \| "edge" \| "brave" \| "opera" \| "vivaldi" \| "yandex" \| "firefox" \| "waterfox" \| "librewolf" \| "zen" \| "floorp" \| "safari" \| "chromium-based" \| "gecko-based" \| "firefox-based" \| "webkit-based" | no | `"chrome"` | |
| `zip` | boolean | no | `false` | Create a .zip file for store distribution |
| `zipSource` | boolean | no | `false` | Include source code zip (required by some stores) |
| `zipFilename` | string | no | | Custom .zip file name (defaults to name and version) |
| `polyfill` | boolean | no | `false` | Apply cross-browser polyfill |
| `silent` | boolean | no | `false` | Suppress build output |
| `mode` | "development" \| "production" \| "none" | no | `"production"` | Bundler mode override (also sets NODE_ENV) |
| `skipValidation` | boolean | no | `false` | Build even when extension_manifest_validate reports build-blocking errors. The build normally refuses: the engine itself stops only on missing scripts, icons, DNR rule files, default_locale and manifest_version, and ships a bundle over the rest. |
| `appName` | string | no | | Safari targets only: name of the generated macOS app, which also names the Xcode scheme and the .app on disk. Defaults to the manifest name. |
| `bundleId` | string | no | | Safari targets only: a reverse-DNS bundle identifier you own, such as com.acme.readinglist. Without one the app is packaged under a generated dev.extensionjs.* identifier derived from the app name, which two projects with the same name share, and the first team to register it takes it. |
| `macOsOnly` | boolean | no | `true` | Safari targets only: generate a macOS-only Xcode project. Pass false for a universal project that also targets iOS and iPadOS, which is what you want if the extension ships on iPhone or iPad. |
| `forceRegenerate` | boolean | no | `false` | Safari targets only: regenerate the Xcode project even when the engine considers it up to date. Use it when an earlier packaging run left the project broken. |
## Ask the agent
> Build this extension for Chrome and Firefox
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_add_feature`](/tools/mcp/add-feature): Plan a new feature surface for an existing extension.
- [`extension_create`](/tools/mcp/create): Create a browser extension project from a template in the extension.dev catalog.
- [`extension_docs_search`](/tools/mcp/docs-search): Find the docs page to cite before answering any question about a WebExtension API, a manifest key, a permission, or a difference between Chrome, Firefox, Edge and Safari.
- [`extension_templates`](/tools/mcp/templates): Browse the extension.dev template catalog.
---
- ["extension_create"](https://docs.extension.dev/tools/mcp/create): "Create a browser extension project from a template in the extension.dev catalog."
# `extension_create`
Create a browser extension project from a template in the extension.dev catalog. Call extension_templates first to see what is available. The scaffolder may initialize a git repository in the new project (with a first commit), and it also writes store metadata and a .gitignore of its own. Read the result's defaultsApplied block for the decisions this tool can read back: parent directory, template, package manager, target browser and whether a git repository was initialized by this call. The result's `scripts` lists every package.json script that runs the engine, with the browser it targets and the folder it writes: the scaffolder passes no --browser to `dev`, `start` and `build`, so the engine runs them as chromium and `npm run build` writes dist/chromium, while `build:chrome` writes dist/chrome; read the folder from that list instead of assuming one. A scaffold that answers `created` is verified (its manifest was found and read): it needs no build to prove it, so do not run `npm run build` to check it; the next step is extension_dev with the answered projectPath, which runs it in a browser, as the answer's `nextSteps` says. After a successful scaffold this tool sends one telemetry event, draft_seeded (template slug, source and commit, a random install id, never a path or a name), to PostHog; the Telemetry section of this package's readme names the two environment variables that turn it off.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | Create and build |
| Needs | none |
| Read only | no |
| Destructive | no |
| Idempotent | no |
| Reaches the open web | yes |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `projectName` | string | yes | | Name of the extension project (used as directory name). Alias: name. |
| `parentDir` | string | no | | Directory to create the project inside. Defaults to the MCP server process cwd, NOT the caller's cwd, so pass it whenever you care where the project lands. Aliases: parent, into. |
| `template` | string | no | `"typescript"` | Template slug from the extension.dev catalog (e.g. 'react', 'ai-claude', 'content-vue'). extension_templates discovers them. |
| `install` | boolean | no | `true` | Install dependencies after creation |
## Ask the agent
> Create a new tab extension with React in ./my-newtab
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_add_feature`](/tools/mcp/add-feature): Plan a new feature surface for an existing extension.
- [`extension_build`](/tools/mcp/build): Build a browser extension for production.
- [`extension_docs_search`](/tools/mcp/docs-search): Find the docs page to cite before answering any question about a WebExtension API, a manifest key, a permission, or a difference between Chrome, Firefox, Edge and Safari.
- [`extension_templates`](/tools/mcp/templates): Browse the extension.dev template catalog.
---
- ["extension_dev"](https://docs.extension.dev/tools/mcp/dev): "Run the extension while you edit it: dev build, hot module replacement, and a browser with the extension loaded."
# `extension_dev`
Run the extension while you edit it: dev build, hot module replacement, and a browser with the extension loaded. Reach for this first when the ask is "run my extension": by default it waits for the session to be ready and answers with the readiness and the first log lines, so one call covers the ask; pass wait:false to return as soon as the server is spawned. A dev session carries the control channel that extension_storage, extension_reload, extension_open and extension_dom_snapshot need (local, same machine, same user) unless you pass allowControl:false; the eval channel that extension_eval needs is the one gate that stays off until you pass allowEval:true (which implies control, so you never need to pass both). Use extension_start instead to run the production build in a browser. When the answer's ready.status is "ready" the session is up and nothing else needs calling before extension_logs, extension_inspect or the control verbs; extension_wait is only for wait:false, an answer whose ready.status was not ready, or a build-only session.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | Run |
| Needs | none |
| Read only | no |
| Destructive | no |
| Idempotent | no |
| Reaches the open web | no |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `projectPath` | string | yes | | Extension project root |
| `browser` | "chrome" \| "chromium" \| "edge" \| "brave" \| "opera" \| "vivaldi" \| "yandex" \| "firefox" \| "waterfox" \| "librewolf" \| "zen" \| "floorp" \| "safari" \| "chromium-based" \| "gecko-based" \| "firefox-based" \| "webkit-based" | no | `"chrome"` | |
| `port` | number | no | | Dev server port (0 for auto-assign) |
| `noBrowser` | boolean | no | `false` | Start the dev server without launching a browser |
| `polyfill` | boolean | no | `true` | Apply cross-browser polyfill |
| `profile` | string | no | | Profile path, or "false" to reuse the real user profile. Omit for a throwaway one. |
| `startingUrl` | string | no | | URL the browser opens on launch |
| `chromiumBinary` | string | no | | Custom Chromium-based binary to launch; `browser` still names the target family the engine builds for (chromium-based when none is given). |
| `geckoBinary` | string | no | | Custom Gecko/Firefox binary to launch; `browser` still names the target family the engine builds for (gecko-based when none is given). |
| `host` | string | no | | Bind host, default 127.0.0.1. Use 0.0.0.0 in Docker or devcontainers. |
| `publicHost` | string | no | | Host the browser dials for HMR and reload when it differs from the bind host |
| `extensions` | array of string | no | | Extra extension paths or store URLs to load alongside the project |
| `replace` | boolean | no | `false` | Stop the live session for this projectPath first, reported as replacedSession. Without it a second call is refused rather than forking: two sessions fight over one profile and the newer browser dies on the lock. |
| `allowControl` | boolean | no | `true` | The agent-bridge control channel that extension_storage/reload/open/dom_snapshot ride (local, same machine). On unless you pass false; eval is the gate that stays off (allowEval). |
| `wait` | boolean | no | `true` | Wait for the session to be ready before answering (the same contract extension_wait reads, up to 45s) and include the first log lines. false returns as soon as the server is spawned; call extension_wait and extension_logs yourself then. |
| `allowEval` | boolean | no | `false` | Enable extension_eval (runs code in a context; writes a 0600 session token). Implies allowControl, so you never need to pass both. |
## Ask the agent
> Run my extension in Chrome and let me act on it
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_start`](/tools/mcp/start): Run the PRODUCTION build in a browser: build the project, serve it, and launch.
- [`extension_stop`](/tools/mcp/stop): Stop a session that extension_dev or extension_start is running: terminate the server and the browser it launched, and remove the live-preview carrier if extension_dev placed one.
- [`extension_wait`](/tools/mcp/wait): Wait for a running dev or start session to be ready.
---
- ["extension_docs_search"](https://docs.extension.dev/tools/mcp/docs-search): "Find the docs page to cite before answering any question about a WebExtension API, a manifest key, a permission, or a difference between Chrome, Firefox, Edge and Safari."
# `extension_docs_search`
Find the docs page to cite before answering any question about a WebExtension API, a manifest key, a permission, or a difference between Chrome, Firefox, Edge and Safari. It searches the Extension.js and extension.dev docs (free, no login) and answers with the pages, each with its URL and an excerpt. Memory is the fallback only when it returns nothing.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | Create and build |
| Needs | none |
| Read only | yes |
| Destructive | no |
| Idempotent | yes |
| Reaches the open web | yes |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `query` | string | yes | | What to look up, in a few words. |
| `limit` | number | no | `5` | How many pages to return, 1 to 8. |
| `api` | string | no | | Platform base URL (default EXTENSION_DEV_API_URL, else https://www.extension.dev) |
## Ask the agent
> How does Firefox handle the sidebar API? Search the docs before you answer
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_add_feature`](/tools/mcp/add-feature): Plan a new feature surface for an existing extension.
- [`extension_build`](/tools/mcp/build): Build a browser extension for production.
- [`extension_create`](/tools/mcp/create): Create a browser extension project from a template in the extension.dev catalog.
- [`extension_templates`](/tools/mcp/templates): Browse the extension.dev template catalog.
---
- ["extension_doctor"](https://docs.extension.dev/tools/mcp/doctor): "Diagnose a dev session end to end: ready contract, dev-server process, control-port agreement, control channel, eval token, executor, browser liveness."
# `extension_doctor`
Diagnose a dev session end to end: ready contract, dev-server process, control-port agreement, control channel, eval token, executor, browser liveness. This returns one {check, status, detail, remediation?} per leg, in dependency order. Read a 'skip' as blocked, not as a pass: it names the check that blocked it. A session started with allowControl: false comes back ok:true with status 'read-only', not as an error: its control channel is off by choice. Run this first when any act tool (storage, reload, eval, open) errors unexpectedly. Call it with no projectPath for a pre-flight environment check (node, the Extension.js CLI, the template cache) before any project exists.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | See |
| Needs | a live dev or start session |
| Read only | yes |
| Destructive | no |
| Idempotent | yes |
| Reaches the open web | no |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `projectPath` | string | no | | Path to the extension project root. Omit for a pre-flight environment check with no project. |
| `browser` | string | no | | Browser session to diagnose. Defaults to the active dev session's browser for this project. |
## Ask the agent
> The session looks stuck. Diagnose it
## The answer can carry page text
Part of this tool's answer is text a web page or the extension wrote. The server fences it between `` and `` with a fresh random `ID` per call, named in `untrusted.boundary`. A fence is a signal to the model, not a gate: pair it with `--no-ship` when the agent reads pages you do not trust. See [Server flags and telemetry](/tools/mcp/flags).
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_analyze`](/tools/mcp/analyze): Analyze a BUILT extension on disk: file sizes, declared entry points, permissions, bundle composition, and store-readiness checks.
- [`extension_dom_snapshot`](/tools/mcp/dom-snapshot): Take a shallow structured DOM snapshot of one chosen surface through the agent bridge (localhost only; the snapshot itself needs no CDP, but listTargets and `tabUrl` resolution...
- [`extension_inspect`](/tools/mcp/inspect): Inspect a running extension deeply over the browser's debugger protocol: full HTML (open shadow roots of #extension-root and [data-extension-root] hosts inlined; other shadow ro...
- [`extension_list_extensions`](/tools/mcp/list-extensions): List the extensions in the running dev browser: id, name, version, and, on Chromium, live contexts.
- [`extension_logs`](/tools/mcp/logs): Read or stream logs from every context of a running dev session (service worker, content scripts, popup, options, sidebar, devtools, pages) in one ordered timeline.
- [`extension_manifest_validate`](/tools/mcp/manifest-validate): Validate a manifest.json across browsers.
- [`extension_theme_verify`](/tools/mcp/theme-verify): Verify a Chrome theme manifest before it ships.
---
- ["extension_dom_snapshot"](https://docs.extension.dev/tools/mcp/dom-snapshot): "Take a shallow structured DOM snapshot of one chosen surface through the agent bridge (localhost only; the snapshot itself needs no CDP, but listTargets and `tabUrl` resolution..."
# `extension_dom_snapshot`
Take a shallow structured DOM snapshot of one chosen surface through the agent bridge (localhost only; the snapshot itself needs no CDP, but listTargets and `tabUrl` resolution ask the browser directly and need the session's debug port: CDP page targets on Chromium, RDP tab descriptors on Firefox): element counts, extension roots, open shadow roots, optional byte-capped HTML, and optional recent console lines. This is the SURFACE PICKER: the only tool that reads an open extension surface by name (`context`: popup, options, sidebar, devtools) or an override page, the only one that takes a numeric chrome.tabs id, and the only one that enumerates what is open (listTargets for CDP targetIds and RDP tab actors, listTabs for numeric tab ids). An ambiguous `tabUrl` returns the candidates instead of guessing. It does not pierce closed shadow roots, run selector probes, or navigate: use extension_inspect for those, and for a deep read of an already-open web page. Rides the dev session's control channel, on unless extension_dev was called with allowControl:false.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | See |
| Needs | a dev session, which carries control unless started with allowControl: false |
| Read only | yes |
| Destructive | no |
| Idempotent | yes |
| Reaches the open web | no |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `projectPath` | string | yes | | Extension project root (needs a live dev session) |
| `tab` | number | no | | Numeric chrome.tabs id, only to disambiguate when several tabs match. With neither `tab` nor `url`, content/page target the active tab. |
| `url` | string | no | | content/page: pick the tab by url (match pattern, then substring). Preferred over `tab`. |
| `tabUrl` | string | no | | Target the tab whose URL contains this substring (case-insensitive; titles checked only when no url matches). Resolved against the live browser first: exactly one match proceeds, zero or several return the candidates instead of a guess. Alternative to `url`. |
| `listTargets` | boolean | no | `false` | Enumerate live page targets and return, ignoring the other args. The discovery path for `tabUrl`. Chromium: {targetId,url,title,type}. Firefox: RDP tab descriptors {actor,url,title,type}. Neither id is a numeric chrome.tabs id; for those use listTabs. |
| `listTabs` | boolean | no | `false` | Enumerate open tabs as {tabId,url,title} and return, ignoring the other args. Use when you need a numeric tab id. |
| `context` | "content" \| "page" \| "popup" \| "options" \| "sidebar" \| "devtools" \| "newtab" \| "history" \| "bookmarks" | no | `"content"` | content/page targets `url`, else the active tab; the rest must already be OPEN |
| `include` | array of "summary" \| "html" | no | `["summary"]` | What to include; html is byte-capped |
| `maxBytes` | number | no | `262144` | |
| `withConsole` | number \| boolean | no | | Also include recent console lines. A number is how many; true means 50. |
| `browser` | string | no | | Session browser; defaults to this project's live session |
| `timeout` | number | no | | Command timeout in ms. When omitted, the default depends on the route: 30000 through the dev session's control channel, 10000 over the Firefox debugger protocol, and 15000 per command over the Chromium debug port. |
## Ask the agent
> Snapshot the popup and tell me what it rendered
## The answer can carry page text
Part of this tool's answer is text a web page or the extension wrote. The server fences it between `` and `` with a fresh random `ID` per call, named in `untrusted.boundary`. A fence is a signal to the model, not a gate: pair it with `--no-ship` when the agent reads pages you do not trust. See [Server flags and telemetry](/tools/mcp/flags).
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_analyze`](/tools/mcp/analyze): Analyze a BUILT extension on disk: file sizes, declared entry points, permissions, bundle composition, and store-readiness checks.
- [`extension_doctor`](/tools/mcp/doctor): Diagnose a dev session end to end: ready contract, dev-server process, control-port agreement, control channel, eval token, executor, browser liveness.
- [`extension_inspect`](/tools/mcp/inspect): Inspect a running extension deeply over the browser's debugger protocol: full HTML (open shadow roots of #extension-root and [data-extension-root] hosts inlined; other shadow ro...
- [`extension_list_extensions`](/tools/mcp/list-extensions): List the extensions in the running dev browser: id, name, version, and, on Chromium, live contexts.
- [`extension_logs`](/tools/mcp/logs): Read or stream logs from every context of a running dev session (service worker, content scripts, popup, options, sidebar, devtools, pages) in one ordered timeline.
- [`extension_manifest_validate`](/tools/mcp/manifest-validate): Validate a manifest.json across browsers.
- [`extension_theme_verify`](/tools/mcp/theme-verify): Verify a Chrome theme manifest before it ships.
---
- ["extension_eval"](https://docs.extension.dev/tools/mcp/eval): "Evaluate an expression in a running extension context."
# `extension_eval`
Evaluate an expression in a running extension context. Start the session with allowEval:true (extension_dev), which writes a 0600 session token; without that token every route of this tool, the debug port included, answers eval-disabled. Context defaults to 'background', except on a Chromium MV3 session (the default template) where it defaults to 'page', the active tab; pass context:'background' to evaluate in the service worker, which on Chromium goes over the debug port. Debug-port evaluates run with a user gesture, so gesture-gated APIs (permissions.request, sidePanel.open) can succeed here and still fail when the extension's own code calls them. For content and page, pass `url` to pick the tab, or omit both `url` and `tab` for the active tab; a numeric `tab` only disambiguates. Extension surfaces (popup, options, sidebar, devtools) and override pages (newtab, history, bookmarks) need no tab id but must already be open: open one with extension_open first, because a closed one returns an explicit error. On a Chromium MV3 session those pages, and context:'page' with a chrome-extension:// url, evaluate over CDP, the inspector path the extension page CSP does not govern; elsewhere they evaluate over the in-bundle relay. On Firefox a document whose content security policy forbids eval (the extension's own pages, or a site's) is evaluated over the debugger protocol instead, which takes one expression; a page inside the extension that is no declared surface (pages/*) is reached the same way by context:'page' and its moz-extension:// url once a tab shows it. Call extension_dom_snapshot with listTabs:true to enumerate {tabId, url, title}.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | Act |
| Needs | a session started with allowEval: true on extension_dev |
| Read only | no |
| Destructive | yes |
| Idempotent | no |
| Reaches the open web | yes |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `projectPath` | string | yes | | Extension project root (needs a live dev session) |
| `expression` | string | yes | | JavaScript expression to evaluate in the target context |
| `context` | "background" \| "popup" \| "options" \| "sidebar" \| "devtools" \| "newtab" \| "history" \| "bookmarks" \| "content" \| "page" | no | | Where to evaluate. Default background, except Chromium MV3 sessions default to page (the active tab). |
| `url` | string | no | | content/page: pick the tab by url (match pattern, then substring). Preferred over `tab`. |
| `tab` | number | no | | Numeric chrome.tabs id, only to disambiguate when several tabs match. |
| `browser` | string | no | | Session browser; defaults to this project's live session |
| `timeout` | number | no | | Command timeout in ms. When omitted, the default depends on the route: 30000 through the dev session's control channel, 10000 over the Firefox debugger protocol, and 15000 per command over the Chromium debug port. |
## Ask the agent
> Evaluate chrome.runtime.id in the background and in the popup
## The answer can carry page text
Part of this tool's answer is text a web page or the extension wrote. The server fences it between `` and `` with a fresh random `ID` per call, named in `untrusted.boundary`. A fence is a signal to the model, not a gate: pair it with `--no-ship` when the agent reads pages you do not trust. See [Server flags and telemetry](/tools/mcp/flags).
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_open`](/tools/mcp/open): Open an extension surface, or replay an event, in a running session.
- [`extension_reload`](/tools/mcp/reload): Reload a running extension's background context, or a tab.
- [`extension_storage`](/tools/mcp/storage): Read or write chrome.storage in a running extension.
---
- ["extension_inspect"](https://docs.extension.dev/tools/mcp/inspect): "Inspect a running extension deeply over the browser's debugger protocol: full HTML (open shadow roots of #extension-root and [data-extension-root] hosts inlined; other shadow ro..."
# `extension_inspect`
Inspect a running extension deeply over the browser's debugger protocol: full HTML (open shadow roots of #extension-root and [data-extension-root] hosts inlined; other shadow roots are not crossed by html, dom_snapshot or probe), DOM structure, content-script injection, console messages, and CSS selector queries through `probe`. This is the ONLY tool that pierces closed shadow roots (deepDom), runs selector probes, and navigates a tab to `url` before reading it. It reads a web or override page and picks the first inspectable target, or the first whose url contains `url`; it cannot address an extension surface by name and takes no chrome.tabs id. Use extension_dom_snapshot to choose which tab or which open surface (popup, options, sidebar, devtools) to read, or to enumerate what is open. Use extension_analyze for a built extension's files and sizes on disk. Chromium rides the Chrome DevTools Protocol and needs the session's debug port, not allowControl. Firefox is fully paired: summary, meta, html, dom_snapshot, extension_roots and probes ride the agent bridge and need allowEval:true, console rides the RDP watcher replay on engine 4.0.15 and later, and deepDom needs an MV2 session with host permissions for the target url, because the Firefox MV3 background CSP blocks bridge evals. This requires an active dev or start session.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | See |
| Needs | a live dev or start session |
| Read only | no |
| Destructive | no |
| Idempotent | yes |
| Reaches the open web | no |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `projectPath` | string | yes | | Extension project root (needs a live dev session) |
| `url` | string | no | | URL to inspect; the tab is navigated there first |
| `probe` | array of string | no | | CSS selectors to query; returns counts and samples for each |
| `include` | array of "html" \| "summary" \| "meta" \| "dom_snapshot" \| "console" \| "extension_roots" | no | `["summary","meta","console"]` | What to include |
| `browser` | string | no | | Session browser; defaults to this project's live session |
| `maxBytes` | number | no | `262144` | Truncate HTML output at this byte count (0 = unlimited) |
| `deepDom` | boolean | no | `false` | Pierce CLOSED shadow roots (open roots of the extension-root hosts are already inlined in html; other open roots are not read). Chromium: CDP DOM pierce. Firefox: a content-script walk via tabs.executeScript (MV2 only, needs host permissions for the target url); the answer says whether that context could see closed roots at all. |
## Ask the agent
> Read the DOM the content script injected on the cart page, shadow roots included
## The answer can carry page text
Part of this tool's answer is text a web page or the extension wrote. The server fences it between `` and `` with a fresh random `ID` per call, named in `untrusted.boundary`. A fence is a signal to the model, not a gate: pair it with `--no-ship` when the agent reads pages you do not trust. See [Server flags and telemetry](/tools/mcp/flags).
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_analyze`](/tools/mcp/analyze): Analyze a BUILT extension on disk: file sizes, declared entry points, permissions, bundle composition, and store-readiness checks.
- [`extension_doctor`](/tools/mcp/doctor): Diagnose a dev session end to end: ready contract, dev-server process, control-port agreement, control channel, eval token, executor, browser liveness.
- [`extension_dom_snapshot`](/tools/mcp/dom-snapshot): Take a shallow structured DOM snapshot of one chosen surface through the agent bridge (localhost only; the snapshot itself needs no CDP, but listTargets and `tabUrl` resolution...
- [`extension_list_extensions`](/tools/mcp/list-extensions): List the extensions in the running dev browser: id, name, version, and, on Chromium, live contexts.
- [`extension_logs`](/tools/mcp/logs): Read or stream logs from every context of a running dev session (service worker, content scripts, popup, options, sidebar, devtools, pages) in one ordered timeline.
- [`extension_manifest_validate`](/tools/mcp/manifest-validate): Validate a manifest.json across browsers.
- [`extension_theme_verify`](/tools/mcp/theme-verify): Verify a Chrome theme manifest before it ships.
---
- ["extension_list_extensions"](https://docs.extension.dev/tools/mcp/list-extensions): "List the extensions in the running dev browser: id, name, version, and, on Chromium, live contexts."
# `extension_list_extensions`
List the extensions in the running dev browser: id, name, version, and, on Chromium, live contexts. This session's own extension carries ownExtension:true, with name and version from the ready contract even when the browser exposes no identity. Chromium rides the Chrome DevTools Protocol, so an entry needs at least one live context, and a dormant MV3 service worker may be absent until it wakes. Firefox rides the RDP root actor (listAddons, engine 4.0.15 and later), so entries are installed add-ons regardless of contexts, are marked temporarilyInstalled where relevant, and carry no contexts. Other extensions' contexts are never attached to or evaluated in. This requires an active dev or start session.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | See |
| Needs | a live dev or start session |
| Read only | yes |
| Destructive | no |
| Idempotent | yes |
| Reaches the open web | no |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `projectPath` | string | yes | | Extension project root (needs a live dev session) |
| `browser` | string | no | | Session browser; defaults to this project's live session |
## Ask the agent
> Which extensions are loaded in the dev browser right now?
## The answer can carry page text
Part of this tool's answer is text a web page or the extension wrote. The server fences it between `` and `` with a fresh random `ID` per call, named in `untrusted.boundary`. A fence is a signal to the model, not a gate: pair it with `--no-ship` when the agent reads pages you do not trust. See [Server flags and telemetry](/tools/mcp/flags).
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_analyze`](/tools/mcp/analyze): Analyze a BUILT extension on disk: file sizes, declared entry points, permissions, bundle composition, and store-readiness checks.
- [`extension_doctor`](/tools/mcp/doctor): Diagnose a dev session end to end: ready contract, dev-server process, control-port agreement, control channel, eval token, executor, browser liveness.
- [`extension_dom_snapshot`](/tools/mcp/dom-snapshot): Take a shallow structured DOM snapshot of one chosen surface through the agent bridge (localhost only; the snapshot itself needs no CDP, but listTargets and `tabUrl` resolution...
- [`extension_inspect`](/tools/mcp/inspect): Inspect a running extension deeply over the browser's debugger protocol: full HTML (open shadow roots of #extension-root and [data-extension-root] hosts inlined; other shadow ro...
- [`extension_logs`](/tools/mcp/logs): Read or stream logs from every context of a running dev session (service worker, content scripts, popup, options, sidebar, devtools, pages) in one ordered timeline.
- [`extension_manifest_validate`](/tools/mcp/manifest-validate): Validate a manifest.json across browsers.
- [`extension_theme_verify`](/tools/mcp/theme-verify): Verify a Chrome theme manifest before it ships.
---
- ["extension_logs"](https://docs.extension.dev/tools/mcp/logs): "Read or stream logs from every context of a running dev session (service worker, content scripts, popup, options, sidebar, devtools, pages) in one ordered timeline."
# `extension_logs`
Read or stream logs from every context of a running dev session (service worker, content scripts, popup, options, sidebar, devtools, pages) in one ordered timeline. This reads the same agent-bridge plane as the `extension logs` CLI: a one-shot returns the most recent matching lines from logs.ndjson, and follow:true connects to the live control channel, receives the broker's replay of its recent ring (up to 5,000 events, counted in value.replayed) and then the frames that arrive during followMs (value.live). This requires an active extension_dev session.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | See |
| Needs | a live dev or start session |
| Read only | yes |
| Destructive | no |
| Idempotent | yes |
| Reaches the open web | no |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `projectPath` | string | yes | | Extension project root (needs a live dev session) |
| `browser` | string | no | | Which dist/extension-js/<browser>/ to read. Defaults to this project's live session, else chrome. |
| `level` | "off" \| "error" \| "warn" \| "info" \| "debug" \| "trace" \| "all" | no | `"all"` | Minimum severity; a level includes everything more severe. |
| `context` | array of "background" \| "content" \| "sidebar" \| "popup" \| "options" \| "devtools" \| "newtab" \| "history" \| "bookmarks" | no | | Restrict to these contexts. Omit for all. |
| `signalsOnly` | boolean | no | `false` | Only structured dx.signal diagnostics (code/status/remediation), no plain console lines. |
| `since` | number | no | | Only events with seq greater than this; the cursor for polling forward. |
| `url` | string | no | | Only events whose url/hostname matches (glob or substring), e.g. https://shop.example/*. |
| `tab` | number | no | | Only events from this tab id. |
| `follow` | boolean | no | `false` | Collect from the live control channel for a bounded window instead of reading the file. |
| `followMs` | number | no | `4000` | How long to collect live frames when follow=true (clamped 500–15000ms). |
| `limit` | number | no | `200` | How many of the most recent events to return. |
## Ask the agent
> Show me every log line since the session started, in order
## The answer can carry page text
Part of this tool's answer is text a web page or the extension wrote. The server fences it between `` and `` with a fresh random `ID` per call, named in `untrusted.boundary`. A fence is a signal to the model, not a gate: pair it with `--no-ship` when the agent reads pages you do not trust. See [Server flags and telemetry](/tools/mcp/flags).
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_analyze`](/tools/mcp/analyze): Analyze a BUILT extension on disk: file sizes, declared entry points, permissions, bundle composition, and store-readiness checks.
- [`extension_doctor`](/tools/mcp/doctor): Diagnose a dev session end to end: ready contract, dev-server process, control-port agreement, control channel, eval token, executor, browser liveness.
- [`extension_dom_snapshot`](/tools/mcp/dom-snapshot): Take a shallow structured DOM snapshot of one chosen surface through the agent bridge (localhost only; the snapshot itself needs no CDP, but listTargets and `tabUrl` resolution...
- [`extension_inspect`](/tools/mcp/inspect): Inspect a running extension deeply over the browser's debugger protocol: full HTML (open shadow roots of #extension-root and [data-extension-root] hosts inlined; other shadow ro...
- [`extension_list_extensions`](/tools/mcp/list-extensions): List the extensions in the running dev browser: id, name, version, and, on Chromium, live contexts.
- [`extension_manifest_validate`](/tools/mcp/manifest-validate): Validate a manifest.json across browsers.
- [`extension_theme_verify`](/tools/mcp/theme-verify): Verify a Chrome theme manifest before it ships.
---
- ["extension_manifest_validate"](https://docs.extension.dev/tools/mcp/manifest-validate): "Validate a manifest.json across browsers."
# `extension_manifest_validate`
Validate a manifest.json across browsers. This reports missing fields, invalid permissions, dangling file references, and cross-browser compatibility issues. Read buildBlocking for the errors that make extension_build refuse.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | See |
| Needs | none |
| Read only | yes |
| Destructive | no |
| Idempotent | yes |
| Reaches the open web | no |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `manifestPath` | string | no | | Path to manifest.json. Or pass projectPath and the manifest is located for you. |
| `projectPath` | string | no | | Path to the extension project root; manifest.json is resolved from it (root or src/). Accepted in place of manifestPath. |
| `browsers` | array of string | no | `["chrome","firefox","edge"]` | Browsers to validate against |
| `browser` | string | no | | Single browser to validate against; alias for browsers:[browser] to match the other tools. |
## Ask the agent
> Will this manifest load on Firefox and Safari?
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_analyze`](/tools/mcp/analyze): Analyze a BUILT extension on disk: file sizes, declared entry points, permissions, bundle composition, and store-readiness checks.
- [`extension_doctor`](/tools/mcp/doctor): Diagnose a dev session end to end: ready contract, dev-server process, control-port agreement, control channel, eval token, executor, browser liveness.
- [`extension_dom_snapshot`](/tools/mcp/dom-snapshot): Take a shallow structured DOM snapshot of one chosen surface through the agent bridge (localhost only; the snapshot itself needs no CDP, but listTargets and `tabUrl` resolution...
- [`extension_inspect`](/tools/mcp/inspect): Inspect a running extension deeply over the browser's debugger protocol: full HTML (open shadow roots of #extension-root and [data-extension-root] hosts inlined; other shadow ro...
- [`extension_list_extensions`](/tools/mcp/list-extensions): List the extensions in the running dev browser: id, name, version, and, on Chromium, live contexts.
- [`extension_logs`](/tools/mcp/logs): Read or stream logs from every context of a running dev session (service worker, content scripts, popup, options, sidebar, devtools, pages) in one ordered timeline.
- [`extension_theme_verify`](/tools/mcp/theme-verify): Verify a Chrome theme manifest before it ships.
---
- ["extension_open"](https://docs.extension.dev/tools/mcp/open): "Open an extension surface, or replay an event, in a running session."
# `extension_open`
Open an extension surface, or replay an event, in a running session. Pass surface:'popup', 'options' or 'sidebar' to open a UI surface, or 'newtab', 'history' or 'bookmarks' to open the matching chrome_url_overrides page in a tab (always a tab, resolved by the server, never sent to the engine). On Chromium, when Chrome refuses the sidebar for lack of a user gesture, the server opens the real panel through a synthetic click on the extension's own page and says so in warnings; if that fails too it renders the sidebar document as a tab. Pass surface:'devtools' to open the browser's DevTools on a tab (the one `url` matches, else the first web page) and show the extension's panel there, picked by `panel` title when there are several: Chromium only, over CDP Target.openDevTools, headed or headless; the result names the panel document's url, which extension_eval reads with context 'page' and that url (the panel is no tab, so the tab-based readers do not reach it). Pass surface:'action' to trigger the toolbar action, which opens its popup or replays chrome.action.onClicked when there is none. Pass surface:'command' with `name` to replay a chrome.commands.onCommand shortcut. Note that action and command replay invoke your listener without a user gesture, so the gesture-derived activeTab grant does not apply; the engine's own frame is returned as is. Rides the dev session's control channel, on unless extension_dev was called with allowControl:false.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | Act |
| Needs | a dev session, which carries control unless started with allowControl: false |
| Read only | no |
| Destructive | no |
| Idempotent | no |
| Reaches the open web | yes |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `projectPath` | string | yes | | Extension project root (needs a live dev session) |
| `surface` | "popup" \| "options" \| "sidebar" \| "devtools" \| "newtab" \| "history" \| "bookmarks" \| "action" \| "command" | no | | Which surface to open or event to replay. |
| `name` | string | no | | For surface 'command': the chrome.commands name to trigger. |
| `panel` | string | no | | For surface 'devtools': the title the extension gave chrome.devtools.panels.create, when it registers more than one panel. Omitted, the extension's first panel is shown. |
| `waitMs` | number | no | | For surface 'devtools': how long to wait for the panel to register after DevTools opens (default 15000, up to 120000). Extensions that create their panel on a page event need longer, or `reload`. |
| `reload` | boolean | no | `false` | For surface 'devtools': reload the inspected tab once DevTools is open, for extensions that create their panel only when the page reports to them on a load that starts with DevTools open (Preact Devtools). Discards the page state under test. |
| `url` | string | no | | Navigate a real tab here instead of opening a surface, in a NEW tab unless `tab` names one (a blank or new-tab page is reused). An absolute url opens as given; a path with no scheme, such as pages/options.html, is resolved against the extension's own origin. Use for content-script test pages, or a surface as a page. |
| `tab` | number | no | | With `url`: navigate this chrome.tabs id in place instead of opening a new tab (rides the engine's navigate verb over the control channel, which a dev session carries unless started with allowControl: false). Without it an existing page is never taken over. |
| `asTab` | boolean | no | `false` | popup/options/sidebar: render the surface's document in a real tab instead of a popup window. This is how you inspect a surface HEADLESSLY, and it is applied automatically when a headless session refuses to open one. Same page and APIs, but no popup sizing and window.close() closes the tab. |
| `browser` | string | no | | Session browser; defaults to this project's live session |
| `timeout` | number | no | | Command timeout in ms. When omitted, the default depends on the route: 30000 through the dev session's control channel, 10000 over the Firefox debugger protocol, and 15000 per command over the Chromium debug port. |
## Ask the agent
> Open the side panel, then trigger the action button
## The answer can carry page text
Part of this tool's answer is text a web page or the extension wrote. The server fences it between `` and `` with a fresh random `ID` per call, named in `untrusted.boundary`. A fence is a signal to the model, not a gate: pair it with `--no-ship` when the agent reads pages you do not trust. See [Server flags and telemetry](/tools/mcp/flags).
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_eval`](/tools/mcp/eval): Evaluate an expression in a running extension context.
- [`extension_reload`](/tools/mcp/reload): Reload a running extension's background context, or a tab.
- [`extension_storage`](/tools/mcp/storage): Read or write chrome.storage in a running extension.
---
- ["extension_preview_web"](https://docs.extension.dev/tools/mcp/preview-web): "Preview an in-progress extension in the web emulator, with no real browser."
# `extension_preview_web`
Preview an in-progress extension in the web emulator, with no real browser. This builds the project (unless build:false) and previews dist/<browser>. Pass share:true unless you are working inside the extension.dev monorepo: it uploads the build and returns a link anyone can open, with no install, sign-in or dev server, and it is the only lane that works from an npm install of this server. Sharing also serves the build as a zip, so it hands over the built code; read the share property before using it. The default lane instead returns a deep link over the dev-only preview://build scheme, which resolves only against a preview.extension.dev dev server on this machine, so it is for people developing extension.dev itself. Call extension_shares to list and revoke every link shared this way, so one never vanishes with this response.
| | |
| --- | --- |
| Group | platform, on with `--features=local,platform` (private alpha) |
| Tier | Platform |
| Needs | the server started with --features=local,platform; sharing needs a login |
| Read only | no |
| Destructive | no |
| Idempotent | no |
| Reaches the open web | yes |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `projectPath` | string | yes | | Extension project root |
| `project` | string | no | | Which stored login to use, as '<workspace>/<project>' (or a project slug that matches one login), when extension_auth has signed in to more than one project on this machine. Omitted, the most recent login is used, after EXTENSION_DEV_TOKEN when that is set. Named, it outranks EXTENSION_DEV_TOKEN. extension_auth (action: status) lists the stored logins. |
| `browser` | "chrome" \| "chromium" \| "edge" \| "brave" \| "opera" \| "vivaldi" \| "yandex" \| "firefox" \| "waterfox" \| "librewolf" \| "zen" \| "floorp" \| "safari" | no | `"chrome"` | Which dist/<browser> output to preview. The emulator renders it as mocked Chrome either way. |
| `build` | boolean | no | `true` | Build first. false previews the existing dist/<browser> as-is. |
| `distPath` | string | no | | Preview this built directory instead of dist/<browser> under projectPath. Implies build:false. |
| `hostUrl` | string | no | | Origin of the running preview.extension.dev dev server (default `http://localhost:3110`). |
| `probe` | boolean | no | `true` | Fetch the surface's dev middleware first to confirm the artifact loads on the local host. With share:true it also checks the shared link the way a browser would, following the zip's redirects and asserting the final response allows the preview origin, and reports that as share.browserLoadable. |
| `open` | boolean | no | `false` | Also open the deep link in a running session's browser, in a focus-safe background tab. Needs a live extension_dev/extension_start session. |
| `openIn` | "chrome" \| "chromium" \| "edge" \| "brave" \| "opera" \| "vivaldi" \| "yandex" \| "firefox" \| "waterfox" \| "librewolf" \| "zen" \| "floorp" \| "safari" | no | | Which session's browser to open it in. Defaults to `browser`. |
| `share` | boolean | no | `false` | Upload the built dist and return a public link (share.previewUrl) that renders those exact bytes for anyone: no install, sign-in or dev server. Uploading is metered against your plan's allowance on extension.dev; left false, the result's share property says what the local deepLink needs, what share:true spends, and the exact call to get a shareable link. It also serves the build as a zip (share.zipUrl), so sharing hands over the code. Needs a token scoped to an extension.dev project (extension_auth or EXTENSION_DEV_TOKEN); without one you get a login hint and the local preview still succeeds. Live until share.expiresAt; DELETE share.revokeUrl to kill it sooner. Revocation is permanent, and re-sharing an unchanged build returns the same link unless it was revoked, so each share is also appended to the project's gitignored .extension.dev/shared-previews.json. |
## Ask the agent
> Share this build with my designer
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_auth`](/tools/mcp/auth): Sign this machine in to extension.dev, report that login, or clear it.
- [`extension_project_create`](/tools/mcp/project-create): Create an extension.dev project for an extension that does not have one yet, without opening the console.
- [`extension_publish`](/tools/mcp/publish): Publish the project your stored token is scoped to (extension_auth, or EXTENSION_DEV_TOKEN) to extension.dev, and return its shareable URL.
- [`extension_release_promote`](/tools/mcp/release-promote): Promote a built extension to a release channel (stable, preview, beta, …) on extension.dev, headless.
- [`extension_release_status`](/tools/mcp/release-status): Read where a project stands on extension.dev, from the public registry (registry.extension.land).
- [`extension_shares`](/tools/mcp/shares): List and revoke the public preview links this token has shared, which is what extension_preview_web share:true hands out.
- [`extension_submit`](/tools/mcp/submit): Submit a built extension for store REVIEW through extension.dev, which holds your store credentials and dispatches from your project's mirror CI: the Chrome Web Store, Firefox A...
- [`extension_workspace_create`](/tools/mcp/workspace-create): Create an extension.dev workspace that does not exist yet, without opening the console.
---
- ["extension_project_create"](https://docs.extension.dev/tools/mcp/project-create): "Create an extension.dev project for an extension that does not have one yet, without opening the console."
# `extension_project_create`
Create an extension.dev project for an extension that does not have one yet, without opening the console. Use it right after extension_create and extension_build, once the extension's source is pushed to a GitHub repository, and BEFORE extension_auth: extension_auth can only log in to a project that already exists, and this tool is what brings that project into existence. Ask for nothing but the project slug and the repo; the platform finds the GitHub App installation on the approving account itself, and if there is none it returns a connect link to open. Two-phase, like login: the first call returns a code and a URL where a signed-in member of the workspace approves creating exactly this project; call again with the returned deviceCode to finish. The approval mints a provisioning grant that lives minutes, can only create the one named project, and is never stored on this machine. On success the platform creates the project and its mirror repository, and dispatches the first build when it can: the answer says in `firstBuild` whether one was dispatched and, when none was, why (no commits, no build workflow, a spent build allowance, a paused dispatch). Every build runs the stored build command for each enabled browser exactly as stored, then reads that browser's outputDirectory, so the default command is `npm run build -- --browser ` (the browser's name in place of ``) into `dist/`. Then run extension_auth (action: login) against the new project, and extension_publish to share it. To create several projects in one workspace under one approval, pass `projects` instead of `project` and `repo`: the approval page lists every name, each project is created by its own request, and each one's 7-day token is stored as that project's login, so no extension_auth call is needed afterwards. A list takes a few calls to finish: while projects remain the answer is status 'creating' with the same deviceCode to call again, and the grant is held in this server's memory only. One approval creates at most 10 projects, the cap the platform states in its login config, because it creates at most 10 per hour for one approving account; the next 10 can start in a new call once that limit allows. A longer list is refused before any approval is asked for, never split silently, and so is any list on a platform that does not advertise batch onboarding.
| | |
| --- | --- |
| Group | platform, on with `--features=local,platform` (private alpha) |
| Tier | Platform |
| Needs | the server started with --features=local,platform |
| Read only | no |
| Destructive | no |
| Idempotent | no |
| Reaches the open web | yes |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `project` | string | no | | Target project as '<workspace>/<project>'. The workspace is the GitHub login of the approving user for personal workspaces; the project slug is the new project's name and must not exist yet. |
| `repo` | string | no | | Source GitHub repository as '<owner>/<repo>'. The extension's code must be pushed there, and the owner must be the same GitHub account that approves the device code. |
| `installationId` | string | no | | Optional override. Leave it out: the platform finds the extension.dev GitHub App installation on the approving account itself. Pass it only when an operator needs to name one explicitly, and it must still be an installation on that account or the platform refuses it. |
| `displayName` | string | no | | Human name for the project. Defaults to the project slug. |
| `description` | string | no | | Short project description. Defaults to a generic sentence naming the repo. |
| `installCommand` | string | no | `"npm install"` | Dependency install command the build runs first. |
| `buildCommand` | string | no | `"npm run build -- --browser "` | Build command the platform runs for each enabled browser, as it is: the platform does not add --browser to a command stored through this tool (only its import and clone paths derive one), and `extension build` with no flag targets chromium and writes `dist/chromium`, which no browser's outputDirectory reads. The default therefore names the browser; `` in a command of your own becomes each enabled browser's name. |
| `outputDirectory` | string | no | `"dist/chrome"` | Directory the build writes the loadable extension into. With more than one browser, `` in the path becomes each browser's name (Extension.js writes `dist/` for the browser its --browser flag names; `extension build` with no flag targets chromium and writes `dist/chromium`, so a buildCommand that leaves the flag out never fills `dist/chrome`), and when it is left out every browser defaults to `dist/`. |
| `browsers` | array of "chrome" \| "edge" \| "firefox" | no | `["chrome"]` | Browsers the platform builds, each enabled with the same install and build command and its own output directory. Pass every browser the extension targets, for example ["chrome", "edge", "firefox"], so the project needs no console visit to go cross-browser. |
| `outputDirectories` | object | no | | Per-browser output directory overrides, for example {"edge": "build/manifestv3"}. A browser named here wins over `outputDirectory`. |
| `projects` | array of object | no | | Create several projects in one workspace under one approval, instead of `project` and `repo`. 1 to 10 entries (the platform's cap per approval; never more than 20), each { project: '<workspace>/<project>', repo: '<owner>/<repo>' }, all in the same workspace, each project named by its exact slug (lowercase letters and digits joined by single dashes, at most 48 characters), none twice and none existing yet. An entry may also carry displayName, description, installCommand, buildCommand, outputDirectory, browsers and outputDirectories for that project alone; the same inputs at the top level are the shared default for every entry that leaves them out. The answer carries one row per project: created and logged in, created with no token (run a batch extension_auth login), refused with the platform's code, or not attempted. A refusal on one project never hides the others. |
| `deviceCode` | string | no | | Resume token from the prior call's `deviceCode`; omit on the first call. A batch returns the same deviceCode until every listed project has an answer. |
| `api` | string | no | | Platform base URL (default EXTENSION_DEV_API_URL, else https://www.extension.dev) |
## Ask the agent
> Create the extension.dev project for this build under acme
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_auth`](/tools/mcp/auth): Sign this machine in to extension.dev, report that login, or clear it.
- [`extension_preview_web`](/tools/mcp/preview-web): Preview an in-progress extension in the web emulator, with no real browser.
- [`extension_publish`](/tools/mcp/publish): Publish the project your stored token is scoped to (extension_auth, or EXTENSION_DEV_TOKEN) to extension.dev, and return its shareable URL.
- [`extension_release_promote`](/tools/mcp/release-promote): Promote a built extension to a release channel (stable, preview, beta, …) on extension.dev, headless.
- [`extension_release_status`](/tools/mcp/release-status): Read where a project stands on extension.dev, from the public registry (registry.extension.land).
- [`extension_shares`](/tools/mcp/shares): List and revoke the public preview links this token has shared, which is what extension_preview_web share:true hands out.
- [`extension_submit`](/tools/mcp/submit): Submit a built extension for store REVIEW through extension.dev, which holds your store credentials and dispatches from your project's mirror CI: the Chrome Web Store, Firefox A...
- [`extension_workspace_create`](/tools/mcp/workspace-create): Create an extension.dev workspace that does not exist yet, without opening the console.
---
- ["extension_publish"](https://docs.extension.dev/tools/mcp/publish): "Publish the project your stored token is scoped to (extension_auth, or EXTENSION_DEV_TOKEN) to extension.dev, and return its shareable URL."
# `extension_publish`
Publish the project your stored token is scoped to (extension_auth, or EXTENSION_DEV_TOKEN) to extension.dev, and return its shareable URL. This is what "deploy" or "ship" an extension usually means; extension_submit is the separate store-review path. The target is the token's project: there is no projectPath, and no local file is uploaded. With several logins stored, pass `project` ('<workspace>/<project>') to pick which one; it outranks EXTENSION_DEV_TOKEN. For a public project the URL is the canonical public page and ttlHours does not apply. For a private one it is a fresh time-limited share link (?share=) whose lifetime is ttlHours.
| | |
| --- | --- |
| Group | platform, on with `--features=local,platform` (private alpha) |
| Tier | Platform |
| Needs | the server started with --features=local,platform, and a login |
| Read only | no |
| Destructive | no |
| Idempotent | no |
| Reaches the open web | yes |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `project` | string | no | | Which stored login to use, as '<workspace>/<project>' (or a project slug that matches one login), when extension_auth has signed in to more than one project on this machine. Omitted, the most recent login is used, after EXTENSION_DEV_TOKEN when that is set. Named, it outranks EXTENSION_DEV_TOKEN. extension_auth (action: status) lists the stored logins. |
| `ttlHours` | number | no | | Private-project share-link lifetime in hours, 1-168 (default 24). Ignored for public projects. |
| `buildSha` | string | no | | Pin the URL to a build sha (7-40 hex chars). The platform rejects a sha missing from a readable build index; when its index cannot be read it echoes the sha back and a pin on a failed build is accepted, so value.buildSha is the platform's claim, not a verified build. extension_release_status lists the builds it knows. |
| `api` | string | no | | Platform base URL (default EXTENSION_DEV_API_URL, else https://www.extension.dev) |
## Ask the agent
> Give me the shareable URL of the latest build
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_auth`](/tools/mcp/auth): Sign this machine in to extension.dev, report that login, or clear it.
- [`extension_preview_web`](/tools/mcp/preview-web): Preview an in-progress extension in the web emulator, with no real browser.
- [`extension_project_create`](/tools/mcp/project-create): Create an extension.dev project for an extension that does not have one yet, without opening the console.
- [`extension_release_promote`](/tools/mcp/release-promote): Promote a built extension to a release channel (stable, preview, beta, …) on extension.dev, headless.
- [`extension_release_status`](/tools/mcp/release-status): Read where a project stands on extension.dev, from the public registry (registry.extension.land).
- [`extension_shares`](/tools/mcp/shares): List and revoke the public preview links this token has shared, which is what extension_preview_web share:true hands out.
- [`extension_submit`](/tools/mcp/submit): Submit a built extension for store REVIEW through extension.dev, which holds your store credentials and dispatches from your project's mirror CI: the Chrome Web Store, Firefox A...
- [`extension_workspace_create`](/tools/mcp/workspace-create): Create an extension.dev workspace that does not exist yet, without opening the console.
---
- ["extension_release_promote"](https://docs.extension.dev/tools/mcp/release-promote): "Promote a built extension to a release channel (stable, preview, beta, …) on extension.dev, headless."
# `extension_release_promote`
Promote a built extension to a release channel (stable, preview, beta, …) on extension.dev, headless. This WRITES: it is the only verb that changes what a channel points at. It is auth-gated by your stored login (extension_auth) or a release token in EXTENSION_DEV_TOKEN, minted and revoked under project settings, Access tokens. Tokens live at most 7 days, so CI must re-mint before expiry. The project comes from the token; with several logins stored, `project` picks which one. Call extension_release_status to find a valid buildId. The status is 'promoted' only when the platform says every asked browser's release was dispatched and the channel pointer moved; 'promoted-partially' lists in its warnings what did not happen (a browser whose dispatch failed, a channel pointer that was not moved) and must not be repeated whole; 'promote-unconfirmed' means the platform's answer did not carry the result, so read extension_release_status before promoting again. Cutting a version-bump PR is not available headlessly, because it writes to your source repo and needs an interactive login.
| | |
| --- | --- |
| Group | platform, on with `--features=local,platform` (private alpha) |
| Tier | Platform |
| Needs | the server started with --features=local,platform, and a login |
| Read only | no |
| Destructive | yes |
| Idempotent | no |
| Reaches the open web | yes |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `project` | string | no | | Which stored login to use, as '<workspace>/<project>' (or a project slug that matches one login), when extension_auth has signed in to more than one project on this machine. Omitted, the most recent login is used, after EXTENSION_DEV_TOKEN when that is set. Named, it outranks EXTENSION_DEV_TOKEN. extension_auth (action: status) lists the stored logins. |
| `buildId` | string | yes | | Build commit SHA to promote (a 7-char short SHA is fine) |
| `channel` | string | yes | | Target release channel, e.g. stable, preview, beta |
| `sourceChannel` | string | no | | Channel to promote from (optional; inferred otherwise) |
| `browsers` | array of string | no | | Browsers to release. Optional: when omitted the platform reads the build's browsers from its index, and falls back to chrome alone when that index cannot be read, so pass them to be sure. |
| `version` | string | no | | Version label for the release (optional) |
| `releaseNotes` | string | no | | Release notes markdown (optional) |
| `approvalId` | string | no | | The approval handle returned by a prior approval-required response. Promoting changes what a public channel serves and is not reversible in place, so when the platform's approval gate is on this needs a human approval: call once without this to get an approval id and URL, have a human approve at extension.dev, then call again with the same id. |
| `api` | string | no | | Platform base URL (default EXTENSION_DEV_API_URL, else https://www.extension.dev) |
## Ask the agent
> Promote build 3f2a1c to the beta channel
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_auth`](/tools/mcp/auth): Sign this machine in to extension.dev, report that login, or clear it.
- [`extension_preview_web`](/tools/mcp/preview-web): Preview an in-progress extension in the web emulator, with no real browser.
- [`extension_project_create`](/tools/mcp/project-create): Create an extension.dev project for an extension that does not have one yet, without opening the console.
- [`extension_publish`](/tools/mcp/publish): Publish the project your stored token is scoped to (extension_auth, or EXTENSION_DEV_TOKEN) to extension.dev, and return its shareable URL.
- [`extension_release_status`](/tools/mcp/release-status): Read where a project stands on extension.dev, from the public registry (registry.extension.land).
- [`extension_shares`](/tools/mcp/shares): List and revoke the public preview links this token has shared, which is what extension_preview_web share:true hands out.
- [`extension_submit`](/tools/mcp/submit): Submit a built extension for store REVIEW through extension.dev, which holds your store credentials and dispatches from your project's mirror CI: the Chrome Web Store, Firefox A...
- [`extension_workspace_create`](/tools/mcp/workspace-create): Create an extension.dev workspace that does not exist yet, without opening the console.
---
- ["extension_release_status"](https://docs.extension.dev/tools/mcp/release-status): "Read where a project stands on extension.dev, from the public registry (registry.extension.land)."
# `extension_release_status`
Read where a project stands on extension.dev, from the public registry (registry.extension.land). This is read-only: it dispatches nothing and promotes nothing. Pass include:'releases' for the release channels (channel to promoted build sha), recent builds, and a public build-page URL for each, which is how you find a valid sha for extension_release_promote, extension_submit or extension_publish. Pass include:'stores' for the per-store picture after an extension_submit (chrome, firefox, edge, safari): configured or not, the last credential health check, the last recorded submission, and the latest review status, read from stores/health.json, stores/status.json and stores/submissions.json. Both are included by default. This defaults to the logged-in project (extension_auth); pass workspace and project to read another. Private projects work when your stored login covers them. Registry state can lag the store dashboards by up to a polling interval.
| | |
| --- | --- |
| Group | platform, on with `--features=local,platform` (private alpha) |
| Tier | Platform |
| Needs | the server started with --features=local,platform, and a login |
| Read only | yes |
| Destructive | no |
| Idempotent | yes |
| Reaches the open web | yes |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `include` | array of "releases" \| "stores" | no | `["releases","stores"]` | Which sections to read. Both by default. |
| `workspace` | string | no | | Workspace slug override (default: the stored login's). |
| `project` | string | no | | Project slug override (default: the stored login's). |
| `api` | string | no | | Platform base URL (default EXTENSION_DEV_API_URL, else https://www.extension.dev) |
## Ask the agent
> Was the last Chrome submission approved?
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_auth`](/tools/mcp/auth): Sign this machine in to extension.dev, report that login, or clear it.
- [`extension_preview_web`](/tools/mcp/preview-web): Preview an in-progress extension in the web emulator, with no real browser.
- [`extension_project_create`](/tools/mcp/project-create): Create an extension.dev project for an extension that does not have one yet, without opening the console.
- [`extension_publish`](/tools/mcp/publish): Publish the project your stored token is scoped to (extension_auth, or EXTENSION_DEV_TOKEN) to extension.dev, and return its shareable URL.
- [`extension_release_promote`](/tools/mcp/release-promote): Promote a built extension to a release channel (stable, preview, beta, …) on extension.dev, headless.
- [`extension_shares`](/tools/mcp/shares): List and revoke the public preview links this token has shared, which is what extension_preview_web share:true hands out.
- [`extension_submit`](/tools/mcp/submit): Submit a built extension for store REVIEW through extension.dev, which holds your store credentials and dispatches from your project's mirror CI: the Chrome Web Store, Firefox A...
- [`extension_workspace_create`](/tools/mcp/workspace-create): Create an extension.dev workspace that does not exist yet, without opening the console.
---
- ["extension_reload"](https://docs.extension.dev/tools/mcp/reload): "Reload a running extension's background context, or a tab."
# `extension_reload`
Reload a running extension's background context, or a tab. Rides the dev session's control channel, on unless extension_dev was called with allowControl:false. A background reload answers once the engine's ready.json shows the new background attached to the dev server again (value.reattached, value.reattachedMs), so the next read or assertion meets the new generation and not the gap between them; if it has not come back within the budget (timeout, at most 5 seconds) the answer is status reloading with the contract's own stamps.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | Act |
| Needs | a dev session, which carries control unless started with allowControl: false |
| Read only | no |
| Destructive | no |
| Idempotent | yes |
| Reaches the open web | no |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `projectPath` | string | yes | | Extension project root (needs a live dev session) |
| `context` | "background" \| "content" \| "page" | no | `"background"` | |
| `tab` | number | no | | For content/page: a specific tab id |
| `browser` | string | no | | Session browser; defaults to this project's live session |
| `timeout` | number | no | | Command timeout in ms. When omitted, the default depends on the route: 30000 through the dev session's control channel, 10000 over the Firefox debugger protocol, and 15000 per command over the Chromium debug port. |
## Ask the agent
> Reload the extension and confirm the worker came back
## The answer can carry page text
Part of this tool's answer is text a web page or the extension wrote. The server fences it between `` and `` with a fresh random `ID` per call, named in `untrusted.boundary`. A fence is a signal to the model, not a gate: pair it with `--no-ship` when the agent reads pages you do not trust. See [Server flags and telemetry](/tools/mcp/flags).
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_eval`](/tools/mcp/eval): Evaluate an expression in a running extension context.
- [`extension_open`](/tools/mcp/open): Open an extension surface, or replay an event, in a running session.
- [`extension_storage`](/tools/mcp/storage): Read or write chrome.storage in a running extension.
---
- ["extension_shares"](https://docs.extension.dev/tools/mcp/shares): "List and revoke the public preview links this token has shared, which is what extension_preview_web share:true hands out."
# `extension_shares`
List and revoke the public preview links this token has shared, which is what extension_preview_web share:true hands out. Pass action:'list' (the default) for every artifact the logged-in project owns, with its artifactId, name, version, live or dead state, createdAt, expiresAt, revokedAt, size, previewUrl, zipUrl and revokeUrl, so a link whose response you lost is findable again. Each row carries owner and sharedBy as the platform returned them. Read attribution.ownership for who may revoke a share: 'project' means the workspace holds it and any member can pull it back, 'personal' means one person holds it alone, 'unknown' means no owner was disclosed. Read attribution.credit as credit only, never access; it names the publisher, and reads 'CLI token <id>' or 'not recorded' when no person can be named. Pass action:'revoke' with an artifactId, or with any URL of the share, to kill one permanently. Pass projectPath to reconcile against the project's own append-only .extension.dev/shared-previews.json, which is read and never rewritten: a share made on another machine shows as remoteOnly, a record with no live artifact as localOnly. That record is append-only, so localOnly is counted by distinct artifactId and a build re-shared unchanged is one share, not two; server.count and server.matched are share counts, while server.scanned counts records the platform read and is never a share count. This needs the same token as sharing (extension_auth or EXTENSION_DEV_TOKEN); without one, listing still returns the local record with a login hint.
| | |
| --- | --- |
| Group | platform, on with `--features=local,platform` (private alpha) |
| Tier | Platform |
| Needs | the server started with --features=local,platform, and a login |
| Read only | no |
| Destructive | yes |
| Idempotent | no |
| Reaches the open web | yes |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `project` | string | no | | Which stored login to use, as '<workspace>/<project>' (or a project slug that matches one login), when extension_auth has signed in to more than one project on this machine. Omitted, the most recent login is used, after EXTENSION_DEV_TOKEN when that is set. Named, it outranks EXTENSION_DEV_TOKEN. extension_auth (action: status) lists the stored logins. |
| `action` | "list" \| "revoke" | no | `"list"` | list reads every share this token owns; revoke permanently kills one and cannot be undone. |
| `artifactId` | string | no | | Which share to revoke (the gen_... id from a share response or from action:"list"). Required for revoke unless url is given. |
| `url` | string | no | | Any URL of the share to revoke (previewUrl, zipUrl, viewUrl, or revokeUrl). The artifact id is read out of it, so the link you sent someone is enough to pull it back. |
| `approvalId` | string | no | | The approval handle returned by a prior approval-required response for a revoke. Revoking permanently burns a share and cannot be undone, so when the platform's approval gate is on this needs a human approval: call revoke once without this to get an approval id and URL, have a human approve at extension.dev, then call revoke again with the same id. Listing never needs it. |
| `projectPath` | string | no | | Path to the extension project root. Reconciles the platform's answer against this project's .extension.dev/shared-previews.json record. Read-only. |
| `status` | "all" \| "live" | no | `"all"` | all (default) includes expired and revoked shares, which is what makes a dead link explainable; live returns only the links the platform reported resolving at list time (a 429 or 503 from the platform answers listed-local-only, which says nothing about any link). |
| `limit` | number | no | | How many shares to return, 1 to 200 (platform default 100). A cut list comes back with truncated:true. |
| `api` | string | no | | Platform base URL (default EXTENSION_DEV_API_URL, else https://www.extension.dev) |
## Ask the agent
> List every link I have shared and revoke the oldest one
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_auth`](/tools/mcp/auth): Sign this machine in to extension.dev, report that login, or clear it.
- [`extension_preview_web`](/tools/mcp/preview-web): Preview an in-progress extension in the web emulator, with no real browser.
- [`extension_project_create`](/tools/mcp/project-create): Create an extension.dev project for an extension that does not have one yet, without opening the console.
- [`extension_publish`](/tools/mcp/publish): Publish the project your stored token is scoped to (extension_auth, or EXTENSION_DEV_TOKEN) to extension.dev, and return its shareable URL.
- [`extension_release_promote`](/tools/mcp/release-promote): Promote a built extension to a release channel (stable, preview, beta, …) on extension.dev, headless.
- [`extension_release_status`](/tools/mcp/release-status): Read where a project stands on extension.dev, from the public registry (registry.extension.land).
- [`extension_submit`](/tools/mcp/submit): Submit a built extension for store REVIEW through extension.dev, which holds your store credentials and dispatches from your project's mirror CI: the Chrome Web Store, Firefox A...
- [`extension_workspace_create`](/tools/mcp/workspace-create): Create an extension.dev workspace that does not exist yet, without opening the console.
---
- ["extension_start"](https://docs.extension.dev/tools/mcp/start): "Run the PRODUCTION build in a browser: build the project, serve it, and launch."
# `extension_start`
Run the PRODUCTION build in a browser: build the project, serve it, and launch. There is no hot module replacement and no control channel, so your edits are not picked up and extension_eval, extension_storage, extension_reload, extension_open and extension_dom_snapshot cannot attach to this session. Use extension_dev while writing code, and this to check what actually ships. Pass build:false to launch an existing dist/<browser> without rebuilding, or outputPath to launch any prebuilt unpacked extension directory, one another toolchain produced included, which implies build:false.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | Run |
| Needs | none |
| Read only | no |
| Destructive | no |
| Idempotent | no |
| Reaches the open web | no |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `projectPath` | string | yes | | Extension project root |
| `browser` | "chrome" \| "chromium" \| "edge" \| "brave" \| "opera" \| "vivaldi" \| "yandex" \| "firefox" \| "waterfox" \| "librewolf" \| "zen" \| "floorp" \| "safari" \| "chromium-based" \| "gecko-based" \| "firefox-based" \| "webkit-based" | no | `"chrome"` | |
| `build` | boolean | no | `true` | Build before serving. false serves the existing dist/<browser> as-is and fails when there is none. |
| `polyfill` | boolean | no | `true` | Apply cross-browser polyfill (build only) |
| `port` | number | no | | Passed to the engine as --port (0 for auto-assign). A production start serves nothing over it today; it matters only to a toolchain that reads it. |
| `noBrowser` | boolean | no | `false` | Build (or, with build:false, check the dist) without launching a browser. A production start serves nothing, so with no browser the engine process ends once the build does; read the result with extension_build rather than a session. |
| `outputPath` | string | no | | An existing unpacked extension directory to launch as it is (a manifest.json at its root), for an artifact built by another toolchain or an exact release candidate. Implies build:false; projectPath still names the project the session belongs to. Relative paths resolve against projectPath. |
| `profile` | string | no | | Profile path, or "false" to reuse the real user profile. Omit for a throwaway one. |
| `startingUrl` | string | no | | URL the browser opens on launch |
| `chromiumBinary` | string | no | | Custom Chromium-based binary to launch; `browser` still names the target family the engine builds for (chromium-based when none is given). |
| `geckoBinary` | string | no | | Custom Gecko/Firefox binary to launch; `browser` still names the target family the engine builds for (gecko-based when none is given). |
| `host` | string | no | | Bind host, default 127.0.0.1. Use 0.0.0.0 in Docker or devcontainers. |
| `publicHost` | string | no | | Host the browser dials for HMR and reload when it differs from the bind host |
| `extensions` | array of string | no | | Extra extension paths or store URLs to load alongside the project |
## Ask the agent
> Launch the production build in Edge
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_dev`](/tools/mcp/dev): Run the extension while you edit it: dev build, hot module replacement, and a browser with the extension loaded.
- [`extension_stop`](/tools/mcp/stop): Stop a session that extension_dev or extension_start is running: terminate the server and the browser it launched, and remove the live-preview carrier if extension_dev placed one.
- [`extension_wait`](/tools/mcp/wait): Wait for a running dev or start session to be ready.
---
- ["extension_stop"](https://docs.extension.dev/tools/mcp/stop): "Stop a session that extension_dev or extension_start is running: terminate the server and the browser it launched, and remove the live-preview carrier if extension_dev placed one."
# `extension_stop`
Stop a session that extension_dev or extension_start is running: terminate the server and the browser it launched, and remove the live-preview carrier if extension_dev placed one. This covers extension_start build:false too, which the registry records as a preview session. Call it when you are done verifying, so sessions do not accumulate.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | Run |
| Needs | a live dev or start session |
| Read only | no |
| Destructive | yes |
| Idempotent | yes |
| Reaches the open web | no |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `projectPath` | string | no | | Extension project root |
| `browser` | string | no | | Browser of the session to stop. Defaults to the single live session for this project rather than assuming chrome. |
| `all` | boolean | no | `false` | Stop every known session across projects and browsers, found from this server's registry AND the on-disk markers written by this server or by servers that are no longer running, so it still works after an MCP restart. A marker owned by another MCP server that is still running is left alone and listed under skippedForeign unless includeOtherServers is true. It also takes back every live-preview carrier still recorded on this machine, including one in a project whose session was never stopped. projectPath/browser are then ignored. |
| `includeOtherServers` | boolean | no | `false` | With all: true, also stop sessions whose markers belong to another MCP server that is still running (the markers sit in a per-user directory every server shares). Off by default, since those sessions are someone else's. |
## Ask the agent
> Stop the running session and close its browser
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_dev`](/tools/mcp/dev): Run the extension while you edit it: dev build, hot module replacement, and a browser with the extension loaded.
- [`extension_start`](/tools/mcp/start): Run the PRODUCTION build in a browser: build the project, serve it, and launch.
- [`extension_wait`](/tools/mcp/wait): Wait for a running dev or start session to be ready.
---
- ["extension_storage"](https://docs.extension.dev/tools/mcp/storage): "Read or write chrome.storage in a running extension."
# `extension_storage`
Read or write chrome.storage in a running extension. Every call runs in the extension's background (the engine honours no context), so it proves nothing about what a content script or page can read. A set is read back and the answer says whether the stored value matches. Rides the dev session's control channel, on unless extension_dev was called with allowControl:false. Set one key per call: there is no bulk-object set.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | Act |
| Needs | a dev session, which carries control unless started with allowControl: false |
| Read only | no |
| Destructive | yes |
| Idempotent | no |
| Reaches the open web | no |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `projectPath` | string | yes | | Extension project root (needs a live dev session) |
| `action` | "get" \| "set" | yes | | get reads a key (or the whole area); set writes a key |
| `area` | "local" \| "sync" \| "session" \| "managed" | no | `"local"` | |
| `key` | string | no | | Key to get or set |
| `value` | any | no | | Value to set (any JSON value); required for action=set |
| `browser` | string | no | | Session browser; defaults to this project's live session |
| `timeout` | number | no | | Command timeout in ms. When omitted, the default depends on the route: 30000 through the dev session's control channel, 10000 over the Firefox debugger protocol, and 15000 per command over the Chromium debug port. |
## Ask the agent
> Read chrome.storage.local and set settings.theme to dark
## The answer can carry page text
Part of this tool's answer is text a web page or the extension wrote. The server fences it between `` and `` with a fresh random `ID` per call, named in `untrusted.boundary`. A fence is a signal to the model, not a gate: pair it with `--no-ship` when the agent reads pages you do not trust. See [Server flags and telemetry](/tools/mcp/flags).
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_eval`](/tools/mcp/eval): Evaluate an expression in a running extension context.
- [`extension_open`](/tools/mcp/open): Open an extension surface, or replay an event, in a running session.
- [`extension_reload`](/tools/mcp/reload): Reload a running extension's background context, or a tab.
---
- ["extension_submit"](https://docs.extension.dev/tools/mcp/submit): "Submit a built extension for store REVIEW through extension.dev, which holds your store credentials and dispatches from your project's mirror CI: the Chrome Web Store, Firefox A..."
# `extension_submit`
Submit a built extension for store REVIEW through extension.dev, which holds your store credentials and dispatches from your project's mirror CI: the Chrome Web Store, Firefox AMO, Edge Add-ons and the App Store (Safari). This is store review only. It does not push a build to the extension.dev platform, and it does not make a shareable link: that is extension_publish, which is what "deploy" or "ship" an extension almost always means. Reach for this only when the ask is explicitly a store submission. It defaults to a dry run that dispatches nothing: the platform verifies the token and project, finds the store workflow, reads the build from its index when that index is readable, reads store health itself and adds a Safari plan verdict; this tool re-reads the per-store health rows beside it. The dry run does not run the owner gate, the approval, the build quota, the dispatch pause or the submission-mode check, which the real run does first. Pass dryRun:false to actually submit, which is irreversible: only the workspace owner who issued the token may, and it dispatches the store workflow; a Chrome or Edge store with no saved submission mode is uploaded as a draft (absent_mode: safe) and does not enter review. A real submission answers 'submitted' only when the platform recorded a submission for every store asked; 'submitted-partially' names the stores it did not record, which are the only ones to submit again; 'submit-unconfirmed' means no usable answer came back, so read extension_release_status before submitting again, because a second call submits a second time. The project comes from your token (extension_auth or EXTENSION_DEV_TOKEN; tokens live at most 7 days, so CI must re-mint from the console's Access tokens page); with several logins stored, `project` picks which one. Store credentials are never arguments, and no local file is uploaded. Call extension_release_status for valid shas, and, after a real submission, for the recorded outcome and review state.
| | |
| --- | --- |
| Group | platform, on with `--features=local,platform` (private alpha) |
| Tier | Platform |
| Needs | the server started with --features=local,platform, and a login; dryRun: false needs the workspace owner's token |
| Read only | no |
| Destructive | yes |
| Idempotent | no |
| Reaches the open web | yes |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `project` | string | no | | Which stored login to use, as '<workspace>/<project>' (or a project slug that matches one login), when extension_auth has signed in to more than one project on this machine. Omitted, the most recent login is used, after EXTENSION_DEV_TOKEN when that is set. Named, it outranks EXTENSION_DEV_TOKEN. extension_auth (action: status) lists the stored logins. |
| `browsers` | array of "chrome" \| "firefox" \| "edge" \| "safari" | yes | | Stores to submit to. |
| `buildSha` | string | yes | | The built commit SHA to submit. It needs a completed build in the project's build index; an unknown sha is rejected. |
| `channel` | string | no | | Release channel to submit from (default stable). |
| `version` | string | no | | Version label for the submission record (optional). |
| `dryRun` | boolean | no | `true` | Preflight only. Pass false to actually dispatch (irreversible, enters store review). |
| `projectPath` | string | no | | Path to the extension project root, read only for the local STORE.md advisory check. Nothing local is uploaded; without it the check falls back to the server's working directory. |
| `approvalId` | string | no | | The approval handle returned by a prior approval-required response for a real submission. A real submission (dryRun:false) is irreversible and needs a human approval when the platform's approval gate is on: call once without this to get an approval id and URL, have a human approve at extension.dev, then call again with the same id. A dry run never needs it. |
| `api` | string | no | | Platform base URL (default EXTENSION_DEV_API_URL, else https://www.extension.dev) |
## Ask the agent
> Dry run a Chrome and Firefox store submission of the latest build
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_auth`](/tools/mcp/auth): Sign this machine in to extension.dev, report that login, or clear it.
- [`extension_preview_web`](/tools/mcp/preview-web): Preview an in-progress extension in the web emulator, with no real browser.
- [`extension_project_create`](/tools/mcp/project-create): Create an extension.dev project for an extension that does not have one yet, without opening the console.
- [`extension_publish`](/tools/mcp/publish): Publish the project your stored token is scoped to (extension_auth, or EXTENSION_DEV_TOKEN) to extension.dev, and return its shareable URL.
- [`extension_release_promote`](/tools/mcp/release-promote): Promote a built extension to a release channel (stable, preview, beta, …) on extension.dev, headless.
- [`extension_release_status`](/tools/mcp/release-status): Read where a project stands on extension.dev, from the public registry (registry.extension.land).
- [`extension_shares`](/tools/mcp/shares): List and revoke the public preview links this token has shared, which is what extension_preview_web share:true hands out.
- [`extension_workspace_create`](/tools/mcp/workspace-create): Create an extension.dev workspace that does not exist yet, without opening the console.
---
- ["extension_templates"](https://docs.extension.dev/tools/mcp/templates): "Browse the extension.dev template catalog."
# `extension_templates`
Browse the extension.dev template catalog. Pass action:'list' (the default) to search and filter it and get metadata per template. Pass action:'source' with a `slug` to read one template's files, for learning a pattern before building something similar. Read `framework` as the UI framework only, never the language: TypeScript and JavaScript templates live under slugs ('typescript', 'content-typescript'), shadcn is a React variant ('sidebar-shadcn'), and provider AIs carry the 'ai' tag ('ai-chatgpt', 'ai-claude'). Reach those through query, tags or slug.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | Create and build |
| Needs | none |
| Read only | yes |
| Destructive | no |
| Idempotent | yes |
| Reaches the open web | yes |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `action` | "list" \| "source" | no | `"list"` | |
| `slug` | string | no | | source: which template to read (e.g. 'ai-claude', 'content-react'). Required for source. |
| `files` | array of string | no | | source: paths to read (e.g. ['src/manifest.json']). Omit for the file listing. |
| `query` | string | no | | list: keyword search over slug, description, tags and useCases. Ranks by word matches, so a natural phrase works. |
| `surface` | "content" \| "sidebar" \| "newtab" \| "background" | no | | list: filter by surface. For a popup/action starter use query:'action', not a surface. |
| `framework` | "react" \| "vue" \| "svelte" \| "preact" \| "" | no | | list: UI framework filter (empty string = vanilla JS). |
| `tags` | array of string | no | | list: filter by tags, e.g. ['ai', 'chat']. |
| `featured` | boolean | no | | list: only featured templates. |
## Ask the agent
> Which templates have a side panel? Show me the source of the best one
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_add_feature`](/tools/mcp/add-feature): Plan a new feature surface for an existing extension.
- [`extension_build`](/tools/mcp/build): Build a browser extension for production.
- [`extension_create`](/tools/mcp/create): Create a browser extension project from a template in the extension.dev catalog.
- [`extension_docs_search`](/tools/mcp/docs-search): Find the docs page to cite before answering any question about a WebExtension API, a manifest key, a permission, or a difference between Chrome, Firefox, Edge and Safari.
---
- ["extension_theme_verify"](https://docs.extension.dev/tools/mcp/theme-verify): "Verify a Chrome theme manifest before it ships."
# `extension_theme_verify`
Verify a Chrome theme manifest before it ships. This settles the four-leg WYSIWYG contract (app-shows == manifest-says == chrome-paints, plus chrome-accepts) as far as is possible headless: it derives every color current Chrome would paint from the manifest through the transcribed Chromium resolver, and classifies each problem as D1 fabrication, D3 parity gap, or D4 acceptance gap (keys Chrome silently discards: dead legacy, incognito, unknown, out-of-range). It verifies only, and never authors or mutates a theme. The app-rendered and real-pixel legs need a browser, so they come back as needsAttended pointing at the assert:theme and install-parity harnesses, never as passed.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | See |
| Needs | none |
| Read only | yes |
| Destructive | no |
| Idempotent | yes |
| Reaches the open web | no |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `manifest` | object | no | | The Chrome theme manifest object (with a `theme` block). Pass this or manifestPath. |
| `manifestPath` | string | no | | Path to a theme manifest.json (or a { manifest } seed wrapper). Read in place of the inline manifest. |
## Ask the agent
> Does this theme manifest paint the colors it declares?
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_analyze`](/tools/mcp/analyze): Analyze a BUILT extension on disk: file sizes, declared entry points, permissions, bundle composition, and store-readiness checks.
- [`extension_doctor`](/tools/mcp/doctor): Diagnose a dev session end to end: ready contract, dev-server process, control-port agreement, control channel, eval token, executor, browser liveness.
- [`extension_dom_snapshot`](/tools/mcp/dom-snapshot): Take a shallow structured DOM snapshot of one chosen surface through the agent bridge (localhost only; the snapshot itself needs no CDP, but listTargets and `tabUrl` resolution...
- [`extension_inspect`](/tools/mcp/inspect): Inspect a running extension deeply over the browser's debugger protocol: full HTML (open shadow roots of #extension-root and [data-extension-root] hosts inlined; other shadow ro...
- [`extension_list_extensions`](/tools/mcp/list-extensions): List the extensions in the running dev browser: id, name, version, and, on Chromium, live contexts.
- [`extension_logs`](/tools/mcp/logs): Read or stream logs from every context of a running dev session (service worker, content scripts, popup, options, sidebar, devtools, pages) in one ordered timeline.
- [`extension_manifest_validate`](/tools/mcp/manifest-validate): Validate a manifest.json across browsers.
---
- ["extension_wait"](https://docs.extension.dev/tools/mcp/wait): "Wait for a running dev or start session to be ready."
# `extension_wait`
Wait for a running dev or start session to be ready. Call it for a session started with wait:false, after an extension_dev answer whose ready.status was not "ready", and for a build-only (noBrowser) session; an extension_dev answer with ready.status "ready" already carries this result, so there is nothing left to wait for. This polls the ready.json contract and reports compiled (the compiler finished), browserAttached (the runtime executor connected), and guestLoaded (the browser's own target list shows your extension). Read guestLoaded as the trustworthy load signal: it catches a silently rejected --load-extension that leaves ready.json stamped attached with empty logs. It is null when it could not be checked, for example a gecko session with no CDP port. Every result reports budgetMs and elapsedMs; on status 'timeout', call again to keep waiting on the same contract. In a noBrowser session this returns as soon as the compile lands, instead of waiting for a browser that will never attach. Ports come from the contract, so they match what the server actually bound.
| | |
| --- | --- |
| Group | local, on by default |
| Tier | Run |
| Needs | a live dev or start session |
| Read only | yes |
| Destructive | no |
| Idempotent | yes |
| Reaches the open web | no |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `projectPath` | string | yes | | Extension project root |
| `browser` | string | no | | Session browser; defaults to this project's live session |
| `timeoutMs` | number | no | `45000` | Wait budget for this call. Default 45000, clamped to 1000-50000 so one call stays under the client's 60s request timeout. On timeout, call again to keep waiting. |
| `timeout` | number | no | | Deprecated alias of timeoutMs, which wins when both are given. |
## Ask the agent
> Wait until the dev session is ready, then tell me the ports
## The answer can carry page text
Part of this tool's answer is text a web page or the extension wrote. The server fences it between `` and `` with a fresh random `ID` per call, named in `untrusted.boundary`. A fence is a signal to the model, not a gate: pair it with `--no-ship` when the agent reads pages you do not trust. See [Server flags and telemetry](/tools/mcp/flags).
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_dev`](/tools/mcp/dev): Run the extension while you edit it: dev build, hot module replacement, and a browser with the extension loaded.
- [`extension_start`](/tools/mcp/start): Run the PRODUCTION build in a browser: build the project, serve it, and launch.
- [`extension_stop`](/tools/mcp/stop): Stop a session that extension_dev or extension_start is running: terminate the server and the browser it launched, and remove the live-preview carrier if extension_dev placed one.
---
- ["extension_workspace_create"](https://docs.extension.dev/tools/mcp/workspace-create): "Create an extension.dev workspace that does not exist yet, without opening the console."
# `extension_workspace_create`
Create an extension.dev workspace that does not exist yet, without opening the console. Use it before extension_project_create when the project's workspace is not there: project creation can only target an existing workspace, and this tool is what brings one into existence. Two-phase, like login: the first call returns a code and a URL where a signed-in GitHub user approves creating exactly this workspace and becomes its owner; call again with the returned deviceCode to finish. Check `ownerGithubLogin` in the answer: whoever approved the code owns the workspace. The approval mints a grant that lives minutes, can only create the one named workspace, names no project, and is never stored on this machine. Then run extension_project_create against '<workspace>/<project>'.
| | |
| --- | --- |
| Group | platform, on with `--features=local,platform` (private alpha) |
| Tier | Platform |
| Needs | the server started with --features=local,platform |
| Read only | no |
| Destructive | no |
| Idempotent | no |
| Reaches the open web | yes |
## Inputs
| Input | Type | Required | Default | What it does |
| --- | --- | --- | --- | --- |
| `workspace` | string | yes | | Slug of the new workspace, lowercase letters, digits and hyphens, no slash. It must not exist yet; a personal workspace (the GitHub login) already exists for every signed-in user, so this is for a shared or organization-style workspace. |
| `displayName` | string | no | | Human name for the workspace. Defaults to the slug. |
| `description` | string | no | | Short workspace description. Optional. |
| `developerUrl` | string | no | | Public URL for the workspace. Optional. |
| `deviceCode` | string | no | | Resume token from the prior call's `deviceCode`; omit on the first call. |
| `api` | string | no | | Platform base URL (default EXTENSION_DEV_API_URL, else https://www.extension.dev) |
## Ask the agent
> Create a workspace called acme on extension.dev
## Related
- [MCP server](/tools/mcp): install, the two groups and the session gates
- [Install in your client](/tools/mcp/clients)
- [`extension_auth`](/tools/mcp/auth): Sign this machine in to extension.dev, report that login, or clear it.
- [`extension_preview_web`](/tools/mcp/preview-web): Preview an in-progress extension in the web emulator, with no real browser.
- [`extension_project_create`](/tools/mcp/project-create): Create an extension.dev project for an extension that does not have one yet, without opening the console.
- [`extension_publish`](/tools/mcp/publish): Publish the project your stored token is scoped to (extension_auth, or EXTENSION_DEV_TOKEN) to extension.dev, and return its shareable URL.
- [`extension_release_promote`](/tools/mcp/release-promote): Promote a built extension to a release channel (stable, preview, beta, …) on extension.dev, headless.
- [`extension_release_status`](/tools/mcp/release-status): Read where a project stands on extension.dev, from the public registry (registry.extension.land).
- [`extension_shares`](/tools/mcp/shares): List and revoke the public preview links this token has shared, which is what extension_preview_web share:true hands out.
- [`extension_submit`](/tools/mcp/submit): Submit a built extension for store REVIEW through extension.dev, which holds your store credentials and dispatches from your project's mirror CI: the Chrome Web Store, Firefox A...
---
- ["Install in your client"](https://docs.extension.dev/tools/mcp/clients): "The MCP server's install line and config block for every client, with the local-only variant beside the full one."
# Install in your client
Every block below is generated from the same recipes the console's Connect dialog prints. The first form of each client turns on both tool groups; the second starts the server with the local group alone, the 23 tools that need no account. Either way the platform tools stay off until the server is started with `--features=local,platform`, see [MCP server](/tools/mcp).
## Claude Code
Local and platform tools:
```bash
claude mcp add extension-dev -- npx @extension.dev/mcp --features=local,platform
```
Local tools only:
```bash
claude mcp add extension-dev -- npx @extension.dev/mcp --features=local
```
## Cursor
Local and platform tools:
[Install with one click in Cursor](https://cursor.com/en/install-mcp?name=extension-dev&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyJAZXh0ZW5zaW9uLmRldi9tY3AiLCItLWZlYXR1cmVzPWxvY2FsLHBsYXRmb3JtIl19)
`.cursor/mcp.json`:
```json
{
"mcpServers": {
"extension-dev": {
"command": "npx",
"args": [
"@extension.dev/mcp",
"--features=local,platform"
]
}
}
}
```
Local tools only:
[Install with one click in Cursor](https://cursor.com/en/install-mcp?name=extension-dev&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyJAZXh0ZW5zaW9uLmRldi9tY3AiLCItLWZlYXR1cmVzPWxvY2FsIl19)
`.cursor/mcp.json`:
```json
{
"mcpServers": {
"extension-dev": {
"command": "npx",
"args": [
"@extension.dev/mcp",
"--features=local"
]
}
}
}
```
## VS Code
Local and platform tools:
```bash
code --add-mcp '{"name":"extension-dev","command":"npx","args":["@extension.dev/mcp","--features=local,platform"]}'
```
`.vscode/mcp.json`:
```json
{
"servers": {
"extension-dev": {
"type": "stdio",
"command": "npx",
"args": [
"@extension.dev/mcp",
"--features=local,platform"
]
}
}
}
```
Local tools only:
```bash
code --add-mcp '{"name":"extension-dev","command":"npx","args":["@extension.dev/mcp","--features=local"]}'
```
`.vscode/mcp.json`:
```json
{
"servers": {
"extension-dev": {
"type": "stdio",
"command": "npx",
"args": [
"@extension.dev/mcp",
"--features=local"
]
}
}
}
```
## Codex
Local and platform tools:
```bash
codex mcp add extension-dev -- npx @extension.dev/mcp --features=local,platform
```
`~/.codex/config.toml`:
```toml
[mcp_servers.extension-dev]
command = "npx"
args = ["@extension.dev/mcp", "--features=local,platform"]
```
Local tools only:
```bash
codex mcp add extension-dev -- npx @extension.dev/mcp --features=local
```
`~/.codex/config.toml`:
```toml
[mcp_servers.extension-dev]
command = "npx"
args = ["@extension.dev/mcp", "--features=local"]
```
## Other clients
Local and platform tools:
`.mcp.json`:
```json
{
"mcpServers": {
"extension-dev": {
"command": "npx",
"args": [
"@extension.dev/mcp",
"--features=local,platform"
]
}
}
}
```
Local tools only:
`.mcp.json`:
```json
{
"mcpServers": {
"extension-dev": {
"command": "npx",
"args": [
"@extension.dev/mcp",
"--features=local"
]
}
}
}
```
## After installing
Platform tools need a login once the server is started with the platform group:
```bash
npx @extension.dev/mcp login --project /
```
See [The platform lane](/tools/mcp/platform#login).
---
- [Start from a template](https://docs.extension.dev/guides/templates): The template catalog, what each page shows, and how a template becomes a repository.
# Start from a template
You want a working extension to start from, not an empty directory. A template
is a working extension: every one builds, runs, and loads in a browser before
you change a line.
[templates.extension.dev](https://templates.extension.dev) renders the whole
catalog, one card per template. Each template page carries a copyable local
CLI command, per browser downloads, and one click that turns the template into
a repository of your own.
## What a template page shows
| Route | Shows |
| --- | --- |
| `/` | The template running live in the browser emulator |
| `//instructions` | What the template does and how to work in it |
| `//source` | The real files, browsable in the page |
| `/a/` | A shared artifact, kept for 90 days |
The preview on the detail page is the template compiled and running, not a
screenshot of it.
## Turning one into a repository
One click on a template opens
`www.extension.dev/import?template=&private=true`, carrying the
repository name and description from the card. That page creates a real
private GitHub repository from the template and connects it as a project.
Configuration works signed out; creating the repository needs a GitHub
session, because the repository is written to your account. Creation is rate
limited to 20 repositories per hour per user, and the refusal answers `429`
with the seconds to wait.
See [Import](/guides/import) for what happens on that page.
## Working locally instead
The command on every template page runs the template on your machine:
```bash
npx extension@latest create my-extension --template init
```
That path is the Extension.js framework and needs no account. It is documented
at [extension.js.org](https://extension.js.org).
## What the catalog covers
| Group | Examples |
| --- | --- |
| Surfaces | Popup, options page, side panel, new tab, devtools |
| Content scripts | Isolated world, main world, multiple entries, CSS and Sass |
| Frameworks | React, Preact, Svelte, Vue, TypeScript, plain JavaScript |
| Styling | Tailwind, Sass, Less, CSS modules |
| AI | ChatGPT, Claude, Gemini, Perplexity, Transformers.js |
For a full-page surface instead of a popup, start from the
[New Tab template](https://templates.extension.dev/newtab).
## Where the templates come from
The corpus is not authored in the catalog app. It is pinned to a commit of
[extension-js/examples](https://github.com/extension-js/examples) and shipped
as a content-addressed artifact. A fidelity harness re-derives the catalog
from the pinned source and fails when the rendered listing and the real files
disagree, so a template that stops building cannot quietly stay in the
catalog.
## When it fails
- **Repository creation is refused with a rate limit.** The limit is 20
repository creations per hour per user. Wait, or use the local CLI command
on the template page in the meantime.
- **A fix made to a template does not last.** The next corpus sync overwrites
it. Fix the template in
[extension-js/examples](https://github.com/extension-js/examples), and the
next pin brings the fix into the catalog.
- **An `/a/` link returns nothing.** Shared artifacts are kept 90
days. Republish from the source that produced the artifact.
- **The create click asks for a sign-in.** Browsing and configuration work
signed out. Creating the repository writes to your GitHub account, so that
step needs a GitHub session.
## Next
- [Import](/guides/import) for what the template hands off to
- [Quickstart](/guides/quickstart) for the walk from a template to a shared build
## Related
- [Extension.js framework docs](https://extension.js.org): The free, open source framework the platform sponsors. It covers create, develop and build on your own machine, up to packaging.
- [Agent skill, installable by URL](https://docs.extension.dev/.well-known/skills/index.json): The @extension.dev/skill package served in the open Agent Skills layout, so `npx skills add https://docs.extension.dev` installs it once the site opens; until then the skill files hold with the fleet and `npx skills add extensiondev/skill` installs the same skill from GitHub.