Stdio Transport, HTTP Bridge & Tool Spec Export¶
In addition to serving Streamable HTTP directly from your Litestar application, litestar-mcp registers a litestar mcp CLI command group with subcommands for in-process stdio execution (stdio), remote Streamable HTTP bridging (bridge), static tool manifest export (export-tool-spec), and interactive CLI inspection (list-tools, list-resources, run).
Choosing Between stdio and bridge¶
| Transport Mode | Command / Python API | How It Executes | Best For |
|---|---|---|---|
| In-Process Stdio | litestar --app my_app:app mcp stdio / run_stdio(app) / run_stdio_async(app) |
Boots the Litestar ASGI lifespan in-process (without binding a TCP port) and routes newline-delimited JSON-RPC over stdin/stdout via ASGIStreamingTransport. |
Single-file PEP 723 scripts (uv run), local desktop MCP hosts spawning the app directly, and test harnesses. |
| HTTP-to-Stdio Bridge | litestar --app my_app:app mcp bridge / run_bridge(endpoint) / run_stdio_streamable_http_bridge(endpoint) |
Reads JSON-RPC from local stdin, attaches MCP 2026-07-28 _meta, SEP-2575 routing headers (MCP-Protocol-Version, Mcp-Method, Mcp-Name, Mcp-Param-*), and bearer tokens, and forwards requests over HTTP/SSE to a running server. |
Connecting stdio-only MCP desktop clients to a remote, containerized, or multi-replica Litestar deployment. |
In-Process Stdio (litestar mcp stdio)¶
Run your Litestar application as an in-process stdio MCP server using the Litestar CLI:
During litestar mcp stdio, standard output is reserved exclusively for newline-delimited JSON-RPC frames; any accidental print() output from application code is redirected to sys.stderr.
Programmatic Stdio & MCPStdioContext¶
You can also drive the in-process stdio loop directly from Python using run_stdio, run_stdio_async, or MCP.run(transport="stdio"). Pass an MCPStdioContext to pre-populate caller identity, task ownership, and request state on every synthesized ASGI scope:
from litestar import Litestar
from litestar_mcp import LitestarMCP, MCPStdioContext
from litestar_mcp.mcp.stdio import run_stdio
app = Litestar(route_handlers=[...], plugins=[LitestarMCP()])
stdio_context = MCPStdioContext(
client_id="local-desktop",
owner_id="user:local-dev",
user={"id": "local-dev", "roles": ["admin"]},
auth={"sub": "local-dev"},
state={"workspace": "default"},
)
run_stdio(app, stdio_context=stdio_context)
MCPStdioContext Field |
Type | Default | Description |
|---|---|---|---|
client_id |
str |
"stdio" |
Default client identifier injected into io.modelcontextprotocol/clientInfo. |
owner_id |
str or None |
None |
Explicit task-owner identifier injected into scope["litestar_mcp.owner_id"] to scope background MCP Tasks created over stdio. |
user |
Any |
None |
Pre-populated request.user principal attached to every synthesized ASGI HTTP scope when no authentication middleware populates scope["user"]. |
auth |
Any |
None |
Pre-populated request.auth context attached to every synthesized ASGI HTTP scope. |
session |
Mapping[str, Any] or None |
None |
Optional session dictionary copied into scope["session"]. |
state |
Mapping[str, Any] or None |
None |
Optional initial state keys merged into scope["state"]. |
HTTP-to-Stdio Bridge (litestar mcp bridge)¶
When your Litestar service runs remotely (or in a separate local process/container) and an MCP host only supports stdio, use litestar mcp bridge:
Automatic Base Path Resolution¶
The bridge inspects the loaded application's LitestarMCP plugin configuration and appends MCPConfig.base_path to --base-url (or http://{LITESTAR_HOST}:{LITESTAR_PORT}, defaulting to http://127.0.0.1:8000):
"""Snippet: bridge target app with a custom MCP base path."""
from litestar import Litestar
from litestar_mcp import LitestarMCP, MCPConfig
def build() -> "Litestar":
# start-example
app = Litestar(
plugins=[LitestarMCP(MCPConfig(base_path="/api/mcp"))],
)
# end-example
return app
For the application above (MCPConfig(base_path="/api/mcp")), running litestar --app my_app:app mcp bridge targets http://127.0.0.1:8000/api/mcp automatically.
litestar mcp bridge & litestar mcp stdio CLI Options¶
| CLI Flag | Applies To | Default | Description |
|---|---|---|---|
--endpoint <url> |
bridge |
None |
Full Streamable HTTP endpoint URL override (takes precedence over --base-url and MCPConfig.base_path). |
--base-url <url> |
bridge |
http://127.0.0.1:8000 |
Base origin URL of the running Litestar app (MCPConfig.base_path is appended automatically). |
--header "Name: value" |
bridge, stdio |
() |
Static HTTP header attached to every forwarded request. Pass multiple times for multiple headers. |
--bearer-env <VAR> |
bridge, stdio |
None |
Environment variable name containing the bearer token. Mutually exclusive with --bearer-cmd. |
--bearer-cmd "<cmd>" |
bridge, stdio |
None |
Command executed to obtain a fresh bearer token on demand (and retried once automatically on 401 Unauthorized). |
--header-name <name> |
bridge, stdio |
"Authorization" |
HTTP header name used when attaching the bearer token (for example, "X-Goog-IAP-JWT-Assertion" for Google IAP). |
--token-prefix <prefix> |
bridge, stdio |
"Bearer " |
Prefix prepended to the resolved token value (pass "" for raw header tokens). |
--timeout <seconds> |
bridge |
30.0 |
HTTP connection, write, and pool timeout in seconds. |
--sse-read-timeout <seconds> |
bridge, stdio |
300.0 |
Maximum quiet-period read timeout in seconds for streaming SSE responses (0 disables the quiet-period timeout). |
--max-message-size <bytes> |
bridge, stdio |
16777216 (16 MiB) |
Maximum byte length for a single newline-delimited JSON-RPC message on stdin or a single SSE event from the server (-1 disables the cap). |
Desktop Client Configuration Example¶
{
"mcpServers": {
"my-litestar-service": {
"command": "litestar",
"args": [
"--app",
"my_app:app",
"mcp",
"bridge",
"--base-url",
"https://api.example.com",
"--bearer-env",
"MCP_API_TOKEN"
]
}
}
}
Static Tool Manifest Export (litestar mcp export-tool-spec)¶
Use litestar mcp export-tool-spec in CI pipelines, schema registries, or documentation builds to export the exact tools/list JSON specification (name, description, inputSchema, outputSchema, annotations) without starting an HTTP server:
Description Length Validation (--strict-bounds)¶
Passing --strict-bounds (or configuring max_tool_description_length, max_param_description_length, or max_output_field_description_length on MCPConfig) runs validate_tool_descriptions against every exported tool entry and exits non-zero if any description exceeds its character budget:
| Bound Option | MCPConfig Default |
--strict-bounds Default |
Validated Target |
|---|---|---|---|
max_tool_description_length |
None |
1000 characters |
tools[].description |
max_param_description_length |
None |
750 characters |
tools[].inputSchema.properties.*.description |
max_output_field_description_length |
None |
300 characters |
tools[].outputSchema.properties.*.description |
You can also invoke validate_tool_descriptions(tools, max_tool_description_length=1000, max_param_description_length=750, max_output_field_description_length=300) directly in your unit test suite to enforce concise LLM tool descriptions in CI.