Exposing Resources: Read-Only Data via Stable URIs

~10 min read

Resources expose read-only content — files or generated text — through a stable URI that clients can fetch at any time during a session, distinct from Tools' action-triggering role.

Alongside Tools (previous subtopic), Resources are the second capability an mcp-use server commonly exposes — and the distinction between the two matters, since it determines which primitive you reach for when building a given piece of server functionality.

Resources expose read-only content such as files or generated text through a stable URI. Clients can fetch this content at any time during a session. The key word here is 'read-only': unlike a Tool, which typically triggers an action or computation (and can have side effects), a Resource is purely about retrieval — handing back data that already exists (or can be generated deterministically) without changing any state as a side effect of being read.

The 'stable URI' part is also worth being precise about: a Resource is identified by an address (like file://path/to/doc.txt or a custom URI scheme your server defines), and a client can fetch that same URI repeatedly to get the current content — the URI itself doesn't change even if what it points to is later updated. This is what makes resources feel similar to fetching a URL: stable addressing, content retrieval, no side effects from the read itself.

This maps directly onto the general Resources primitive covered in this curriculum's mcp-core-primitives topic (controlled by the app, passive read-only data like files, calendars, knowledge bases) — this subtopic is specifically about how that same primitive is DECLARED when building a server with mcp-use's framework: a lightweight, declarative registration (mirroring the Tool registration pattern from the previous subtopic) that associates a URI pattern with a callback returning that URI's content. Practically, this is the mechanism you'd reach for to expose things like a local file's contents, a document store, or any other 'let the agent read this' scenario — versus a Tool, which you'd reach for when the agent needs to actually DO something, not just read something.

💻 Code example

# Book's mcp-use example is TypeScript; here's the equivalent
# declarative resource registration in Python via the MCP SDK --
# a stable URI mapped to a callback returning read-only content.
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("docs-server")

@mcp.resource("file://{path}")
def read_file(path: str) -> str:
    """A Resource: read-only, no side effects, fetchable
    repeatedly at the same stable URI -- distinct from a Tool,
    which triggers an action."""
    with open(path, "r") as f:
        return f.read()

if __name__ == "__main__":
    mcp.run()
    # A client can now GET file:///home/user/notes.txt at any
    # point during a session -- the URI stays stable even as the
    # underlying file's contents may change between reads

💬 Deep Dive with AI

Key points

  • Resources expose read-only content (files, generated text) through a stable URI, unlike Tools which trigger actions with potential side effects
  • Clients can fetch a resource's content at any time during a session via its stable URI
  • This maps to the general Resources primitive covered elsewhere (app-controlled, passive read-only data) — this subtopic covers how it's declared specifically in mcp-use
  • In mcp-use, resource registration mirrors the declarative shape of tool registration: a URI pattern mapped to a callback
  • Reach for a Resource when the agent needs to read something; reach for a Tool when the agent needs to do something