🔌 Model Context Protocol (MCP)
The Model Context Protocol (MCP) is an open standard spearheaded by Anthropic that defines how applications (hosts/clients) provide structured context, prompts, and tools to Large Language Models. Conceptually analogous to a “USB-C port” for AI, MCP decouples the integration of data sources and functions from the core orchestration layers, enabling LLM-agnostic tool execution across models and platforms.
MCP Ecosystem Overview
The Model Context Protocol establishes a clear boundary between host applications, embedded clients, and external servers that expose local or remote resources.
🏗️ 1. Protocol Architecture & Core Primitives
MCP operates over a client-server architecture using JSON-RPC 2.0 messages. The client (typically embedded inside an agent framework or IDE host) initiates subprocesses or remote sessions running MCP servers, discovering and invoking capabilities dynamically.
The Three Protocol Primitives
MCP divides server-side capabilities into three distinct primitives:
- Resources (
resources/list,resources/read): Read-only data schemas representing file paths, database tables, or real-time context feeds. Resources are retrieved in a single turn and do not execute code. - Prompts (
prompts/list,prompts/get): Predefined, parameterized templates that provide system or user prompts to the client. This shifts prompt versioning away from the client application. - Tools (
tools/list,tools/call): Executable function endpoints that take input parameters, perform operations with side effects (e.g., executing code, writing to databases, writing files), and return content payloads.
Stdio vs. SSE Transport Matrix
MCP defines two standard transports for carrying JSON-RPC payloads:
| Dimension | Stdio Transport | SSE (Server-Sent Events) Transport |
|---|---|---|
| Use Case | Local integrations (IDE extensions, local DBs, file utilities) | Remote service integrations (hosted databases, SaaS APIs) |
| Connection Lifecycle | Launched as a subprocess; transport is closed when process exits | Dynamic HTTP connection (SSE for downstream, POST for upstream) |
| Authentication | Inherited from host system permissions | Bearer tokens, CORS validation, Mutual TLS (mTLS) |
| Network Requirement | Zero network overhead (runs purely on local stdin/stdout) | Standard HTTP/HTTPS connectivity required |
Connection Handshake & Execution Lifecycle
🛠️ 2. Implementing a Production MCP Server (Python SDK)
To build a production MCP Server, use the official Python SDK. This example exposes a filesystem read resource and a SQLite database query tool.
Critical Gotcha: stdout vs. stderr Logging
Because Stdio transport utilizes standard input/output (sys.stdin and sys.stdout) to carry JSON-RPC message payloads, any standard application logging (e.g., calling print() or default logging.info()) will corrupt the JSON-RPC stream and crash the client connection. All application logs, debugging printouts, and stack traces must be directed to sys.stderr.
import logging
import sys
import sqlite3
from mcp.server.fastmcp import FastMCP
# 1. Initialize FastMCP server
mcp = FastMCP("SQLite & Filesystem Server")
# 2. Redirect all root logging strictly to stderr
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
stream=sys.stderr
)
logger = logging.getLogger("mcp_server")
DB_PATH = "production_data.db"
# 3. Expose a read-only Resource
@mcp.resource("data://logs/{log_id}")
def get_log_resource(log_id: str) -> str:
"""Retrieve raw historical execution logs by log ID."""
logger.info(f"Reading resource log_id: {log_id}")
try:
with open(f"/var/log/app/{log_id}.log", "r") as f:
return f.read()
except FileNotFoundError:
return "Log file not found."
# 4. Expose an executable Tool
@mcp.tool()
def query_sqlite_database(query: str) -> str:
"""Execute a structured SELECT query against the production SQLite database."""
logger.info(f"Executing database query: {query}")
# Restrict tool actions to prevent destructive commands
upper_query = query.upper()
if any(keyword in upper_query for keyword in ["INSERT", "UPDATE", "DELETE", "DROP"]):
return "Error: Read-only query access permitted. Destructive SQL commands are blocked."
try:
conn = sqlite3.connect(DB_PATH)
cursor = conn.cursor()
cursor.execute(query)
results = cursor.fetchall()
conn.close()
return str(results)
except Exception as e:
logger.error(f"SQL execution crashed: {str(e)}")
return f"Database Error: {str(e)}"
if __name__ == "__main__":
# 5. Start the server using standard stdio transport
mcp.run(transport="stdio")Node.js MCP Client & OpenAI Interoperability (TypeScript)
This TypeScript client spawns the Python server as a subprocess, initializes the stdio transport, and maps its tools into standard OpenAI tool schemas.
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
// 1. Define OpenAI Tool interface
interface OpenAIFunctionTool {
type: "function";
function: {
name: string;
description: string;
parameters: Record<string, any>;
};
}
async function startMcpClientAndRegisterTools(): Promise<OpenAIFunctionTool[]> {
// 2. Initialize Stdio transport, spawning the target Python server process
const transport = new StdioClientTransport({
command: "python3",
args: ["server.py"]
});
const client = new Client({
name: "HandbookClient",
version: "1.0.0"
}, {
capabilities: {}
});
// 3. Connect and execute the JSON-RPC handshake
await client.connect(transport);
console.error("MCP Handshake Completed.");
// 4. Retrieve tool schemas from the server
const response = await client.listTools();
// 5. Map MCP tools to OpenAI-native function schemas
return response.tools.map((mcpTool) => {
return {
type: "function",
function: {
name: mcpTool.name,
description: mcpTool.description || "",
// MCP inputSchema aligns natively with JSON Schema parameters
parameters: mcpTool.inputSchema
}
};
});
}🔒 4. Security Boundaries & Transport Hardening
Because MCP servers act as bridges to local systems and secure networks, the transport channels must be hardened against unauthorized access and arbitrary execution.
Local Stdio Subprocess Hardening
When spawning local Stdio servers:
- Minimize Process Environment: Never copy the full parent process environment variables to the spawned child. Pass only the minimal required runtime path configurations.
- Disable Shell Invocation: Spawn the server binary directly (e.g.
spawn("python3", ["server.py"])rather than running inside/bin/shor/bin/bash), neutralizing command injection vectors at the process boundary. - Chroot & Filesystem Jails: Restrict file reading tools to specific, relative paths to prevent directory traversal attacks (e.g.
../etc/passwdinjection).
Remote SSE Transport Hardening
When exposing servers over remote HTTP connections:
- Mutual TLS (mTLS): Enforce client certificates to ensure only authorized client applications (e.g. specific hosted agent runners) can open the SSE streaming socket.
- JWT Client Verification: Authenticate all HTTP POST requests (which trigger tool invocations) using JWT tokens containing scoped access claims.
- Strict CORS Policies: Configure the CORS headers to reject tool requests coming from browser-based clients unless explicitly whitelisted.
🔭 5. Observability & Debugging Workflows
Interactive Debugging with the MCP Inspector
Anthropic provides the MCP Inspector, an interactive web-based utility to debug Stdio-based servers by sending manual requests, checking resource read states, and verifying parameters:
# Attach the MCP Inspector to your local python server script
npx @modelcontextprotocol/inspector python3 server.pyThis launches a browser workspace at http://localhost:3000 where you can manually click listTools, trigger tools/call with parameters, and monitor stderr logs.
Trace Propagation across Process Boundaries
To trace agent loops from the host app through the MCP server tools, pass W3C trace propagation contexts within the _meta parameter of JSON-RPC requests. This links client and server spans under a single OpenTelemetry parent trace ID.
{
"jsonrpc": "2.0",
"method": "tools/call",
"params": {
"name": "query_sqlite_database",
"arguments": {
"query": "SELECT * FROM users"
},
"_meta": {
"traceparent": "00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01"
}
},
"id": 1
}🔗 Related Sections
- Agent Skills & Capabilities — Designing reliable tool parameter schemas.
- Agent Security & Guardrails — Detailed code execution containment and process sandboxing runtimes.
- AI Agent Tools Comparison — Framework alignment and support matrix.
- Agent Observability & Tracing — OpenTelemetry GenAI span semantic conventions.