stand-in
A stand-in for your MCP server boilerplate.
You write with stand-in declarative macros that look like your MCP server — tools, resources, prompts — but when the compiler rolls, the macros step aside and production-ready code takes their place. You never touch the generated code. You only ever interact with the stand-in.
Status
🚧 Maturing — Core macros (#[mcp_tool], #[mcp_server], #[mcp_prompt], #[mcp_resource]) and both transports (Stdio, Streamable HTTP) are implemented. The public surface is stable; APIs may still change before 1.0.
Installation
[]
= "0.1.0"
= { = "1", = ["rt-multi-thread", "macros"] }
Feature Flags
| Feature | Default | Description |
|---|---|---|
stdio |
✅ | Stdio transport for local/CLI usage |
http |
Streamable HTTP transport (MCP spec 2025-03-26) |
To enable HTTP transport:
= { = "0.1.0", = ["http"] }
Quick Start
use *;
async
;
async
That's it. No handler registration. No JSON-RPC routing. No protocol plumbing. The stand-in handles the setup; the compiler delivers the performance.
Adding a Prompt
use *;
async
Option<T> parameters become optional arguments in the MCP prompt definition. Required parameters stay required. The return type is always Result<Prompt>.
Adding a Resource
use *;
/// Concrete resource — fixed URI, no parameters.
async
/// Template resource — URI template with {param}, extracted at read time.
async
Resources with {param} in the URI become template resources. Concrete resources (no {param}) appear in resources/list; templates appear in resources/templates/list. Return Result<Vec<u8>> for binary data — the macro auto-detects the return type and produces base64-encoded BlobResourceContents.
Features
#[mcp_tool]— Declare a tool with typed parameters. Schema is inferred from the function signature.#[mcp_prompt]— Define reusable prompt templates with typed arguments. Arguments are inferred from the function signature;Option<T>parameters are optional.#[mcp_resource]— Expose data as MCP resources (concrete URIs or URI templates with{param}). ReturnResult<String>for text content orResult<Vec<u8>>for base64-encoded blobs.#[mcp_server]— Wire everything together. Generates initialization, capability negotiation, and dispatch.- Transports — Stdio (default) and Streamable HTTP (feature-gated). Extensible via the
Transporttrait. - Async-first — Built on
tokio. Every handler isasync fn.
Philosophy
Inspired by frameworks like Spring Boot, stand-in follows a simple principle: convention eliminates configuration. If the shape of your code already tells us what you mean, you shouldn't have to say it twice.
The MCP protocol is well-defined but verbose to implement. Every server needs the same handshake, the same capability negotiation, the same JSON-RPC dispatch. stand-in absorbs all of that behind derive macros and attribute macros, so you focus on what your server does — not on how it speaks the protocol.
Related Crates
| Crate | Role |
|---|---|
stand-in |
The main library — runtime, transports, protocol types, re-exported macros. |
stand-in-macros |
Procedural macros. Generates JSON-RPC dispatch, capability advertisement, and handler wiring at compile time. (Pulled in automatically by stand-in.) |
stand-in-client |
The client-side SDK — connect to, discover, and call any MCP server from Rust. |
Why "stand-in"?
Because good infrastructure disappears.
A stand-in does essential work — they're on set for hours so the real performance can happen in minutes. But you never see them in the final cut. That's exactly what these macros do: they show up in your source code, do the hard work at compile time, and vanish from the binary.
Your code reads like a declaration of intent. The compiler turns it into a server. The stand-in was never in the final cut.
License
MIT