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:
"""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:
- MIME Type Precedence:
- Route
optmcp_resource_mime_type="...". - Decorator
@mcp_resource(mime_type="..."). - The Litestar route handler's own
media_typeattribute (for example,@get("/reports/latest", mcp_resource=True, media_type="application/pdf")). - Fallback default:
"application/json". - Text vs Base64 Blob Routing:
- 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 undercontents[].text. - Binary MIME types (or text MIME payloads containing non-UTF-8 bytes) are base64-encoded and returned under
contents[].blob, bounded byMCPConfig.max_blob_bytes(default25 MiB).
"""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:
"""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 underresourceTemplateswithuriTemplate,name,description, andmimeType. - Variable Extraction (
resources/read): When a client requests a concrete URI matching the template (for example,app://workspaces/acme/files/readme),match_uriextracts 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 inRegistry.templateswins.
URI Resolution Precedence & skill:// Interaction¶
When resources/read receives a params.uri string, MCPHandlerService resolves the URI in a strict four-stage order:
litestar://openapi: Returns the application's serialized OpenAPI schema.- Agent Skills (
skill://<name>/<relative_path>): WhenMCPConfig.skillsis enabled and the URI starts withskill://, resolution delegates exclusively toSkillCatalog. Any unmatchedskill://URI fails immediately withINVALID_PARAMS(-32602) without falling through to route handlers. - Concrete Handler Resources: Matches against
discovered_resourcesby comparingparams.urito each handler'smcp_resource_uri(orlitestar://<name>). - RFC 6570 URI Templates: Evaluates
Registry.templatesin registration order usingmatch_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 aPOST /mcprequest with"method": "subscriptions/listen"and"params": {"notifications": {"notifications/resources/list_changed": true, ...}}to receive a long-livedtext/event-streamSSE response with periodic: keepalivecomments (MCPConfig.subscription_keepalive_seconds). - Dynamic Registration Broadcasts: Calling
plugin.registry.register_resource(name, handler)(orregister_tool/register_prompt) at runtime automatically broadcastsnotifications/resources/list_changedto subscribed streams. - Cross-Worker Fan-Out: Pass a configured Litestar
ChannelsPlugintoMCPConfig(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": ...} |