给 AI 接了 100 个工具最后全乱了——MCP 协议焊死,从 JSON-RPC 到三大原语一篇打通 Agent 与外部世界的标准接口
给 AI 接了 100 个工具最后全乱了——MCP 协议焊死,从 JSON-RPC 到三大原语一篇打通 Agent 与外部世界的标准接口
本文基于 MCP 协议规范(无状态化、server identity、异步操作等重大更新于 2025-11-25 规范引入,2026 年走向 1.0 稳定阶段),Python SDK 1.26+(FastMCP),TypeScript SDK 1.x。所有代码均经过实机验证。
本文非官方规范文档,内容基于公开资料与实机验证整理,请以 modelcontextprotocol.io 最新规范为准。
注:本文含 Mermaid 流程图,若阅读平台不渲染 Mermaid,请复制到支持 Mermaid 的编辑器(VS Code + Markdown Preview Enhanced、Typora 等)查看。
你是不是也遇到过这种翻车
你给 AI Agent 接了 5 个工具:查数据库、发邮件、读文件、调 Jira、搜索知识库。每个工具单独测都没问题。但上线一周后:
- 工具越多越蠢。5 个工具的 schema 塞进上下文就占了 4000 token,模型还没干活就先晕了。加到 20 个工具,上下文窗口直接被工具定义占满。
- 换模型全得重来。你的工具是按 OpenAI Function Calling 格式写的,换 Claude 就不认;换 DeepSeek 又是另一套 schema。N 个模型乘 M 个工具等于 N x M 套适配代码。
- 工具没法复用。A 团队写了个"查订单"工具,B 团队的 Agent 想用,只能复制代码重新适配。工具成了消耗品,不是资产。
- 安全审计为零。模型调了什么工具、传了什么参数、返回了什么敏感数据,没有统一记录。出了事故谁也说不清。
根本原因:你用"手写胶水代码"的方式做工具集成,没有标准协议。 就像 USB-C 出现之前,每个设备一根专用充电线,互不通用,抽屉塞满了线还是找不到能用的那根。
MCP(Model Context Protocol,模型上下文协议)就是 AI 时代的 USB-C 接口。
MCP 是什么
MCP 是由 Anthropic 于 2024 年 11 月开源的开放协议,用于标准化 AI 应用连接外部数据源、工具和服务的方式。2025 年 12 月 Anthropic 把它捐给了 Linux Foundation 旗下的 Agentic AI Foundation,从此成为厂商中立、社区治理的标准。Google、OpenAI、微软、亚马逊均为白金会员。
截至 2026 年 8 月:
| 指标 | 数值 |
|---|---|
| 官方规范版本 | 日期版本号持续演进(无状态化于 2025-11-25 引入,2026 进入 1.0 稳定) |
| GitHub Stars(modelcontextprotocol/servers) | 86K+ |
| 社区公开 MCP Server 数量 | 10,000+ active(2025-12 官方口径,持续增长) |
| 官方 SDK 月下载量(Python + TypeScript 合计) | 9700 万+(2025-12 官方口径) |
| 原生支持的 AI 应用 | Claude、ChatGPT、Cursor、VS Code、Cline、Windsurf 等 |
| Python SDK 版本 | 1.26+(v2 设计中) |
| TypeScript SDK 版本 | 1.x |
| Java SDK 版本 | 1.0.0(2026 年 3 月) |
MCP 的设计灵感
MCP 的架构受 LSP(Language Server Protocol,语言服务器协议)启发。LSP 统一了编辑器和编程语言服务之间的通信——VS Code、Vim、Emacs 不需要为每种语言单独写插件,语言服务也不需要为每个编辑器单独适配。MCP 做的是同样的事,只不过服务对象从"编辑器与语言服务"变成了"AI 应用与外部上下文"。
MCP vs 传统 API 集成
| 对比维度 | 传统 Function Calling | MCP |
|---|---|---|
| 协议标准 | 每个模型自定义 schema | 统一开放协议 |
| 工具复用 | 换框架重写 | 一套 Server 到处接 |
| 工具发现 | 手工注册 | 启动自动发现 |
| 部署方式 | 与 Agent 同进程 | 本地子进程 / 远程独立部署 |
| 安全审计 | 无统一方案 | OAuth 2.1 + 输入验证 |
| 上下文管理 | 全部塞进 prompt | 按需加载,渐进发现 |
| 跨模型 | 不支持 | 原生支持 |
| N x M 问题 | 严重 | 一次实现永久可用 |
打个比方:传统 Function Calling 像给每个设备焊死一根线——换个插座就得重新焊。MCP 像统一用 USB-C 接口——外设只要符合协议就能即插即用,不用管里面是什么芯片。
核心架构:三大角色
Host(宿主)
Host 是 AI 应用本身——Claude Desktop、Cursor、VS Code、ChatGPT 或你自定义的 Agent 平台。Host 负责:
- 管理用户界面和权限控制
- 创建和管理 Client 实例
- 聚合来自多个 Server 的上下文
- 调用大模型并处理响应
Host 就像手机——它是用户直接操作的设备。
Client(客户端)
Client 住在 Host 内部,是连接 Server 的"USB-C 端口"。通常一个 Client 对应一个 Server,负责:
- 与 Server 建立连接和协议握手
- 转发工具调用请求
- 管理安全边界
Client 就像手机上的 USB-C 接口——用户几乎不需要看到它,但它负责说协议、管连接。
Server(服务器)
Server 是提供外部能力的服务进程。它可以暴露文件系统、数据库、API、业务系统或其他工具能力。Server 负责:
- 注册和暴露 Resources / Tools / Prompts
- 执行工具调用并返回结果
- 管理自身资源和状态
Server 就像插进来的外设——U 盘、充电器、显示器,只要接口对了就能用。
能力协商机制
Client 连接 Server 时,双方会先做能力协商(Capability Negotiation):
- Client 发送
initialize请求,声明自己支持的能力(如 sampling、roots) - Server 返回自己支持的能力(如 tools、resources、prompts)
- 双方只使用对方声明支持的功能,避免不兼容
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-11-25",
"capabilities": {
"roots": { "listChanged": true },
"sampling": {}
},
"clientInfo": { "name": "my-agent", "version": "1.0.0" }
}
}
协议基础:JSON-RPC 2.0
MCP 的通信协议是 JSON-RPC 2.0——一个轻量级的远程过程调用标准。所有消息都是 JSON 对象,通过三种类型交互:
请求(Request)
客户端发给服务端,期望得到响应:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": { "city": "北京" }
}
}
响应(Response)
服务端处理完请求后返回:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [{ "type": "text", "text": "北京今天晴,25°C" }]
}
}
通知(Notification)
单向消息,不需要响应(id 字段省略):
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
标准错误码
| 错误码 | 含义 | 说明 |
|---|---|---|
| -32700 | Parse error | JSON 解析失败 |
| -32600 | Invalid Request | 请求格式不合法 |
| -32601 | Method not found | 方法不存在或不可用 |
| -32602 | Invalid params | 参数无效 |
| -32603 | Internal error | 服务器内部错误 |
生命周期
生命周期分三个阶段:
- 初始化:Client 发
initialize请求,双方协商协议版本和能力。Client 发notifications/initialized确认。 - 正常通信:Client 调用 Server 的工具、读取资源、获取提示词。
- 关闭:连接断开,资源释放。
注意:自 2025-11-25 规范起,MCP 最大的变化之一是从有状态(Stateful)转向无状态(Stateless)。旧版依赖的 Session ID 被移除,Server 不再维护会话状态,每次请求都是独立的。这意味着如果你的 Server 依赖了 Session ID,必须迁移。
三大原语:Resources / Prompts / Tools
MCP Server 向 Client 暴露三类核心能力,称为"原语"(Primitives):
Resources(资源)——应用程序控制
Resources 是只读的上下文数据,由应用程序(Host)决定何时读取。类似于网站上的文件——应用按需拉取。
资源类型:
- 直接资源:
file:///path/to/file.txt - URI 模板资源:
db://tables/{table_name}(参数化)
资源订阅:Client 可以订阅资源变更,当资源内容更新时 Server 主动推送通知。
Python 实现示例:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-server")
@mcp.resource("config://app-settings")
def get_settings() -> str:
"""返回应用配置"""
return '{"theme": "dark", "language": "zh-CN"}'
@mcp.resource("db://tables/{table_name}")
def get_table_schema(table_name: str) -> str:
"""返回指定表的结构信息"""
schemas = {
"users": "id INT, name VARCHAR(100), email VARCHAR(200)",
"orders": "id INT, user_id INT, amount DECIMAL(10,2), status VARCHAR(20)",
}
return schemas.get(table_name, f"表 {table_name} 不存在")
Prompts(提示词)——用户控制
Prompts 是预定义的提示词模板,由用户主动选择使用。类似于 IDE 中的代码模板——用户点一下就插入。
Python 实现示例:
@mcp.prompt()
def code_review(language: str, code: str) -> str:
"""生成代码审查提示词"""
return f"""请审查以下 {language} 代码,关注:
1. 安全漏洞(SQL 注入、XSS、硬编码密钥)
2. 性能问题(N+1 查询、不必要的循环)
3. 代码风格(命名规范、注释完整性)
代码:
{code}
"""
Tools(工具)——模型控制
Tools 是可被大模型调用的函数,由模型自主决定何时调用。这是最常用的原语——模型根据用户意图,自动选择合适的工具执行。
Python 实现示例:
@mcp.tool()
def get_weather(city: str) -> str:
"""查询指定城市的当前天气
Args:
city: 城市名称,如"北京"、"上海"
"""
# 实际项目中调用天气 API
weather_db = {"北京": "晴 25°C", "上海": "多云 28°C"}
return weather_db.get(city, f"未找到 {city} 的天气信息")
@mcp.tool()
def create_jira_ticket(title: str, description: str, priority: str = "medium") -> str:
"""在 Jira 中创建工单
Args:
title: 工单标题
description: 工单描述
priority: 优先级(high / medium / low)
"""
# 实际项目中调用 Jira API
return f"工单已创建:{title}(优先级:{priority})"
三大原语对比
| 维度 | Resources | Prompts | Tools |
|---|---|---|---|
| 控制权 | 应用程序 | 用户 | 模型 |
| 可执行 | 否(只读) | 否(模板) | 是(有副作用) |
| 类比 | 文件 | 代码模板 | 函数调用 |
| URI 格式 | scheme://path |
prompt://name |
无(Tools 按名称调用,不以 URI 寻址) |
| 谁决定使用 | Host 应用程序 | 用户手动选择 | LLM 自主决定 |
| 典型场景 | 读配置、读数据库 schema | 代码审查模板、周报模板 | 查天气、发邮件、创建工单 |
关键区别:Tools 可以有副作用(写数据库、发邮件、创建资源),Resources 是只读的。Tools 由模型决定调用,Resources 由应用程序决定读取,Prompts 由用户主动选择。
客户端功能
Roots(文件系统根目录)
Roots 是 Client 告诉 Server"你能访问哪些文件系统目录"的机制。Server 只能访问 Client 声明的 Roots 范围内的文件,防止越权访问。
{
"method": "roots/list",
"result": {
"roots": [
{ "uri": "file:///home/user/project", "name": "项目目录" },
{ "uri": "file:///home/user/docs", "name": "文档目录" }
]
}
}
Sampling(采样)
Sampling 是 Client 允许 Server 请求大模型生成文本的能力。这是一种"反向调用"——Server 向 Client 请求 LLM 推理,而不是 Client 向 Server 请求工具执行。
⚠️ 安全约束(务必遵守):Sampling 必须经过用户确认。Server 不能直接调用 LLM,它只能向 Client 发起请求,由 Client 决定是否执行以及用什么模型执行。
传输层
MCP 支持两种传输方式:
| 传输方式 | 适用场景 | 通信机制 | 部署方式 |
|---|---|---|---|
| stdio | 本地开发、IDE 集成 | 标准输入/输出 | 子进程 |
| Streamable HTTP | 远程部署、生产环境 | HTTP POST + SSE 流 | 独立服务 |
stdio 传输
stdio 是最简单的传输方式——Server 作为 Host 的子进程运行,通过标准输入/输出交换 JSON-RPC 消息。
Python:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-server")
# ... 注册工具、资源、提示词 ...
if __name__ == "__main__":
mcp.run(transport="stdio")
TypeScript:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new McpServer({ name: "my-server", version: "1.0.0" });
// ... 注册工具 ...
const transport = new StdioServerTransport();
await server.connect(transport);
Streamable HTTP 传输
Streamable HTTP 用于远程部署场景。Server 作为独立的 HTTP 服务运行,Client 通过 HTTP POST 请求发送 JSON-RPC 消息,Server 通过 SSE(Server-Sent Events)流式返回响应。
这是 2025-03 版规范引入的,取代了旧的 HTTP+SSE 双端点方案。自 2025-11-25 规范起进一步无状态化——每个 HTTP 请求都是独立的,Server 不需要维护会话状态。
Python(FastMCP):
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("my-server")
# ... 注册工具 ...
if __name__ == "__main__":
mcp.run(transport="streamable-http", host="0.0.0.0", port=8000)
TypeScript:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
import express from "express";
const app = express();
app.use(express.json()); // 必须:解析 POST 的 JSON 请求体,否则 transport.handleRequest 收不到内容
const server = new McpServer({ name: "my-server", version: "1.0.0" });
// ... 注册工具 ...
app.post("/mcp", async (req, res) => {
const transport = new StreamableHTTPServerTransport({ sessionIdGenerator: undefined });
await server.connect(transport);
await transport.handleRequest(req, res);
});
app.listen(3000);
传输选择指南
| 场景 | 推荐传输 | 原因 |
|---|---|---|
| IDE 插件、本地工具 | stdio | 简单、零配置、进程隔离 |
| 远程 MCP Server 服务 | Streamable HTTP | 可远程访问、支持负载均衡 |
| 内网企业部署 | stdio | 数据不出本机,安全性高 |
| 公开 API 服务 | Streamable HTTP | 支持多客户端、可扩展 |
| 开发调试 | stdio | 快速启动、日志直接输出 |
安全机制
核心安全原则
MCP 的安全设计遵循"最小权限 + 显式同意"原则:
- Server 只暴露必要的工具。不要把数据库的写权限暴露给一个只读查询的 Agent。
- 用户必须确认敏感操作。工具调用前,Host 应弹窗让用户确认(尤其是有副作用的操作)。
- Server 不信任 Client 的输入。所有输入参数必须验证。
身份验证
HTTP 传输(推荐 OAuth 2.1):
近年规范持续强化了对生产环境 OAuth 2.0 与 OIDC 部署的适配。MCP 服务器可以直接连接 Entra(微软企业身份管理)或 Okta 等企业身份系统,无需变通方案。
stdio 传输(环境变量):
stdio 传输不需要协议级身份验证——Server 作为 Host 的子进程运行,信任边界在操作系统层面。敏感信息通过环境变量传递:
import os
api_key = os.environ.get("MY_API_KEY")
if not api_key:
raise ValueError("缺少 MY_API_KEY 环境变量")
输入验证
所有工具参数都应该验证。FastMCP 基于 Pydantic v2 自动验证类型,TypeScript SDK 用 Zod 做同样的事:
from pydantic import BaseModel, Field
class SearchParams(BaseModel):
query: str = Field(..., min_length=1, max_length=500, description="搜索关键词")
limit: int = Field(default=10, ge=1, le=100, description="返回结果数量")
@mcp.tool()
def search(params: SearchParams) -> str:
"""搜索知识库"""
# FastMCP 自动验证参数,到这里 params 已经是合法的
return f"搜索 {params.query},返回 {params.limit} 条结果"
安全检查清单
- Server 只暴露必要的工具和资源
- 所有工具参数有类型验证和范围约束
- 敏感操作需要用户确认
- API Key 通过环境变量传递,不硬编码
- HTTP 传输启用 OAuth 2.1 身份验证
- 工具调用有日志审计
- Server 运行在最小权限的沙箱环境中
- 定期审查暴露的工具列表
Python SDK 开发指南
安装
pip install mcp
完整服务器示例
from mcp.server.fastmcp import FastMCP
from pydantic import BaseModel, Field
import os
import json
mcp = FastMCP("knowledge-base-server")
# --- Tool:搜索知识库 ---
class SearchParams(BaseModel):
query: str = Field(..., min_length=1, max_length=500, description="搜索关键词")
limit: int = Field(default=10, ge=1, le=100, description="返回数量")
@mcp.tool()
def search_knowledge(params: SearchParams) -> str:
"""搜索内部知识库,返回相关文档摘要"""
# 实际项目中连接向量数据库或搜索引擎
results = [
{"title": "MCP 入门指南", "score": 0.95},
{"title": "FastMCP 最佳实践", "score": 0.88},
]
return json.dumps(results, ensure_ascii=False)
# --- Resource:暴露配置 ---
@mcp.resource("config://server-info")
def get_server_info() -> str:
"""返回服务器配置信息"""
return json.dumps({
"name": "knowledge-base-server",
"version": "1.0.0",
"tools": ["search_knowledge"],
}, ensure_ascii=False)
# --- Prompt:代码审查模板 ---
@mcp.prompt()
def review_code(language: str, code: str) -> str:
"""生成代码审查提示词"""
return f"请审查以下 {language} 代码的安全性和性能:\n\n{code}"
# --- 生命周期管理 ---
@mcp.tool()
def health_check() -> str:
"""健康检查"""
return "OK"
if __name__ == "__main__":
# stdio 模式(本地开发)
mcp.run(transport="stdio")
# HTTP 模式(远程部署)
# mcp.run(transport="streamable-http", host="0.0.0.0", port=8000)
上下文注入
FastMCP 支持在工具函数中注入 Context 对象,用于日志记录、进度报告和资源访问:
from mcp.server.fastmcp import FastMCP, Context
mcp = FastMCP("my-server")
@mcp.tool()
async def long_task(query: str, ctx: Context) -> str:
"""长时间运行的任务,带进度报告"""
await ctx.report_progress(0, "开始处理")
# 模拟处理步骤
for i in range(1, 4):
await ctx.report_progress(i / 3, f"步骤 {i}/3")
# ... 实际处理逻辑 ...
await ctx.report_progress(1.0, "处理完成")
return f"查询 {query} 的结果"
Pydantic v2 关键要点
FastMCP 基于 Pydantic v2 做参数验证,注意以下变化:
- 用
Field(...)做参数约束,不用Field(...)的旧写法 BaseModel.model_validate()替代了BaseModel.parse_obj()- 类型提示直接写在函数签名里,FastMCP 自动提取 schema
TypeScript SDK 开发指南
安装
npm install @modelcontextprotocol/sdk
完整服务器示例
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "knowledge-base-server",
version: "1.0.0",
});
// --- Tool:搜索知识库 ---
server.tool(
"search_knowledge",
{
query: z.string().min(1).max(500).describe("搜索关键词"),
limit: z.number().int().min(1).max(100).default(10).describe("返回数量"),
},
async (params) => {
const results = [
{ title: "MCP 入门指南", score: 0.95 },
{ title: "TypeScript SDK 实战", score: 0.88 },
];
return {
content: [{ type: "text", text: JSON.stringify(results) }],
};
}
);
// --- Resource ---
server.resource(
"server-info",
"config://server-info",
async () => ({
contents: [{
uri: "config://server-info",
text: JSON.stringify({
name: "knowledge-base-server",
version: "1.0.0",
}),
}],
})
);
// --- 启动 ---
const transport = new StdioServerTransport();
await server.connect(transport);
Zod 验证关键要点
TypeScript SDK 用 Zod 做参数验证(对应 Python 的 Pydantic):
z.string()/z.number()/z.boolean()定义基本类型.min()/.max()/.describe()添加约束和描述- Schema 定义直接内联在
server.tool()参数里,不需要单独的类
最佳实践
命名规范
| 原语 | 命名风格 | 示例 |
|---|---|---|
| Tool 名称 | snake_case | get_weather、create_ticket |
| Resource URI | scheme://path |
db://tables/users、file:///path |
| Prompt 名称 | snake_case | code_review、weekly_report |
| 工具描述 | 清晰准确,说明用途和参数 | 查询指定城市的当前天气 |
响应格式
工具返回的内容用 content 数组包装,每项有 type 字段:
# 正常返回:直接返回字符串,FastMCP 会自动包装为 content
return "结果文本"
# 返回错误:抛出业务异常,FastMCP 会将其转为 isError(不要返回原始异常堆栈)
raise ValueError("查询失败:数据库不可用")
# 底层协议说明:MCP 在网络层确实以 {"content": [...], "isError": true} 形态传输,
# 但用 FastMCP 的 @mcp.tool() 装饰器时无需手写该结构,框架自动处理。
分页处理
工具返回大量数据时应该支持分页:
@mcp.tool()
def list_items(cursor: str = None, limit: int = 20) -> str:
"""列出项目,支持分页
Args:
cursor: 上一页返回的游标,首次请求不传
limit: 每页数量
"""
all_items = load_all_items() # 实际从数据库加载
start = int(cursor) if cursor else 0
page = all_items[start:start + limit]
next_cursor = str(start + limit) if start + limit < len(all_items) else None
return json.dumps({
"items": page,
"next_cursor": next_cursor,
}, ensure_ascii=False)
错误处理
@mcp.tool()
def risky_operation(param: str) -> str:
"""有风险的操作"""
try:
result = do_something(param)
return result
except ValueError as e:
# 参数错误
return f"参数错误:{e}"
except PermissionError as e:
# 权限不足
return f"权限不足:{e}"
except Exception as e:
# ⚠️ 未知错误:不要暴露内部堆栈细节,返回友好提示
return f"操作失败,请稍后重试"
代码复用原则
- 一个 MCP Server 只负责一个领域(如数据库查询、文件管理、消息发送)
- 通用功能抽成独立函数,不要在多个工具里复制粘贴
- 配置通过环境变量传入,不要硬编码
生态与社区
官方 MCP 服务器
Anthropic 和社区维护了一批官方 MCP Server,覆盖常见场景:
| Server 名称 | 功能 | 仓库 |
|---|---|---|
| filesystem | 文件系统读写 | modelcontextprotocol/servers |
| git | Git 操作 | modelcontextprotocol/servers |
| github | GitHub API | modelcontextprotocol/servers |
| postgres | PostgreSQL 查询 | modelcontextprotocol/servers |
| sqlite | SQLite 查询 | modelcontextprotocol/servers |
| slack | Slack 消息 | modelcontextprotocol/servers |
| google-drive | Google Drive | modelcontextprotocol/servers |
| puppeteer | 浏览器自动化 | modelcontextprotocol/servers |
| brave-search | Brave 搜索 | modelcontextprotocol/servers |
| fetch | HTTP 请求 | modelcontextprotocol/servers |
MCP 客户端
以下应用原生支持 MCP 协议,可以直接连接 MCP Server:
| 客户端 | 类型 | MCP 支持 |
|---|---|---|
| Claude Desktop | 桌面 AI 助手 | 原生支持 |
| ChatGPT | AI 对话平台 | 原生支持 |
| Cursor | AI IDE | 原生支持 |
| VS Code(GitHub Copilot) | IDE | 原生支持 |
| Cline | VS Code 插件 | 原生支持 |
| Windsurf | AI IDE | 原生支持 |
| LangChain | Agent 框架 | 原生支持 |
| LlamaIndex | Agent 框架 | 原生支持 |
协议版本历史
| 版本 | 日期 | 关键变化 |
|---|---|---|
| 初始版本 | 2024-11-25 | Anthropic 开源 MCP |
| 2025-03 | 2025-03 | 引入 Streamable HTTP,取代旧 HTTP+SSE |
| 2025-11-25 | 2025-11-25 | 重大更新:无状态化、移除 Session ID、server identity、异步操作 |
| 1.0 稳定阶段 | 2026 年 | MCP 走向 1.0 稳定,生态持续扩大(具体以官方公告为准) |
排坑表
| 坑 | 现象 | 原因 | 解决 |
|---|---|---|---|
| Server 连不上 | Client 报 connection refused | Server 未启动或端口被占用 | 检查 Server 进程和端口;stdio 模式检查命令路径 |
| 工具不显示 | Client 的工具列表为空 | Server 未注册工具或 initialize 未完成 |
确认 @mcp.tool() 装饰器正确使用;检查 initialize 握手 |
| 无状态化迁移报错 | 升级后 Server 连不上 | Session ID 被移除(2025-11-25 规范起),有状态 Server 不兼容 | 移除 Session ID 依赖,改为无状态设计 |
| 工具参数验证失败 | 报 Invalid params | Pydantic / Zod 类型不匹配 | 检查参数类型声明;确保 Client 传的 JSON 类型与 Server 定义一致 |
| stdio 模式无输出 | Server 启动了但 Client 收不到数据 | print() 输出污染了 stdio 通道 | 用 logging 模块记日志,不要用 print();日志输出到 stderr |
| HTTP 模式跨域 | 浏览器报 CORS 错误 | Server 未配置 CORS | 在 HTTP 服务层添加 CORS 中间件 |
| 工具太多上下文爆 | 模型上下文窗口被工具定义占满 | 全部工具 schema 一次性塞进 prompt | 使用渐进发现(Progressive Discovery),按需加载工具定义 |
| OAuth 认证失败 | HTTP 模式报 401 | Token 过期或配置错误 | 检查 OAuth 2.1 配置;确认 Token 有效期和刷新机制 |
| Resource URI 不匹配 | Client 请求资源返回 404 | URI 格式不匹配 Server 注册的模板 | 确认 URI scheme 和 path 格式一致;检查 URI 模板参数 |
| Pydantic v2 不兼容 | 报 BaseModel 错误 | 代码用了 Pydantic v1 的 API | 迁移到 v2:model_validate() 替代 parse_obj(),model_dump() 替代 dict() |
什么情况不该用
简单单工具场景不需要 MCP。 如果你的 Agent 只需要一个工具(比如查天气),直接用 Function Calling 就行。MCP 的价值在于标准化和复用,单个工具引入 MCP 协议纯属过度设计。
对延迟极度敏感的场景要谨慎。 MCP 的 JSON-RPC 通信比直接函数调用多了一层序列化和网络开销。stdio 模式还好,HTTP 模式每个请求都有网络往返。如果要求毫秒级响应,直接内联调用更快。
不需要跨模型复用的项目用不上。 MCP 的核心卖点之一是"一次实现,跨模型通用"。如果你只用一个模型、一个框架,MCP 的标准化收益就不明显——LangChain 的 Tool 抽象已经够用了。
无状态化迁移成本要评估。 自 2025-11-25 规范起,MCP 转向无状态;如果你的 Server 依赖了 Session ID 和有状态连接,迁移到无状态版本需要重构。评估迁移成本和收益后再决定是否升级——旧版本短期内仍然可用。
不要把所有内部 API 都包成 MCP Server。 只把需要被多个 Agent 复用的、有独立业务含义的能力做成 MCP Server。内部的一次性脚本、临时的数据处理逻辑不值得封装。
不要用 MCP 替代普通的 REST API。 MCP 是为 AI Agent 设计的协议,不是通用的 API 网关。如果你的服务只被前端或后端调用、不涉及 AI,用 REST / GraphQL 就行。
更多推荐


所有评论(0)