# Resources A **resource** is data you expose for the application to read. That's the split. A tool is something the **model** decides to call. A resource is something the **application** decides to load (a config file, a record, a document) and put in front of the model as context. You declare one by putting `@mcp.resource(uri)` on a plain Python function. ## Your first resource ```python title="server.py" hl_lines="6-8" --8<-- "docs_src/resources/tutorial001.py" ``` It's the same shape as a tool, plus one thing: the **URI**. Resources are addressed, not named. A client asks for `config://app`, never for `get_config`. The SDK still reads the rest from the function: * The **name** is the function name: `get_config`. * The **description** the client sees is the docstring. * The **content** is whatever you return. During `resources/list` the client gets this: ```json { "name": "get_config", "uri": "config://app", "description": "The active shop configuration.", "mimeType": "text/plain" } ``` And when it reads `config://app`, your function runs and the return value comes back as text: ```python result.contents # [TextResourceContents(uri="config://app", mime_type="text/plain", text="theme=dark\nlanguage=en")] ``` !!! tip Listing is cheap. Your function is **not** called during `resources/list`, only during `resources/read`, and only for the URI that was asked for. Expose a thousand resources and you pay for the ones somebody opens. ### Try it Run the server with the MCP Inspector: ```console uv run mcp dev server.py ``` Open the URL it prints and go to the **Resources** tab. `config://app` is in the list with its description. Click it and the Inspector reads it: there are your two lines of config. ## Resource templates One URI per record doesn't scale. Put a **placeholder** in the URI and a matching parameter on the function: ```python title="server.py" hl_lines="12-13" --8<-- "docs_src/resources/tutorial002.py" ``` `{user_id}` in the URI, `user_id: str` on the function. That is the entire contract. This is now a **resource template**, and it moves house: it leaves `resources/list` and shows up in `resources/templates/list` instead, as a pattern rather than an address: ```json { "name": "get_user_profile", "uriTemplate": "users://{user_id}/profile", "description": "A customer's profile.", "mimeType": "text/plain" } ``` The client fills in the placeholder and reads a concrete URI: `users://42/profile`, `users://ada/profile`. One function answers all of them, with the matched value passed in as `user_id`: ```python result.contents # [TextResourceContents(uri="users://42/profile", text="User 42: 12 orders since 2021.")] ``` Notice the `uri` in the result. It is the **concrete** URI the client asked for, not the template. !!! check The placeholders and the parameters have to agree. Rename the function parameter to `user` while the URI still says `{user_id}` and the decorator refuses **at import time**, before any client gets near it: ```text ValueError: Mismatch between URI parameters {'user_id'} and function parameters {'user'} ``` A mismatch can only ever be a bug, so the SDK makes it impossible to start the server with one. The placeholder syntax is [RFC 6570](https://datatracker.ietf.org/doc/html/rfc6570): `{+path}` for multi-segment values, `{?q,lang}` for optional query parameters, and more. The SDK also applies path-safety checks to extracted values by default. See **[URI templates and path safety](uri-templates.md)** for the full reference. `get_user_profile` can also take a parameter annotated `Context`. The SDK injects it without ever treating it as a URI parameter, and **[The Context](../handlers/context.md)** page covers what it gives you. ## What you return You're not limited to `str`. Give each resource a `mime_type` and return whatever fits: ```python title="server.py" hl_lines="8-9 14-15 20-21" --8<-- "docs_src/resources/tutorial003.py" ``` * `readme` returns a `str`, so it's sent as-is. This is the common case. * `catalog_stats` returns a `dict`, so the SDK serialises it to **JSON text** for you: ```json { "books": 1204, "authors": 391 } ``` * `placeholder_cover` returns `bytes`, so the client gets a `BlobResourceContents` instead of a `TextResourceContents`, with your bytes base64-encoded in its `blob` field. The same rule applies to anything else JSON-serialisable: a list, a Pydantic model, a dataclass. If it isn't a `str` and isn't `bytes`, it becomes JSON. `mime_type` is yours to declare, and it defaults to `text/plain`. The SDK never inspects what you return to guess it, so a `dict` resource you don't label is still advertised as plain text. !!! tip `name=`, `title=` and `description=` are also accepted by `@mcp.resource()` when you don't want to derive them from the function. And when there's no function to write at all, `mcp.server.mcpserver.resources` has ready-made `Resource` classes (`TextResource`, `BinaryResource`, `FileResource`, `HttpResource`, `DirectoryResource`) that you register with `mcp.add_resource(...)`. A client can also **subscribe** to a resource and be notified when it changes; that's the client's half of the story and it lives in **[The Client](../client/index.md)**. ## Recap * `@mcp.resource(uri)` on a function makes it a resource. The URI is the address, the return value is the content, the docstring is the description. * A `{placeholder}` in the URI makes it a **template**: it's listed under `resources/templates/list` and one function serves every URI that matches. * Placeholder names must equal the function's parameter names. Get it wrong and you find out at import time, not in production. * Your function runs when the resource is **read**, not when it's listed. * `str` becomes text, `bytes` becomes a base64 blob, anything else becomes JSON text. `mime_type=` is how you label it. * Tools are for the model to act. Resources are for the application to read. The third primitive, the one a person picks from a menu, is **[Prompts](prompts.md)**.