从零开始读懂 MCP:大模型如何通过标准化协议“调用”你的工具?
如果你最近被 MCP、Agent、Tool Calling 这些词绕晕了,本文将带你从概念到实战,手把手编写第一个 MCP Server,并集成到 Cursor / Trae 中使用。
前言:大模型已经能“干活”了,但还不够顺滑
过去一年,LLM(大语言模型)的能力边界不断拓展。我们不再满足于让它“聊天”,而是希望它能 读取文件、执行命令、查询数据库、调用第三方 API。于是,“LLM + Tools = Agent” 这个公式开始流行。
但很快,一个新的痛点出现了:工具变多了、变杂了。
- 你想让 Node.js 写的 Agent 调用一个 Python 脚本?
- 你想把公司内部的用户查询服务暴露给 Cursor?
- 你想集成某个大厂以 MCP 形式发布的 API?
这些需求本质上都是在问:如何让大模型以统一、安全、跨语言的方式调用各种工具?
MCP(Model Context Protocol) 就是为解决这个问题而生的。
一、到底什么是 MCP?
1.1 一句话定义
MCP 是一套标准化的“工具描述与调用协议”,由 Anthropic 在 2024 年底提出并贡献给开源社区。
它规定了一个工具应该如何:
- 声明自己的元信息(名称、描述、参数 schema)
- 接收大模型的调用请求
- 返回结构化的执行结果
有了这套协议,任何语言编写的工具,只要遵循 MCP 规范,就能被任何支持 MCP 的客户端(如 Cursor、Claude Desktop、Trae)无缝调用。
1.2 类比理解:就像 USB-C 接口
| 现实世界 | MCP 世界 |
|---|---|
| 不同设备需要不同接口(Micro-USB、Lightning、Type-C) | 不同工具用不同方式暴露(HTTP API、命令行、gRPC……) |
| USB-C 统一了物理形态和通信协议 | MCP 统一了工具的描述格式和通信方式 |
| 一个 Type-C 充电器可以充手机、电脑、Switch | 一个 MCP Client 可以调用本地脚本、远程服务、Python 函数 |
二、MCP 的通信层:Stdio 与 HTTP
MCP 协议本身是“传输无关”的,目前最主流的两种通信方式为:
| 通信方式 | 适用场景 | 特点 |
|---|---|---|
| Stdio(标准输入输出) | 本地子进程工具 | 简单、安全、无需网络配置,适合个人开发者 |
| HTTP / SSE | 远程服务、团队共享 | 支持跨网络调用,适合将内部服务封装成 MCP 暴露 |
在本文的实战部分,我们将使用 Stdio 方式——MCP 客户端(如 Trae)会启动一个子进程运行我们的 .mjs 脚本,并通过标准输入输出与它通信。
三、实战:编写你的第一个 MCP Server(Node.js 版)
目标:实现一个
query-user工具,让大模型可以查询模拟数据库中的用户信息。
3.1 环境准备
确保你已经安装了 Node.js(v18+)。然后新建一个空目录,初始化项目:
mkdir my-mcp-server
cd my-mcp-server
npm init -y
安装 MCP 官方 SDK 和参数校验库 zod:
npm install @modelcontextprotocol/sdk zod
3.2 编写 MCP Server 代码
创建一个文件 my-mcp-server.mjs,内容如下:
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { z } from 'zod';
// 模拟数据库
const database = {
users: {
"001": { id: "001", name: "张三", email: "zhangsan@example.com", role: "admin" },
"002": { id: "002", name: "李四", email: "lisi@example.com", role: "user" },
"003": { id: "003", name: "王五", email: "wangwu@example.com", role: "user" },
}
};
// 1. 创建 MCP Server 实例
const server = new McpServer({
name: 'my-mcp-server',
version: '1.0.0',
});
// 2. 注册一个工具 (Tool)
server.registerTool(
'query-user', // 工具名称
{
description: '查询数据库中的用户信息。输入用户ID,返回该用户的详细信息(姓名、邮箱、角色)。',
inputSchema: { // 定义输入参数的 schema
userId: z.string().describe("用户 ID,例如:001, 002, 003")
}
},
async ({ userId }) => { // 工具的实际执行逻辑
const user = database.users[userId];
if (!user) {
return {
content: [
{
type: 'text',
text: `❌ 用户ID ${userId} 不存在。可用的ID: 001, 002, 003`
}
]
};
}
return {
content: [
{
type: 'text',
text: `✅ 用户信息:\n- ID: ${user.id}\n- 姓名: ${user.name}\n- 邮箱: ${user.email}\n- 角色: ${user.role}`
}
]
};
}
);
// 3. 选择 Stdio 传输层,并启动 Server
const transport = new StdioServerTransport();
await server.connect(transport);
3.3 代码关键点解析
| 代码部分 | 作用 |
|---|---|
McpServer | MCP 服务端主类,负责管理工具列表、处理请求。 |
StdioServerTransport | 声明使用标准输入输出作为通信管道。 |
registerTool | 向 MCP 客户端宣告:“我有一个叫 query-user 的工具,你需要传入一个 userId 字符串,我会返回一段文本。” |
inputSchema | 使用 zod 描述参数类型和说明。这些信息会被发送给大模型,让它理解如何正确调用工具。 |
返回格式 { content: [{ type: 'text', text: '...' }] } | MCP 协议规定的标准工具返回结构。 |
3.4 测试你的 MCP Server(可选)
你可以直接在终端运行该脚本,看看它是否正常工作(虽然 Stdio 模式下,通常由客户端来启动它):
node my-mcp-server.mjs
由于它通过 Stdio 通信,直接运行会“卡住”(等待输入),这其实是正常的。真正的对话在下一节配置客户端后发生。
四、在 Trae / Cursor 中配置并使用你的 MCP Server
现在,我们要让 AI 编程工具(以 Trae 为例,Cursor 配置几乎相同)加载这个 Server。
4.1 添加 MCP 配置
打开 Trae 的设置,找到 MCP 配置文件(通常是一个 JSON 文件,例如 mcp.json),添加如下内容:
{
"mcpServers": {
"my-mcp-server": {
"command": "node",
"args": [
"C:/Users/29031/Desktop/workspace/lesson_zp/ai/agent/mini-cursor/mcp/mcp-tool/my-mcp-server.mjs"
]
}
}
}
配置图片
配置说明:
my-mcp-server:给你的 Server 起个名字,会显示在客户端界面。command:启动 Server 的命令,这里是node。args:传给命令的参数,即你的.mjs文件的绝对路径。
⚠️ 请务必将路径替换为你自己电脑上的实际路径。
4.2 重启客户端并验证
保存配置文件后,重启 Trae(或点击 MCP 列表旁边的刷新按钮)。
如果一切正常,你应该能在 MCP 面板中看到 my-mcp-server 状态为 已连接,并且可用工具列表中出现了 query-user。
4.3 实际对话测试
现在,在 Trae 的对话框中输入以下内容(确保 AI 可以调用 MCP 工具):
帮我查一下用户 002 的详细信息。
大模型会:
- 理解你的意图。
- 发现有一个叫
query-user的工具能满足需求。 - 自动调用该工具,并传入参数
{ "userId": "002" }。 - 等待你的 Server 返回结果。
- 将结果整理成自然语言回复你:
用户 002 的信息如下:
- 姓名:李四
- 邮箱:lisi@example.com
- 角色:user
如图
🎉 恭喜!你已经完成了第一个 MCP 工具的开发与集成!
五、深入思考:MCP 为什么是“Agent 时代的基石”?
5.1 跨语言、跨进程能力
通过 MCP,你可以用 Node.js 写胶水层,调用 Rust 写的高性能计算模块、Python 写的机器学习脚本、Java 写的企业级服务。大模型完全不需要关心底层实现语言。
5.2 从“个人工具”到“生态服务”
文中提到:“大厂将自己的服务以 MCP 的方式向外提供”。想象一下:
- Stripe 提供一个
create-paymentMCP 工具。 - Slack 提供一个
send-messageMCP 工具。 - 你的公司内部提供
query-employeeMCP 工具。
任何支持 MCP 的 Agent(Cursor、Claude Desktop 等)都能立刻获得这些超能力,80% 的轻量级 APP 确实可能被 Agent + MCP 工具链替代。
5.3 安全性与上下文隔离
MCP 将工具的执行隔离在独立的进程(甚至远程服务器)中。大模型只拿到你精心设计的 Schema 描述 和 执行结果文本,它无法直接访问你的数据库连接池、文件系统句柄或内存变量。这是一种天然的安全沙箱。
六、常见问题与下一步学习
Q1:我不想用 Stdio,我想把 Server 部署在远程服务器上供团队共用,怎么办?
A:MCP 也支持 HTTP + SSE(Server-Sent Events)传输。你可以使用 @modelcontextprotocol/sdk 中的 SSEServerTransport 模块,将 Server 挂载到一个 Express / Fastify 服务上。
Q2:我的工具执行时间很长(比如生成报表),会不会超时?
A:MCP 协议本身对执行时间没有硬性限制,但客户端通常有自己的超时设置。对于长任务,建议设计为“异步任务 + 状态查询”两个工具配合使用。
Q3:除了查询,我能做写入操作吗(比如修改文件、删除用户)?
A:完全可以。但请注意:
- 在工具描述中明确告知用户这是有副作用的操作。
- 在代码中做好权限校验和日志记录。
下一步学习建议:
- 阅读 MCP 官方文档
- 研究
@modelcontextprotocol/sdk中的Resource(资源)概念,它允许大模型直接读取文件、数据库表结构等静态内容。 - 尝试用 Python 或 Go 语言编写一个 MCP Server(官方已有对应 SDK)。
七、总结
| 阶段 | 核心认知 |
|---|---|
| 理解 MCP | 一套标准化的工具描述与调用协议,解决大模型与各类工具之间的连接问题。 |
| 编写 Server | 使用官方 SDK,通过 registerTool 暴露功能,通过 StdioServerTransport 通信。 |
| 配置客户端 | 在 Cursor / Trae 的 mcp.json 中声明启动命令,即可让 AI 获得新能力。 |
| 展望未来 | MCP 正在成为 Agent 生态的“USB-C 接口”,让大模型真正成为可编程的操作系统。 |
现在,不妨打开你的编辑器,把第一个 MCP Server 跑起来。当你亲眼看到 AI 调用你写的代码并返回正确结果时,你会真正理解那句——
“LLM + Tools = Agent”
而 MCP,就是让这个加法可以规模化复制的关键。
附录
流程图
如果本文对你有帮助,欢迎点赞、收藏、转发给正在学习 AI 编程的小伙伴。有疑问欢迎在评论区交流!