DS DevShelfHub Projects · AI tools
Cheatsheets / Model Context Protocol
Cheatsheet · AI frameworks

Model Context Protocol Cheatsheet: Tools, Resources and Sampling Reference

By DevShelfHub

JSON-RPC primitives, tools, resources, prompts, sampling, lifecycle — the spec compressed into a one-page reference.

78 items ◷ 6 min JSON-RPC Tools Resources

Start hereQuick start · 6 you’ll reach for daily

Define a tool@mcp.tool()
Define a resource@mcp.resource("uri://…")
Define a prompt@mcp.prompt()
Run a servermcp.run(transport="stdio")
Inspect locallymcp dev server.py
Registermcp install server.py

Target versions · paceVersions

Spec: protocolVersion 2025-06-18 mcp (python) ≥ 1.4 @modelcontextprotocol/sdk ≥ 1.10

The spec version is a date string negotiated during initialize. Servers should accept anything they support and reply with the highest version both sides understand. The legacy SSE transport was deprecated 2025-03-26 — new servers use streamable HTTP or stdio. Spec lives at modelcontextprotocol.io.

SDKs · CLISetup

bash
# Python SDK (FastMCP) — recommended for new servers
pip install "mcp[cli]"

# Run a server (stdio transport — Claude Desktop default)
mcp dev server.py                # interactive inspector
mcp install server.py            # register with Claude Desktop

# TypeScript SDK
npm install @modelcontextprotocol/sdk

# Pick a transport
#   stdio   - desktop apps, subprocess-style
#   http    - streamable HTTP, web hosts
#   sse     - legacy server-sent events (deprecated 2025-03)

three rolesArchitecture

HostThe user-facing app (Claude Desktop, an IDE). Owns the LLM and config. Spawns clients.
ClientOne-to-one connection to a server. Lives inside the host. Routes JSON-RPC.
ServerExposes tools / resources / prompts. Stateless across connections; long-lived per session.
Server → client primitivesTools, resources, prompts, logging. The classic “what does this server expose?”.
Client → server primitivesSampling (ask the host’s LLM), roots (filesystem scope), elicitation (ask the user).
Mental model: MCP is “LSP for tool use”. The host is the editor, servers are the language servers, primitives are completions / hovers / actions.

wire protocolJSON-RPC

MCP is JSON-RPC 2.0 over a transport. Every message is either a request (has id), a response (has id + result or error), or a notification (no id).

{"jsonrpc":"2.0","id":N,"method":"…","params":{…}}Request shape. Expects a response.
{"jsonrpc":"2.0","id":N,"result":{…}}Success response.
{"jsonrpc":"2.0","id":N,"error":{"code","message"}}Error response.
{"jsonrpc":"2.0","method":"notifications/…"}Notification. No reply expected.
-32700 / -32600 / -32601 / -32602 / -32603Parse / Invalid Request / Method Not Found / Invalid Params / Internal.
_metaOptional metadata block on any params or result. Use for progress tokens, tracing.
progressTokenEcho it in notifications/progress to stream progress.

Common methods (cheatsheet)

initializeHandshake. Negotiates protocolVersion + capabilities.
notifications/initializedClient confirms it’s ready. Required before other calls.
pingEither side. Liveness check.
tools/list · tools/callDiscover and invoke tools.
resources/list · resources/readEnumerate and fetch resources.
resources/subscribe · .../unsubscribeWatch a resource for changes.
prompts/list · prompts/getEnumerate and render prompt templates.
sampling/createMessageServer asks client to run an LLM call.
roots/listServer asks client for filesystem roots.
elicitation/createServer asks client to prompt the user for input.
logging/setLevelClient adjusts server log verbosity.
notifications/cancelledEither side cancels an in-flight request.
notifications/progressStream progress for a long-running call.

connect → exchange → closeLifecycle

1. initialize (request)Client sends version + its capabilities.
2. initialize (response)Server replies with version + its capabilities + serverInfo.
3. notifications/initializedClient confirms; the exchange phase begins.
4. tools/list · resources/list · prompts/listClient discovers what the server exposes.
5. Normal request/response trafficUntil either side closes the transport.
6. Transport closeNo formal “shutdown” method — just close.

initialize over the wire

json
// 1 — client → server
{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "initialize",
  "params": {
    "protocolVersion": "2025-06-18",
    "capabilities": {
      "roots":    { "listChanged": true },
      "sampling": {}
    },
    "clientInfo": { "name": "ExampleClient", "version": "1.0.0" }
  }
}

// 2 — server → client
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "protocolVersion": "2025-06-18",
    "capabilities": {
      "tools":     { "listChanged": true },
      "resources": { "listChanged": true, "subscribe": true },
      "prompts":   { "listChanged": true },
      "logging":   {}
    },
    "serverInfo": { "name": "ExampleServer", "version": "0.3.1" }
  }
}

// 3 — client → server (must follow before any other request)
{ "jsonrpc": "2.0", "method": "notifications/initialized" }

