Skip to content
Developer Preview

MCP servers in plugins

After this page you will be able to declare an MCP server in a plugin, predict the names its tools get, approve it, and explain why a plugin server carries no credentials. For a complete, tested plugin read An MCP server in a plugin. For the exact manifest fields read the Manifest reference.

An MCP (Model Context Protocol) server gives the model tools that run outside Muse Code. Declaring one in a plugin means the person who installs the plugin gets the server without editing settings.json, and gets it in every session on that machine, including sessions served to SDK clients.

Once approved, the server starts with each new session and its tools are registered before the first model request. The model sees them as ordinary tools and calls them under the usual approval rules. The standalone form, a server declared under mcpServers in settings.json or in a project .mcp.json, is documented in MCP servers; this page covers what changes when the server ships inside a plugin.

Each entry in capabilities.mcpServers declares one server. The transport field picks the shape.

Field stdio http
id Required. Capability id, unique among the plugin’s servers. Same.
transport "stdio", or omit it (stdio is the default). "http", required.
command Required. Non-empty array of strings, run as argv with no shell. Ignored.
url Ignored. Required, non-blank. A streamable HTTP endpoint.

A stdio server is a program Muse Code starts and talks to over its stdin and stdout:

{
"id": "forecast",
"transport": "stdio",
"command": ["python3", "mcp/server.py"]
}

An HTTP server is a URL Muse Code connects to:

{
"id": "docs",
"transport": "http",
"url": "https://example.com/mcp"
}

Any other transport value is an error (invalid-manifest-schema), a stdio entry without a command array is an error (missing-capability-command), and an http entry without a url is an error. Fields such as env, args, headers and cwd are not read from a plugin entry; the What plugin servers cannot do today section explains the consequences.

A stdio command element that spells a relative path, such as mcp/server.py, must exist as a regular file inside the plugin directory. Validation fails otherwise (missing-capability-path), and a path that escapes the plugin directory is refused (unsafe-path).

On the wire, a stdio server may frame messages either as newline-delimited JSON or with LSP-style Content-Length headers. Muse Code probes for newline-delimited JSON first and falls back to Content-Length, so a server that writes one JSON object per line and flushes after each one works without configuration.

Muse Code registers each plugin server under the name plugin:<plugin-id>:<server-id> and builds every model-facing tool name from that:

mcp__plugin_<pluginId>_<serverId>__<tool>

Every character in the plugin id, the server id or the tool name that is not an ASCII letter, digit or underscore becomes _. The colons in the server name and the hyphen in a plugin id are replaced the same way. For the plugin forecast-tools with server forecast exposing describe_forecast, the registered name is:

mcp__plugin_forecast_tools_forecast__describe_forecast

Two consequences follow. Match on the registered name when you write approval rules or tool filters, not on a name you rebuild from the ids. And choose ids that stay distinct after sanitisation: get.forecast and get-forecast on the same server collapse to the same name.

An SDK client’s per-session mcpServers map may not reuse a plugin server’s id or its plugin:<pluginId>:<serverId> key. Plugins in SDK sessions has the details of that check.

MCP servers are runtime capabilities. Installing a plugin never starts one. muse plugins inspect <plugin-id> lists the server with a review status: review_needed after install, trusted_enabled once you approve the current definition, and modified when the package changed after your decision. Only a trusted_enabled server starts, and it starts with the next new session. Trust, review and scopes describes all six statuses and what moves a capability between them.

Approve one server or the whole plugin:

Terminal window
muse plugins approve forecast-tools:mcp_server:forecast
muse plugins approve forecast-tools

muse plugins reject records the opposite decision. Both accept the stable id plugin:<plugin-id>:mcp_server:<server-id> as well.

An approval binds to a definition hash that covers the entry’s fields and the package digest. Editing the server script, the manifest or any other file in the plugin and running muse plugins update changes that hash, and the server drops to modified with the diagnostic modified_definition_hash. Nothing about the command or the URL can change behind an approval you gave. Trust, review and scopes explains the same mechanism for hooks and the other reviewed kinds.

  • A trusted, enabled server starts during session startup, before the first model request, so the model’s tool list is complete from the first turn. This applies to the terminal UI, muse exec and sessions served over the SDK.
  • Approvals, enables, disables, updates and removals take effect in the next new session. A running session keeps the servers it started with.
  • Plugin servers are optional. A server that fails to start, or whose initialize handshake takes longer than 30 seconds, does not stop the session; the session runs without that server’s tools.
  • A stdio server is terminated when the session ends. Write the server so it also exits on its own when stdin closes, and do not fork long-lived children that would outlive it.
  • Each tool call gets 300 seconds. A slower call is reported to the model as a failure.

