Skip to content

Manifest reference

This page is the exhaustive field list for extension.json, derived from the schema the host validates against. Use it to look up a type, a default, or the full value set of an enum. For the reasoning behind the choices — which fields you actually need and which ones fail quietly — read The manifest first.

Where it lives, and where the id comes from

Section titled “Where it lives, and where the id comes from”

extension.json sits in the extension’s root directory, beside main/, ui/, skills/, and dist/. It is the only required file: a directory with UI or main code but no manifest is reported as a failed scan.

The extension’s id is the directory name, not a manifest field. There is no id key. The directory name must be lowercase kebab-case (^[a-z][a-z0-9-]*$) because every id-shaped field elsewhere — dependencies, catalog slugs — is validated against that pattern.

The root object is permissive: an unknown top-level key is accepted and ignored rather than failing validation.

Field Type Required Default Constraint
name string yes — non-empty
version string yes — ^\d+\.\d+\.\d+$ — no pre-release tags
description string no — —
origin object no — Generated, read-only provenance for an imported Agent Plugins, Claude, or Codex plugin. See Portable plugins.
longDescription string no — Markdown
icon string no — a lucide-react export name
author string no — —
permissions array no [] see Permissions
capabilities string[] no [] unknown strings accepted
products string[] no — each ^[a-z0-9][a-z0-9-]*$
webRequestRewrite object no — see webRequestRewrite
contributes object no — see contributes
build object no — strict; see build
activationEvents string[] no ["onStartupFinished"] see Activation events
dependencies string[] no — each ^[a-z][a-z0-9-]*$
main string no — path relative to the extension root
server string no — path relative to the extension root
requiresElectron boolean no false —
compat { pluginApi: string } no — pluginApi non-empty; a semver range

What each one controls:

Field Controls
name The human label in the sidebar, the catalog card, and search
version Update comparison at install; the version reported to the registry
description One-line summary on catalog cards, in search results, and in the detail hero
longDescription The Markdown body of the catalog detail page
icon The sidebar and catalog icon
author Attribution shown in the catalog
permissions Which host APIs are exposed or allowed, and what the catalog discloses
capabilities Call-time gates on ctx.api.webviews, and which webview backing is used
products Which branded catalogs list the extension. Absent or empty means all
webRequestRewrite Default header rewriting for webviews this extension creates
contributes Everything declarative: pages, views, commands, skills, settings, servers
build Per-path loader overrides for the public SDK builder
activationEvents When the main module is loaded and activate() runs
dependencies Other extensions installed recursively alongside this one
main Entry point of the host/Electron-side module
server Entry point of the headless (electron-free) module
requiresElectron A headless core skips this extension at scan unless server is also declared
compat.pluginApi Plugin-API range checked against the host at registration; a mismatch refuses activation

The host checks compat.pluginApi when registering an extension. A missing range is permissive; an incompatible range refuses activation.

The host’s current plugin-API version is 3.0.0. A range with syntax the host cannot parse falls back to exact string comparison against that version, so a typo blocks activation rather than matching everything.

permissions is an array whose entries are either a string from the closed set below, or one of the two structured forms.

Permissions gate named WAMP host APIs only. They do not sandbox the package: Node entry points retain ambient Node access, and UI code runs in the host renderer’s page realm. Installing an extension means trusting its code.

Permission What declaring it does
http-routes Adds ctx.api.http
notifications Adds ctx.api.notifications
process Adds ctx.api.process, ctx.api.pty, and ctx.api.git
auth.identity Adds ctx.api.auth; fetch additionally requires auth.outbound
ai Model-call disclosure; also required for an ACP runtime declaring modelAccess: "host"
network Disclosure only
terminal Disclosure only
filesystem Disclosure only — ctx.api.fs is present either way

The structured forms, which may share one object:

{
"permissions": [
{
"auth.outbound": ["https://api.example.com"],
"auth.resource": [{ "audience": "internal-docs", "baseUrl": "https://docs.example.com/api/v1" }]
}
]
}

