Chapter 09

测试、调试与发布

建立完整的 MCP Server 质量保障体系,从本地测试到生产发布的全流程

MCP Inspector:最重要的调试工具

MCP Inspector 是官方提供的可视化调试工具,是开发 MCP Server 的必备利器。它提供了一个 Web 界面,无需配置 Claude Desktop 就能直接连接并测试 Server 的所有能力。

  MCP Inspector
  ┌─────────────────────────────────────────────────────────┐
  │  浏览器 Web 界面(http://localhost:5173)                 │
  │                                                         │
  │  ┌─────────────┐  ┌─────────────┐  ┌─────────────┐    │
  │  │   Tools     │  │  Resources  │  │   Prompts   │    │
  │  │  列举工具    │  │  列举资源    │  │  列举模板    │    │
  │  │  调用测试    │  │  读取内容    │  │  获取填充    │    │
  │  └─────────────┘  └─────────────┘  └─────────────┘    │
  │                                                         │
  │  ┌─────────────────────────────────────────────────┐   │
  │  │           JSON-RPC 消息日志(实时)                │   │
  │  │  → {"method":"tools/call","params":{...}}        │   │
  │  │  ← {"result":{"content":[...]}}                  │   │
  │  └─────────────────────────────────────────────────┘   │
  └──────────────────────────┬──────────────────────────────┘
                             │ stdio / HTTP
                        MCP Server
                      (你的 Server 进程)

启动 Inspector

# 基本用法:连接 stdio Server
npx @modelcontextprotocol/inspector node dist/index.js

# 使用 tsx 直接运行 TypeScript
npx @modelcontextprotocol/inspector tsx src/index.ts

# 传递环境变量
GITHUB_TOKEN=xxx npx @modelcontextprotocol/inspector node dist/index.js

# 连接 HTTP Server(Streamable HTTP 传输)
npx @modelcontextprotocol/inspector --transport http --url http://localhost:3000/mcp

# 指定不同端口(默认 5173)
npx @modelcontextprotocol/inspector --port 5174 node dist/index.js

Inspector 主要功能

🔧

Tools 测试

列举所有工具,填写参数并直接调用,查看返回结果和错误信息

📁

Resources 浏览

列举并读取所有资源,查看内容类型和内容本身

💬

Prompts 预览

列举并获取填充后的 Prompt,预览生成的消息序列

📡

消息日志

实时查看所有 JSON-RPC 请求和响应,方便排查协议层面的问题

单元测试策略

MCP Server 的单元测试重点在于测试工具处理函数的业务逻辑,而不是 MCP 协议本身。

提取可测试的处理函数

// handlers/file-tools.ts — 将处理逻辑独立出来,便于测试
import * as fs from "fs/promises";
import * as path from "path";

export interface ReadFileOptions {
  allowedBase: string;
}

export async function readFileHandler(
  filePath: string,
  options: ReadFileOptions
): Promise<{ content: string; error?: string }> {
  const resolved = path.resolve(filePath);
  const base = path.resolve(options.allowedBase);

  if (!resolved.startsWith(base)) {
    return { content: "", error: `访问被拒绝:${resolved}` };
  }

  try {
    const content = await fs.readFile(resolved, "utf-8");
    return { content };
  } catch (err) {
    return { content: "", error: (err as Error).message };
  }
}

使用 Vitest 编写测试

// handlers/file-tools.test.ts
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { readFileHandler } from "./file-tools.js";
import * as fs from "fs/promises";
import * as os from "os";
import * as path from "path";

describe("readFileHandler", () => {
  let tmpDir: string;

  beforeAll(async () => {
    tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "mcp-test-"));
    await fs.writeFile(path.join(tmpDir, "test.txt"), "Hello MCP!");
  });

  afterAll(async () => {
    await fs.rm(tmpDir, { recursive: true });
  });

  it("应该能读取允许目录内的文件", async () => {
    const result = await readFileHandler(
      path.join(tmpDir, "test.txt"),
      { allowedBase: tmpDir }
    );
    expect(result.error).toBeUndefined();
    expect(result.content).toBe("Hello MCP!");
  });

  it("应该拒绝路径遍历攻击", async () => {
    const result = await readFileHandler(
      path.join(tmpDir, "../..", "etc/passwd"),
      { allowedBase: tmpDir }
    );
    expect(result.error).toContain("访问被拒绝");
  });

  it("应该在文件不存在时返回错误", async () => {
    const result = await readFileHandler(
      path.join(tmpDir, "nonexistent.txt"),
      { allowedBase: tmpDir }
    );
    expect(result.error).toBeDefined();
  });
});

集成测试

集成测试验证 MCP 协议层的完整交互,使用 SDK 的 Client 直接连接 Server 进行测试:

// integration.test.ts
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";

describe("MCP Server 集成测试", () => {
  let client: Client;

  beforeAll(async () => {
    // 启动 Server 进程并建立 MCP 连接
    const transport = new StdioClientTransport({
      command: "node",
      args: ["dist/index.js"],
      env: { ...process.env, NODE_ENV: "test" },
    });

    client = new Client(
      { name: "test-client", version: "1.0.0" },
      { capabilities: {} }
    );

    await client.connect(transport);
  });

  afterAll(async () => {
    await client.close();
  });

  it("应该返回工具列表", async () => {
    const { tools } = await client.listTools();
    expect(tools.length).toBeGreaterThan(0);
    // 检查特定工具是否存在
    const addTool = tools.find(t => t.name === "add");
    expect(addTool).toBeDefined();
  });

  it("add 工具应该返回正确结果", async () => {
    const result = await client.callTool({
      name: "add",
      arguments: { a: 3, b: 4 },
    });
    expect(result.isError).toBeFalsy();
    expect(result.content[0].type).toBe("text");
    const textContent = result.content[0] as { type: "text"; text: string };
    expect(textContent.text).toContain("7");
  });
});

发布到 npm / PyPI

npm 发布准备

{
  "name": "@yourname/mcp-server-mytools",
  "version": "1.0.0",
  "description": "MCP Server for ...",
  "main": "dist/index.js",
  "types": "dist/index.d.ts",
  "bin": {
    "mcp-server-mytools": "dist/index.js"
  },
  "files": ["dist", "README.md"],
  "scripts": {
    "build": "tsc",
    "prepublishOnly": "npm run build"
  },
  "keywords": ["mcp", "model-context-protocol", "claude"],
  "peerDependencies": {
    "@modelcontextprotocol/sdk": "^1.0.0"
  }
}
# 构建并发布到 npm
npm run build
npm login
npm publish --access public

# 发布后,用户可以这样使用:
npx @yourname/mcp-server-mytools

README 最佳实践

MCP Server 的 README 应包含:

向 MCP Registry 提交

MCP Registry(registry.modelcontextprotocol.io)是官方的 Server 目录,提交后可以大幅提升发现率。

  1. 确保 npm 包已发布,README 完整清晰
  2. 访问 github.com/modelcontextprotocol/servers
  3. Fork 仓库,在 README.md 中的社区 Server 列表添加你的 Server 条目
  4. 提交 Pull Request,等待 Maintainer 审核
  5. 审核通过后,Server 会出现在 Claude Desktop 和其他 MCP 工具的推荐列表中
发布前检查清单 发布前务必完成:1) 所有功能有单元测试且通过;2) 在真实 Claude Desktop 中完成 end-to-end 测试;3) README 包含安装和配置说明;4) 版本号遵循语义化版本控制(semver);5) 安全审查已完成(无硬编码密钥、无路径遍历漏洞);6) 在 package.json 中包含 "mcp" 关键词便于发现。