---
title: MCP Servers
description: "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"
canonical: https://giglabo.com/heretic/docs/heretic-cli/configuration/mcp-servers
locale: en
---

# MCP Servers

> Markdown twin of https://giglabo.com/heretic/docs/heretic-cli/configuration/mcp-servers
> Fetch this instead of the HTML page: same content, a fraction of the bytes.
> Site structure and the full page list for agents: https://giglabo.com/llms.txt

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

```bash
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
```

> **Note: 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 |

```json
{
  "mcpServers": {
    "context7": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@upstash/context7-mcp"],
      "env": {}
    }
  }
}
```

Per entry the transport is decided like this:

- `type: "http"`, or no `type` plus a string `url` → **HTTP**: `url` is required, `headers` optional
- otherwise → **stdio**: `command` is required, `args` optional

`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

```yaml
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

```yaml
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:

```yaml
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

```bash
heretic-cli run claude --mcp ./my-servers.json
heretic-cli run claude --mcp '[{"name":"fs","command":"npx","args":["-y","@modelcontextprotocol/server-filesystem","/workspace"]}]'
```

> **Warning: --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:

```json
{
  "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: ["*"]`:

```json
{
  "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 |

```yaml
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**

```yaml
# ~/.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**

```yaml
# ~/.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**

```yaml
# .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](https://giglabo.com/heretic/docs/heretic-cli/commands/agents) — the `agents mcp` subcommand in detail
- [Agent Profiles](https://giglabo.com/heretic/docs/heretic-cli/configuration/agent-profiles) — `mcp`, `mcp_file`, `mcp_override` in the schema
- [Local Overrides](https://giglabo.com/heretic/docs/heretic-cli/configuration/local-overrides) — project-specific servers

## Related

- HTML version of this page: https://giglabo.com/heretic/docs/heretic-cli/configuration/mcp-servers
- Site map for agents: https://giglabo.com/llms.txt
