Model Context Protocol (MCP) - Open standard for connecting AI applications to external data sources, tools, and systems. Use for building MCP servers (tools, resources, prompts), clients, understanding protocol architecture, and implementing AI integrations.
The Model Context Protocol (MCP) is an open-source standard that provides a universal way to connect AI-powered applications to external data sources, tools, and systems. Think of MCP as a USB-C port for AI applications - a standardized interface that enables seamless integration regardless of the underlying implementation.
Core Value Proposition: Build once, connect anywhere. MCP servers work with any MCP-compatible AI application, eliminating the need for custom integrations per application.
This skill should be triggered when:
MCP addresses a fundamental challenge: AI systems need dynamic, context-aware access to resources, but traditional APIs were built for predictable workflows.
Traditional APIs assume:
AI systems require:
Before MCP:
After MCP:
Just as USB-C provides a universal connector for devices:
"The Host mediates ALL AI-resource interactions"
┌────────────────────────────────────────────────────────┐
│ HOST APPLICATION │
│ (Claude Desktop, IDE, Custom App) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Client 1 │ │ Client 2 │ │ Client 3 │ │
│ │ ↕ │ │ ↕ │ │ ↕ │ │
│ │Server A │ │Server B │ │Server C │ 1:1 │
│ └──────────┘ └──────────┘ └──────────┘ mapping │
└────────────────────────────────────────────────────────┘
Key security principles:
┌─────────────────────────────────────────────────────────────┐
│ MCP ARCHITECTURE │
└─────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────┐
│ HOST (AI App) │
│ ┌────────────────────────────────────────────────────────┐ │
│ │ MCP CLIENT │ │
│ │ • Maintains 1:1 connections with servers │ │
│ │ • Handles protocol negotiation │ │
│ │ • Routes messages to/from servers │ │
│ └───────────┬────────────────────────┬───────────────────┘ │
│ │ │ │
└──────────────┼────────────────────────┼───────────────────────┘
│ stdio │ HTTP
▼ ▼
┌──────────────────────┐ ┌──────────────────────┐
│ LOCAL SERVER │ │ REMOTE SERVER │
│ (subprocess) │ │ (network) │
│ │ │ │
│ • Tools │ │ • Tools │
│ • Resources │ │ • Resources │
│ • Prompts │ │ • Prompts │
└──────────────────────┘ └──────────────────────┘
MCP uses JSON-RPC 2.0 over various transports (Specification: 2024-11-05):
JSON-RPC Requirements:
id MUST NOT be null for requests (use string or integer)id MUST be unique within a session-32601 (Method not found)// Request (id required, must be unique per session)
{
"jsonrpc": "2.0",
"id": "req-1", // String or integer, never null
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": { "city": "San Francisco" }
}
}
// Response (id matches request)
{
"jsonrpc": "2.0",
"id": "req-1",
"result": {
"content": [{
"type": "text",
"text": "Weather in San Francisco: 65°F, partly cloudy"
}]
}
}
// Notification (no id, no response expected)
{
"jsonrpc": "2.0",
"method": "notifications/resources/updated",
"params": { "uri": "file:///data/config.json" }
}
┌─────────────────────────────────────────────────────────────┐
│ CONNECTION LIFECYCLE │
└─────────────────────────────────────────────────────────────┘
1. INITIALIZATION (Version Negotiation)
Client ──initialize──────► Server
└─ protocolVersion: "2024-11-05"
└─ capabilities: { sampling: {}, roots: {} }
└─ clientInfo: { name, version }
Client ◄──result────────── Server
└─ protocolVersion: "2024-11-05" (server's supported version)
└─ capabilities: { tools: {}, resources: {} }
└─ serverInfo: { name, version }
Client ──initialized──────► Server (notification, no response)
2. OPERATION PHASE
Client ◄──► Server (bidirectional messages)
• Client calls server methods (tools/call, resources/read)
• Server sends notifications (resource updates, progress)
• Server may call client methods (sampling/createMessage)
3. TERMINATION
For stdio: Close input stream, wait for server exit, terminate
For HTTP: Send HTTP DELETE with Mcp-Session-Id header
Version Negotiation:
protocolVersion in initializeMCP servers expose three primary primitives:
Tools are executable functions that AI models can invoke to perform actions:
{
"name": "send_email",
"description": "Send an email to a recipient",
"inputSchema": {
"type": "object",
"properties": {
"to": {
"type": "string",
"description": "Recipient email address"
},
"subject": {
"type": "string",
"description": "Email subject line"
},
"body": {
"type": "string",
"description": "Email body content"
}
},
"required": ["to", "subject", "body"]
}
}
Tool Call Flow:
1. Client requests: tools/list
2. Server returns: Available tools with schemas
3. Model decides to call tool
4. Client sends: tools/call with arguments
5. Server executes and returns: result content
Tool Result Content Types:
text - Plain text responseimage - Base64-encoded image dataaudio - Base64-encoded audio dataresource - Embedded resource contentError Handling with isError:
// Normal result
{
"content": [{ "type": "text", "text": "Success!" }],
"isError": false // Optional, defaults to false
}
// Execution error (not a JSON-RPC error)
{
"content": [{ "type": "text", "text": "File not found: /data/missing.txt" }],
"isError": true // Tool ran but encountered an error
}
Use isError: true when the tool executed but encountered an expected error (file not found, validation failed, etc.). Use JSON-RPC errors for protocol-level failures.
Resources are data sources that provide context to AI applications:
{
"uri": "file:///projects/myapp/README.md",
"name": "README.md",
"description": "Project readme file",
"mimeType": "text/markdown"
}
Resource URIs:
file://, https://postgres://, git://file:///{path} (parameterized)Resource Templates:
{
"uriTemplate": "file:///{path}",
"name": "Project Files",
"description": "Access files in the project directory",
"mimeType": "text/plain"
}
Templates use URI Template syntax (RFC 6570) for parameterized resource access. Servers that support templates should also expose completion/complete for auto-completion.
Content Types:
// Text content
{
"uri": "file:///README.md",
"mimeType": "text/markdown",
"text": "# Project Title\n..."
}
// Binary content (blob)
{
"uri": "file:///image.png",
"mimeType": "image/png",
"blob": "iVBORw0KGgoAAAANSUhEUgAA..." // base64-encoded
}
Resource Operations:
// List resources
{ "method": "resources/list" }
// Read resource
{
"method": "resources/read",
"params": { "uri": "file:///data/config.json" }
}
// Subscribe to changes
{
"method": "resources/subscribe",
"params": { "uri": "file:///data/config.json" }
}
Prompts are reusable templates for AI interactions:
{
"name": "code_review",
"title": "Request Code Review",
"description": "Analyze code quality and suggest improvements",
"arguments": [
{
"name": "code",
"description": "The code to review",
"required": true
},
{
"name": "language",
"description": "Programming language",
"required": false
}
]
}
Prompt Messages:
{
"method": "prompts/get",
"params": {
"name": "code_review",
"arguments": {
"code": "def hello(): print('world')",
"language": "python"
}
}
}
// Response
{
"result": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "Please review this Python code:\ndef hello(): print('world')"
}
}
]
}
}
Servers can request LLM completions through the client:
{
"method": "sampling/createMessage",
"params": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "Summarize this document..."
}
}
],
"modelPreferences": {
"hints": [{ "name": "claude-3-sonnet" }],
"intelligencePriority": 0.8,
"speedPriority": 0.5
},
"systemPrompt": "You are a helpful assistant.",
"maxTokens": 500
}
}
Model Preferences (0-1 scale):
costPriority - Prefer cheaper modelsspeedPriority - Prefer faster modelsintelligencePriority - Prefer more capable modelsHuman-in-the-Loop: Sampling requests SHOULD be reviewed by users before execution.
Clients can expose filesystem roots to servers:
{
"capabilities": {
"roots": {
"listChanged": true
}
}
}
Roots define boundaries for server access, allowing servers to understand which directories or resources they can interact with.
MCP includes base utilities and server utilities for protocol-level operations.
Ping (Connection Health):
// Request
{ "jsonrpc": "2.0", "id": 1, "method": "ping" }
// Response
{ "jsonrpc": "2.0", "id": 1, "result": {} }
Used to check connection health. Either party can send ping; receiver MUST respond promptly.
Cancellation:
{
"jsonrpc": "2.0",
"method": "notifications/cancelled",
"params": {
"requestId": "req-123",
"reason": "User cancelled operation"
}
}
Notification to cancel a pending request. The receiver SHOULD stop processing and MAY return a partial result or error.
Progress Notifications:
{
"jsonrpc": "2.0",
"method": "notifications/progress",
"params": {
"progressToken": "token-456",
"progress": 50,
"total": 100,
"message": "Processing files..."
}
}
For long-running operations. The progressToken is provided in the original request's _meta.progressToken.
Completion (Auto-complete):
// Request
{
"jsonrpc": "2.0",
"id": 1,
"method": "completion/complete",
"params": {
"ref": {
"type": "ref/resource",
"uri": "file:///{path}"
},
"argument": {
"name": "path",
"value": "src/"
}
}
}
// Response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"completion": {
"values": ["src/index.ts", "src/utils/", "src/types/"],
"hasMore": false
}
}
}
Provides auto-completion suggestions for resource template arguments or prompt arguments.
Logging:
{
"jsonrpc": "2.0",
"method": "notifications/message",
"params": {
"level": "info", // debug, info, notice, warning, error, critical, alert, emergency
"logger": "database",
"data": "Connected to PostgreSQL at localhost:5432"
}
}
Servers can send log messages to clients. The client MAY filter based on level threshold set via logging/setLevel.
Pagination: For large result sets, use cursor-based pagination:
// Request with cursor
{
"method": "tools/list",
"params": { "cursor": "eyJvZmZzZXQiOjEwMH0=" }
}
// Response with next cursor
{
"result": {
"tools": [...],
"nextCursor": "eyJvZmZzZXQiOjIwMH0=" // null if no more results
}
}
Cursors are opaque strings. Clients SHOULD NOT assume any structure.
For subprocess-based communication:
# Server launched by client as subprocess
$ my-mcp-server
# Communication via stdin/stdout
Server reads: stdin (JSON-RPC messages)
Server writes: stdout (JSON-RPC responses)
Server logs: stderr (debugging only)
Requirements:
For network-based communication:
POST /mcp HTTP/1.1
Content-Type: application/json
Accept: application/json, text/event-stream
MCP-Protocol-Version: 2025-06-18
{"jsonrpc":"2.0","id":1,"method":"tools/list"}
Response Types:
application/json - Single JSON responsetext/event-stream - SSE stream for multiple messagesSession Management:
1. Server returns: Mcp-Session-Id header
2. Client includes: Mcp-Session-Id in subsequent requests
3. Server MAY: Return 404 to terminate session
4. Client MAY: DELETE with session ID to close
Security Requirements:
Origin header (prevent DNS rebinding)npm install @modelcontextprotocol/sdk
import { McpServer, StdioServerTransport } from "@modelcontextprotocol/sdk/server";
const server = new McpServer({
name: "my-server",
version: "1.0.0"
});
// Add a tool
server.tool("get_weather", {
description: "Get weather for a city",
inputSchema: {
type: "object",
properties: {
city: { type: "string", description: "City name" }
},
required: ["city"]
}
}, async (args) => {
const weather = await fetchWeather(args.city);
return {
content: [{ type: "text", text: `Weather: ${weather}` }]
};
});
// Start server
const transport = new StdioServerTransport();
await server.connect(transport);
GitHub: https://github.com/modelcontextprotocol/typescript-sdk
pip install mcp
# or with uv
uv add mcp
from mcp.server import Server
from mcp.server.stdio import stdio_server
server = Server("my-server")
@server.tool()
async def get_weather(city: str) -> str:
"""Get weather for a city."""
weather = await fetch_weather(city)
return f"Weather: {weather}"
@server.resource("config://app")
async def get_config() -> str:
"""Get application configuration."""
return json.dumps(config)
async def main():
async with stdio_server() as (read, write):
await server.run(read, write)
if __name__ == "__main__":
import asyncio
asyncio.run(main())
GitHub: https://github.com/modelcontextprotocol/python-sdk
| Language | Installation | Repository |
|----------|--------------|------------|
| Go | go get github.com/modelcontextprotocol/go-sdk | go-sdk |
| Kotlin | Maven/Gradle | kotlin-sdk |
| Swift | Swift Package Manager | swift-sdk |
| Java | Maven | java-sdk |
| C# | NuGet | csharp-sdk |
| Ruby | gem install mcp | ruby-sdk |
| Rust | cargo add mcp | rust-sdk |
| PHP | Composer | php-sdk |
import { McpServer, StdioServerTransport } from "@modelcontextprotocol/sdk/server";
const server = new McpServer({
name: "example-server",
version: "1.0.0",
capabilities: {
tools: {},
resources: {},
prompts: {}
}
});
// Tool: Calculate
server.tool("calculate", {
description: "Perform basic calculations",
inputSchema: {
type: "object",
properties: {
operation: { type: "string", enum: ["add", "subtract", "multiply", "divide"] },
a: { type: "number" },
b: { type: "number" }
},
required: ["operation", "a", "b"]
}
}, async ({ operation, a, b }) => {
let result: number;
switch (operation) {
case "add": result = a + b; break;
case "subtract": result = a - b; break;
case "multiply": result = a * b; break;
case "divide": result = a / b; break;
}
return {
content: [{ type: "text", text: `Result: ${result}` }]
};
});
// Resource: Static config
server.resource("config://app", {
name: "App Configuration",
description: "Application settings",
mimeType: "application/json"
}, async () => {
return {
contents: [{
uri: "config://app",
mimeType: "application/json",
text: JSON.stringify({ version: "1.0", debug: false })
}]
};
});
// Prompt: Greeting
server.prompt("greeting", {
name: "greeting",
description: "Generate a personalized greeting",
arguments: [
{ name: "name", description: "Person's name", required: true }
]
}, async ({ name }) => {
return {
messages: [{
role: "user",
content: { type: "text", text: `Please greet ${name} warmly.` }
}]
};
});
// Connect transport
const transport = new StdioServerTransport();
await server.connect(transport);
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent, Resource, Prompt, PromptMessage
server = Server("example-server")
# Tool: Calculate
@server.tool()
async def calculate(operation: str, a: float, b: float) -> list[TextContent]:
"""Perform basic calculations (add, subtract, multiply, divide)."""
ops = {
"add": a + b,
"subtract": a - b,
"multiply": a * b,
"divide": a / b if b != 0 else float('inf')
}
result = ops.get(operation, 0)
return [TextContent(type="text", text=f"Result: {result}")]
# Resource: Config
@server.resource("config://app")
async def get_config() -> str:
"""Application configuration."""
return '{"version": "1.0", "debug": false}'
# Prompt: Greeting
@server.prompt()
async def greeting(name: str) -> list[PromptMessage]:
"""Generate a personalized greeting."""
return [
PromptMessage(
role="user",
content=TextContent(type="text", text=f"Please greet {name} warmly.")
)
]
async def main():
async with stdio_server() as (read, write):
await server.run(read, write)
if __name__ == "__main__":
import asyncio
asyncio.run(main())
import { McpClient, StdioClientTransport } from "@modelcontextprotocol/sdk/client";
import { spawn } from "child_process";
// Spawn server as subprocess
const serverProcess = spawn("node", ["path/to/server.js"]);
// Create client
const client = new McpClient({
name: "my-client",
version: "1.0.0"
});
// Connect via stdio
const transport = new StdioClientTransport({
reader: serverProcess.stdout,
writer: serverProcess.stdin
});
await client.connect(transport);
// Initialize and get capabilities
const capabilities = await client.initialize();
console.log("Server capabilities:", capabilities);
// List available tools
const tools = await client.listTools();
console.log("Available tools:", tools);
// Call a tool
const result = await client.callTool("calculate", {
operation: "add",
a: 5,
b: 3
});
console.log("Tool result:", result);
// List and read resources
const resources = await client.listResources();
const config = await client.readResource("config://app");
console.log("Config:", config);
// Get a prompt
const prompt = await client.getPrompt("greeting", { name: "Alice" });
console.log("Prompt messages:", prompt.messages);
// Cleanup
await client.close();
serverProcess.kill();
from mcp.client import ClientSession
from mcp.client.stdio import stdio_client
import subprocess
import asyncio
async def main():
# Spawn server subprocess
server = subprocess.Popen(
["python", "path/to/server.py"],
stdin=subprocess.PIPE,
stdout=subprocess.PIPE
)
# Connect client
async with stdio_client(server.stdin, server.stdout) as (read, write):
async with ClientSession(read, write) as session:
# Initialize
await session.initialize()
# List tools
tools = await session.list_tools()
print("Tools:", tools)
# Call tool
result = await session.call_tool("calculate", {
"operation": "multiply",
"a": 7,
"b": 6
})
print("Result:", result)
# Read resource
config = await session.read_resource("config://app")
print("Config:", config)
if __name__ == "__main__":
asyncio.run(main())
Servers and clients exchange capabilities during initialization:
{
"capabilities": {
"tools": {
"listChanged": true
},
"resources": {
"subscribe": true,
"listChanged": true
},
"prompts": {
"listChanged": true
},
"logging": {}
<!-- Content truncated for initial SEO render. Open the source file tab for the full file. -->
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer