手把手教你写一个 MCP Server:从零构建文件读取服务
前言
上篇我们聊了 MCP(Model Context Protocol)是什么,这次直接动手,用 Node.js 手写一个文件读取 MCP Server,并在 Trae 中实际跑起来。
目标:让 AI 能通过 MCP 协议安全地读取你指定的本地文件。
整体架构
MCP Server 的通信链路一句话概括:
c
用户输入 → LLM 分析 → 匹配 MCP 工具 → StdioServerTransport → stdin → Server 执行 → stdout → 返回结果给 LLM
核心流程:AI 不直接操作文件系统,而是通过标准输入输出(stdio)把请求发给 Server,Server 执行后返回结果。这就是 MCP 的 "USB-C 协议" 本质。
项目初始化
bash
mkdir simple-read-mcp && cd simple-read-mcp
pnpm init
pnpm i zod @modelcontextprotocol/sdk
两个核心依赖:
| 包 | 作用 |
|---|---|
@modelcontextprotocol/sdk |
MCP 协议通信层,提供 Server 和 Transport |
zod |
运行时 Schema 校验,定义工具的入参格式 |
编写 Server 代码
创建 server.js:
javascript
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import fs from "fs/promises";
const server = new McpServer({
name: "simple-read-mcp",
version: "1.0.0",
});
server.tool(
"read_file",
"读取指定路径的本地文件内容",
{
path: z.string().describe("文件的绝对或相对路径"),
},
async ({ path }) => {
try {
const content = await fs.readFile(path, "utf-8");
return {
content: [{ type: "text", text: content }],
};
} catch (err) {
return {
isError: true,
content: [{ type: "text", text: `读取文件失败:${err.message}` }],
};
}
},
);
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP read_file 服务已启动(stdio模式)");
}
main().catch(console.error);
代码解读
第一步:实例化 Server
javascript
const server = new McpServer({
name: "simple-read-mcp",
version: "1.0.0",
});
给 Server 起个名字和版本号,方便调试和区分。
第二步:注册 Tool
javascript
server.tool(
"read_file", // 工具名
"读取指定路径的本地文件内容", // 描述(LLM 靠这个判断何时调用)
{ path: z.string().describe("...") }, // 参数 Schema
async ({ path }) => { ... } // 执行逻辑
);
新版 SDK 的 server.tool() 用 zod 定义参数 Schema,不再需要手写 JSON Schema,非常简洁。LLM 会阅读工具描述和参数说明,自行判断何时调用这个工具。
第三步:启动 stdio 通信
javascript
const transport = new StdioServerTransport();
await server.connect(transport);
StdioServerTransport 通过标准输入输出与客户端通信------客户端通过 stdin 发请求,Server 通过 stdout 返回结果。这是 MCP 最常用的本地通信方式。
配置 Trae 客户端
在 Trae 的 mcp.json(路径:C:\Users\你的用户名\AppData\Roaming\Trae CN\User\mcp.json)中添加:
json
{
"mcpServers": {
"simple-read-mcp": {
"command": "node",
"args": ["文件路径名"]
}
}
}
"simple-read-mcp":给这个 Server 起一个在 Trae 中使用的名字"command": "node":用 Node.js 运行"args":Server 文件的绝对路径
重启 Trae 后,就可以在对话中直接调用这个工具了。
实际效果
配置生效后,在 Trae 中说:
"请使用 simple-read-mcp 读取 server.js 的内容"
AI 会自动:
- 分析你的意图
- 匹配到
read_file工具 - 通过 stdio 把
{ path: "d:/workspace/.../server.js" }发给 Server - Server 读取文件并返回内容
- AI 展示给你看
整个过程对用户完全透明,就像 AI 自己会读文件一样。
为什么不用 fs 直接读?
你可能会想:Node.js 本身就能 fs.readFile,为什么要通过 MCP?
关键在于安全边界:
- 直接调用:AI 可以访问任何路径,风险不可控
- MCP 方式:Server 可以加上路径白名单、权限校验等逻辑,把文件访问限制在安全范围内
在生产环境中,你还可以在 Server 里加入日志记录、限流、加密等能力,这正是 MCP 作为"中间层"的价值。
总结
一个完整的 MCP Server 只需要三步:
| 步骤 | 做的事情 | 关键 API |
|---|---|---|
| 实例化 | 创建 Server | new McpServer() |
| 注册工具 | 声明功能 + Schema + 逻辑 | server.tool() |
| 启动通信 | 通过 stdio 连接客户端 | StdioServerTransport + server.connect() |
加上客户端的 mcp.json 配置,总共不到 50 行代码,就让 AI 获得了安全的文件读取能力。
掌握了这个模式,你可以轻松扩展出更多能力:写文件、搜索文件、调用 API、操作数据库......MCP 的想象空间才刚刚打开。
!