引言:用 Node + TypeScript 实现一个基于
stdio的 MCP(Model Context Protocol)服务------mcp-file-counter,并通过@modelcontextprotocol/inspector进行交互式调试。文中提供完整代码、命令与调试步骤,按步骤即可搭建运行。
一、MCP 简介与目标
- MCP 是一种让模型与外部工具安全、结构化交互的协议,常用于将代码、数据库、Web API 等能力接入到模型对话中。
- 本文目标:实现一个名为
mcp-file-counter的 MCP 服务,通过count_files工具统计指定目录的文件数量(支持忽略目录),并用 Inspector 可视化调用与调试。
二、创建项目
环境准备
- Node 版本:
>= 18 - 包管理:推荐
pnpm - 辅助工具:
ni/nr(可选,统一安装/运行体验)
检查与安装:
bash
node -v
pnpm -v
# 若未安装 pnpm
npm i -g pnpm
# 可选:安装 ni/nr(Antfu 的统一包管理器命令)
npm i -g @antfu/ni
初始项目和安装依赖
在空目录中初始化并安装依赖:
bash
# 初始化(可选)
pnpm init -y
# 运行时依赖
ni @modelcontextprotocol/sdk
# 开发依赖
ni -D typescript tsx @types/node @modelcontextprotocol/inspector
若未安装 ni,可使用 pnpm add 等价替代:
bash
pnpm add @modelcontextprotocol/sdk
pnpm add -D typescript tsx @types/node @modelcontextprotocol/inspector
项目结构
建议目录结构如下:
text
package.json
tsconfig.json
src/
server.ts
tools/
fileCount.ts

TypeScript 配置
在项目根目录创建 tsconfig.json:
json
{
"compilerOptions": {
"target": "ES2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"outDir": "dist",
"rootDir": "src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true
}
}
三、实现文件计数工具
新建 src/tools/fileCount.ts:
功能与实现简述:
- 功能:遍历指定根目录,统计普通文件总数,并支持忽略目录或相对路径。
- 实现:以深度优先递归遍历目录,使用
ignore集合过滤,visited集合避免重复计数与符号链接循环。 - 关键点:
withFileTypes: true获取类型;目录继续递归,文件累加;path.relative帮助按根相对路径做忽略匹配。
ts
import fs from "node:fs";
import path from "node:path";
export async function countFiles(root: string, ignore: string[] = []): Promise<number> {
const visited = new Set<string>();
const ig = new Set(ignore.map(s => s.trim()).filter(Boolean));
async function walk(dir: string): Promise<number> {
const dirents = await fs.promises.readdir(dir, { withFileTypes: true });
let count = 0;
for (const d of dirents) {
const full = path.join(dir, d.name);
const rel = path.relative(root, full);
if (ig.has(d.name) || ig.has(rel)) continue;
if (d.isDirectory()) {
count += await walk(full);
} else if (d.isFile()) {
if (!visited.has(full)) {
visited.add(full);
count += 1;
}
}
}
return count;
}
return walk(root);
}
四、实现 MCP Server(Stdio)
传输协议概览:
stdio:通过进程的标准输入/输出传输结构化消息,适合本地或被其他进程托管的场景,零网络依赖。SSE:基于 HTTP 的text/event-stream单向服务器推送通道,客户端建立长连接,服务端持续按事件流推送消息,适合跨网络的实时输出与状态变更。Streamable HTTP:用标准 HTTP 请求/响应承载 MCP 消息,响应体支持分块/增量输出(非 SSE 帧),保持 REST 语义与中间件兼容,适合网关与服务化部署。
StdioServerTransport 能力:
- 封装
process.stdin/process.stdout为消息通道,负责读取、解析与写回协议消息。 - 管理连接的生命周期与错误处理,确保消息循环稳定运行。
- 与
Server.connect(transport)协作完成握手、工具注册与调用路由。对业务代码暴露的主要用法是构造并传入connect,其余由 SDK 管理。
代码说明:
- 定义
Server元信息与能力声明,用于握手阶段告知客户端可用能力。 - 通过
server.addTool注册工具,包括名字、描述和输入参数描述,以及实现execute。 - 创建
StdioServerTransport并server.connect(transport),启动基于stdio的消息循环。
新建 src/server.ts:
ts
import path from "node:path";
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { countFiles } from "./tools/fileCount.js";
const server = new Server(
{ name: "mcp-file-counter", version: "0.1.0" },
{ capabilities: { tools: {} } }
);
server.addTool({
name: "count_files",
description: "Count files under a directory",
inputSchema: {
type: "object",
properties: {
path: { type: "string", default: "." },
ignore: {
type: "array",
items: { type: "string" },
default: ["node_modules", ".git", "dist"]
}
}
},
execute: async (args) => {
const rootArg = typeof args.path === "string" ? args.path : ".";
const ignoreArg = Array.isArray(args.ignore) ? args.ignore : ["node_modules", ".git", "dist"];
const root = path.resolve(process.cwd(), rootArg);
const total = await countFiles(root, ignoreArg);
return { content: [{ type: "text", text: String(total) }] };
}
});
const transport = new StdioServerTransport();
await server.connect(transport);
这个execute 可以理解为最终重新启动了个进程,提供服务,比如
ini
node server.js --path=/my/path
这样,execute函数内部就能获取到命令行参数path(即args.path)
MCP是提供给Agent使用的,Agent会根据名称和描述去调用这个工具(命令),并传入合适的参数。
五、配置脚本和使用inspector调试MCP
在 package.json 中加入关键配置与脚本:
json
{
"type": "module",
"engines": { "node": ">=18" },
"packageManager": "pnpm@9",
"scripts": {
"dev": "tsx src/server.ts",
"build": "tsc -p tsconfig.json",
"start": "node dist/server.js",
"inspect:dev": "mcp-inspector tsx \"tsx src/server.ts\"",
"inspect": "mcp-inspector node dist/server.js"
}
}
Inspector 原理:
mcp-inspector充当 MCP 客户端与可视化 UI,会启动你提供的命令(如tsx src/server.ts),并通过stdio与服务端交换协议消息。- 启动后完成握手(识别服务名与能力),拉取工具列表,渲染交互界面;当你在 UI 中调用某个工具时,它将请求参数序列化为协议消息发送给服务,随后展示返回的内容。
- 这种方式不依赖网络,便于本地开发与调试;若换成
SSE或Streamable HTTP等传输,Inspector 则通过对应通道进行连接。
运行示例:
bash
# 开发态直接运行(Stdio)
nr dev
# 构建并启动(Node 执行 dist)
nr build
nr start
# 使用 Inspector 调试(开发态)
nr inspect:dev
# 使用 Inspector 调试(构建后)
nr build && nr inspect


