模型上下文协议(MCP):让 Agent 用统一方式连接工具
MCP 解决的是工具集成问题。
如果非要用一个生活类比,它有点像 AI 世界里的 USB-C。USB-C 出现前,手机、相机、电脑各有各的线,出门得带一小包转接头;统一接口之后,设备不一定变聪明了,但连接这件事终于没那么折磨人。
MCP 做的也是这件事:它不负责让模型更会推理,也不替模型决定要不要调用工具。它做的是给 Agent 和外部工具之间定一套通用插口,让客户端能用统一方式发现、连接和调用工具。
没有统一协议时,每个模型或客户端要分别对接每个工具:
GitHub × Claude
GitHub × ChatGPT
Slack × Claude
Slack × ChatGPT
数据库 × IDE Agent
...工具越多、客户端越多,集成复杂度就越高。MCP 的价值在于:工具方按统一协议暴露能力,客户端按统一协议消费能力。它不是 Function Calling 的替代品,而是工具连接层的标准接口。
一、MCP 解决什么问题
先看最核心的变化:从 M×N 集成,变成 M+N 集成。
以前是「工具和客户端互相硬编码」:一个 GitHub 工具要接 Claude、ChatGPT、IDE Agent,可能就有三套胶水代码;一个客户端要接 GitHub、Slack、数据库,又要再写三套。
MCP 把双方中间的接口固定下来。工具侧实现 MCP Server,说明自己有哪些能力;客户端实现 MCP Client,负责连接 Server、读取能力清单、发起调用。两边不用彼此猜格式,升级时也不必每条连接线都重新改一遍。
这就是它最朴素、也最值钱的地方:不是让一个 demo 更酷,而是让工具生态可以复用。
二、MCP 不是什么
很多人第一次听 MCP,会把它理解成「更高级的 Function Calling」。这个说法容易误导,因为它们不在同一层。
| 概念 | 解决的问题 | 和 MCP 的关系 |
|---|---|---|
| Function Calling | 模型是否调用某个函数、如何填参 | 模型层能力 |
| MCP | 工具在哪里、如何发现、如何调用 | 协议层能力 |
| A2A / 多 Agent 协议 | Agent 之间如何协作 | 通信/协作层能力 |
一个简单类比:
Function Calling 像「模型会不会用工具」;
MCP 像「工具用什么标准插口接进来」。两者通常是组合关系。模型可以通过 Function Calling 决定「我要查数据库」,而 MCP 负责告诉客户端「数据库能力在哪个 Server 上、参数是什么、结果怎么返回」。
所以判断一个问题是不是 MCP 的问题,可以问一句:你是在解决「模型怎么思考和选择」,还是在解决「工具怎么被发现和接入」?前者更接近模型调用能力,后者才是 MCP 的地盘。
三、MCP 怎么工作
运行时通常包含四个动作:握手、发现、调用、返回。
这里真正重要的不是「多了一种工具调用格式」,而是能力发现、资源读取、提示词模板和工具调用都进入了统一协议。
Client 不再需要预先写死「这个 Server 一定有某个函数」。它可以先问 Server:你有哪些 tools、resources、prompts?Server 返回结构化清单后,Client 再决定展示给用户、交给模型,或在权限检查后调用。
这也带来一个工程边界:MCP Server 不是一个神奇黑盒,它只是把能力暴露出来。鉴权、权限隔离、审计、危险操作确认,仍然要由 Server、Client 和业务系统一起负责。
四、什么时候该用 MCP
不要为了用协议而用协议。MCP 的收益来自复用、解耦和生态,不来自让一个小 demo 更复杂。
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 只做一个应用,接一两个内部工具 | 原生 Function Calling | 简单直接,成本低 |
| 工具要给多个客户端复用 | MCP | Server 写一次,多处接入 |
| IDE / 桌面 Agent 读本地资源 | MCP Local | 本地进程、低延迟、权限边界更清楚 |
| SaaS 对外开放工具能力 | MCP Remote | 多用户共享,需要鉴权和审计 |
| 高风险写操作 | MCP + 人工确认 | 协议不能替代权限控制 |
一句话判断:如果你的工具只服务一个应用,而且短期不会被别的客户端复用,直接写原生工具调用更省心;如果你希望同一组工具被 IDE、桌面 Agent、内部平台甚至第三方客户端复用,MCP 就开始有价值。
五、三大原语
MCP Server 可以暴露三类能力:Tools、Resources、Prompts。它们最容易被混用,但控制权完全不同。
| 原语 | 谁触发 | 是否可能有副作用 | 适合什么 |
|---|---|---|---|
| Tools | 模型决定调用 | 可能有 | 查询、创建、执行动作 |
| Resources | 应用或用户显式加载 | 通常只读 | 文件、表结构、文档 |
| Prompts | 用户主动触发 | 通常无副作用 | 预设工作流模板 |
这个差别非常关键。Tool 是模型可以选择调用的动作,所以风险最高;Resource 更像一份可读资料,通常应该由应用或用户显式加载;Prompt 则像一个可复用的工作流入口。
如果只是读取表结构,优先做成 Resource,而不是让模型用 Tool 偷偷读。把「只读资料」做成 Tool,看起来省事,实际上是在把控制权交给模型。
经验法则:
会改变外部世界的动作,才适合做 Tool;
只读、需要用户看得见的上下文,优先做 Resource;
可复用的操作模板,放进 Prompt。六、Local 还是 Remote
MCP 常见有两种部署方式,新手最容易在这里选错。
| 方式 | 怎么运行 | 适合场景 | 主要风险 |
|---|---|---|---|
| Local | Client 在本地启动 Server 进程,常见是 stdio | IDE、桌面 Agent、本地文件和命令 | 本机权限过大、配置泄漏 |
| Remote | Server 独立部署,通过网络访问 | SaaS 官方能力、企业共享工具 | 鉴权、多租户隔离、审计 |
自己电脑上用,Local 通常更简单:延迟低,部署轻,权限边界相对直观。要服务一群用户,Remote 才更合适,但它不是「把 Local 搬到云上」这么简单。只要上 Remote,就必须认真处理 OAuth/PKCE、短期 token、细粒度 scope、租户隔离和审计日志。
这里有个常见误区:MCP 统一了协议,不等于统一解决了安全。协议只能规定怎么握手、怎么列能力、怎么调用;至于这个用户有没有权限查这张表、能不能发这封邮件,仍然是业务系统必须兜住的事。
七、最小 Server + Client 示例
下面是一个查数据库的最小骨架:Server 暴露一个只读查询 Tool、一个表结构 Resource、一个 SQL Prompt。
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("demo-db")
DB_SCHEMA = "users(id, name, country)"
@mcp.tool()
def query_database(sql: str) -> str:
"""只读 SQL 查询,仅允许 SELECT。"""
if not sql.strip().lower().startswith("select"):
return "ERROR: only SELECT is allowed"
return "id|name|country\n1|Alice|CN"
@mcp.resource("schema://main")
def get_schema() -> str:
"""数据库表结构。"""
return DB_SCHEMA
@mcp.prompt()
def write_sql_query(question: str) -> str:
"""根据问题生成只读 SQL。"""
return f"参考 schema://main,只写 SELECT 查询。问题:{question}"
if __name__ == "__main__":
mcp.run(transport="stdio")Client 端至少要做三件事:只连接可信 Server、显式读取资源、把工具结果当成不可信数据。
async def run_client(client, question):
tools = await client.list_tools()
resources = await client.list_resources()
prompts = await client.list_prompts()
assert "schema://main" in [item.uri for item in resources]
schema = await client.read_resource("schema://main")
prompt = await client.get_prompt("write_sql_query", {
"question": question,
})
tool_result = await client.call_tool("query_database", {
"sql": "SELECT name FROM users WHERE country = 'CN'",
})
return {
"tools": tools,
"schema": schema,
"prompt": prompt,
"tool_result_untrusted": tool_result,
}这段代码不是完整生产实现,只是协议交互骨架。生产里还要补超时、重试、鉴权、审计日志、权限隔离,以及更严格的 SQL 解析。注意那句 tool_result_untrusted:工具结果是数据,不是指令。
八、安全检查清单
MCP 打开了工具生态,也扩大了攻击面。尤其是第三方 Server:它们看起来像工具,实际上能把内容塞进 Agent 上下文。
| 风险 | 防线 |
|---|---|
| 恶意 Server | 来源白名单、签名或官方发布渠道 |
| 工具描述投毒 | 描述只当数据,加载前审查 |
| 工具返回值注入 | 返回内容标记为 untrusted,不当指令执行 |
| Remote 鉴权混乱 | OAuth/PKCE、短期 token、细粒度 scope |
| 高风险写操作 | 用户确认、最小权限、审计日志 |
| 第三方 Server 失控 | 沙箱、权限隔离、可撤销连接 |
提示词提醒和关键词扫描只能算基础防线,不能替代权限系统。真正可靠的防线要在实现层:Server 不该做的事就不做,用户没授权的范围就不开放,写操作必须可确认、可追踪、可撤销。
九、和相关模式的关系
| 模式 | 关系 |
|---|---|
| 工具使用 | MCP 是工具接入的一种协议层方案 |
| 路由 | 路由可决定使用哪个 MCP Server 或工具组 |
| 规划 | 计划步骤里可以包含 MCP 工具调用 |
| 反思 | 调用结果可以被审查和回滚 |
| 学习与适应 | 成功/失败的工具使用经验可以沉淀 |
| 目标监控 | MCP 调用需要 trace、成本和失败原因 |
如果说工具使用解决的是「Agent 能不能伸手做事」,MCP 解决的就是「这只手怎么接到更多工具上」。它让工具接入标准化,但不会自动替你做好判断、权限和监控。
十、一句话记住 MCP
MCP 的价值不是让一个工具调用更酷,而是让工具用统一方式被发现、连接和调用。
好的 MCP 接入要回答:
这个 Server 可信吗?
哪些能力是 Tool、Resource、Prompt?
该用 Local 还是 Remote?
工具结果是否被当成不可信数据?
高风险动作谁确认?
调用过程是否可审计?