Skip to content

MCP client

MCP (Model Context Protocol) lets Atomic Agent borrow tools from other programs. Point it at an MCP server — a GitHub helper, a database reader, a search service — and that server’s tools show up in the agent’s toolbox right next to the built-in browser and filesystem tools. The model can call them without you writing any glue code.

You configure servers in one place (config.json), Atomic Agent connects to them at startup, and their tools get qualified names like mcp.github.search_issues so they never collide with native tools.

What you can do with it

Add tools without code

Drop a server config into config.json. Its tools join the registry automatically — no rebuild, no plugin API.

Read resources & prompts

Beyond tools, the agent can list and read a server’s resources and fetch its prompt templates via built-in meta-tools.

Stay safe by default

Every MCP tool is approval-gated unless you explicitly mark a server pure_read. Hostile or noisy servers are fenced off.

Add servers live

In the TUI you can add, remove, restart, or toggle servers without restarting the runtime.

Quick start

Add an mcp.servers array to your config.json (under <stateDir>/config.json, default ~/.atomic-agent/):

{
"mcp": {
"servers": [
{
"name": "filesystem",
"enabled": true,
"transport": {
"kind": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/data"]
},
"description": "Local file server"
}
]
}
}

Restart the agent (or, in the TUI, add the server live). At startup the runtime connects every enabled server in parallel, discovers its tools, and registers them under mcp.filesystem.<toolName>. The model can now call them.

How it works

Atomic Agent wraps the official @modelcontextprotocol/sdk in a single McpManager. On start(), the manager connects each enabled server, refreshes its catalog, wraps every discovered tool as a normal ToolDefinition, and registers it in the shared tool registry. From there an MCP tool behaves exactly like a native one — it flows through the same grammar, prompt descriptors, batching, and approval gates.

flowchart TD
    Config["config.json<br/>mcp.servers[]"] -->|bootstrap| Mgr["McpManager.start()"]
    Mgr -->|parallel, isolated| Conn["client.connect()<br/>per server"]
    Conn --> Cat["refreshCatalog()<br/>discover tools / resources / prompts"]
    Cat --> Reg["register mcp.<server>.<tool><br/>in ToolRegistry"]
    Reg --> Resolver["install trust-based<br/>resource-class resolver"]

    subgraph Invoke["Agent emits mcp.<server>.<tool>"]
        Emit["model tool call"] --> Class["resolve resource class<br/>(approval_gated | pure_read)"]
        Class --> Gate["approval gate / batch check"]
        Gate --> Call["client.callTool(rawName, args)"]
        Call --> Project["project heterogeneous<br/>MCP response → text + details"]
        Project --> Compress["compressToolResult<br/>(max 8 KB)"]
    end

    Reg -.-> Emit
    Resolver -.->|O(1) lookup| Class

Three things make this work cleanly:

  • Qualified names. Tools are registered as mcp.<server>.<rawName>. The server name is a kebab-case namespace (max 32 chars, no dots) so names never clash with native tools or each other.
  • Grammar & descriptors rebuilt on demand. When the first server connects, the runtime registers the MCP meta-tools and rebuilds the GBNF grammar plus tool descriptors so the model can actually emit the new names. Live-added servers trigger the same rebuild via refreshMcp().
  • Fail-closed trust. A dynamic resource-class resolver is installed at bootstrap even with zero servers, so unknown or hallucinated server names always land in the safe, approval-gated lane.

Configuring servers

Each entry in mcp.servers[] is an McpServerConfig:

FieldRequiredMeaning
nameyesUnique kebab-case namespace (max 32 chars, no dots). Becomes the mcp.<name>.* prefix.
enabledyesWhether to connect at bootstrap.
transportyesHow to reach the server — stdio, streamable_http, or sse (see below).
trustnoapproval_gated (default) or pure_read. Controls batching and approval.
envnoPer-server env var overrides (stdio only). Merged on top of the process env.
descriptionnoOne-liner shown in the TUI and logs.

