从零写一个 MCP Server:文件搜索工具实战

编程

用 100 来行 TypeScript 写一个能在 Claude Code、Cursor 里直接使用的文件搜索 MCP Server:工具定义、参数校验、路径安全、接入配置和调试方法,一篇跑通全流程。

理解 MCP 最快的方式不是读协议文档,而是亲手写一个 Server 并在 Claude Code 或 Cursor 里用起来。这篇教程带你实现一个「本地文件搜索」Server:给 AI 助手一个能力——在你指定的目录里按关键词搜索文件内容,返回命中的文件和上下文片段。全程 100 来行 TypeScript,不需要任何外部服务。

选文件搜索做例子有两个原因:一是足够真实(「让 AI 搜我本机/内网的资料」是高频需求),二是刚好覆盖 MCP 开发的所有关键点——工具定义、参数校验、安全边界、结果格式。如果你还没想清楚要不要用 MCP,先读《MCP vs Function Calling:AI 工具调用到底该选哪个》

代码编辑器中的 TypeScript 项目
代码编辑器中的 TypeScript 项目

一个最小可用的 MCP Server:一个入口文件、一个工具、一条 stdio 传输通道。

准备工作与项目初始化

环境要求:Node.js 18+,以及任意一个 MCP Host(本文用 Claude Code 和 Cursor 演示)。初始化项目:

mkdir mcp-file-search && cd mcp-file-search
npm init -y
npm install @modelcontextprotocol/sdk zod
npm install -D typescript @types/node
npx tsc --init

@modelcontextprotocol/sdk 是官方 TypeScript SDK,封装了 JSON-RPC 协议细节;zod 用来声明和校验工具参数。SDK 的接口在持续演进,本文写法以发布时的官方文档为准,跑不通时先对照 SDK 的 README 检查 API 名称。

核心代码:定义 Server 和搜索工具

创建 src/index.ts

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
import fs from "node:fs/promises";
import path from "node:path";

// 允许搜索的根目录:从启动参数读取,这是本 Server 的安全边界
const ROOT = path.resolve(process.argv[2] ?? ".");

const server = new McpServer({ name: "file-search", version: "1.0.0" });

server.registerTool(
  "search_files",
  {
    description:
      "在指定目录中按关键词搜索文本文件内容,返回命中文件路径和上下文片段。" +
      "适用于查找文档、笔记、代码中的关键词。",
    inputSchema: {
      query: z.string().min(1).describe("要搜索的关键词"),
      subdir: z.string().default(".").describe("相对根目录的子目录,默认全部"),
      maxResults: z.number().int().min(1).max(50).default(10),
    },
  },
  async ({ query, subdir, maxResults }) => {
    const base = path.resolve(ROOT, subdir);
    // 防目录穿越:解析后的路径必须仍在 ROOT 之内
    if (!base.startsWith(ROOT)) {
      return {
        content: [{ type: "text", text: "错误:目录超出允许范围" }],
        isError: true,
      };
    }
    const hits = await searchDir(base, query, maxResults);
    return {
      content: [{
        type: "text",
        text: hits.length
          ? hits.map(h => `${h.file}:${h.line}\n  ${h.snippet}`).join("\n\n")
          : `未找到包含「${query}」的文件`,
      }],
    };
  }
);

const transport = new StdioServerTransport();
await server.connect(transport);

搜索函数本身是普通的 Node 代码——递归遍历目录、逐行匹配、截取上下文,跳过 node_modules、二进制文件和超大文件即可,完整实现不到 50 行,这里不赘述。三个设计点值得说明:

  • 描述写给模型看description 决定模型什么时候、怎么用这个工具。写清楚「适用于什么」比写技术细节重要。
  • 参数上限maxResults 封顶 50,防止模型请求超大结果撑爆上下文。工具返回的内容最终都会进入模型的上下文窗口,节制是美德。
  • 路径安全ROOT 白名单 + 穿越检查是底线。Server 的权限就是模型的权限,你把根目录设为 /,就等于允许 AI 读你整块硬盘。

编译并试运行:

npx tsc && node dist/index.js ~/Documents/notes

