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.
Two sources: inline and file
Section titled “Two sources: inline and file”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"] } }}Transport is inferred
Section titled “Transport is inferred”You never specify transport explicitly. The loader picks one based on the fields you set:
| Field present | Transport |
|---|---|
command: | stdio |
url: | streamable-http |
# stdio — the runtime spawns the processintel-server: command: node args: [mcp/intel.js]
# HTTP — the runtime opens a streaming connectionremote-intel: url: https://mcp.example.com/intel headers: Authorization: Bearer ${INTEL_API_TOKEN}Setting both is a validation error.
Variable interpolation
Section titled “Variable interpolation”Two kinds of placeholders are recognized in command, args, url, headers, and env:
| Form | Resolved at | Source |
|---|---|---|
${CAPABILITY_ROOT} | Parse time | Capability directory on disk |
${VAR} | Connect time | os.environ |
${VAR:-default} | Connect time | os.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.
Working directory
Section titled “Working directory”Stdio servers run with the capability root as their working directory. Relative paths in command, args, or config files resolve against that root.
Python MCP servers with uv
Section titled “Python MCP servers with uv”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.
Flag gating
Section titled “Flag gating”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.
Failure isolation
Section titled “Failure isolation”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.
Reconnecting
Section titled “Reconnecting”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.