从零开始,完全掌握模型上下文协议 — AI 智能体连接外部世界的通用标准
MCP(Model Context Protocol,模型上下文协议)是由 Anthropic 于 2024 年 11 月发布的一项开放标准,旨在为 AI 应用(特别是大语言模型 LLM)提供一种统一的方式来连接外部数据源、工具和服务。
简单来说:如果 LLM 是一个「大脑」,那么 MCP 就是它的「神经系统」——让大脑可以感知外部世界、获取信息并执行操作。
MCP 是 AI 应用与外部系统之间的「USB-C 接口」——标准化、通用、即插即用。
MCP 的灵感来自 LSP(Language Server Protocol,语言服务器协议)。LSP 标准化了代码编辑器与编程语言支持之间的通信——有了 LSP,一个编辑器可以支持所有语言,一个语言服务可以被所有编辑器使用。
MCP 做了完全相同的事情,但面向的是 AI 应用与外部数据/工具的连接。
MCP 之前 vs MCP 之后
每个 AI 应用都需要为每个工具/数据源编写独立的自定义连接器。3 个 AI 应用 × 5 个工具 = 15 个集成。
所有 AI 应用通过 MCP 协议通信。只需 3 + 5 = 8 个集成,且每个只实现一次。
在 MCP 出现之前,开发者面临的核心痛点是 「孤立的 AI」:
| 痛点 | MCP 解决方案 |
|---|---|
| 数据孤岛 | 通过 Resources(资源)向 AI 暴露实时数据 |
| 集成碎片化 | 统一协议标准,一次实现,处处可用 |
| 厂商锁定 | 开源标准,已捐赠给 Linux 基金会 |
| 能力受限 | 通过 Tools(工具)让 AI 执行实际操作 |
MCP 采用经典的 Client-Server(客户端-服务器) 架构,包含三个核心角色:
MCP 架构总览
宿主是运行 LLM 的主应用程序,负责整体协调。典型的宿主包括 Claude Desktop、VS Code、Cursor IDE 等。宿主的职责包括:管理多个 MCP 客户端连接、执行安全策略和权限控制、协调 AI 模型与工具之间的交互。
客户端嵌入在宿主内部,负责与特定的 MCP 服务器建立 1:1 的连接。每个客户端维护自己独立的会话状态,不会与其他客户端混淆信息。
服务器是真正暴露能力的一方。它可以提供三类原语:Resources(资源)、Tools(工具)和 Prompts(提示模板)。服务器可以是本地运行的进程,也可以是远程部署的 Web 服务。
一个 Host 内可以同时运行多个 Client,每个 Client 对应一个 Server。例如:Claude Desktop 可以同时连接 GitHub Server、Slack Server 和 PostgreSQL Server。
MCP 的功能通过三种「原语(Primitives)」来提供,每种原语有不同的控制方和使用模式:
| 原语 | 控制方 | 描述 | 示例 |
|---|---|---|---|
| Tools | 模型控制 | 可执行的操作(有副作用) | 发送邮件、创建 Issue、查询 API |
| Resources | 应用控制 | 只读的数据/内容(无副作用) | 读取文件、获取数据库记录 |
| Prompts | 用户控制 | 预定义的提示模板 | 代码审查模板、摘要生成模板 |
工具是最重要、使用最频繁的原语。它代表模型可以决定调用的操作函数。每个工具有名称、描述和输入参数的 JSON Schema 定义。
{
"name": "send_email",
"description": "发送一封电子邮件到指定地址",
"inputSchema": {
"type": "object",
"properties": {
"to": { "type": "string", "description": "收件人邮箱" },
"subject": { "type": "string", "description": "邮件主题" },
"body": { "type": "string", "description": "邮件正文" }
},
"required": ["to", "subject", "body"]
}
}
资源代表服务器可以提供的只读数据。它类似于 REST API 中的 GET 端点。资源通过 URI 标识,客户端可以先列出可用资源,再按需读取。
{
"resources": [
{
"uri": "file:///project/README.md",
"name": "项目说明文档",
"mimeType": "text/markdown"
},
{
"uri": "db://users/recent",
"name": "最近注册的用户",
"mimeType": "application/json"
}
]
}
提示模板是服务器预定义的、由用户主动选择的交互模板。它可以包含参数,填充后生成结构化的提示词发送给模型。
Tools:模型自主决定何时调用。Resources:由应用/客户端程序控制加载。Prompts:由最终用户从菜单中选择触发。理解控制权的不同是正确使用 MCP 的关键。
MCP 使用 JSON-RPC 2.0 作为消息格式。JSON-RPC 是一种轻量级的远程过程调用协议,使用 JSON 编码消息。它定义了三种消息类型:
method、params 和唯一 id,期望对方返回响应。result(成功)或 error(失败),携带相同的 id。id,不期望回复。用于进度更新、状态变更等。// ① 客户端发送请求:列出所有工具 { "jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {} } // ② 服务器返回响应 { "jsonrpc": "2.0", "id": 1, "result": { "tools": [ { "name": "get_weather", "description": "获取指定城市天气", "inputSchema": { ... } } ] } }
MCP 支持多种传输方式,传输层负责将 MCP 消息转换为 JSON-RPC 格式进行发送/接收:
| 传输方式 | 适用场景 | 特点 |
|---|---|---|
| stdio | 本地集成、CLI 工具 | 通过标准输入/输出通信,简单直接,适合单机部署 |
| HTTP + SSE | 远程服务、Web 部署 | 客户端用 HTTP POST 发请求,服务器通过 SSE 流式返回 |
| 自定义传输 | 特殊需求 | 实现 Transport 接口即可,支持 WebSocket 等 |
一个 MCP 连接从建立到结束,会经历三个阶段:
这是一个三步握手过程:
initialize 请求,携带自己支持的协议版本和能力声明initialized 通知,确认握手完成// 步骤 1: 客户端 → 服务器 { "jsonrpc": "2.0", "id": 0, "method": "initialize", "params": { "protocolVersion": "2025-11-25", "capabilities": { "sampling": {} }, "clientInfo": { "name": "my-ai-app", "version": "1.0.0" } } } // 步骤 2: 服务器 → 客户端 { "jsonrpc": "2.0", "id": 0, "result": { "protocolVersion": "2025-11-25", "capabilities": { "tools": {}, "resources": { "subscribe": true } }, "serverInfo": { "name": "weather-server", "version": "2.1.0" } } } // 步骤 3: 客户端 → 服务器 (通知,无 id) { "jsonrpc": "2.0", "method": "notifications/initialized" }
握手完成后,双方进入正常通信。客户端可以发现和调用工具、请求资源、获取提示模板。服务器也可以反向请求客户端进行 LLM 采样(Sampling)。
连接可以通过以下方式结束:客户端调用 close() 进行正常关闭、传输层断开连接、或出现错误导致异常终止。
Block(Square 母公司)使用 MCP 构建了连接支付系统的 AI 助手。Bloomberg 用 MCP 将金融数据实时接入 AI 模型。在 Claude.ai 中,已有 75+ 个官方连接器可直接使用,包括 Gmail、Google Calendar、Figma、Slack 等。
下面用 TypeScript 和 Python 两种语言展示如何从零构建一个简单的 MCP 服务器,该服务器提供一个天气查询工具。
mkdir weather-mcp-server && cd weather-mcp-server npm init -y npm install @modelcontextprotocol/sdk zod
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; // 创建 MCP 服务器实例 const server = new McpServer({ name: "weather-server", version: "1.0.0", }); // 注册一个工具:获取天气 server.tool( "get_weather", // 工具名称 "获取指定城市的当前天气信息", // 描述 { // 参数 schema (Zod) city: z.string().describe("城市名称"), }, async ({ city }) => { // 处理函数 // 这里可以调用真实的天气 API const weatherData = { city, temperature: "22°C", condition: "晴朗", humidity: "45%", }; return { content: [ { type: "text", text: JSON.stringify(weatherData, null, 2), }, ], }; } ); // 注册一个资源:服务器状态 server.resource( "status", "server://status", async () => ({ contents: [{ uri: "server://status", text: "服务器运行正常,已注册 1 个工具", mimeType: "text/plain", }], }) ); // 启动服务器,使用 stdio 传输 const transport = new StdioServerTransport(); await server.connect(transport);
pip install mcp
from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent import json # 创建服务器实例 app = Server("weather-server") # 列出可用工具 @app.list_tools() async def list_tools(): return [ Tool( name="get_weather", description="获取指定城市的当前天气", inputSchema={ "type": "object", "properties": { "city": { "type": "string", "description": "城市名称" } }, "required": ["city"] } ) ] # 处理工具调用 @app.call_tool() async def call_tool(name: str, arguments: dict): if name == "get_weather": city = arguments["city"] # 模拟天气数据(实际可调用 API) data = { "city": city, "temperature": "22°C", "condition": "晴朗", } return [TextContent( type="text", text=json.dumps(data, ensure_ascii=False) )] # 启动服务器 async def main(): async with stdio_server() as (read, write): await app.run( read, write, app.create_initialization_options() ) if __name__ == "__main__": import asyncio asyncio.run(main())
将服务器信息添加到 claude_desktop_config.json 中,Claude Desktop 会自动发现并连接你的 MCP 服务器:
{
"mcpServers": {
"weather": {
"command": "node",
"args": ["path/to/server.js"]
}
}
}
客户端负责连接服务器、发现能力、并在合适时机调用工具。以下是一个 TypeScript 客户端的核心流程:
import { Client } from "@modelcontextprotocol/sdk/client/index.js"; import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js"; // ① 创建传输层:启动服务器进程 const transport = new StdioClientTransport({ command: "node", args: ["./server.js"], }); // ② 创建客户端并连接 const client = new Client({ name: "my-client", version: "1.0.0", }); await client.connect(transport); // ③ 发现服务器提供的工具 const { tools } = await client.listTools(); console.log("可用工具:", tools.map(t => t.name)); // ④ 调用工具 const result = await client.callTool({ name: "get_weather", arguments: { city: "北京" }, }); console.log("天气结果:", result.content); // ⑤ 读取资源 const resource = await client.readResource({ uri: "server://status", }); console.log("状态:", resource.contents); // ⑥ 断开连接 await client.close();
完整的工具调用流程
"北京今天天气怎么样?"
get_weather(city="北京")
tools/call 请求
| 语言 | 包名 | 安装命令 |
|---|---|---|
| TypeScript | @modelcontextprotocol/sdk | npm install @modelcontextprotocol/sdk |
| Python | mcp | pip install mcp |
| Java | spring-ai-mcp | Maven/Gradle |
| Kotlin | 官方 SDK | Maven/Gradle |
| C# | 官方 SDK | NuGet |
| Go / Rust / Swift | 社区 SDK | 各语言包管理器 |
MCP 允许 AI 执行任意代码和数据访问,因此安全问题不可忽视。2025 年的安全审计发现了提示注入、工具权限滥用、数据泄露等多个风险领域。
MCP 服务器应只暴露完成任务所必需的最少能力。避免一个服务器同时提供读写数据库、发送邮件和操作文件系统的能力。
宿主应用必须在调用任何工具之前获得用户的明确授权。工具的描述信息(annotations)应被视为不可信的,除非来自可信服务器。
使用 JSON Schema 验证所有传入参数,在客户端和服务器两侧都要进行验证。
localhost(127.0.0.1),而非 0.0.0.0Origin 头部以防止 DNS 重绑定攻击工具返回的数据可能包含恶意提示词,企图操控 LLM 的行为。服务器应对输出进行清理,宿主应用应将工具结果与用户输入区分处理。
| 实践 | 说明 |
|---|---|
| 使用 OAuth 2.0 | 远程服务器采用标准 OAuth 流程进行身份验证 |
| 日志审计 | 记录所有工具调用及其参数,便于事后审查 |
| 速率限制 | 对工具调用频率进行限制,防止滥用 |
| 沙箱执行 | 在隔离环境中执行危险操作 |
| 版本锁定 | 使用 protocolVersion 确保兼容性 |
| 能力声明 | 精确声明服务器支持的能力,不多不少 |
MCP 用一年时间完成了许多标准十年才能实现的目标:全行业采纳和治理转移。从 Anthropic 的内部实验到 Linux 基金会治理的全球标准,MCP 的历程堪比 Docker 和 Kubernetes。对于开发者而言,现在就是学习和构建 MCP 的最佳时机。