进程静默等待 stdin 输入即为正常——stdio 传输模式下,Host 会以子进程方式拉起它并通过标准输入输出通信。

接入 Claude Code 和 Cursor

Claude Code:在项目里执行一条命令即可注册:

claude mcp add file-search -- node /绝对路径/dist/index.js ~/Documents/notes

或者在项目根目录的 .mcp.json 里声明(适合团队共享):

{
  "mcpServers": {
    "file-search": {
      "command": "node",
      "args": ["/绝对路径/dist/index.js", "/Users/you/Documents/notes"]
    }
  }
}

Cursor:在 ~/.cursor/mcp.json(全局)或项目 .cursor/mcp.json 写入同样格式的配置,重启后在 MCP 设置里能看到 Server 状态变绿。

接入后直接对助手说:「用 file-search 搜一下笔记里关于报销流程的内容」,就能看到模型发起 search_files 调用、拿到结果并整理回答。

终端中运行的服务进程
终端中运行的服务进程

stdio 模式下 Server 由 Host 拉起,注册配置只需指定启动命令和参数。

调试:Inspector 是你的第一工具

MCP 官方提供了调试利器 Inspector,不经过任何 AI 应用直接连接你的 Server:

npx @modelcontextprotocol/inspector node dist/index.js ~/Documents/notes

浏览器里会打开一个调试界面,能看到 Server 声明的工具列表、手动填参数发起调用、查看原始返回。先用 Inspector 验证工具行为正确,再接入 Host,能省掉大量「不知道是 Server 错还是模型错」的排查时间。另外两个高频排查点:

  • stdio 模式下不要往 stdout 打日志(会污染协议通道),调试信息一律走 console.error
  • Host 里工具没出现,通常是配置路径不是绝对路径、或 Node 版本不符,看 Host 的 MCP 日志(Claude Code 里 claude mcp list 可查状态)。
调试界面与日志输出
调试界面与日志输出

先让 Inspector 里每个工具都手动跑通,再谈模型调用——分层排查永远最快。

常见坑

表现修法
stdout 打了日志Host 报协议解析错误日志全部改走 stderr
返回内容过大上下文被撑爆、回答变慢变差限制结果条数和片段长度,提供分页参数
工具描述含糊模型不用或乱用工具描述里写清适用场景和参数含义
根目录给太宽敏感文件可被读取白名单目录 + 穿越检查,默认最小范围
相对路径配置Server 启动失败Host 配置中一律用绝对路径

适用人群与下一步

适合想把内部数据、私有工具接入 Claude Code / Cursor 等 AI 应用的开发者。跑通本文后有三个自然的延伸方向:把搜索升级为 Embedding 语义检索(配合向量数据库,就是一个私有 RAG Server);加上 resources 能力让 Host 直接读取文件全文;以及改用 Streamable HTTP 传输部署成远程 Server 供团队共用——远程部署后鉴权和权限控制就成了必修课,参考《AI Agent 为什么容易失控》里的工具权限设计。

如果只是想用现成能力,不必自己写:文件系统、GitHub、数据库等官方和社区 Server 已经很丰富,先搜后写。

常见问题

Q:Python 写法差别大吗? A:思路完全一致。官方 Python SDK(FastMCP 风格)用装饰器定义工具,代码量相当,团队用什么语言顺手就用什么。

Q:一个 Server 该放多少个工具? A:按职责内聚,一般 3–10 个。工具太多会占用大量上下文并干扰模型选择;不相关的能力拆成不同 Server,让用户按需挂载。

Q:怎么控制哪些操作需要确认? A:确认机制在 Host 侧:Claude Code、Cursor 都会对工具调用请求用户批准(可配置白名单)。Server 侧的责任是把读和写拆成不同工具,别把危险操作藏在无害名字里。

小结

一个 MCP Server 的最小构成就三件事:声明身份、注册工具、接上传输。把参数校验和路径安全做扎实,用 Inspector 验证后接入 Host,你的 AI 助手就多了一个真实可用的能力。从这个 100 来行的骨架出发,替换掉搜索逻辑,就能长出数据库查询、内部 API、工单系统等任何你需要的 Server。