Skip to content

Resources & URI Templates

Resources represent read-only application data—such as configuration snapshots, OpenAPI schemas, database projections, or binary documents—exposed via resources/list, resources/templates/list, and resources/read.

Static Resources

Mark any Litestar route handler with mcp_resource=True (or mcp_resource="<name>") to publish it in resources/list:

docs/examples/snippets/marking_resources.py
"""Snippet: marking a route as an MCP resource. Referenced from docs/usage/marking.rst."""

from typing import Any

from litestar import Litestar, get

from litestar_mcp import LitestarMCP


def build() -> "Litestar":
    # start-example
    @get("/api/schema", mcp_resource="api_schema")
    async def get_schema() -> "dict[str, Any]":
        """Return the API JSON schema - exposed as the ``api_schema`` MCP resource."""
        return {"type": "object", "properties": {}}

    app = Litestar(route_handlers=[get_schema], plugins=[LitestarMCP()])
    # end-example
    return app

By default, a resource named api_schema is assigned the URI litestar://api_schema. Pass mcp_resource_uri="custom://schema/v1" on the route decorator to override the concrete URI.

Built-In litestar://openapi Resource

Every LitestarMCP application automatically advertises and serves a synthetic resource at litestar://openapi with mimeType: "application/json". Reading litestar://openapi via resources/read returns the live OpenAPI schema generated by Litestar (app.openapi_schema.to_schema()), giving LLM agents full visibility into your HTTP API contract even when MCPConfig.include_in_schema is False.

MIME Types & Binary Resources

litestar-mcp determines how to serialize a resource's payload (text vs base64 blob) from its resolved MIME type:

  1. MIME Type Precedence:
  2. Route opt mcp_resource_mime_type="...".
  3. Decorator @mcp_resource(mime_type="...").
  4. The Litestar route handler's own media_type attribute (for example, @get("/reports/latest", mcp_resource=True, media_type="application/pdf")).
  5. Fallback default: "application/json".
  6. Text vs Base64 Blob Routing:
  7. Text-compatible MIME types (text/*, application/json, *+json, *+xml, application/xml, application/javascript, application/yaml, application/x-yaml) are decoded as UTF-8 and returned under contents[].text.
  8. Binary MIME types (or text MIME payloads containing non-UTF-8 bytes) are base64-encoded and returned under contents[].blob, bounded by MCPConfig.max_blob_bytes (default 25 MiB).
docs/examples/snippets/resource_binary.py
"""Snippet: binary MCP resources with MIME metadata."""

from litestar import Litestar, get

from litestar_mcp import LitestarMCP, mcp_resource


def build() -> "Litestar":
    # start-route-example
    @get("/reports/latest", mcp_resource=True, media_type="application/pdf")
    async def latest_report() -> bytes:
        return b"...pdf bytes..."

    # end-route-example

    # start-decorator-example
    @mcp_resource(mime_type="application/pdf")
    @get("/reports/archive")
    async def archived_report() -> bytes:
        return b"...pdf bytes..."

    # end-decorator-example

    return Litestar(route_handlers=[latest_report, archived_report], plugins=[LitestarMCP()])

RFC 6570 Level 1 Resource URI Templates

Pass mcp_resource_template="scheme://path/{var}" alongside mcp_resource (or @mcp_resource(uri_template="...")) to register a parameterized RFC 6570 Level 1 URI template:

docs/examples/snippets/resource_template.py
"""Register a resource with an RFC 6570 Level 1 URI template."""

# start-example
from litestar import Litestar, get
from litestar.params import FromPath

from litestar_mcp import LitestarMCP


@get(
    "/workspaces/{workspace_id:str}/files/{file_id:str}",
    mcp_resource="workspace_file",
    mcp_resource_template="app://workspaces/{workspace_id}/files/{file_id}",
    sync_to_thread=False,
)
def read_workspace_file(workspace_id: "FromPath[str]", file_id: "FromPath[str]") -> "dict[str, str]":
    """Return the concrete workspace/file payload."""
    return {"workspace": workspace_id, "file": file_id}


app = Litestar(route_handlers=[read_workspace_file], plugins=[LitestarMCP()])
# end-example
  • Discovery (resources/templates/list): Registered templates are advertised under resourceTemplates with uriTemplate, name, description, and mimeType.
  • Variable Extraction (resources/read): When a client requests a concrete URI matching the template (for example, app://workspaces/acme/files/readme), match_uri extracts each {var} segment and passes {"workspace_id": "acme", "file_id": "readme"} into the internal ASGI dispatch scope, binding directly to the route handler's path or scalar parameters.
  • Segment Matching Rules: Each {var} placeholder matches a single non-empty segment and never crosses /. If multiple templates could match a URI, the first registered template in Registry.templates wins.

URI Resolution Precedence & skill:// Interaction

When resources/read receives a params.uri string, MCPHandlerService resolves the URI in a strict four-stage order:

  1. litestar://openapi: Returns the application's serialized OpenAPI schema.
  2. Agent Skills (skill://<name>/<relative_path>): When MCPConfig.skills is enabled and the URI starts with skill://, resolution delegates exclusively to SkillCatalog. Any unmatched skill:// URI fails immediately with INVALID_PARAMS (-32602) without falling through to route handlers.
  3. Concrete Handler Resources: Matches against discovered_resources by comparing params.uri to each handler's mcp_resource_uri (or litestar://<name>).
  4. RFC 6570 URI Templates: Evaluates Registry.templates in registration order using match_uri(entry.template, uri).

Avoid skill:// on Route Handlers

Once MCPConfig.skills is enabled, the skill:// URI scheme is owned by SkillCatalog. Never assign mcp_resource_uri="skill://..." to a route handler, as resources/read intercepts all skill:// URIs before checking route handlers.

Resource Subscriptions & Change Notifications

LitestarMCP maintains a SubscriptionManager on plugin.registry.subscription_manager for real-time catalog and resource change notifications:

  • Opening a Subscription Stream (subscriptions/listen): Clients send a POST /mcp request with "method": "subscriptions/listen" and "params": {"notifications": {"notifications/resources/list_changed": true, ...}} to receive a long-lived text/event-stream SSE response with periodic : keepalive comments (MCPConfig.subscription_keepalive_seconds).
  • Dynamic Registration Broadcasts: Calling plugin.registry.register_resource(name, handler) (or register_tool / register_prompt) at runtime automatically broadcasts notifications/resources/list_changed to subscribed streams.
  • Cross-Worker Fan-Out: Pass a configured Litestar ChannelsPlugin to MCPConfig(subscription_channels=channels_plugin) so notification broadcasts fan out across all worker processes.

Resource Error Contract

Unlike tools (which report execution failures via isError: true), resources/read maps failures to structured JSON-RPC 2.0 error codes:

Failure Condition JSON-RPC Error Code Error Payload Details
Unknown, filtered, or empty resource URI INVALID_PARAMS (-32602) {"code": -32602, "message": "Resource not found: <uri>", "data": {"uri": "<uri>"}}
Handler raises HTTPException / Guard rejection / Exception INTERNAL_ERROR (-32603) {"code": -32603, "message": "Resource read failed", "data": {"statusCode": <http_status>, "content": ...}}
Binary payload exceeds MCPConfig.max_blob_bytes INTERNAL_ERROR (-32603) {"code": -32603, "message": "Resource read failed", "data": ...}
Skill file SHA-256 digest mismatch (SkillIntegrityError) INTERNAL_ERROR (-32603) {"code": -32603, "message": "Resource read failed", "data": ...}