Skip to content

System Architecture & Interactive Diagrams

litestar-mcp unifies Model Context Protocol (MCP) servers, durable background task and DAG workflow execution, multi-agent orchestration, and Agent-to-Agent (A2A 1.0) interoperability inside a single Litestar ASGI lifecycle.

This page collects all six interactive architectural diagrams and links each subsystem to its implementation guide and API reference.


1. Request & Transport Dispatch Pipeline

MCPController mounts at MCPConfig.base_path (default /mcp) and delegates JSON-RPC framing and version negotiation to MCPHandlerService. Both 2026-07-28 stateless discovery (server/discover + SEP-2575 headers) and 2025-06-18 initialization (initialize, ping, notifications/initialized, DELETE /mcp) share the same registry and internal ASGI execution path.

When a client calls tools/call or prompts/get, MCPHandlerService dispatches an in-memory ASGI request through the host Litestar router rather than opening a loopback TCP socket or bypassing the framework. Your authentication middleware, route guards, Provide factories, Dishka FromDishka[T] providers, DTO validators, lifecycle hooks, and OpenTelemetry spans execute on every invocation.

Stateless Streamable HTTP and Internal ASGI Dispatch Pipeline JSON-RPC requests arrive at MCPController (/mcp), negotiate 2026-07-28 or 2025-06-18 via MCPHandlerService, and execute tools and prompts through Litestar internal ASGI dispatch with middleware, guards, DI, and OpenTelemetry spans. MCP Client POST / DELETE /mcp 2026-07-28: server/discover 2025-06-18: initialize & ping SEP-2575 Capability Headers JSON or text/event-stream MCPController MCPHandlerService Router Guards & route_opt Origin & Protocol Validation 202 Accepted (notifications) 204 No Content (DELETE /mcp) Internal ASGI Dispatch execute_tool / prompt Forwards Auth & Trace Headers Preserves request.user & auth Binds ToolContext in scope Zero TCP Loopback Hop Litestar Handler @get / @post / @tool Middleware & Route Guards Provide & FromDishka[T] DTOs & msgspec Validation before/after_request Hooks Cross-Cutting Observability & Registry Layer Registry (Tools • Resources • Prompts • Skills) | TelemetryConfig OpenTelemetry Spans (mcp.request • mcp.tool.call)
In-Process Stdio and HTTP Bridge Pipeline Desktop MCP clients communicate over newline-delimited JSON on stdin/stdout using either in-process Litestar.lifespan() execution with MCPStdioContext or the stateless HTTP relay bridge. Desktop Host stdin / stdout NDJSON Claude Desktop / Cursor / IDE litestar mcp stdio litestar mcp bridge mcp.run(transport="stdio") Stdio Lifecycle Adapter run_stdio_async() Enters Litestar.lifespan() Injects MCPStdioContext user • auth • session • state Bounded shutdown_timeout=5.0s Synthetic ASGI Scope MCPHandlerService In-Memory ASGI Dispatch Or Remote HTTP Relay (--url) Owner ID: user:<subject> SSE Progress Notification Loop Application DI Full Litestar Pipeline DB Pools & Stores Active Dishka Scope.REQUEST Route Guards Enforced Identical Tool Semantics Two CLI Modes: Local In-Process vs Remote HTTP Relay litestar --app app:app mcp stdio (in-process ASGI) | litestar mcp bridge --url https://api.example.com/mcp (remote HTTP)

2. Primitives & Discovery Compilation

