io.github.extensiondev/mcp
NPM · @EXTENSION.DEV/MCP · SCANNED SEP 20
Build, run, inspect, and publish browser extensions from any MCP client. 30 tools, 11 browsers.
Available components
How this component scores in each security and reliability category. Every signal is checked automatically from public evidence about the published package, including repeated runs of it in an isolated sandbox, and we only credit what we can confirm. How we score → Why this is hard to score →
Supply Chain Security99
- No malware found by supply-chain analysis.Pass
- A known CVE affects image-size, but no fixed version has been published, so there is nothing to upgrade to. View diagnostics → Partial
- No install/post-install scripts declared.Pass
- 76 of 345 dependencies flagged as unhealthy. View diagnostics → Partial
Provenance & Transparency97
- Source repository is publicly reachable at the declared URL. View diagnostics → Pass
- Cryptographically verified build provenance (signed, bound to extensiondev/mcp). View diagnostics → Pass
- Clear OSI-approved license (Apache-2.0).Pass
- Actively maintained (last published 29 days ago).Pass
- Disclosure check failed: no security disclosure policy was found in the source repository. See how to fix → Fail
Schema Quality & AI Usability61
- AI-judged instruction clarity (excellent).Pass
- Context-footprint check failed: tool/resource definitions use about 9290 tokens (~309/item across 30 items; 30 tools + 0 resources), over budget; trim descriptions and params. See how to fix → Fail
- Usage-examples check failed: none of the tools include examples. See how to fix → Fail
Stability & Change Management100
- No destabilizing schema changes in the last 30 days.Pass
Tool Coverage98
- 100% of tools have a non-trivial description (not blank, and not just the tool's name).Pass
- 94% of tool parameters carry a description.Partial
Tool Safety75
- No prompt-injection markers were found in the server instructions, tool names or descriptions we captured.Pass
- 0 of 2 tool(s) whose name or description implies an irreversible operation declare an MCP destructiveHint annotation; "extension_eval" implies "eval" and declares no destructiveHint at all, which the MCP spec reads as destructive by default. See how to fix → Fail
- An AI judge read all 30 captured unit(s) of tool text and found none that tries to manipulate the model reading it.Pass
Capabilities100
- Implements a supported MCP spec version (2025-11-25); the latest is 2026-07-28.Pass
How do I install the io.github.extensiondev/mcp server?
io.github.extensiondev/mcp runs locally as an npm package, launched with npx -y @extension.dev/mcp. Ready-made configuration for Claude, Cursor, VS Code, Codex and 5 more is on this page, copied from each client's own documentation.
npm · @extension.dev/mcp
claude mcp add extensiondev-mcp -- npx -y @extension.dev/mcp
{
"mcpServers": {
"extensiondev-mcp": {
"command": "npx",
"args": [
"-y",
"@extension.dev/mcp"
]
}
}
} {
"servers": {
"extensiondev-mcp": {
"command": "npx",
"args": [
"-y",
"@extension.dev/mcp"
]
}
}
} codex mcp add extensiondev-mcp -- npx -y @extension.dev/mcp
{
"$schema": "https://opencode.ai/config.json",
"mcp": {
"extensiondev-mcp": {
"type": "local",
"command": [
"npx",
"-y",
"@extension.dev/mcp"
],
"enabled": true
}
}
} openclaw mcp add extensiondev-mcp --command npx --arg -y --arg @extension.dev/mcp
mcp_servers:
extensiondev-mcp:
command: "npx"
args: ["-y", "@extension.dev/mcp"] {
"McpServers": {
"extensiondev-mcp": {
"Transport": "stdio",
"Command": "npx",
"Arguments": [
"-y",
"@extension.dev/mcp"
]
}
}
} assistant mcp add extensiondev-mcp -t stdio -c npx -a -y @extension.dev/mcp
{
"mcpServers": {
"extensiondev-mcp": {
"command": "npx",
"args": [
"-y",
"@extension.dev/mcp"
]
}
}
} Every change we have recorded for this component, newest first. Security-relevant changes are always shown. ▲ marks a change for the better, ▼ a change for the worse; unmarked changes are neutral.
- 20 Sept 26 0
- Stability: 0.97 → pass security
- 19 Sept 26 +1
- Security disclosure: unverified → fail ▼ functional
- 18 Sept 26 0
- Security disclosure: fail → unverified ▼ functional
- 17 Sept 26 −1
- Stability: pass → 0.90 functional
- 16 Sept 26 0
- Stability: 0.97 → pass security
- 15 Sept 26 0
- Stability: pass → 0.97 functional
- 14 Sept 26 0
- Stability: 0.97 → pass security
- 13 Sept 26 +1
No change was recorded against any check on this day. Stability & Change Management went from 93 to 97. That category is still filling its 30-day observation window: 28 days of observed history at the previous scan, 29 at this one. The score rises as the window fills, whether or not the server changes.
Diagnostic detail from the automated scan of this channel: what the scanner observed at each step, so you can see exactly where a check passed or failed. It is informational only and never changes the trust score.
Captured 20 Sept 2026 · Analysed npm/@extension.dev/mcp@10.9.0
Provenance Verified
A signed build attestation was found and verified, binding this exact artifact to the source repository it claims to come from.
| Result | Verified |
|---|---|
| Ecosystem | npm |
| Reason | Verified |
| Discovered via | Registry attestation endpoint |
| Source repo | extensiondev/mcp |
| Certificate issuer | https://token.actions.githubusercontent.com |
| Certificate SAN | https://github.com/extensiondev/mcp/.github/workflows/release.yml@refs/heads/main |
| Rekor log index | 2552575819 |
| Predicate type | https://slsa.dev/provenance/v1 |
| Subject digest | sha512:56404aa7d55cdea669f3cd7c34c09b05e7557710dcdde9f4f69b7469a832f5001626727b9a167ba94993ed36a6d493a396efd617f7dc77953d17bb257 |
Background: How many MCP packages publish verified provenance →
Vulnerabilities 2 findings
| ID | CVE | Severity | Vector | Fix available |
|---|---|---|---|---|
| GHSA-5p2g-fcmc-qvqq | CVE-2025-71329 | high | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H | no |
| GHSA-w3rx-r6r6-pgpr | CVE-2025-71330 | high | CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H | no |
Background: What a vulnerability scan can and cannot prove →
Dependencies 345 packages
| Packages resolved | 345 |
|---|---|
| Stale | 76 |
| Tree resolution | Complete |
Background: SBOMs and build attestations, explained →
The tools this component advertises to a client, with an estimated token cost for each. Expand a tool to see its parameters and schema. The per-tool counts are indicative and are not scored directly; the schema's total context footprint is one signal in Schema Quality & AI Usability. A tool's description is untrusted text the model reads on every call, which is what makes this list a security surface and not just an inventory: how tool poisoning works →
extension_add_feature ~81
Plan a new feature surface for an existing extension. This returns step-by-step instructions, the manifest additions to make, and reference templates from the extension.dev catalog. It modifies no files: apply the returned plan yourself.
| Name | Type | Req | Description |
|---|---|---|---|
| feature | string | yes | Feature surface to add |
| framework | string | – | – |
| projectPath | string | yes | Extension project root |
No output schema declared.
No examples provided.
extension_analyze ~105
Analyze a BUILT extension on disk: file sizes, declared entry points, permissions, bundle composition, and store-readiness checks. This is static only: it reads dist/<browser> from the filesystem and never touches a browser, so build first with extension_build. Use extension_inspect for a running extension's live DOM and console.
| Name | Type | Req | Description |
|---|---|---|---|
| browser | string | – | Browser build to analyze |
| format | string | – | – |
| projectPath | string | yes | Extension project root |
No output schema declared.
No examples provided.
extension_assert ~251
Run a test stage against a live dev session: state expectations and read one verdict for each, instead of reading a blob and hand-rolling the judgement. Every expectation comes back pass, fail or inconclusive, where inconclusive means this platform cannot cover the question today and the verdict says what would settle it. An inconclusive check is never a pass. Start the session with extension_dev; use extension_inspect or extension_logs when you want the raw reading instead of a verdict.
| Name | Type | Req | Description |
|---|---|---|---|
| browser | string | – | Session browser; defaults to this project's live session |
| expect | array | 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… |
| projectPath | string | yes | Extension project root (needs a live dev session) |
| timeout | number | – | Command timeout in ms (default 5000) |
No output schema declared.
No examples provided.
extension_auth ~371
Sign this machine in to extension.dev, report that login, or clear it. Pass action:'status' (the default) to name the workspace and project the stored token is scoped to and when it expires, never the token itself; that identity comes from the stored token alone, and does not change with the current working directory or whichever project folder you are in. Status also asks the platform's whoami endpoint whether that credential actually resolves there: the answer is reported as confirmed, refused-by-server, or unverified when the server cannot be reached, so a local file claiming a login the server would refuse is never reported as simply 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; the token stays valid server-side until it is revoked at the URL the response returns.
| Name | Type | Req | Description |
|---|---|---|---|
| action | string | – | – |
| api | string | – | Platform base URL (default EXTENSION_DEV_API_URL, else https://www.extension.dev) |
| deviceCode | string | – | login: resume token from the prior call's `deviceCode`; omit on the first call. |
| project | string | – | 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>. Creat… |
No output schema declared.
No examples provided.
extension_browsers ~179
Find, install and remove the browsers Extension.js tooling can launch. Pass action:'detect' (the default) to scan both system-installed and managed browsers, and report each one's binary path, version, engine and debugger support. Pass action:'list' for the managed cache this tool downloads into, with sizes on disk. Pass action:'install' to download a managed binary: 580 to 625 MB in one blocking call, so allow a generous client timeout. Pass action:'uninstall' to remove managed binaries; it never touches a system install.
| Name | Type | Req | Description |
|---|---|---|---|
| action | string | – | – |
| all | boolean | – | uninstall: remove every managed binary. |
| browser | string | – | install/uninstall: which managed binary. Required for install. |
| browsers | array | – | detect: limit the scan to these. Omit to check all. |
No output schema declared.
No examples provided.
extension_build ~446
Build a browser extension for production. The output lands in dist/<browser>/. 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.
| Name | Type | Req | Description |
|---|---|---|---|
| appName | string | – | 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. |
| browser | string | – | – |
| bundleId | string | – | 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, w… |
| forceRegenerate | boolean | – | 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. |
| macOsOnly | boolean | – | 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. |
| mode | string | – | Bundler mode override (also sets NODE_ENV) |
| polyfill | boolean | – | Apply cross-browser polyfill |
| projectPath | string | yes | Extension project root |
| silent | boolean | – | Suppress build output |
| skipValidation | boolean | – | Build even when extension_manifest_validate reports build-blocking errors. The build normally refuses: a manifest error yields a broken bundle the bundler itself never flags. |
| zip | boolean | – | Create a .zip file for store distribution |
| zipFilename | string | – | Custom .zip file name (defaults to name and version) |
| zipSource | boolean | – | Include source code zip (required by some stores) |
No output schema declared.
No examples provided.
extension_create ~180
Create a browser extension project from a template in the extension.dev catalog. Call extension_templates first to see what is available. The scaffolder may initialize a git repository in the new project. Read the result's defaultsApplied block for that, and for every other decision made without being asked.
| Name | Type | Req | Description |
|---|---|---|---|
| install | boolean | – | Install dependencies after creation |
| parentDir | string | – | 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. |
| projectName | string | yes | Name of the extension project (used as directory name). Alias: name. |
| template | string | – | Template slug from the extension.dev catalog (e.g. 'react', 'ai-claude', 'content-vue'). extension_templates discovers them. |
No output schema declared.
No examples provided.
extension_dev ~524
Run the extension while you edit it: dev build, hot module replacement, and a browser with the extension loaded. Reach for this first when the ask is "run my extension". ONLY this tool unlocks the control channel that extension_storage, extension_reload, extension_open and extension_dom_snapshot need (allowControl:true) and the eval channel that extension_eval needs (allowEval:true, which implies allowControl, so you never need to pass both). Use extension_start instead to run the production build in a browser. The result carries the process info that extension_wait and extension_inspect need.
| Name | Type | Req | Description |
|---|---|---|---|
| allowControl | boolean | – | Enable the agent-bridge control channel that extension_storage/reload/open/dom_snapshot need |
| allowEval | boolean | – | Enable extension_eval (runs code in a context; writes a 0600 session token). Implies allowControl, so you never need to pass both. |
| browser | string | – | – |
| carrier | boolean | – | Load the bundled Live Preview carrier beside your extension (Chromium only) so allowlisted pages (preview.extension.dev, localhost) can pair with the session and stream its real-lane chrome.* trace.… |
| chromiumBinary | string | – | Custom Chromium-based binary (overrides browser) |
| extensions | array | – | Extra extension paths or store URLs to load alongside the project |
| geckoBinary | string | – | Custom Gecko/Firefox binary (overrides browser) |
| host | string | – | Bind host, default 127.0.0.1. Use 0.0.0.0 in Docker or devcontainers. |
| noBrowser | boolean | – | Start the dev server without launching a browser |
| polyfill | boolean | – | Apply cross-browser polyfill |
| port | number | – | Dev server port (0 for auto-assign) |
| profile | string | – | Profile path, or "false" to reuse the real user profile. Omit for a throwaway one. |
| projectPath | string | yes | Extension project root |
| publicHost | string | – | Host the browser dials for HMR and reload when it differs from the bind host |
| replace | boolean | – | 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… |
| startingUrl | string | – | URL the browser opens on launch |
No output schema declared.
No examples provided.
extension_doctor ~207
Diagnose a dev session end to end: ready contract, dev-server process, control-port agreement, control channel, eval token, executor, browser liveness. This returns one {check, status, detail, remediation?} per leg, in dependency order. Read a 'skip' as blocked, not as a pass: it names the check that blocked it. A session started without allowControl 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.
| Name | Type | Req | Description |
|---|---|---|---|
| browser | string | – | Browser session to diagnose. Defaults to the active dev session's browser for this project. |
| projectPath | string | – | Path to the extension project root. Omit for a pre-flight environment check with no project. |
No output schema declared.
No examples provided.
extension_dom_snapshot ~571
Take a shallow structured DOM snapshot of one chosen surface through the agent bridge (localhost only; the snapshot itself needs no CDP, but listTargets and `tabUrl` resolution ask the browser directly and need the session's debug port: CDP page targets on Chromium, RDP tab descriptors on Firefox): element counts, extension roots, open shadow roots, optional byte-capped HTML, and optional recent console lines. This is the SURFACE PICKER: the only tool that reads an open extension surface by name (`context`: popup, options, sidebar, devtools) or an override page, the only one that takes a numeric chrome.tabs id, and the only one that enumerates what is open (listTargets for CDP targetIds and RDP tab actors, listTabs for numeric tab ids). An ambiguous `tabUrl` returns the candidates instead of guessing. It does not pierce closed shadow roots, run selector probes, or navigate: use extension_inspect for those, and for a deep read of an already-open web page. Start the session with allowControl:true (extension_dev).
| Name | Type | Req | Description |
|---|---|---|---|
| browser | string | – | Session browser; defaults to this project's live session |
| context | string | – | content/page targets `url`, else the active tab; the rest must already be OPEN |
| include | array | – | What to include; html is byte-capped |
| listTabs | boolean | – | Enumerate open tabs as {tabId,url,title} and return, ignoring the other args. Use when you need a numeric tab id. |
| listTargets | boolean | – | 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… |
| maxBytes | number | – | – |
| projectPath | string | yes | Extension project root (needs a live dev session) |
| tab | number | – | Numeric chrome.tabs id, only to disambiguate when several tabs match. With neither `tab` nor `url`, content/page target the active tab. |
| tabUrl | string | – | 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 retu… |
| timeout | number | – | Command timeout in ms (default 5000) |
| url | string | – | content/page: pick the tab by url (match pattern, then substring). Preferred over `tab`. |
| withConsole | number|boolean | – | Also include recent console lines. A number is how many; true means 50. |
No output schema declared.
No examples provided.
extension_eval ~337
Evaluate an expression in a running extension context. Start the session with allowEval:true (extension_dev), which writes a 0600 session token. Context defaults to 'background', except on a Chromium MV3 session (the default template) where it defaults to 'page', the active tab, because the MV3 service worker CSP blocks eval; pass context:'background' to target the worker anyway and get that explanation back. 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 evaluate over the in-bundle relay and need no tab id, but must already be open: open one with extension_open first, because a closed one returns an explicit error. Call extension_dom_snapshot with listTabs:true to enumerate {tabId, url, title}.
| Name | Type | Req | Description |
|---|---|---|---|
| browser | string | – | Session browser; defaults to this project's live session |
| context | string | – | Where to evaluate. Default background, except Chromium MV3 sessions default to page (the active tab). |
| expression | string | yes | JavaScript expression to evaluate in the target context |
| projectPath | string | yes | Extension project root (needs a live dev session) |
| tab | number | – | Numeric chrome.tabs id, only to disambiguate when several tabs match. |
| timeout | number | – | Command timeout in ms (default 5000) |
| url | string | – | content/page: pick the tab by url (match pattern, then substring). Preferred over `tab`. |
No output schema declared.
No examples provided.
extension_inspect ~414
Inspect a running extension deeply over the browser's debugger protocol: full HTML including shadow DOM, DOM structure, content-script injection, console messages, and CSS selector queries through `probe`. This is the ONLY tool that pierces closed shadow roots (deepDom), runs selector probes, and navigates a tab to `url` before reading it. It reads a web or override page and picks the first inspectable target, or the first whose url contains `url`; it cannot address an extension surface by name and takes no chrome.tabs id. Use extension_dom_snapshot to choose which tab or which open surface (popup, options, sidebar, devtools) to read, or to enumerate what is open. Use extension_analyze for a built extension's files and sizes on disk. Chromium rides the Chrome DevTools Protocol and needs the session's debug port, not allowControl. Firefox is fully paired: summary, meta, html, dom_snapshot, extension_roots and probes ride the agent bridge and need allowEval:true, console rides the RDP watcher replay on engine 4.0.15 and later, and deepDom needs an MV2 session with host permissions for the target url, because the Firefox MV3 background CSP blocks bridge evals. This requires an active dev or start session.
| Name | Type | Req | Description |
|---|---|---|---|
| browser | string | – | Session browser; defaults to this project's live session |
| deepDom | boolean | – | Pierce CLOSED shadow roots; open ones are read anyway. Chromium: CDP DOM pierce. Firefox: a content-script walk via tabs.executeScript (MV2 only, needs host permissions for the target url). |
| include | array | – | What to include |
| maxBytes | number | – | Truncate HTML output at this byte count (0 = unlimited) |
| probe | array | – | CSS selectors to query; returns counts and samples for each |
| projectPath | string | yes | Extension project root (needs a live dev session) |
| url | string | – | URL to inspect; the tab is navigated there first |
No output schema declared.
No examples provided.
extension_list_extensions ~188
List the extensions in the running dev browser: id, name, version, and, on Chromium, live contexts. This session's own extension carries ownExtension:true, with name and version from the ready contract even when the browser exposes no identity. Chromium rides the Chrome DevTools Protocol, so an entry needs at least one live context, and a dormant MV3 service worker may be absent until it wakes. Firefox rides the RDP root actor (listAddons, engine 4.0.15 and later), so entries are installed add-ons regardless of contexts, are marked temporarilyInstalled where relevant, and carry no contexts. Other extensions' contexts are never attached to or evaluated in. This requires an active dev or start session.
| Name | Type | Req | Description |
|---|---|---|---|
| browser | string | – | Session browser; defaults to this project's live session |
| projectPath | string | yes | Extension project root (needs a live dev session) |
No output schema declared.
No examples provided.
extension_logs ~319
Read or stream logs from every context of a running dev session (service worker, content scripts, popup, options, sidebar, devtools, pages) in one ordered timeline. This reads the same agent-bridge plane as the `extension logs` CLI: a one-shot returns the most recent matching lines from logs.ndjson, and follow:true collects from the live control channel for a bounded window. This requires an active extension_dev session.
| Name | Type | Req | Description |
|---|---|---|---|
| browser | string | – | Which dist/extension-js/<browser>/ to read. Defaults to this project's live session, else chromium. |
| context | array | – | Restrict to these contexts. Omit for all. |
| follow | boolean | – | Collect from the live control channel for a bounded window instead of reading the file. |
| followMs | number | – | How long to collect live frames when follow=true (clamped 500–15000ms). |
| level | string | – | Minimum severity; a level includes everything more severe. |
| limit | number | – | How many of the most recent events to return. |
| projectPath | string | yes | Extension project root (needs a live dev session) |
| signalsOnly | boolean | – | Only structured dx.signal diagnostics (code/status/remediation), no plain console lines. |
| since | number | – | Only events with seq greater than this; the cursor for polling forward. |
| tab | number | – | Only events from this tab id. |
| url | string | – | Only events whose url/hostname matches (glob or substring), e.g. https://shop.example/*. |
No output schema declared.
No examples provided.
extension_manifest_validate ~139
Validate a manifest.json across browsers. This reports missing fields, invalid permissions, dangling file references, and cross-browser compatibility issues. Read buildBlocking for the errors that make extension_build refuse.
| Name | Type | Req | Description |
|---|---|---|---|
| browser | string | – | Single browser to validate against; alias for browsers:[browser] to match the other tools. |
| browsers | array | – | Browsers to validate against |
| manifestPath | string | – | Path to manifest.json. Or pass projectPath and the manifest is located for you. |
| projectPath | string | – | Path to the extension project root; manifest.json is resolved from it (root or src/). Accepted in place of manifestPath. |
No output schema declared.
No examples provided.
extension_open ~359
Open an extension surface, or replay an event, in a running session. Pass surface:'popup', 'options' or 'sidebar' to open a UI surface, or 'newtab', 'history' or 'bookmarks' to open the matching chrome_url_overrides page in a tab. 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 result reports gesture:false and warns when activeTab is declared. Start the session with allowControl:true (extension_dev).
| Name | Type | Req | Description |
|---|---|---|---|
| asTab | boolean | – | 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 ref… |
| browser | string | – | Session browser; defaults to this project's live session |
| name | string | – | For surface 'command': the chrome.commands name to trigger. |
| projectPath | string | yes | Extension project root (needs a live dev session) |
| surface | string | – | Which surface to open or event to replay. |
| timeout | number | – | Command timeout in ms (default 5000) |
| url | string | – | Navigate a real tab here instead of opening a surface (Firefox needs allowEval: true). Use for content-script test pages, or a surface as a page: chrome-extension://<id>/popup.html. |
No output schema declared.
No examples provided.
extension_preview_web ~551
Preview an in-progress extension in the web emulator, with no real browser. This builds the project (unless build:false) and previews dist/<browser>. Pass share:true unless you are working inside the extension.dev monorepo: it uploads the build and returns a link anyone can open, with no install, sign-in or dev server, and it is the only lane that works from an npm install of this server. Sharing also serves the build as a zip, so it hands over the built code; read the share property before using it. The default lane instead returns a deep link over the dev-only preview://build scheme, which resolves only against a preview.extension.dev dev server on this machine, so it is for people developing extension.dev itself. Call extension_shares to list and revoke every link shared this way, so one never vanishes with this response.
| Name | Type | Req | Description |
|---|---|---|---|
| browser | string | – | Which dist/<browser> output to preview. The emulator renders it as mocked Chrome either way. |
| build | boolean | – | Build first. false previews the existing dist/<browser> as-is. |
| distPath | string | – | Preview this built directory instead of dist/<browser> under projectPath. Implies build:false. |
| hostUrl | string | – | Origin of the running preview.extension.dev dev server (default http://localhost:3110). |
| open | boolean | – | 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 | string | – | Which session's browser to open it in. Defaults to `browser`. |
| probe | boolean | – | 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 ass… |
| projectPath | string | yes | Extension project root |
| share | boolean | – | 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. It also serves the build as a zip (share.zipUrl), so sh… |
No output schema declared.
No examples provided.
extension_project_create ~498
Create an extension.dev project for an extension that does not have one yet, without opening the console. Use it right after extension_create and extension_build, once the extension's source is pushed to a GitHub repository, and BEFORE extension_auth: extension_auth can only log in to a project that already exists, and this tool is what brings that project into existence. Ask for nothing but the project slug and the repo; the platform finds the GitHub App installation on the approving account itself, and if there is none it returns a connect link to open. Two-phase, like login: the first call returns a code and a URL where the signed-in workspace owner approves creating exactly this project; call again with the returned deviceCode to finish. The approval mints a provisioning grant that lives minutes, can only create the one named project, and is never stored on this machine. On success the platform creates the project, its mirror repository, and dispatches the first build. Then run extension_auth (action: login) against the new project, and extension_publish to share it.
| Name | Type | Req | Description |
|---|---|---|---|
| api | string | – | Platform base URL (default EXTENSION_DEV_API_URL, else https://www.extension.dev) |
| buildCommand | string | – | Build command producing the extension bundle. |
| description | string | – | Short project description. Defaults to a generic sentence naming the repo. |
| deviceCode | string | – | Resume token from the prior call's `deviceCode`; omit on the first call. |
| displayName | string | – | Human name for the project. Defaults to the project slug. |
| installCommand | string | – | Dependency install command the build runs first. |
| installationId | string | – | 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… |
| outputDirectory | string | – | Directory the build writes the loadable extension into. |
| project | string | yes | 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 | yes | 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. |
No output schema declared.
No examples provided.
extension_publish ~216
Publish the project your stored token is scoped to (extension_auth, or EXTENSION_DEV_TOKEN) to extension.dev, and return its shareable URL. This is what "deploy" or "ship" an extension usually means; extension_submit is the separate store-review path. The target is the token's project: there is no projectPath, and no local file is uploaded. 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.
| Name | Type | Req | Description |
|---|---|---|---|
| api | string | – | Platform base URL (default EXTENSION_DEV_API_URL, else https://www.extension.dev) |
| buildSha | string | – | Pin the URL to a build sha (7-40 hex chars). An unknown sha is rejected, so the returned URL always points at a real build. |
| ttlHours | number | – | Private-project share-link lifetime in hours, 1-168 (default 24). Ignored for public projects. |
No output schema declared.
No examples provided.
extension_release_promote ~349
Promote a built extension to a release channel (stable, preview, beta, …) on extension.dev, headless. This WRITES: it is the only verb that changes what a channel points at. It is auth-gated by your stored login (extension_auth) or a release token in EXTENSION_DEV_TOKEN, minted and revoked under project settings, Access tokens. Tokens live at most 7 days, so CI must re-mint before expiry. The project comes from the token. Call extension_release_status to find a valid buildId. Cutting a version-bump PR is not available headlessly, because it writes to your source repo and needs an interactive login.
| Name | Type | Req | Description |
|---|---|---|---|
| api | string | – | Platform base URL (default EXTENSION_DEV_API_URL, else https://www.extension.dev) |
| approvalId | string | – | The approval handle returned by a prior approval-required response. Promoting changes what a public channel serves and is not reversible in place, so when the platform's approval gate is on this need… |
| browsers | array | – | Browsers to release (optional; auto-detected from the build) |
| buildId | string | yes | Build commit SHA to promote (a 7-char short SHA is fine) |
| channel | string | yes | Target release channel, e.g. stable, preview, beta |
| releaseNotes | string | – | Release notes markdown (optional) |
| sourceChannel | string | – | Channel to promote from (optional; inferred otherwise) |
| version | string | – | Version label for the release (optional) |
No output schema declared.
No examples provided.
extension_release_status ~271
Read where a project stands on extension.dev, from the public registry (registry.extension.land). This is read-only: it dispatches nothing and promotes nothing. Pass include:'releases' for the release channels (channel to promoted build sha), recent builds, and a public build-page URL for each, which is how you find a valid sha for extension_release_promote, extension_submit or extension_publish. Pass include:'stores' for the per-store picture after an extension_submit (chrome, firefox, edge, safari): configured or not, the last credential health check, the last recorded submission, and the latest review status, read from stores/health.json, stores/status.json and stores/submissions.json. Both are included by default. This defaults to the logged-in project (extension_auth); pass workspace and project to read another. Private projects work when your stored login covers them. Registry state can lag the store dashboards by up to a polling interval.
| Name | Type | Req | Description |
|---|---|---|---|
| api | string | – | Platform base URL (default EXTENSION_DEV_API_URL, else https://www.extension.dev) |
| include | array | – | Which sections to read. Both by default. |
| project | string | – | Project slug override (default: the stored login's). |
| workspace | string | – | Workspace slug override (default: the stored login's). |
No output schema declared.
No examples provided.
extension_reload ~102
Reload a running extension's background context, or a tab. Start the session with allowControl:true (extension_dev).
| Name | Type | Req | Description |
|---|---|---|---|
| browser | string | – | Session browser; defaults to this project's live session |
| context | string | – | – |
| projectPath | string | yes | Extension project root (needs a live dev session) |
| tab | number | – | For content/page: a specific tab id |
| timeout | number | – | Command timeout in ms (default 5000) |
No output schema declared.
No examples provided.
extension_shares ~661
List and revoke the public preview links this token has shared, which is what extension_preview_web share:true hands out. Pass action:'list' (the default) for every artifact the logged-in project owns, with its artifactId, name, version, live or dead state, createdAt, expiresAt, revokedAt, size, previewUrl, zipUrl and revokeUrl, so a link whose response you lost is findable again. Each row carries owner and sharedBy as the platform returned them. Read attribution.ownership for who may revoke a share: 'project' means the workspace holds it and any member can pull it back, 'personal' means one person holds it alone, 'unknown' means no owner was disclosed. Read attribution.credit as credit only, never access; it names the publisher, and reads 'CLI token <id>' or 'not recorded' when no person can be named. Pass action:'revoke' with an artifactId, or with any URL of the share, to kill one permanently. Pass projectPath to reconcile against the project's own append-only .extension.dev/shared-previews.json, which is read and never rewritten: a share made on another machine shows as remoteOnly, a record with no live artifact as localOnly. That record is append-only, so localOnly is counted by distinct artifactId and a build re-shared unchanged is one share, not two; server.count and server.matched are share counts, while server.scanned counts records the platform read and is never a share count. This needs the same token as sharing (extension_auth or EXTENSION_DEV_TOKEN); without one, listing still returns the local record with a login hint.
| Name | Type | Req | Description |
|---|---|---|---|
| action | string | – | list reads every share this token owns; revoke permanently kills one and cannot be undone. |
| api | string | – | Platform base URL (default EXTENSION_DEV_API_URL, else https://www.extension.dev) |
| approvalId | string | – | 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… |
| artifactId | string | – | Which share to revoke (the gen_... id from a share response or from action:"list"). Required for revoke unless url is given. |
| limit | number | – | How many shares to return, 1 to 200 (platform default 100). A cut list comes back with truncated:true. |
| projectPath | string | – | Path to the extension project root. Reconciles the platform's answer against this project's .extension.dev/shared-previews.json record. Read-only. |
| status | string | – | all (default) includes expired and revoked shares, which is what makes a dead link explainable; live returns only links that still resolve. |
| url | string | – | 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. |
No output schema declared.
No examples provided.
extension_start ~338
Run the PRODUCTION build in a browser: build the project, serve it, and launch. There is no hot module replacement and no control channel, so your edits are not picked up and extension_eval, extension_storage, extension_reload, extension_open and extension_dom_snapshot cannot attach to this session. Use extension_dev while writing code, and this to check what actually ships. Pass build:false to launch an existing dist/<browser> without rebuilding.
| Name | Type | Req | Description |
|---|---|---|---|
| browser | string | – | – |
| build | boolean | – | Build before serving. false serves the existing dist/<browser> as-is and fails when there is none. |
| chromiumBinary | string | – | Custom Chromium-based binary (overrides browser) |
| extensions | array | – | Extra extension paths or store URLs to load alongside the project |
| geckoBinary | string | – | Custom Gecko/Firefox binary (overrides browser) |
| host | string | – | Bind host, default 127.0.0.1. Use 0.0.0.0 in Docker or devcontainers. |
| noBrowser | boolean | – | Serve without launching a browser |
| polyfill | boolean | – | Apply cross-browser polyfill (build only) |
| port | number | – | Server port (0 for auto-assign) |
| profile | string | – | Profile path, or "false" to reuse the real user profile. Omit for a throwaway one. |
| projectPath | string | yes | Extension project root |
| publicHost | string | – | Host the browser dials for HMR and reload when it differs from the bind host |
| startingUrl | string | – | URL the browser opens on launch |
No output schema declared.
No examples provided.
extension_stop ~182
Stop a session that extension_dev or extension_start is running: terminate the server and the browser it launched, and remove the live-preview carrier if extension_dev placed one. This covers extension_start build:false too, which the registry records as a preview session. Call it when you are done verifying, so sessions do not accumulate.
| Name | Type | Req | Description |
|---|---|---|---|
| all | boolean | – | Stop every known session across projects and browsers, found from this server's registry AND the on-disk markers earlier runs left, so it still works after an MCP restart. It also takes back every li… |
| browser | string | – | Browser of the session to stop. Defaults to the single live session for this project rather than assuming chrome. |
| projectPath | string | – | Extension project root |
No output schema declared.
No examples provided.
extension_storage ~155
Read or write chrome.storage in a running extension. Start the session with allowControl:true (extension_dev). Set one key per call: there is no bulk-object set.
| Name | Type | Req | Description |
|---|---|---|---|
| action | string | yes | get reads a key (or the whole area); set writes a key |
| area | string | – | – |
| browser | string | – | Session browser; defaults to this project's live session |
| context | string | – | – |
| key | string | – | Key to get or set |
| projectPath | string | yes | Extension project root (needs a live dev session) |
| timeout | number | – | Command timeout in ms (default 5000) |
| value | – | – | Value to set (any JSON value); required for action=set |
No output schema declared.
No examples provided.
extension_submit ~502
Submit a built extension for store REVIEW through extension.dev, which holds your store credentials and dispatches from your project's mirror CI: the Chrome Web Store, Firefox AMO, Edge Add-ons and the App Store (Safari). This is store review only. It does not push a build to the extension.dev platform, and it does not make a shareable link: that is extension_publish, which is what "deploy" or "ship" an extension almost always means. Reach for this only when the ask is explicitly a store submission. It defaults to a dry run that dispatches nothing: the platform verifies auth, project, build and store workflow, and this tool adds each store's credential-health verdict. Trust those per-store rows over the platform's bare preflight line, which does not check store health. Pass dryRun:false to actually submit, which is irreversible and enters store review. 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). 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.
| Name | Type | Req | Description |
|---|---|---|---|
| api | string | – | Platform base URL (default EXTENSION_DEV_API_URL, else https://www.extension.dev) |
| approvalId | string | – | The approval handle returned by a prior approval-required response for a real submission. A real submission (dryRun:false) is irreversible and needs a human approval when the platform's approval gate… |
| browsers | array | yes | Stores to submit to. |
| buildSha | string | yes | The built commit SHA to submit. It needs a completed build in the project's build index; an unknown sha is rejected. |
| channel | string | – | Release channel to submit from (default stable). |
| dryRun | boolean | – | Preflight only. Pass false to actually dispatch (irreversible, enters store review). |
| projectPath | string | – | 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. |
| version | string | – | Version label for the submission record (optional). |
No output schema declared.
No examples provided.
extension_templates ~312
Browse the extension.dev template catalog. Pass action:'list' (the default) to search and filter it and get metadata per template. Pass action:'source' with a `slug` to read one template's files, for learning a pattern before building something similar. Read `framework` as the UI framework only, never the language: TypeScript and JavaScript templates live under slugs ('typescript', 'content-typescript'), shadcn is a React variant ('sidebar-shadcn'), and provider AIs carry the 'ai' tag ('ai-chatgpt', 'ai-claude'). Reach those through query, tags or slug.
| Name | Type | Req | Description |
|---|---|---|---|
| action | string | – | – |
| featured | boolean | – | list: only featured templates. |
| files | array | – | source: paths to read (e.g. ['src/manifest.json']). Omit for the file listing. |
| framework | string | – | list: UI framework filter (empty string = vanilla JS). |
| query | string | – | list: keyword search over slug, description, tags and useCases. Ranks by word matches, so a natural phrase works. |
| slug | string | – | source: which template to read (e.g. 'ai-claude', 'content-react'). Required for source. |
| surface | string | – | list: filter by surface. For a popup/action starter use query:'action', not a surface. |
| tags | array | – | list: filter by tags, e.g. ['ai', 'chat']. |
No output schema declared.
No examples provided.
extension_theme_verify ~218
Verify a Chrome theme manifest before it ships. This settles the four-leg WYSIWYG contract (app-shows == manifest-says == chrome-paints, plus chrome-accepts) as far as is possible headless: it derives every color current Chrome would paint from the manifest through the transcribed Chromium resolver, and classifies each problem as D1 fabrication, D3 parity gap, or D4 acceptance gap (keys Chrome silently discards: dead legacy, incognito, unknown, out-of-range). It verifies only, and never authors or mutates a theme. The app-rendered and real-pixel legs need a browser, so they come back as needsAttended pointing at the assert:theme and install-parity harnesses, never as passed.
| Name | Type | Req | Description |
|---|---|---|---|
| manifest | object | – | The Chrome theme manifest object (with a `theme` block). Pass this or manifestPath. |
| manifestPath | string | – | Path to a theme manifest.json (or a { manifest } seed wrapper). Read in place of the inline manifest. |
No output schema declared.
No examples provided.
extension_wait ~264
Wait for a running dev or start session to be ready. This polls the ready.json contract and reports compiled (the compiler finished), browserAttached (the runtime executor connected), and guestLoaded (the browser's own target list shows your extension). Read guestLoaded as the trustworthy load signal: it catches a silently rejected --load-extension that leaves ready.json stamped attached with empty logs. It is null when it could not be checked, for example a gecko session with no CDP port. Every result reports budgetMs and elapsedMs; on status 'timeout', call again to keep waiting on the same contract. In a noBrowser session this returns as soon as the compile lands, instead of waiting for a browser that will never attach. Ports come from the contract, so they match what the server actually bound.
| Name | Type | Req | Description |
|---|---|---|---|
| browser | string | – | Session browser; defaults to this project's live session |
| projectPath | string | yes | Extension project root |
| timeout | number | – | Deprecated alias of timeoutMs, which wins when both are given. |
| timeoutMs | number | – | 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. |
No output schema declared.
No examples provided.
What is the io.github.extensiondev/mcp server?
io.github.extensiondev/mcp is listed in the public MCP registry as io.github.extensiondev/mcp. Build, run, inspect, and publish browser extensions from any MCP client. 30 tools, 11 browsers. This page covers its npm package (@extension.dev/mcp).
Is the io.github.extensiondev/mcp server safe to use?
io.github.extensiondev/mcp scores 90 out of 100 on VerifyMCP. We recorded 2 known advisories against it as of 20 September 2026. It declares no install or post-install scripts. Its build provenance is signed and verified. That is a record of what we were able to check automatically, not an endorsement. The category breakdown on this page shows every signal behind the number, including the ones we could not confirm.
What tools does the io.github.extensiondev/mcp server expose?
io.github.extensiondev/mcp exposes 30 tools: extension_create, extension_templates, extension_build, extension_dev, extension_start, and 25 more. Their descriptions and schemas cost roughly 9,290 tokens of context every time the server is loaded.
Is the io.github.extensiondev/mcp server still maintained?
io.github.extensiondev/mcp is still listed as active in the MCP registry. We last reached this channel on 20 September 2026. Those dates come from our own scans of the registry and the channel itself, not from anything the publisher announced.
What licence is the io.github.extensiondev/mcp server under?
io.github.extensiondev/mcp declares the Apache-2.0 licence, which is OSI-approved. That covers the source only, and says nothing about the cost of any service it calls.