Configuring MCP transport protocols for Docker containers
Kashish Hora
Co-founder of AgentCat
The Quick Answer
MCP servers in Docker use different transport protocols based on your deployment needs. For local development, use stdio transport—it's like a direct phone line between processes. For production, use StreamableHTTP—it's built for scale and resilience.
{
"mcpServers": {
"local-dev": {
"command": "docker",
"args": ["run", "-i", "--rm", "my-mcp-server:stdio"]
},
"production": {
"url": "http://localhost:8080/mcp"
}
}
}Decision guide: Use stdio when your MCP server runs on the same machine as the client. Use StreamableHTTP when you need multiple clients, horizontal scaling, or remote access. SSE is deprecated—migrate to StreamableHTTP.
Understanding Transport Protocols
Transport protocols determine how MCP clients and servers communicate. Think of them as different ways to have a conversation—each with its own strengths and ideal scenarios.
Stdio: The Direct Connection
Standard I/O transport works like a direct intercom between two rooms. The client launches the Docker container as a subprocess, and they communicate through stdin/stdout streams. This creates an exclusive, high-performance channel perfect for local development or single-user scenarios.
The magic happens through Docker's interactive mode (-i flag), which keeps the stdin stream open. Without this flag, the container can't receive messages and exits immediately—like trying to have a phone conversation after hanging up.
StreamableHTTP: The Modern Standard
StreamableHTTP routes all MCP traffic through a single HTTP endpoint. Every client message is its own HTTP POST, and the server answers each one with either a plain JSON response or an SSE stream scoped to that request.
As of the 2026-07-28 spec revision, every request stands alone: MCP is a stateless protocol, and a server must not infer context from earlier requests on the same connection. Each request carries its own protocol version and client capabilities in _meta; the protocol version and the method are additionally mirrored into the MCP-Protocol-Version and Mcp-Method headers on every POST — plus Mcp-Name, but only on tools/call, resources/read, and prompts/get requests, where it mirrors params.name or params.uri. Client capabilities travel only in _meta; no header carries them. That is exactly what makes horizontal scaling across container replicas straightforward—any replica can serve any request. State that has to outlive a single call is passed explicitly, as a server-minted handle the client sends back as an ordinary tool argument. OAuth 2.1 authentication is unchanged and still lives on this transport.
Before the 2026-07-28 revision, StreamableHTTP had a "stateful mode" built on an Mcp-Session-Id header, along with resumable streams via Last-Event-ID and a standalone GET stream for server-initiated messages. All three are gone. A broken response stream now simply loses the in-flight request, and the client re-issues it with a new request ID; long-lived change notifications come from a subscriptions/listen request instead of a GET.
Most shipping clients still speak pre-2026-07-28 revisions today, so a production container should serve both eras rather than the current one alone. Most Tier-1 SDKs (TypeScript, Python, Go, and C#) negotiate era automatically, but check your SDK's own opt-in before assuming zero-config dual-era support — the Go SDK, for instance, only accepts 2026-07-28 traffic once you set StreamableHTTPOptions.Stateless = true; without it, the server negotiates every connection down to legacy 2025-11-25 sessions.
SSE: The Legacy Protocol
Server-Sent Events represented MCP's first attempt at HTTP-based transport. It uses separate endpoints—POST for requests and SSE for responses. That two-endpoint split, and the message routing it forced on both sides, led to its deprecation in favor of StreamableHTTP; the 2026-07-28 revision formally classifies it as Deprecated under the spec's feature-lifecycle policy, making it eligible for removal in a future revision.
Transport Selection Guide
Choosing the right transport depends on your deployment architecture, scaling needs, and client requirements. Here's a practical decision matrix:
| Deployment Scenario | Recommended Transport | Key Benefits |
|---|---|---|
| Local development | stdio | Zero latency, simple debugging, no network configuration |
| Single production instance | StreamableHTTP | Simple deployment, remote access, restart-safe |
| Scalable microservices | StreamableHTTP behind a load balancer | Horizontal scaling, plain round-robin routing, fault tolerance |
| Legacy integration | SSE → StreamableHTTP migration | Maintain compatibility while upgrading |
When to Use Each Transport
Choose stdio when:
- Developing and testing locally
- Running single-user tools or agents
- Network overhead is unacceptable
- Client and server share the same host
Choose StreamableHTTP when:
- Deploying to production
- Supporting multiple concurrent clients
- Implementing microservices architecture
- Requiring authentication and authorization
- Needing automatic failover and load balancing
Docker Configuration Essentials
Successfully running MCP servers in Docker requires understanding how containers interact with different transport protocols. Each transport has specific requirements that affect your Docker configuration.
Container Lifecycle Management
Docker containers are ephemeral by design. For stdio transport, this means the container lives only as long as the client connection. The container starts when the client connects and stops when it disconnects. This behavior is perfect for development but requires careful consideration for production use.
StreamableHTTP containers, conversely, run continuously and handle multiple connections. They require proper health checks, resource limits, and restart policies to ensure reliability.
Networking Considerations
Stdio transport bypasses networking entirely—communication happens through process pipes. This eliminates network-related issues but limits you to local deployments.
StreamableHTTP requires careful network configuration. Containers must expose the appropriate ports, and you need to consider:
- Port mapping between container and host
- Network isolation for security
- DNS resolution for service discovery
- Load balancer integration for scaling
Security Best Practices
Running MCP servers in containers introduces unique security considerations. Always run containers as non-root users to limit potential damage from compromises. For stdio transport, the -i flag creates an attack surface—ensure you trust the container image.
StreamableHTTP deployments should implement:
- TLS encryption for all communications
- OAuth 2.1 for authentication
- Network policies to restrict access
- Regular security scanning of base images
- Minimal container images to reduce attack surface
Common Patterns
Basic Stdio Configuration
$docker run -i --rm \$ -v "$PWD:/workspace:ro" \$ -e API_KEY="$API_KEY" \$ my-mcp-server:stdio
This pattern mounts the current directory read-only and passes environment variables for configuration. The --rm flag ensures cleanup after disconnection.
Production StreamableHTTP Setup
version: '3.8'
services:
mcp-server:
image: my-mcp:latest
ports:
- "8080:8080"
deploy:
replicas: 3
resources:
limits:
memory: 512MThis configuration enables horizontal scaling with resource constraints, suitable for production deployments.
Troubleshooting
Container Exits Immediately
This common stdio issue occurs when the -i flag is missing. The container can't read from stdin and terminates. Always include -i for interactive mode. Adding --init helps with proper signal handling and prevents zombie processes.
Connection Refused Errors
For StreamableHTTP, this usually indicates:
- Port mapping issues (container port not exposed)
- Firewall blocking connections
- Service not fully started (add health checks)
- Wrong protocol in client configuration
State Lost After Container Restart
There are no protocol sessions left to lose. Under the 2026-07-28 revision every request carries its own protocol version, capabilities, and identity, so a freshly restarted container can serve the very next request from any client with no re-handshake. What can still break is application state your server invented on top of the protocol—typically a handle minted by one replica that only that replica knows how to resolve. Solutions include:
- Back your handles with external storage (Redis, Postgres, object storage) so any replica can resolve them
- Encode enough information in the handle itself that it survives a process restart, rather than pointing at in-process memory
- Have clients re-establish any active
subscriptions/listenstreams after a restart, since servers hold no subscription state - Design clients to re-issue an interrupted request with a new request ID rather than expecting the stream to resume
Servers still running the legacy session model (Mcp-Session-Id, pre-2026-07-28) do lose real sessions on restart, and need external session storage plus sticky routing to survive it.
Performance Degradation
Monitor resource usage and implement limits. Common causes:
- Memory leaks accumulating over time
- CPU throttling from insufficient allocation
- Network congestion from poor configuration
- Disk I/O from excessive logging
Migration Strategies
Moving from SSE to StreamableHTTP
SSE users should migrate to StreamableHTTP for better reliability and features. The migration involves:
- Update server code to use StreamableHTTP endpoints
- Modify client configuration to use single endpoint
- Send
MCP-Protocol-VersionandMcp-Methodon every POST; addMcp-Name(mirroringparams.nameorparams.uri) specifically ontools/call,resources/read, andprompts/getrequests — and make sure every header you send matches the corresponding body value - Test thoroughly with parallel deployments
- Gradually shift traffic using load balancer rules
Transitioning from Development to Production
Moving from stdio to StreamableHTTP requires architectural changes:
- Containerize with appropriate base images
- Externalize all configuration
- Implement proper logging and monitoring
- Add health checks and readiness probes
- Design for horizontal scaling from day one
Remember: successful MCP deployments in Docker balance simplicity with scalability. Start simple with stdio for development, then graduate to StreamableHTTP when production demands grow. Focus on understanding your transport choice deeply rather than implementing complex configurations prematurely.
Related Guides
Comparing stdio vs. SSE vs. Streamable HTTP
How to choose an MCP transport: stdio for local subprocesses, Streamable HTTP for remote services, and why the old HTTP+SSE transport is deprecated, not a third option.
Configuring MCP transport protocols for Docker containers
Configure MCP servers in Docker containers with proper transport protocols and networking.
Building a serverless MCP server
Deploy an MCP server to AWS Lambda, Cloudflare Workers, or Vercel over Streamable HTTP, using stateless request handling so a fresh instance can serve any request.