模型上下文协议(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 集成。

流程图画布 · 115%
Mermaid 流程图加载中...

以前是「工具和客户端互相硬编码」:一个 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 怎么工作

运行时通常包含四个动作:握手、发现、调用、返回。

流程图画布 · 115%
Mermaid 流程图加载中...

这里真正重要的不是「多了一种工具调用格式」,而是能力发现、资源读取、提示词模板和工具调用都进入了统一协议。

Client 不再需要预先写死「这个 Server 一定有某个函数」。它可以先问 Server:你有哪些 tools、resources、prompts?Server 返回结构化清单后,Client 再决定展示给用户、交给模型,或在权限检查后调用。

这也带来一个工程边界:MCP Server 不是一个神奇黑盒,它只是把能力暴露出来。鉴权、权限隔离、审计、危险操作确认,仍然要由 Server、Client 和业务系统一起负责。

四、什么时候该用 MCP

不要为了用协议而用协议。MCP 的收益来自复用、解耦和生态,不来自让一个小 demo 更复杂。

场景推荐方案理由
只做一个应用,接一两个内部工具原生 Function Calling简单直接,成本低
工具要给多个客户端复用MCPServer 写一次,多处接入
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 常见有两种部署方式,新手最容易在这里选错。

方式怎么运行适合场景主要风险
LocalClient 在本地启动 Server 进程,常见是 stdioIDE、桌面 Agent、本地文件和命令本机权限过大、配置泄漏
RemoteServer 独立部署,通过网络访问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?
工具结果是否被当成不可信数据?
高风险动作谁确认?
调用过程是否可审计?