llm-tool-mcp
MCP (Model Context Protocol) stdio server
for llm-tool registries.
Register your tools in a ToolRegistry, hand it to McpServer, and get a
fully compliant MCP server — no boilerplate.
Quick start
use ;
use McpServer;
/// Adds two numbers.
/// Code review instruction template.
/// Dynamic application config resource.
let registry = new.with_tool;
let server = builder
.with_prompt
.with_resource
.with_context
.build;
// In production: server.run_stdio().expect("server failed");
// Here we feed a request via an in-memory buffer:
let input = r#"{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"add","arguments":{"a":17,"b":25}}}"#;
let reader = new;
let mut output = Vecnew;
server.run.unwrap;
let resp: Value = from_slice.unwrap;
assert_eq!;
Transports: Stdio vs TCP vs Unix Sockets
McpServer is builder-style and supports all common execution models
out-of-the-box. Two flavors are available:
- Blocking convenience —
run_stdio,run_tcp,run_unix, and the transport-dispatchingserve(Transport). These build a Tokio runtime internally, so a simple binary'smainneeds noasync. They block until the transport finishes. - Async first-class —
run_async,listen_tcp,listen_unix. Use these when you already have a Tokio runtime (as most real applications do) so the server shares it instead of spawning a second one.
# use ToolRegistry;
# use ;
let server = new;
// ── Blocking convenience (no async main required) ──
// 1. Standard MCP desktop client transport (stdio subprocess):
// server.run_stdio().expect("stdio server failed");
// 2. TCP network server (localhost only):
// server.run_tcp("127.0.0.1:3000").expect("tcp server failed");
// 3. Unix Domain Socket (local IPC):
// server.run_unix("/tmp/my-agent.sock").expect("unix server failed");
// 4. Pick a transport at runtime (e.g. from CLI flags) and serve it:
let transport = Tcp;
// server.serve(transport).expect("server failed");
# let _ = transport;
# use ToolRegistry;
# use McpServer;
# new_current_thread.enable_all.build.unwrap.block_on
What it handles
| MCP method | Behavior |
|---|---|
initialize |
Returns server info and capabilities for registered primitives |
notifications/initialized |
Acknowledged silently |
tools/list |
Derives schemas from ToolRegistry::definitions() |
tools/call |
Dispatches via ToolRegistry::dispatch(), returns content |
prompts/list |
Lists all registered prompts and their argument schemas |
prompts/get |
Renders prompt messages with argument substitution |
resources/list |
Lists all static resources registered on the server |
resources/templates/list |
Lists all URI templates (e.g. "file:///config/{app}.json") |
resources/read |
Matches URIs against resources/templates and returns content |
Tool errors are returned as MCP content with isError: true (spec-compliant),
not as JSON-RPC errors.
Async & custom transports
If running inside an existing Tokio application or network server, use run_async:
# use ToolRegistry;
# use McpServer;
# new_current_thread.enable_all.build.unwrap.block_on
For custom request/response routing (e.g. Axum HTTP POST or WebSockets), call
handle_message. It accepts a single request or a JSON-RPC batch array and
returns a structured [RpcOutcome] — a Single response object or a Batch
array — which you can inspect or render to the wire in a single pass with
.to_wire(). None means the input was purely a notification, so there is
nothing to send back:
# use ToolRegistry;
# use ;
# new_current_thread.enable_all.build.unwrap.block_on
License
Dual-licensed under Apache-2.0 OR MIT.