MCP 协议落地实录:把公司内部系统接给 AI,我踩了 3 个坑
去年 11 月底刷到 Anthropic 发 MCP 那条公告的时候,我正蹲在客户现场改一个破接口,扫了一眼就划过去了,心想又是一个厂商自己造的概念。真正把它捡起来是今年 2 月,我们那个工单系统要接 AI 助手,后端哥们儿被 Function Calling 的胶水代码折磨到开始怀疑人生——27 个接口,每个都要手写一遍参数校验、异常兜底、字段映射,产品改个字段名就得重新发版。那天晚上我把 MCP 的规范打印出来看,看到第 12 页才反应过来:这玩意儿不是给模型用的,是给人用的。
先说清楚它和 Function Calling 到底差在哪
很多人一上来就把这两个东西混着说,其实不是一个层面的东西。Function Calling 是模型厂商定的一套接口约定,你给它一个 JSON Schema,它告诉你“我要调这个函数,参数是这些”,至于这个函数在哪跑、怎么跑、跑完了怎么回来,它不管。MCP 管的是后面这一坨:它是一个走 JSON-RPC 2.0 的进程间协议,客户端(Claude Desktop、Cline、Cursor 这些都行)和 server 之间约定好三种原语——Tools、Resources、Prompts。Tools 大家熟,就是能执行的动作;Resources 我觉得是被严重低估的一个东西,它是只读数据源,可以按 URI 挂上去,比如 postgres://prod/orders/schema 这种,模型直接读,不用你封装成一个假函数;Prompts 就是预置的提示词模板,适合把公司内部那套黑话固化下来。这个设计的好处是,你的 server 是个独立进程,Python 写的、Go 写的、跑在容器里都行,换模型换客户端都不用动它。坏处嘛,后面讲。
坑一:tool 注册太多,模型开始乱点菜
我们第一版是把 27 个接口全注册上去,再加上 filesystem 和 git 两个官方 server,Claude Desktop 里一共 51 个 tool。测的时候问它“帮我查一下张三上周提的那个登录报错的工单”,它给我调了 list_users。我以为是偶发,重试了 6 次,3 次都跑偏。后来把 tool 的 JSON Schema 全 dump 出来数了一下 token,单个 tool 的描述加上参数定义大概 120 到 280 token,51 个就是 8000 上下。也就是说还没开始聊正事,200k 的上下文先没了 4%。更麻烦的是选择空间的膨胀,模型在 51 个长得差不多的函数里挑,跟你在 51 个按钮的面板上找那个对的,难度完全不是一回事。最后砍到 12 个最常用的,把剩下 15 个塞进一个 query_workorder 的泛化 tool 里,用参数区分类型,准确率肉眼可见地上来了。经验值:手上的 tool 超过 20 个就该琢磨合并了,超过 40 个基本就是给自己上强度。
坑二:权限,这才是真正让人头大的地方
stdio 模式下 server 是跑在你本机上的,用的是你本机的凭证。听着挺安全,实际上一旦部署到服务器给全组用,问题就来了。我们的后端同学一开始图省事,配了一个全权限的 service account,测试环境跑了一周我去翻日志,发现它读了三张跟工单八竿子打不着的表——因为那个账号有 db_datareader,模型看见 Resources 里有就顺手读了。这事要搁在审计那边,够写一份报告的。改法也不复杂,按 tool 维度拆权限:查工单的账号只能 select 工单相关的 4 张表,发通知的账号只能调消息网关,每个 server 一个独立凭证。麻烦的是管起来费劲,你得维护一张 tool 到权限的映射表,人一多就乱。顺带说一句,MCP 在 2025 年 3 月 26 号那版规范里加了基于 OAuth 2.1 的授权,方向是对的,但生态没跟上,至少我们用的那几个开源 server 到 5 月还没适配。
坑三:超时和重试,一个能把人熬到半夜的问题
MCP 客户端的默认超时是 60 秒左右(不同客户端不太一样),我们有个报表接口冷启动加查询要跑 40 多秒,赶上数据库慢查询直接超。表现是模型那边显示“工具调用失败”,然后它自己开始编结果,这个最能骗人——你以为它查到了,其实是它脑补的。后来改成异步:拆成 submit_report_job 和 get_report_result 两个 tool,前者立刻返回一个 job_id,后者轮询,间隔 3 秒,最多 20 次。另外给所有写操作的 tool 加了幂等键,参数里塞一个 idempotency_key,服务端用 Redis 存 10 分钟。这个不是 MCP 要求的,是血的教训——模型重试起来是没有节制的。
具体怎么搭,三步
第一步,环境。Node 20 以上或者 Python 3.10 以上,Python 的话 pip install mcp,新版本里有 FastMCP,写起来比原始 SDK 舒服不少。第二步,写 server,基本就是一个装饰器的事:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("workorder")
@mcp.tool()
def get_workorder(order_id: str) -> dict:
"""根据工单号查详情"""
...
第三步,配置客户端。macOS 是 ~/Library/Application Support/Claude/claude_desktop_config.json,Windows 是 %APPDATA%\Claude\claude_desktop_config.json,内容大概长这样:
{
"mcpServers": {
"workorder": {
"command": "python",
"args": ["/path/to/server.py"],
"env": {"API_TOKEN": "xxx"}
}
}
}
改完重启客户端。调的时候用 npx @modelcontextprotocol/inspector 起一个可视化面板,比看日志快多了,能直接看到每个 tool 的实际入参出参,我强烈建议先用它,能省掉至少两个小时的怀疑人生。
我的独立观点:MCP 没解决真正难的那部分
圈里现在把 MCP 吹得有点像万能插头,但我觉得它的价值不在“连接”,而在于把权限边界从代码里挪到了配置里——这个是真进步,以前权限是嵌在业务逻辑里的,现在好歹能拉出来单独看。可企业落地真正卡脖子的不是协议,是身份。谁在调用?用的是人的身份还是服务的身份?人离职了 token 怎么回收?审计日志里记的是“某个 MCP client 调了某个 tool”,还是“张三在 3 点 14 分通过 AI 助手查了李四的工单”?这些问题协议层给不了答案,得靠你自己的 IAM 兜。我们内部现在是服务账号加网关做二次鉴权,客户端传一个上游的用户 token,网关那边解出来映射成权限,多绕一层但踏实。代价是延迟多了大概 120 毫秒,能接受。
要不要上,给个粗糙的判断标准
如果你只是自己折腾,接几个公开 API 玩玩,那 MCP 的收益其实一般,直接 Function Calling 更快。如果是团队场景,接口超过 10 个、还要给非技术同事用、未来可能换模型或者换客户端,那 MCP 值得折腾。最不划算的是那种“接口就 3 个、一年改两次”的项目,为了 MCP 搭一套 server 和权限,纯属给自己找事。我们这次前后大概花了 3 周多,两个人,其中真正的编码时间不到 5 天,剩下的全耗在权限梳理和跟安全那边扯皮上。要是有人跟我说这东西“半小时就能跑通”,我信,demo 确实半小时。生产环境是另一回事。