auth.outbound takes an array of absolute URLs — each entry must parse as a URL or the manifest fails validation. It declares the origins ctx.api.auth.fetch may target; other origins are refused at the host proxy.

auth.resource takes one to eight { audience, baseUrl } entries, each audience at most once. audience matches ^[a-z][a-z0-9-]{1,127}$; baseUrl is a canonical HTTPS URL with a path and no credentials, query, fragment or trailing slash. It declares the connected resources pluginAPI.auth.resourceFetch may read as the viewer; see Read your service from extension UI.

The object and each auth.resource entry are strict: any other key fails validation.

The local typed store (ctx.api.data) is present with no permission at all. See Permissions for the enforcement model and Plugin API reference for the per-namespace table.

capabilities is a separate list, unrelated to permissions. Four strings are recognized:

Capability Gates
webviews.navigate Navigation control on a webview handle
webviews.executeScript Script injection into a webview
webviews.interceptRequests Per-request inspection and rewriting
webRequestRewrite Header rewriting declared via the webRequestRewrite field

Declaring any of the three webviews.* values also selects the WebContentsView backing for ctx.api.webviews; without one of them the webview uses the sandboxed-iframe backing.

The allowlist is informational. An unrecognized string validates and then matches nothing the host gates on, so a misspelling produces a capability that is silently never granted.

Every entry must match one of six forms. The literal * is rejected.

Form Fires when
onStartupFinished The initial extension scan completes
onCommand:<id> A contributed command with that id is invoked
onAgent:<id> An agent with that id is about to run
onTool:<name> A tool with that name is about to execute
onMcpServer:<id> An MCP server with that id is starting
onChatCommand:<command> That chat command is typed

The id segment accepts [\w.:-]+. An absent or empty array is replaced with ["onStartupFinished"] for an extension with a selected Node entry (main, or server on a headless host). Omitting it does not make that entry lazy.

Default header rewriting applied to webviews this extension creates. All three fields are optional.

Field Type Effect
stripCSP boolean Removes Content-Security-Policy response headers
stripFrameOptions boolean Removes X-Frame-Options response headers
allowlist string[] Restricts the rewrite to these hosts

Using it requires the webRequestRewrite capability.

Per-path loader overrides for wamp ext dev and wamp ext pack, so a modest asset need stays on the standard public builder. The build object and each rule are strict.

Field Type Required Constraint
loaders array no —
loaders[].match string yes non-empty glob, relative to the extension root
loaders[].loader enum yes text | json | base64 | dataurl | binary
{ "build": { "loaders": [{ "match": "presets/**.svg", "loader": "text" }] } }

Every contribution key is optional. The contributes object itself is permissive; strictness varies per contribution type and is tabulated in Strict and permissive objects.

Key Type
pages array
views array
commands array
skills boolean, string, or string[]
services array
settings array
toolMetadata array
mcpServers array
agentRuntimes array
ipcNamespaces string[]

A page is a top-level workspace surface plus a sidebar entry. Strict — an unknown key fails the manifest.

Field Type Required Default Notes
id string yes — non-empty; the bundle’s views[<id>] export is auto-registered into workspace.main with type: <id>
title string yes — non-empty; the sidebar label
icon string no — lucide-react export name
context enum no both project (needs an open workspace) | both
presentation enum no docked docked | app
placement enum no top docked pages only: top | footer

context is normalized to both at parse time, so every consumer reads a concrete value. presentation: 'app' hides the WAMP chrome and lets the page render its own via AppShell; placement: 'footer' puts the sidebar row in the fixed bottom group instead of the reorderable list. See Pages and windows.

Do not also declare a views entry for the page’s own surface — the page id already registers one.

A component mounted into a named slot. Use this only for slots other than the page’s own workspace.main surface.

Field Type Required Notes
id string yes non-empty; must match a key of the bundle’s views export
slot enum yes one of the five slots below
type string no discriminator within workspace.main
title string no label where the slot shows one
icon string no lucide-react export name

