turbomcp-server 3.1.3

Production-ready MCP server with zero-boilerplate macros and transport-agnostic design
Documentation

TurboMCP Server

Crates.io Documentation License: MIT

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

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 ServerBuilder fluent API for runtime transport and config selection.
  • The JSON-RPC router (router::route_request et al.) shared by all transports.
  • ServerConfig and its validated builder (try_build) plus ProtocolConfig for version negotiation.
  • The typed McpMiddleware trait and MiddlewareStack<H> composition wrapper.
  • Progressive disclosure (VisibilityLayer) and server composition (CompositeHandler).

Authentication (OAuth 2.1 / JWT / API keys) lives in turbomcp-auth. Telemetry lives in turbomcp-telemetry. Session management lives in turbomcp-protocol. 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.

use turbomcp::prelude::*;

#[derive(Clone)]
struct Calculator;

#[server(name = "calculator", version = "1.0.0")]
impl Calculator {
    /// Add two numbers
    #[tool]
    async fn add(&self, a: i64, b: i64) -> i64 { a + b }
}

#[tokio::main]
async fn main() -> McpResult<()> {
    // STDIO by default
    Calculator.run().await
}

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 fully constructed ServerConfig
.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
use std::time::Duration;
use turbomcp::prelude::*;

#[tokio::main]
async fn main() -> McpResult<()> {
    Calculator.builder()
        .transport(Transport::http("0.0.0.0:8080"))
        .with_rate_limit(100, Duration::from_secs(1))
        .with_connection_limit(1000)
        .with_graceful_shutdown(Duration::from_secs(30))
        .serve()
        .await
}

BYO server (Axum integration)

use axum::{Router, routing::get};
use turbomcp::prelude::*;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let mcp = Calculator.builder().into_axum_router();

    let app = Router::new()
        .route("/health", get(|| async { "OK" }))
        .merge(mcp);

    let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await?;
    axum::serve(listener, app).await?;
    Ok(())
}

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

Use .build() for an infallible build with defaults, or .try_build() to validate. try_build() returns ConfigValidationError when:

  • max_message_size is below 1024 bytes
  • RateLimitConfig::max_requests is 0
  • RateLimitConfig::window is Duration::ZERO
  • All four fields of ConnectionLimits are 0
use std::time::Duration;
use turbomcp_server::{ServerConfig, RateLimitConfig};

let config = ServerConfig::builder()
    .max_message_size(1024 * 1024)
    .rate_limit(RateLimitConfig::new(100, Duration::from_secs(1)))
    .try_build()
    .expect("invalid server configuration");

Protocol Version Negotiation

ProtocolConfig controls which MCP spec versions the server accepts. Fields: preferred_version: ProtocolVersion, supported_versions: Vec<ProtocolVersion>, allow_fallback: bool.

Default (as of v3.1): preferred_version = ProtocolVersion::LATEST, supported_versions = ProtocolVersion::STABLE.to_vec() (all stable spec versions), allow_fallback = false. Older clients are accepted and responses are filtered through the appropriate version adapter.

Use ProtocolConfig::strict(version) to restore exact-match negotiation against a single version. Use ProtocolConfig::multi_version() to construct the default multi-version config explicitly.

use turbomcp::prelude::*;
use turbomcp_server::config::ProtocolVersion;

// Exact-match against the latest version only
Calculator.builder()
    .with_protocol(ProtocolConfig::strict(ProtocolVersion::LATEST.clone()))
    .serve().await?;

// Explicit multi-version (same as default)
Calculator.builder()
    .with_protocol(ProtocolConfig::multi_version())
    .serve().await?;

ProtocolConfig::negotiate(client_version) returns the negotiated ProtocolVersion or None if no compatible version is found (and fallback is disabled).

Middleware

Middleware is typed around the MCP operation set. Implement McpMiddleware and layer it onto any McpHandler via MiddlewareStack:

use turbomcp_server::{McpMiddleware, MiddlewareStack, Next, McpServerExt};
use turbomcp_core::context::RequestContext;
use turbomcp_core::error::{McpError, McpResult};
use turbomcp_types::ToolResult;
use serde_json::Value;
use std::future::Future;
use std::pin::Pin;

struct Logging;

impl McpMiddleware for Logging {
    fn on_call_tool<'a>(
        &'a self,
        name: &'a str,
        args: Value,
        ctx: &'a RequestContext,
        next: Next<'a>,
    ) -> Pin<Box<dyn Future<Output = McpResult<ToolResult>> + Send + 'a>> {
        Box::pin(async move {
            tracing::info!(tool = name, "calling");
            next.call_tool(name, args, ctx).await
        })
    }
}

// MiddlewareStack wraps a handler; it itself implements McpHandler,
// so it participates in the same builder / transport pipeline.
let stack = MiddlewareStack::new(Calculator).with_middleware(Logging);
stack.builder().serve().await?;

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 JSON-RPC over HTTP POST (Axum)
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

License

Licensed under the MIT License.


Part of the TurboMCP Rust SDK for the Model Context Protocol.