MCP Servers
Mount external Model Context Protocol (MCP) tool servers into a
pindussession with the repeatable--mcpflag. Each endpoint's tools are connected through the framework MCP pool and concatenated onto the built-in capability deck as ordinaryAgentTools, so the agent calls them exactly likereadorbash. Requires theindusagi[mcp]extra (pulled in byindusagi[mcp,tui]).
Table of Contents
- What MCP gives you
- The --mcp flag
- Config file format
- Auto-detected config search
- How tools are attached
- Failure handling and noise suppression
- Tool naming and filtering
- Reference
What MCP gives you
The Model Context Protocol lets a third-party server expose a set of tools (and
resources/prompts) over a wire protocol. pindus connects to such servers at
boot, lists their tools, and adapts each one into the conductor's
AgentTool shape. The model then sees
those tools in its briefing alongside the built-ins and can invoke them mid-turn.
Connections are made through the framework MCP facade
(indusagi.mcp), which is a thin shim over the core
protocol bridge (indusagi.interop). The shim
speaks both transports: subprocess stdio servers (the command/args form)
and HTTP/SSE servers (the url form).
The --mcp flag
--mcp names where to find MCP server config. It is a list flag — repeat it
or comma-join values, and every endpoint is attached:
# A single project/user config (auto-detected — see search order below)
pindus --mcp .
# An explicit config file
pindus --mcp ./mcp.json
# Several endpoints at once (repeated or comma-joined are equivalent)
pindus --mcp ./fs.json --mcp ./github.json
pindus --mcp ./fs.json,./github.json
Each value is interpreted by the framework loader: if it points at a file, that file is loaded directly; otherwise it is treated as a working directory and the standard config locations under it are merged (see Auto-detected config search).
When --mcp is omitted, no external servers are attached and the session runs on
the built-in deck alone.
Config file format
A config file is JSON with a top-level servers field. servers may be either
an array of server objects or an object keyed by server name (the key
becomes the server's name). A server is reached over HTTP when it carries a
url, or over stdio when it carries a command; one of the two is required.
A server with "enabled": false is skipped.
{
"servers": [
{
"name": "filesystem",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/dir"],
"enabled": true
},
{
"name": "github",
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": { "GITHUB_TOKEN": "your-github-token" },
"enabled": true
},
{
"name": "remote-server",
"url": "http://localhost:8080/mcp",
"headers": { "Authorization": "Bearer your-token" },
"enabled": false
}
]
}
The object form is equivalent and often tidier:
{
"servers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "."]
},
"remote-server": {
"url": "http://localhost:8080/mcp",
"headers": { "Authorization": "Bearer your-token" }
}
}
}
| Field | Applies to | Purpose |
|---|---|---|
name |
both | Server id; namespaces its tools. In the object form this is the map key. |
command |
stdio | Executable to spawn (e.g. npx, python, node). |
args |
stdio | Argument list passed to command. |
env |
stdio | Extra environment variables for the spawned process. |
url |
HTTP | Endpoint URL; the core transport speaks SSE. |
headers |
HTTP | Headers sent on the connection (e.g. an auth bearer token). |
timeout |
both | Per-request timeout in milliseconds. |
enabled |
both | false skips the entry without removing it from the file. |
A file that is missing the servers field, or whose servers is neither array
nor object, is reported and yields no servers — it does not crash the run.
Auto-detected config search
When a --mcp value is a directory (or .), the framework loader merges
config from the conventional locations, in this order:
- Project:
./.indusvx/mcp.json - User (XDG):
$XDG_CONFIG_HOME/indusvx/mcp.json(default~/.config/indusvx/mcp.json) - Legacy:
~/.indusvx/agent/mcp.json - Legacy alt:
~/.indusvx/agent/mcp-servers.json
All present sources are concatenated, so a project mcp.json and a user
mcp.json both contribute their servers. Passing an explicit file path bypasses
the search and loads only that file.
How tools are attached
Attachment happens at conductor-assembly time, in
induscode.boot.runners.session. The flow for
each --mcp path:
servers = loadMCPConfig(path) # parse file or merge a cwd
pool = MCPClientPool(MCPClientPoolOptions(servers=servers))
await pool.connectAll()
for client in pool.getAllClients():
for definition in await client.listTools():
tools.append(createMCPAgentToolFactory(definition, client)())
The framework factory createMCPAgentToolFactory mints a tool that is
structurally a conductor AgentTool, so MCP tools require no adapter — they are
simply appended to the deck:
# induscode/boot/runners/session.py
tools = [*select_tools(cwd, inv), *(await load_mcp_tools(inv))]
The combined list becomes SessionConductorOptions.tools, and the system prompt
is composed after attachment, so the briefing the model receives already
describes the MCP tools. The deck itself comes from provision_deck("all", ...)
— see the Capability Deck page for the
built-in catalog and how the framework's
capabilities become AgentTools.
Failure handling and noise suppression
--mcp is best-effort and never sinks a session:
- An endpoint that fails to load or connect is skipped; remaining endpoints and the built-in deck still come up. A single bad config or unreachable server degrades the deck rather than aborting boot.
- A config with zero usable servers contributes nothing and is silently passed over.
The framework pool reports per-server connection progress on stdout/stderr and
through logging. During attachment those streams are scope-redirected to
/dev/null and logging below ERROR is disabled, so MCP chatter can never
corrupt the Textual console render or the -p --json NDJSON stream. Setting the
INDUSAGI_DEBUG environment variable keeps all of that connection output
visible for troubleshooting:
INDUSAGI_DEBUG=1 pindus --mcp ./mcp.json -p "list the repo tools you have"
Tool naming and filtering
MCP tools land on the same deck as the built-ins, so the --tools / --no-tools
allow-list applies to them too. Names are matched case-insensitively with _
and - stripped, which is worth knowing when an MCP server's tool name contains
separators. --no-tools disables the built-in deck for the run; MCP tools
are attached after that selection, so they are still available.
# Built-ins off, but the MCP server's tools still attach
pindus --no-tools --mcp ./mcp.json
# Restrict to a named subset (built-in or MCP), separators ignored
pindus --tools read,bash --mcp ./mcp.json
See Models & flags for the full flag table and Configuration: Settings for persisted session defaults.
Reference
| Symbol | Kind | Source | Purpose |
|---|---|---|---|
--mcp |
flag (list) | launch/invocation/flags.py |
Attach external MCP endpoints; comma-separated or repeated. |
Invocation.mcp |
field | boot/contract.py |
The parsed tuple of --mcp paths (None when unset). |
load_mcp_tools |
async function | boot/runners/session.py |
Connect every --mcp endpoint and return their tools; skips failures. |
select_tools |
function | boot/runners/session.py |
Build the built-in deck and apply --tools / --no-tools. |
build_session_conductor |
async function | boot/runners/session.py |
Assemble the conductor; concatenates deck + MCP tools into options.tools. |
loadMCPConfig |
function | indusagi.mcp |
Parse a config file or merge the auto-detected locations. |
MCPClientPool / MCPClientPoolOptions |
class | indusagi.mcp |
Connect a set of MCP servers and surface their clients. |
createMCPAgentToolFactory |
function | indusagi.mcp |
Mint a conductor-compatible AgentTool from an MCP tool definition. |
StdioServerConfig / HttpServerConfig |
dataclass | indusagi.mcp |
The stdio (command) and HTTP (url) server config shapes. |
The framework MCP surface is documented in full on the
indusagi.mcp facade page; the underlying protocol bridge
lives in interop.
