前言

在当下大语言模型(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。这种模式存在两个核心痛点:

  1. 接口标准不一:每个应用、每个扩展都有自己的数据组织形式,开发迁移成本极高。
  2. 上下文孤立:大模型无法以结构化、可感知的方式确认自己是否拥有某种能力,难以进行精准的工具决策。

MCP 就像是 AI 时代的“USB 接口协议”。它定义了一个标准的 C/S(Client/Server)架构:

  • MCP Host / Client(宿主/客户端):例如集成了 AI 能力的开发工具(Trae、VS Code 或是 Claude Desktop)。它们负责接收用户的自然语言指令,并将指令翻译给 AI。
  • MCP Server(服务器):由开发者编写的独立服务,负责暴露具体的“工具(Tools)”或“资源(Resources)”。

当用户发出指令时,整体的工作流如下所示:

  1. 用户下达指令:在 AI 聊天框输入:“帮我看看本地 config.json 里写了什么”。
  2. 客户端与大模型决策:Host 接收 Prompt 传给 LLM,LLM 分析上下文发现需要读取文件,于是选中了注册在客户端内的 read_file 工具。
  3. 协议传输(Stdio 管道):客户端通过标准输入(stdin)向 MCP Server 发送一条 JSON-RPC 请求。
  4. 服务器执行:MCP Server 接收请求,调用底层的 fs 模块执行本地文件读取,并将结果通过标准输出(stdout)返回。
  5. 生成最终响应:客户端(Host)将读取到的文件上下文喂给 LLM,LLM 根据最新的完整数据生成最终回复返还给用户。

通过这一层标准化的封装,LLM 可以在不需要知道本地细节的情况下,安全、动态地调用各类本地或者云端服务。


开发准备

在开始动手之前,我们需要初始化一个 Node.js 项目并引入相关的依赖。这里主要依赖两个核心库:

  1. @modelcontextprotocol/sdk:官方提供的协议 SDK,帮助我们快捷实现符合 MCP 规范的底层通信与生命周期管理。
  2. zod:一个强类型 Schema 声明与验证库。由于 MCP 核心需要向 LLM 声明工具的输入参数格式,Zod 能够提供极简且严谨的校验能力,避免手写繁琐的 JSON Schema。

首先,在你的终端中执行如下命令进行依赖安装:

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 标记,告知大模型执行失败,以便大模型在后续对话中进行调整或向用户报错。
  • StdioServerTransportconsole.error 的设计妙处
    在主函数 main 中,我们采用了 Stdio(标准输入输出)进程管道进行通信。这意味着客户端与服务器之间传递 JSON-RPC 数据是通过 process.stdinprocess.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 工具库的大门!

Logo

一站式 AI 云服务平台

更多推荐