The Three Primitives
Tools are functions Claude can call that do things — they take actions, retrieve dynamic data, or interact with external systems.
# MCP Server — exposing Tools
@server.tool("search_knowledge_base")
async def search_knowledge_base(query: str, max_results: int = 10) -> list[dict]:
"""
Searches the knowledge base for relevant articles.
Dynamic — results depend on the query at call time.
"""
return await kb_client.search(query, limit=max_results)
@server.tool("submit_article_feedback")
async def submit_article_feedback(article_id: str, rating: int, comment: str) -> dict:
"""
Submits user feedback on a knowledge base article.
Has a side effect — writes to the database.
"""
return await feedback_client.submit(article_id, rating, comment)
Characteristics of Tools:
- Called by Claude when it decides to take an action
- Can have side effects (writes, sends, creates, deletes)
- Return structured data
- Have input schemas Claude uses to construct calls
Primitive 2: Resources
Resources are data sources — read-only, addressable by URI, representing persistent data.
# MCP Server — exposing Resources
@server.resource("knowledge://articles/{article_id}")
async def get_article(article_id: str) -> Resource:
"""
Provides read access to a specific knowledge base article.
Read-only — doesn't change anything.
"""
article = await kb_client.get_article(article_id)
return Resource(
uri=f"knowledge://articles/{article_id}",
name=article.title,
description=f"Knowledge base article: {article.title}",
mimeType="text/markdown",
text=article.content
)
@server.resource("knowledge://categories")
async def list_categories() -> Resource:
"""
Provides the list of all article categories.
"""
categories = await kb_client.get_categories()
return Resource(
uri="knowledge://categories",
name="Article Categories",
mimeType="application/json",
text=json.dumps(categories)
)
Characteristics of Resources:
- Addressed by URI (like web URLs)
- Read-only — no side effects
- Represent data that exists independently of the request
- Claude reads them like files or documents
When Resource is correct vs Tool:
| Scenario | Primitive |
|---|
| Get the content of a specific article | Resource |
| Search for articles matching a query | Tool (dynamic) |
| Read configuration settings | Resource |
| Update configuration settings | Tool (has side effects) |
| List available categories (static) | Resource |
| Generate a personalized recommendation | Tool (dynamic/computed) |
Primitive 3: Prompts
Prompts are reusable templates that structure how Claude approaches specific tasks. They’re invoked by users or client applications, not by Claude itself.
# MCP Server — exposing Prompts
@server.prompt("troubleshoot_issue")
async def troubleshoot_issue_prompt(
issue_description: str,
product: str
) -> list[PromptMessage]:
"""
Provides a structured workflow for troubleshooting a product issue.
"""
return [
PromptMessage(
role="user",
content=f"""Help me troubleshoot this {product} issue:
{issue_description}
Please:
1. First, ask me the 3 most important diagnostic questions
2. Based on my answers, identify the most likely root cause
3. Provide step-by-step resolution instructions
4. If you cannot resolve it, tell me what information to escalate to support"""
)
]
@server.prompt("monthly_report")
async def monthly_report_prompt(month: str, year: str) -> list[PromptMessage]:
"""
Template for generating monthly performance reports.
"""
return [
PromptMessage(
role="user",
content=f"Generate a monthly performance report for {month} {year}. "
f"Use the get_metrics tool to retrieve data, then structure "
f"the report with: executive summary, key metrics, trend analysis, "
f"and recommended actions."
)
]
Characteristics of Prompts:
- Pre-written instructions for specific workflows
- Parameterized (can take inputs that get embedded in the prompt)
- Invoked by users/clients, not by Claude
- Structure Claude’s approach to a task without requiring the user to know how to prompt effectively
A common exam question: what’s the difference between defining a tool directly in the Claude API tools array vs exposing a tool through MCP?
| Aspect | Direct API Tool | MCP Tool |
|---|
| Where defined | In your API call | In an MCP server |
| Discovery | Static — you define what Claude gets | Dynamic — Claude/client discovers what’s available |
| Reusability | One application | Any MCP-compatible client |
| Transport | In-process | Separate process (stdio, SSE) |
| Best for | Application-specific operations | Shared enterprise tools |
Key Takeaways
- Tools: callable functions that do things — can have side effects
- Resources: read-only data sources with URIs — represent persistent data
- Prompts: reusable workflow templates — invoked by users, not Claude
- Use the right primitive: read-only data → Resource, action/dynamic → Tool, workflow → Prompt
- All three are exposed by MCP servers and discovered by MCP clients