Configuring MCP transport protocols for Docker containers

Kashish Hora

Kashish Hora

Co-founder of AgentCat

Try out 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 ScenarioRecommended TransportKey Benefits
Local developmentstdioZero latency, simple debugging, no network configuration
Single production instanceStreamableHTTPSimple deployment, remote access, restart-safe
Scalable microservicesStreamableHTTP behind a load balancerHorizontal scaling, plain round-robin routing, fault tolerance
Legacy integrationSSE → StreamableHTTP migrationMaintain 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: 512M

This 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/listen streams 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:

  1. Update server code to use StreamableHTTP endpoints
  2. Modify client configuration to use single endpoint
  3. Send MCP-Protocol-Version and Mcp-Method on every POST; add Mcp-Name (mirroring params.name or params.uri) specifically on tools/call, resources/read, and prompts/get requests — and make sure every header you send matches the corresponding body value
  4. Test thoroughly with parallel deployments
  5. Gradually shift traffic using load balancer rules

Transitioning from Development to Production

Moving from stdio to StreamableHTTP requires architectural changes:

  1. Containerize with appropriate base images
  2. Externalize all configuration
  3. Implement proper logging and monitoring
  4. Add health checks and readiness probes
  5. 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.