MCP Servers
Configure Model Context Protocol servers for containerized agents — accepted JSON formats, stdio and HTTP transports, per-agent mount paths and transformations, merge order, and the existing-workspace-config rule
MCP (Model Context Protocol) servers give the containerized agent its tools. heretic-cli takes server definitions in any of the common JSON shapes, normalises them, writes one config per session, and mounts it where the agent expects to find it.
Four ways to define servers
| Way | Where | Best for |
|---|---|---|
heretic-cli agents mcp <name> | writes into the profile (or the local override with --local) | importing a snippet from a README or VS Code |
mcp: in the profile | ~/.heretic/agents/<name>.yaml | servers you always want |
mcp_file: in the profile or override | any JSON file, ${VAR} interpolated | reusing an existing .vscode/mcp.json |
--mcp on run | one run only | experiments and CI |
Importing JSON
heretic-cli agents mcp claude --file ./mcp.json # read a file
heretic-cli agents mcp claude # opens $EDITOR for a paste
heretic-cli agents mcp claude --local --file .vscode/mcp.json
Paste mode opens your editor
Without --file, the command opens $EDITOR (then $VISUAL, else vi) so you can paste multi-line JSON. Save and close to submit. Servers merge by name: same name replaces, new names are appended, unrelated servers are kept.
Accepted JSON formats
Auto-detected from the top-level key — all three are equivalent:
| Format | Top-level key | Used by |
|---|---|---|
{ "mcpServers": { … } } | mcpServers | Claude Code, Copilot CLI |
{ "servers": { … } } | servers | VS Code |
{ "<name>": { "command": … } } | bare map | READMEs, ad-hoc |
{
"mcpServers": {
"context7": {
"type": "stdio",
"command": "npx",
"args": ["-y", "@upstash/context7-mcp"],
"env": {}
}
}
}
Per entry the transport is decided like this:
type: "http", or notypeplus a stringurl→ HTTP:urlis required,headersoptional- otherwise → stdio:
commandis required,argsoptional
env is always carried through. A malformed entry aborts the import: MCP server '<name>' in <source> is missing required field 'command' (or … 'url' for http transport).
Inline definition
mcp:
- name: filesystem
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
- name: github
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_TOKEN: "${GH_TOKEN}"
- name: remote-tools
type: http
url: https://mcp.example.com/sse
headers:
Authorization: "Bearer ${MCP_TOKEN}"
File-based config
mcp_file: ${CWD}/.vscode/mcp.json # any of the three formats
The path is interpolated and ~ is expanded. A missing file fails with MCP file not found: <path>; invalid JSON with Failed to parse MCP file as JSON: <path>: ….
Merging mcp_file with inline mcp
Within one layer, file servers form the base and inline entries override them by name, appending new ones:
mcp_file: ~/shared/mcp-servers.json
mcp:
- name: trello # overrides "trello" from the file
command: npx
args: ["-y", "mcp-remote", "http://localhost:9090/sse"]
- name: extra-server # appended
command: npx
args: ["-y", "my-mcp-server"]
Runtime override
heretic-cli run claude --mcp ./my-servers.json
heretic-cli run claude --mcp '[{"name":"fs","command":"npx","args":["-y","@modelcontextprotocol/server-filesystem","/workspace"]}]'
--mcp takes a JSON array
--mcp uses the profile's mcp shape — an array of server objects — not an mcpServers map. It is also unavailable in the heretic-cli <profile> shorthand.
Where the config is mounted
The generated file is written to .heretic/temp/<session>/.mcp.json in {"mcpServers": { … }} form, then bind-mounted according to agent_type:
agent_type | Container path(s) |
|---|---|
claude, aider, generic | /workspace/.mcp.json |
copilot-cli | /root/.copilot/mcp-config.json and /home/agent/.copilot/mcp-config.json |
Per-agent transformation
Claude / aider / generic — a clean entry per server; unknown source fields are dropped:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"],
"env": { "KEY": "value" }
}
}
}
Copilot CLI — every entry additionally gets type (defaulting to "stdio") and tools: ["*"]:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"],
"type": "stdio",
"tools": ["*"]
}
}
}
HTTP servers are written as type, url and optional headers instead of command/args.
Existing workspace config wins by default
If your project already contains a usable MCP config, heretic does not mount its generated one:
agent_type | Path checked in the project |
|---|---|
claude, aider, generic | <project>/.mcp.json |
copilot-cli | <project>/.copilot/mcp-config.json |
"Usable" means: the file exists, is non-empty, parses as JSON, and contains a non-empty mcpServers object. A zero-byte or broken file counts as absent — that check exists precisely so a stray empty .mcp.json cannot silently leave the agent with no tools.
| Situation | Behaviour |
|---|---|
| no existing config | mount the generated config |
existing usable config, mcp_override: false (default) | keep yours, mount nothing |
existing usable config, mcp_override: true | mount the generated config over it |
mcp_override: true
Configuration layers
global profile mcp / mcp_file
↓ replaced entirely
local override mcp / mcp_file
↓ replaced entirely
--mcp on the command line
The mcp array is replaced, never merged, between layers. Merge-by-name only happens between mcp_file and inline mcp inside the same layer — so to extend a profile's servers from a project, re-list the ones you want to keep, or move the shared ones into a mcp_file both layers reference.
Session files
.heretic/temp/<session>/
└── .mcp.json regenerated on every run
The file is rewritten each run and left in place afterwards (it is cleaned up when a run fails to start). Session directories are never pruned automatically — delete them by hand to reset an agent.
Examples
Claude agent with two servers
# ~/.heretic/agents/claude.yaml
image: giglabo/claude-heretic:latest
runner: docker
provider: anthropic
agent_type: claude
mcp:
- name: filesystem
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/workspace"]
- name: github
command: npx
args: ["-y", "@modelcontextprotocol/server-github"]
env:
GITHUB_TOKEN: "${GH_TOKEN}"
Copilot agent reusing the VS Code config
# ~/.heretic/agents/copilot.yaml
image: heretic-agent-copilot:latest
runner: docker
provider: copilot
agent_type: copilot-cli
mcp_file: ${CWD}/.vscode/mcp.json
mcp_override: true
Project-specific server via a local override
# .heretic/cli/claude.yaml
extends: claude
mcp:
- name: project-docs
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/workspace/docs"]
mcp_override: true
Troubleshooting
| Symptom | Cause |
|---|---|
| the agent sees no MCP servers | the project has a usable .mcp.json of its own — set mcp_override: true, or edit that file |
| a server disappeared after adding one locally | the override replaced the whole mcp array — re-list every server |
mcp[i].command must be non-empty | a stdio entry without a command, usually an HTTP server missing type: http |
Invalid --mcp value: MCP config must be a JSON array of server objects | you passed an mcpServers map to --mcp |
| the server starts but has no credentials | put the token in the entry's env, referencing a secret: env: { TOKEN: "${MY_SECRET}" } |
Next Steps
- agents — the
agents mcpsubcommand in detail - Agent Profiles —
mcp,mcp_file,mcp_overridein the schema - Local Overrides — project-specific servers