Transports

Spawns a local process and talks to it over stdin/stdout. Best for tools you run on the same machine.

{
"name": "github",
"enabled": true,
"transport": {
"kind": "stdio",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"cwd": "/optional/working/dir"
},
"env": { "GITHUB_TOKEN": "ghp_..." }
}

The cwd defaults to the agent’s working directory if omitted. Stdio servers inherit process.env before your per-server env overrides are applied.

Trust levels and safety

By default, every MCP tool is approval-gated: the model can’t run it without you approving the call (TUI modal, CLI prompt, Telegram button, or HTTP webhook). This is the safe default for tools you don’t fully control.

If a server only reads data and you trust it, mark it pure_read. Its tools then opt into parallel batching alongside other read-only tools like os.fs.read — faster, but no approval prompt.

{
"name": "docs-reader",
"enabled": true,
"trust": "pure_read",
"transport": { "kind": "stdio", "command": "my-docs-mcp", "args": [] }
}

The trust model is deliberately fail-closed:

  • An unspecified, disabled, failed, or absent server resolves to approval_gated — never pure_read.
  • Approval-gated tools can’t ride in a multi-call batch. If the model tries, the batch is trimmed and it retries the call solo.
  • Even a hallucinated mcp.ghost.do_thing lands in the safe lane because the resolver is always installed.

Resources and prompts

Once any MCP server is connected, four read-only meta-tools become available to the agent (they’re not registered when zero servers exist):

ToolPurposeArgs
mcp.resource.listList a server’s resourcesserver, optional limit (1–100)
mcp.resource.readRead one resourceserver, uri
mcp.prompt.listList a server’s prompt templatesserver, optional limit (1–100)
mcp.prompt.getRender a prompt templateserver, name, optional arguments

All four are pure_read. The agent uses them to discover what a server offers before acting.

Sampling (optional)

Some MCP servers want to call back into a language model — for example, to summarize something mid-task. Atomic Agent supports this: when a server issues a /sampling/createMessage request, the runtime routes it to the local llama-server.

Two things to know:

  • Sampling always runs on slotId: -1, never the main agent or reflection slot. This avoids KV-cache collisions, but means the request is free-form (no grammar attached).
  • Sampling capability is decided at construction time. If you boot with zero servers and add the first one live, that server won’t get a sampling handler. Configure at least one server up front if you need sampling.

Live control (TUI)

In the TUI’s MCP panel you can manage servers without restarting:

  • Add — paste a server config; addServerLive() connects and registers it idempotently, then refreshMcp() rebuilds the grammar so the next inference sees the new tools.
  • Remove — disconnects, unregisters the tools, and rebuilds the resolver.
  • Restart — stop and start a single server.
  • Toggle — flip enabled at runtime.

Status moves through disabled → starting → up (or down on failure), surfaced as badges in the panel.

Behavior worth knowing

  • One bad server can’t break the others. Connections happen in parallel and failures are isolated. A server that won’t start just shows down.
  • Responses are clipped. Heterogeneous MCP content is projected to a single text output capped at 8,000 characters before compression, so a noisy server can’t balloon your prompt.
  • Invalid tools are dropped silently. Tools whose names fail validation (or fail to round-trip through name parsing) are skipped fail-closed rather than registered broken.
  • Names with dots still parse. A raw tool name like os.read_file works — mcp.server.os.read_file parses to server server, tool os.read_file. The server name is the part that must not contain dots.
  • Live-add is idempotent. Calling add twice with the same name is a no-op; check the added return value rather than assuming.

Tool security & approval gates

How approval gates work across all dangerous tools, including MCP.

Configuration reference

Full config.json schema, including the mcp.servers[] block.

Tools overview

How the tool registry, batching, and resource classes fit together.

TUI guide

The MCP panel and live-control gestures.