TechByteByByte

Resources: Exposing Context, Not Actions

A resource represents data the model can read, not something it executes. See the precise, real distinction from tools, real URI schemes, and a genuine resource template.

#MCP#Resources#Context

Recall Module 5โ€™s own closing promise โ€” Resources are the second real capability type, and the precise distinction from Tools matters enough to state plainly, right away: a Tool does something; a Resource is something. A Tool performs a real action, possibly with side effects. A Resource exposes real, existing data the model can read, with no action performed at all.

The real distinction, made concrete

flowchart LR
    A[Tool] --> B["do_something()\nmay have side effects"]
    C[Resource] --> D["here_is_context\nread-only, no side effects"]

Recall Module 5โ€™s create_support_ticket โ€” a genuine action, creating a real, new record. A Resource never does anything like that; it only ever hands back real, existing content.

Exposing your first, real resource

Letโ€™s build one directly, using the same FastMCP framework from Module 5.

Weโ€™ll expose a real, local fileโ€™s content as something a Client can genuinely read.

from fastmcp import FastMCP

mcp = FastMCP("docs-server")

@mcp.resource("file://readme")
def get_readme() -> str:
    """The project's real README content."""
    return "# My Project\nThis project does real, useful things."  # genuinely just returns data

Notice @mcp.resource(...), not @mcp.tool(...) โ€” a genuinely distinct, real decorator, and notice the real, literal "file://readme" โ€” a URI, not a plain function name.

Real URI schemes, and why they matter

Recall every capability this course has built so far โ€” each one needs a real, unambiguous way to be identified. Resources use real URIs for exactly this.

Letโ€™s build resources using several, genuinely different, real URI schemes.

@mcp.resource("file:///documents/policy.pdf")
def get_policy() -> str:
    """A real, local file resource."""
    return "Return policy: 30 days."

@mcp.resource("db://customers/schema")
def get_schema() -> str:
    """A real, custom scheme โ€” this one describes a database schema."""
    return "customers(id, name, plan, created_at)"

@mcp.resource("config://app/settings")
def get_settings() -> dict:
    """A real, custom scheme for exposing application configuration."""
    return {"max_upload_mb": 25, "timezone": "UTC"}

Notice db:// and config:// are genuinely custom, made-up schemes โ€” MCP doesnโ€™t require a URI to point at a real, literal file. The scheme is simply a real, structured way of naming what kind of resource this is, letting a Client reason about it before ever reading its actual content.

Resource templates โ€” parameterized, real resources

Real applications rarely expose one, fixed resource per real thing โ€” recall Module 5โ€™s own parameterized tools. Resources support the same real idea.

Letโ€™s build a genuine, templated resource, covering an entire real category of content with one, single definition.

@mcp.resource("github://repo/{owner}/{repo}/issues/{issue_id}")
def get_github_issue(owner: str, repo: str, issue_id: str) -> dict:
    """A real, templated resource โ€” one definition, covering every real GitHub issue."""
    return {"owner": owner, "repo": repo, "issue_id": issue_id, "title": "Example issue", "status": "open"}

Notice {owner}, {repo}, and {issue_id} โ€” genuine, real template variables. A Client can request github://repo/acme/webapp/issues/42, and the Server fills in the real, actual values, without you needing a separate, hardcoded resource definition for every single real issue.

Resource discovery, the same real pattern as tools

Recall Module 5โ€™s own tools/list โ€” Resources use the genuinely same, real discovery pattern.

# the real, actual shape of a resources/list request
list_resources_request = {"jsonrpc": "2.0", "id": 3, "method": "resources/list"}

# and the real server response โ€” every resource this server genuinely exposes
list_resources_response = {
    "jsonrpc": "2.0",
    "id": 3,
    "result": {
        "resources": [
            {"uri": "file:///documents/policy.pdf", "name": "Return Policy", "mimeType": "text/plain"},
            {"uri": "config://app/settings", "name": "App Settings", "mimeType": "application/json"},
        ]
    },
}

Notice mimeType โ€” a real, genuine hint about the actual shape of the content, letting a Client decide how to handle it before ever reading it.

Common mistakes worth avoiding

Building a Resource that actually performs an action. Recall this moduleโ€™s own opening distinction directly โ€” if a capability genuinely has a real side effect, even a small one, it belongs as a Tool, not a Resource.

Hardcoding a separate resource for every real instance of the same kind of content. Recall this moduleโ€™s own real GitHub example โ€” a templated resource, with real {variables}, covers an entire genuine category with one, single definition.

Choosing a URI scheme with no real, consistent meaning. Recall this moduleโ€™s own real examples โ€” db://, config://, github:// each genuinely signal what kind of content to expect; a real, inconsistent scheme makes that signal meaningless.

What you should take away from this module

  • A Tool performs a real action; a Resource exposes real, existing, read-only data โ€” this distinction is worth never blurring.
  • Real URI schemes, including genuinely custom ones, identify what kind of resource something is, before itโ€™s ever read.
  • Resource templates, with real {variables}, cover an entire category of content with one, single definition.
  • resources/list is the real, genuine discovery mechanism, structurally identical to tools/list.

Where this goes next

The next module covers Prompts โ€” the third and final real capability type, for exposing reusable, genuine prompt templates rather than actions or raw data.

Author
TechByteByByte Editorial Team
Reviewed by
TechByteByByte Admin
Published
Last reviewed