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/listis the real, genuine discovery mechanism, structurally identical totools/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