# 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: 34 tools that scaffold, run, inspect, test and build an extension on your machine, and ship it through extension.dev. Reach for it when the agent should debug from evidence, instead of fixing a content script that never injected without noticing it never injected. ## Quick start ```bash claude mcp add extension-dev npx @extension.dev/mcp ``` Then ask the agent to run your extension: it starts a dev session and a browser opens with the extension loaded. The same server works with Claude Desktop, Cursor, VS Code, Codex and any MCP client, and every client's block is on [Install in your client](/tools/mcp/clients). ## 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 as any other client would. The agent and the CLI always behave the same way, and the agent never holds anything the CLI does not. ### Two tool groups Without `--features` the server starts with the `local` group alone, the 23 tools that work on this machine. The 11 platform tools (account, workspace, builds, share, release and store) come on with `--features=local,platform` or `EXTENSION_DEV_FEATURES=local,platform`: ```bash claude mcp add extension-dev -- npx @extension.dev/mcp --features=local,platform ``` Or install it 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`. The same shape works in any client configured by hand: ```json { "mcpServers": { "extension-dev": { "command": "npx", "args": ["@extension.dev/mcp", "--features=local,platform"] } } } ``` ### 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 this machine already runs, which is why they are on unless you turn them off. ### Engine pinning for scaffolds A project scaffolded by `extension_create` declares its Extension.js version in its own `package.json`. That project-local pin wins over the server's environment, so later sessions build with the version the scaffold declared. ### Pair with the skill The 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. ## Reference The server registers 34 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 11 platform tools come on with `--features=local,platform` and are described on [Platform tools](/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 the surface before adding an options page, a popup, a side panel, a content script, a devtools panel, a new tab page or a background script to 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_builds`](/tools/mcp/builds) | Read, rebuild and configure the platform's builds of the logged-in project on extension.dev. | 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, create 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`](/tools/mcp/workspace) | Read the extension.dev workspace a stored login belongs to: its plan and limits, its projects, and its usage. | reads | | [`extension_workspace_create`](/tools/mcp/workspace-create) | Create an extension.dev workspace that does not exist yet, without opening the console. | acts | 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 [Run on Safari](/tools/mcp/safari). ## 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), [Platform tools](/tools/mcp/platform), [Run on Safari](/tools/mcp/safari) - [Pair with 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 decide which MCP tools an agent sees, the approvals that wait for a person, the fence around page-written text, and the one telemetry event. # Server flags and telemetry The MCP server is one command with three flags, and each flag has an environment variable twin. Reach for them when you want to limit what an agent can list, ship or act on, or when you want the one telemetry event off. ## Quick start ```json { "mcpServers": { "extension-dev": { "command": "npx", "args": ["@extension.dev/mcp", "--features=local,platform", "--no-ship"] } } } ``` This config lists all 34 tools and refuses every call that puts a build in front of other people. The [session gates](/tools/mcp#session-gates) still decide what a listed tool may do. ## How it works `--features` picks the tool groups. The default is `local`, the 23 tools that work on this machine. `--features=local,platform` adds the 11 platform tools (account, workspace, builds, share, release and store), and `--features=platform` exposes only that group. `--no-ship` refuses the calls that put something in front of other people: `extension_publish`, every `extension_release_promote` action, `extension_submit` with `dryRun: false` and its `cancel` and `rollout` actions, `extension_preview_web` with `share: true`, and a share create or revoke. Dry runs, preflight, share listing, reads, login, and workspace or project creation still work. `--project /` pins the server to one project, however many logins the machine holds. A call that names another project is refused. A refused call answers `E_TOOL_DISABLED` and names the flag to change. A share revoke waits for a person by default. The first call answers `approval-required` with a link on extension.dev. A workspace member approves exactly that revoke, and the same call with the returned `approvalId` then runs once. Seven more acts always wait for a workspace owner, whatever the flags say: a promotion, a real store submission, a build settings change, a release cut, a rollback, withdrawing a submission and raising a staged rollout. See [Use the platform from your agent](/tools/mcp/platform-from-your-agent). Every tool carries the MCP annotations `readOnlyHint`, `destructiveHint`, `idempotentHint` and `openWorldHint`, so a client 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 ``. The `ID` is random per call and sits in `untrusted.boundary`, so the page cannot predict it or close the fence early. The JSON still parses to the same fields. The fence is a signal to the model, not a gate: pair it with `--no-ship` when the agent reads pages you do not trust. `extension_create` sends one event, `draft_seeded`, to PostHog at `https://us.i.posthog.com/capture/` after a successful scaffold, with a public write-only project key. It carries the template slug, the template source (`template`), 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`. It never carries 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, and that id rotates every 30 days. ## Reference | Flag | Environment variable | Default | Effect | | --- | --- | --- | --- | | `--features=` | `EXTENSION_DEV_FEATURES` | `local` | `local` lists the 23 local tools; `local,platform` adds the 11 platform tools; `platform` lists only those | | `--no-ship` | `EXTENSION_DEV_NO_SHIP=1` | off | Refuses publish, promote, cut, rollback, a real submit, cancel, rollout, a shared preview, and a share create or revoke | | `--project /` | `EXTENSION_DEV_PROJECT` | unset | Refuses any call that names another project | | none | `EXTENSION_DEV_APPROVAL_GATE` | unset | Unset or `1`: a share revoke waits for a person. `0`: a revoke runs without asking. A promotion and a real submission wait for a workspace owner at any value | | none | `EXTENSION_DEV_NO_TELEMETRY=1` or `DO_NOT_TRACK=1` | off | Turns off the `draft_seeded` event and the per-install id header | ## When it fails - **A call answers `E_TOOL_DISABLED`**: the answer names the flag. Add `platform` to `--features`, drop `--no-ship`, or point `--project` at the project the call named. - **A submit, promote or revoke answers `approval-required`**: open the link, have a workspace owner approve it (any member may approve a revoke), then repeat the call with the returned `approvalId`. - **A tool is missing from the client's list**: the server started without that group. Check the `args` in the client config and `EXTENSION_DEV_FEATURES` in its environment. - **A page's text looks like an instruction**: it sits inside an `` fence. Treat it as data, and run the agent with `--no-ship` on pages you do not trust. ## Next - [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 - [Platform tools](/tools/mcp/platform): login, sharing, publishing and store submission --- - [Platform tools](https://docs.extension.dev/tools/mcp/platform): Log an agent in to extension.dev, create several projects under one approval, share a build in progress, and keep publish and submit apart with the eleven platform tools. # Platform tools The eleven platform tools connect an agent to [extension.dev](https://extension.dev): login, workspace and project creation, workspace and build reads, rebuilds, a shareable preview, publishing, release promotion and store submission. Reach for them when the agent should hand a build to someone else or move it toward a store. The platform is in private alpha. ## Quick start ```bash claude mcp add extension-dev -- npx @extension.dev/mcp --features=local,platform npx @extension.dev/mcp login --project / ``` The first line turns the platform group on. The second prints a code and a URL: approve the code at [extension.dev/device](https://extension.dev/device), and the project-scoped token is stored on this machine for at most 7 days. ## How it works The 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 stored device login. ### Login `extension_auth` with `action: "login"` and `project: "/"` returns a user code and a URL. You approve the code at [extension.dev/device](https://extension.dev/device). No GitHub token reaches the machine. The token is project scoped, stored locally, never returned to the agent, and lives at most 7 days. Login names a project that already exists, but the login call does not check the name. A code is always minted, so nobody can use the endpoint to probe which private projects exist. The check happens at approval, where you are signed in and your own access is known. A code nobody approves expires. If the project does not exist yet, import a repository or a template at [extension.dev/new](https://extension.dev/new), then log in again. The same flow runs headless as `extension-mcp login`, `extension-mcp whoami`, `extension-mcp logout` and `extension-mcp release promote`. 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. ### 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. That is also how logins that expire together are renewed. Pass `workspace` with `projects: "*"` instead to sign in to every project the workspace has, at most 20; see [Use the platform from your agent](/tools/mcp/platform-from-your-agent). Both lists hold one workspace and 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 rather than 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, and the bytes run in an isolated sandbox origin or not at all. 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 and 365 on Pro. The answer carries its exact `expiresAt` and a `revokeUrl`. A revoke is permanent. Every share is appended to `.extension.dev/shared-previews.json` in the project, and the server adds that folder to `.gitignore`. 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`. It revokes one by `artifactId` or any of its URLs, and with `projectPath` it reconciles against the project's own record (`remoteOnly`, `localOnly`) without rewriting it. ### Publish is not submit `extension_publish` answers with the project's build page on its workspace host: the canonical [public page](/share/public-pages) for a public project, or 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 above is the other verb: it uploads the local build ([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. A real submission is not reversible from the tool, and 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 you let an agent submit. Safari submission needs a paid plan. A free workspace that names `safari` is refused, 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. ## Reference | Tool | What it does | Needs | | --- | --- | --- | | [`extension_auth`](/tools/mcp/auth) | Logs this machine in, reports the login, or clears it | A person approving the code | | [`extension_workspace_create`](/tools/mcp/workspace-create) | Creates a workspace without the console | A person approving the code | | [`extension_project_create`](/tools/mcp/project-create) | Creates 1 to 10 projects under one approval | A person approving the code | | [`extension_preview_web`](/tools/mcp/preview-web) | Previews the build in the emulator; `share: true` uploads it and mints a link | Login for `share: true`; up to 2,000 files and about 64MB of text, roughly 48MB of images, fonts or wasm | | [`extension_shares`](/tools/mcp/shares) | Lists and revokes shared links | Login; a revoke waits for approval by default | | [`extension_publish`](/tools/mcp/publish) | Returns the project's build page URL; `ttlHours` 1 to 168 for a private project | Login | | [`extension_release_promote`](/tools/mcp/release-promote) | Points a release channel at a build | A login approved by an owner; every promote waits for an owner's approval | | [`extension_release_status`](/tools/mcp/release-status) | Reads channels, submissions and store health | Login | | [`extension_workspace`](/tools/mcp/workspace) | Reads the workspace's plan, projects, usage and a project's settings | Login of any project in the workspace | | [`extension_builds`](/tools/mcp/builds) | Reads builds and their logs, rebuilds, changes build settings, adds a channel | Login; a settings change waits for an owner's approval | | [`extension_submit`](/tools/mcp/submit) | Dispatches a store submission; dry run by default | Login; `dryRun: false` needs the owner's token and waits for an owner's approval | ## When it fails - **A platform tool returns an auth error**: the stored 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. - **The tool is not listed**: the server started without the platform group. Add `--features=local,platform` to its arguments. - **A share upload is refused as too large**: the cap is 2,000 files and about 64MB of text. Trim bundled assets, or build without large source maps. - **A create list is refused**: it named more than 10 projects, two workspaces, a slug twice, or a project outside the server's `--project` pin. Fix the list, because the server never splits it. - **A submit naming `safari` is refused on a free workspace**: the refusal covers the whole call. Drop `safari` from `browsers`, or move the workspace to a paid plan. ## Next - [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 - [Use the platform from your agent](/tools/mcp/platform-from-your-agent): what a login reads and does, and which acts wait for an owner - [Access tokens](/tools/access-tokens) and [Publish from the CLI](/tools/publish) --- - [Run on Safari](https://docs.extension.dev/tools/mcp/safari): Start a Safari dev session from an agent, turn on the one Safari setting it needs, and see which MCP tools work there and which Extension.js version each one needs. # Run on Safari A Safari dev session runs on the same local bridge as every other browser, so an agent can build, open, reload and read your extension there. Reach for it when you develop on macOS with Xcode and want the Safari build checked while you edit. ## Quick start With a project open, ask the agent: ``` Run my extension in Safari ``` The agent calls `extension_dev` with `browser: "safari"`. The build converts your extension into a macOS app, signs it with `xcodebuild`, and opens it. Turn the extension on once in Safari > Settings > Extensions. From then on every save reloads it, and its background and content lines land in the session log. To let the agent read pages too, run `safaridriver --enable` once and turn on Safari > Settings > Developer > "Allow remote automation" (Safari > Develop > Allow Remote Automation on older Safari). ## How it works The session needs Extension.js 4.1.21 or newer in the project. On 4.1.33 or newer the run opens the app in the background, leaves your own Safari alone, and quits only that app on stop. The control channel is on by default, so the bridge tools work as soon as the extension is enabled: `extension_storage`, `extension_reload`, `extension_open` for surfaces, `extension_dom_snapshot` by tab id, `extension_logs`, and the assertions. Safari has no CDP or RDP, and its MV3 background CSP blocks eval. So `extension_eval` in `background` fails, and `extension_inspect` has no Safari path. Read the background through its log lines instead. Pages ride a safaridriver automation session. On Extension.js 4.1.32 or newer, with remote automation on, the dev session opens one and records `webdriverPort` and `webdriverSessionId` in `ready.json`. `extension_open` with `url` and `extension_eval` with `context: "page"` use it. `extension_doctor` reports it as the `safari-window` check. Without that session, `extension_open` with `url` rides the bridge's navigate verb (Extension.js 4.1.29 or newer), and `extension_eval` in `content` or `page` needs a tab already open at the url. Apple ships its own MCP server as `safaridriver --mcp` (Safari 27, or Safari Technology Preview 247 and later). It reads pages in an isolated automation window, has no extension-aware tool, and runs beside a dev session. `extension_browsers` reports whether your safaridriver has `--mcp`. ## Reference | Tool | On Safari | Extension.js | | --- | --- | --- | | `extension_dev` with `browser: "safari"` | Builds the app, opens it, reloads on every save, streams background and content logs | 4.1.21 or newer | | `extension_logs` | Works once the extension is enabled | 4.1.21 or newer | | `extension_storage`, `extension_reload` | Work over the bridge | 4.1.21 or newer | | `extension_open` with `surface` | Opens the extension's surfaces; the `devtools` surface is refused | 4.1.21 or newer | | `extension_dom_snapshot` | Works by tab id | 4.1.21 or newer | | `extension_assert` | `content-script-injected`, `background-worker-booted`, `storage-key-present` and `console-errors-empty` work; `surface-rendered` reads through the relay and can pass or fail | 4.1.21 or newer | | `extension_open` with `url` | Over the safaridriver session; without one, over the bridge's navigate verb | 4.1.32 (safaridriver), 4.1.29 (navigate) | | `extension_eval` with `context: "page"` | Over the safaridriver session; without one, the tab must already be open at the url | 4.1.32 or newer | | `extension_eval` with `context: "content"` | Needs a tab already open at the url | 4.1.21 or newer | | `extension_eval` with `context: "background"` | Fails: Safari's MV3 background CSP blocks eval | none | | `extension_inspect` | No Safari path: Safari has no CDP or RDP | none | | `extension_doctor` | Adds the `safari-window` check: `pass`, `fail` when the window is gone, `skip` with the engine's reason when none was opened | 4.1.32 or newer | | `extension_start` | Not supported: the framework's `start` and `preview` commands refuse Safari targets | none | ## When it fails - **`extension_logs` answers `E_LOGS_MISSING`**: the extension is not enabled yet, or the project runs an engine older than 4.1.21. Turn it on in Safari > Settings > Extensions, check the project's `extension` version, then read again. - **`extension_doctor` shows `safari-window` as `skip`**: the dev session opened no safaridriver session, and the detail carries the engine's reason. Run `safaridriver --enable`, turn on remote automation, then restart `extension_dev`. Page eval and open by url fall back to the bridge until then. - **`safari-window` is `fail`**: the automation window or the driver is gone. Stop and restart `extension_dev` with `browser: "safari"`. - **`extension_eval` in `background` fails**: Safari's CSP blocks it. Have the background write one line on start and assert `background-worker-booted`, or read `extension_logs`. - **The extension is enabled but a content script does nothing**: Safari grants website access separately from the enable switch. Allow the site under Permissions in the same Extensions panel. - **`extension_start` refuses the Safari target**: the framework does not run `start` or `preview` for Safari. Use `extension_dev` for the loop and `extension_build` for the app. ## Next - [Safari guide](https://extension.js.org/docs/browsers/safari) on extension.js.org: requirements, signing and the enable step - [`extension_dev`](/tools/mcp/dev), [`extension_doctor`](/tools/mcp/doctor) and [`extension_browsers`](/tools/mcp/browsers) - [MCP server](/tools/mcp): install and the session gates - [Publishing](/publish/overview) when the build is ready for the stores --- - [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 `@extension.dev/skill` gives a coding agent judgment for extension work: when to reach for which tool, the cross-browser rules, the silent-failure gotchas and the publish checklist. Reach for it beside the [MCP server](/tools/mcp), which gives the agent hands. The package ships one skill in the open [Agent Skills](https://agentskills.io) format, plain markdown with no login and no network, so any agent that reads the format can use it. ## Quick start ```bash npx skills add extensiondev/skill ``` The command installs the skill 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. ## Install Every path installs the same skill. Pick the one that fits your setup. 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. ## How it works `SKILL.md` carries the trigger metadata, a seven step workflow and the core rules. 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 workflow: 1. Start from a [template](/templates/), or migrate an existing build. 2. Treat `manifest.json` as the source of truth. 3. Write cross-browser by default with `chromium:` and `firefox:` prefixes. 4. Develop with feedback, never faith, by reading logs and the live DOM. 5. Build and verify per browser, then stop the session. 6. Show someone the build as a link, not a file. 7. Keep publish and submit apart. The skill is `@extension.dev/skill` 1.1.0. `SKILL.md` is 305 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 | 362 | | `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`) | 60 | | `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 6 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. | | `migrate-entrypoints` | This browser extension (in the input directory) builds with a toolchain I want to leave. | | `migrate-file-conventions` | This browser extension (in the input directory) builds with a toolchain I want to leave. | 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. 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? ``` ## Reference 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 calls them, reads a plausible result, and moves 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 - [Pair with Chrome DevTools MCP](/tools/chrome-devtools-mcp) for the second server it knows about --- - [Pair with Chrome DevTools MCP](https://docs.extension.dev/tools/chrome-devtools-mcp): Run Google's chrome-devtools-mcp and the extension.dev MCP server in one agent config, and know which one to reach for. # Pair with Chrome DevTools MCP [chrome-devtools-mcp](https://github.com/ChromeDevTools/chrome-devtools-mcp) gives an agent raw control of a live Chrome, and its extension tools (`install_extension`, `list_extensions`, `reload_extension`, `trigger_extension_action`, `uninstall_extension`) load and poke a built extension. Reach for both servers at once: let chrome-devtools-mcp drive Chrome itself, and let [`@extension.dev/mcp`](/tools/mcp) own everything that depends on the project, because it wraps an [Extension.js](https://extension.js.org) dev session instead of a bare browser. ## Quick start ```bash claude mcp add extension-dev npx @extension.dev/mcp claude mcp add chrome-devtools -- npx chrome-devtools-mcp@latest --categoryExtensions ``` The `--` hands `--categoryExtensions` to the server. Without it, `claude` parses the flag as its own. The extension.dev tools are namespaced `extension_*`, so the two servers never collide. ::: code-group ```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"] } } } ``` ::: ## How it works The two servers split the work by what a task touches. If it 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. chrome-devtools-mcp triggers an action over the DevTools protocol (`Extensions.triggerAction`), which Chrome runs as a user action on the toolbar button, the same path as a click: the handler gets a user gesture and the `activeTab` grant, and the selected page must be a tab in a browser window. Extension.js takes a different route: it captures the extension's `chrome.action.onClicked` and `chrome.commands.onCommand` listeners at install time and replays them on demand. Triggering becomes scriptable and reproducible, and because it only touches `addListener` it works on both Chromium and Firefox. A single `background.service_worker` source works on Firefox too, because 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` and satisfies gesture-gated APIs (`chrome.permissions.request`, interactive `identity.getAuthToken`). A replay does not. A handler that relies on `activeTab` (`chrome.scripting.executeScript` on the active tab, or `captureVisibleTab`) behaves differently than it does on a real click. The result carries `gesture: false`, and an action replay also carries a `warning` when the manifest declares `activeTab`. When you need click fidelity, use chrome-devtools-mcp's `trigger_extension_action`. ::: The replay wraps live only in the dev build. A production build carries no reload client and no wrap, and the `addListener` wraps delegate to the originals, so the extension behaves the same with or without the bridge. The framework side is documented at [extension.js.org/docs/debugging/trigger-actions-and-commands](https://extension.js.org/docs/debugging/trigger-actions-and-commands). ::: 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 the dev session launched, so its extension tools work today. ::: ## Reference | Task | Use | | --- | --- | | Build, dev server, hot reload, production packaging | extension.dev (`extension_dev`, `extension_build`) | | Validate the manifest across browsers before a build | 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 the extensions loaded 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`) | 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` | `extension_dev` loads a real build with hot reload; `extension_start` runs the production build, or a prebuilt folder with `outputPath` | | `list_extensions` | `extension_list_extensions` | Read-only, on Chromium and Firefox; live contexts on Chromium only | | `reload_extension` | `extension_reload` | Reloads the background or a tab, and waits for the new background to reattach | | `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; chrome-devtools-mcp has no equivalent | | `uninstall_extension` | `extension_stop` | The dev session owns load and unload | | console messages filtered by service worker | `extension_logs` | One ordered timeline across every context, with `follow` | ## When it fails - **A replayed handler does nothing on the active tab**: it depends on `activeTab`, which a replay never grants. Read `gesture: false` and the `warning` in the result, then trigger it with chrome-devtools-mcp's `trigger_extension_action`. That path is Chromium only. - **chrome-devtools-mcp lists no extension tools**: the category is off by default. Start it with `--categoryExtensions`, and in Claude Code put `--` before `npx` so the flag reaches the server. - **The extension tools fail against a browser you attached to**: `--categoryExtensions` needs a pipe connection, and `browserUrl` or `wsEndpoint` are not supported for it before Chrome 149. Let chrome-devtools-mcp launch its own Chrome. - **The same name answers from both servers**: it cannot. The extension.dev tools are all `extension_*`, and a `trigger_extension_action` call is chrome-devtools-mcp's. ## 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 Extension.js command line, the one command that touches the platform, and the agent-facing package beside it. # CLI `extension` is the Extension.js command line: it creates, runs, debugs and builds an extension on your machine with no account. Reach for it in a terminal or a CI job. An agent reaches the same engine through [`@extension.dev/mcp`](/tools/mcp). ## Quick start ```bash npx extension@latest create my-extension --template=init cd my-extension npm install npm run dev ``` A browser opens with the extension loaded, and every save reloads it. Full documentation lives at [extension.js.org](https://extension.js.org). ## How it works Two packages, two jobs. `extension` creates, runs, debugs and builds an extension on your machine. `@extension.dev/mcp` gives a coding agent the same verbs plus the platform ones, and ships the `extension-mcp` headless commands. The command surface is `create`, `dev`, `start`, `preview`, `build`, `logs`, `doctor`, `capabilities`, `install`, `uninstall`, `eval`, `storage`, `reload`, `inspect`, `navigate`, `open`, `publish` and `telemetry`. The framework's [commands reference](https://extension.js.org/docs/commands/create) documents each one, and this site does not restate them. One command touches the platform: `extension publish` asks it for the project's build page URL. It uploads nothing. A public project answers with its public page, and 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 longer clock set by your plan, is a different verb and an upload: [Share an unpublished build for review](/share/unpublished-build-for-review). `publish` reads its token from three places, in this order: the `--token` flag, `EXTENSION_DEV_TOKEN` in the environment, then the login stored by `npx @extension.dev/mcp login` at `~/.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. `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. Store submission and release promotion are platform actions, not framework commands. 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). 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). ## Reference | Command | Needs | | --- | --- | | `create`, `dev`, `start`, `preview`, `build`, `logs`, `doctor`, `capabilities`, `install`, `uninstall`, `telemetry` | Nothing: no account, no session flag | | `storage`, `reload`, `inspect`, `navigate`, `open` | A dev session started with `--allow-control` | | `eval` | A dev session started with `--allow-eval`, which implies control | | `publish` | A token (`--token`, `EXTENSION_DEV_TOKEN`, or the stored login) and a platform address (`--api` or `EXTENSION_DEV_API_URL`) | | `publish` flag | Values | | --- | --- | | `--ttl ` | 1 to 168, default 24, private projects only | | `--project ` | Publishes the stored login's project on purpose when the folder name differs | | `--api ` | The platform base URL, or set `EXTENSION_DEV_API_URL` | ## 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 happens with a token 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` or `open` refuse**: they act on a live dev session, and the gate is a flag on the session, not on the verb. Start `extension dev --allow-control` (or `--allow-eval` for `eval`), 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. 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 - [MCP server](/tools/mcp) for the same engine from an agent --- - [Agent bridge](https://docs.extension.dev/tools/agent-bridge): The local channel that lets a terminal, a script or an AI agent observe a running extension and, per session, act on it. # Agent bridge The agent bridge is the Extension.js channel between a running extension and whoever is watching it: a terminal, a script, or an AI agent. Reach for it when you want to read every extension context from outside the browser, or act on the extension without clicking through it yourself. ## Quick start ```bash npx extension@latest dev --allow-control ``` When `extension dev` runs, the framework loads a companion beside your extension and opens the channel on `127.0.0.1`. Observation is always on, and `--allow-control` lets this session act as well. ## How it works The companion lives only in the dev build. It connects every extension context (service worker, content scripts, popup, options, sidebar, devtools and the override pages) to the dev server, so one client sees them all in order. Reading is free. Anything that changes state is opt-in per session: `--allow-control` unlocks storage, reload and open, and `--allow-eval` unlocks `eval`, implies control, and writes a `0600` session token. A refusal names the exact flag to restart with. Everything stays on your machine. The channel binds to `127.0.0.1`, needs no account, and sends nothing off the machine. The data plane, the addressing model and the limits are documented in the framework's [Debugging section](https://extension.js.org/docs/debugging). The [MCP server](/tools/mcp) reaches the same channel from an agent, so the agent and the CLI see the same thing. The platform stores none of it. ## Reference | MCP tool | Transport | | --- | --- | | `extension_logs`, `extension_storage`, `extension_reload`, `extension_open`, `extension_dom_snapshot` | The bridge, as any other client | | `extension_eval` | The bridge where the extension's content security policy allows it; the browser's debugger protocol where it does not | | `extension_inspect` | The browser's debugger protocol on Chromium; the bridge on Firefox | ## When it fails - **An act command refuses and names a flag**: the session was started without it. Restart `extension dev` with `--allow-control`, or `--allow-eval` for `eval`. - **`extension_eval` answers `eval-disabled`**: no route of that tool runs without the session token. Start the session with `allowEval: true` from the agent, or `--allow-eval` from the terminal. ## 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, written to a local file and readable from the terminal or an agent. # Agent logs Agent logs are the one ordered stream Extension.js writes while `extension dev` runs: every context of the extension, in sequence, in a local file. Reach for them when you need to know what the service worker, a content script and the popup did, in the order they did it, without opening four consoles. ## Quick start ```bash npx extension@latest logs ``` Run it beside a dev session to print the most recent lines, or add `--follow` to keep streaming. The file behind it is `dist/extension-js//logs.ndjson`. ## How it works The [agent bridge](/tools/agent-bridge) merges every context into one timeline: `background`, `content`, `page`, `popup`, `options`, `sidebar`, `devtools`, `newtab`, `history` and `bookmarks`. The session appends each event to `logs.ndjson` under `dist/extension-js//`. A one-shot read needs no channel and no unlock. It is local, needs no account, and the file never leaves your machine. 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. When the writer rotates the file, `extension logs` announces it on stderr and names the sibling the earlier events moved to. The [MCP server](/tools/mcp)'s `extension_logs` reads the same stream from an agent. It is bounded by default, so it does not flood the agent's context: a one-shot returns the most recent matching lines, and `follow` collects for a bounded window. There is no platform log retention, and logs are never uploaded. ## Reference | `extension logs` flag | Effect | | --- | --- | | `--follow` | Keeps streaming instead of returning the most recent lines | | `--context ` | Only these contexts, from the ten above | | `--since ` | Only events after a sequence number or a timestamp | | `--url ` | Only events from documents at a matching url | | `--tab ` | Only events from one tab | | `extension_logs` default | Value | | --- | --- | | Lines per one-shot read | 200 | | `follow` window | 4000 ms | ## When it fails - **`extension logs` finds no file**: no dev session has written one for that browser. Start `extension dev` for that browser, then read again. - **`extension_logs` answers `E_LOGS_MISSING` on Safari**: the extension is not enabled yet, or the project runs an engine older than 4.1.21. Turn it on in Safari > Settings > Extensions, then read again. See [Run on Safari](/tools/mcp/safari). - **The stream announces a rotation**: the earlier events of this session live in the sibling file it names. Read that file for the lines before the rotation. ## 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 extension context you care about, on Chrome and Firefox. # Inspect and act Inspect and act are the Extension.js commands that read a running extension and change it from outside the browser: `inspect` reads the live, post-injection DOM of any surface, and `eval`, `storage`, `reload` and `open` run inside the extension's own context. Reach for them when "it should work" needs proof, on Chrome and Firefox, with no account. ## Quick start ```bash npx extension@latest dev --allow-control npx extension@latest inspect --context content --url example.com ``` The first command starts a session that may act. The second reads the DOM your content script produced on the matching tab. Add `--allow-eval` to the first line to unlock `eval` as well. ## How it works Every command goes through the [agent bridge](/tools/agent-bridge), the in-browser companion the dev session loads, not the Chrome DevTools Protocol. That is why the same loop reaches your own surfaces on both Chrome and Firefox. `inspect` addresses `content`, `page`, or any open surface: `popup`, `options`, `sidebar`, `devtools`, `newtab`, `history` and `bookmarks`. `--url` picks the tab by glob or substring, and `--tab` picks one by id. Acting is gated per session, not per verb. `storage`, `reload`, `inspect`, `navigate` and `open` need a session started with `--allow-control`. `eval` needs `--allow-eval`, which implies control and writes a `0600` session token outside `dist/`. A refusal names the exact flag to restart with. The [MCP server](/tools/mcp) reaches the same machinery from an agent under the same gates, which `extension_dev` names `allowControl` and `allowEval`. Nothing the agent reads or touches here leaves your machine. The addressing model, the safety gates and every flag live in the framework's [Debugging section](https://extension.js.org/docs/debugging). ## Reference | CLI command | MCP tool | Needs | | --- | --- | --- | | `extension inspect` | `extension_inspect` | CLI: `--allow-control`. MCP: the browser's debugger protocol on Chromium, the bridge on Firefox | | none | `extension_dom_snapshot` | `allowControl` | | `extension storage` | `extension_storage` | `--allow-control` / `allowControl` | | `extension reload` | `extension_reload` | `--allow-control` / `allowControl` | | `extension open` | `extension_open` | `--allow-control` / `allowControl` | | `extension navigate` | `extension_open` with `url` | `--allow-control` / `allowControl` | | `extension eval` | `extension_eval` | `--allow-eval` / `allowEval`, which implies control | ## When it fails - **A command refuses and names `--allow-control` or `--allow-eval`**: the running session lacks that unlock. Restart `extension dev` with the named flag, then retry. - **`extension_eval` answers `eval-disabled`**: the session was started without `allowEval: true`, and no route of the tool runs without that token. Call `extension_dev` again with `allowEval: true` and `replace: true`. - **`inspect` finds no document for a surface**: the surface is not open. Open it first with `extension open options` (or `extension_open`), then inspect it. - **`extension open popup` refuses on Chromium**: Chromium opens a popup only in response to a user gesture. Click the toolbar button, or replay the click handler with `extension open action` on an action without a popup. ## 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 the surface before adding an options page, a popup, a side panel, a content script, a devtools panel, a new tab page or a background script to an existing extension." # `extension_add_feature` Plan the surface before adding an options page, a popup, a side panel, a content script, a devtools panel, a new tab page or a background script to an existing extension. ## Quick start > Add an options page to this extension Asked to add an options page, the agent plans the surface, writes it, validates the manifest and proves it in a dev session. ## How the agent uses it It answers with the manifest additions, the files to create and the catalog template to copy from, and modifies nothing: write what the plan says, then validate the manifest and run extension_dev. ## Reference | | | | --- | --- | | 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"` | | ## Next - [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. ## Quick start > What is in the built extension and is anything too big for the store? Asked what ended up in the bundle, the agent builds and reads the output size by size. ## How the agent uses it 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. ## Reference | | | | --- | --- | | 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"` | | ## Next - [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. ## Quick start > Check that the worker booted, the popup rendered and the console is clean Asked whether the extension works, the agent states expectations and reads one verdict each. ## How the agent uses it 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. ## Page text in the answer 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). ## Reference | | | | --- | --- | | 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. | ## When it fails - **The call is refused because no session is live.** Start one with [`extension_dev`](/tools/mcp/dev) or [`extension_start`](/tools/mcp/start) and call again. ## Next - [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. ## Quick start > Log this machine in to the acme/newtab project on extension.dev ## How the agent uses 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 server and workspace/project, and status lists them all under `logins`; a call uses the login minted on the server it targets (`api`, else EXTENSION_DEV_API_URL, else production), so a local dev server login never replaces or stands in for a production one. To sign in to several existing projects of one workspace at once, pass `projects` instead of `project`: one approval, one stored token per project. To sign in to every project a workspace has, pass `workspace` with projects:"*": the approval page lists them and the answer names each project signed in. ## Reference | | | | --- | --- | | 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 \| 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. Or pass the string "*" with `workspace` to sign in to every project that workspace has when the approver opens the page (at most 20); the answer names each project signed in. | | `workspace` | string | no | | login with projects: "*": the workspace slug whose every project to sign in to, with one approval. Only a project of this workspace is ever stored. A platform that does not offer it is refused before any code is spent. | | `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) | ## When it fails - **The tool is missing from the client's list.** Start the server with `--features=local,platform`; see [Platform tools](/tools/mcp/platform). - **The answer reads `status: "platform-held"`.** extension.dev is not open to the public yet, so nothing ran on its machines. Creating, running and building an extension still work on your machine; see [Use the platform from your agent](/tools/mcp/platform-from-your-agent). - **A login for every project is refused at approval.** The workspace has more than 20 projects, or none. Name up to 20 projects in `projects` instead. - **The answer says the platform does not offer signing in to every project.** The server you target predates `projects: "*"`. Pass the project names in `projects`. - **A login stops working before its 7 days are up, as `MinterNotMember`.** The account that approved it left the workspace. Sign in again as a current member. - **The answer depends on the network.** This tool reaches the open web, so a refusal can be the network, not the project. ## Next - [MCP server](/tools/mcp): install, the two groups and the session gates - [Install in your client](/tools/mcp/clients) - [`extension_builds`](/tools/mcp/builds): Read, rebuild and configure the platform's builds of the logged-in project on extension.dev. - [`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, create 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`](/tools/mcp/workspace): Read the extension.dev workspace a stored login belongs to: its plan and limits, its projects, and its usage. - [`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. ## Quick start > Which browsers are installed here? Install Firefox if it is missing Asked which browsers the machine can run, the agent lists them with what each one supports. ## How the agent uses it 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 each binary's version and size on disk. Both report the version the binary itself carries (its bundle metadata, else --version), prerelease suffix included (159.0a1), never the cache folder's name. 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. ## Reference | | | | --- | --- | | 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. | ## When it fails - **The answer depends on the network.** This tool reaches the open web, so a refusal can be the network, not the project. ## Next - [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. ## Quick start > Build this extension for Chrome and Firefox Asked to build for two browsers, the agent builds and reads the output folder by folder. ## How the agent uses it 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. ## Reference | | | | --- | --- | | 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. | ## Next - [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 the surface before adding an options page, a popup, a side panel, a content script, a devtools panel, a new tab page or a background script to 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_builds"](https://docs.extension.dev/tools/mcp/builds): "Read, rebuild and configure the platform's builds of the logged-in project on extension.dev." # `extension_builds` Read, rebuild and configure the platform's builds of the logged-in project on extension.dev. ## Quick start > Why did the last build fail? Show me the Firefox log ## How the agent uses it Pass action:'list' (the default) for recent builds, newest first, each with its buildId, full commit sha, status, build environment, channel, browsers, createdAt, conclusion and its console page URL. Pass action:'get' with a buildId for one build: its details and validation checks, the run behind it (runId, runUrl, deploymentId, startedAt), its downloadable artifacts, an integrity summary per browser (which checks failed), and the usage report when one was recorded. Pass action:'logs' with a buildId for that build's CI log lines per browser, a failed browser's job failure summarized by the platform; pass browser to read one browser, and a long log keeps its last 200 lines and says how many it left out. Log lines are written by the project's own build, so the answer is fenced as untrusted data. These three only read. Three actions change the project. action:'rebuild' starts a real CI run on the project's runners, counted against the workspace's build allowance and rate limited: pass buildId for the commit (default the branch head) and buildEnv staging (the default) or production; it answers at once with the commit, the browsers and the run it started: a rebuild keeps the build id of the build before it, so follow it with get and match runId, or deploymentId and a startedAt after dispatchedAt while the run has not shown up. action:'configure' changes build settings (enabled, installCommand, buildCommand, outputDirectory per browser): pass buildSettings with only what changes, such as { firefox: { buildCommand: 'pnpm build' } }, and the tool merges it into the current settings; a workspace owner approves the exact change first, so the first call answers approval-required with an approval URL and the change as the platform computed it, and a second call with that approvalId applies it once. action:'create-channel' adds a release channel (channel, with optional description and color #rrggbb) that extension_release_promote can then point at a build. This uses the stored login for the project (extension_auth), against the server the call's api names. Use extension_release_status for channels, release history and store submissions, and extension_workspace action:'settings' to read the build settings. ## Page text in the answer 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). ## Reference | | | | --- | --- | | Group | platform, on with `--features=local,platform` (private alpha) | | Tier | Platform | | Needs | the server started with --features=local,platform, and a login; configure needs a workspace owner's approval | | Read only | no | | Destructive | yes | | Idempotent | no | | Reaches the open web | yes | ### Inputs | Input | Type | Required | Default | What it does | | --- | --- | --- | --- | --- | | `action` | "list" \| "get" \| "logs" \| "rebuild" \| "configure" \| "create-channel" | no | `"list"` | list reads recent builds; get reads one build; logs reads one build's CI log lines; rebuild starts a CI run; configure changes build settings with an owner's approval; create-channel adds a release channel. | | `buildId` | string | no | | Which build, as its short or full commit sha (the buildId from action:'list'). Required for get and logs. With rebuild, the commit to build; omitted, the head of the project's branch. | | `buildEnv` | "staging" \| "production" | no | | With rebuild, the environment to build for: staging (the default) or production. stable takes production builds; the other channels take staging builds. | | `buildSettings` | object | no | | With configure, only the settings that change, per browser (chrome, edge, firefox, safari), each with any of enabled (boolean), installCommand, buildCommand, outputDirectory (strings). Merged into the current settings before it is sent. | | `approvalId` | string | no | | With configure, the approval id from a prior approval-required answer, once a workspace owner approved it. Each approval applies its exact change once. | | `channel` | string | no | | With create-channel, the new channel's name (up to 40 characters). | | `description` | string | no | | With create-channel, an optional description of the channel (up to 200 characters). | | `color` | string | no | | With create-channel, an optional color as #rrggbb. | | `browser` | string | no | | With logs, read only this browser's lines (chrome, firefox, edge, safari). With get, summarize integrity for this browser only. | | `limit` | number | no | | How many builds list returns, 1 to 50 (default 10). | | `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. | | `api` | string | no | | Platform base URL (default EXTENSION_DEV_API_URL, else https://www.extension.dev) | ## When it fails - **The tool is missing from the client's list.** Start the server with `--features=local,platform`; see [Platform tools](/tools/mcp/platform). - **The platform refuses the call.** Log in first with [`extension_auth`](/tools/mcp/auth). - **The answer reads `status: "platform-held"`.** extension.dev is not open to the public yet, so nothing ran on its machines. Creating, running and building an extension still work on your machine; see [Use the platform from your agent](/tools/mcp/platform-from-your-agent). - **The answer reads `status: "approval-required"`.** The act needs a person's approval. Open the approval URL in the answer, approve it, then call again with the same arguments and the `approvalId`. - **The approval is refused as `ApprovalExpired`.** An approval lasts 15 minutes. Call again with no `approvalId` to ask for a fresh one. - **The approval is refused as `ApprovalConsumed`.** An approval runs its act once, and the call that spent it may already have run. Read the current state first, then ask again only if the act did not happen. - **The approval is refused as `ApprovalScopeMismatch`.** It covers a different act, or the arguments changed since it was approved. Call again with no `approvalId`. - **A rebuild answers `status: "rate-limited"`.** The workspace spent its monthly builds or its hourly burst. Wait the `retryAfterSeconds` in the answer, or see [Usage and limits](/console/usage). - **A rebuild is refused with `REBUILD_REPLACES_LIVE_ARTIFACT`.** The commit is live on a channel built for the other environment. Pass `buildEnv: "production"` for stable and `staging` for any other channel. - **`configure` answers `status: "needs-owner"`.** Changing build settings needs a login approved by a workspace owner. Ask a workspace owner to sign in with [`extension_auth`](/tools/mcp/auth) and run it, or make the change in the console. - **The answer depends on the network.** This tool reaches the open web, so a refusal can be the network, not the project. ## Next - [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, create 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`](/tools/mcp/workspace): Read the extension.dev workspace a stored login belongs to: its plan and limits, its projects, and its usage. - [`extension_workspace_create`](/tools/mcp/workspace-create): Create an extension.dev workspace that does not exist yet, without opening the console. --- - ["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. ## Quick start > Create a new tab extension with React in ./my-newtab Asked for a React new tab extension, the agent scaffolds it from a template and lists what landed. ## How the agent uses it 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. ## Reference | | | | --- | --- | | 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 | ## When it fails - **The answer depends on the network.** This tool reaches the open web, so a refusal can be the network, not the project. ## Next - [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 the surface before adding an options page, a popup, a side panel, a content script, a devtools panel, a new tab page or a background script to 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. ## Quick start > Run my extension in Chrome and let me act on it Asked to run the extension, the agent starts the session and the browser opens with it loaded. ## How the agent uses it 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. ## Reference | | | | --- | --- | | 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. | ## Next - [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. ## Quick start > How does Firefox handle the sidebar API? Search the docs before you answer Asked a cross-browser question, the agent searches the docs before answering. ## How the agent uses it 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. ## Reference | | | | --- | --- | | 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) | ## When it fails - **The answer depends on the network.** This tool reaches the open web, so a refusal can be the network, not the project. ## Next - [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 the surface before adding an options page, a popup, a side panel, a content script, a devtools panel, a new tab page or a background script to 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. ## Quick start > The session looks stuck. Diagnose it Asked whether the session is healthy, the agent runs the doctor and reports every leg. ## How the agent uses it 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. ## Page text in the answer 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). ## Reference | | | | --- | --- | | 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. | ## When it fails - **The call is refused because no session is live.** Start one with [`extension_dev`](/tools/mcp/dev) or [`extension_start`](/tools/mcp/start) and call again. ## Next - [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. ## Quick start > Snapshot the popup and tell me what it rendered Asked what a surface rendered, the agent runs the extension and snapshots the page. ## How the agent uses it 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. ## Page text in the answer 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). ## Reference | | | | --- | --- | | 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. | ## When it fails - **The call is refused because no session is live.** Start one with [`extension_dev`](/tools/mcp/dev) or [`extension_start`](/tools/mcp/start) and call again. - **The control channel is off.** The session was started with `allowControl: false`. Start it again without that flag. ## Next - [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. ## Quick start > Evaluate chrome.runtime.id in the background and in the popup Asked to evaluate an expression, the agent starts the session with eval on and reads the value back. ## How the agent uses it 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}. ## Page text in the answer 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). ## Reference | | | | --- | --- | | 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. | ## When it fails - **The call is refused because no session is live.** Start one with [`extension_dev`](/tools/mcp/dev) or [`extension_start`](/tools/mcp/start) and call again. - **Eval is refused.** Eval is the one gate that stays off. Start the session with `allowEval: true` on [`extension_dev`](/tools/mcp/dev). - **The answer depends on the network.** This tool reaches the open web, so a refusal can be the network, not the project. ## Next - [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`. ## Quick start > Read the DOM the content script injected on the cart page, shadow roots included Asked what the content script injected, the agent runs the extension and reads the page DOM. ## How the agent uses it 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. ## Page text in the answer 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). ## Reference | | | | --- | --- | | 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. | ## When it fails - **The call is refused because no session is live.** Start one with [`extension_dev`](/tools/mcp/dev) or [`extension_start`](/tools/mcp/start) and call again. ## Next - [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. ## Quick start > Which extensions are loaded in the dev browser right now? Asked what the dev browser has loaded, the agent lists the extensions and the session facts. ## How the agent uses it This session's own extension carries ownExtension:true, with name and version from the ready contract even when the browser exposes no identity. On Chromium, entries come from the live CDP targets and from the session profile's installed set, so an extension whose MV3 service worker is dormant is still listed, with running:false and no contexts. 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. ## Page text in the answer 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). ## Reference | | | | --- | --- | | 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 | ## When it fails - **The call is refused because no session is live.** Start one with [`extension_dev`](/tools/mcp/dev) or [`extension_start`](/tools/mcp/start) and call again. ## Next - [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. ## Quick start > Show me every log line since the session started, in order Asked for every log line, the agent runs the extension and reads the session log in order. ## How the agent uses it 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. ## Page text in the answer 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). ## Reference | | | | --- | --- | | 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. | ## When it fails - **The call is refused because no session is live.** Start one with [`extension_dev`](/tools/mcp/dev) or [`extension_start`](/tools/mcp/start) and call again. ## Next - [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. ## Quick start > Will this manifest load on Firefox and Safari? Asked to check the manifest for Firefox, the agent validates it and reads the findings. ## How the agent uses it This reports missing fields, invalid permissions, dangling file references, and cross-browser compatibility issues. Read buildBlocking for the errors that make extension_build refuse. ## Reference | | | | --- | --- | | 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. | ## Next - [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. ## Quick start > Open the side panel, then trigger the action button Asked to open a surface, the agent runs the extension and opens the page in a tab. ## How the agent uses it 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. ## Page text in the answer 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). ## Reference | | | | --- | --- | | 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. | ## When it fails - **The call is refused because no session is live.** Start one with [`extension_dev`](/tools/mcp/dev) or [`extension_start`](/tools/mcp/start) and call again. - **The control channel is off.** The session was started with `allowControl: false`. Start it again without that flag. - **The answer depends on the network.** This tool reaches the open web, so a refusal can be the network, not the project. ## Next - [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. ## Quick start > Share this build with my designer ## How the agent uses it 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. ## Reference | | | | --- | --- | | 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. | ## When it fails - **The tool is missing from the client's list.** Start the server with `--features=local,platform`; see [Platform tools](/tools/mcp/platform). - **The platform refuses the call.** Log in first with [`extension_auth`](/tools/mcp/auth). - **The answer reads `status: "platform-held"`.** extension.dev is not open to the public yet, so nothing ran on its machines. Creating, running and building an extension still work on your machine; see [Use the platform from your agent](/tools/mcp/platform-from-your-agent). - **The answer depends on the network.** This tool reaches the open web, so a refusal can be the network, not the project. ## Next - [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_builds`](/tools/mcp/builds): Read, rebuild and configure the platform's builds of the logged-in project on extension.dev. - [`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, create 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`](/tools/mcp/workspace): Read the extension.dev workspace a stored login belongs to: its plan and limits, its projects, and its usage. - [`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. ## Quick start > Create the extension.dev project for this build under acme ## How the agent uses it 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 kept on this machine only while a create's outcome is unknown: then it sits in a file only this user can read, beside the stored logins, so calling again with the same deviceCode and arguments reads that outcome (created, still creating, or refused) with no second approval. 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 each call answers within about 45 seconds. Until it expires the list's grant is kept in a file only this user can read, beside the stored logins, so a restarted server resumes the list with no second approval. 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. ## Reference | | | | --- | --- | | 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) | ## When it fails - **The tool is missing from the client's list.** Start the server with `--features=local,platform`; see [Platform tools](/tools/mcp/platform). - **The answer reads `status: "platform-held"`.** extension.dev is not open to the public yet, so nothing ran on its machines. Creating, running and building an extension still work on your machine; see [Use the platform from your agent](/tools/mcp/platform-from-your-agent). - **The answer depends on the network.** This tool reaches the open web, so a refusal can be the network, not the project. ## Next - [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_builds`](/tools/mcp/builds): Read, rebuild and configure the platform's builds of the logged-in project on extension.dev. - [`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, create 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`](/tools/mcp/workspace): Read the extension.dev workspace a stored login belongs to: its plan and limits, its projects, and its usage. - [`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. ## Quick start > Give me the shareable URL of the latest build ## How the agent uses it 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. ## Reference | | | | --- | --- | | 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) | ## When it fails - **The tool is missing from the client's list.** Start the server with `--features=local,platform`; see [Platform tools](/tools/mcp/platform). - **The platform refuses the call.** Log in first with [`extension_auth`](/tools/mcp/auth). - **The answer reads `status: "platform-held"`.** extension.dev is not open to the public yet, so nothing ran on its machines. Creating, running and building an extension still work on your machine; see [Use the platform from your agent](/tools/mcp/platform-from-your-agent). - **The answer depends on the network.** This tool reaches the open web, so a refusal can be the network, not the project. ## Next - [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_builds`](/tools/mcp/builds): Read, rebuild and configure the platform's builds of the logged-in project on extension.dev. - [`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, create 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`](/tools/mcp/workspace): Read the extension.dev workspace a stored login belongs to: its plan and limits, its projects, and its usage. - [`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. ## Quick start > Promote build 3f2a1c to the beta channel ## How the agent uses it This WRITES: action:'promote' (the default) points channel at buildId, action:'cut' opens a release pull request in the project's source repository that bumps the manifest version (level patch, minor or major, or an exact version; optionally armed to release to channel when merged, with releaseNotes in its body), and action:'rollback' returns channel to the build it served before. A promote needs a token minted by a workspace owner and a workspace owner's approval of that exact promote, every time; cut and rollback need a workspace owner's approval of the exact act too. For all three the first call runs the platform's checks and either refuses with the reason and the next step, asking nobody, or answers approval-required with the approval URL and the platform's own description of the act; once approved, the same call with approvalId runs it once. 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. ## Reference | | | | --- | --- | | Group | platform, on with `--features=local,platform` (private alpha) | | Tier | Platform | | Needs | the server started with --features=local,platform, and a login; cut and rollback need a workspace owner's approval | | Read only | no | | Destructive | yes | | Idempotent | no | | Reaches the open web | yes | ### Inputs | Input | Type | Required | Default | What it does | | --- | --- | --- | --- | --- | | `action` | "promote" \| "cut" \| "rollback" | no | `"promote"` | promote (the default) points channel at buildId; cut opens a release pull request that bumps the manifest version; rollback returns channel to the build it served before. | | `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 | no | | Build commit SHA to promote (a 7-char short SHA is fine) | | `channel` | string | no | | Target release channel, e.g. stable, preview, beta. With cut, the channel the pull request releases to when merged (optional); with rollback, the channel to roll back. | | `level` | "patch" \| "minor" \| "major" | no | | With cut, how far to bump the manifest version (default patch). A version, when passed, decides the bump instead. | | `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). With cut, the exact manifest version to bump to, above the current one. | | `releaseNotes` | string | no | | Release notes markdown (optional). With cut, the notes written into the pull request body. | | `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 every promote needs a workspace owner's 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) | ## When it fails - **The tool is missing from the client's list.** Start the server with `--features=local,platform`; see [Platform tools](/tools/mcp/platform). - **The platform refuses the call.** Log in first with [`extension_auth`](/tools/mcp/auth). - **The answer reads `status: "platform-held"`.** extension.dev is not open to the public yet, so nothing ran on its machines. Creating, running and building an extension still work on your machine; see [Use the platform from your agent](/tools/mcp/platform-from-your-agent). - **The answer reads `status: "approval-required"`.** The act needs a person's approval. Open the approval URL in the answer, approve it, then call again with the same arguments and the `approvalId`. - **The approval is refused as `ApprovalExpired`.** An approval lasts 15 minutes. Call again with no `approvalId` to ask for a fresh one. - **The approval is refused as `ApprovalConsumed`.** An approval runs its act once, and the call that spent it may already have run. Read the current state first, then ask again only if the act did not happen. - **The approval is refused as `ApprovalScopeMismatch`.** It covers a different act, or the arguments changed since it was approved. Call again with no `approvalId`. - **A rollback is refused with `NO_ROLLBACK_TARGET`.** The channel has served no earlier build. Promote another build to it instead. - **A cut is refused with `NO_SOURCE_REPO`.** The project has no source repository to open a pull request in. Connect one in the console's project settings. - **A cut or a rollback answers `status: "needs-owner"`.** Both need a login approved by a workspace owner. Ask a workspace owner to sign in with [`extension_auth`](/tools/mcp/auth) and run it, or make the change in the console. - **The answer depends on the network.** This tool reaches the open web, so a refusal can be the network, not the project. ## Next - [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_builds`](/tools/mcp/builds): Read, rebuild and configure the platform's builds of the logged-in project on extension.dev. - [`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, create 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`](/tools/mcp/workspace): Read the extension.dev workspace a stored login belongs to: its plan and limits, its projects, and its usage. - [`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). ## Quick start > Was the last Chrome submission approved? ## How the agent uses it 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; when your login covers the project it also reads the platform's own submission records and their store runs, so a submission whose run ended before recording anything still shows its run status, conclusion, run URL and whether any store step ran (a run that failed before any store step is recorded as failed, and no store received it). Those two are included by default. Pass include:'history' for the platform's release history: each release (browser and channel) with its build, version, rollout, promotion run and its 5 most recent events, plus whether a build is ready to promote, which only a token minted by a workspace owner may read and is otherwise reported as needing an owner. Pass releaseId (an id from history, such as chrome-stable) for that one release and up to 20 of its events. include:'runs' reads the newest release workflow runs (the last 7 days, or since a date within the last 30 days), which also needs a token minted by a workspace owner; include:'rollout' reads the Chrome staged rollout percentage and whether it can be raised; include:'cut' reads whether a release pull request opened now could build after merging. The stores section also reads each store's configuration from the platform when your login covers the project: whether it is enabled and configured, whether its credentials are set and the names of any missing ones (never a credential value), its listing URL and submission mode, plus up to 5 earlier submissions per store besides the latest; each submission the platform recorded carries its id, which extension_submit (action:'cancel') takes. 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. ## Reference | | | | --- | --- | | 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" \| "history" \| "runs" \| "rollout" \| "cut" | no | `["releases","stores"]` | Which sections to read: releases and stores by default; history adds the platform's release history; runs the newest release workflow runs; rollout the Chrome staged rollout; cut whether a release pull request could build after merging. | | `since` | string | no | | With runs, the earliest date to read from (ISO 8601, within the last 30 days; default 7 days). | | `releaseId` | string | no | | Read this one release and its events (an id from include:'history', such as chrome-stable). Alone, it reads only that release. | | `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) | ## When it fails - **The tool is missing from the client's list.** Start the server with `--features=local,platform`; see [Platform tools](/tools/mcp/platform). - **The platform refuses the call.** Log in first with [`extension_auth`](/tools/mcp/auth). - **The answer reads `status: "platform-held"`.** extension.dev is not open to the public yet, so nothing ran on its machines. Creating, running and building an extension still work on your machine; see [Use the platform from your agent](/tools/mcp/platform-from-your-agent). - **History reports promote readiness as `needsOwner`, or the runs section is refused.** Both reads need a login approved by a workspace owner. Ask an owner to sign in and read them. - **The answer depends on the network.** This tool reaches the open web, so a refusal can be the network, not the project. ## Next - [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_builds`](/tools/mcp/builds): Read, rebuild and configure the platform's builds of the logged-in project on extension.dev. - [`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, create 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`](/tools/mcp/workspace): Read the extension.dev workspace a stored login belongs to: its plan and limits, its projects, and its usage. - [`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. ## Quick start > Reload the extension and confirm the worker came back Asked to reload, the agent reloads the background and confirms the worker came back. ## How the agent uses it 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. ## Page text in the answer 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). ## Reference | | | | --- | --- | | 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. | ## When it fails - **The call is refused because no session is live.** Start one with [`extension_dev`](/tools/mcp/dev) or [`extension_start`](/tools/mcp/start) and call again. - **The control channel is off.** The session was started with `allowControl: false`. Start it again without that flag. ## Next - [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, create and revoke the public preview links this token has shared, which is what extension_preview_web share:true hands out." # `extension_shares` List, create and revoke the public preview links this token has shared, which is what extension_preview_web share:true hands out. ## Quick start > List every link I have shared and revoke the oldest one ## How the agent uses it Pass action:'create' with a buildId (and browser, default chrome) to share a build the platform already made, with no local build: the build's package is downloaded from the registry, unpacked in a temporary folder, and uploaded the same way share:true uploads a local build, metered the same way, and the answer carries its previewUrl, zipUrl, revokeUrl and expiresAt. 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. ## Reference | | | | --- | --- | | Group | platform, on with `--features=local,platform` (private alpha) | | Tier | Platform | | Needs | the server started with --features=local,platform, and a login; a revoke waits for a member's approval by default | | 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" \| "create" \| "revoke" | no | `"list"` | list reads every share this token owns; create shares one of the platform's builds as a public link; revoke permanently kills one and cannot be undone. | | `buildId` | string | no | | With create, the platform build to share, as its short or full commit sha (extension_builds lists them). | | `browser` | string | no | | With create, which browser's package of the build to share (default chrome). | | `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) | ## When it fails - **The tool is missing from the client's list.** Start the server with `--features=local,platform`; see [Platform tools](/tools/mcp/platform). - **The platform refuses the call.** Log in first with [`extension_auth`](/tools/mcp/auth). - **The answer reads `status: "platform-held"`.** extension.dev is not open to the public yet, so nothing ran on its machines. Creating, running and building an extension still work on your machine; see [Use the platform from your agent](/tools/mcp/platform-from-your-agent). - **The answer reads `status: "approval-required"`.** The act needs a person's approval. Open the approval URL in the answer, approve it, then call again with the same arguments and the `approvalId`. - **The approval is refused as `ApprovalExpired`.** An approval lasts 15 minutes. Call again with no `approvalId` to ask for a fresh one. - **The approval is refused as `ApprovalConsumed`.** An approval runs its act once, and the call that spent it may already have run. Read the current state first, then ask again only if the act did not happen. - **The approval is refused as `ApprovalScopeMismatch`.** It covers a different act, or the arguments changed since it was approved. Call again with no `approvalId`. - **The answer depends on the network.** This tool reaches the open web, so a refusal can be the network, not the project. ## Next - [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_builds`](/tools/mcp/builds): Read, rebuild and configure the platform's builds of the logged-in project on extension.dev. - [`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`](/tools/mcp/workspace): Read the extension.dev workspace a stored login belongs to: its plan and limits, its projects, and its usage. - [`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. ## Quick start > Launch the production build in Edge Asked for the production build, the agent builds it and launches it in Chrome. ## How the agent uses it 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. The answer says what it confirmed (the engine process, and the browser pid the launcher recorded once the contract lands) and what a production session cannot confirm (that the browser loaded the extension: it opens no debug port and has no console stream), and names extension_dev for a proven load. ## Reference | | | | --- | --- | | 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 | ## Next - [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. ## Quick start > Stop the running session and close its browser Asked to stop, the agent ends the session and names the server and browser it shut down. ## How the agent uses it The answer names the server pid and the browser pid the launcher recorded, each with whether it is gone (serverGone, browserGone), so a "close its browser" ask is settled by this answer with no process check by hand. 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. ## Reference | | | | --- | --- | | 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. | ## When it fails - **The call is refused because no session is live.** Start one with [`extension_dev`](/tools/mcp/dev) or [`extension_start`](/tools/mcp/start) and call again. ## Next - [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. ## Quick start > Read chrome.storage.local and set settings.theme to dark Asked to set a storage key, the agent writes it through the control channel and reads it back. ## How the agent uses it 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. ## Page text in the answer 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). ## Reference | | | | --- | --- | | 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. | ## When it fails - **The call is refused because no session is live.** Start one with [`extension_dev`](/tools/mcp/dev) or [`extension_start`](/tools/mcp/start) and call again. - **The control channel is off.** The session was started with `allowControl: false`. Start it again without that flag. ## Next - [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). ## Quick start > Dry run a Chrome and Firefox store submission of the latest build ## How the agent uses it 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: it needs a token issued by a workspace owner and a workspace owner's approval of that exact submission, every time, 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. action:'preflight' reads each store's readiness for browsers (the failing checks with what to fix, the warnings, never a secret value) and writes nothing. action:'cancel' withdraws a submission from review (store, default chrome, and submissionId from extension_release_status), and action:'rollout' raises the Chrome staged rollout to deployPercentage, which Chrome does not let be lowered again; both need a workspace owner's approval of the exact act, so the first call answers approval-required with the approval URL, or refuses with the reason before anyone is asked. 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. ## Reference | | | | --- | --- | | Group | platform, on with `--features=local,platform` (private alpha) | | Tier | Platform | | Needs | the server started with --features=local,platform, and a login; dryRun: false, cancel and rollout need a workspace owner's approval | | Read only | no | | Destructive | yes | | Idempotent | no | | Reaches the open web | yes | ### Inputs | Input | Type | Required | Default | What it does | | --- | --- | --- | --- | --- | | `action` | "submit" \| "preflight" \| "cancel" \| "rollout" | no | `"submit"` | submit (the default) sends a build for store review; preflight reads each store's readiness and writes nothing; cancel withdraws a submission from review; rollout raises the Chrome staged rollout. | | `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" | no | | Stores to submit to. | | `buildSha` | string | no | | 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 workspace owner's approval every time: 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) | | `store` | "chrome" \| "firefox" \| "edge" \| "safari" | no | | With cancel or rollout, the store (default chrome, the one store that supports both). | | `submissionId` | string | no | | With cancel, the platform's id of the submission to withdraw, from extension_release_status include:['stores']. | | `deployPercentage` | number | no | | With rollout, the share of users to raise the staged rollout to: a whole number from 1 to 100, above the current one. | ## When it fails - **The tool is missing from the client's list.** Start the server with `--features=local,platform`; see [Platform tools](/tools/mcp/platform). - **The platform refuses the call.** Log in first with [`extension_auth`](/tools/mcp/auth). - **The answer reads `status: "platform-held"`.** extension.dev is not open to the public yet, so nothing ran on its machines. Creating, running and building an extension still work on your machine; see [Use the platform from your agent](/tools/mcp/platform-from-your-agent). - **The answer reads `status: "approval-required"`.** The act needs a person's approval. Open the approval URL in the answer, approve it, then call again with the same arguments and the `approvalId`. - **The approval is refused as `ApprovalExpired`.** An approval lasts 15 minutes. Call again with no `approvalId` to ask for a fresh one. - **The approval is refused as `ApprovalConsumed`.** An approval runs its act once, and the call that spent it may already have run. Read the current state first, then ask again only if the act did not happen. - **The approval is refused as `ApprovalScopeMismatch`.** It covers a different act, or the arguments changed since it was approved. Call again with no `approvalId`. - **A rollout is refused with `ROLLOUT_NOT_STAGED`.** Chrome publishes this project to everyone at once. Turn on staged publish in the project's Chrome store settings in the console; it applies from the next submission. - **A rollout is refused with `ROLLOUT_NOT_HIGHER`.** A staged rollout only goes up. Read the current share with [`extension_release_status`](/tools/mcp/release-status) and `include: ["rollout"]`. - **A cancel is refused with `SUBMISSION_NOT_IN_REVIEW`.** Only a submission the store is still reviewing can be withdrawn. Read its review state with `include: ["stores"]`. - **A cancel or a rollout answers `status: "needs-owner"`.** Both need a login approved by a workspace owner. Ask a workspace owner to sign in with [`extension_auth`](/tools/mcp/auth) and run it, or make the change in the console. - **The answer depends on the network.** This tool reaches the open web, so a refusal can be the network, not the project. ## Next - [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_builds`](/tools/mcp/builds): Read, rebuild and configure the platform's builds of the logged-in project on extension.dev. - [`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, create and revoke the public preview links this token has shared, which is what extension_preview_web share:true hands out. - [`extension_workspace`](/tools/mcp/workspace): Read the extension.dev workspace a stored login belongs to: its plan and limits, its projects, and its usage. - [`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. ## Quick start > Which templates have a side panel? Show me the source of the best one Asked what to start from, the agent lists the templates by surface and names the command. ## How the agent uses it 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. ## Reference | | | | --- | --- | | 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. | ## When it fails - **The answer depends on the network.** This tool reaches the open web, so a refusal can be the network, not the project. ## Next - [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 the surface before adding an options page, a popup, a side panel, a content script, a devtools panel, a new tab page or a background script to 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. ## Quick start > Does this theme manifest paint the colors it declares? Asked whether a theme is valid, the agent verifies the manifest and reads the verdict. ## How the agent uses it 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 with the step that settles each one (the theme editor at themes.extension.dev, and a dev session in Chrome), never as passed. ## Reference | | | | --- | --- | | 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. | ## Next - [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. ## Quick start > Wait until the dev session is ready, then tell me the ports Asked to start without waiting and then report the ports, the agent waits for ready and names every port the session bound. ## How the agent uses it 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. Every ready answer carries the ports the session bound, read from the contract: port (the dev server), controlPort (the control channel) and cdpPort (the browser's debug port on Chromium) or rdpPort (on Gecko), or debugPortNote when the session opened no debug port, so a question about ports is answered here without a shell. ## Page text in the answer 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). ## Reference | | | | --- | --- | | 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. | ## When it fails - **The call is refused because no session is live.** Start one with [`extension_dev`](/tools/mcp/dev) or [`extension_start`](/tools/mcp/start) and call again. ## Next - [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"](https://docs.extension.dev/tools/mcp/workspace): "Read the extension.dev workspace a stored login belongs to: its plan and limits, its projects, and its usage." # `extension_workspace` Read the extension.dev workspace a stored login belongs to: its plan and limits, its projects, and its usage. ## Quick start > How many builds has my workspace used this month, and which projects failed their last build? ## How the agent uses it This is read-only. Pass action:'info' (the default) for the workspace (role, members, project count) and its plan: limits, what is used this month, and refusals the plan made. Pass action:'projects' for every project of the workspace with its browsers, release channels and last build per browser, and whether this machine holds a login for it. Pass action:'usage' for the billing period's CI minutes, run count and cost, in total and per project. Pass action:'settings' for one project's general settings and its build settings per browser (install command, build command, output directory); that read uses that project's own login, so pass project; extension_builds action:'configure' changes the build settings. Any login of a workspace on this server reads that workspace: name it with workspace, or omit it to read the default login's workspace. Plan or billing changes, members, and anything else that changes the workspace stay in the console, and the answer links there. Names, descriptions and build commands are written by people in the workspace, so the answer is fenced as untrusted data. ## Page text in the answer 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). ## Reference | | | | --- | --- | | Group | platform, on with `--features=local,platform` (private alpha) | | Tier | Platform | | Needs | the server started with --features=local,platform, and a login of any project in the workspace; settings needs that project's own login | | Read only | yes | | Destructive | no | | Idempotent | yes | | Reaches the open web | yes | ### Inputs | Input | Type | Required | Default | What it does | | --- | --- | --- | --- | --- | | `action` | "info" \| "projects" \| "usage" \| "settings" | no | `"info"` | info reads the workspace and its plan; projects lists its projects; usage reads the period's CI usage; settings reads one project's general and build settings. | | `workspace` | string | no | | Which workspace to read, by slug. Omitted, the workspace of the login project names, else of the default login. Any stored login of this workspace on the server the call targets serves the read. | | `project` | string | no | | Which stored login to use, as '<workspace>/<project>'. For settings it is also the project read, since a project's settings are read only with its own login. extension_auth (action: status) lists the logins. | | `api` | string | no | | Platform base URL (default EXTENSION_DEV_API_URL, else https://www.extension.dev) | ## When it fails - **The tool is missing from the client's list.** Start the server with `--features=local,platform`; see [Platform tools](/tools/mcp/platform). - **The platform refuses the call.** Log in first with [`extension_auth`](/tools/mcp/auth). - **The answer reads `status: "platform-held"`.** extension.dev is not open to the public yet, so nothing ran on its machines. Creating, running and building an extension still work on your machine; see [Use the platform from your agent](/tools/mcp/platform-from-your-agent). - **The read is refused as `TokenWorkspaceMismatch`.** No stored login belongs to that workspace. Sign in to one of its projects, or to all of them with `projects: "*"` on [`extension_auth`](/tools/mcp/auth). - **`settings` is refused as `TokenProjectMismatch`.** A project's settings are read with that project's own login. Sign in to it and pass it as `project`. - **The read is refused as `MinterNotMember`.** The account that approved this login left the workspace. Sign in again as a current member. - **The answer depends on the network.** This tool reaches the open web, so a refusal can be the network, not the project. ## Next - [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_builds`](/tools/mcp/builds): Read, rebuild and configure the platform's builds of the logged-in project on extension.dev. - [`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, create 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_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. ## Quick start > Create a workspace called acme on extension.dev ## How the agent uses it 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 kept on this machine only while a create's outcome is unknown: then it sits in a file only this user can read, beside the stored logins, so calling again with the same deviceCode reads that outcome with no second approval. Then run extension_project_create against '<workspace>/<project>'. ## Reference | | | | --- | --- | | 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) | ## When it fails - **The tool is missing from the client's list.** Start the server with `--features=local,platform`; see [Platform tools](/tools/mcp/platform). - **The answer reads `status: "platform-held"`.** extension.dev is not open to the public yet, so nothing ran on its machines. Creating, running and building an extension still work on your machine; see [Use the platform from your agent](/tools/mcp/platform-from-your-agent). - **The answer depends on the network.** This tool reaches the open web, so a refusal can be the network, not the project. ## Next - [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_builds`](/tools/mcp/builds): Read, rebuild and configure the platform's builds of the logged-in project on extension.dev. - [`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, create 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`](/tools/mcp/workspace): Read the extension.dev workspace a stored login belongs to: its plan and limits, its projects, and its usage. --- - ["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 [Platform tools](/tools/mcp/platform#login). --- - [Templates](https://docs.extension.dev/templates/): Six default starters and a catalog of 59 working extensions, each created from the terminal, from an agent, or from templates.extension.dev. # Templates A template is a working extension you start from instead of an empty directory. Reach for one when you want something that builds, runs, and loads in a browser before you change a line. ## The six starters Each starter is the same small extension, a sidebar panel plus a content script, in a different language or framework. Pick the one you write in. | Starter | Written in | Create it | | --- | --- | --- | | [JavaScript](/templates/javascript) | Plain JavaScript | `--template javascript` | | [TypeScript](/templates/typescript) | TypeScript | `--template typescript` | | [React](/templates/react) | React with TypeScript | `--template react` | | [Preact](/templates/preact) | Preact with TypeScript | `--template preact` | | [Vue](/templates/vue) | Vue with TypeScript | `--template vue` | | [Svelte](/templates/svelte) | Svelte with TypeScript | `--template svelte` | Every starter page shows the same three ways in, with a clip of each: - **The terminal.** `npx extension@latest create my-extension --template `, then `npm run dev`. No account and nothing hosted. This is the Extension.js framework on your machine. - **An agent.** With the [MCP server](/tools/mcp) installed, "Create a React extension and run it" calls [`extension_create`](/tools/mcp/create) with the template and [`extension_dev`](/tools/mcp/dev) to run it. - **The catalog.** [templates.extension.dev](https://templates.extension.dev) runs each template live in the page and turns it into a private repository of your own with one click. ## Quick start ```bash npx extension@latest create my-extension --template react cd my-extension npm run dev ``` The first command creates the project and installs its dependencies. The last one opens a browser with the extension loaded. ## How it works [templates.extension.dev](https://templates.extension.dev) renders the whole catalog, one card per template. Each template page carries a copyable CLI command, a download of the built extension, and one click that turns the template into a repository of your own. The preview on that page is the template compiled and running, not a screenshot of it. The repository click opens `www.extension.dev/import?template=&private=true`, carrying the repository name and description from the card. That page creates a private GitHub repository from the template and connects it as a project. Configuration works signed out. Creating the repository writes to your GitHub account, so that step needs a GitHub session. See [Import](/guides/import) for what happens on that page. The catalog does not author its templates. It pins a commit of [extension-js/examples](https://github.com/extension-js/examples) and ships it 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. ## Reference What a catalog 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 | What the 59 templates cover: | Group | Examples | | --- | --- | | Surfaces | Popup, side panel, new tab, devtools | | Content scripts | Main world, multiple entries, CSS modules, Sass, Less | | Frameworks | React, Preact, Svelte, Vue, TypeScript, plain JavaScript | | Tooling | ESLint, Prettier, Stylelint, Playwright, Nx and Turborepo monorepos | | 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). ## When it fails - **Repository creation is refused with a rate limit.** The limit is 20 repository creations per hour per user, and the `429` answer carries the seconds to wait. Wait, or run the 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 - [`extension_create`](/tools/mcp/create) for every input the agent can set --- - [JavaScript template](https://docs.extension.dev/templates/javascript): A sidebar panel and a content script in JavaScript, created in one command from the terminal, from an agent, or from the catalog. # JavaScript The JavaScript starter is a sidebar panel plus a content script, written in plain JavaScript. Start from it when you want a working extension in JavaScript before you change a line. The toolbar icon opens the panel, and the panel shows the title of the page you are on. The content script adds an "Open sidebar" button to every page, inside a shadow root so page styles stay out. The background worker opens the panel when you click that button. There are no types and no framework. The sidebar is one function that builds DOM nodes, and the content script is one file. | | | | --- | --- | | Surfaces | Sidebar, content script, action, background | | Language | Plain JavaScript | | Browsers | Chrome, Edge, Firefox | | Source | [extension-js/examples/javascript](https://github.com/extension-js/examples/tree/main/examples/javascript) | | Catalog page | [templates.extension.dev/javascript](https://templates.extension.dev/javascript) | ## From the terminal One command creates the project and installs its dependencies. Then `dev` opens a browser with the extension loaded. ```bash npx extension@latest create my-javascript --template javascript cd my-javascript npm run dev ``` ## From an agent With the [MCP server](/tools/mcp) installed, one sentence does the same. The agent calls [`extension_create`](/tools/mcp/create) with this template, then [`extension_dev`](/tools/mcp/dev) to run it. ```text Create a JavaScript extension called my-javascript and run it ``` ## From the catalog The [catalog page](https://templates.extension.dev/javascript) runs the template live in the page, with its instructions and source one tab away. One click turns it into a private repository of your own. That click lands on [Import](/guides/import) with the template preselected. ## Files ```text src/background.js src/content/ContentApp.js src/content/scripts.js src/content/styles.css src/images/ src/manifest.json src/sidebar/index.html src/sidebar/page-title.js src/sidebar/scripts.js src/sidebar/SidebarApp.js src/sidebar/styles.css ``` `src/manifest.json` declares the action, the side panel, the content script's `` match and the background worker. The `sidebar` folder is the panel, the `content` folder runs on the page, and `images` holds the icons the manifest declares. ## When it fails - **`[Extension.js] Requires Node.js >= 22.12`.** The CLI checks the runtime before any command and exits. Upgrade to Node.js 22.12 or newer. Bun 1.2 and Deno 2.5 or newer also pass the check. - **The "Open sidebar" button does nothing in Firefox.** Firefox opens a sidebar only from a toolbar click, so the Firefox build shows a hint in place of the button. Click the toolbar icon instead. ## Next - The other starters: [TypeScript](/templates/typescript), [React](/templates/react), [Preact](/templates/preact), [Vue](/templates/vue), [Svelte](/templates/svelte) - [Import a repository](/guides/import) once the template is yours - [`extension_create`](/tools/mcp/create) for every input the agent can set --- - [TypeScript template](https://docs.extension.dev/templates/typescript): A sidebar panel and a content script in TypeScript, created in one command from the terminal, from an agent, or from the catalog. # TypeScript The TypeScript starter is a sidebar panel plus a content script, written in TypeScript. Start from it when you want a working extension in TypeScript before you change a line. The toolbar icon opens the panel, and the panel shows the title of the page you are on. The content script adds an "Open sidebar" button to every page, inside a shadow root so page styles stay out. The background worker opens the panel when you click that button. It is the JavaScript starter with types. The files are the same, `types.d.ts` sits beside the content script, and `@types/chrome` is already a dev dependency. | | | | --- | --- | | Surfaces | Sidebar, content script, action, background | | Language | TypeScript | | Browsers | Chrome, Edge, Firefox | | Source | [extension-js/examples/typescript](https://github.com/extension-js/examples/tree/main/examples/typescript) | | Catalog page | [templates.extension.dev/typescript](https://templates.extension.dev/typescript) | ## From the terminal One command creates the project and installs its dependencies. Then `dev` opens a browser with the extension loaded. ```bash npx extension@latest create my-typescript --template typescript cd my-typescript npm run dev ``` ## From an agent With the [MCP server](/tools/mcp) installed, one sentence does the same. The agent calls [`extension_create`](/tools/mcp/create) with this template, then [`extension_dev`](/tools/mcp/dev) to run it. ```text Create a TypeScript extension called my-typescript and run it ``` ## From the catalog The [catalog page](https://templates.extension.dev/typescript) runs the template live in the page, with its instructions and source one tab away. One click turns it into a private repository of your own. That click lands on [Import](/guides/import) with the template preselected. ## Files ```text src/background.ts src/content/ContentApp.ts src/content/scripts.ts src/content/styles.css src/content/types.d.ts src/images/ src/manifest.json src/sidebar/index.html src/sidebar/page-title.ts src/sidebar/scripts.ts src/sidebar/SidebarApp.ts src/sidebar/styles.css ``` `src/manifest.json` declares the action, the side panel, the content script's `` match and the background worker. The `sidebar` folder is the panel, the `content` folder runs on the page, and `images` holds the icons the manifest declares. ## When it fails - **`[Extension.js] Requires Node.js >= 22.12`.** The CLI checks the runtime before any command and exits. Upgrade to Node.js 22.12 or newer. Bun 1.2 and Deno 2.5 or newer also pass the check. - **The "Open sidebar" button does nothing in Firefox.** Firefox opens a sidebar only from a toolbar click, so the Firefox build shows a hint in place of the button. Click the toolbar icon instead. ## Next - The other starters: [JavaScript](/templates/javascript), [React](/templates/react), [Preact](/templates/preact), [Vue](/templates/vue), [Svelte](/templates/svelte) - [Import a repository](/guides/import) once the template is yours - [`extension_create`](/tools/mcp/create) for every input the agent can set --- - [React template](https://docs.extension.dev/templates/react): A sidebar panel and a content script in React, created in one command from the terminal, from an agent, or from the catalog. # React The React starter is a sidebar panel plus a content script, written in React with TypeScript. Start from it when you want a working extension in React before you change a line. The toolbar icon opens the panel, and the panel shows the title of the page you are on. The content script adds an "Open sidebar" button to every page, inside a shadow root so page styles stay out. The background worker opens the panel when you click that button. The sidebar and the in-page button are React components. While `dev` runs, a saved component updates in place through React Refresh instead of reloading the surface. | | | | --- | --- | | Surfaces | Sidebar, content script, action, background | | Language | React with TypeScript | | Browsers | Chrome, Edge, Firefox | | Source | [extension-js/examples/react](https://github.com/extension-js/examples/tree/main/examples/react) | | Catalog page | [templates.extension.dev/react](https://templates.extension.dev/react) | ## From the terminal One command creates the project and installs its dependencies. Then `dev` opens a browser with the extension loaded. ```bash npx extension@latest create my-react --template react cd my-react npm run dev ``` ## From an agent With the [MCP server](/tools/mcp) installed, one sentence does the same. The agent calls [`extension_create`](/tools/mcp/create) with this template, then [`extension_dev`](/tools/mcp/dev) to run it. ```text Create a React extension called my-react and run it ``` ## From the catalog The [catalog page](https://templates.extension.dev/react) runs the template live in the page, with its instructions and source one tab away. One click turns it into a private repository of your own. That click lands on [Import](/guides/import) with the template preselected. ## Files ```text src/background.ts src/content/ContentApp.tsx src/content/scripts.tsx src/content/styles.css src/images/ src/manifest.json src/sidebar/index.html src/sidebar/page-title.ts src/sidebar/scripts.tsx src/sidebar/SidebarApp.tsx src/sidebar/styles.css ``` `src/manifest.json` declares the action, the side panel, the content script's `` match and the background worker. The `sidebar` folder is the panel, the `content` folder runs on the page, and `images` holds the icons the manifest declares. ## When it fails - **`[Extension.js] Requires Node.js >= 22.12`.** The CLI checks the runtime before any command and exits. Upgrade to Node.js 22.12 or newer. Bun 1.2 and Deno 2.5 or newer also pass the check. - **The "Open sidebar" button does nothing in Firefox.** Firefox opens a sidebar only from a toolbar click, so the Firefox build shows a hint in place of the button. Click the toolbar icon instead. ## Next - The other starters: [JavaScript](/templates/javascript), [TypeScript](/templates/typescript), [Preact](/templates/preact), [Vue](/templates/vue), [Svelte](/templates/svelte) - [Import a repository](/guides/import) once the template is yours - [`extension_create`](/tools/mcp/create) for every input the agent can set --- - [Preact template](https://docs.extension.dev/templates/preact): A sidebar panel and a content script in Preact, created in one command from the terminal, from an agent, or from the catalog. # Preact The Preact starter is a sidebar panel plus a content script, written in Preact with TypeScript. Start from it when you want a working extension in Preact before you change a line. The toolbar icon opens the panel, and the panel shows the title of the page you are on. The content script adds an "Open sidebar" button to every page, inside a shadow root so page styles stay out. The background worker opens the panel when you click that button. The component files match the React starter. Extension.js points `react` at `preact/compat` for you, and a save reloads the surface rather than refreshing it in place. | | | | --- | --- | | Surfaces | Sidebar, content script, action, background | | Language | Preact with TypeScript | | Browsers | Chrome, Edge, Firefox | | Source | [extension-js/examples/preact](https://github.com/extension-js/examples/tree/main/examples/preact) | | Catalog page | [templates.extension.dev/preact](https://templates.extension.dev/preact) | ## From the terminal One command creates the project and installs its dependencies. Then `dev` opens a browser with the extension loaded. ```bash npx extension@latest create my-preact --template preact cd my-preact npm run dev ``` ## From an agent With the [MCP server](/tools/mcp) installed, one sentence does the same. The agent calls [`extension_create`](/tools/mcp/create) with this template, then [`extension_dev`](/tools/mcp/dev) to run it. ```text Create a Preact extension called my-preact and run it ``` ## From the catalog The [catalog page](https://templates.extension.dev/preact) runs the template live in the page, with its instructions and source one tab away. One click turns it into a private repository of your own. That click lands on [Import](/guides/import) with the template preselected. ## Files ```text src/background.ts src/content/ContentApp.tsx src/content/scripts.tsx src/content/styles.css src/images/ src/manifest.json src/sidebar/index.html src/sidebar/page-title.ts src/sidebar/scripts.tsx src/sidebar/SidebarApp.tsx src/sidebar/styles.css ``` `src/manifest.json` declares the action, the side panel, the content script's `` match and the background worker. The `sidebar` folder is the panel, the `content` folder runs on the page, and `images` holds the icons the manifest declares. ## When it fails - **`[Extension.js] Requires Node.js >= 22.12`.** The CLI checks the runtime before any command and exits. Upgrade to Node.js 22.12 or newer. Bun 1.2 and Deno 2.5 or newer also pass the check. - **The "Open sidebar" button does nothing in Firefox.** Firefox opens a sidebar only from a toolbar click, so the Firefox build shows a hint in place of the button. Click the toolbar icon instead. ## Next - The other starters: [JavaScript](/templates/javascript), [TypeScript](/templates/typescript), [React](/templates/react), [Vue](/templates/vue), [Svelte](/templates/svelte) - [Import a repository](/guides/import) once the template is yours - [`extension_create`](/tools/mcp/create) for every input the agent can set --- - [Vue template](https://docs.extension.dev/templates/vue): A sidebar panel and a content script in Vue, created in one command from the terminal, from an agent, or from the catalog. # Vue The Vue starter is a sidebar panel plus a content script, written in Vue with TypeScript. Start from it when you want a working extension in Vue before you change a line. The toolbar icon opens the panel, and the panel shows the title of the page you are on. The content script adds an "Open sidebar" button to every page, inside a shadow root so page styles stay out. The background worker opens the panel when you click that button. The sidebar and the in-page button are single-file components, each mounted by a short script. `shims-vue.d.ts` declares the `.vue` module type so the imports check. | | | | --- | --- | | Surfaces | Sidebar, content script, action, background | | Language | Vue with TypeScript | | Browsers | Chrome, Edge, Firefox | | Source | [extension-js/examples/vue](https://github.com/extension-js/examples/tree/main/examples/vue) | | Catalog page | [templates.extension.dev/vue](https://templates.extension.dev/vue) | ## From the terminal One command creates the project and installs its dependencies. Then `dev` opens a browser with the extension loaded. ```bash npx extension@latest create my-vue --template vue cd my-vue npm run dev ``` ## From an agent With the [MCP server](/tools/mcp) installed, one sentence does the same. The agent calls [`extension_create`](/tools/mcp/create) with this template, then [`extension_dev`](/tools/mcp/dev) to run it. ```text Create a Vue extension called my-vue and run it ``` ## From the catalog The [catalog page](https://templates.extension.dev/vue) runs the template live in the page, with its instructions and source one tab away. One click turns it into a private repository of your own. That click lands on [Import](/guides/import) with the template preselected. ## Files ```text src/background.ts src/content/ContentApp.vue src/content/scripts.ts src/content/styles.css src/content/types.d.ts src/images/ src/manifest.json src/shims-vue.d.ts src/sidebar/index.html src/sidebar/page-title.ts src/sidebar/scripts.ts src/sidebar/SidebarApp.vue src/sidebar/styles.css ``` `src/manifest.json` declares the action, the side panel, the content script's `` match and the background worker. The `sidebar` folder is the panel, the `content` folder runs on the page, and `images` holds the icons the manifest declares. ## When it fails - **`[Extension.js] Requires Node.js >= 22.12`.** The CLI checks the runtime before any command and exits. Upgrade to Node.js 22.12 or newer. Bun 1.2 and Deno 2.5 or newer also pass the check. - **The "Open sidebar" button does nothing in Firefox.** Firefox opens a sidebar only from a toolbar click, so the Firefox build shows a hint in place of the button. Click the toolbar icon instead. ## Next - The other starters: [JavaScript](/templates/javascript), [TypeScript](/templates/typescript), [React](/templates/react), [Preact](/templates/preact), [Svelte](/templates/svelte) - [Import a repository](/guides/import) once the template is yours - [`extension_create`](/tools/mcp/create) for every input the agent can set --- - [Svelte template](https://docs.extension.dev/templates/svelte): A sidebar panel and a content script in Svelte, created in one command from the terminal, from an agent, or from the catalog. # Svelte The Svelte starter is a sidebar panel plus a content script, written in Svelte with TypeScript. Start from it when you want a working extension in Svelte before you change a line. The toolbar icon opens the panel, and the panel shows the title of the page you are on. The content script adds an "Open sidebar" button to every page, inside a shadow root so page styles stay out. The background worker opens the panel when you click that button. The sidebar and the in-page button are Svelte 5 components, and each script mounts its component with `mount`. | | | | --- | --- | | Surfaces | Sidebar, content script, action, background | | Language | Svelte with TypeScript | | Browsers | Chrome, Edge, Firefox | | Source | [extension-js/examples/svelte](https://github.com/extension-js/examples/tree/main/examples/svelte) | | Catalog page | [templates.extension.dev/svelte](https://templates.extension.dev/svelte) | ## From the terminal One command creates the project and installs its dependencies. Then `dev` opens a browser with the extension loaded. ```bash npx extension@latest create my-svelte --template svelte cd my-svelte npm run dev ``` ## From an agent With the [MCP server](/tools/mcp) installed, one sentence does the same. The agent calls [`extension_create`](/tools/mcp/create) with this template, then [`extension_dev`](/tools/mcp/dev) to run it. ```text Create a Svelte extension called my-svelte and run it ``` ## From the catalog The [catalog page](https://templates.extension.dev/svelte) runs the template live in the page, with its instructions and source one tab away. One click turns it into a private repository of your own. That click lands on [Import](/guides/import) with the template preselected. ## Files ```text src/background.ts src/content/ContentApp.svelte src/content/scripts.ts src/content/styles.css src/images/ src/manifest.json src/sidebar/index.html src/sidebar/page-title.ts src/sidebar/scripts.ts src/sidebar/SidebarApp.svelte src/sidebar/styles.css ``` `src/manifest.json` declares the action, the side panel, the content script's `` match and the background worker. The `sidebar` folder is the panel, the `content` folder runs on the page, and `images` holds the icons the manifest declares. ## When it fails - **`[Extension.js] Requires Node.js >= 22.12`.** The CLI checks the runtime before any command and exits. Upgrade to Node.js 22.12 or newer. Bun 1.2 and Deno 2.5 or newer also pass the check. - **The "Open sidebar" button does nothing in Firefox.** Firefox opens a sidebar only from a toolbar click, so the Firefox build shows a hint in place of the button. Click the toolbar icon instead. ## Next - The other starters: [JavaScript](/templates/javascript), [TypeScript](/templates/typescript), [React](/templates/react), [Preact](/templates/preact), [Vue](/templates/vue) - [Import a repository](/guides/import) once the template is yours - [`extension_create`](/tools/mcp/create) for every input the agent can set --- - [Use the platform from your agent](https://docs.extension.dev/tools/mcp/platform-from-your-agent): Sign an agent in to one project or a whole workspace, see what its login reads and does, and which acts wait for an owner's approval. # Use the platform from your agent The platform tools let an agent read your workspace, rebuild, promote, share and submit on [extension.dev](https://extension.dev) without opening the console. Reach for this page when you decide what an agent may do on your behalf, and what stays with a person. ## Quick start Start the server with the platform group, then ask the agent to sign in to every project of your workspace: ```bash claude mcp add extension-dev -- npx @extension.dev/mcp --features=local,platform ``` > Sign this machine in to every project of acme on extension.dev, then tell me which projects failed their last build The agent calls [`extension_auth`](/tools/mcp/auth) with `workspace: "acme"` and `projects: "*"`. You approve one code at [extension.dev/device](https://extension.dev/device), the page lists every project it signs in to, and the agent then reads them with [`extension_workspace`](/tools/mcp/workspace). ## How it works ### Sign in to a project or a workspace Every login is a project token. It is scoped to one project, stored on this machine, never shown to the agent, and lives at most 7 days. There is no workspace-wide token. - **One project.** `extension_auth` with `action: "login"` and `project: "/"`. - **Several projects.** Pass `projects` with 1 to 20 names in one workspace. One approval stores one token per project, and one missing project refuses the whole list. - **Every project.** Pass `workspace` with `projects: "*"`. The approval page lists the projects the workspace has when you open it, and the answer names each one signed in. A workspace with more than 20 projects, or none, is refused whole, never cut short. Logins sit side by side. A call uses the most recent one, and `project` chooses another. `extension_auth` with `action: "status"` lists them all and when each expires. Logins that expire together renew with the same call. ### What a login reads A login reads its own project and, through any project of a workspace, that workspace. The platform checks on every call that the account which approved the login is still a member. - **Workspace:** the plan, its limits and what is used this month, the members count and your role, every project with its last build per browser, and the period's CI minutes and cost. See [`extension_workspace`](/tools/mcp/workspace). - **Project settings:** general settings and build settings per browser, read with that project's own login. - **Builds:** recent builds, one build's checks, artifacts and integrity, and its CI log lines per browser. See [`extension_builds`](/tools/mcp/builds). - **Releases and stores:** channels, release history, store configuration, submissions and review state, the Chrome staged rollout, and whether a release pull request could build. See [`extension_release_status`](/tools/mcp/release-status). Promote readiness and release workflow runs read only with a login approved by a workspace owner. ### What a login does Some acts run at once. A rebuild starts a real CI run on the project's runners. `create-channel` adds a release channel. [`extension_shares`](/tools/mcp/shares) with `action: "create"` shares a build as a public link. [`extension_publish`](/tools/mcp/publish) returns the build page URL. Other acts wait for a person. The first call answers `status: "approval-required"` with an approval URL, or refuses with the reason before anyone is asked. Once a person approves, the same call with the `approvalId` runs the act once. | Act | Tool and input | Who approves | Login it needs | | --- | --- | --- | --- | | Revoke a share | `extension_shares`, `action: "revoke"` | Any workspace member | Any | | Promote to a channel | `extension_release_promote` | A workspace owner | Approved by an owner | | Submit for store review | `extension_submit`, `dryRun: false` | A workspace owner | Approved by an owner | | Change build settings | `extension_builds`, `action: "configure"` | A workspace owner | Approved by an owner | | Open a release pull request | `extension_release_promote`, `action: "cut"` | A workspace owner | Approved by an owner | | Roll a channel back | `extension_release_promote`, `action: "rollback"` | A workspace owner | Approved by an owner | | Withdraw a submission from review | `extension_submit`, `action: "cancel"` | A workspace owner | Approved by an owner | | Raise the Chrome staged rollout | `extension_submit`, `action: "rollout"` | A workspace owner | Approved by an owner | A revoke asks by default, and the server flag `EXTENSION_DEV_APPROVAL_GATE` changes that. The other seven acts always ask an owner, whatever the flag says. See [Server flags and telemetry](/tools/mcp/flags). ### The approval page The approval URL opens on extension.dev and asks you to sign in with GitHub. It names the act, the project and the account that asked. It lists the exact inputs, such as the build, the channel, the store or the rollout percentage. A build settings change lists each setting that changes. Otherwise the page shows the agent's own description of the act. Approving lets the agent run exactly that act, once, within 15 minutes. Any change to the inputs needs a new approval. A person who is not a member, or a member approving an owner's act, is refused and nothing changes. ### What stays in the console A login never changes these. Make them in the [console](/console/overview): - Billing and the plan, see [Usage and limits](/console/usage) - Members, roles and invites, see [Workspaces](/guides/workspaces) - Deleting a release, a project or a workspace - Store credentials, see [Credentials](/account/credentials#store-credentials) - Environment variables and secrets, see [Environments](/guides/environments) - The GitHub integration and deployment settings, see [Projects](/guides/projects) - Minting and revoking access tokens, see [Access tokens](/tools/access-tokens) [`extension_workspace`](/tools/mcp/workspace) answers with links to the workspace's plan, usage and members pages in the console, and to a project's settings page. ## Reference | Limit | Value | | --- | --- | | Login lifetime | 7 days at most | | Projects per login approval | 1 to 20 | | Approval lifetime | 15 minutes, one use | | Rebuilds per month | The workspace's build dispatches: 100 on Free, 350 on Pro | | Rebuild burst | 12 builds in any 60 minutes on Free, 30 on Pro | | Channels created per login | 20 per hour | A rebuild counts against the same allowance as every other build of the workspace. See [Usage and limits](/console/usage). ## When it fails - **The answer reads `status: "platform-held"`.** extension.dev is not open to the public yet, so nothing ran on its machines. Creating, running and building an extension still work on your machine. - **A read is refused as `TokenWorkspaceMismatch`.** The login belongs to another workspace. Sign in to a project of the workspace you want, or to all of them with `projects: "*"`. - **A read is refused as `TokenProjectMismatch`.** A project's own records read only with that project's login. Sign in to it and pass it as `project`. - **Every login stops working as `MinterNotMember`.** The account that approved them left the workspace. Sign in again as a current member. - **An approval is refused as `ApprovalExpired` or `ApprovalConsumed`.** An approval lasts 15 minutes and runs once. Read the current state, then call again with no `approvalId` if the act did not happen. - **An act answers `status: "needs-owner"`.** The login was approved by a member who is not an owner. Ask an owner to sign in and run it, or make the change in the console. - **A rollback is refused with `NO_ROLLBACK_TARGET`.** The channel has served no earlier build. Promote another build to it instead. - **A rollout is refused with `ROLLOUT_NOT_STAGED`.** Chrome publishes this project to everyone at once. Turn on staged publish in the project's [Chrome store settings](/publish/chrome); it applies from the next submission. - **A rebuild answers `status: "rate-limited"`.** The workspace spent its monthly builds or its hourly burst. Wait the `retryAfterSeconds` in the answer. ## Next - [Platform tools](/tools/mcp/platform): login, batch creation, shares and publish - [`extension_workspace`](/tools/mcp/workspace) and [`extension_builds`](/tools/mcp/builds) - [Server flags and telemetry](/tools/mcp/flags): turn the platform group and the approvals on and off ## 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.