At application startup (on_app_init), LitestarMCP inspects every registered Litestar route handler, PromptController, SkillController, and MCPSkillsConfig source to populate the central Registry:

  • Tools: Compiles route signatures, msgspec.Struct / Pydantic / @dataclass models, and Provide dependencies into JSON Schema while stripping internal DI parameters (Request, State, ToolContext, and Dishka-resolved types). Infers ToolAnnotations (readOnlyHint, destructiveHint, idempotentHint) from the HTTP verb unless overridden.
  • Resources & Templates: Separates static URIs (resources/list) from parameterized RFC 6570 Level 1 templates (resources/templates/list) and manages resources/subscribe change notifications.
  • Prompts: Extracts PromptArgument metadata from route handlers or PromptController methods and normalizes return values into MCP message sequences.
  • Agent Skills (SEP-2640): Combines static SKILL.md directories (FilesystemSkillSource), dynamic request-aware SkillSource providers, and SkillController endpoints under io.modelcontextprotocol/skills (skills/list, skills/load, skills/read_file) and skills:// fallback resources.
MCP Tools and ToolAnnotations Compilation Pipeline Litestar routes marked with mcp_tool or @mcp_tool compile signature types, Provide dependencies, and HTTP verb hints into JSON Schema and ToolAnnotations for tools/list and tools/call. 1. Route Declaration mcp_tool=True | "name" @mcp_tool(...) / @mcp.tool() mcp_tool_annotations=ToolAnnotations mcp_tool_output_schema={...} task=True | "required" | "optional" HTTP verb -> readOnly/idempotent hints 2. Registry Schema Builder Registry.register_tool() Extracts msgspec / Pydantic / dataclass Strips Dishka & internal DI params Precedence: description > summary > doc Applies include/exclude tag filters litestar mcp export-tool-spec 3. JSON-RPC Execution tools/list & tools/call Dispatches via internal ASGI scope Emits structuredContent when typed Supports ToolContext.report_progress Handles params.task async execution isError: true on 4xx/5xx tool errors
MCP Resources and RFC 6570 URI Templates Pipeline Static resources and parameterized RFC 6570 URI templates register in the MCP Registry, supporting resources/list, resources/templates/list, resources/read, and resources/subscribe. 1. Resource & Template Opts mcp_resource="name" mcp_resource_uri="app://config" mcp_resource_template="notes://{id}" @mcp_resource / @mcp.resource() Built-in: litestar://openapi RFC 6570 Level 1 Path Variables 2. URI Matcher & Subscriptions Registry Resource Index Compiles URI regex & param extractors Infers MIME type (application/json) Tracks active URI subscriptions Optional ChannelsPlugin fan-out notify_resource_updated(uri) 3. Resource JSON-RPC Surface resources/read & list resources/list (static URIs) resources/templates/list (RFC 6570) resources/subscribe & unsubscribe Returns text or base64 blob contents notifications/resources/updated
MCP Prompts and PromptController Compilation Pipeline Prompts declared via route markers, @mcp_prompt, or class-based PromptController resolve dependencies through Litestar and Dishka DI during prompts/get. 1. Prompt Authoring PromptController / @mcp_prompt Route opt: mcp_prompt="name" Class-based PromptController Universal DI: Provide / FromDishka[T] Inject() & Depends(fn) markers Controller-level guards & dependencies 2. Prompt Argument Schema PromptArgument Extraction Excludes DI & Request parameters Derives required/optional arguments Parses Google docstring Args table Supports completion suggestions completion/complete handler 3. Prompt Message Normalization prompts/list & prompts/get Executes within ASGI request scope Normalizes str, dict, or list[dict] Produces role + TextContent blocks Enforces controller & route guards messages: [{role, content}]
Agent Skills SEP-2640 and SkillSource Pipeline MCPSkillsConfig combines static FilesystemSkillSource directories, request-aware SkillSource plugins, and SkillController classes into the io.modelcontextprotocol/skills extension and skills:// resources. 1. Skill Sources (SEP-2640) MCPSkillsConfig paths=[Path("skills")] (SKILL.md) sources=[CustomSkillSource()] SkillController & @skill_list/@skill_load Skill.build() & Skill.from_content() SkillFile.from_content() + SHA-256 2. Request-Aware Resolution CompositeSkillSource Passes Request + DI per invocation Tenant & user-scoped skill filtering Path traversal protection & symlink guard Parses YAML frontmatter & manifest io.modelcontextprotocol/skills 3. Dual Discovery Surface skills/* & skills://* skills/list (cursor pagination) skills/load (SKILL.md + file manifest) skills/read_file (UTF-8 or base64) expose_as_resources: skills://{name} skills://{name}/{path} fallback

