从零开始读懂 MCP:大模型如何通过标准化协议“调用”你的工具?

从零开始读懂 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+)。然后新建一个空目录,初始化项目:

bash 复制代码
mkdir my-mcp-server
cd my-mcp-server
npm init -y

安装 MCP 官方 SDK 和参数校验库 zod

bash 复制代码
npm install @modelcontextprotocol/sdk zod

3.2 编写 MCP Server 代码

创建一个文件 my-mcp-server.mjs,内容如下:

javascript 复制代码
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 模式下,通常由客户端来启动它):

bash 复制代码
node my-mcp-server.mjs

由于它通过 Stdio 通信,直接运行会"卡住"(等待输入),这其实是正常的。真正的对话在下一节配置客户端后发生。


四、在 Trae / Cursor 中配置并使用你的 MCP Server

现在,我们要让 AI 编程工具(以 Trae 为例,Cursor 配置几乎相同)加载这个 Server。

4.1 添加 MCP 配置

打开 Trae 的设置,找到 MCP 配置文件(通常是一个 JSON 文件,例如 mcp.json),添加如下内容:

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 的详细信息。

大模型会:

  1. 理解你的意图。
  2. 发现有一个叫 query-user 的工具能满足需求。
  3. 自动调用该工具,并传入参数 { "userId": "002" }
  4. 等待你的 Server 返回结果。
  5. 将结果整理成自然语言回复你:
diff 复制代码
用户 002 的信息如下:
- 姓名:李四
- 邮箱:lisi@example.com
- 角色:user

如图

🎉 恭喜!你已经完成了第一个 MCP 工具的开发与集成!


五、深入思考:MCP 为什么是"Agent 时代的基石"?

5.1 跨语言、跨进程能力

通过 MCP,你可以用 Node.js 写胶水层,调用 Rust 写的高性能计算模块、Python 写的机器学习脚本、Java 写的企业级服务。大模型完全不需要关心底层实现语言。

5.2 从"个人工具"到"生态服务"

文中提到:"大厂将自己的服务以 MCP 的方式向外提供"。想象一下:

  • Stripe 提供一个 create-payment MCP 工具。
  • Slack 提供一个 send-message MCP 工具。
  • 你的公司内部提供 query-employee MCP 工具。

任何支持 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 编程的小伙伴。有疑问欢迎在评论区交流!

相关推荐
ZC跨境爬虫2 小时前
3D 地球卫星轨道可视化平台开发 Day12(解决初始相位拥挤问题,实现卫星均匀散开渲染)
前端·javascript·算法·3d·json
好雨知时节t2 小时前
告别“刷新”:一文搞懂 WebSocket、SSE 与轮询机制
javascript·ai编程
搬砖的前端2 小时前
本地模型+TRAE CN 打造最优模型组合实测:开源主模型+本地辅模型,对标GPT5.2/5.3/Gemini-3-Flash
前端·ai·mac·ai编程·qwen·trae·qwen3.6
userxxcc2 小时前
Waigo是用“Golang+Web”写的“视图窗口+稳定服务”的桌面端(Win、Mac、Ubuntu)多功能程序基座。开箱即用但有一定上手门槛。
javascript·golang·桌面应用基座·wails3
吴声子夜歌2 小时前
Vue3——Vue CLI
前端·javascript·vue.js
我的世界洛天依2 小时前
洛天依讲编程:调音教学|调性 ——MIDI 里的「钩子函数」
linux·前端·javascript
Cobyte3 小时前
7.响应式系统比对:手写一个响应式状态库并应用在 React 上
前端·javascript·vue.js
渔舟小调3 小时前
P18 | Element Plus 通用 CRUD 页面模板:一个模板覆盖 80% 管理页面
javascript·vue.js·elementui
1314lay_10073 小时前
匿名插槽和具名插槽的使用
前端·javascript·vue.js