Model Context Protocol (MCP)
MCP 是 Anthropic 推出的开放协议,用于连接 AI 应用与数据源
2. Model Context Protocol (MCP)
2.1 核心概念
MCP 定义了 AI 模型与外部系统交互的标准化方式,核心架构包含三个角色:
┌────────────────────────────────────────────────────────────────────────┐
│ MCP 架构 │
├────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ MCP Host │ │ MCP Client │ │ MCP Server │ │
│ │ (如 Cursor) │ ──────── │ (协议层) │ ──────── │ (工具提供方) │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │ │ │ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ 用户界面 │ │ 消息路由 │ │ 具体实现 │ │
│ │ 权限管理 │ │ 协议转换 │ │ 工具/资源 │ │
│ │ 会话管理 │ │ 连接管理 │ │ 提示模板 │ │
│ └──────────────┘ └──────────────┘ └──────────────┘ │
│ │
└────────────────────────────────────────────────────────────────────────┘三个角色:
| 角色 | 职责 | 示例 |
|---|---|---|
| Host | 容纳 AI 应用的宿主环境 | Cursor IDE, Claude Desktop |
| Client | 与 Server 建立连接的协议层 | MCP SDK 客户端实现 |
| Server | 提供工具、资源和提示的服务端 | 文件系统 Server, GitHub Server |
2.2 三大核心能力
MCP Server 可以提供三种类型的能力:
typescript
// 1. Resources - 资源(类似 REST 资源)
// 提供数据读取能力,由 Server 暴露,由 Client 读取
interface Resource {
uri: string; // 资源唯一标识,如 "file:///path/to/file.txt"
name: string; // 人类可读名称
description?: string; // 资源描述
mimeType?: string; // MIME 类型
}
// 2. Tools - 工具(模型可调用的函数)
// 允许 AI 执行操作,需要模型主动调用
interface Tool {
name: string;
description: string;
inputSchema: JSONSchema; // JSON Schema 定义参数
}
// 3. Prompts - 提示模板(可复用的提示)
// 预定义的提示模板,支持参数化
interface Prompt {
name: string;
description?: string;
arguments?: PromptArgument[];
}2.3 通信协议
MCP 基于 JSON-RPC 2.0,支持多种传输层:
typescript
// 请求格式
interface JSONRPCRequest {
jsonrpc: "2.0";
id: string | number;
method: string;
params?: object;
}
// 响应格式
interface JSONRPCResponse {
jsonrpc: "2.0";
id: string | number;
result?: any;
error?: {
code: number;
message: string;
data?: any;
};
}
// 通知格式(无需响应)
interface JSONRPCNotification {
jsonrpc: "2.0";
method: string;
params?: object;
}支持的传输层:
| 传输方式 | 适用场景 | 特点 |
|---|---|---|
| stdio | 本地进程通信 | 简单可靠,适合本地 Server |
| HTTP + SSE | 远程服务 | 支持 Web 部署,适合云服务 |
| WebSocket | 实时双向通信 | 低延迟,适合高频交互 |
2.4 实现 MCP Server
基础 Server 实现 (TypeScript)
typescript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
CallToolRequestSchema,
ListToolsRequestSchema,
ListResourcesRequestSchema,
ReadResourceRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
// 创建 Server 实例
const server = new Server(
{
name: "my-mcp-server",
version: "1.0.0",
},
{
capabilities: {
tools: {}, // 声明支持工具
resources: {}, // 声明支持资源
},
}
);
// 定义工具列表
server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "search_codebase",
description: "在代码库中语义搜索",
inputSchema: {
type: "object",
properties: {
query: {
type: "string",
description: "搜索查询"
},
maxResults: {
type: "number",
description: "最大结果数",
default: 10
}
},
required: ["query"]
}
},
{
name: "read_file",
description: "读取文件内容",
inputSchema: {
type: "object",
properties: {
path: { type: "string", description: "文件路径" }
},
required: ["path"]
}
}
]
}));
// 实现工具调用
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
switch (name) {
case "search_codebase": {
const results = await performSearch(args.query, args.maxResults);
return {
content: [
{
type: "text",
text: JSON.stringify(results, null, 2)
}
]
};
}
case "read_file": {
const content = await fs.readFile(args.path, "utf-8");
return {
content: [
{
type: "text",
text: content
}
]
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
});
// 定义资源列表
server.setRequestHandler(ListResourcesRequestSchema, async () => ({
resources: [
{
uri: "config://app-settings",
name: "应用配置",
description: "应用程序的配置信息",
mimeType: "application/json"
}
]
}));
// 实现资源读取
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
const { uri } = request.params;
if (uri === "config://app-settings") {
return {
contents: [
{
uri,
mimeType: "application/json",
text: JSON.stringify(appConfig, null, 2)
}
]
};
}
throw new Error(`Unknown resource: ${uri}`);
});
// 启动 Server
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP Server running on stdio");
}
main().catch(console.error);Python 实现
python
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent, Resource
# 创建 Server
server = Server("my-python-server")
# 定义工具
@server.list_tools()
async def list_tools() -> list[Tool]:
return [
Tool(
name="execute_python",
description="执行 Python 代码并返回结果",
inputSchema={
"type": "object",
"properties": {
"code": {
"type": "string",
"description": "要执行的 Python 代码"
}
},
"required": ["code"]
}
)
]
# 实现工具调用
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[TextContent]:
if name == "execute_python":
try:
# 安全考虑:实际应用中需要沙箱执行
result = eval(arguments["code"])
return [TextContent(type="text", text=str(result))]
except Exception as e:
return [TextContent(type="text", text=f"Error: {e}")]
raise ValueError(f"Unknown tool: {name}")
# 启动 Server
async def main():
async with stdio_server() as (read_stream, write_stream):
await server.run(read_stream, write_stream)
if __name__ == "__main__":
import asyncio
asyncio.run(main())2.5 实现 MCP Client
typescript
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
import { spawn } from "child_process";
async function createClient() {
// 启动 Server 进程
const serverProcess = spawn("node", ["./my-server.js"]);
// 创建传输层
const transport = new StdioClientTransport({
command: "node",
args: ["./my-server.js"]
});
// 创建 Client
const client = new Client(
{ name: "my-client", version: "1.0.0" },
{ capabilities: {} }
);
// 连接到 Server
await client.connect(transport);
return client;
}
async function main() {
const client = await createClient();
// 列出可用工具
const { tools } = await client.listTools();
console.log("Available tools:", tools.map(t => t.name));
// 调用工具
const result = await client.callTool({
name: "search_codebase",
arguments: { query: "authentication", maxResults: 5 }
});
console.log("Search results:", result.content);
// 列出资源
const { resources } = await client.listResources();
console.log("Available resources:", resources.map(r => r.name));
// 读取资源
const resource = await client.readResource({
uri: "config://app-settings"
});
console.log("Config:", resource.contents);
}2.6 配置与集成
Cursor/Claude Desktop 配置
json
// ~/.cursor/mcp.json 或 claude_desktop_config.json
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/allowed/dir"],
"env": {}
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_TOKEN": "your-github-token"
}
},
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
"env": {
"DATABASE_URL": "postgresql://user:pass@localhost/db"
}
},
"custom-server": {
"command": "node",
"args": ["/path/to/my-custom-server.js"],
"cwd": "/path/to/working/dir"
}
}
}2.7 安全最佳实践
typescript
// 1. 权限控制
const ALLOWED_PATHS = [
process.env.HOME,
'/tmp',
process.cwd()
];
function validatePath(path: string): boolean {
const resolved = path.resolve(path);
return ALLOWED_PATHS.some(allowed =>
resolved.startsWith(allowed)
);
}
// 2. 输入验证
import { z } from 'zod';
const SearchArgsSchema = z.object({
query: z.string().min(1).max(1000),
maxResults: z.number().int().positive().max(100).default(10)
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const args = SearchArgsSchema.parse(request.params.arguments);
// 使用验证后的 args
});
// 3. 速率限制
import { RateLimiter } from 'rate-limiter';
const limiter = new RateLimiter({
tokensPerInterval: 100,
interval: "minute"
});
server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (!limiter.tryRemoveTokens(1)) {
throw new Error("Rate limit exceeded");
}
// 处理请求
});
// 4. 审计日志
function logToolCall(name: string, args: any, result: any) {
const entry = {
timestamp: new Date().toISOString(),
tool: name,
arguments: args,
resultSize: JSON.stringify(result).length,
// 不记录敏感内容
};
auditLogger.info(entry);
}