ARTICLE · 1153927
MCP 凭什么统一 AI 工具生态?附手写 Server 教程
点击上方「蓝字」关注我们,每周一篇 AI 工程实战
原创 · AI 工程笔记 · 系列第 3 篇 · 预计阅读 14 分钟
导读:前两篇我们讲了 Agent 的架构和评测。这一篇聊工具——Agent 的手脚。过去两年,这个领域最大的变化不是某个新工具,而是 MCP 这个协议:它把"N 个模型对接 M 个工具"的适配地狱,变成了一次性接入。这篇讲清它的设计逻辑,带你手写一个能跑的 Server,并解读 2026 年 7 月那次让协议彻底改变的大改版。
一、为什么需要 MCP:N×M 问题
假设你有 3 个 AI 应用(IDE 插件、内部 Agent、聊天客户端),要接 5 个工具(文件系统、数据库、GitHub、Jira、内部 API)。
没有统一协议时,你需要写 3 × 5 = 15 套适配代码。而且这 15 套里,鉴权方式、错误处理、参数格式各不相同,任何一方升级都要跟着改。
有 MCP 之后:每个应用实现一次 MCP 客户端(3 份),每个工具实现一次 MCP 服务端(5 份),总共 8 份。而且新加一个工具,3 个应用全都自动能用。
MCP 的价值就一句话:它是 AI 工具生态的 USB-C。工具方实现一次,所有支持 MCP 的宿主都能用;宿主实现一次,能接入整个生态。
这个类比不是修辞。USB-C 的意义不在于"接口更小",而在于它把接口的复杂度,从每对设备之间转移到了一个公共标准上。MCP 做的事一模一样。
二、三个角色:Host、Client、Server
讲 MCP 架构的图到处都是,但最常见的错误是把 Client 理解成"客户端软件"。实际不是。
· Host(宿主) 用户直接面对的那个 AI 应用。Claude Desktop、Cursor、你自己写的 Agent 运行时,都是 Host。它负责调模型、管上下文、渲染结果、做用户审批。
· Client(客户端) Host 内部的连接器。一个 Client 只连一个 Server,它是协议层的存在,不是一个独立软件。
· Server(服务端) 一个进程,把某类能力暴露出来。它可能是本地跑的子进程,也可能是远端 HTTP 服务。
关键点在这句:Host 里可以有多个 Client,一个 Client 对应一个 Server。所以你看到"Claude 连了 5 个 MCP Server",实际上是宿主里跑着 5 个 Client 实例。
还有一个容易被忽略的事实:MCP 是双向的。Server 不只是被动响应,Client 也能反过来给 Server 提供能力:
· Sampling Server 可以请求宿主帮它调一次大模型(Server 自己不持有模型凭证)
· Roots Client 告诉 Server"你的文件访问边界在这儿"
· Elicitation Server 需要额外信息时,可以请求宿主向用户提问
这三条是 MCP 和"普通 Function Calling"最本质的差别:它不是一组无状态的函数调用,而是一套有边界的双向协议。
三、三种原语:别把什么都做成 Tool
Server 对外暴露的能力分三类。这三个概念最容易混,但有一条极简的判断标准——看谁控制。
· Tools 是"做事情" 模型决定什么时候调,所以有副作用,所以需要审批和权限控制。
· Resources 是"给上下文" 注意控制权在应用手里,不在模型手里。这正是它的意义——数据进入上下文这件事应该由人决定,而不是模型自己伸手去拿。
· Prompts 是"工作流" 把"怎么用好这些工具"固化下来。生产环境里用得最少,但它是 Server 作者表达使用约定的地方。
两个常见的设计错误:
1. 把一切读取都做成 Tool。 因为 Function Calling 熟,习惯性什么都包成函数。但如果客户端只是需要一份文档、一个配置当上下文,用 Resource 更干净——而且不需要模型花一次调用去拿。
2. 把有副作用的操作藏在 Resource 后面。 Resource 语义是只读的,宿主不会对它做审批。你把"删除记录"包装成看起来无害的资源,等于绕过了所有安全设计。
判断准则一句话:Tools 是动作,Resources 是上下文,Prompts 是模板。按"谁来决定这件事发生"来选。
四、协议长什么样:JSON-RPC + 两种传输
MCP 的消息格式是 JSON-RPC 2.0——一个极简、成熟、所有语言都有现成库的 RPC 协议。这也是它推广快的原因之一:不用造新轮子。
选择逻辑很直接:Server 跑在用户机器上 → stdio;Server 要部署成服务 → Streamable HTTP。
早期版本(2024-11)用的是 HTTP + SSE 双端点方案,已经被 Streamable HTTP 取代,新项目不用再考虑。
注意倒数第二步——MCP 工具最终还是要被翻译成模型原生的 Function Calling 格式。MCP 标准化的不是"模型怎么调工具",而是"工具怎么被描述和调用"。这条边界想清楚,很多困惑就没有了。
五、2026 年 7 月:MCP 变成无状态了
这是协议发布以来最大的一次改版,也是这篇最需要更新的部分。如果你看的还是老教程,这一节请重点看。
改版前(截至 2025-11-25 版本):连接要先握手。客户端发 initialize 协商版本和能力,服务端回 initialized,然后产生一个 Mcp-Session-Id,后续所有请求都要带上它。
改版后(2026-07-28 版本):握手没了,Session ID 也没了。每个请求自带它需要的全部上下文。
为什么这件事重要?一句话:无状态意味着它可以躺在任何普通负载均衡后面。
以前你要为 MCP 服务端做 sticky session——用户第一次请求打到 A 机器,后续必须还打 A 机器,否则会话状态就丢了。这在云原生环境里是个很别扭的要求。现在任何实例都能处理任何请求,扩容、滚动更新、灰度发布,全都变成标准操作。
对服务端作者的实际影响:别再在连接上存状态。如果你的工具需要在多次调用之间记住东西(比如"先开一个事务,后面几步都在里面"),正确做法是服务端生成一个 handle 返回给客户端,客户端下次把它当普通参数传回来,而不是挂在连接上。
兼容性不用担心:老版本客户端仍然能用,官方 SDK 也支持自动探测和回退。但如果你在写新 Server,按 2026-07-28 版本设计是不会错的。
六、动手:写一个能跑的最小 Server
用官方 Python SDK(pip install mcp),核心代码不到 30 行:
from mcp.server.fastmcp import FastMCP import json mcp = FastMCP("demo-server") @mcp.tool() def get_weather(city: str) -> str: """查询指定城市的天气。 Args: city: 城市名,例如 "杭州" """ return fetch_weather_api(city) @mcp.resource("config://app") def app_config() -> str: """当前应用的配置,供模型作为上下文读取""" return json.dumps(load_config(), ensure_ascii=False) if __name__ == "__main__": mcp.run() # 默认 stdio 传输就这么多。装饰器把普通函数注册成 Tool / Resource,函数签名自动变成参数 schema,docstring 自动变成工具描述。
两点提醒:
① docstring 不是注释,是 prompt。 模型只能靠这段文字判断什么时候该调这个工具。写清楚"做什么、什么场景用、参数什么含义",效果差别很大。
② 跑起来只是第一步,还得注册到宿主。
{ "mcpServers": { "demo": { "command": "python", "args": ["/path/to/server.py"] } } }重启宿主,工具就出现在列表里了。要部署成远端服务,mcp.run(transport="streamable-http") 换个传输方式即可。
七、五条设计经验
01 工具粒度:一个工具干一件完整的事。 太细(read_file + parse_json + filter)会让模型在编排上消耗大量步数;太粗(do_everything)会让参数变成一坨没法校验的 JSON。按"用户意图"切分,不是按"代码函数"切分。
02 描述即提示词。 模型看不见你的实现,只看得见 name 和 description。把边界条件写进描述里——"仅支持 30 天内的订单"这类约束不写,模型就会瞎调。
03 错误要能纠正。 返回"city 必须是 北京/上海/杭州 之一"远好过"Invalid input"。错误信息是给模型看的,不是给日志看的。
04 权限最小化。 Server 是带着你给的凭证在跑。一个只读报表工具不需要写权限,一个查天气的 Server 不需要你的文件系统。凭证能分开就分开,能只读就别给写。
05 装 Server 要像加依赖,不像装 App。 恶意工具描述、仿冒包、过宽的权限范围,都是真实存在的攻击面。接入前看一眼它的代码或来源,比事后排查便宜得多。
八、什么时候不该用 MCP
说了这么多好处,也得说反面。这几种情况,MCP 是过度设计:
· 你只有一两个自己家的工具,而且工具和 Agent 是同一个团队、同一个生命周期一起演进——直接写原生 Function Calling 更省事,少一层协议成本。
· 延迟敏感路径。多一层进程间通信和协议转换,就是多一份延迟。
· 运行时已深度绑定某套原生工具模型,迁移成本远大于收益。
MCP 解决的是多对多的集成复杂度问题。如果你的世界本来就是一对一,它带来的收益很有限。
写在最后
MCP 值得学,不是因为它是当下的热点,而是因为它的位置足够底层。
模型几个月换一茬,客户端来来去去,工具背后的服务也在不断变。而协议作为中间层,让这些东西可以各自演进、互不牵制。这也是为什么 2026 年 7 月那次改版、以及更早把它捐给 Linux 基金会,都是同一件事的两面:它在往基础设施的方向走,而不是某个厂商的功能。
工具描述怎么写、权限边界画在哪、什么时候该把能力拆开——这些判断,比记住协议版本号是哪天发布的要重要得多。
下一篇,我们聊聊 Agent 的上下文工程:为什么窗口越做越大,Agent 反而更容易犯迷糊。
如果这篇对你有帮助,点个「赞」和「在看」
转给正在折腾 Agent 的朋友 👇
关注我们,每周一篇 AI 工程实战
让每一次技术投入,都算数