Step-by-step guide to converting local STDIO MCP servers to production-ready Streamable HTTP servers
This guide covers converting STDIO MCP servers to Streamable HTTP, the current standard for remote MCP deployments (protocol version 2025-03-26). All code examples follow correct initialization patterns to avoid common errors.
# http_server.pyfrom fastmcp import FastMCP# Create MCP server at startupmcp = FastMCP("weather-server") // [!code highlight]# Define your tool (same logic as before!)@mcp.tool()def get_weather(location: str) -> str: """Get weather for a location.""" return f"Weather in {location}: Sunny, 72Β°F"if __name__ == "__main__": # FastMCP handles transport initialization mcp.run( // [!code highlight] transport="http", // [!code highlight] host="0.0.0.0", // [!code highlight] port=8000, // [!code highlight] path="/mcp" // [!code highlight] ) // [!code highlight]
# http_server_fastapi.pyimport contextlibfrom fastapi import FastAPIfrom fastmcp import FastMCP# Create MCP server at startupmcp = FastMCP("weather-server", stateless_http=True) // [!code highlight]@mcp.tool()def get_weather(location: str) -> str: """Get weather for a location.""" return f"Weather in {location}: Sunny, 72Β°F"# Lifespan manager initializes MCP@contextlib.asynccontextmanagerasync def lifespan(app: FastAPI): // [!code highlight] async with contextlib.AsyncExitStack() as stack: // [!code highlight] await stack.enter_async_context(mcp.session_manager.run()) // [!code highlight] yield // [!code highlight]# Create FastAPI app with lifespanapp = FastAPI(lifespan=lifespan) // [!code highlight]# Mount MCP server at /weather endpointapp.mount("/weather", mcp.streamable_http_app())if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8000)
// http_server.tsimport express from "express";import { Server } from "@modelcontextprotocol/sdk/server/index.js";import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";import { CallToolRequestSchema, ListToolsRequestSchema,} from "@modelcontextprotocol/sdk/types.js";const app = express();app.use(express.json());// Create MCP server at startupconst server = new Server( { name: "weather-server", version: "1.0.0" }, { capabilities: { tools: {} } } ); // Register handlersserver.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: "get_weather", description: "Get weather for a location", inputSchema: { type: "object", properties: { location: { type: "string" } }, required: ["location"], }, }, ],}));server.setRequestHandler(CallToolRequestSchema, async (request) => { if (request.params.name === "get_weather") { const location = request.params.arguments?.location || "Unknown"; return { content: [ { type: "text", text: `Weather in ${location}: Sunny, 72Β°F` }, ], }; } throw new Error(`Unknown tool: ${request.params.name}`);});// Create transport at startupconst transport = new StreamableHTTPServerTransport({ path: "/mcp", }); // Initialize server with transportasync function initializeServer() { await server.connect(transport); console.log("β MCP server initialized"); } // Register transport handlerapp.use("/mcp", (req, res) => transport.handleRequest(req, res));// Start serverconst PORT = 8000;app.listen(PORT, async () => { await initializeServer(); console.log(`π Server running on http://0.0.0.0:${PORT}/mcp`);});
FastMCP vs FastAPI: FastMCP provides a simpler API for quick setups. Use FastAPI when integrating MCP into existing FastAPI applications or when you need more control over the web server configuration.
Solution: Ensure tool handlers are registered before the server starts
Correct Order
# Register handlers FIRST@mcp.tool()def my_tool(): pass# THEN run servermcp.run(transport="http")
Session errors
Solution: Client must store and send session ID correctly
Session Handling
# Extract from initialization responsesession_id = response.headers.get("Mcp-Session-Id")# Include in all subsequent requestsheaders = {"Mcp-Session-Id": session_id}