3. MCP Tasks & Distributed Queue Execution

The io.modelcontextprotocol/tasks capability lets clients request asynchronous execution on any tool by supplying params.task. MCPTaskStore persists TaskRecord metadata in the litestar_mcp_tasks store namespace and delegates execution to a pluggable MCPTaskExecutor:

  • QueueTaskExecutor (litestar-mcp[queues]): Auto-attaches when QueuePlugin from litestar-queues is present on the Litestar app (or when passed explicitly to MCPTaskConfig(executor=...)). Captures a PrincipalSnapshot (request.user, request.auth, litestar-security SecurityContext scopes/roles, and Dishka Scope.REQUEST context) so background worker replicas rehydrate the exact caller identity. Supports cooperative tasks/cancel polling and multi-round MCPInputRequiredResult / tasks/update human-in-the-loop resume across distinct replicas.
  • InProcessTaskExecutor: Default zero-dependency executor using an in-process AnyIO TaskGroup for local development, stdio servers, and single-worker deployments.
Distributed QueueTaskExecutor on litestar-queues tools/call with params.task persists a TaskRecord in MCPTaskStore (litestar_mcp_tasks), snapshots PrincipalSnapshot and SecurityContext, enqueues to litestar-queues, rehydrates Dishka Scope.REQUEST on any worker replica, and supports cooperative tasks/cancel and multi-round MCPInputRequiredResult via tasks/update. 1. Task Submission tools/call (params.task) Captures PrincipalSnapshot request.user & request.auth SecurityContext (scopes/roles) Owner ID: user:<subject> Returns CreateTaskResult 2. Durable Store & Broker MCPTaskStore + QueuePlugin Store: litestar_mcp_tasks QueueTaskExecutor.submit() Per-route queue="..." override Postgres / Redis / SQLSpec status: "working" 3. Worker Rehydration Replica Worker Execution Rehydrates user & auth in scope Restores SecurityContext Enters Dishka Scope.REQUEST Polls store for cancel_requested ToolContext.report_progress 4. Pause / Complete tasks/result & update MCPInputRequiredResult -> status: "input_required" tasks/update re-enqueues tasks/cancel sets flag Cross-Replica Safe Multi-Round Human-in-the-Loop & Cooperative Cancellation Across Replicas Replica A: tools/call -> Worker B: MCPInputRequiredResult -> Replica C: tasks/update(input) -> Worker A: tasks/result
Single-Process InProcessTaskExecutor Lifecycle Default single-process task executor running background AnyIO tasks inside the Litestar process while persisting state in MCPTaskStore. 1. Client Request tools/call (params.task) Validates task="optional"|"required" Binds owner_id from authenticated user Applies MCPTaskConfig.default_ttl Enforces max_tasks capacity limit Immediate taskId handle 2. InProcessTaskExecutor AnyIO Background TaskGroup Spawns local async task in same process Holds live CancelScope & input Event Executes tool via internal ASGI dispatch Persists TaskRecord to MCPTaskStore Zero external broker required 3. Polling, Resume & Cancel tasks/get • list • result • cancel Owner-isolated lookup (non-owners masked) tasks/result awaits terminal status tasks/update wakes local input waiter tasks/cancel triggers AnyIO CancelScope notifications/tasks/status SSE When to Use InProcessTaskExecutor vs QueueTaskExecutor InProcessTaskExecutor: Local dev, stdio servers & single-worker apps | QueueTaskExecutor: Multi-replica HTTP deployments

