解密 MCP 协议:如何用 Node.js 从零手写一个本地文件读取 MCP 服务器
- 前言
- 为什么需要 MCP?它解决了什么问题?
- 开发准备
- 核心实现:手写 `simple-read-mcp` 服务
-
- 代码深度解析
- 落地集成:接入 AI 研发环境
- 总结
前言
在当下大语言模型(LLM)与 AI Agent 技术爆发的时代,AI 助手在编写代码、解答疑问时表现得越发成熟。然而,受限于自身的训练数据与运行沙箱,AI 在执行“实时任务”或“触达本地生态”时常显得无能为力——它既无法感知你的本地文件,也无法直接调用你的专属工具。
为了打破这种“认知沙箱”,Anthropic 推出了 MCP(Model Context Protocol,模型上下文协议)。它统一了 AI 与外部数据源、工具之间的通信标准。
本文将跳过复杂的理论灌输,通过基于 Node.js 的原生开发视角,带你从零手写一个具备本地文件读取能力(File System Server)的 MCP 服务,并将其接入到现代化 AI IDE 中。读完本文,你不仅能掌握 MCP 的底层通信逻辑,更能亲手为你的 AI 助手装上一双触达本地系统的“手”。
为什么需要 MCP?它解决了什么问题?
在 MCP 出现之前,如果你想让 AI 读取本地项目结构或文件,通常需要通过 IDE 插件或是自定义脚本来硬编码拼凑 Prompt。这种模式存在两个核心痛点:
MCP 就像是 AI 时代的“USB 接口协议”。它定义了一个标准的 C/S(Client/Server)架构:
- MCP Host / Client(宿主/客户端):例如集成了 AI 能力的开发工具(Trae、VS Code 或是 Claude Desktop)。它们负责接收用户的自然语言指令,并将指令翻译给 AI。
- MCP Server(服务器):由开发者编写的独立服务,负责暴露具体的“工具(Tools)”或“资源(Resources)”。
当用户发出指令时,整体的工作流如下所示:
通过这一层标准化的封装,LLM 可以在不需要知道本地细节的情况下,安全、动态地调用各类本地或者云端服务。
开发准备
在开始动手之前,我们需要初始化一个 Node.js 项目并引入相关的依赖。这里主要依赖两个核心库:
首先,在你的终端中执行如下命令进行依赖安装:
pnpm i zod @modelcontextprotocol/sdk
核心实现:手写 simple-read-mcp 服务
接下来,我们创建一个名为 app.service.mjs(或者其他符合 ECMAScript Modules 规范的命名)的文件。以下是完整的核心代码实现:
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 服务端
const server = new McpServer({
name: 'simple-read-mcp',
version: '1.0.0'
});
// 2. 利用新版极简工具注册器注册 read_file 工具
server.tool(
"read_file",
"读取指定路径的本地文件内容",
{
path: z.string().describe("文件的绝对或相对路径") // 校验参数是否为字符串,并提供描述供 LLM 理解
},
async ({ path }) => {
try {
// 执行本地异步文件读取
const content = await fs.readFile(path, 'utf-8');
// 严格按照 MCP 规范返回 text 类型的上下文数据
return {
content: [{ type: "text", text: content }]
};
} catch (err) {
// 容错处理:若文件不存在或无权限,返回 error 标识并附带错误原因
return {
isError: true,
content: [{ type: "text", text: `读取文件失败:${err.message}` }]
};
}
}
);
// 3. 异步启动函数,建立标准输入输出通信通道
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error("MCP read_file 服务已启动(stdio模式)");
}
main().catch(console.error);
代码深度解析
很多开发者在接触 MCP 时,容易被繁琐的协议细节劝退。上面的代码利用了新版官方 SDK 的高层抽象,非常优雅地完成了功能实现。我们来重点拆解它的几个关键设计:
- new McpServer(…):初始化 MCP 服务的实例。服务名(name)和版本(version)会在 Client 与 Server 建立连接时进行握手交换,便于客户端识别和管理。
- server.tool(…) 内的 Schema 声明:这是 MCP 的精髓所在。我们不需要用复杂的 JSON 格式去写定义,而是直接传入 read_file 的名字、功能的自然语言描述,以及一个 Zod 对象 { path: z.string().describe(…) }。大模型就是通过这些“描述文字(Description)”来感知这个工具有什么用、需要什么参数的。当大模型决定调用时,Zod 会在底层自动拦截并校验入参是否合法。
- 返回值结构体:MCP 协议规定了 Server 返回给大模型的上下文格式。必须符合 { content: [{ type: "text", text: … }] } 的数组嵌套结构。如果中途发生异常(例如无权限、路径不存在),则需提供 isError: true 标记,告知大模型执行失败,以便大模型在后续对话中进行调整或向用户报错。
- StdioServerTransport 与 console.error 的设计妙处: 在主函数 main 中,我们采用了 Stdio(标准输入输出)进程管道进行通信。这意味着客户端与服务器之间传递 JSON-RPC 数据是通过 process.stdin 和 process.stdout 完成的。 因此,服务器代码中绝对不能出现普通的 console.log。因为任何 console.log 都会污染 stdout 管道,导致客户端解析 JSON 报错。这就解释了为什么代码中提示服务启动的日志必须使用 console.error 输出——因为 stderr 不会影响主通信管道,它是安全的日志输出通道。
落地集成:接入 AI 研发环境
编写完代码后,如何让我们的 AI 编辑器(如 Trae / VS Code 等)用上这个工具呢?
我们可以通过编辑编辑器支持的 MCP 配置文件(通常位于项目根目录的 .trae/mcp.json 或全局配置中)来导入该服务。配置的核心逻辑是告诉 Host 如何用 Node 启动我们写好的脚本:
{
"mcpServers": {
"simple-read-mcp": {
"command": "node",
"args": ["/你的项目绝对路径/app.service.mjs"],
"disabled": false
}
}
}

配置完成后,打开编辑器的 MCP 管理面板,你会看到如图所示的界面:

当看到 simple-read-mcp 显示为启用状态且带有绿色对勾时,说明宿主客户端已经成功通过 Stdio 管道拉起了我们的 Node.js 进程,并完成了能力握手。
现在,你可以在聊天对话框或者 Agent 模式下直接对 AI 说:“帮我分析一下系统里根目录下的 package.json”。AI 将在后台自动识别并调用 read_file 工具,读取内容并给出专业的解答。
总结
MCP 协议的出现,在 LLM 与本地物理世界之间架起了一座标准化的桥梁。通过本文的实践,我们仅用了不到 40 行的 Node.js 代码,就实现了一个安全、规范的本地文件读取服务器。
在实际的生产实践中,你可以基于此架构进行更广阔的扩展:
- 安全性控制:在 read_file 中加入沙箱路径校验,防止 AI 被提示词攻击(Prompt Injection)进而读取系统敏感文件(如 .env 或 ~/.ssh)。
- 多功能集成:在同一个 McpServer 实例上,通过 server.tool 继续挂载数据库查询、外部 API 联动、自动化编译等更多工具。
掌握 MCP,就是掌握了让 AI Agent 走向务实落地的钥匙。希望这篇文章能帮你顺利开启自定义 AI 工具库的大门!
网硕互联帮助中心




评论前必须登录!
注册