An exhaustive, production-grade masterclass from absolute zero to staff-level Model Context Protocol (MCP) systems architecture.
Master the open standard connecting AI models to local databases, enterprise APIs, internal tools, secure sandboxes, and autonomous multi-agent runtimes.
Getting Started β’ Transports Deep Dive β’ Server Engineering β’ Client & Host Integration β’ Enterprise Security β’ Observability & Sampling β’ Staff Handbook
flowchart LR
S1["Stage 1: Protocol Foundations"] --> S2["Stage 2: stdio & SSE Transports"]
S2 --> S3["Stage 3: Server Primitives (Tools, Resources, Prompts)"]
S3 --> S4["Stage 4: Client & Host Orchestration"]
S4 --> S5["Stage 5: Enterprise Security & Sandboxing"]
S5 --> S6["Stage 6: Observability, Sampling & Evals"]
S6 --> S7["Stage 7: Staff Architect Handbook & Cheatsheet"]
| Stage | Domain Focus | Core Concepts & Production Outcomes |
|---|---|---|
| Stage 1 | Protocol Foundations & Handshake | Solving the |
| Stage 2 | Transports & Connection Lifecycles |
stdio sub-process pipes vs SSE HTTP streaming, backpressure, session headers, resilient reconnection loops. |
| Stage 3 | Server Engineering: Primitives | Building production servers via TypeScript SDK & Python FastMCP. Dynamic Tools (Zod/Pydantic), URI Resources, and Prompts. |
| Stage 4 | Client & Host Orchestration | Writing custom MCP clients, Claude Desktop & IDE integration, multi-server routing, LangGraph & CrewAI interop. |
| Stage 5 | Enterprise Security & Governance | Path sandboxing, prompt injection mitigation, mTLS, Bearer auth over SSE, zero-trust human-in-the-loop gates. |
| Stage 6 | Observability, Sampling & Evals | Reverse LLM sampling (sampling/createMessage), structured logging, OpenTelemetry tracing, automated mock testing. |
| Stage 7 | Staff Architect Interview Handbook | 40+ staff-level scenario Q&As, production failure modes, architectural blueprints, and enterprise cheatsheet. |
- 1. Stage 1: Absolute Beginner Foundations & Protocol Architecture
- 2. Stage 2: Transports Deep Dive & Connection Lifecycles
- 3. Stage 3: Server Engineering: Resources, Prompts & Dynamic Tools
- 4. Stage 4: Client & Host Orchestration
- 5. Stage 5: Enterprise Security, Sandboxing & Governance
- 6. Stage 6: Observability, Sampling & Evals
- 7. Stage 7: Staff MCP Architect Interview Handbook & Cheatsheet
- π€ Contributing
- π License
Before the Model Context Protocol (MCP), connecting
BEFORE MCP (M x N Fragile Custom Code):
[ Claude Desktop ] ----> (Custom Postgres Plugin) ----> [ PostgreSQL DB ]
[ LangChain App ] ----> (Custom LangChain Tool) ----> [ GitHub API ]
[ Cursor IDE ] ----> (Custom IDE Tooling) ----> [ Local Files ]
AFTER MCP (Standardized USB-C for AI Context):
[ Any AI Host / Client ] === (Standard JSON-RPC over stdio / SSE) ===> [ Any MCP Server ]
MCP acts as the open standard protocol (analogous to the Language Server Protocol / LSP for IDEs, or USB-C for hardware) allowing any LLM runtime to instantly query, invoke, and inspect tools, resources, and prompts across any server.
The MCP architecture separates concerns across three strict logical entities:
graph TD
subgraph Host ["1. Host Environment (e.g. Claude Desktop, IDE, Agent)"]
UI["User Interface / Chat Loop"]
Orchestrator["Model Orchestrator & LLM"]
subgraph Client ["2. MCP Client (1..N Instances)"]
ClientA["Client A (stdio)"]
ClientB["Client B (SSE)"]
end
end
subgraph ServerA ["3. MCP Server A (Local)"]
ToolsA["Tools: runQuery, listTables"]
ResourcesA["Resources: postgres://schema"]
end
subgraph ServerB ["3. MCP Server B (Remote)"]
ToolsB["Tools: createIssue, mergePR"]
PromptsB["Prompts: review_pull_request"]
end
UI --> Orchestrator
Orchestrator --> ClientA
Orchestrator --> ClientB
ClientA <-- stdio pipe --> ServerA
ClientB <-- HTTP / SSE --> ServerB
- Host: The orchestrator application (e.g., Claude Desktop, VS Code, Antigravity IDE, custom agent). It owns the LLM connection, security permissions, user interface, and lifecycle.
- Client: Protocol client initialized inside the host. Maintains a 1:1 connection with an individual MCP server.
- Server: Lightweight programs providing structured capabilities:
- Tools: Executable functions that cause side effects or retrieve live data.
- Resources: Read-only binary or textual data accessed via URI schemes (
file://,postgres://). - Prompts: Pre-configured, parameterized prompt templates provided to users.
Every MCP message is encoded as strict JSON-RPC 2.0:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "execute_query",
"arguments": {
"sql": "SELECT id, email FROM users WHERE active = true LIMIT 5;"
}
}
}Standard responses return structured content arrays:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "[{\"id\": 101, \"email\": \"alice@corp.internal\"}]"
}
],
"isError": false
}
}When an MCP client initiates a connection, it performs an explicit bidirectional capability exchange:
sequenceDiagram
autonumber
participant C as MCP Client
participant S as MCP Server
C->>S: initialize request (clientInfo, capabilities: roots, sampling)
S-->>C: initialize response (serverInfo, capabilities: tools, resources, prompts)
C->>S: initialized notification (handshake confirmed)
Note over C,S: Normal protocol operations can now occur
C->>S: tools/list
S-->>C: [{ name: "execute_query", inputSchema: {...} }]
The stdio transport is the primary mechanism for local tools. The Host process spawns the MCP server as a child process and communicates via stdin and stdout.
Warning
In stdio mode, the server process MUST NEVER output regular logs or debugging text to stdout (console.log in Node.js or print() in Python). Doing so breaks JSON-RPC message framing. All logs must be written strictly to stderr (console.error or sys.stderr.write).
// stdio message framing rule:
// Messages are separated by newlines (\n). Each line MUST be a complete JSON-RPC 2.0 payload.For distributed, containerized, or remote team setups, MCP uses an HTTP + SSE transport:
GET /sse: The client connects and establishes a long-lived unidirectional stream for server-to-client events.POST /message?sessionId=<id>: The client sends client-to-server JSON-RPC requests via standard HTTP POST.
sequenceDiagram
participant C as MCP Client
participant S as Remote MCP Server (SSE)
C->>S: GET /sse (Accept: text/event-stream)
S-->>C: 200 OK + event: endpoint\ndata: /message?sessionId=sess_982341
Note over C,S: Connection is open
C->>S: POST /message?sessionId=sess_982341 (JSON-RPC body)
S-->>C: 202 Accepted
S-->>C: event: message\ndata: {"jsonrpc":"2.0","id":1,"result":{...}}
| JSON-RPC Code | Constant | Meaning in MCP |
|---|---|---|
-32700 |
PARSE_ERROR |
Server received unparseable JSON string on transport stream. |
-32600 |
INVALID_REQUEST |
Payload is not a valid JSON-RPC 2.0 object. |
-32601 |
METHOD_NOT_FOUND |
Method does not exist (e.g. server does not support prompts/list). |
-32602 |
INVALID_PARAMS |
Tool invocation arguments failed schema validation (Zod/Pydantic). |
-32603 |
INTERNAL_ERROR |
Uncaught runtime exception during tool or resource execution. |
Production-grade implementation of an enterprise SQLite query server using @modelcontextprotocol/sdk:
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
ErrorCode,
McpError
} from "@modelcontextprotocol/sdk/types.js";
import { z } from "zod";
const server = new Server(
{
name: "enterprise-database-server",
version: "1.0.0"
},
{
capabilities: {
tools: {},
resources: {}
}
}
);
// 1. Tool Definitions
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: "execute_readonly_query",
description: "Executes a safe, read-only SQL SELECT query against the analytics database.",
inputSchema: {
type: "object",
properties: {
sql: {
type: "string",
description: "The SQL statement to execute. Must begin with SELECT or EXPLAIN."
},
limit: {
type: "number",
description: "Maximum rows to return (default 50, maximum 500).",
default: 50
}
},
required: ["sql"]
}
}
]
};
});
// 2. Tool Execution Handler
const QueryArgsSchema = z.object({
sql: z.string().refine(
(q) => /^\s*(SELECT|EXPLAIN)\s+/i.test(q),
{ message: "Only SELECT and EXPLAIN queries are permitted in read-only mode." }
),
limit: z.number().min(1).max(500).default(50)
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name !== "execute_readonly_query") {
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
}
const parseResult = QueryArgsSchema.safeParse(request.params.arguments);
if (!parseResult.success) {
throw new McpError(
ErrorCode.InvalidParams,
`Schema Validation Failed: ${parseResult.error.issues.map((i) => i.message).join(", ")}`
);
}
const { sql, limit } = parseResult.data;
try {
// Simulated query execution
const results = [
{ user_id: "usr_1", status: "active", revenue_cents: 9900 },
{ user_id: "usr_2", status: "churned", revenue_cents: 0 }
].slice(0, limit);
return {
content: [
{
type: "text",
text: JSON.stringify(results, null, 2)
}
]
};
} catch (err: any) {
return {
isError: true,
content: [
{
type: "text",
text: `Database Error: ${err.message}`
}
]
};
}
});
// Start via stdio transport
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("[INFO] Enterprise Database MCP Server running on stdio.");
}
main().catch((err) => {
console.error("[FATAL] Startup failed:", err);
process.exit(1);
});The modern Python approach utilizes FastMCP from the official Python SDK:
"""
Enterprise Filesystem & Git Inspector MCP Server (Python FastMCP)
"""
import os
import subprocess
from mcp.server.fastmcp import FastMCP
from pydantic import BaseModel, Field
# Initialize server
mcp = FastMCP("git-code-inspector")
class GitLogArgs(BaseModel):
max_commits: int = Field(default=5, ge=1, le=50, description="Max commits to return")
branch: str = Field(default="main", description="Target Git branch")
@mcp.tool()
def get_git_log(max_commits: int = 5, branch: str = "main") -> str:
"""Returns the latest structured commit history for a local git repository."""
try:
result = subprocess.run(
["git", "log", f"-n{max_commits}", "--oneline", branch],
capture_output=True,
text=True,
check=True
)
return result.stdout.strip()
except subprocess.CalledProcessError as e:
return f"Git CLI Error: {e.stderr.strip()}"
@mcp.resource("config://app-settings")
def get_application_settings() -> str:
"""Returns runtime application configuration settings as a static resource."""
return '{"environment": "production", "rate_limit_rpm": 120, "telemetry_enabled": true}'
@mcp.prompt()
def code_review_prompt(pr_number: int) -> str:
"""Generates an architectural review template for an open PR."""
return f"""You are a principal engineer conducting a code review for PR #{pr_number}.
1. Check for security vulnerabilities (injection, OWASP).
2. Validate async/await performance and memory leaks.
3. Suggest typed unit test assertions.
"""
if __name__ == "__main__":
mcp.run()An MCP client discovers tools from connected servers, maps them to OpenAI/Anthropic tool schemas, and dispatches calls:
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
async function runClient() {
const transport = new StdioClientTransport({
command: "node",
args: ["./dist/server.js"]
});
const client = new Client(
{ name: "custom-agent-orchestrator", version: "1.0.0" },
{ capabilities: {} }
);
await client.connect(transport);
console.log("Connected to MCP Server!");
// Discover available tools
const { tools } = await client.listTools();
console.log(`Discovered ${tools.length} tools:`, tools.map((t) => t.name));
// Invoke a tool
const result = await client.callTool({
name: "execute_readonly_query",
arguments: {
sql: "SELECT * FROM users",
limit: 10
}
});
console.log("Tool execution output:", result.content);
await client.close();
}
runClient().catch(console.error);MCP servers are wired into host applications using standard JSON configurations:
{
"mcpServers": {
"enterprise-db": {
"command": "node",
"args": ["C:/Users/manth/servers/db-mcp/dist/index.js"],
"env": {
"DATABASE_URL": "postgresql://readonly:secret@localhost:5432/analytics"
}
},
"github-tools": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_xxxxxxxxxxxx"
}
}
}
}Integrating LLMs directly with operational tooling introduces distinct vulnerabilities:
flowchart TD
Attack1["1. Indirect Prompt Injection"] -->|Hidden in web page or DB row| LLM["LLM Host Engine"]
LLM -->|Manipulated into invoking dangerous tool| MCP["MCP Tool: delete_database"]
Attack2["2. Directory Traversal"] -->|Path: ../../../etc/shadow| Resource["MCP Resource Reader"]
Attack3["3. SSRF / Network Pivot"] -->|Target: http://169.254.169.254| Fetch["MCP Tool: fetch_url"]
Always enforce canonical path resolution and strict prefix validation:
import path from "node:path";
export function resolveSafePath(userRelativePath: string, allowedRoot: string): string {
// Resolve absolute path
const targetPath = path.resolve(allowedRoot, userRelativePath);
// Enforce boundary jail
if (!targetPath.startsWith(path.resolve(allowedRoot) + path.sep)) {
throw new Error(`Security Violation: Path traversal outside sandbox boundary (${userRelativePath})`);
}
return targetPath;
}MCP enables an advanced capability: Servers can request LLM completions back from the Host:
// Server requests an LLM completion from the host client
const response = await server.createMessage({
messages: [
{
role: "user",
content: {
type: "text",
text: "Summarize this SQL query execution plan and identify index misses: ..."
}
}
],
maxTokens: 500
});| Command / Tool | Purpose | Usage / Example |
|---|---|---|
mcp inspector |
Visual debugging UI for local MCP servers | npx @modelcontextprotocol/inspector node dist/index.js |
stdio Transport |
Local sub-process pipe communication | Best for CLI tools, local databases, desktop applications |
SSE Transport |
Remote HTTP/2 streaming communication | Best for Kubernetes microservices, cloud tools, SaaS APIs |
tools/list |
Queries server for callable function schemas | Returns JSON Schema objects for LLM function calling |
resources/list |
Queries server for readable data sources | Returns URI schemes (postgres://, file://, s3://) |
prompts/list |
Queries server for curated prompt templates | Reusable system prompt configurations |
Q1: What fundamental problem does the Model Context Protocol solve that OpenAI Function Calling or LangChain Tools did not?
Answer: Function Calling and LangChain Tools solved tool formatting for a specific library or model vendor, creating an
$M \times N$ matrix where every tool had to be rewritten for every framework. MCP creates a universal protocol layer over JSON-RPC 2.0 with capability negotiation, standard transports (stdio/SSE), resources, and prompts, allowing any tool to work with any client without code changes.
Answer: In
stdiotransport, the standard input and standard output streams form the bidirectional JSON-RPC 2.0 transport channel. If a server prints unformatted logs tostdout, the client's JSON parser encounters non-JSON data and throws a framing/parse error (-32700), terminating the connection. All application logs must be sent tostderr.
Answer: During the
initializehandshake, the client sends its supported protocol version and client capabilities (such as whether it supports sampling or root changes). The server replies with its protocol version and capabilities (e.g. tools, resources, prompts). If versions are incompatible, connection negotiation aborts cleanly before any payload transmission.
Answer: Implement a multi-layered defense: (1) Schema enforcement restricting input lengths and parameter formats; (2) Demoting resource content to untrusted data blocks in prompt templates; (3) Requiring explicit Human-In-The-Loop confirmation tokens before executing irreversible tool actions (
write,delete,execute).
(36 additional Staff-level architecture scenarios detailed inside repository documentation...)
Contributions are welcomed! Fork the repository, create a feature branch, and submit a PR conforming to the unified curriculum architecture.
Released under the MIT License.