3 concepts MCP: - Tools — actions (create, update, delete) - Prompts — instructions (templates) - Resources — data (read-only)
- Hiểu MCP resources — read-only data exposed by server
- Static vs dynamic resources
- Use @mcp.resource decorator
- URI patterns cho resources
When resources vs tools?
Resource = GET. Tool = POST/PUT/DELETE (loose analogy).
| Resource | Tool | |
|---|---|---|
| Purpose | Expose data | Perform action |
| Side effect | None (read) | May have |
| URI | Yes (unique identifier) | No |
| Example | file://report.pdf, db://users/123 | get_user(123), create_post() |
Static resource
Fixed URI, content:
Clients can read resource docs://report.pdf.
@mcp.resource("docs://report.pdf")
def get_report():
return """This report describes Q3 performance..."""Dynamic resource (URI template)
URI with parameters:
Client reads docs://report.pdf or docs://plan.md — same function.
@mcp.resource("docs://{doc_id}")
def get_doc(doc_id: str):
if doc_id not in docs:
raise ValueError(f"Doc {doc_id} not found")
return docs[doc_id]Full example — documents
Test via Inspector → Resources tab.
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("DocumentMCP")
docs = {
"report.pdf": "Q4 report content...",
"plan.md": "Project plan content...",
}
# List all docs (static resource listing)
@mcp.resource("docs://all")
def list_all_docs() -> str:
return "\n".join(docs.keys())
# Individual doc (dynamic)
@mcp.resource("docs://{doc_id}")
def read_doc(doc_id: str) -> str:
if doc_id not in docs:
raise ValueError(f"Doc '{doc_id}' not found")
return docs[doc_id]Client read resource
resources = await client.list_resources()
# [{"uri": "docs://all", "name": "All docs"}, ...]
# Read specific
result = await client.read_resource("docs://report.pdf")
# ReadResourceResult with contents
for content in result.contents:
if content.type == "text":
print(content.text)Why resources matter
1. Auto-discovery
Client list all resources without prior knowledge. User browse data without remembering tool names.
2. Caching-friendly
Read-only + URI-identified → client can cache by URI.
3. Standard contract
Any client can read any resource từ any MCP server. Universal data access pattern.
4. Claude awareness
Claude thấy resources available, có thể reference: "Based on docs://report.pdf, the answer is..."
Resource types
Text
JSON
@mcp.resource("schema://users")
def get_schema() -> str:
return """CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(100)
)"""JSON
Binary (via URI)
import json
@mcp.resource("config://app")
def get_config() -> str:
return json.dumps({"version": "1.0", "debug": False})Binary (via URI)
MCP supports binary resources qua specific content type.
@mcp.resource("image://logo")
def get_logo():
with open("logo.png", "rb") as f:
return f.read() # binary contentResources + tools combo
Resources cho browsing, tools cho mutation:
Clean separation.
# Resource: read-only
@mcp.resource("docs://{doc_id}")
def get_doc(doc_id):
return docs[doc_id]
# Tool: modify
@mcp.tool()
def edit_doc(doc_id: str, old: str, new: str):
docs[doc_id] = docs[doc_id].replace(old, new)URI conventions
Good:
Bad:
Use scheme that makes sense cho domain.
- docs://report.pdf — clear scheme + identifier
- users://alice — namespace
- file:///home/jimmy/note.md — RFC 3986 format
- doc1 — no scheme
- somedata — no structure
Anti-patterns
❌ Resource with side effects
Resources MUST be read-only. Client caches, call multiple times → inconsistent.
Fix: Tool if action needed.
❌ Expose entire database as resource
Too much data, tied to impl details.
Fix: Curated views. docs://all, docs://{id}, specific.
❌ Mix text + binary without content type
Client parse fail.
Fix: Explicit MIME types.
@mcp.resource("counter://next")
def counter():
global count
count += 1
return str(count) # MUTATING on read!Áp dụng ngay
Bài tập 1: Add resources (20 phút)
Add to server:
Test via Inspector.
Bài tập 2: Client list + read (15 phút)
Client enumerate resources, read one, print content.
- docs://list — list all doc IDs
- docs://{id} — specific doc
- docs://stats — count + total size
Tóm tắt
🎯 Resources = read-only data với URI.
🎯 Static: fixed URI. Dynamic: URI template with params.
🎯 @mcp.resource("scheme://path") decorator.
🎯 Complement tools: resources for browse, tools for mutate.
🎯 Never mutate on resource read — contract.