The local-setup lesson covered spinning up a minimal server with one tool. Building a real MCP server from scratch means understanding all three capability types the protocol defines — because picking the wrong one for a given piece of functionality is a common design mistake.
Tools vs. resources vs. prompts
Tools are functions the model decides to call on its own, based on the conversation — a calculator, a database query, a file write. Resources are read-only data the client (or user) attaches to the conversation as context — think of them as addressable documents, not actions; a resource is identified by a URI template like greeting://{name} rather than invoked with a function call. Prompts are reusable message templates that the protocol spec explicitly designs to be user-triggered — for example, exposed as a slash command in a chat UI — rather than something the model decides to invoke by itself.
Using Python's official FastMCP SDK, each primitive gets its own decorator:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("Docs Assistant")
# TOOL — the model calls this itself, with arguments
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
# RESOURCE — addressable data, not an action
@mcp.resource("greeting://{name}")
def greeting(name: str) -> str:
"""Greet someone by name."""
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run()
Under the hood, each primitive maps to its own pair of JSON-RPC methods — a client calls tools/list then tools/call for tools, and the equivalent prompts/list / prompts/get for prompts. A prompt's response isn't free text — it returns one or more structured PromptMessage objects, each with a role ("user" or "assistant") and typed content (text, image, audio, or an embedded resource), which the client then feeds into the conversation.
| Primitive | Who invokes it | Typical use |
|---|---|---|
| Tool | The model, autonomously | Actions with side effects: fetch a URL, run a calculation, write a file |
| Resource | The client or user, explicitly | Read-only context: a file's contents, a config value, a database record |
| Prompt | The user, explicitly (e.g. a slash command) | Reusable templates: "review this code," "summarize this ticket" |
Getting this split right matters for more than tidiness: because tools are the one primitive the model can trigger on its own, the spec's security guidance is strictest there — a host application must get explicit user consent before invoking any tool, since tool descriptions represent arbitrary code execution and should be treated as untrusted unless they come from a server you already trust.
Practical Challenge
Extend the server above with a @mcp.resource("file://{path}") that reads a local file's contents, and a @mcp.prompt() that generates a "summarize this file" message template referencing that resource.
Concept Check
Sources & Further Reading
- modelcontextprotocol/python-sdk (GitHub) — official Python SDK; source of the
@mcp.tool()and@mcp.resource()examples above. - MCP Specification — Prompts — the official spec section defining prompts as user-controlled, plus the
prompts/listandprompts/getJSON-RPC methods andPromptMessagecontent types.
AI