Stdio mode (mcpd stdio)
mcpd stdio serves MCP over stdin/stdout instead of the network: the MCP client starts it as a local process, the way most MCP servers are run. The tools, resources and their output are exactly the same as over HTTPS - only the transport differs.
mcpd stdio [--user NAME] [--config-dir DIR]
When it is useful
| Situation | How |
|---|---|
| The agent's own machine - a laptop or dev box. No daemon, no port, no TLS, no tokens. | client runs mcpd stdio |
| A remote server with no open port. Authentication is your existing SSH key; nothing listens on the network, and the agent still gets typed tools instead of a shell. | client runs ssh admin@web1 mcpd stdio |
| Inside a container or a pod - look at it from the inside, as its own user. | docker exec -i app mcpd stdio --user app, kubectl exec -i pod -- mcpd stdio --user app |
| Trying mcpd out in a minute, before setting up the daemon, users and TLS. | mcpd stdio |
| MCP Inspector, CI, catalogs' build checks - anything that starts a server as a process. | npx @modelcontextprotocol/inspector mcpd stdio |
| Locked-down hosts where opening a port is not allowed. | over SSH, as above |
When not to use it: several people or agents sharing one host, a separate identity per agent, root for specific tools, one audit log for everyone. That is what the network daemon is for.
:::caution Over SSH, it limits the agent - not the key
ssh host mcpd stdio gives the agent only mcpd's tools. The SSH key it uses still opens a full shell for anyone who has it. Keep that key for this purpose only, or restrict it on the server with command="mcpd stdio" in authorized_keys.
:::
Security model
Over stdio there is no token and no MCP user, so the rules are simpler and stricter than the daemon's:
- The caller is an OS account. Started by an ordinary user, every tool call runs as that user - whatever the kernel lets them do, the agent can do, and nothing more.
- Never root.
privileged: trueis always refused, andmcp-sudo.yamlis not read. A root grant over stdio would belong to whoever controls the client's config, which is exactly the stdio risk MCP clients have. - Started as root (
docker exec, a catalog's container), mcpd refuses to serve unless--user NAMEnames an unprivileged account; each call then runs as NAME, like the daemon's workers.--user rootis refused. - stdout carries only MCP messages; mcpd's own log goes to stderr, in the same format as the daemon's (
session=stdio).
A refused root call looks like this:
$ echo '{"jsonrpc":"2.0","id":4,"method":"tools/call","params":{"name":"files/read","arguments":{"path":"/etc/shadow","privileged":true}}}' | mcpd stdio --user agent
{"jsonrpc":"2.0","id":4,"result":{"content":[{"type":"text","text":"privileged: true is not available when mcpd serves over stdio - it never runs anything as root. For root on specific tools, use the mcpd network daemon with a grant in mcp-sudo.yaml"}],"isError":true}}
And starting as root without --user:
$ sudo mcpd stdio
ERROR cannot serve over stdio err="started as root: pass --user NAME (an unprivileged account) - mcpd stdio never runs tools as root"
Connecting a client
Claude Code
claude mcp add linux -- mcpd stdio # this machine
claude mcp add web1 -- ssh admin@web1 mcpd stdio # a remote server, over SSH
claude mcp add app -- docker exec -i app mcpd stdio --user app # inside a container
Claude Desktop and other mcp_config.json-style clients
{
"mcpServers": {
"web1": {
"command": "ssh",
"args": ["admin@web1", "mcpd", "stdio"]
}
}
}
MCP Inspector - a quick check that everything answers. Here mcpd runs in a container, reached with docker exec -i through a two-line wrapper (exec docker exec -i stdio-t mcpd stdio --user agent):
$ npx @modelcontextprotocol/inspector --cli ./mcpd-stdio-docker.sh --method tools/list
37 tools: files/list, files/read, files/create, files/update, ...
$ npx @modelcontextprotocol/inspector --cli ./mcpd-stdio-docker.sh --method resources/list
11 resources
$ npx @modelcontextprotocol/inspector --cli ./mcpd-stdio-docker.sh --method tools/call --tool-name memory/usage
total used free shared buff/cache available
...
(Output trimmed; the Inspector prints the full JSON.)
Configuration
Nothing is required. If --config-dir (or MCPD_CONFIG_DIR, or configs/ in the working directory) holds a daemon.yaml, stdio mode uses only its worker timeout, per-tool timeouts (tools:) and logging:. Listeners, users, TLS and mcp-sudo.yaml belong to the network daemon and are ignored here - so are the network: limits in mcp-sudo.yaml: network/curl over stdio can reach whatever the calling user can.
See also
- AI Agent Configuration - the network daemon with Claude Code and Claude Desktop
- Permissions and Risks - what root grants mean, for the daemon