MCP (Model Context Protocol)
Model Context Protocol reference: architecture, primitives, server config, building servers, tools/resources/prompts, transports, auth, and debugging.
Protocol Overview & Architecture
What MCP is, how clients and servers relate, and the two transport types.
MCP โ Model Context Protocol
Open standard for connecting AI applications to external tools and data.
Think of it as "USB-C for AI" โ one interface, many peripherals.
ROLES
MCP Host AI application that embeds an MCP client
(Claude Desktop, Claude Code, VS Code, Cursor โฆ)
MCP Client Component inside the host managing one server connection
MCP Server Process exposing tools / resources / prompts via MCP
Host โโโโโ MCP Client โโโโโ MCP Server (filesystem)
โโโโโ MCP Client โโโโโ MCP Server (GitHub)
โโโโโ MCP Client โโโโโ MCP Server (Postgres)
One host can connect to many servers simultaneously.
Each connection is a dedicated client instance.
WIRE PROTOCOL
JSON-RPC 2.0 over the chosen transport.
Messages: Request โ Response | Notification (fire-and-forget)
TRANSPORT OPTIONS
stdio stdin / stdout โ local only, zero network overhead
Streamable HTTP POST for clientโserver, optional SSE for streaming
supports standard HTTP auth, multi-client, remoteMCP Primitives
The five building blocks servers and clients expose: Tools, Resources, Prompts, Sampling, and Roots.
PRIMITIVE WHO DECIDES TO USE IT WHAT IT IS
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Tools The model (AI) Executable functions the LLM can call
e.g. searchFlights(), queryDatabase()
Protocol: tools/list tools/call
Resources The application Read-only data sources for context
e.g. file contents, DB schemas, API docs
Direct (fixed URI) or Template (parameterised)
Protocol: resources/list resources/read
resources/templates/list
resources/subscribe
Prompts The user Reusable instruction templates with parameters
e.g. "Plan a vacation" with destination/days args
Protocol: prompts/list prompts/get
CLIENT-SIDE PRIMITIVES (what servers can request from the host)
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Sampling Server asks client to run an LLM completion
sampling/complete โ lets servers leverage the host model
Roots Client tells server which filesystem roots it may access
Servers should respect roots for path-based operationsServer Configuration
JSON config format for Claude Desktop, Claude Code, and other MCP clients.
CLIENT CONFIG FILE LOCATION
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Claude Desktop ~/Library/Application Support/Claude/claude_desktop_config.json (macOS)
%APPDATA%\Claude\claude_desktop_config.json (Windows)
Claude Code ~/.claude/settings.json (mcpServers key)
or .claude/settings.json (project-level)
or --mcp-config <path> (CLI flag per session)
VS Code / Cursor .vscode/mcp.json or workspace settings{
"mcpServers": {
// Local stdio server โ runs a child process
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/home/user/docs"],
"env": {}
},
// Python server via uv (recommended for Python projects)
"weather": {
"command": "uv",
"args": ["--directory", "/absolute/path/to/weather-server", "run", "weather.py"],
"env": {
"WEATHER_API_KEY": "sk_live_abc123"
}
},
// Remote HTTP/SSE server
"my-api": {
"type": "sse",
"url": "https://mcp.example.com/sse",
"headers": {
"Authorization": "Bearer ${MY_TOKEN}"
}
},
// Streamable HTTP (newer standard)
"remote-tools": {
"type": "http",
"url": "https://mcp.example.com/mcp",
"headers": {
"X-API-Key": "${REMOTE_API_KEY}"
}
}
}
}
// IMPORTANT: Always use absolute paths โ relative paths often fail
// because the client's working directory is undefined at launch.Building an MCP Server โ TypeScript
Creating an MCP server with the official TypeScript SDK.
# Initialise project
mkdir my-mcp-server && cd my-mcp-server
npm init -y
# Install SDK
npm install @modelcontextprotocol/sdk zod
# TypeScript tooling
npm install -D typescript @types/node tsx
# package.json โ add type & bin
# "type": "module"
# "bin": { "my-server": "dist/index.js" }// src/index.ts
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: "my-server",
version: "1.0.0",
});
// Register a tool
server.tool(
"add",
"Add two numbers",
{ a: z.number(), b: z.number() },
async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
})
);
// Register a resource
server.resource(
"config",
"config://app",
async (uri) => ({
contents: [{ uri: uri.href, text: "port=3000
debug=false" }],
})
);
// Register a prompt
server.prompt(
"review-code",
{ code: z.string() },
({ code }) => ({
messages: [{
role: "user",
content: { type: "text", text: `Review this code:\n${code}` },
}],
})
);
// Start
const transport = new StdioServerTransport();
await server.connect(transport);
// NEVER log to stdout in stdio servers โ it corrupts JSON-RPC
// Use console.error() or process.stderr.write() instead// src/http-server.ts
import express from "express";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
const app = express();
app.use(express.json());
const server = new McpServer({ name: "remote-server", version: "1.0.0" });
// ... register tools/resources/prompts ...
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
await server.connect(transport);
app.post("/mcp", (req, res) => transport.handleRequest(req, res));
app.get("/mcp", (req, res) => transport.handleRequest(req, res)); // SSE
app.delete("/mcp", (req, res) => transport.handleRequest(req, res));
app.listen(3000);Building an MCP Server โ Python
Creating an MCP server with the official Python SDK using FastMCP.
# Using uv (recommended) uv init my-mcp-server cd my-mcp-server uv add "mcp[cli]" # Or pip pip install "mcp[cli]" # Scaffold a new server interactively mcp new my-server # Run during development mcp dev server.py # Install into Claude Desktop mcp install server.py
# server.py
from mcp.server.fastmcp import FastMCP
from mcp.types import Resource, TextContent
mcp = FastMCP("weather")
# โโ TOOL โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
@mcp.tool()
async def get_forecast(city: str, days: int = 3) -> str:
"""Get a weather forecast for a city."""
# implementation โฆ
return f"Forecast for {city}: sunny for {days} days"
# โโ RESOURCE โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
@mcp.resource("config://settings")
def get_settings() -> str:
"""Application settings."""
return "theme=dark\nlocale=en"
# Dynamic resource with URI template
@mcp.resource("weather://{city}/current")
def current_weather(city: str) -> str:
"""Current conditions for a city."""
return f"Current weather in {city}: 22ยฐC, partly cloudy"
# โโ PROMPT โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
@mcp.prompt()
def weather_report(city: str) -> str:
"""Generate a weather briefing prompt."""
return f"Write a friendly weather report for {city}."
# โโ RUN โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
if __name__ == "__main__":
mcp.run() # defaults to stdio
# mcp.run(transport="streamable-http") # for remoteTool Definition Deep-Dive
How to define tools with JSON Schema input validation and proper return types.
// Using low-level server API for full schema control
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [{
name: "search_flights",
title: "Flight Search", // human-readable display name
description: "Search available flights between two cities.",
inputSchema: {
type: "object",
properties: {
origin: { type: "string", description: "IATA departure code, e.g. JFK" },
destination: { type: "string", description: "IATA arrival code, e.g. LHR" },
date: { type: "string", format: "date", description: "ISO 8601 date" },
class: { type: "string", enum: ["economy","business","first"],
default: "economy" },
max_price: { type: "number", description: "Max price in USD" },
},
required: ["origin", "destination", "date"],
},
}],
}));
server.setRequestHandler(CallToolRequestSchema, async (req) => {
if (req.params.name === "search_flights") {
const { origin, destination, date, class: cls = "economy" } = req.params.arguments;
const results = await flightApi.search(origin, destination, date, cls);
return {
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
isError: false,
};
}
});
// RETURN TYPES
// content is an array of:
// { type: "text", text: string }
// { type: "image", data: base64, mimeType: "image/png" }
// { type: "resource", resource: { uri, text | blob } }Resources & Prompts
Defining static and dynamic resources, and reusable prompt templates.
// Static resource (fixed URI)
server.resource(
"app-config",
"config://app",
{ mimeType: "application/json", description: "App configuration" },
async () => ({
contents: [{
uri: "config://app",
mimeType: "application/json",
text: JSON.stringify({ port: 3000, debug: false }),
}],
})
);
// Dynamic resource (URI template)
server.resource(
"user-profile",
new ResourceTemplate("users://{userId}/profile", { list: undefined }),
{ mimeType: "application/json", description: "User profile by ID" },
async (uri, { userId }) => ({
contents: [{
uri: uri.href,
mimeType: "application/json",
text: JSON.stringify(await db.getUser(userId)),
}],
})
);
// PROTOCOL METHODS
// resources/list โ list direct resources
// resources/templates/list โ list URI templates
// resources/read { uri } โ fetch contents
// resources/subscribe { uri } โ stream updates
// notifications/resources/updated โ server-push on changeserver.prompt(
"code-review",
"Review code for issues and improvements",
{
code: z.string().describe("Source code to review"),
language: z.string().optional().describe("Programming language"),
focus: z.enum(["security","performance","readability"]).optional(),
},
({ code, language = "unknown", focus = "all" }) => ({
messages: [
{
role: "user",
content: {
type: "text",
text: `Review the following ${language} code focusing on ${focus}:\n\n${code}`,
},
},
],
})
);
// PROTOCOL METHODS
// prompts/list โ discover available prompts
// prompts/get { name, args} โ get rendered prompt messagesTransport Types
stdio vs Streamable HTTP โ when to use each and how they work.
FEATURE STDIO STREAMABLE HTTP โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ Where Local machine only Local or remote Clients Single (1:1) Multiple (1:N) Network overhead None Minimal (HTTP/2) Auth Env vars / process perms Bearer token, API key, headers Setup complexity Low Medium Streaming N/A (synchronous) Server-Sent Events (SSE) Session state Process lifetime Mcp-Session-Id header Use for Local tools (fs, git, db) Cloud services, shared servers Logging stderr ONLY stdout or SSE notifications STDIO GOLDEN RULE Never write to stdout inside a stdio server. stdout is the JSON-RPC channel โ any stray bytes corrupt the protocol. Use: console.error(), process.stderr.write(), logging to a file. HTTP SESSION LIFECYCLE 1. Client POST /mcp with Initialize request 2. Server returns Mcp-Session-Id header 3. Client includes Mcp-Session-Id on all subsequent requests 4. Client DELETE /mcp to terminate session
// โโ STDIO โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const transport = new StdioServerTransport();
await server.connect(transport);
// โโ STREAMABLE HTTP โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import { randomUUID } from "crypto";
const transport = new StreamableHTTPServerTransport({
sessionIdGenerator: () => randomUUID(), // stateful sessions
// sessionIdGenerator: undefined, // stateless (simpler)
onsessioninitialized: (sessionId) => {
sessions.set(sessionId, transport);
},
});
await server.connect(transport);
app.post("/mcp", (req, res) => transport.handleRequest(req, res, req.body));
app.get("/mcp", (req, res) => transport.handleRequest(req, res)); // SSE
app.delete("/mcp", (req, res) => transport.handleRequest(req, res));
// โโ PYTHON FastMCP โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
mcp.run() # stdio (default)
mcp.run(transport="streamable-http") # HTTPAuthentication & Security
How auth works with MCP servers and key security considerations.
STDIO SERVERS
Auth is implicit โ the server inherits the process owner's permissions.
API keys go in env vars in the config file:
"env": { "GITHUB_TOKEN": "ghp_abc123" }
Access them in the server: process.env.GITHUB_TOKEN
HTTP SERVERS โ OPTIONS
API Key (simplest)
Client sends header: Authorization: Bearer <token>
Server validates against a known key or secret.
OAuth 2.0 (third-party APIs)
MCP spec uses OAuth 2.0 with dynamic client registration.
Flow: client registers โ user consents โ server gets token.
Server validates the token was issued TO IT, not just forwarded.
mTLS (high-security internal services)
Client and server both present certificates.
SECURITY RULES
โ Never hardcode secrets in source code
โ Never blindly forward client tokens to third-party APIs
("token passthrough" โ confused deputy attack)
โ Validate tokens were issued for your server's audience
โ Use HTTPS for all remote HTTP transport in production
โ Implement per-client OAuth consent, not shared static IDs
โ Apply minimal scope โ request only what each operation needs// Express middleware to validate Bearer token
app.use("/mcp", (req, res, next) => {
const auth = req.headers.authorization;
if (!auth?.startsWith("Bearer ")) {
return res.status(401).json({ error: "Missing token" });
}
const token = auth.slice(7);
if (!isValidToken(token)) { // your validation logic
return res.status(403).json({ error: "Invalid token" });
}
next();
});
// Block SSRF โ private IP ranges in fetch-based servers
const BLOCKED = [
/^127\./, /^10\./, /^192\.168\./, /^172\.(1[6-9]|2\d|3[01])\./,
/^169\.254\./, // link-local / AWS metadata
];
function isSafeUrl(url: string): boolean {
const { hostname } = new URL(url);
return !BLOCKED.some(re => re.test(hostname));
}Notable MCP Servers
Official and community MCP servers worth knowing about.
SERVER PACKAGE WHAT IT DOES โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ filesystem @modelcontextprotocol/server-filesystem Read/write/search local files git @modelcontextprotocol/server-git Git log, diff, status, clone github @modelcontextprotocol/server-github Issues, PRs, repos, code search gitlab @modelcontextprotocol/server-gitlab GitLab API equivalents postgres @modelcontextprotocol/server-postgres SQL queries, schema inspection sqlite @modelcontextprotocol/server-sqlite SQLite queries memory @modelcontextprotocol/server-memory Persistent knowledge graph fetch @modelcontextprotocol/server-fetch Web content retrieval + HTMLโMD brave-search @modelcontextprotocol/server-brave-search Web search via Brave API puppeteer @modelcontextprotocol/server-puppeteer Browser automation, screenshots time @modelcontextprotocol/server-time Time, timezone conversions slack @modelcontextprotocol/server-slack Slack channels, messages INSTALL ANY VIA npx (no install needed) "command": "npx", "args": ["-y", "@modelcontextprotocol/server-filesystem", "/path"]
SERVER SOURCE WHAT IT DOES โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ AWS KB Retrieval awslabs/mcp Query AWS Bedrock Knowledge Bases Cloudflare cloudflare/mcp-server-cf Workers, KV, R2, D1 management Stripe stripe/agent-toolkit Payments, customers, subscriptions Sentry getsentry/sentry-mcp Issues, events, performance data Linear linear-mcp Issues, projects, teams Jira atlassian/mcp-atlassian Jira issues, Confluence pages Grafana grafana-mcp Dashboards, alerts, datasources Docker mcp-docker Container management Kubernetes mcp-kubernetes Cluster resources, logs, pods Redis redis/mcp-server-redis Get/set/query Redis Qdrant qdrant-mcp Vector search and storage Playwright mcp-playwright Browser automation (alt to Puppeteer) DISCOVERY https://github.com/modelcontextprotocol/servers โ official list https://mcp.so โ community directory
Debugging & Testing
MCP Inspector, log locations, and common errors.
# Launch Inspector against a local stdio server npx @modelcontextprotocol/inspector npx @modelcontextprotocol/server-filesystem /tmp # Custom port npx @modelcontextprotocol/inspector --port 5174 node dist/index.js # Against an HTTP server npx @modelcontextprotocol/inspector --transport http --url http://localhost:3000/mcp # Python server via uv npx @modelcontextprotocol/inspector uv run server.py # Inspector features (runs at http://localhost:5173 by default) # โข List and call tools interactively # โข Browse resources and templates # โข Invoke prompts with arguments # โข View raw JSON-RPC messages # โข Inspect server capabilities and version
# Claude Desktop log files
tail -f ~/Library/Logs/Claude/mcp*.log # macOS
# %APPDATA%\Claude\logs\mcp*.log # Windows
# Claude Code (stdio server stderr goes here)
journalctl -f # or check terminal output
# Python FastMCP dev mode โ shows all messages
mcp dev server.py
COMMON ERRORS & FIXES
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
"spawn ENOENT" command not found โ use absolute path
or ensure executable is on PATH
"JSON parse error" server wrote to stdout (stdio transport)
Move all logging to stderr
"Protocol version mismatch" SDK version mismatch between client/server
Update both to same MCP spec version
"ECONNREFUSED" HTTP server not running on expected port
Check port and that server started
"Timeout" Tool handler too slow
Add timeouts; return partial results early
Config not loading Restart the MCP host after editing config
Check JSON syntax (no trailing commas)