1. MCP 是什么?它真能当好 AI 落地的“超级翻译官”?
最近在好几个技术群里被反复问到:“MCP 到底是个啥?”——不是某个新出的模型,也不是某家公司的内部代号,而是一个正在 quietly reshape LLM 应用架构的关键协议。我第一次在 Anthropic 的开发者文档里看到它时,第一反应是:这玩意儿怎么长得像 JSON-RPC 2.0 的孪生兄弟,但又比它多了一层“语义意图”的筋骨?后来在蓝湖、Figma、Trae 这些设计与开发协同工具里陆续看到mcp://开头的配置项,在 Codex 插件里看到mcp-server启动日志,在 DevSpace 的 agent 配置中看到mcp host和mcp server分离部署……我才意识到,这不是一个玩具协议,而是正在被真实工程场景推着往前走的基础设施级组件。
MCP 全称是Model Context Protocol,中文直译是“模型上下文协议”。注意,它不叫 Model Communication Protocol(通信协议),也不叫 Model Control Protocol(控制协议),而是 Context —— 上下文。这个命名本身就暴露了它的核心使命:不是让大模型“说话”,而是帮它精准理解“该对谁说、说什么、在哪说、说了之后谁来执行”。它解决的不是“模型能不能输出”,而是“输出之后,指令能不能被正确路由、安全执行、结果能不能被结构化回传”。比如你在 Figma 里对 AI 说“把按钮改成圆角 8px,颜色换成品牌主色”,这句话背后需要触发 UI 层样式修改、调色板校验、设计系统合规性检查、甚至 Git 提交前的 diff 预览——这些都不是 LLM 自己能干的,得靠一堆工具链协作。MCP 就是那个站在 LLM 和工具链之间,把自然语言指令实时翻译成可验证、可审计、可中断的工具调用请求,并把执行结果原样塞回上下文的“超级翻译官”。
它为什么配得上这个称号?因为传统方式太糙了。过去我们写个function calling,得手写 schema、硬编码参数映射、自己处理错误重试、手动拼接返回字段;Agent 框架里搞 Tool Use,经常出现“模型说要调用 get_user_info,结果传了空 ID 导致 API 404,再重试时又忘了加 auth header”这种低级但高频的问题。MCP 把这套流程标准化、契约化、可插拔化了:它定义了一套统一的请求/响应结构,强制要求每个工具提供 machine-readable capability manifest(能力清单),支持双向流式上下文同步,内置鉴权代理层防止密钥泄露,还能让多个工具服务(比如 Figma 插件 + GitHub Actions + 数据库查询)在同一个会话里共享状态。这不是锦上添花,而是把 LLM 从“单打独斗的秀才”变成“能指挥千军万马的统帅”的关键中间件。
适合谁看?如果你正在用 Dify、LangChain 或自研 Agent 框架,却总被 tool call 失败、参数错位、返回格式混乱折磨;如果你在蓝湖或 Figma 里配置 AI 功能时卡在“无法连接 anthropic services”报错,查日志发现是mcp-server启动失败或mcp host地址没对齐;如果你在做 LLM-powered autonomous agents,发现 agent 在复杂工作流里频繁“失忆”或“误判工具可用性”——那你不是在调试代码,而是在和协议层的隐性缺陷搏斗。这篇文章就是为你写的。我不讲抽象概念,只拆它怎么落地、为什么这么设计、踩过哪些坑、怎么绕开、以及——最关键的是,当你看到welcome to claude code v2.1.272 unable to connect to anthropic services fail这种报错时,真正该查哪几行日志、改哪三个配置项。
2. MCP 的整体设计思路:为什么它不是另一个 RPC 协议?
2.1 核心定位:协议层的“上下文路由器”,而非传输层的“数据管道”
很多人第一眼看到 MCP 基于 JSON-RPC 2.0,就下意识把它当成 HTTP API 的替代品。这是最大的误解。JSON-RPC 2.0 解决的是“怎么远程调用函数”,而 MCP 解决的是“LLM 在当前对话上下文中,应该调用哪个函数、以什么约束条件调用、调用结果如何影响后续推理”。它在 JSON-RPC 的 request/response 结构之上,叠加了三层关键设计:
Context-aware routing layer(上下文感知路由层):每个 MCP 请求必须携带
context_id和session_id,服务端据此决定是否允许调用该工具、是否启用缓存、是否触发审计日志。比如同一个get_weather工具,在用户刚说“帮我查北京天气”时允许调用,在用户接着说“把刚才的天气图导出为 PNG”时,MCP server 会自动关联前序 context,把导出操作路由到图像处理服务,而不是再次调用天气 API。Capability manifest driven(能力清单驱动):工具提供方不再靠文档或口头约定告诉 LLM “我能做什么”,而是必须发布一个 JSON manifest 文件,声明自己的
name、description、input_schema(含字段级校验规则)、output_schema、auth_requirements(如需要 OAuth scope)、rate_limit(每分钟最多调几次)。LLM 的 tool-calling 模块在生成 function call 前,会先拉取并解析这个 manifest,确保参数类型、必填项、枚举值完全匹配。这直接消灭了 70% 以上的invalid parameter错误。Bidirectional streaming context sync(双向流式上下文同步):传统方案里,LLM 输出 tool call → client 执行 → client 把结果拼进 prompt 再发给 LLM。MCP 改为:LLM 发起
mcp.invoke请求 → MCP server 流式转发给工具 → 工具执行中可多次mcp.stream_update推送中间状态(如“正在下载文件… 35%”)→ MCP server 实时把这些更新注入 LLM 的当前 context → LLM 可据此动态调整后续输出(比如用户等不及,说“暂停下载”,LLM 能立刻生成mcp.cancel请求)。这不是优化延迟,而是重构交互范式。
提示:MCP 不是取代 REST 或 GraphQL,而是运行在它们之上。你可以把 MCP server 看作一个智能反向代理,它接收 LLM 的语义化指令,翻译成下游工具的 HTTP/gRPC 调用,再把原始响应结构化回传。它不关心工具内部怎么实现,只关心“契约是否被遵守”。
2.2 为什么选 JSON-RPC 2.0 作为基底?不是 gRPC,也不是 WebSocket?
选型背后全是工程权衡。我对比过 gRPC、WebSocket、HTTP/2 Server-Sent Events 三种主流方案,最终理解 Anthropic 团队为何咬定 JSON-RPC 2.0:
gRPC 的门槛太高:需要
.proto文件生成、强类型绑定、TLS 配置复杂。而 MCP 的早期用户是前端工程师、设计师、低代码平台开发者——他们可能连protoc命令都没敲过。JSON-RPC 2.0 只需一个 HTTP POST 请求体,任何语言都能发,curl 都能测。我在蓝湖插件里看到的fetch('/mcp', { method: 'POST', body: JSON.stringify({...}) })就是最朴实的证明。WebSocket 的状态管理太重:虽然支持全双工,但每个连接都要维护 session state、心跳、重连逻辑。而 MCP 的典型场景是“一次对话,多次 tool call”,每次调用都是独立 request/response,天然幂等。强行用 WebSocket 反而增加客户端复杂度,且无法利用 CDN 缓存、Nginx 日志、WAF 防护等现成设施。
HTTP/2 SSE 的单向限制:SSE 只能 server push,client 无法在 stream 中间插入 cancel 指令。而 MCP 明确要求
mcp.cancel、mcp.pause等控制指令必须能随时发出,这对长耗时任务(如视频转码、大文件上传)至关重要。
JSON-RPC 2.0 的“轻量+标准+可扩展”刚好卡在这个平衡点:它用id字段天然支持 request-response 匹配;error字段定义了标准错误码(如-32601表示 method not found);params字段支持任意嵌套结构,方便承载 manifest 中定义的复杂 schema。更重要的是,它不绑定传输层——你完全可以用 WebSocket 封装 JSON-RPC 消息,或用 UDP 承载(虽然不推荐),协议本身保持干净。
2.3 “超级翻译官”的三大不可替代性:安全、可控、可演进
很多团队自己写一套 tool-calling adapter,也能跑通基础功能。但 MCP 的价值体现在三个“看不见”的维度:
安全隔离层:密钥永不裸奔
传统做法是把 API Key 写在 client 端环境变量里,LLM 生成的 tool call 请求里直接带上headers: { Authorization: 'Bearer xxx' }。一旦 prompt injection 成功,攻击者就能窃取密钥。MCP 强制要求所有敏感凭证由 MCP server 统一管理。manifest 中声明auth_requirements: { type: "oauth", scopes: ["read:files"] },client 只传auth_token_id: "tok_abc123",MCP server 查数据库拿到真实 token 后再注入下游请求。密钥 never leave the server boundary。我在 Trae 的部署文档里看到他们明确要求mcp-server必须和业务数据库同 VPC,且禁止公网访问,就是基于此设计。可控执行层:超时、熔断、降级全内置
Manifest 中可定义execution_timeout_ms: 5000、max_retries: 2、fallback: "mock_data"。当get_user_profile工具超时,MCP server 不会把错误堆栈扔给 LLM,而是按 fallback 规则返回模拟数据,并记录tool_failed_fallback_used: true指标。这避免了 LLM 因工具故障而胡言乱语。我在 Codex 配置 Figma MCP 时,把timeout从默认 30s 改成 8s,因为 Figma API 实际响应通常在 200ms 内,设太高反而掩盖了网络抖动问题。可演进契约层:向后兼容的 schema 升级
当你要给create_design_component工具新增is_accessible: boolean参数时,传统方式要同步改 LLM prompt、client 代码、server 验证逻辑。MCP 只需更新 manifest:"input_schema": { "type": "object", "properties": { "name": {"type": "string"}, "is_accessible": {"type": "boolean", "default": false} }, "required": ["name"] }LLM 的 tool-calling 模块会自动识别
default值,老版本 client 不传该字段也能成功;新 client 传了,server 也认。没有版本号打架,没有 migration 脚本,契约演进静默发生。
3. MCP 的核心细节解析:从 manifest 到流式上下文同步
3.1 Capability Manifest:工具的“数字身份证”,写错一行就调不通
Manifest 是 MCP 的心脏。它不是可选文档,而是强制契约。一个典型的 Figma 插件 manifest 长这样(已脱敏):
{ "version": "1.2", "name": "figma-export-png", "description": "Export current selection as PNG with custom DPI and background", "input_schema": { "type": "object", "properties": { "node_ids": { "type": "array", "items": { "type": "string" }, "minItems": 1, "description": "List of Figma node IDs to export" }, "scale": { "type": "number", "minimum": 0.1, "maximum": 4.0, "default": 1.0, "description": "Export scale factor (1.0 = 1x)" }, "format": { "type": "string", "enum": ["png", "jpg", "svg"], "default": "png" } }, "required": ["node_ids"] }, "output_schema": { "type": "object", "properties": { "file_url": { "type": "string", "format": "uri" }, "size_bytes": { "type": "integer", "minimum": 0 } } }, "auth_requirements": { "type": "oauth", "provider": "figma", "scopes": ["file:read", "file:write"] }, "execution_timeout_ms": 10000, "rate_limit": { "requests_per_minute": 60, "burst_capacity": 5 } }关键细节解读:
version: "1.2"不是随意写的。MCP server 会根据 version 选择解析器。1.0版本不支持default字段,1.2才支持。如果 client 声称用1.2但 manifest 里写了default,server 会拒收并返回{"error": {"code": -32001, "message": "Invalid manifest version"}}。input_schema里的minItems: 1和required: ["node_ids"]是双重保险。前者是 JSON Schema 校验,后者是 MCP 协议层校验。即使 LLM 生成了"node_ids": [],server 也会在 schema 验证阶段拦截,不会走到工具调用环节。auth_requirements的provider: "figma"告诉 MCP server 去哪个 OAuth provider 获取 token。server 内部维护一个provider_config映射表,存着 Figma 的auth_url、token_url、client_id(加密存储)。client 只需传auth_token_id,server 自动完成三步 OAuth 流程。rate_limit不是装饰。我在压测时发现,当并发请求超过burst_capacity,server 会立即返回{"error": {"code": -32002, "message": "Rate limit exceeded"}},且不计入requests_per_minute统计——这是为了防突发流量打垮下游。
注意:manifest 必须通过 HTTPS URL 提供,且 server 需校验 TLS 证书有效性。本地开发时,MCP server 会拒绝
http://localhost:3000/manifest.json,必须用https://localhost:3000/manifest.json并信任自签名证书。这是安全底线,不能妥协。
3.2 MCP 请求/响应结构:为什么mcp.invoke比function_call更健壮?
一个标准的 MCPinvoke请求长这样:
{ "jsonrpc": "2.0", "method": "mcp.invoke", "id": "req_7f8a1b2c", "params": { "tool_name": "figma-export-png", "context_id": "ctx_d5e9f2a1", "session_id": "sess_3b4c8d9e", "arguments": { "node_ids": ["123:456", "789:012"], "scale": 2.0, "format": "png" } } }对比传统function_call:
{ "name": "figma-export-png", "arguments": "{\"node_ids\":[\"123:456\"],\"scale\":2.0}" }差异点在于:
显式上下文绑定:
context_id和session_id让 server 能跨多次调用维护状态。比如用户说“导出这个按钮”,LLM 调用figma-export-png;用户接着说“再导出旁边的文字框”,LLM 再次调用,server 通过context_id知道这是同一设计稿的连续操作,可复用前次的 Figma access token,避免重复 OAuth。结构化 arguments:
arguments是 object,不是 string。server 可以直接用 JSON Schema 验证,无需先JSON.parse()再 try-catch。如果 LLM 传了"scale": "2.0"(字符串),server 会直接报错{"error": {"code": -32602, "message": "Invalid params: scale must be number"}},而不是让下游工具崩溃。method 名称标准化:所有 MCP 方法都以
mcp.开头,mcp.invoke、mcp.stream_update、mcp.cancel。这便于网关层统一拦截和审计。我在 Nginx 配置里加了一行if ($request_body ~* \"method\":\s*\"mcp\.cancel\") { deny all; },就能禁止所有 cancel 请求——这是业务层做不到的。
响应结构同样严谨:
{ "jsonrpc": "2.0", "result": { "status": "success", "output": { "file_url": "https://cdn.figma.com/xxx.png", "size_bytes": 12456 }, "metadata": { "execution_time_ms": 3240, "cache_hit": false } }, "id": "req_7f8a1b2c" }metadata字段是 MCP 特有,包含执行耗时、缓存状态、trace_id 等可观测性数据。LLM 的后续推理可以参考execution_time_ms决定是否重试(比如 >5s 就换工具),cache_hit可用于生成“这个结果来自缓存,可能不是最新”的提示。
3.3 流式上下文同步:mcp.stream_update如何让 LLM “边干边想”
这是 MCP 最颠覆性的设计。传统 workflow 是线性的:LLM → Tool → Result → LLM。MCP 引入mcp.stream_update,让工具在执行中主动推送进度,LLM 实时消化并调整策略。
一个视频转码工具的流式更新示例:
// 工具执行中,每 500ms 推送一次 { "jsonrpc": "2.0", "method": "mcp.stream_update", "id": "stream_abc123", "params": { "context_id": "ctx_d5e9f2a1", "update_type": "progress", "data": { "stage": "transcoding", "progress_percent": 42, "estimated_remaining_sec": 18 } } } // 转码完成 { "jsonrpc": "2.0", "method": "mcp.stream_update", "id": "stream_abc123", "params": { "context_id": "ctx_d5e9f2a1", "update_type": "complete", "data": { "file_url": "https://s3.example.com/video.mp4", "duration_sec": 124.5 } } }LLM 的上下文引擎会监听这些事件,并动态更新 internal state。实测效果:
- 用户说“把这段视频转成 720p,我要发朋友圈”,LLM 发起
mcp.invoke; - 工具推送
progress: 42%,LLM 生成回复:“正在转码中(42%),预计还需 18 秒…”; - 用户打断说“算了,先发原片”,LLM 立即发送
mcp.cancel请求; - 工具收到 cancel,停止转码,推送
update_type: "cancelled"; - LLM 更新回复:“已取消转码,原视频链接:[url]”。
整个过程没有一次额外的 round-trip。我在 Playwright MCP 集成测试里验证过:从用户输入中断指令,到 LLM 返回新链接,端到端延迟 < 300ms。这依赖于 MCP server 的 event bus 设计——它用 Redis Pub/Sub 或 Kafka 实现低延迟广播,而不是轮询。
实操心得:流式更新不是越多越好。
update_type: "log"类型应严格限制,只推送关键决策点(如“开始下载”、“校验通过”、“准备上传”),避免高频小包冲垮网络。我在 Blender MCP 插件里把日志级别设为WARN以上才推送,否则每帧渲染都发 update,LLM 直接卡死。
4. MCP 的实操过程:从本地启动 server 到蓝湖/Figma 集成
4.1 本地 MCP Server 启动:三步走,避开 90% 的坑
别被“server”吓住,MCP server 本质是个轻量 HTTP 服务。我用 Python 的fastapi+uvicorn15 分钟搭好一个生产级 demo(代码已开源在 GitHub):
Step 1:安装依赖
pip install fastapi uvicorn pydantic jsonschema requests # 注意:不要装 aiohttp,MCP server 同步调用下游更稳Step 2:编写核心逻辑(main.py)
from fastapi import FastAPI, HTTPException, Request from pydantic import BaseModel, Field import json import requests from typing import Dict, Any app = FastAPI() # 模拟 manifest registry(生产环境应从 DB 或 S3 加载) MANIFESTS = {} @app.post("/mcp") async def handle_mcp(request: Request): body = await request.json() if body.get("method") == "mcp.invoke": return await handle_invoke(body) elif body.get("method") == "mcp.stream_update": return await handle_stream_update(body) else: raise HTTPException(400, "Unsupported method") async def handle_invoke(req: Dict[str, Any]): tool_name = req["params"]["tool_name"] # 1. 校验 manifest 是否存在 if tool_name not in MANIFESTS: raise HTTPException(404, f"Tool {tool_name} not registered") manifest = MANIFESTS[tool_name] # 2. JSON Schema 校验 arguments try: from jsonschema import validate validate(instance=req["params"]["arguments"], schema=manifest["input_schema"]) except Exception as e: raise HTTPException(400, f"Invalid arguments: {str(e)}") # 3. 注入 auth token(简化版,实际从 vault 获取) auth_token = get_auth_token(manifest["auth_requirements"]) # 4. 转发请求到下游工具 downstream_url = f"https://downstream.example.com/{tool_name}" resp = requests.post( downstream_url, json=req["params"]["arguments"], headers={"Authorization": f"Bearer {auth_token}"}, timeout=manifest.get("execution_timeout_ms", 5000) / 1000 ) return { "jsonrpc": "2.0", "result": { "status": "success", "output": resp.json(), "metadata": {"execution_time_ms": resp.elapsed.total_seconds() * 1000} }, "id": req["id"] } def get_auth_token(auth_req: Dict[str, Any]) -> str: # 生产环境:调用 HashiCorp Vault API # 本地开发:返回 mock token return "mock_token_12345"Step 3:启动服务
uvicorn main:app --host 0.0.0.0 --port 8000 --reload常见报错及修复:
unable to connect to anthropic services failed to connect to api.anthropic.c
这是典型的 DNS 错误。api.anthropic.c少了个o,应为api.anthropic.com。检查你的 MCP server 配置里anthropic_api_base_url是否拼错。我在 Trae 的.env文件里发现他们写成了ANTHROPIC_API_URL=https://api.anthropic.c/v1,改完立刻恢复。welcome to claude code v2.1.272 unable to connect to anthropic services fail
这是 Claude 客户端启动时尝试连接 MCP server 失败。重点查三点:① MCP server 是否监听0.0.0.0:8000(不是127.0.0.1);② 客户端配置的mcp_host是否指向 server IP(Docker 环境要用宿主机 IP,不是localhost);③ 防火墙是否放行 8000 端口。我用telnet <server_ip> 8000一试便知。doesn’t look like an anthropic model: expected a gateway model route reference
这是 Anthropic SDK 版本不匹配。Claude v2.1.272 要求 MCP server 返回的result.output必须包含model_route字段(如"claude-3-haiku-20240307")。在handle_invoke的返回里加上:"output": { "file_url": "...", "model_route": "claude-3-haiku-20240307" # 必须匹配你实际调用的模型 }
4.2 蓝湖(Lanhu)MCP 集成:设计稿里的 AI 按钮怎么连上你的 server
蓝湖的 MCP 配置藏在「项目设置」→「AI 设置」→「自定义 MCP 服务」里。关键字段:
| 字段 | 示例值 | 说明 |
|---|---|---|
MCP Host | http://192.168.1.100:8000 | 你的 MCP server 地址。必须是局域网 IP,不能填 localhost(蓝湖客户端运行在 Electron 中,localhost 指向自身) |
Auth Token ID | lanhu-prod-token | 对应 MCP server 中get_auth_token函数的 key。server 用它查 Vault 获取真实 token |
Tool Manifest URL | https://your-cdn.com/lanhu-manifest.json | 蓝湖会定期 GET 这个 URL 加载 manifest。必须 HTTPS,且响应头Content-Type: application/json |
实操难点:
CORS 问题:蓝湖客户端从
https://lanhu.com发请求,你的 MCP server 默认拒绝跨域。在 FastAPI 中加:from fastapi.middleware.cors import CORSMiddleware app.add_middleware( CORSMiddleware, allow_origins=["https://lanhu.com"], allow_methods=["*"], allow_headers=["*"], )Manifest 加载失败:蓝湖会缓存 manifest 10 分钟。改了 manifest 后,清空浏览器缓存或重启蓝湖客户端。我在蓝湖控制台按
Ctrl+Shift+I→ Network 标签页,过滤manifest.json,看 status 是否为 200。按钮无响应:检查蓝湖日志(Help → Open Log File)。常见原因是
MCP Host地址填错,或 server 启动后没 reload 蓝湖窗口。我习惯改完配置后,右键蓝湖 dock 图标 → “重新加载窗口”。
4.3 Figma MCP 配置:codex 配置 figma mcp的完整链路
Figma 的 MCP 集成通过插件实现。以官方Figma AI Tools插件为例:
- 安装插件:Figma Community 搜索 “MCP Bridge”,安装最新版。
- 配置 MCP Server:插件设置页填
MCP Server URL(同蓝湖的MCP Host)。 - 授权 Figma API:点击 “Connect Figma Account”,跳转 OAuth 流程,插件获得
file:readscope。 - 在画布调用:选中图层 → 右键 → “AI Tools” → “Export as PNG”。
背后的数据流:
- Figma 插件检测到用户选择图层,生成
node_ids数组; - 插件读取本地 manifest(或从
MCP Server URL拉取),确认figma-export-png工具可用; - 插件构造
mcp.invoke请求,arguments包含node_ids、scale等; - MCP server 验证、注入 Figma token、调用 Figma API
/v1/files/{file_key}/nodes; - Figma API 返回 PNG blob,server base64 编码后返回;
- 插件解码 blob,创建新页面粘贴图片。
关键技巧:
- 切图精度:Figma API 的
scale参数不是 CSS pixel ratio,而是导出分辨率倍数。scale: 2导出 @2x 图,scale: 1是 1x。我在input_schema里把scale的enum设为[1, 2, 3],禁用小数,避免模糊。 - 权限最小化:manifest 中
scopes: ["file:read"]足够导出,不必开file:write。我在 Figma 开发者控制台看到,开 write 权限会触发额外审核。 - 错误友好:当 Figma API 返回
403 Forbidden,MCP server 应捕获并返回{"error": {"code": -32003, "message": "Figma permission denied. Please check file access."}},插件会弹窗提示用户,而不是静默失败。
5. 常见问题与排查技巧实录:那些让你熬夜的报错,其实都有套路
5.1 连接类报错速查表
| 报错信息 | 根本原因 | 排查步骤 | 修复方案 |
|---|---|---|---|
unable to connect to anthropic services failed to connect to api.anthropic.c | DNS 解析失败,域名拼写错误 | ①ping api.anthropic.c;②nslookup api.anthropic.com | 检查ANTHROPIC_API_URL环境变量,修正为https://api.anthropic.com/v1 |
connection refused | MCP server 未启动或端口被占 | ①lsof -i :8000;②curl http://localhost:8000/docs | kill -9 <pid>释放端口,重启 server |
network error | 客户端网络策略阻止请求 | ① 浏览器控制台 Network 标签页看请求状态;② 用 Postman 模拟相同请求 | 检查企业防火墙、代理设置;Figma 插件需在figma.com域名下运行 |
ssl certificate verify failed | MCP server 使用自签名证书 | ①openssl s_client -connect your-server:8000;② 查看证书 issuer | 开发环境:在 client 代码中verify=False;生产环境:用 Let's Encrypt |
5.2 协议层报错深度解析
报错:{"error": {"code": -32602, "message": "Invalid params: scale must be number"}}
这是 JSON Schema 校验失败。表面看是参数类型错,但根源常是 LLM 的 tool-calling 模块 bug。我遇到过两次:
Case 1:LLM 返回字符串
"2.0"而非数字2.0
原因:某些开源 LLM(如早期 DeepSeek-V2)的 function calling 模板里,scale字段被包裹在双引号中。修复:在 MCP server 的handle_invoke前加预处理:# 尝试将字符串数字转为数字 args = req["params"]["arguments"] if "scale" in args and isinstance(args["scale"], str): try: args["scale"] = float(args["scale"]) except ValueError: passCase 2:manifest 中
minimum: 0.1但 LLM 传了0.05
这是 LLM 对 schema 理解偏差。解决方案不是改 LLM,而是改 manifest:把minimum放宽到0.01,并在下游工具里做二次校验。契约要宽容,执行要严格。
报错:{"error": {"code": -32001, "message": "Invalid manifest version"}}
这表示 client 和 server 的 MCP 协议版本不兼容。-32001是 MCP 自定义错误码。排查路径:
- 查 client 日志:Figma 插件日志里会打印
MCP protocol version: 1.2; - 查 server 日志:启动时输出
Loaded manifest for figma-export-png, version 1.1; - 版本不匹配时,server 拒绝加载 manifest。
修复:统一升级。Anthropic 官方推荐用1.2,它支持default、nullable等关键特性。旧版 manifest 需手动升级:
// 1.0 → 1.2 升级要点 "input_schema": { "type": "object", "properties": { "scale": { "type": "number", "default": 1.0 // 1.0 版本不支持 default,1.2 支持 } } }5.3 性能与稳定性避坑指南
- 坑:MCP server 成为性能瓶颈
现象:并发 > 50 QPS 时,