Every ViewSlot value, the props its component receives, and whether a host renders it today:

Slot Props Renders today
workspace.main { paneId: string; type: string } yes — the main workspace pane
session.dock SessionDockContext (below) yes — the session dock strip
chat.newSessionControls { workspace; activeWorkspacePath; draftGeneration; setSubmissionPending(pending) } yes — one compact control beside the local new-chat run-destination pill. To add a place a chat can run, publish destinations with host.chat.setSessionDestinations instead of drawing a control
chat.composerStatus { chatId; visible; workspace; activeWorkspacePath; isStreaming; onInsert(text) } yes — status and next action above the active chat composer
chatInput.attachments { draftText: string; onInsert(text): void; onAttachmentRemoved(id): void } yes — below the chat composer

Publishing validates the slot strictly, so a typo is refused at upload. An INSTALLED manifest is treated differently: a view whose slot this build no longer knows is dropped at load and named in the log, and everything else the extension contributes still loads. An extension published against a slot that is later retired therefore keeps working without that one view.

SessionDockContext, the props a session.dock tool receives:

Field Type Notes
instanceId string | undefined Present for a rendered instance; absent for a hidden controller that publishes tabs
restoredTabIds readonly string[] | undefined Saved provider-local tab references; restore lazily, without starting resources
visible boolean | undefined Hidden instances retain state and should suspend unnecessary work
workspace SessionWorkspaceSource Discriminated engine or Desktop Cloud authority; described below
sessionId string | null null on the new-session surface, before a record exists
scopeId string Opaque shell-owned dock-layout identity for transient UI state; never workspace authority
context SessionContext | undefined undefined until a project is chosen
hydrated boolean false while the workspace is still loading
activeTabId string | null This instance’s provider-local resource tab; null means render a default surface. Controllers also receive null
requestFocus (tabId?: string) => void brings your tool to the front of the dock strip

SessionContext is { kind: 'dir'; path: string; coreUrl?: string; worktree?: string }.

SessionWorkspaceSource has two authority branches:

  • engine: { authority: 'engine', context, hydrated }. The host can address context.path through its engine. The Cloud browser also uses this branch because its engine is the sandbox.
  • cloud: { authority: 'cloud', organizationId, sessionId, state }. Desktop brokers the Cloud session’s files; the private sandbox root is not a local filesystem path. state is connecting, ready, reconnecting, paused, read-only, unavailable, or access-lost. paused is the normal resting state of a finished session whose sandbox was released and can resume, so present it neutrally; unavailable means the workspace cannot come back. Before a session exists, sessionId is null, state is unavailable, and organizationId may also be null.

Use workspace.authority to select the supported file path. The older context and hydrated props are only the engine projection. A dock view that supports engine files alone should render an explicit unavailable state for Desktop Cloud authority. Lifecycle example.

Palette entries. Strict.

Field Type Required Notes
id string yes non-empty
title string yes non-empty; the palette label
keybinding string no e.g. "mod+shift+k"
category string no palette grouping
scope string no non-empty. focused-view | focused-slot | global, or a custom string

Declaring a command does not give it behavior — ctx.api.commands.register supplies the handler, and nothing registers on your behalf. The declaration is what the marketplace listing shows and what the contract check holds your activation to; a declared command your activation never registers never reaches the palette, and the contract check fails the build naming it.

Value Meaning
true Scan <extension root>/skills
false Register nothing
"some/dir" Load a relative skill root; absolute paths and escapes are rejected
["a", "b"] Load each relative skill root; results accumulate under one extension id

The string forms name directories, not individual files. A root containing SKILL.md is one skill; "." names the extension root. Otherwise, its immediate child directories containing SKILL.md form a collection. A loose <name>.md file is not a skill package. A skills/ directory without this declaration loads nothing. Skill directories and manifests must stay inside the extension; symlinks escaping it are rejected.

wamp ext pack currently copies skill packages from skills/ only. Use "skills" or true for a packed WAMP extension; custom roots work in a directly loaded extension directory.