4. Durable DAG Workflows (DurableWorkflowEngine)

DynamicWorkflow defines a Directed Acyclic Graph (DAG) of WorkflowNode steps (wrapping either an Agent or an async Python callable) with topological wave dependencies and conditional WorkflowNode(condition=...) branching.

The same DynamicWorkflow definition runs in-process via WorkflowEngine or across distributed worker replicas via DurableWorkflowEngine on litestar-queues:

  • Conflict-Free Persistence (WorkflowStore): Persists a write-once NodeRecord per completed node ({prefix}:{run_id}:node:{node_name}) and a monotonic WorkflowRun document in litestar_mcp_workflows, preventing sibling fan-in write races.
  • Forever-Unique Node Scheduling: Schedules ready nodes with unique_key="wf-node:{run_id}:{node}" and unique_until="forever". If a node fails transiently and retries via litestar-queues, DurableWorkflowEngine releases the reservation with service.reset_task_identity() before re-raising.
  • Self-Rearming Per-Run Watchdog: Schedules wf-watchdog:{run_id}:{tick} tasks that inspect ready_nodes() and heal stalled runs if a worker process crashes mid-wave.
Distributed DurableWorkflowEngine on litestar-queues DynamicWorkflow DAG waves execute across worker replicas via DurableWorkflowEngine, persisting write-once NodeRecord and monotonic WorkflowRun documents in WorkflowStore (litestar_mcp_workflows), using forever-unique wf-node:{run_id}:{node} reservations with reset_task_identity on retry and per-run wf-watchdog:{run_id}:{tick} recovery. 1. DynamicWorkflow DAG engine.start(name, ...) Topological wave validation Agent nodes & Python step nodes Dynamic StepResult(next_nodes) Captures PrincipalSnapshot Returns run_id immediately 2. WorkflowStore litestar_mcp_workflows Write-once NodeRecord per node Eliminates sibling fan-in races Monotonic WorkflowRun status Completed outputs never lost engine.snapshot(run_id) 3. Forever-Unique Queue wf-node:{run_id}:{node} Deduplicates concurrent fan-in Rehydrates Dishka Scope.REQUEST On transient error: releases key via service.reset_task_identity() Enqueues ready downstream wave 4. Watchdog wf-watchdog:{id}:{t} Per-run self-rearming Checks ready_nodes() Heals crashed workers Stops on terminal state Zero orphan DAG runs Parallel Wave Fan-Out & Conflict-Free Fan-In Wave 0: [extract] -> Wave 1: [enrich_a, enrich_b] (parallel workers) -> Wave 2: [aggregate] (forever-unique gate)
In-Process WorkflowEngine Execution In-process WorkflowEngine executes DynamicWorkflow topological waves concurrently using AnyIO task groups within a single request or background coroutine. 1. Workflow Definition DynamicWorkflow(name="...") WorkflowNode(name, agent=... | fn=...) depends_on=["upstream_a", ...] Validates acyclic dependency graph Supports runtime graph mutation workflow.add_node(...) in step 2. AnyIO Wave Scheduler WorkflowEngine.run(context) Computes ready nodes per wave Runs sibling wave nodes in TaskGroup Passes WorkflowContext(inputs, state) Records ctx.node_outputs[node] Enforces max_steps ceiling 3. Dynamic Branching & Output StepResult & WorkflowContext StepResult(output=..., next_nodes=[...]) Prunes unselected conditional branches Returns completed WorkflowContext Same DynamicWorkflow runs on both WorkflowEngine & DurableWorkflowEngine Portable DAG Definition Across In-Process and Distributed Engines Author DynamicWorkflow once -> test with WorkflowEngine in unit tests -> deploy on DurableWorkflowEngine

5. Multi-Agent Runtime, Handoffs & Protocol Bridges

