Ship MCP servers with a plugin. A root mcp.json declares servers the assistant connects on install, and their tools land in the same catalog as workspace-configured MCP tools.
This is a declaration, not a TypeScript surface: there is no mcp/ directory and nothing to import from @vellumai/plugin-api. The file follows the Agent Plugins 1.0.0 MCP schema.
Use mcp.json when the capability already exists as an MCP server and you want it to come up with the plugin. Prefer a native plugin tool when you are writing the action yourself: a plugin tool has an explicit risk level, a typed execute context, and no extra process.
A user can also add MCP servers in settings without a plugin. A plugin is what lets you version, install, and distribute those servers with the rest of the capability.
Place mcp.json at the plugin root, next to package.json:
{
"$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
"mcpServers": {
"example": { "type": "streamable-http", "url": "https://mcp.example.com" }
}
}stdio, sse, and streamable-http transports are supported:
{
"mcpServers": {
"local-tools": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@example/mcp-server"],
"env": { "DATA_DIR": "${PLUGIN_DATA}" }
},
"remote-sse": {
"type": "sse",
"url": "https://mcp.example.com/sse"
}
}
}A missing file is skipped. An invalid file disables MCP for that plugin only. An invalid individual entry disables only that entry. Sibling plugins keep their servers.
Plugin servers share one namespace with workspace MCP servers. The assistant qualifies each id with the plugin name, then collapses the redundant case:
| Plugin name | mcp.json key | Server id | Tool names |
|---|---|---|---|
example | example | example | mcp__example__<tool> |
example-tools | search | example-tools__search | mcp__example-tools__search__<tool> |
Two plugins claiming the same id is a skip, not a shadow. The second declaration is logged and dropped so the first plugin's tools stay put.
On stdio servers, ${PLUGIN_ROOT} and ${PLUGIN_DATA} interpolate in args, env values, and cwd. They never interpolate in command, a URL, or a header, so a manifest cannot use them to build the executable path itself.
cwdis accepted by the spec but has no host equivalent. It is ignored, with a warning, and the server runs in the assistant's working directory. Put absolute paths in args or env via those variables instead.
A plugin cannot ship a credential. The spec defines no portable OAuth or credential-reference fields, and any headers in the file are literal package data. Plugin servers also never resolve the assistant's stored mcp:<serverId>:* credentials: a plugin controls both its server key and its URL, so honoring them would send a workspace credential to an endpoint the plugin chose.
Risk defaults to low, so the tools run without prompting under the default auto-approve threshold. mcp.jsonhas no risk field (the spec defines none). The review is the marketplace whitelist plus the user's decision to install. A user who wants a different bar sets defaultRiskLevel on a workspace config.json entry of the same id, which outranks the plugin's declaration and replaces it wholesale (transport included). Each server is capped at 20 tools (maxTools), the same default a workspace MCP entry ships with.
The assistant connects these servers on start and registers their tools alongside workspace-configured ones. Installing, removing, upgrading, enabling, or disabling a plugin reconnects the set as part of that operation: its servers come up and go down with the plugin, no restart involved.
A disabled plugin (.disabled sentinel) contributes no MCP servers, matching hooks, tools, and routes. A directory with no loadable package.json is ignored even if it has an mcp.json. assistant mcp list shows plugin servers with their originating plugin, and status: declared for one the assistant holds no live connection to.
example/
├── package.json
├── mcp.json
└── ...{
"$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
"mcpServers": {
"example": {
"type": "streamable-http",
"url": "https://mcp.example.com"
}
}
}Reach for mcp.json when you are packaging an existing MCP server so it installs with the plugin, rather than asking the user to paste a URL into settings. If you are implementing the action yourself, write a plugin tool instead.