Configuration#

The Litestar MCP plugin is configured through MCPConfig. This page walks through each knob the plugin exposes, from a default registration to task-lifecycle support.

Minimal Setup#

The plugin registers with sensible defaults when no configuration is passed. Every marked route is picked up and served from /mcp.

docs/examples/snippets/configuration_minimal.py#
app = Litestar(route_handlers=[], plugins=[LitestarMCP()])

Custom Configuration#

Override the base path, server name, or OpenAPI visibility via MCPConfig.

docs/examples/snippets/configuration_custom.py#
config = MCPConfig(
    base_path="/api/mcp",
    name="My MCP Server",
    include_in_schema=True,
)
app = Litestar(route_handlers=[], plugins=[LitestarMCP(config)])

Standalone Prompts#

Prompt callables that are not bound to an HTTP route are registered by passing them to LitestarMCP(prompts=[...]). Each function must first be decorated with mcp_prompt(); the plugin rejects plain callables to keep prompt metadata explicit.

docs/examples/snippets/configuration_prompts.py#
app = Litestar(
    route_handlers=[],
    plugins=[
        LitestarMCP(
            MCPConfig(name="prompt-demo"),
            prompts=[greet],
        ),
    ],
)

See Marking Routes for the handler-based mcp_prompt opt-key form, which routes a prompt under HTTP and publishes it via prompts/get.

Task Lifecycle#

Enable the opt-in io.modelcontextprotocol/tasks extension by passing an MCPTaskConfig. Task records use a Litestar Store; the default in-memory Store is intended for development.

A shared Store persists records but does not distribute the task runner, input queue or cancellation queue. Applications must coordinate worker ownership and recovery for tasks that cross processes or survive restarts. Require authentication on every protected Tasks operation; an owner-aware Store does not authenticate anonymous callers. See Security for the task-handle authorization boundary.

docs/examples/snippets/configuration_tasks.py#
config = MCPConfig(
    tasks=MCPTaskConfig(
        default_ttl_ms=300_000,
        max_ttl_ms=3_600_000,
        poll_interval_ms=1_000,
    ),
)
app = Litestar(route_handlers=[], plugins=[LitestarMCP(config)])

Skills#

Enable the opt-in io.modelcontextprotocol/skills extension by passing an MCPSkillsConfig. The catalog is loaded once from the configured directories at startup; a skill folder that fails validation fails application startup, not a later request.

Pass Path objects for paths — the annotation is Sequence[Path], and while a plain string is coerced at runtime, a type-checked caller passing str sees a mypy or pyright error. See MCP Skills for the directory layout, wire methods, and error contract.

docs/examples/snippets/configuration_skills.py#
config = MCPConfig(
    skills=MCPSkillsConfig(
        paths=[SKILLS_ROOT],
        directory_read=True,
        max_files_per_skill=512,
        max_bytes_per_skill=16_777_216,
    ),
)
app = Litestar(route_handlers=[], plugins=[LitestarMCP(config)])

Configuration Options#

Option

Default

Description

base_path

"/mcp"

Base path for the MCP Streamable HTTP endpoint.

include_in_schema

False

Whether to include MCP routes in the OpenAPI schema.

name

None

Server name override (falls back to the OpenAPI title).

guards

None

Litestar guards applied to the MCP router.

route_opt

None

Route opt mapping merged into the MCP JSON-RPC handler. Declare an opt-based authentication policy here, for example {"auth": required("api-key")} with litestar-security.

allowed_origins

None

Restrict accepted Origin header values.

include_operations / exclude_operations

None

Filter exposure by Litestar operation name.

include_tags / exclude_tags

None

Filter exposure by OpenAPI tags.

tasks

False

Enable the Tasks extension, optionally with a persistent Store.

skills

None

Serve Agent Skills over MCP from the configured directories; see MCP Skills.

cache_ttl_ms / cache_scope

0 / "private"

Cache hints on discovery, list, and resource-read results.

subscription_max_streams

10000

Maximum concurrent subscriptions/listen response streams.

subscription_keepalive_seconds

15.0

Seconds between subscription keepalive comments.

subscription_channels

None

Optional configured Litestar ChannelsPlugin for cross-worker fan-out.

stream_queue_capacity

256

Bounds subscription and request-progress queues. Progress applies backpressure; slow subscription consumers are completed and disconnected.

stream_cleanup_timeout

5.0

Positive finite seconds allowed for cooperative response cleanup; expiry is logged as incomplete cleanup.

before_tool_call

None

Optional callback invoked once before each tools/call dispatch.

after_tool_call

None

Optional callback invoked once after each tools/call dispatch with the result or exception and elapsed duration.

list_page_size

100

Server-chosen page size for the */list methods (see below).

max_blob_bytes

25 * 1024 * 1024

Maximum raw byte size for embedded MCP blobs in tool results and resources/read responses. Set None to disable the library cap.

The LitestarMCP constructor also accepts a top-level prompts argument — a sequence of @mcp_prompt-decorated callables — for standalone prompt registration (see above).

Filters apply both to list responses and direct invocation. A filtered tool or resource is omitted from tools/list, resources/list, or resources/templates/list and a direct tools/call / resources/read returns the same not-found response as an unknown name or URI. Filters narrow the MCP exposure surface; use guards or auth middleware for access control.

Tool-Call Callbacks#

Use before_tool_call and after_tool_call when you need audit, metrics, or tracing hooks that are independent of route ownership layers. Both callbacks receive the MCP tool name, a shallow copy of the submitted arguments, and the synthesized litestar.Request used for the handler dispatch. after_tool_call also receives result, exception, and duration keyword-only values, and fires for successes, guard failures, error responses, and unhandled exceptions. Callback exceptions are logged and swallowed so observability code does not alter tool-call behavior.

docs/examples/snippets/configuration_tool_callbacks.py#
async def after_tool_call(
    tool_name: "str",
    arguments: "dict[str, Any]",
    request: "Request[Any, Any, Any]",
    *,
    result: "Any",
    exception: "Exception | None",
    duration: "float",
) -> "None":
    request.app.logger.info(
        "mcp tool=%s duration=%0.4f failed=%s",
        tool_name,
        duration,
        exception is not None,
    )


config = MCPConfig(after_tool_call=after_tool_call)

List Pagination#

tools/list, resources/list, resources/templates/list, and prompts/list are paginated per the MCP spec's opaque-cursor model. Each accepts an optional cursor parameter and returns nextCursor when another page is available; treat nextCursor as an opaque token and pass it back as params.cursor until the response omits it. An invalid cursor returns INVALID_PARAMS (-32602).

The MCP spec lets the server pick the page size — clients cannot request a limit on these methods. Set it with the MCPConfig.list_page_size option (e.g. MCPConfig(list_page_size=25)); it defaults to 100 and must be a positive integer.

These methods enumerate the catalog of registered primitives, which is built once at startup; the cursor is a base64-encoded offset into that stable list. Implement application-data pagination inside an individual tool. The Tasks extension intentionally has no list method.

Environment Overrides#

MCPConfig is a plain dataclass, so the ordinary Litestar pattern applies: read the environment before constructing it and pass the resolved values through. For example, to keep base_path and name configurable at deploy time:

export MCP_BASE_PATH=/api/mcp
export MCP_SERVER_NAME="My MCP Server"

Then build MCPConfig using os.getenv for each option - the shape is identical to the Custom Configuration snippet above, just with environment lookups replacing literal values.