Declares service ids this extension intends to register. Permissive.

Field Type Required Notes
id string yes non-empty; the id other extensions pass to services.require
interface string no documentation only

The declaration is bookkeeping; ctx.api.services.register does the actual registering. Consumers read a registered service with ctx.api.services.require (throws if the id isn’t registered yet) or subscribe with ctx.api.services.onRegister to wait for it.

User-editable settings surfaced in the host’s settings UI. Strict.

Field Type Required Notes
key string yes ^[a-zA-Z][a-zA-Z0-9._-]*$
title string yes non-empty
description string no helper text
kind enum yes boolean | string | number | enum
default any no returned by ctx.api.settings.get until the user stores an override
enum string[] no the choice list for kind: 'enum'

Reads and writes go through ctx.api.settings, persisted in the extension’s storage under the key prefix settings:.

Display and behavior hints for tools, keyed by tool name. Strict.

Field Type Required Notes
id string yes non-empty; the tool name
icon string no lucide-react export name
label string no display label on the tool card
category string no grouping
persistenceQuota number no >= 0
compactable boolean no the result may be dropped during compaction
longRunning boolean no relaxes execution timeouts
readOnly boolean no the tool makes no changes

Each entry registers a runnable MCP server under <extensionId>_<serverId>. Permissive, but with one cross-field rule: transport: 'http' or 'sse' requires url; anything else requires command. Violating it fails the manifest with the message “stdio MCP servers require command; http/sse MCP servers require url.”

Declare an npm-packaged server as "command": "npx", "args": ["-y", "<name>@<exact version>", …]. WAMP installs that exact pin once into its own package store and then runs the package’s bin directly, so later starts need neither npm nor the network. A range, a tag or any other launcher is run exactly as declared.

A server the extension ships itself names its files with a value that starts with ${extensionDir}/, in command, args or an envVars default — the rule contributes.agentRuntimes uses. WAMP replaces it with the installed path when the extension loads:

{ "id": "words", "command": "node", "args": ["${extensionDir}/runtime/server.mjs"] }

Keep the server under runtime/, which wamp ext pack ships as-is; every build replaces dist/. The token must start the value and name a path inside the extension. wamp ext pack refuses any other use of it, and a path that is not in the archive. The server’s working directory is the open project, not the extension, so it finds its other files from its own path (import.meta.url).

Other ${NAME} and ${NAME:-default} references in command and args expand when the server starts, from its envVars values and then the engine’s environment.

Field Type Required Notes
id string yes non-empty
transport enum no stdio | http | sse. Absent means stdio
command string no required for stdio; non-empty
args string[] no stdio only
envVars array no surfaced in Settings so users can edit credentials
envVars[].key string yes non-empty
envVars[].description string no —
envVars[].required boolean no —
envVars[].default string no —
url string no required for http/sse; must parse as a URL
headers Record<string,string> no http/sse only
oauth.clientId string no non-empty; skips dynamic registration
oauth.flow "device_code" no requires oauth.clientId; absent keeps authorization code + PKCE
wampAuth.audience string no WAMP-managed identity for a registered resource slug; http/sse over HTTPS only

Prefer http (Streamable HTTP) over sse; sse exists because several hosted servers still ship SSE-only endpoints. OAuth flows are run by the host — the extension never sees tokens. A pre-registered public client may select device authorization with { "clientId": "…", "flow": "device_code" }; the bundled GitHub MCP connector uses this path. wampAuth is mutually exclusive with oauth and a static Authorization header. The host requires the URL origin to match the origin registered for that audience; see Connected resources.

An external process that can answer a chat turn — the agent analogue of an MCP server. Strict at every level, including the nested home, usage, limits, and identity objects. command, args, env and a terminal login’s command and args name a file the extension ships the way an MCP server does: a value starting ${extensionDir}/.

