MCPMCP Python SDK 2.2 · Python 3.10+
0%
1
Curious builder0 XP earned · 300 to level 2
0 daysFinish a lesson to begin
Badge collection0 of 6 unlocked
24 small wins to finish your pathNext lesson

Resource templates: one function for many URIs

No one writes a resource function per order. A placeholder in the URI turns a resource into a template that serves every order.

Exampleshop.py, new
@mcp.resource("orders://{order_id}", mime_type="application/json")
def order_record(order_id: str) -> Order:
    """The full record for one order."""
    if order_id not in ORDERS:
        raise ResourceNotFoundError(f"No order with id {order_id!r}.")
    return Order(id=order_id, **ORDERS[order_id])

{order_id} in the URI matches the order_id parameter. Reading orders://A17 calls the function with order_id="A17". The imports gain ResourceNotFoundError, the resource version of ToolError.

Example
import asyncio

from mcp import Client
from shop import mcp


async def main():
    async with Client(mcp) as client:
        templates = await client.list_resource_templates()
        print([t.uri_template for t in templates.resource_templates])
        print([r.uri for r in (await client.list_resources()).resources])

        result = await client.read_resource("orders://A17")
        print(result.contents[0].text)


asyncio.run(main())

A template is listed separately from plain resources, as a pattern, because there is nothing to read until someone fills in the placeholder. The returned Order was turned into JSON text.

An order that does not exist

Example
import asyncio

from mcp import Client, MCPError
from shop import mcp


async def main():
    async with Client(mcp) as client:
        try:
            await client.read_resource("orders://Z9")
        except MCPError as error:
            print(error.error.code, error.error.message)
            print(error.error.data)


asyncio.run(main())

Unlike a tool, a resource read has no error result: it returns contents or the request fails. ResourceNotFoundError becomes the protocol error code the specification gives a missing resource, with the URI in data so the client knows which read failed.

A mistake caught early

Example
from mcp.server import MCPServer

mcp = MCPServer("Shop support")

@mcp.resource("orders://{order_id}")
def order_record(order: str) -> str:
    return order

The placeholder and the parameter must have the same name. A mismatch can only be a bug, so the decorator refuses when the file is imported, before any client connects.

Try it yourself
  • Add customers://{customer_id}/orders, returning a list of order ids.
  • Read orders://B42.
  • Remove mime_type from the template and compare contents[0].mime_type.

This is what real progress feels like.