How to Configure MCP Tools in nanoinfra
This guide adds an MCP server to nanoinfra so the agent can use external tools through the Model Context Protocol.
What you will build
- a working nanoinfra agent
- one MCP integration configured through Apps or
~/.nanoinfra/config.json - a restricted set of MCP tools exposed to the model
When to use this
Use MCP when the capability you need already exists as an MCP server, or when you want external tools to be managed outside nanoinfra core.
Install
Quick Start ranks four install methods easiest first. This is the first of them. Use pip, Docker or a source checkout instead if you prefer, and come back here.
uv tool install nanoinfra
nanoinfra onboard --wizard
nanoinfra agent -m "Hello!"
Install the MCP server runtime separately. Many examples use npx, uvx, or a
remote HTTP endpoint.
Minimal working example
For local interactive setup:
- Run
nanoinfra webuiand open Apps. - Choose a known integration preset, or add a custom stdio, HTTP, or SSE server.
- Limit the enabled tools when the server exposes more than the task needs.
- Save and restart when prompted.
- Mention the integration with
@in the next message and ask for a small test action.
For manual or deployment-managed config, add this to ~/.nanoinfra/config.json:
{
"tools": {
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"],
"enabledTools": ["read_file"]
}
}
}
}
Restart nanoinfra and ask a question that requires the MCP tool.
Pause a server you are not using
Every configured MCP server sends its full tool schemas in every prompt, on every turn. Three servers with fifteen tools each cost about 23,000 input tokens per turn, even for a message that uses none of them.
Pause the servers you are not using today:
- Open Apps in the sidebar.
- Turn off the switch on the server's row.
- Restart when prompted. The tool registry is built at start.
The row's second line says what it costs: 15 tools · in every prompt, or
15 tools · paused, not in any prompt when the switch is off.
A paused server keeps its command, arguments, environment, headers and enabledTools
list. nanoinfra does not connect to it, so its schemas are in no prompt.
In config, the same state is one field:
{
"tools": {
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/dir"],
"enabled": false
}
}
}
}
Turn the switch back on to resume. Delete the server instead when you do not want the configuration either.
Send a server's tools only when you ask for it
Pausing removes the capability. This keeps the capability and still takes the schemas out of the prompt. The server stays connected. The prompt carries one line saying it exists, and its tools arrive only for a turn that names it.
- Open Apps in the sidebar.
- Open the row's
⋯menu and choose Send tools only when mentioned. - Restart when prompted.
The row then reads 15 tools · sent only when you say @github. Say @github in the
composer and that turn gets the tools.
In config:
{
"tools": {
"mcpServers": {
"github": {
"type": "streamableHttp",
"url": "https://api.githubcopilot.com/mcp/",
"attach": "mention"
}
}
}
}
The advertised line is why this is not the same as sending nothing. A model that cannot
see that a capability exists cannot say "I can do that if you attach github". It fails,
or it uses a worse tool without telling you.
An unattended turn has nobody to type @github, so a cron job or a trigger declares what
it needs:
{ "mcpPresets": ["github"] }
Every other run then stops paying for those schemas.
Let the model load it by searching
mention waits for you to type @github. Send tools only when searched (attach: "search") instead lets the model load the server itself. It calls the built-in tool_search
tool with a topic, and you type no @:
{
"tools": {
"mcpServers": {
"github": {
"type": "streamableHttp",
"url": "https://api.githubcopilot.com/mcp/",
"attach": "search"
}
}
}
}
mention pays one advertised line per server. search replaces the whole list with a
single pointer, so it is the mode that scales when many servers are deferred. Both keep
the server connected and both respect an agent's ceiling. See
Built-in tool groups → Letting the model search for a group
for how the one tool_search tool spans groups, MCP servers and connectors.
Where stdio servers run
A stdio MCP server no longer runs in the agent process. It runs in a separate MCP host process.
The agent sends a server name over a Unix socket. The host reads the command, the arguments, the environment, and the working directory from its own copy of the config. The agent therefore names a server and never a program.
Three consequences matter in practice:
- One connection is one session. The stdio child ends with its connection, so a
dead agent leaves no orphan server behind. The kernel enforces the same rule for the case the host cannot handle itself. Each child asks to
receive
SIGKILLwhen its parent dies. So a host that somebody kills withSIGKILLalso leaves nothing behind. A platform with noprctlsays so once at start and keeps the first rule. - The gateway starts the host only when at least one configured server is a stdio server. An install with no stdio server starts no host.
- The container image runs the host under its own account,
nanoinfra-mcp. That account holds no credential and reaches no inventory host.
HTTP and SSE MCP behaviour did not change. Those transports stay in the agent process behind the existing SSRF guards.
For the accounts, the sockets, and the environment variables, see Deployment.
The Host Runs Under a Sandbox
The MCP host starts under a Landlock ruleset, and a ruleset survives an exec. Every stdio server the host starts therefore inherits the same rules.
Three parts of that policy change what a stdio server can do:
- No rule names the workspace. A stdio server cannot read the workspace, and that includes the credential store. Point a server that must read files at a directory outside the workspace.
- The exec surface holds the system
bindirectories and the toolchain caches in the home directory, such as.npm,.cache, and.bun.npx,uvx, andbunxeach write a cache and then run a program out of it. - No process may open a TCP listener, on a kernel at Landlock ABI 4 or above. An MCP server that binds a port does not work in this host.
A kernel with no Landlock support starts the host anyway and logs the absence. A kernel that reports Landlock and then rejects the ruleset refuses the start, and every stdio MCP tool then stays unreachable. Read Per-Process Confinement for the failure modes and the messages.
Production notes
- Prefer
enabledToolsover exposing every tool by default. - Pause a server you do not use every day. Its schemas leave the prompt and its configuration stays.
- Set
attach: "mention"on a server you use sometimes. One line in the prompt instead of every schema, and the tools are one@away. - Use
toolTimeoutfor slow MCP operations. - Use HTTP MCP only for endpoints you trust.
- Keep MCP server commands stable and versioned in deployment docs or scripts.
Security notes
- Stdio MCP starts a local process. Review the command before you enable it.
- The MCP host starts that process, and the agent cannot choose a program.
- The host and every stdio server it starts run under a Landlock ruleset that names no workspace path.
- HTTP/SSE MCP uses nanoinfra's SSRF guard.
- Allow private HTTP MCP hosts only with narrow
tools.ssrfWhitelistCIDRs. - Do not place secrets in command arguments when environment variables or headers can be used.
Troubleshooting
- Run the MCP command outside nanoinfra first.
- Start
nanoinfra gateway --verboseand inspect tool registration logs. - If an HTTP MCP URL is blocked, check whether it points to loopback or a private address that needs explicit allowlisting.
- If every stdio tool fails with
Could not reach the MCP host, the host process is not running. That is a deployment fault and not a broken MCP server. See Troubleshooting.