model-callable actionsTools

tools/listReturns {tools: [{name, description, inputSchema}]}.
tools/callParams: {name, arguments}. Result: {content[], isError?}.
inputSchemaPlain JSON Schema. Describe required + types or models hallucinate args.
content: text{type:"text", text:"…"} — default return shape.
content: image{type:"image", data:base64, mimeType}.
content: resource{type:"resource", resource:{uri,…}} — embed a known resource.
isError: trueSoft tool failure. Model sees the message; not a JSON-RPC error.
annotations.destructiveHintHint for the host UI to require user confirmation.
notifications/tools/list_changedServer tells client to re-fetch the tool list.
Use JSON-RPC errors for protocol failures (bad params, internal). Use isError: true for “the tool ran but couldn’t fulfil the request” so the model can see + recover.

app-controlled contextResources

resources/listReturns {resources: [{uri, name, mimeType, description?}]}.
resources/templates/listURI templates the server can serve (e.g. file:///{path}).
resources/readParams {uri}. Result {contents:[{uri,text|blob,mimeType}]}.
resources/subscribeWatch a resource. Server emits notifications/resources/updated.
notifications/resources/list_changedHint the client to re-list resources.
file:// / https:// / custom://URIs are free-form. Pick a scheme that makes sense.
Tools vs resources. Tools are “model-controlled” (the model decides to call them). Resources are “app-controlled” (the host attaches them to context). When in doubt, expose both: @resource for browse + attach, @tool for search.

user-controlled templatesPrompts

prompts/listReturns {prompts:[{name, description, arguments?}]}.
prompts/getParams {name, arguments}. Result {messages:[{role,content}]}.
arguments: required vs optionalDeclare which args the user must supply.
content: text / resourcePrompt messages can embed a resource by URI.
notifications/prompts/list_changedTell the client the catalogue moved.
Prompts surface in hosts as slash commands or quick actions — not in the model’s normal tool-use loop. They’re the “user picks this from a menu” primitive.

server → host LLMSampling

The reverse direction: a server asks the client’s LLM for a completion. The host MUST mediate (show the user what’s being asked, allow modification). Powers agentic patterns without the server holding API keys.

sampling/createMessageServer-initiated request to the client.
messages: [{role, content}]Conversation history to feed the model.
modelPreferences.hintsSoft hints (e.g. claude-3-5-sonnet) the client may honour.
modelPreferences.costPriority / .intelligencePriority / .speedPriority0–1 floats. Client picks a matching model.
includeContext: "thisServer" | "allServers" | "none"Whether the host should add MCP context to the call.
stopReasonendTurn, stopSequence, maxTokens.
Sampling is gated. Most hosts (incl. Claude Desktop today) don’t expose it without an explicit opt-in. Don’t design a server that requires sampling unless you control the host.

client capabilitiesRoots & elicitation

roots/listServer asks the client which filesystem roots it’s allowed to see.
notifications/roots/list_changedClient tells server the roots changed.
elicitation/createServer asks the host to prompt the user for structured input.
requestedSchemaJSON Schema describing the answer the server expects.
action: "accept" | "decline" | "cancel"User decisions surfaced to the server.

how bytes moveTransports

stdioPreferred for desktop. Server is a subprocess; host writes JSON-RPC to stdin, reads from stdout.
Streamable HTTPPreferred for remote. Single /mcp endpoint; POST request, response can be JSON or an SSE stream.
HTTP+SSELegacy Two endpoints (POST /messages, GET /sse). Deprecated 2025-03-26.
Origin / Host header checksRequired on HTTP transports. Prevents DNS-rebinding attacks.
Mcp-Session-IdHeader used by Streamable HTTP to multiplex sessions on one endpoint.
stderrLog to stderr in stdio servers — stdout is for JSON-RPC only.

build a serverSDKs · server skeletons

Python · FastMCP

python
from mcp.server.fastmcp import FastMCP
from pydantic import Field

mcp = FastMCP("Weather")

@mcp.tool()
def get_forecast(
    city: str = Field(..., description="City name, English"),
    units: str = Field("metric", description="metric | imperial"),
) -> dict:
    """Return the next-48h forecast for the given city."""
    return fetch_forecast(city, units)

@mcp.resource("weather://stations")
def list_stations() -> str:
    """Static directory of weather stations as Markdown."""
    return load_stations_markdown()

@mcp.prompt()
def daily_briefing(city: str) -> str:
    """Prompt template — caller substitutes {city}."""
    return f"Give me a 3-line weather briefing for {city}."

if __name__ == "__main__":
    mcp.run()  # stdio by default; mcp.run(transport="http") for HTTP

TypeScript

javascript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({ name: "weather", version: "0.1.0" });

server.tool(
  "get_forecast",
  "Next-48h forecast for a city.",
  {
    city:  z.string().describe("City name, English"),
    units: z.enum(["metric", "imperial"]).default("metric"),
  },
  async ({ city, units }) => ({
    content: [{ type: "text", text: await fetchForecast(city, units) }],
  }),
);

server.resource("stations", "weather://stations", async () => ({
  contents: [{ uri: "weather://stations", text: await loadStations() }],
}));

await server.connect(new StdioServerTransport());

register a serverClaude Desktop config

json
// claude_desktop_config.json
// macOS:   ~/Library/Application Support/Claude/claude_desktop_config.json
// Windows: %APPDATA%\Claude\claude_desktop_config.json
{
  "mcpServers": {
    "weather": {
      "command": "uv",
      "args": ["--directory", "/Users/me/code/weather", "run", "server.py"],
      "env": { "WEATHER_API_KEY": "..." }
    },
    "filesystem": {
      "command": "npx",
      "args": [
        "-y", "@modelcontextprotocol/server-filesystem",
        "/Users/me/Documents"
      ]
    }
  }
}
command + argsHow Claude Desktop launches the server.
envExtra env vars passed to the subprocess.
Restart Claude after editsConfig is read once at startup.
~/Library/Logs/Claude/mcp-server-*.logWhere to look when a server “disappears” from the UI.

server → tool → resource → promptEnd-to-end · FastMCP server

A complete weather server with one tool, one resource, one prompt. Wire it into Claude Desktop with the JSON above and the model can call it.

python
from mcp.server.fastmcp import FastMCP
from pydantic import Field

mcp = FastMCP("Weather")

@mcp.tool()
def get_forecast(
    city: str = Field(..., description="City name, English"),
    units: str = Field("metric", description="metric | imperial"),
) -> dict:
    """Return the next-48h forecast for the given city."""
    return fetch_forecast(city, units)

@mcp.resource("weather://stations")
def list_stations() -> str:
    """Static directory of weather stations as Markdown."""
    return load_stations_markdown()

@mcp.prompt()
def daily_briefing(city: str) -> str:
    """Prompt template — caller substitutes {city}."""
    return f"Give me a 3-line weather briefing for {city}."

if __name__ == "__main__":
    mcp.run()  # stdio by default; mcp.run(transport="http") for HTTP

Best practiceGood to know

Describe inputs with JSON Schema, including description. The model only sees inputSchema; a tool with bare types ({"type":"string"}) gets hallucinated arguments. Always include description, enum, and required.
Use mcp dev early. The inspector lets you fire tools/call by hand and see exact JSON-RPC. Catches schema bugs faster than driving through the host.
Keep servers stateless across connections. Connection state is fine; persist nothing else there. If the host restarts you, a fresh subprocess must behave identically — store durable state on disk or a DB.

Common trapsWatch out for

Never write to stdout in a stdio server (except JSON-RPC). A stray print() in your tool corrupts the transport. The host disconnects with a parse error. Log to stderr or a file.
Tools must declare required params. Missing required in the schema is the #1 reason models call your tool with empty args — JSON Schema treats every field as optional by default.
Don’t ship the legacy SSE transport for new servers. It’s deprecated and many newer clients don’t implement it. Use Streamable HTTP for remote, stdio for local.

Go deeperSee also

Model Context Protocol FAQ

What is the Model Context Protocol (MCP)?

MCP is an open protocol (published by Anthropic) that standardizes how AI applications connect to external tools, data sources, and context providers. It defines a JSON-RPC 2.0 message layer with primitives for tools (executable functions), resources (readable data), prompts (reusable templates), and sampling (LLM inference requests). Any MCP client (Claude Desktop, Cursor, custom apps) can talk to any MCP server.

What is the difference between MCP tools and resources?

Tools are server-side functions the LLM can invoke to take actions or query live data — they have a name, description, and JSON Schema for inputs. Resources are read-only data sources (files, database rows, API responses) the host can fetch by URI and inject as context. Tools are for doing; resources are for reading.

What transports does MCP support?

MCP ships with two standard transports: stdio (default for local tools — the client spawns the server as a subprocess and communicates over stdin/stdout) and SSE over HTTP (for remote or multi-client servers). The Python and TypeScript SDKs abstract the transport, so you define tools with decorators and call mcp.run(transport='stdio') or mcp.run(transport='sse').

What is sampling in MCP?

Sampling lets an MCP server request LLM inference from the connected client (e.g., Claude Desktop). Instead of calling an LLM API directly from the server, the server sends a sampling/createMessage request and the client routes it through its own model. This keeps API keys on the client side and lets servers leverage the host's model without needing separate credentials.

How do I test an MCP server locally?

Run mcp dev server.py to launch the MCP Inspector — a browser UI that lets you call your tools, read resources, and inspect JSON-RPC traffic in real time without connecting a full LLM client. Use mcp install server.py to register the server with Claude Desktop so it appears in the menu for end-to-end testing.

Is MCP open source and free to use?

Yes. The MCP specification and official SDKs (Python, TypeScript) are MIT-licensed and free. The spec is maintained by Anthropic but designed to be implemented by any vendor or developer. There are no licencing fees to build or deploy MCP servers.