AgentRuntime manages Agent and AgentGroup execution inside your Litestar application:

  • TurnBudget & GoogleGenAIClient: Enforces per-turn ceilings (max_turns, max_tool_calls, timeout_seconds) while driving ModelClient implementations. GoogleGenAIClient defaults to gemini-3.8-flash and preserves Gemini 3/2.5 thought_signature bytes across multi-turn tool calls.
  • AgentGroup Handoffs: Coordinates a primary agent and specialist sub-agents by injecting a synthetic Literal-typed transfer_to_agent tool without mutating the underlying Agent.tools lists.
  • Owner-Scoped Sessions & SSE Streaming: SessionStore (MemorySessionStore or LitestarStoreSessionStore) isolates conversation history by authenticated owner_id. AgentChatController and QueuedAgentChatController expose ready-to-mount REST and Server-Sent Events (SSE) endpoints.
  • Bidirectional Protocol Bridges: agent_to_mcp, mcp_to_tools, discover_mcp_tools, and agent_to_a2a connect Litestar routes, remote MCP servers, local agents, and A2A 1.0 endpoints without duplicate glue code.
AgentGroup, AgentRuntime, TurnBudget, and Owner-Scoped Sessions AgentChatController and QueuedAgentChatController stream SSE turns through AgentRuntime, enforcing TurnBudget limits, calling GoogleGenAIClient (gemini-3.8-flash), executing non-mutating AgentGroup handoffs via transfer_to_agent, and persisting history in owner-scoped SessionStore. Chat & Queue Controllers AgentChatController QueuedAgentChatController POST /chat/stream (SSE) Owner-Scoped SessionStore Memory / LitestarStore Streams StreamChunk deltas AgentRuntime Plugin TurnBudget Enforcement max_steps • max_tool_calls max_tokens • max_duration_s Parallel tool call fan-out Dishka & Litestar DI resolution TelemetryConfig GenAI Spans AgentGroup Handoffs Coordinator & Specialists Synthetic transfer_to_<agent> Non-mutating member toolsets max_handoffs cycle guard Preserves conversation history Tracks active_agent in session ModelClient GoogleGenAIClient Default: gemini-3.8-flash Preserves thought_signature generate() & stream() Structured output_schema Custom ModelClient protocol Bidirectional Protocol Bridges agent_to_mcp(agent) • mcp_to_tools(app) • discover_mcp_tools(client) • agent_to_a2a(agent_or_group)
Bidirectional Protocol Bridges Between Agent, MCP, and A2A 1.0 Four zero-boilerplate bridges connect Agent and AgentGroup instances to MCP tools/call, remote MCP Client discovery, and A2A 1.0 LitestarA2A executors. MCP Primitives & Clients LitestarMCP / MCPClient Local marked routes (@get/@post) Remote Streamable HTTP servers Exposed as callable MCP tools tools/list & tools/call mcp_to_tools agent_to_mcp Agent / AgentGroup litestar_mcp.agent Unified Tool(name, schema, fn) discover_mcp_tools(mcp_client) Forward headers & ToolContext Agent.run() & Agent.stream() agent_to_a2a A2A 1.0 Interoperability LitestarA2A(A2AConfig) Auto-generates AgentCard & Skills Wraps Agent in AgentExecutor Bridges SessionStore by context_id JSON-RPC 2.0 + HTTP+JSON REST Compose Any Direction Without Duplicate Tool Definitions Litestar Routes -> mcp_to_tools(app) -> Agent -> agent_to_a2a(agent) -> LitestarA2A & agent_to_mcp(agent)

6. A2A 1.0 Interoperability & Multi-Tenancy (LitestarA2A)

