Add tools without code
Drop a server config into config.json. Its tools join the registry automatically — no rebuild, no plugin API.
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.
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.
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.
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:
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.refreshMcp().Each entry in mcp.servers[] is an McpServerConfig:
| Field | Required | Meaning |
|---|---|---|
name | yes | Unique kebab-case namespace (max 32 chars, no dots). Becomes the mcp.<name>.* prefix. |
enabled | yes | Whether to connect at bootstrap. |
transport | yes | How to reach the server — stdio, streamable_http, or sse (see below). |
trust | no | approval_gated (default) or pure_read. Controls batching and approval. |
env | no | Per-server env var overrides (stdio only). Merged on top of the process env. |
description | no | One-liner shown in the TUI and logs. |
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.
Connects to a remote HTTP MCP endpoint.
{ "name": "search", "enabled": true, "transport": { "kind": "streamable_http", "url": "https://mcp.example.com/v1", "headers": { "Authorization": "Bearer ${API_TOKEN}" } }}Connects over Server-Sent Events for servers that stream responses.
{ "name": "events", "enabled": true, "transport": { "kind": "sse", "url": "https://mcp.example.com/sse", "headers": { "Authorization": "Bearer ${API_TOKEN}" } }}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:
approval_gated — never pure_read.mcp.ghost.do_thing lands in the safe lane because the resolver is always installed.Once any MCP server is connected, four read-only meta-tools become available to the agent (they’re not registered when zero servers exist):
| Tool | Purpose | Args |
|---|---|---|
mcp.resource.list | List a server’s resources | server, optional limit (1–100) |
mcp.resource.read | Read one resource | server, uri |
mcp.prompt.list | List a server’s prompt templates | server, optional limit (1–100) |
mcp.prompt.get | Render a prompt template | server, name, optional arguments |
All four are pure_read. The agent uses them to discover what a server offers before acting.
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:
slotId: -1, never the main agent or reflection slot. This avoids KV-cache collisions, but means the request is free-form (no grammar attached).In the TUI’s MCP panel you can manage servers without restarting:
addServerLive() connects and registers it idempotently, then refreshMcp() rebuilds the grammar so the next inference sees the new tools.enabled at runtime.Status moves through disabled → starting → up (or down on failure), surfaced as badges in the panel.
down.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.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.