从零构建 MCP 文件服务:50 行代码让 AI 读懂你的文件

0 阅读3分钟

手把手教你写一个 MCP Server:从零构建文件读取服务

前言

上篇我们聊了 MCP(Model Context Protocol)是什么,这次直接动手,用 Node.js 手写一个文件读取 MCP Server,并在 Trae 中实际跑起来。

目标:让 AI 能通过 MCP 协议安全地读取你指定的本地文件。

整体架构

MCP Server 的通信链路一句话概括:

用户输入 → LLM 分析 → 匹配 MCP 工具 → StdioServerTransport → stdin → Server 执行 → stdout → 返回结果给 LLM

核心流程:AI 不直接操作文件系统,而是通过标准输入输出(stdio)把请求发给 Server,Server 执行后返回结果。这就是 MCP 的 "USB-C 协议" 本质。

项目初始化

mkdir simple-read-mcp && cd simple-read-mcp
pnpm init
pnpm i zod @modelcontextprotocol/sdk

两个核心依赖:

作用
@modelcontextprotocol/sdkMCP 协议通信层,提供 Server 和 Transport
zod运行时 Schema 校验,定义工具的入参格式

编写 Server 代码

创建 server.js

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

const server = new McpServer({
  name: "simple-read-mcp",
  version: "1.0.0",
});

给 Server 起个名字和版本号,方便调试和区分。

第二步:注册 Tool

server.tool(
  "read_file",                              // 工具名
  "读取指定路径的本地文件内容",               // 描述(LLM 靠这个判断何时调用)
  { path: z.string().describe("...") },     // 参数 Schema
  async ({ path }) => { ... }               // 执行逻辑
);

新版 SDK 的 server.tool() 用 zod 定义参数 Schema,不再需要手写 JSON Schema,非常简洁。LLM 会阅读工具描述和参数说明,自行判断何时调用这个工具。

第三步:启动 stdio 通信

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)中添加:

{
  "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 会自动:

  1. 分析你的意图
  2. 匹配到 read_file 工具
  3. 通过 stdio 把 { path: "d:/workspace/.../server.js" } 发给 Server
  4. Server 读取文件并返回内容
  5. AI 展示给你看

整个过程对用户完全透明,就像 AI 自己会读文件一样。

为什么不用 fs 直接读?

你可能会想:Node.js 本身就能 fs.readFile,为什么要通过 MCP?

关键在于安全边界

  • 直接调用:AI 可以访问任何路径,风险不可控
  • MCP 方式:Server 可以加上路径白名单、权限校验等逻辑,把文件访问限制在安全范围内

在生产环境中,你还可以在 Server 里加入日志记录、限流、加密等能力,这正是 MCP 作为"中间层"的价值。

总结

一个完整的 MCP Server 只需要三步:

步骤做的事情关键 API
实例化创建 Servernew McpServer()
注册工具声明功能 + Schema + 逻辑server.tool()
启动通信通过 stdio 连接客户端StdioServerTransport + server.connect()

加上客户端的 mcp.json 配置,总共不到 50 行代码,就让 AI 获得了安全的文件读取能力。

掌握了这个模式,你可以轻松扩展出更多能力:写文件、搜索文件、调用 API、操作数据库……MCP 的想象空间才刚刚打开。