LitestarA2A (litestar-mcp[a2a]) integrates the official a2a-sdk 1.0 runtime (AgentCard, AgentSkill, AgentExecutor, DefaultRequestHandler, TaskStore, ServerCallContext) directly into Litestar:

  • Dual Transport Bindings: Serves JSON-RPC 2.0 at POST /a2a and, when enable_http_json=True, mounts native A2A 1.0 HTTP+JSON REST routes (POST /a2a/message:send, POST /a2a/message:stream, GET /a2a/tasks, GET /a2a/tasks/{id}, POST /a2a/tasks/{id}:cancel, POST /a2a/tasks/{id}:subscribe, and push notification config endpoints) while advertising both interfaces in /.well-known/agent-card.json.
  • Split Route Security: Applies A2AConfig.route_opt to execution routes while keeping discovery public (or separately scoped) via A2AConfig.agent_card_route_opt.
  • Multi-Tenant Context Resolution: Extracts tenant identity from /{tenant:str} path parameters or the X-A2A-Tenant request header (with path parameters taking precedence) and populates ServerCallContext.tenant via A2AConfig.context_builder.
LitestarA2A Dual Transport Bindings and AgentExecutor Pipeline LitestarA2A serves /.well-known/agent-card.json, JSON-RPC 2.0 at /a2a, and A2A 1.0 HTTP+JSON REST routes (:cancel, :subscribe, /message:send, /message:stream) backed by DefaultRequestHandler and AgentExecutor. 1. Discovery & Clients /.well-known/agent-card.json agent_card_route_opt (public) Advertises JSONRPC & HTTP+JSON Lists AgentSkill capabilities A2A-Version: 1.0 header enable_http_json=True 2. Dual Transport Routes POST /a2a (JSON-RPC 2.0) POST /a2a/message:send POST /a2a/message:stream (SSE) GET /a2a/tasks & /tasks/{id} POST /a2a/tasks/{id}:cancel POST /a2a/tasks/{id}:subscribe 3. Request Handler DefaultRequestHandler Builds ServerCallContext context_builder hook InMemoryTaskStore / custom PushNotificationConfigStore ProtoJSON Event Serializer 4. Executor AgentExecutor execute(ctx, queue) cancel(ctx, queue) Or agent_to_a2a() Emits TaskStatus & ArtifactUpdate Official a2a-sdk 1.0 Wire Compliance Single A2AConfig mounts JSON-RPC 2.0, HTTP+JSON REST (:send, :stream, :cancel, :subscribe), and Push Notifications
Multi-Tenant Resolution and Split Route Security Multi-tenant resolution extracts tenant identity from /{tenant:str}/a2a path parameters or X-A2A-Tenant headers, passes it through context_builder into ServerCallContext.tenant, and separates public agent_card_route_opt from protected route_opt. 1. Tenant Extraction Sources Path Param + Header base_path="/{tenant:str}/a2a" Or Header: X-A2A-Tenant: acme Precedence: path param > header Applies to JSON-RPC & HTTP+JSON Tenant-scoped URLs in AgentCard 2. Context Builder & Auth ServerCallContext.tenant Default builder populates .tenant Custom A2AConfig.context_builder Forwards request.user & request.auth Enforces Litestar router guards context.state["litestar_request"] 3. Split Route Opt Security route_opt vs agent_card_route_opt route_opt={"auth": required(...)} Protects RPC & REST execution routes agent_card_route_opt={"auth": public()} Keeps /.well-known/agent-card.json open First-party litestar-security ready Zero-Leakage Tenant Isolation GET /acme/a2a/tasks?tenant=acme | X-A2A-Tenant: acme -> ServerCallContext(tenant="acme")

7. Runnable Reference Examples & PEP 723 Single-File Apps

Every subsystem above is accompanied by runnable, pytest-verified reference applications and single-file PEP 723 scripts:

  • Reference Examples & Notes Matrix: Full comparison of the advanced_alchemy and sqlspec Notes reference application families across 10 database/auth variants (no_auth, jwt_auth, cloud_run, google_iap).
  • Single-File uvx & PEP 723 Guide: Zero-setup execution of standalone litestar-mcp scripts using inline PEP 723 dependency metadata, uv run, and litestar mcp bridge.