Skip to content

MCP Servers

Ship MCP servers with a capability — stdio and HTTP, inline and file-based, with env interpolation and flag gating.

MCP (Model Context Protocol) servers extend a capability with tools that aren’t Python — shell commands, Node services, remote APIs, or anything with its own lifecycle. Declare them in the manifest and the runtime starts, stops, and supervises them alongside your Python tools.

mcp:
servers:
intel-server:
command: node
args: [mcp/intel.js]
env:
API_BASE: ${INTEL_API_BASE:-https://intel.example.com}

That server starts with the capability, its tools appear in the runtime’s tool registry, and it exits cleanly when the capability reloads.

You can declare MCP servers in two places, and they merge:

mcp:
files:
- .mcp.json
servers:
override-server:
command: node
args: [mcp/override.js]

Inline servers under mcp.servers.<name> live in capability.yaml. They can use flag gating and the full manifest feature set.

File-based servers come from a .mcp.json or mcp.json in the capability root, using the standard mcpServers format that Claude Code, Cursor, and other MCP clients read. The loader auto-discovers these files when mcp: is omitted. On name conflicts, the inline version wins. File-based servers cannot use when: gating — declare them inline if you need conditional loading.

{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["@modelcontextprotocol/server-filesystem", "/workspace"]
}
}
}

You never specify transport explicitly. The loader picks one based on the fields you set:

Field presentTransport
command:stdio
url:streamable-http
# stdio — the runtime spawns the process
intel-server:
command: node
args: [mcp/intel.js]
# HTTP — the runtime opens a streaming connection
remote-intel:
url: https://mcp.example.com/intel
headers:
Authorization: Bearer ${INTEL_API_TOKEN}

Setting both is a validation error.

Two kinds of placeholders are recognized in command, args, url, headers, and env:

FormResolved atSource
${CAPABILITY_ROOT}Parse timeCapability directory on disk
${VAR}Connect timeos.environ
${VAR:-default}Connect timeos.environ, falling back to the default

Connect-time resolution means you can push a capability that references ${INTEL_API_TOKEN} without having the token set locally. The error only fires when the server starts without the variable.

intel-server:
command: ${CAPABILITY_ROOT}/bin/intel
args: ['--config', '${CAPABILITY_ROOT}/config.json']
env:
API_BASE: ${INTEL_API_BASE:-https://intel.example.com}
API_TOKEN: ${INTEL_API_TOKEN}

Unset ${VAR} without a default raises a ValueError at connect time with the name of the missing variable.

Stdio servers run with the capability root as their working directory. Relative paths in command, args, or config files resolve against that root.

For stdio servers written in Python, ship the server as a self-contained PEP 723 script and let uv resolve dependencies at spawn. This is the recommended pattern — no shared venv to manage, dependencies live next to the code, and the same script works identically in local dev and a sandbox.

mcp:
servers:
intel:
command: uv
args: ['run', '${CAPABILITY_ROOT}/mcp_server.py']
#!/usr/bin/env -S uv run
# /// script
# requires-python = ">=3.11"
# dependencies = [
# "fastmcp>=2.0",
# "httpx>=0.27",
# ]
# ///
from fastmcp import FastMCP
server = FastMCP("intel")
@server.tool()
async def lookup(host: str) -> dict:
...
if __name__ == "__main__":
server.run()

uv run reads the /// script block, provisions an isolated environment on first spawn (cached across restarts), and execs the server. The shebang is optional — it lets the file run directly without uv run when you’re iterating locally.

Use when: on an inline server to load it only when a flag is on:

flags:
burp:
description: Route traffic through Burp Suite proxy at :9876
default: false
mcp:
servers:
burp-proxy:
command: node
args: [mcp/burp.js]
when: [burp]

when: takes a list of flag names. The server loads if any flag in the list is true. Empty lists and undeclared flag names are validation errors.

See Flags for the full resolution story.

One MCP server failing to start doesn’t block the rest of the capability. Failed servers produce a health entry you can see in the TUI capability manager, and the runtime keeps going with the servers that did start.

This matters for capabilities that ship multiple integrations: a broken Burp install doesn’t take down your intel server.

The TUI capability manager surfaces a Reconnect action on each server row. From a worker, call client.reconnect_mcp_server(capability, server_name) to force a fresh connection — see the Worker API reference.