Field Type Required Notes
id string yes ^[a-z][a-z0-9-]*$; stored on the chat
label string yes non-empty
transport literal yes "acp" — the only accepted value
command string yes non-empty; executable speaking the protocol on stdio
args string[] no —
env Record<string,string> no layered over the inherited environment
icon string no brand-icon registry key
requires string no the underlying CLI, probed to report “not installed”
modelAccess literal no "host" requests the host’s metered model plane; requires the extension’s ai permission
steering.method string yes within steering ^_[a-zA-Z0-9][a-zA-Z0-9._/-]*$; private ACP request for mid-turn delivery
steering.idleBehavior literal yes within steering "promptRequired"; an idle request must reject without consuming its content
sessionFork.point literal yes within sessionFork "jetbrains.air.v1"; qualified exact-message native forks, requiring live fork and resume/load capabilities
tools string[] no WAMP tools lent over MCP. Omitted lends only present_files; [] lends nothing
authMethods array no min 1 entries; ACP method ids or declarative headless login commands, in order. Omitted means every ACP method
home.env string yes within home non-empty; env var that repoints the agent at a WAMP-owned directory
home.credentials string[] yes within home min 1; files inside that directory carrying the login
home.defaultDir string no inherited machine login location when home.env is unset; read-only
home.continuationRoots string[] no non-empty safe relative POSIX directories eligible for exact native-session resume
skills.handover string | string[] yes within skills "directory", "plugin-root", "agents-root", distinct, tried in order
skills.directory string yes when handover includes "directory" safe relative POSIX directory inside the managed home that receives the skill-set link
mcpServers string[] no non-empty namespaced server ids; WAMP starts a private copy for each ACP session and offers the ones that start within 30 s
usage.kind "http" | "service" yes selects the usage provider
usage.url string yes for HTTP usage must parse as a URL
usage.credential string yes for HTTP usage non-empty; file in the home holding the token
usage.tokenPath string yes for HTTP usage non-empty; dotted path to the token in that file
usage.headers Record<string,string> no HTTP form only
usage.windows array yes for HTTP usage min 1 of { label, path }, both non-empty; dotted paths to { utilization, resets_at }
usage.service string yes for service usage extension-owned service id implementing AgentRuntimeUsageProvider
limits.patterns string[] yes within limits non-empty quota-exhaustion patterns; case-insensitive
limits.transient string[] no non-empty throttle patterns; report without benching or rotating the account
limits.terminalAuth array no exact structured authentication failures: { code, data?: { path, equals } }; code is integer and equals is string, finite number, boolean, or null
identity.kind "declarative" | "service" no for declarative; yes for service omitted remains the backward-compatible declarative form
identity.credential string yes for declarative identity non-empty
identity.path string no dotted path to the identity inside credential
identity.claim string no JWT claim name, when the value at path is a JWT
identity.url string no must parse as a URL — the endpoint mode
identity.tokenPath string no dotted path to the bearer, for endpoint mode
identity.headers Record<string,string> no —
identity.paths string[] no min 1; response paths tried in order
identity.service string yes for service identity extension-owned service id implementing AgentRuntimeIdentityProvider

steering requires both this declaration and the agent’s initialize response _meta.steering.supported; a declaration alone does not enable delivery.

sessionFork is optional. Declare it only after verifying that native forks preserve the exact selected prefix, including compacted context. WAMP retains private message boundaries and loads the fork before its first prompt; it does not clone vendor transcript files. Unsupported adapters and older conversations without those boundaries continue through retained portable history.

modelAccess: "host" lets the host choose the model endpoint and credential; you cannot override those through env.

args[], env values, and object authMethods[].args[] may contain a whole ${npmPackage:<name>@<x.y.z>} value, for example ${npmPackage:@agentclientprotocol/claude-agent-acp@0.84.0}. The version must be exact, without build metadata. The engine replaces the value with an absolute, read-only package directory before preparing credentials or starting the process. It installs runtime packages without lifecycle scripts, so an adapter must use shipped files as-is and write derived data under its own runtime cache. Commands and substrings cannot contain this placeholder. A pack using it must declare compat.pluginApi: ">=4.0.0 <5.0.0"; older hosts refuse the pack.

