By Sagar Shankaran, Founder of CallSphere
Build and deploy MCP servers accessible over HTTP using the Streamable HTTP transport, with Server-Sent Events for streaming, stateless vs stateful session modes, and strategies for horizontal scaling.
Key takeaways
Stdio transport works perfectly when the MCP server runs on the same machine as the agent. But production scenarios often require remote access — a centralized database server accessed by multiple agents, a tool server running in a different cloud region, or a shared service that teams across the organization connect to.
The Streamable HTTP transport solves this. It uses standard HTTP POST for client-to-server messages and Server-Sent Events (SSE) for server-to-client streaming responses. This means MCP servers can run behind load balancers, in Kubernetes clusters, and behind standard HTTP infrastructure.
The transport exposes a single HTTP endpoint (typically /mcp). The client sends JSON-RPC messages via POST requests. The server responds with either a direct JSON response or opens an SSE stream for long-running operations:
flowchart LR
HOST(["MCP host<br/>Claude Desktop or IDE"])
CLIENT["MCP client"]
subgraph SERVERS["MCP Servers"]
S1["Filesystem server"]
S2["GitHub server"]
S3["Postgres server"]
SX["Custom tool server"]
end
LLM["LLM session"]
OUT(["Grounded action"])
HOST <--> CLIENT
CLIENT <-->|stdio or HTTP+SSE| S1
CLIENT <--> S2
CLIENT <--> S3
CLIENT <--> SX
CLIENT --> LLM --> OUT
style HOST fill:#f1f5f9,stroke:#64748b,color:#0f172a
style CLIENT fill:#4f46e5,stroke:#4338ca,color:#fff
style OUT fill:#059669,stroke:#047857,color:#fff
# Simplified view of the HTTP message flow
# Client sends a request via POST
# POST /mcp
# Content-Type: application/json
# {
# "jsonrpc": "2.0",
# "id": 1,
# "method": "tools/call",
# "params": {"name": "query_db", "arguments": {"sql": "SELECT 1"}}
# }
# Server responds with SSE stream
# Content-Type: text/event-stream
#
# event: message
# data: {"jsonrpc":"2.0","id":1,"result":{"content":[...]}}
The SSE format allows the server to send multiple messages in a single response — progress notifications, partial results, and the final response — all over one HTTP connection.
FastMCP makes HTTP deployment straightforward:
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
# http_server.py
from mcp.server.fastmcp import FastMCP
import json
mcp_server = FastMCP(
name="RemoteTools",
instructions="Remote tool server accessible over HTTP.",
)
@mcp_server.tool()
async def analyze_text(text: str, analysis_type: str = "sentiment") -> str:
"""Analyze text using various NLP methods.
Args:
text: The text to analyze.
analysis_type: Type of analysis - sentiment, entities, or summary.
"""
# Simulated analysis (replace with real NLP logic)
results = {
"sentiment": {
"label": "positive",
"score": 0.87,
"text_length": len(text),
},
"entities": {
"entities_found": 3,
"types": ["PERSON", "ORG", "DATE"],
},
"summary": {
"original_length": len(text),
"summary": text[:100] + "..." if len(text) > 100 else text,
},
}
result = results.get(analysis_type, {"error": "Unknown analysis type"})
return json.dumps(result, indent=2)
@mcp_server.tool()
async def health_check() -> str:
"""Return server health status."""
from datetime import datetime
return json.dumps({
"status": "healthy",
"timestamp": datetime.utcnow().isoformat(),
"version": "1.0.0",
})
if __name__ == "__main__":
mcp_server.run(
transport="streamable-http",
host="0.0.0.0",
port=8001,
)
This server listens on port 8001 and accepts MCP connections at /mcp by default.
Streamable HTTP supports two session modes. Understanding when to use each is critical for scaling:
Stateless mode treats each HTTP request independently. There is no session ID, no server-side state between requests. This is ideal for servers that do not need to track conversation history or accumulate context across tool calls:
# Stateless server — each request is independent
# No session management needed
# Scales horizontally behind a load balancer with no sticky sessions
from mcp.server.fastmcp import FastMCP
stateless_server = FastMCP(name="StatelessTools")
@stateless_server.tool()
async def convert_units(
value: float,
from_unit: str,
to_unit: str,
) -> str:
"""Convert between units. Each call is independent."""
import json
conversions = {
("celsius", "fahrenheit"): lambda v: v * 9 / 5 + 32,
("fahrenheit", "celsius"): lambda v: (v - 32) * 5 / 9,
("kg", "lb"): lambda v: v * 2.20462,
("lb", "kg"): lambda v: v / 2.20462,
}
key = (from_unit.lower(), to_unit.lower())
fn = conversions.get(key)
if not fn:
return json.dumps({"error": f"Unknown conversion: {key}"})
return json.dumps({
"input": f"{value} {from_unit}",
"output": f"{fn(value):.4f} {to_unit}",
})
Stateful mode assigns a session ID to each client connection. The server maintains state between requests — useful for servers that manage transactions, accumulate context, or maintain resource subscriptions:
# Stateful server — tracks session context
# Requires sticky sessions or session store (Redis)
from mcp.server.fastmcp import FastMCP
import json
stateful_server = FastMCP(name="StatefulTools")
# Per-session state (in production, use Redis or similar)
_sessions: dict[str, dict] = {}
@stateful_server.tool()
async def start_transaction(session_id: str) -> str:
"""Begin a database transaction for this session."""
_sessions[session_id] = {
"transaction_active": True,
"operations": [],
}
return json.dumps({"transaction": "started", "session": session_id})
@stateful_server.tool()
async def add_operation(session_id: str, operation: str) -> str:
"""Add an operation to the current transaction."""
session = _sessions.get(session_id)
if not session or not session["transaction_active"]:
return json.dumps({"error": "No active transaction"})
session["operations"].append(operation)
return json.dumps({
"queued": operation,
"total_operations": len(session["operations"]),
})
For stateless servers, horizontal scaling is straightforward — run multiple instances behind a load balancer:
# docker-compose.yml pattern for scaling
# services:
# mcp-tools:
# image: mcp-tools:latest
# deploy:
# replicas: 3
# ports:
# - "8001-8003:8001"
#
# nginx:
# image: nginx:latest
# ports:
# - "80:80"
# depends_on:
# - mcp-tools
For stateful servers, you need either sticky sessions at the load balancer level or a shared session store like Redis. The session ID from the MCP protocol maps to a session key in Redis, allowing any server instance to resume a session.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
From the agent side, connecting to an HTTP MCP server uses the streamable HTTP client:
from agents.mcp import MCPServerStreamableHTTP
remote_server = MCPServerStreamableHTTP(
name="RemoteTools",
params={
"url": "https://mcp.internal.company.com/mcp",
"headers": {
"Authorization": "Bearer <token>",
},
},
cache_tools_list=True,
)
The headers parameter lets you pass authentication tokens with every request. Combined with TLS, this provides a secure channel for remote MCP communication.
Yes. The MCP SDKs provide transport classes that integrate with existing HTTP frameworks. In Python, you can mount the MCP transport handler as a route in a Starlette or FastAPI application. In TypeScript, you can add it as an Express route. This lets you serve MCP alongside regular REST endpoints from the same application.
HTTP adds network latency (typically 1-10ms on a local network) and TLS handshake overhead for the first connection. For tool calls that take hundreds of milliseconds (database queries, API calls), this overhead is negligible. For extremely latency-sensitive tools, stdio is faster because it avoids the network stack entirely.
Set appropriate timeouts on both client and server. The SSE stream keeps the HTTP connection alive during long operations — the server can send progress notifications to prevent the client from timing out. Configure your load balancer and reverse proxy to allow long-lived connections for the MCP endpoint specifically.
#MCP #HTTP #SSE #Streaming #Deployment #AIAgents #AgenticAI #LearnAI #AIEngineering

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
A founder's guide to the personal AI assistant market: best AI assistant apps, business-grade options, and how CallSphere's voice agent fits in.
A founder's guide to free AI agents, low-code AI agent builders, and how to know when you should pay for a real platform like CallSphere.
Graphiti is the open-source temporal knowledge graph for AI agents in 2026. Learn how bi-temporal memory beats vector RAG for voice agents and long-running LLMs.
Chatbot app vs ChatGPT in 2026: a founder's clear take on the difference, when to use which, and how a real AI chatbot app development works.
How we built a fault-tolerant HVAC emergency triage and tech-dispatch platform on Kubernetes — three-tier CQRS, 11 micro-agents on the OpenAI Agents SDK + LangGraph, NATS JetStream, DTMF/SMS/WebSocket acceptance, circuit breakers, and an evaluation pipeline that catches regressions before they wake a tech at 3 AM.
How to design a multi-agent system using MCP for tools and A2A for cross-vendor coordination, with a CallSphere voice agent as a participating node.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.