Skip to main content
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.

Why Convert to Remote?

Cloud Deployment

Host your server on any cloud platform and make it globally accessible

Multi-Client Support

Handle multiple concurrent client connections simultaneously

Better Integration

Easier integration with web apps, mobile apps, and distributed systems

Horizontal Scaling

Deploy behind load balancers and scale as needed

Understanding MCP Transports

STDIO Transport

Best for: Local development, single client
Client spawns server as subprocess → stdin/stdout communication
Pros: Zero network overhead, simple setup
Cons: Same machine only, no multi-client support
Best for: Production, cloud hosting, multiple clients
Server runs independently → Clients connect via HTTP
Pros: Single endpoint, bidirectional, optional sessions
Cons: Requires web server configuration
Streamable HTTP is the current standard (protocol version 2025-03-26). Use this for all new projects!

SSE Transport (Legacy)

Status: Superseded by Streamable HTTP
SSE is no longer the standard. Only use for backward compatibility with older clients.

Prerequisites

# Check Python version (need 3.10+)
python --version

# Install dependencies
pip install mcp fastapi uvicorn

# Optional: FastMCP for rapid development
pip install fastmcp

1️⃣ Your Original STDIO Server

Let’s start with a typical STDIO server that runs locally:
# stdio_server.py
import asyncio
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent

server = Server("weather-server", version="1.0.0")

@server.list_tools()
async def list_tools() -> list[Tool]:
    return [
        Tool(
            name="get_weather",
            description="Get weather for a location",
            inputSchema={
                "type": "object",
                "properties": {
                    "location": {"type": "string"}
                },
                "required": ["location"]
            }
        )
    ]

@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
    if name == "get_weather":
        location = arguments.get("location", "Unknown")
        return [TextContent(
            type="text", 
            text=f"Weather in {location}: Sunny, 72°F"
        )]
    raise ValueError(f"Unknown tool: {name}")

async def main():
    # STDIO transport - runs as subprocess
    async with stdio_server() as (read_stream, write_stream):
        await server.run(
            read_stream,
            write_stream,
            server.create_initialization_options()
        )

if __name__ == "__main__":
    asyncio.run(main())

2️⃣ Convert to Streamable HTTP

# http_server.py
from fastmcp import FastMCP

# Create MCP server at startup
mcp = 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]
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.

3️⃣ Add auth

Most STDIO servers use environment variables for authentication. Convert these to HTTP-based auth patterns for remote servers.

Example: OAuth Credentials Pattern

STDIO Version (environment variables):
Claude Desktop Config
{
  "mcpServers": {
    "google-calendar": {
      "command": "npx",
      "args": ["@cocal/google-calendar-mcp"],
      "env": {
        "GOOGLE_OAUTH_CREDENTIALS": "/path/to/gcp-oauth.keys.json"
      }
    }
  }
}
Remote Version (request headers):
from fastapi import Header, HTTPException, Depends
import base64
import json

def get_credentials(authorization: str = Header(None)) -> dict:
    """Extract credentials from Authorization header."""
    if not authorization or not authorization.startswith("Bearer "):
        raise HTTPException(status_code=401, detail="Invalid auth")
    
    token = authorization.replace("Bearer ", "")
    try:
        return json.loads(base64.b64decode(token))
    except Exception:
        raise HTTPException(status_code=401, detail="Invalid token")

@app.post("/mcp")
async def handle_mcp(
    request: Request,
    credentials: dict = Depends(get_credentials)
):
    # Use credentials from request
    pass

Simpler Pattern: API Keys

For basic authentication, use API keys:
from fastapi import Header, HTTPException, Depends
import os

async def verify_api_key(authorization: str = Header(None)):
    """Verify API key from header."""
    if not authorization:
        raise HTTPException(status_code=401, detail="Missing API key")
    
    api_key = authorization.replace("Bearer ", "")
    if api_key != os.getenv("API_KEY"):
        raise HTTPException(status_code=401, detail="Invalid API key")
    
    return api_key

@app.post("/mcp")
async def handle_mcp(
    request: Request,
    api_key: str = Depends(verify_api_key)
):
    # Request is authenticated
    pass

4️⃣ Run Your MCP Server

Start your converted server:
python http_server.py
# Server runs at http://localhost:8000/mcp

5️⃣ Testing with Hoot 🦉

Hoot - MCP Testing Tool

Like Postman, but specifically designed for testing MCP servers. Perfect for development!

Quick Start

Install & Run
# Run directly (no installation needed!)
npx -y @portkey-ai/hoot

# Or install globally
npm install -g @portkey-ai/hoot
hoot
Hoot opens at http://localhost:8009

Using Hoot

1

Start your server

python http_server.py
# Server runs at http://localhost:8000/mcp
2

Open Hoot

Navigate to http://localhost:8009
3

Connect to your server

  • Paste URL: http://localhost:8000/mcp
  • Hoot auto-detects the transport type!
4

Test your tools

  • View all available tools
  • Select get_weather
  • Add parameters: {"location": "San Francisco"}
  • Click “Execute”
  • See the response!

Hoot Features

Auto-Detection

Automatically detects HTTP vs SSE

Tool Explorer

View and test all server tools

OAuth Support

Handles OAuth 2.1 authentication

Beautiful Themes

8 themes with light & dark modes

Optional: Session Management

Session management is optional in the MCP spec. FastMCP handles it automatically if you need stateful interactions.
# FastMCP handles sessions automatically

# Stateful mode (maintains session state)
mcp = FastMCP("weather-server", stateless_http=False)

# Stateless mode (no session state)
mcp = FastMCP("weather-server", stateless_http=True)

Optional: CORS Configuration

Only add CORS if you need to support browser-based clients. For server-to-server communication, CORS isn’t necessary.
# CORS with FastMCP
mcp.run(
    transport="http",
    host="0.0.0.0",
    port=8000,
    cors_allow_origins=["https://yourdomain.com"]
)

Deployment

Docker

FROM python:3.11-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["python", "http_server.py"]

Quick Deploy

# Install Fly CLI
curl -L https://fly.io/install.sh | sh

# Deploy
fly launch
fly deploy

Troubleshooting

Can't connect to server

Check:
  • Server is running on the correct port
  • Firewall allows connections
  • URL is correct (including /mcp path)
Test with curl:
curl -X POST http://localhost:8000/mcp \
  -H "Content-Type: application/json" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'
Solution: Ensure tool handlers are registered before the server starts
Correct Order
# Register handlers FIRST
@mcp.tool()
def my_tool():
    pass

# THEN run server
mcp.run(transport="http")
Solution: Client must store and send session ID correctly
Session Handling
# Extract from initialization response
session_id = response.headers.get("Mcp-Session-Id")

# Include in all subsequent requests
headers = {"Mcp-Session-Id": session_id}

Summary

You’ve successfully converted your STDIO server to a remote Streamable HTTP server!

Key Principles

Use HTTP Transport

Replace STDIO with Streamable HTTP for remote access

Header-Based Auth

Convert environment variables to HTTP headers

Initialize at Startup

Server and transport created once at startup

Test Thoroughly

Use Hoot to verify all tools work correctly

What We Covered

  1. ✅ Original STDIO server structure
  2. ✅ Converting to Streamable HTTP
  3. ✅ Auth conversion from env vars to headers
  4. ✅ Running your converted server
  5. ✅ Testing with Hoot

Resources

Building something cool? Share it with the MCP community and let us know how this guide helped!