手写 MCP Server 全栈实战:用 Node.js + Zod 从零构建 AI 文件读取工具
上篇文章我们学会了配置 MCP------用官方的
server-filesystem。但如果想自定义工具逻辑呢?本文从零开始,用@modelcontextprotocol/sdk+zod手写一个完整的 MCP Server,实现 AI 读取本地文件的能力,深入理解 MCP 协议的通信机制和工具注册流程。
前言
在上一篇文章中,我们通过配置 .mcp.json 使用了官方的 server-filesystem。但如果你的需求不只是读写文件呢?比如想让 AI 读取日志、查询数据库、调用内部 API------就需要自己写 MCP Server。
本文将手把手带你:
- 理解 MCP Server 的通信原理(stdio 传输)
- 用
@modelcontextprotocol/sdk创建 Server 实例 - 用
zod声明参数 Schema(类型安全校验) - 实现一个完整的文件读取 MCP Server
- 理解从 Claude Code Prompt 到 Server 执行的完整链路
一、MCP Server 的工作原理
1.1 整体通信链路
当你(通过 Claude Code)发出一个请求时,数据的流动路径如下:
c
┌──────────────────────────────────────────────────────────────┐
│ MCP 完整通信链路 │
│ │
│ 用户 Prompt │
│ ↓ │
│ Claude Code(Host) │
│ ↓ │
│ LLM 推理:需要读取文件 → 选择 filesystem Client │
│ ↓ │
│ StdioServerTransport(stdin/stdout) │
│ ↓ stdin(JSON-RPC 消息) │
│ MCP Server(我们的 server.js) │
│ ↓ 执行 read_file 工具 │
│ 读取本地文件 │
│ ↓ stdout(JSON-RPC 返回结果) │
│ StdioServerTransport │
│ ↓ │
│ Claude Code → LLM → 生成最终回答 │
└──────────────────────────────────────────────────────────────┘
关键理解:MCP Server 是一个独立的 Node.js 进程,通过标准输入输出(stdin/stdout)与 Host 通信。Host 启动 Server 作为子进程,两者通过 JSON-RPC 消息交换数据。
1.2 Server 的三大职责
| 职责 | 说明 |
|---|---|
| 声明工具 | 用 Schema 定义工具名、参数、描述 |
| 执行逻辑 | 接收参数 → 执行操作 → 返回结果 |
| 协议通信 | 通过 stdio/SSE 与 Host 交换消息 |
二、项目搭建:从 package.json 开始
2.1 初始化项目
bash
mkdir simple-read-mcp
cd simple-read-mcp
npm init -y
2.2 安装依赖
bash
npm install @modelcontextprotocol/sdk zod
两个核心依赖:
| 依赖 | 版本 | 作用 |
|---|---|---|
@modelcontextprotocol/sdk |
^1.29.0 | MCP 官方 SDK,封装协议通信细节 |
zod |
^4.4.3 | TypeScript 优先的运行时数据校验库 |
2.3 完整的 package.json
json
{
"name": "simple-read-mcp",
"version": "1.0.0",
"description": "自定义 MCP Server,为 AI 提供文件读取能力",
"main": "server.js",
"type": "module",
"dependencies": {
"@modelcontextprotocol/sdk": "^1.29.0",
"zod": "^4.4.3"
}
}
注意 :"type": "module" 声明了使用 ES Modules(.mjs 或 .js 中可用 import/export)。
三、核心代码:40 行实现一个 MCP Server
3.1 完整代码
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';
// 1. 创建 MCP Server 实例
const server = new McpServer({
name: 'simple-read-mcp',
version: '1.0.0'
});
// 2. 注册工具:内置 zod schema 校验
server.tool(
"read_file", // 工具名称
"读取指定路径的本地文件内容", // 工具描述(LLM 会看到)
{
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}` }]
};
}
}
);
// 3. 启动服务(stdio 传输模式)
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP read_file 服务已启动(stdio模式)");
}
main().catch(console.error);
3.2 代码逐行解析
第一步:创建 Server 实例
javascript
const server = new McpServer({
name: 'simple-read-mcp', // Server 名称(在 Host 中标识)
version: '1.0.0' // 版本号
});
McpServer 是 MCP SDK 提供的高层封装,内部处理了 JSON-RPC 消息的解析和序列化,让开发者只需关注工具注册和业务逻辑。
第二步:注册工具
javascript
server.tool(
"read_file", // 参数1:工具名
"读取指定路径的本地文件内容", // 参数2:工具描述(LLM 看到这个来决策)
{ // 参数3:参数 Schema
path: z.string().describe("文件的绝对或相对路径")
},
async ({ path }) => { ... } // 参数4:执行函数
);
server.tool() 方法接收 4 个参数:
| 参数 | 说明 | 类比 |
|---|---|---|
| 工具名 | LLM 调用时的标识符 | 函数名 |
| 描述 | LLM 看到后决定是否调用 | 函数文档 |
| Schema | 参数的类型校验 | 函数参数类型 |
| 执行函数 | 接收校验后的参数,执行操作 | 函数体 |
第三步:标准返回格式
javascript
return {
content: [{ type: "text", text: content }]
};
MCP 协议规定工具返回必须是 content 数组,每个元素有 type 和 text(或 image)。
第四步:错误处理
javascript
return {
isError: true, // 告诉 LLM:这是一个错误结果
content: [{ type: "text", text: `读取文件失败:${err.message}` }]
};
isError: true 让 LLM 知道操作失败了,可以选择重试或换方案。
第五步:启动 stdio 传输
javascript
const transport = new StdioServerTransport();
await server.connect(transport);
StdioServerTransport 通过 stdin 接收 Host 的请求,通过 stdout 返回结果。注意日志要用 console.error 而非 console.log,因为 stdout 已被协议占用。
四、Zod:参数校验的安全网
4.1 为什么需要 Zod?
MCP Server 接收 LLM 传来的参数,但 LLM 的输出是概率性的,可能传入错误类型的参数。Zod 在运行时对参数进行校验,防止意外错误。
javascript
// z.string() 确保参数是字符串
// .describe() 提供描述(MCP SDK 会将其转换为 JSON Schema 发给 LLM)
{
path: z.string().describe("文件的绝对或相对路径")
}
4.2 Zod 的常见用法
javascript
import { z } from "zod";
// 字符串
z.string()
z.string().min(1) // 非空
z.string().describe("描述")
// 数字
z.number()
z.number().int().positive() // 正整数
// 布尔
z.boolean()
// 枚举
z.enum(["read", "write", "delete"])
// 对象
z.object({
path: z.string(),
encoding: z.enum(["utf-8", "gbk"]).optional(),
lineCount: z.number().int().optional()
})
// 数组
z.array(z.string())
4.3 Zod vs 手写 JSON Schema
css
旧版 MCP SDK(手写 JSON Schema):
{
type: "object",
properties: {
path: { type: "string", description: "文件路径" }
},
required: ["path"]
}
新版 MCP SDK + Zod:
{
path: z.string().describe("文件路径")
}
新版 SDK 内部自动将 Zod Schema 转换为 JSON Schema,代码更简洁、类型更安全。
五、接入 Claude Code:配置 .mcp.json
5.1 配置文件
在项目根目录创建 .mcp.json:
json
{
"mcpServers": {
"simple-read": {
"type": "stdio",
"command": "node",
"args": ["/path/to/simple-read-mcp/server.js"]
}
}
}
关键区别 :之前用的是 npx @modelcontextprotocol/server-filesystem(官方 Server),现在用的是 node server.js(我们自己写的 Server)。
5.2 参数对比
| 配置项 | 官方 Server | 自定义 Server |
|---|---|---|
command |
npx |
node |
args |
["@modelcontextprotocol/server-filesystem", "/path"] |
["/path/to/server.js"] |
| 可定制性 | 固定功能 | 完全自由 |
5.3 在 Claude Code 中使用
arduino
⟩ 使用 simple-read 工具读取 server.js 的内容
Claude Code 会:
- 分析请求 → 选择
simple-readClient - 生成调用参数
{"path": "server.js"} - 通过 stdin 发送 JSON-RPC 消息给我们的 Server
- Server 读取文件 → 通过 stdout 返回内容
- LLM 基于文件内容生成回答
六、项目结构全景
perl
simple-read-mcp/
├── package.json # 项目配置 + 依赖声明
├── server.js # MCP Server 核心代码(40行)
├── .mcp.json # Claude Code 接入配置
└── node_modules/
├── @modelcontextprotocol/sdk/ # MCP 协议 SDK
└── zod/ # 参数校验库
代码量统计:整个 MCP Server 只需 40 行代码------MCP SDK 的抽象做得非常干净。
七、从简单到进阶:扩展你的 MCP Server
7.1 添加更多工具
javascript
// 工具1:读取文件
server.tool("read_file", "读取文件", { path: z.string() }, async ({ path }) => {
const content = await fs.readFile(path, 'utf-8');
return { content: [{ type: "text", text: content }] };
});
// 工具2:列出目录
server.tool("list_dir", "列出目录内容", { path: z.string() }, async ({ path }) => {
const files = await fs.readdir(path);
return { content: [{ type: "text", text: files.join('\n') }] };
});
// 工具3:获取文件信息
server.tool("file_info", "获取文件元信息",
{ path: z.string() },
async ({ path }) => {
const stat = await fs.stat(path);
return {
content: [{ type: "text", text: JSON.stringify({
size: stat.size,
created: stat.birthtime,
modified: stat.mtime,
isFile: stat.isFile(),
isDir: stat.isDirectory()
}, null, 2) }]
};
}
);
7.2 添加资源(Resources)
MCP 不仅支持工具(Tool),还支持资源(Resource)------为 LLM 提供上下文数据:
javascript
server.resource(
"config", // 资源名
"file:///config.json", // URI
async (uri) => {
const config = await fs.readFile('./config.json', 'utf-8');
return {
contents: [{ uri, mimeType: "application/json", text: config }]
};
}
);
7.3 进阶方向
| 方向 | 说明 |
|---|---|
| 数据库工具 | 用 pg / mysql2 实现查询工具 |
| API 代理 | 封装内部 API 调用,让 AI 访问私有服务 |
| 日志分析 | 读取并分析应用日志,生成诊断报告 |
| 多工具组合 | 一个 Server 注册多个工具,提供完整能力 |
知识树
typescript
手写 MCP Server 全栈实战
├── 工作原理
│ ├── 完整通信链路(Host → stdin → Server → stdout → Host)
│ └── Server 三大职责(声明工具 / 执行逻辑 / 协议通信)
├── 项目搭建
│ ├── package.json(ESM + SDK + Zod)
│ └── npm install @modelcontextprotocol/sdk zod
├── 核心代码(40行)
│ ├── McpServer 实例化
│ ├── server.tool() 注册工具
│ │ ├── 工具名 + 描述
│ │ ├── Zod Schema 参数校验
│ │ └── 异步执行函数 + 标准返回格式
│ ├── StdioServerTransport 启动
│ └── console.error(不要用 console.log)
├── Zod 参数校验
│ ├── string / number / boolean / enum
│ ├── object / array
│ └── Zod vs 手写 JSON Schema
├── 接入 Claude Code
│ ├── .mcp.json 配置(command: node)
│ └── 自然语言调用工具
├── 安全边界
│ └── 路径白名单(防止越界访问)
└── 进阶方向
├── 多工具注册
├── Resources 资源
├── 数据库 / API 工具
└── 日志分析
结语
手写一个 MCP Server 只需要 40 行代码------这得益于 @modelcontextprotocol/sdk 的高层封装和 zod 的简洁语法。
理解 MCP Server 的本质后,你会发现它的架构非常优雅:
- McpServer 负责协议通信(你不需要了解 JSON-RPC 细节)
- server.tool() 负责工具注册(名称 + 描述 + Schema + 执行函数)
- StdioServerTransport 负责传输(stdin/stdout)
- zod 负责安全(运行时参数校验)
从使用官方 Server 到手写自定义 Server,你完成了从"消费者"到"创造者"的跨越。现在你可以为 AI 构建任何能力------读取数据库、调用内部 API、分析日志、甚至远程控制设备。
MCP 让每个开发者都成为 AI 能力的构建者。40 行代码,打开了一个无限可能的世界。
参考与拓展阅读:
@modelcontextprotocol/sdk官方文档zodv4 官方文档- MCP 协议规范(Model Context Protocol Specification)
- 《MCP 协议深度解析》------ AI 界的 USB-C 实战指南
- 《Harness Engineering 深度解析》------ MCP 是 Harness 工具层的核心
如果本文帮你理解了手写 MCP Server 的完整流程,欢迎点赞 + 收藏。有什么想扩展的 MCP Server 能力,欢迎在评论区交流讨论 👇
#MCP #Node.js #Zod #ClaudeCode #AI工程化 #掘金技术社区