rtb-mcp¶
A thin wrapper over the official rmcp SDK. Walks
BUILTIN_COMMANDS, keeps the entries that opt into exposure, and serves them as
Model Context Protocol tools. Registers the
mcp command, gated on Feature::Mcp.
Why a command is not exposed by default¶
Command::mcp_exposed() defaults to false, so adding rtb-mcp to a tool exposes
nothing until someone says otherwise, command by command.
The reason is that the CLI and MCP surfaces have different threat models. A CLI command runs because a person typed it. An MCP tool runs because a model decided to call it, possibly from text it read somewhere. Anything destructive, anything that spends money, anything that writes to a shared system — the decision to make that reachable by a model is a decision worth taking one command at a time.
Opt-in also means the trait methods could be added without touching a single existing implementation.
Why the registry is built once¶
McpServer::new walks BUILTIN_COMMANDS eagerly and freezes the result, keeping
each entry's factory function rather than a constructed command. Every
tools/call then builds a fresh instance.
That matches CLI execution, where each invocation gets a new command object, and it
sidesteps holding a Box<dyn Command> across an await boundary. tools/list and
tools/call become pure dispatch against a frozen table.
Why stdio is the transport that matters¶
MCP clients overwhelmingly spawn a server as a subprocess and talk JSON-RPC over
its stdin and stdout. That is the shape mcp serve implements, and it needs no
port, no bind address and no authentication story.
It also imposes a rule that is easy to trip over: stdout is the protocol stream. Anything a command prints goes into the middle of a JSON-RPC conversation. That is why the crate overrides rmcp's default "client initialized" log line, and why a command that prints tables is a poor candidate for exposure.
Sse and Http exist as Transport variants and return an error when selected.
They are declared so the enum shape is settled, not because they work.
How far the implementation currently goes¶
The honest summary is that the plumbing is complete and the payload is not.
tools/list publishes real names, descriptions and schemas. tools/call finds the
right command, runs it against a clone of the host App, and reports success or
failure without terminating the server.
What it does not do is carry data in either direction: the arguments a client sends
are discarded, and the reply is the fixed string <name> ok rather than anything
the command produced. Until that changes, exposure is useful for commands that need
no input and whose effect is the point — not for anything you want an answer from.
The limitations page
states this alongside the rest.