A plugin stdio server starts with a cleared environment. It receives only these variables:

Variable Value
The pass-through allowlist The same short list hooks get, HOME, PATH and a handful of user, locale and temporary-directory variables, copied from the Muse Code process when set; Hook events and payloads names each one.
MUSE_PLUGIN_ID The plugin id, for example forecast-tools.
MUSE_PLUGIN_ROOT The installed copy of the plugin directory, inside the plugin cache.
MUSE_PLUGIN_DATA_DIR A per-plugin directory for state, <data dir>/plugins/data/<plugin-id>.
PLUGIN_ROOT, CLAUDE_PLUGIN_ROOT Aliases of MUSE_PLUGIN_ROOT for scripts written for other hosts.
PLUGIN_DATA, CLAUDE_PLUGIN_DATA Aliases of MUSE_PLUGIN_DATA_DIR.
MUSE_SESSION_ID The id of the session that started the server, applied last.

Nothing else reaches the server: no proxy settings, no language toolchain variables, and never a provider API key. Hooks get the same variables except MUSE_SESSION_ID, which only MCP children receive.

The MUSE_PLUGIN_DATA_DIR directory may not exist yet, so create it before writing to it. Native hook and MCP commands get no placeholder substitution: Muse Code rewrites exactly one argv element, the one that spells a relative path declared in the manifest, to the installed copy, and your script should read MUSE_PLUGIN_ROOT to find any other file. The server’s working directory is the session’s working directory, not the plugin directory.

The server’s stderr is discarded. If you need a trace, write to a file under MUSE_PLUGIN_DATA_DIR.

Plugin MCP server entries have no env, headers or credentials, so authenticated servers belong in settings.json instead (MCP servers shows that entry). In detail:

  • There is no env map, and the process environment is cleared, so a stdio server cannot receive a token through the environment.
  • There is no headers map for HTTP entries, and muse mcp login only applies to servers named in settings.json, so an HTTP plugin server cannot send an Authorization header or use OAuth.
  • There is no cwd field. The server runs in the session’s working directory.
  • There is no per-server framing, tool_timeout_sec or required setting. Plugin servers use auto-detected framing, the 300 second tool budget and optional mode.

A server that needs a secret can still read one from a file the user places under HOME or under MUSE_PLUGIN_DATA_DIR, but nothing in the plugin format provisions it. If your server needs a credential to be useful, document a settings.json entry instead of packaging it in a plugin.

Tools from an approved plugin server follow the ordinary MCP approval path. Under the default on-request approval mode a call prompts the user unless a permission rule already allows it. Permission rules match on the registered mcp__plugin_... name. SDK clients see these prompts as ordinary approval requests; Approvals covers the decide flow.

One rule is specific to admitted local servers, and approved plugin servers count as such. If the server’s tools/list response annotates a tool with readOnlyHint: true, a call to that tool under on-request mode is admitted without a prompt; the event log records the decision as allow:mcp_read_only_hint. An explicit deny or prompt rule for the tool still wins, and the shortcut applies only to on-request mode. The server declares the hint about itself, so treat it as part of what you review before approving: only set it on tools that change nothing, and be wary of a third-party server that marks everything read-only.

Symptom Likely cause Fix
muse plugins validate reports missing-capability-command A stdio entry has no command array, or the array is empty. Add "command": ["<program>", ...].
muse plugins validate reports missing-capability-path A relative path in command does not point at a file in the plugin. Fix the path or add the file.
inspect shows review_needed after install Servers never start until approved. muse plugins approve <plugin-id>.
inspect shows modified You changed the package after approving. Re-read the change, approve again, start a new session.
The tool is missing from a session you already had open Servers load at session start. Start a new session.
The tool is missing from a new session and there is no error The server failed to start or did not answer initialize within 30 seconds; plugin servers are optional, so the session continued. Run the server by hand with a pipe (the example page shows how), check that the program in command is on PATH, and log to a file under MUSE_PLUGIN_DATA_DIR. In the terminal UI /mcp lists the session’s servers under plugin:<plugin-id>:<server-id>.
The server cannot find a data file next to the script Only the one declared relative path is rewritten, and the working directory is the session’s. Build paths from MUSE_PLUGIN_ROOT.
Writing to MUSE_PLUGIN_DATA_DIR fails The directory is not created for you. Create it first.
The server needs a token and never gets it Plugin entries carry no env, headers or credentials. Declare the server in settings.json instead.
The model calls the tool without asking The tool declares readOnlyHint and the mode is on-request. Add a prompt or deny rule for the registered tool name.