TurboMCP Server
Server framework for the Model Context Protocol. Provides the McpHandlerExt
entry points, the ServerBuilder for runtime transport selection, typed
middleware (McpMiddleware / MiddlewareStack), configuration types
(ServerConfig, ProtocolConfig, rate limits, connection limits, origin
validation), and the JSON-RPC router shared by every transport.
Table of Contents
- Overview
- Quick Start
- Server Builder
- Server Configuration
- HTTP Authorization
- Protocol Version Negotiation
- Visibility
- Middleware
- Transports
- Feature Flags
- Related Crates
Overview
turbomcp-server is a Layer 5 crate that turns any McpHandler
(implemented by the #[server] macro, CompositeHandler, or a hand-written
type) into a running MCP server. It owns:
- Entry points (
McpHandlerExt::run,run_stdio,run_http,run_tcp,run_unix,run_websocket,handle_request). - The
ServerBuilderfluent API for runtime transport and config selection. - The JSON-RPC router (
router::route_requestet al.) shared by all transports. ServerConfigand its validated builder (try_build) plusProtocolConfigfor version negotiation.- The typed
McpMiddlewaretrait andMiddlewareStack<H>composition wrapper. - Progressive disclosure (
VisibilityLayer) and server composition (CompositeHandler). - MCP authorization for the Streamable HTTP transport (
HttpAuthorization,BearerTokenValidator), with thehttpfeature.
Token validators (OAuth 2.1 / JWT / API keys) live in turbomcp-auth.
Telemetry lives in turbomcp-telemetry. This crate does not bundle them.
Quick Start
Any type that implements McpHandler (the #[server] macro generates one for
you) gets the run* and builder() methods automatically via blanket impls.
The macros come from the turbomcp facade crate:
use *;
;
async
Server Builder
ServerBuilder<H> is obtained via McpServerExt::builder() (blanket impl
on every McpHandler). The methods available on it:
| Method | Description |
|---|---|
.transport(Transport) |
Select transport (default: Transport::Stdio) |
.with_rate_limit(u32, Duration) |
Enable token-bucket rate limiting (per client) |
.with_connection_limit(usize) |
Cap concurrent connections across TCP/HTTP/WS/Unix |
.with_graceful_shutdown(Duration) |
Wait up to this duration for in-flight requests on shutdown (HTTP transport) |
.with_max_message_size(usize) |
Reject messages larger than this (default: 10 MB) |
.with_protocol(ProtocolConfig) |
Configure protocol version negotiation |
.with_allowed_origin(impl Into<String>) |
Allow a specific HTTP origin |
.with_origin_validation(OriginValidationConfig) |
Replace the full origin config |
.allow_localhost_origins(bool) |
Accept/deny localhost origins |
.allow_any_origin(bool) |
Disable origin checks entirely |
.with_config(ServerConfig) |
Apply a ServerConfig (protocol, rate limit, connection limits, required capabilities, message size, origin validation) |
.serve() |
Start the server (async, blocks until shutdown) |
.into_axum_router() |
Return an axum::Router for BYO server integration (requires http) |
.into_service() |
Return a Tower service (requires http) |
.handler() / .into_handler() |
Borrow / consume the underlying handler |
With Calculator from the Quick Start and the http feature:
use Duration;
use *;
async
BYO server (Axum integration)
use ;
use *;
async
Server Configuration
ServerConfig is constructed through ServerConfig::builder(). Fields:
| Field | Type | Default |
|---|---|---|
protocol |
ProtocolConfig |
See Protocol Version Negotiation |
rate_limit |
Option<RateLimitConfig> |
None |
connection_limits |
ConnectionLimits |
1000 per transport |
required_capabilities |
RequiredCapabilities |
none |
max_message_size |
usize |
10 MB |
origin_validation |
OriginValidationConfig |
allow_localhost = true, no explicit origins, allow_any = false, allow_missing_origin = false, cors = false |
http_sessions |
HttpSessionConfig |
1 hour idle timeout, 10,000 sessions |
authorization |
Option<HttpAuthorization> (http feature) |
None |
A request with no Origin header from a non-loopback address is refused
unless allow_missing_origin(true) is set. CLIs, agent runtimes, and other
servers send no Origin, so a networked server that serves them needs it,
paired with authorization.
Use .build() for an infallible build with defaults, or .try_build() to
validate. try_build() returns ConfigValidationError when:
max_message_sizeis below 1024 bytesRateLimitConfig::max_requestsis 0RateLimitConfig::windowisDuration::ZERO- All four fields of
ConnectionLimitsare 0
use Duration;
use ;
let config = builder
.max_message_size
.rate_limit
.try_build
.expect;
HTTP Authorization
With the http feature, ServerConfig::builder().authorization(...) makes
the Streamable HTTP transport an OAuth 2.1 protected resource, as MCP's
authorization spec describes. The server publishes RFC 9728 Protected
Resource Metadata under /.well-known/oauth-protected-resource, answers a
request without a valid bearer token 401 with a WWW-Authenticate
challenge pointing at it (403 insufficient_scope for a missing scope), and
puts the validated Principal on each request's context.
Tokens are checked by a BearerTokenValidator, which must accept only tokens
issued for this server. turbomcp_auth::server::JwtBearerValidator (feature
mcp-http-server) validates JWTs, audience included; a hand-written one looks
like this:
use *;
use Principal;
use ;
/// Accepts one static token. Real servers validate a JWT, audience included.
;
async
Protocol Version Negotiation
ProtocolConfig controls which MCP spec versions the server speaks.
Fields: preferred_version: ProtocolVersion, supported_versions: Vec<ProtocolVersion>, allow_fallback: bool.
Default: preferred_version = ProtocolVersion::LATEST (2025-11-25),
supported_versions = ProtocolVersion::STABLE.to_vec() (2025-06-18 and
2025-11-25), allow_fallback = true. A client asking for a supported
version gets it, and responses are filtered through that version's adapter.
A client asking for any other version is offered the preferred one, as the
lifecycle spec requires, and decides for itself whether to continue.
ProtocolConfig::strict(version) speaks only version. It still answers a
client that asked for another version, offering version rather than
refusing the handshake, because the spec does not permit a refusal.
ProtocolConfig::multi_version() builds the default explicitly.
use *;
use ProtocolVersion;
async
ProtocolConfig::negotiate(client_version) returns the negotiated
ProtocolVersion. It returns None only when allow_fallback has been set to
false and the client asked for an unsupported version, which makes the
server refuse the handshake with -32602; the spec does not allow that, so
leave allow_fallback on.
Visibility
VisibilityLayer wraps any McpHandler and filters tools/list,
resources/list, resources/templates/list, and prompts/list. Disabled
tools, resources, and prompts are also rejected on call/read/get paths as not
found; hidden components stay callable but are omitted from list responses.
Use it for both human UX and LLM-facing AX: expose only the tools relevant to a deployment profile, disable unsafe operations, and keep client context focused.
use *;
async
For config files, map user choices into VisibilityConfig: exact tool
allowlists reduce context load, disabled tools remove risky operations, hidden
tools stay callable without consuming tools/list context, and
require_read_only_tools() only exposes tools annotated with readOnlyHint: true.
Direct call/read/get authorization is registry-backed. The registry is populated
from list responses and lazily initialized on first direct use, so dispatch does
not re-enumerate components on every request. Dynamic servers that add or remove
advertised components at runtime can call refresh_component_registry() or
clear_component_registry() on the layer.
Middleware
Middleware is typed around the MCP operation set. Implement McpMiddleware
and layer it onto any McpHandler via MiddlewareStack:
use Value;
use Future;
use Pin;
use *;
use ;
;
async
The trait's other hooks (on_list_tools, on_list_resources,
on_list_prompts, on_read_resource, on_get_prompt, etc.) all have
pass-through default implementations — override only the ones you need.
Transports
Runtime transport selection is done through Transport. Each variant is
gated by the matching feature flag.
| Constructor | Feature flag | Notes |
|---|---|---|
Transport::stdio() |
stdio |
Default; line-based JSON-RPC over stdin/stdout (Claude Desktop) |
Transport::http(addr) |
http |
Streamable HTTP (Axum): POST for requests, SSE for server messages, Mcp-Session-Id sessions; served at / and /mcp |
Transport::websocket(addr) |
websocket |
Bidirectional JSON-RPC; depends on http |
Transport::tcp(addr) |
tcp |
Line-framed JSON-RPC over TCP |
Transport::unix(path) |
unix |
Line-framed JSON-RPC over Unix domain socket |
Each McpHandler also has direct run_stdio / run_http / run_websocket
/ run_tcp / run_unix methods (feature-gated) via McpHandlerExt, plus
handle_request(Value, RequestContext) for serverless-style one-shot use.
Feature Flags
| Feature | Description | Default |
|---|---|---|
stdio |
STDIO transport | ✅ |
http |
HTTP transport (Axum) | ❌ |
websocket |
WebSocket transport (implies http) |
❌ |
tcp |
TCP transport | ❌ |
unix |
Unix domain socket transport | ❌ |
channel |
In-process channel transport | ❌ |
all-transports |
stdio + http + websocket + tcp + unix + channel |
❌ |
full |
Alias for all-transports |
❌ |
experimental-tasks |
Opt into experimental Tasks API (SEP-1686) | ❌ |
Related Crates
- turbomcp — Main SDK that re-exports this crate's public API
- turbomcp-core —
McpHandler,McpError, JSON-RPC primitives - turbomcp-protocol — Protocol implementation, session management
- turbomcp-transport — Transport re-export hub
- turbomcp-auth — OAuth 2.1 / JWT / API keys (optional)
- turbomcp-telemetry — OpenTelemetry / Prometheus (optional)
License
Licensed under the MIT License.
Part of the TurboMCP Rust SDK for the Model Context Protocol.