skills hands the user’s WAMP skill set to the agent. WAMP stages one content-addressed copy of the set in its own store and hands it over through the first handover kind whose precondition holds. "directory" links the staged set at skills.directory inside a WAMP-managed home and therefore requires home; it never writes into the machine’s own vendor login. "plugin-root" passes the set as a local plugin directory for one session, in both adapter fields that accept one: Claude Code’s _meta.claudeCode.options.plugins and Grok Build’s _meta.pluginDirs. "agents-root" passes it as an extra .agents/skills root through _meta["dev.wamp/skillRoots"], which the Codex adapter accepts without widening its sandbox. When no kind applies, the runtime reports the omission instead of copying skills anywhere. Declare handover explicitly; a directory value also requires a managed home. mcpServers selects configured MCP servers independently of tools, which lends the WAMP tool registry. A selected server is offered when it starts. One that does not start within 30 s is left out of that session, the transcript says why, and the turn runs without it.

Each authMethods entry is either an ACP method id string or a strict object { id, name, description?, command, args?, input? }. Use the object only when the ACP agent exposes a local-browser login but the same vendor CLI provides an official remote-safe command. WAMP runs it in the managed home; set input: false for device-code commands that poll after printing their URL/code.

usage and identity are only meaningful alongside home, which supplies the managed credential layout and optional inherited defaultDir. The HTTP usage form covers one bearer GET; declarative identity covers JSON, JWT, and one bearer GET. For dynamic headers, RPCs, fallback endpoints, vendor payloads, or dynamic auth-file layouts, register an extension service and declare { "kind": "service", "service": "…" }. The service must belong to the same extension and implement AgentRuntimeUsageProvider or AgentRuntimeIdentityProvider. It receives the credential home currently used by the runtime plus an abort signal and must remain read-only. WAMP owns service ownership checks, timeout, normalization, caching, and persistence of the credential-free result. An inherited machine login is read in place; it is never copied into WAMP or added to account rotation by a metadata probe.

Namespace prefixes this extension claims for its own IPC channels.

Constraint Value
Type string[]
Each entry ^[a-z][a-z0-9._-]*$

Claims happen at registration, before first use. A channel whose prefix is not declared is refused by ctx.api.ipc.broadcast, synchronously. Two extensions claiming the same prefix is a collision: the second faults and does not load. Prefix with your extension id.

Two failure modes, and which one you get depends on where the typo is. A strict object rejects unknown keys and names the offending field; a permissive one accepts and silently ignores them.

Object Unknown keys
Manifest root ignored
contributes ignored
contributes.pages[] rejected
contributes.commands[] rejected
contributes.settings[] rejected
contributes.toolMetadata[] rejected
contributes.agentRuntimes[] and its nested objects rejected
build and build.loaders[] rejected
{ "auth.outbound": [...], "auth.resource": [...] } and each auth.resource entry rejected
contributes.services[] ignored
contributes.views[] ignored
contributes.mcpServers[] ignored

A rejected manifest is reported as a failed scan with the field path, and the extension does not appear. An ignored key costs you a debugging session, so when something you declared has no effect, check the spelling of the key one level up first.

Every optional field is left out except the ones this extension needs. It has a page, a palette command, and a tool contributed at runtime from main.

{
"name": "Release Watch",
"description": "Lists release feeds and files a task on refresh.",
"version": "1.4.0",
"compat": { "pluginApi": "^4.0.0" },
"icon": "Radar",
"author": "Example Corp",
"main": "dist/main.js",
"permissions": ["notifications"],
"activationEvents": ["onStartupFinished"],
"contributes": {
"pages": [
{ "id": "release-watch", "title": "Releases", "icon": "Radar", "context": "both" }
],
"commands": [
{ "id": "release-watch.refresh", "title": "Releases: Refresh now", "keybinding": "mod+shift+r" }
],
"toolMetadata": [
{ "id": "release_watch_check", "label": "Check releases", "readOnly": true }
],
"ipcNamespaces": ["release-watch"]
}
}