为什么需要 MCP
给模型接工具这件事,在 MCP(Model Context Protocol,模型上下文协议)出现之前是各家自定义:这个 Agent 框架要求实现一个函数注册表,那个框架要 JSON Schema 加回调,换了模型供应商,工具描述格式又得再写一遍。工具开发者面对的不是一次适配,而是 N 次重复劳动。
MCP 是 Anthropic 在 2024 年开源并推给业界的开放协议,把「Agent 与工具之间怎么对话」这一层标准化:工具能力写一次,任何支持 MCP 的客户端都能发现并调用它。社区常用一个类比——它是「AI 应用的 USB-C」:接口统一之后,设备(工具)与主机(Agent 客户端)可以自由组合,不用为每种组合单独做转接头。
三个核心原语
MCP Server 能对外提供三类能力:
- tools(工具):模型可以决定调用的函数,比如「查库存」「跑一条只读 SQL」。由模型根据当前任务自主决定是否调用,是最常用的原语。
- resources(资源):以 URI 标识、供客户端读取的数据,比如一份配置文件、一段文档内容。读取动作由客户端发起,不经过模型决策。
- prompts(提示模板):预先写好的提示词模板,由用户主动选用,比如「提交前代码评审」模板。
一句话区分主导方:tools 模型说了算,resources 客户端来拉,prompts 用户来点。
两种传输方式
协议消息是 JSON-RPC 格式,承载它的传输层主要有两种:
- stdio:Server 作为客户端的本地子进程启动,双方通过标准输入输出交换消息。本地开发的主流方式,无需网络配置,许多 IDE 插件与终端类 Agent 都走这条通道。
- Streamable HTTP:Server 暴露一个 HTTP 端点,客户端通过 POST 发送消息、按需升级为流式响应。适合把 Server 部署成远程共享服务,供多个客户端与多名用户接入。
先本地 stdio 跑通,再考虑上 HTTP,是通常的路线。
动手:写一个最小的 MCP Server
初始化项目并安装官方 SDK:
npm init -y
npm install typescript @modelcontextprotocol/sdk zod
npm install -D @types/node
新建 server.ts,注册一个查询服务器磁盘用量的工具:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import { execFile } from "node:child_process";
import { promisify } from "node:util";
const run = promisify(execFile);
const server = new McpServer({ name: "disk-usage", version: "1.0.0" });
server.registerTool(
"disk_usage",
{
description: "查询指定路径所在磁盘的占用情况,返回 df -h 的结果",
inputSchema: {
path: z.string().describe("要查询的目录绝对路径"),
},
},
async ({ path }) => {
const { stdout } = await run("df", ["-h", path]);
return { content: [{ type: "text", text: stdout }] };
},
);
await server.connect(new StdioServerTransport());
不到三十行。三个点值得注意:description 和参数的 describe 是模型的「说明书」,写得越具体模型调用越准;inputSchema 用 zod 声明,SDK 会自动转成协议要求的 JSON Schema 并做校验;handler 返回的 content 数组就是最终喂回模型的结果。
在支持 MCP 的客户端里注册它(各家配置大同小异,以常见的 mcpServers 结构为例):
{
"mcpServers": {
"disk-usage": {
"command": "node",
"args": ["/绝对路径/build/server.js"]
}
}
}
配好后重启客户端,工具列表里就应该出现 disk_usage。
客户端是怎么发现并调用工具的
一次完整的调用走四步:
- initialize:客户端与服务端握手,交换协议版本与各自支持的能力。
- tools/list:客户端拉取工具清单,拿到每个工具的名字、描述与参数 schema。
- 模型决策:客户端把工具清单放进模型上下文;模型判断当前任务需要哪个工具,生成一次结构化调用请求。
- tools/call:客户端把请求转发给 Server 执行,再把结果回填给模型,模型继续组织回答。
如果想脱离客户端直接调试,Streamable HTTP 模式下可以用 curl 发 JSON-RPC 报文(先 initialize 再调用):
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "disk_usage",
"arguments": { "path": "/" }
}
}
上线前的注意事项
工具一旦挂上 Agent,就等于把一部分执行权交了出去,四件事必须做:
- 输入校验:zod 只是第一道关。涉及文件路径要防目录穿越,涉及 SQL 要参数化,涉及命令要白名单,别拼字符串。
- 超时:每个工具调用设超时上限,外部依赖(HTTP、数据库)单独设,避免 Agent 挂在一个卡死的调用上。
- 最小权限:工具进程用什么用户跑、能读哪些目录、能不能出网络,都按「不够用才加」的原则给。只读工具别夹带写操作。
- 日志与审计:记录每次调用的工具名、参数与结果摘要。出了问题要能回答「Agent 到底执行了什么」。
MCP 与 Function Calling 的关系
二者经常被放在一起比,但它们不在同一层。Function Calling 是模型能力:模型经过训练,能按给定的 schema 生成合法的结构化调用参数。MCP 是协议层标准:定义工具如何描述、如何被发现、如何被安全地调用。两者是互补关系——MCP Server 里的工具,最终仍然依赖模型的 Function Calling 能力来使用。可以这么记:模型负责「决定调什么、怎么传参」,MCP 负责「把工具标准化地送到模型面前」。
小结
- MCP 统一了 Agent 接工具的协议层:tools、resources、prompts 三原语,stdio 与 Streamable HTTP 两种传输
- 用官方 SDK 注册一个工具只要十几行,description 与参数描述的质量直接决定调用准确率
- 上线前把校验、超时、最小权限、审计四件事做完
- MCP 管标准化,Function Calling 管模型能力,一个在协议层一个在模型层,互补而非替代
读者留言
COMMENTS 暂无还没有留言,来说第一句?