Skip to main content
Version: 1.1.0 (latest)

Deploy Your Server over HTTP

Stdio is great for local tools. For remote access, multiple concurrent clients, load balancers, and observability - you need Streamable HTTP.

This tutorial covers both backends (Axum and Actix-web) with tabs. The handler code is identical, only main() changes.

What you'll build: the weather server from Build Your First MCP Server, served over Streamable HTTP with resumability, health checks, and SSE fallback for older clients. Any handler works - the steps below apply to whatever server you already have.

Prerequisites: a working server (from Quickstart or Server tutorial).

Step 1 - Pick your backendโ€‹

cargo add rust-mcp-axum

Axum is our recommended backend - it's the async ecosystem default, well-tested, and has excellent middleware support.

Same API shape

Both backends expose the exact same API shape (XxxServerOptions, create_xxx_server). Switching between them is a one-line change in main() - your ServerHandler code never needs to change.

Step 2 - Configure the serverโ€‹

The production bits live in the options struct: an event store for resumability, a health endpoint for load balancers, SSE fallback for older clients, and DNS rebinding protection.

src/main.rs
mod tools;
mod handler;

use rust_mcp_axum::{create_axum_server, AxumServerOptions};
use rust_mcp_sdk::{
ToMcpServerHandler,
error::SdkResult,
event_store::InMemoryEventStore,
mcp_http::DnsRebindingOptions,
schema::{
Implementation, InitializeResult, ProtocolVersion, ServerCapabilities,
ServerCapabilitiesTools,
},
};
use std::sync::Arc;

use handler::WeatherHandler;

#[tokio::main]
async fn main() -> SdkResult<()> {
// server name, version and capabilities
let server_details = InitializeResult {
server_info: Implementation {
name: "weather-mcp".into(),
version: "1.0.0".into(),
description: Some("A weather server with current conditions and forecasts".into()),
icons: vec![],
website_url: None,
title: Some("Weather MCP Server".into()),
},
capabilities: ServerCapabilities {
tools: Some(ServerCapabilitiesTools::default()),
..Default::default()
},
protocol_version: ProtocolVersion::V2025_11_25.into(),
instructions: None,
meta: None,
};

// instantiate our custom handler for handling MCP messages
let handler = WeatherHandler {};

// create the MCP server
let server = create_axum_server(
server_details,
handler.to_mcp_server_handler(),
AxumServerOptions {
host: "127.0.0.1".into(),
port: 8080,
// โ”€โ”€ Production features โ”€โ”€
// Resumability: clients can reconnect and replay missed events
event_store: Some(Arc::new(InMemoryEventStore::default())),
// Health check endpoint for load balancers / orchestration
health_endpoint: Some("/health".into()),
// Backward compatibility with older SSE-only clients
sse_support: true,
// DNS rebinding protection (protection itself is on by default)
dns_rebinding: DnsRebindingOptions {
allowed_hosts: Some(vec!["127.0.0.1:8080".into()]),
..Default::default()
},
..Default::default()
},
);

server.start().await?;
Ok(())
}
Any handler works

The snippets use the WeatherHandler from the previous tutorial, but nothing here is weather-specific - point create_xxx_server() at any ServerHandler implementation.

Step 3 - Run itโ€‹

cargo run
โ€ข Streamable HTTP server is available at http://127.0.0.1:8080/mcp
โ€ข SSE server is available at http://127.0.0.1:8080/sse

Check the health endpoint:

curl http://127.0.0.1:8080/health
# {"status":"ok","server":"<crate>","version":"..."}

Step 4 - Connect the inspectorโ€‹

Open the MCP Inspector pointed at your server:

npx -y @modelcontextprotocol/inspector@latest \
--transport http \
--server-url http://127.0.0.1:8080/mcp

Key options explainedโ€‹

OptionDefaultPurpose
host"127.0.0.1"Bind address. Use "0.0.0.0" for all interfaces
port8080TCP port
event_storeNoneEnables resumability - clients can reconnect and replay missed events
health_endpointNoneAdds a GET /health endpoint for load balancers and container orchestration
health_handlerbuilt-inCustom response for the health endpoint
sse_supporttrueEnables the SSE fallback endpoint for backward compatibility
dns_rebindingprotection onDNS rebinding protection; allowed_hosts auto-derives from host:port unless overridden
authNoneOAuth provider (see Add OAuth to Your Server)
task_storeNoneServer-side MCP Tasks store
custom_streamable_http_endpointNoneServe MCP at a custom path instead of /mcp
enable_ssl / ssl_cert_path / ssl_key_pathoffTerminate TLS directly in the server

What you learnedโ€‹

  • Switching transports - the handler stays identical; only main() changes
  • Resumability - InMemoryEventStore lets clients reconnect without losing messages
  • Health checks - ready for Kubernetes, HAProxy, or any orchestration
  • SSE fallback - older clients that don't support Streamable HTTP still work
  • DNS rebinding protection - on by default, with configurable allowed hosts

Where nextโ€‹