Multi-Replica Production Deployment¶
Deploying litestar-mcp across multiple web and worker replicas behind a load balancer requires coordinating four shared state planes:
- Stateless HTTP Routing & Origin Validation (
MCPConfig.allowed_origins) - Shared
StoreRegistry(litestar_mcp_tasks,litestar_mcp_workflows,LitestarStoreSessionStore) - Cross-Worker
ChannelsPlugin(MCPConfig.subscription_channelsandQueuedAgentChatController) - Durable
litestar-queuesBackend & External Worker Placement (WorkerConfig(placement="external"))
flowchart LR
LB["Load Balancer (Stateless HTTP)"] --> Web1["Web Replica 1 (LitestarMCP)"]
LB --> Web2["Web Replica 2 (LitestarMCP)"]
Web1 --> Shared["Shared StoreRegistry + ChannelsPlugin + Queue Backend"]
Web2 --> Shared
Shared --> Workers["External Queue Workers (litestar workers run)"]
1. Stateless Streamable HTTP & allowed_origins¶
LitestarMCP serves Streamable HTTP on MCPConfig.base_path (default /mcp) without requiring sticky load-balancer sessions:
- Stateless
2026-07-28&2025-06-18Routing: EveryPOST /mcpJSON-RPC request (server/discover,initialize,tools/list,tools/call,resources/read,prompts/get,skills/*,tasks/*) andDELETE /mcprequest is self-contained. Any web replica can handle any request in a sequence. - Browser & Cross-Origin Protection (
MCPConfig.allowed_origins): To prevent DNS rebinding and cross-origin CSRF attacks from browser contexts,MCPControllervalidates theOriginheader on incoming requests wheneverOriginis present. By default, only requests matching the server's own origin (scheme://host[:port]) are accepted; mismatched origins receive HTTP403 Forbidden. In production behind an API gateway or hosted web inspector, list trusted origins explicitly:
from litestar_mcp import MCPConfig
config = MCPConfig(
allowed_origins=[
"https://mcp-inspector.internal.example.com",
"https://app.example.com",
],
)
2. Shared StoreRegistry (litestar_mcp_tasks, litestar_mcp_workflows, Sessions)¶
By default, Litestar initializes an in-memory MemoryStore when no stores registry is configured. In a multi-replica deployment, a process-local MemoryStore causes tasks/get, tasks/update, tasks/cancel, workflow_engine.snapshot(), and multi-turn agent sessions to fail with not-found errors when routed to a different replica.
Configure a shared StoreRegistry (such as Litestar's RedisStore) on Litestar(stores=...):
| Store Consumer | Store Resolution Mechanism | Default Store Name / Key Prefix |
|---|---|---|
MCPTaskStore (io.modelcontextprotocol/tasks) |
Automatically resolves "litestar_mcp_tasks" from app.stores when MCPTaskConfig.store is None. |
Store name "litestar_mcp_tasks", keys mcp-task:{task_id} and mcp-task:{task_id}:job |
WorkflowStore (DurableWorkflowEngine) |
Automatically resolves "litestar_mcp_workflows" from app.stores when DurableWorkflowEngine.store is None. |
Store name "litestar_mcp_workflows", keys litestar_mcp:workflow:{run_id}:run and litestar_mcp:workflow:{run_id}:node:{name} |
LitestarStoreSessionStore (AgentRuntime) |
Pass a shared Store instance explicitly: LitestarStoreSessionStore(redis_store.with_namespace("sessions")). |
Key prefix litestar_mcp:session:{owner}:{session_id} |
from litestar import Litestar
from litestar.stores.redis import RedisStore
from litestar.stores.registry import StoreRegistry
from litestar_mcp import LitestarMCP, MCPConfig
from litestar_mcp.agent import Agent, AgentRuntime
from litestar_mcp.agent.sessions import LitestarStoreSessionStore
root_redis_store = RedisStore.with_client(url="redis://redis.internal:6379/0")
stores = StoreRegistry(
default_factory=root_redis_store.with_namespace,
)
agent = Agent(name="assistant", instructions="Assist the authenticated user.")
runtime = AgentRuntime(
target=agent,
sessions=LitestarStoreSessionStore(root_redis_store.with_namespace("litestar_mcp_sessions")),
)
app = Litestar(
stores=stores,
plugins=[
LitestarMCP(MCPConfig(tasks=True)),
runtime,
],
)
Because StoreRegistry(default_factory=root_redis_store.with_namespace) automatically namespaces every store lookup, LitestarMCP resolves "litestar_mcp_tasks" and DurableWorkflowEngine resolves "litestar_mcp_workflows" against Redis with zero boilerplate.
3. Cross-Worker ChannelsPlugin (subscription_channels)¶
Two real-time streaming subsystems use Litestar's ChannelsPlugin to fan out events across processes:
- MCP Resource & Task Notifications (
MCPConfig.subscription_channels): When a worker or web replica callsregistry.notify_resource_updated(uri)or transitions a task inMCPTaskStore(notifications/tasks),SubscriptionManagerpublishes the notification across replicas whenMCPConfig(subscription_channels=channels_plugin)is configured. - Queued Agent Turn SSE (
QueuedAgentChatController): Workers runningrun_agent_turn_taskpublish sequencedAgentStreamFramepayloads tolitestar_mcp:agent:{turn_id}viaChannelsPlugin(configured with a shared Redis pub/sub backend andhistoryenabled).
from litestar.channels import ChannelsPlugin
from litestar.channels.backends.redis import RedisChannelsPubSubBackend
from litestar_mcp import MCPConfig
channels_plugin = ChannelsPlugin(
backend=RedisChannelsPubSubBackend(url="redis://redis.internal:6379/0", history=1000),
arbitrary_channels_allowed=True,
)
mcp_config = MCPConfig(
tasks=True,
subscription_channels=channels_plugin,
)
4. Durable litestar-queues Backends & Worker Placement¶
For multi-replica production deployments, configure QueuePlugin with a durable backend (such as Redis or SQLSpec PostgreSQL) and decouple web replicas from queue workers using WorkerConfig(placement="external"):
WorkerConfig.placement |
Execution Model | When to Use |
|---|---|---|
"external" (Recommended for Production) |
Web replicas only enqueue tasks, cancel jobs, and serve HTTP/SSE requests. Dedicated worker containers claim and execute tasks via litestar workers run. |
Multi-replica production deployments, Kubernetes / Cloud Run worker pools, and CPU- or LLM-heavy workloads. |
"server" / "asgi" |
Background queue workers start inside the same ASGI server lifespan as the web application. | Single-container deployments, staging previews, and local development. |
Production Configuration (placement="external")¶
from litestar import Litestar
from litestar_queues import QueueConfig, QueuePlugin, WorkerConfig
from litestar_mcp import LitestarMCP, MCPConfig
queue_plugin = QueuePlugin(
config=QueueConfig(
queue_backend="redis",
broker_url="redis://redis.internal:6379/0",
worker=WorkerConfig(
placement="external",
run_app_lifespan=True,
queues=["mcp", "workflow", "agent", "heavy"],
concurrency=8,
),
),
)
app = Litestar(
plugins=[
queue_plugin,
LitestarMCP(MCPConfig(tasks=True)),
],
)
Note: Always keep
WorkerConfig(run_app_lifespan=True)enabled for external workers. Running the Litestar application lifespan ensuresLitestarMCP.on_startupbindsMCPTaskRuntimetoQueueTaskExecutor, opens sharedStoreRegistryandChannelsPluginconnections, and initializes database pools or Dishka containers before the worker claims its first job.
Launching Web Replicas and External Workers¶
Run stateless web replicas with Uvicorn or Gunicorn, and launch dedicated queue worker processes from the same application entrypoint using litestar workers run:
5. Production Readiness & Conformance Testing¶
Before promoting a deployment configuration to production, verify protocol compliance against the official MCP 2026-07-28 conformance suite under both in-process and distributed QueueTaskExecutor modes:
| Target | Command | What It Verifies |
|---|---|---|
| In-Process Conformance | make conformance |
Runs tools/ci/run_mcp_conformance.py against the MCP 2026-07-28 specification (server/discover, SEP-2575 headers, tools, resources, prompts, skills, and in-process tasks). |
| Distributed Queue Conformance | make conformance-queue |
Runs MCP_CONFORMANCE_TASK_EXECUTOR=queue python tools/ci/run_mcp_conformance.py, verifying QueueTaskExecutor, principal/security rehydration, cooperative tasks/cancel, and multi-round MCPInputRequiredResult / tasks/update resumption over litestar-queues. |
Multi-Replica Deployment Checklist Summary¶
| Component | Single-Process Default | Multi-Replica Production Requirement |
|---|---|---|
Litestar(stores=...) |
MemoryStore |
Shared StoreRegistry (e.g. RedisStore) backing "litestar_mcp_tasks" and "litestar_mcp_workflows" |
AgentRuntime.sessions |
MemorySessionStore |
LitestarStoreSessionStore bound to a shared Store namespace |
MCPConfig.subscription_channels |
None (process-local SSE) |
Shared ChannelsPlugin (e.g. RedisChannelsPubSubBackend) |
QueueConfig.queue_backend |
"memory" |
Durable broker ("redis" or "sqlspec") |
WorkerConfig.placement |
In-process worker | "external" launched via litestar --app app:app workers run with run_app_lifespan=True |
MCPConfig.allowed_origins |
Same-origin only | Explicit allowlist of trusted browser / gateway origins |