1. 项目概述:一个为AI时代量身定制的脚手架工具

如果你正在为Claude、Cursor或者GitHub Copilot这样的AI助手开发工具,那你一定听说过MCP。Model Context Protocol,这个由Anthropic推出的开放协议,正在成为连接AI智能体与外部世界的标准桥梁。简单来说,它允许你的AI助手调用你编写的工具,就像给它装上了可以操作现实世界数据的“手”和“眼”。但问题来了,从零开始搭建一个符合MCP标准、生产就绪的服务器,远不止写几个工具函数那么简单。你需要考虑类型安全、测试覆盖、持续集成、错误处理、发布流程……这一系列工程化问题,足以让一个简单的想法在启动阶段就陷入泥潭。

这正是 create-mcp-server-pro 诞生的原因。它不是一个普通的项目模板,而是一个凝聚了实战经验的“一站式”脚手架生成器。它的作者 ofershap 在构建了四个真实上线的MCP服务器、累计超过100个工具后,将其中所有被验证过的“最佳实践”和“避坑指南”都固化到了这个CLI工具里。当官方的TypeScript脚手架模板在2024年11月归档后,这个项目就成了社区中事实上的生产级替代方案。它面向的,正是那些希望快速启动一个健壮、可维护、能直接交付给团队或社区的MCP服务器开发者。无论你是想为团队内部打造一个提效工具,还是计划发布一个公开的MCP服务,这个脚手架都能帮你跳过繁琐的初始化配置,直接聚焦于核心业务逻辑的开发。

2. 核心优势解析:为什么选择它而非其他方案?

在技术选型时,我们总希望工具能“开箱即用”,而不是成为一个需要持续维护的技术债。 create-mcp-server-pro 的设计哲学正是如此。为了让你更直观地理解它的价值,我们不妨将其与社区中常见的几种方案进行深度对比。这不仅仅是功能列表的罗列,更是工程成熟度的体现。

2.1 与官方及社区模板的全面对比

官方脚手架曾是许多人的起点,但其归档状态意味着你将无法获得任何安全更新或对新版SDK的适配。而一些社区模板为了追求极简,往往牺牲了生产环境所必需的环节,比如测试和自动化。 create-mcp-server-pro 则在这两者之间找到了完美的平衡点:既保持了与官方MCP SDK的完全兼容,确保你的工具能在所有主流AI客户端中稳定运行,又补全了从开发到上线的全链路工具链。

架构完整性对比

  • 官方模板(已归档) :提供了最基础的MCP服务器骨架和官方SDK集成,但缺乏现代前端工程所标配的测试框架、代码规范检查和自动化流水线。它更像一个“概念验证”的起点,离“生产就绪”还有相当距离。
  • FastMCP等社区模板 :它们可能基于非官方的SDK封装,虽然在某些场景下更便捷,但也带来了潜在的兼容性风险。更重要的是,它们通常只关注“如何让工具跑起来”,而忽略了“如何让工具持续、稳定地跑下去”。测试、代码质量、发布流程这些保障软件生命周期的关键环节常常是缺失的。
  • create-mcp-server-pro :它的目标非常明确——生成一个你明天就能部署上线的项目。它内置了完整的开发体验(ESLint + Prettier + TypeScript严格模式)、质量保障(Vitest单元测试 + 覆盖率报告)、自动化流程(GitHub Actions CI/CD),甚至贴心地为AI助手准备了友好的README文档。这一切,都是为了让你能心无旁骛地实现工具逻辑本身。

2.2 深入核心特性:不仅仅是生成文件

这个脚手架生成的不是一个空壳,而是一个配备了精良武器的作战单元。让我们拆解几个关键特性,看看它们在实际开发中如何发挥作用:

1. 面向AI优化的项目结构 生成的项目采用了“逻辑与注册分离”的清晰架构。所有工具的实现都集中在 src/tools.ts (或未来可扩展的 src/tools/ 目录),而MCP服务器的初始化和工具注册则在 src/index.ts 中完成。这种分离带来了巨大的好处:你可以对工具的核心业务逻辑进行纯粹的单元测试,而无需启动一个完整的MCP服务器进程。这极大地提升了测试的效率和可靠性。

2. 内置的错误处理范式 在AI调用工具的语境下,错误信息不仅仅是给开发者看的,更是给AI模型看的。脚手架生成的示例工具代码中,包含了一套标准的错误处理模式:

try {
  // 你的业务逻辑
} catch (err) {
  const message = err instanceof Error ? err.message : String(err);
  return {
    content: [{ type: "text", text: `Error: ${message}` }],
    isError: true, // 关键:明确告知AI这是一个错误结果
  };
}

更重要的是,它鼓励你返回“可操作的”错误信息。例如,对于认证失败,返回“ API_TOKEN 环境变量未设置。请在您的MCP客户端配置中设置。”远比一个简单的“Authentication failed”更有价值,因为AI可以据此给用户更明确的指导。

3. 为AI而写的元数据 这是很多新手开发者容易忽略,但却至关重要的部分。脚手架强制要求为每个工具参数(使用Zod schema定义)添加 .describe() 描述。这些描述是AI模型理解如何调用你工具的主要依据。例如:

{
  query: z.string().describe("用于搜索npm包的关键词,例如 'react', 'utility library'"),
  limit: z.number().int().min(1).max(50).default(10).describe("返回结果的最大数量,介于1到50之间"),
}

模糊的描述会导致AI误用你的工具,而清晰、具体的描述则能大幅提升工具调用的准确率和用户体验。

4. 完整的CI/CD与发布流水线 项目生成的 .github/workflows/ 目录下包含了两套工作流:

  • ci.yml : 在Node.js 20和22环境下运行矩阵测试,依次执行代码检查(lint)、类型检查(typecheck)、构建(build)和单元测试(test)。这确保了每次提交的代码质量。
  • release.yml : 与 semantic-release 集成。当你推送一个符合语义化版本控制(如 v1.0.0 )的Git标签时,它会自动生成变更日志、更新版本号、发布到npm,并在GitHub上创建Release。这彻底自动化了发布流程,避免了手动操作可能带来的错误。

3. 从零到一:手把手创建你的第一个MCP服务器

理论说得再多,不如亲手实践。接下来,我将带你完整走一遍使用 create-mcp-server-pro 创建、开发、测试并连接一个MCP服务器的全过程。我们会以一个“天气查询服务器”为例,因为它涉及了常见的API调用、参数处理和错误反馈场景。

3.1 环境准备与项目初始化

首先,确保你的开发环境已经安装了Node.js(版本18或以上,推荐20或22)和npm。然后,打开你的终端,执行初始化命令。

交互式创建(推荐初次使用)

npx create-mcp-server-pro weather-server

执行后,CLI会启动一个友好的交互式问答界面:

  1. Server name : 输入你的npm包名,例如 mcp-server-weather 。如果你打算将来发布到npm,这个名字需要全局唯一。脚手架会提供一个默认值( mcp-server-weather-server ),但建议你改得更简洁、达意。
  2. Description : 用一句话描述你的服务器是做什么的。例如:“A MCP server that provides current weather and forecast data.” 这个描述会出现在 package.json 和 README中。
  3. Authentication : 询问你的服务器是否需要API密钥。对于天气服务器,我们通常需要调用如OpenWeatherMap这样的第三方服务,所以这里选择 Yes 。这会在项目中生成处理环境变量的样板代码。
  4. Author : 输入你的名字或团队名称。

回答完问题后,脚手架会在当前目录下创建一个名为 weather-server 的文件夹,并自动生成所有文件。

非交互式创建(适用于自动化或脚本) 如果你已经很清楚要创建什么,或者想在CI流程中集成,可以使用命令行参数一次性完成:

npx create-mcp-server-pro weather-server \
  --name mcp-server-weather \
  --description "MCP server for weather data" \
  --auth \
  --author "Your Name"

使用 --auth 标志来声明需要认证,使用 --name --description 可以跳过所有提示。

3.2 生成的项目结构深度解读

进入项目目录 cd weather-server ,让我们看看脚手架为我们打造了一个怎样的“精装修”工程。

weather-server/
├── src/
│   ├── index.ts          # MCP服务器入口:初始化、工具注册、传输层配置
│   └── tools.ts          # 工具实现层:所有工具的业务逻辑和错误处理
├── tests/
│   └── tools.test.ts     # 基于Vitest的单元测试,包含fetch模拟
├── .github/
│   └── workflows/
│       ├── ci.yml        # CI流水线:lint, typecheck, build, test (Node 20/22)
│       └── release.yml   # 发布流水线:基于semantic-release的自动化发布
├── package.json          # 完整的脚本、依赖、lint-staged配置
├── tsconfig.json         # 严格的TypeScript配置 (ES2022, NodeNext)
├── tsup.config.ts        # 构建配置:输出ESM,针对Node 20优化
├── vitest.config.ts      # 测试配置:全局模式,v8覆盖率
├── eslint.config.js      # ESLint 9 扁平化配置,集成TypeScript严格规则
├── .prettierrc.json      # 代码格式化配置
├── .gitignore
├── LICENSE               # MIT 许可证
└── README.md             # 面向AI和开发者的友好文档

关键文件解析

  • src/index.ts : 这是服务器的“大脑”。它使用官方 @modelcontextprotocol/sdk 创建 McpServer 实例,将 src/tools.ts 中定义的工具注册进来,最后通过 StdioServerTransport 启动一个标准输入/输出的服务器进程。这是MCP客户端(如Claude Desktop)与之通信的标准方式。
  • src/tools.ts : 这是你的“武器库”。里面已经预置了一个示例工具 get_weather (如果你选择了需要认证)。这个示例完美展示了工具定义的四个核心部分:名称、描述、输入模式(Zod Schema)和异步处理器。你的主要工作就是在这里增删改查你的工具。
  • tests/tools.test.ts : 质量保障的基石。它使用Vitest框架,并演示了如何通过 vi.spyOn 来模拟 fetch 请求,从而对工具逻辑进行隔离测试,不依赖真实的网络和API。
  • .github/workflows/ci.yml : 你的自动化质量门禁。它定义了一个在每次推送到GitHub时都会运行的工作流,确保代码风格一致、类型安全、能够成功构建并通过所有测试。

3.3 安装依赖与首次运行

生成项目后,第一步是安装依赖:

npm install
# 或使用你喜欢的包管理器,如 yarn 或 pnpm

安装完成后,你可以立即运行几个核心命令来验证项目是否就绪:

  1. 代码检查 npm run lint 。这会运行ESLint,确保代码符合预设的规范。
  2. 类型检查 npm run typecheck 。运行TypeScript编译器进行检查(不输出文件),确保类型安全。
  3. 运行测试 npm test 。执行Vitest单元测试。初始状态下,示例工具的测试应该会通过。
  4. 构建项目 npm run build 。使用tsup将TypeScript代码打包成单一的、可在Node.js环境中运行的JavaScript文件(输出到 dist/index.js )。这是最终要发布或运行的产物。

如果所有这些命令都成功执行,恭喜你,一个具备生产基础能力的MCP服务器项目已经搭建完毕。

4. 核心开发:实现一个真实的天气查询工具

现在,让我们抛开示例,从头实现一个真正可用的天气查询工具。我们将调用一个免费的天气API,并处理各种边界情况。

4.1 设计工具:明确输入、输出与行为

在动手写代码之前,先进行设计。一个好的工具应该对AI“知无不言”,即通过清晰的描述让AI完全理解其用途和用法。

  • 工具名称 get_current_weather 。遵循snake_case命名规范,避免LLM在token化时产生歧义。
  • 工具描述 :“获取指定城市的当前天气情况,包括温度、体感温度、湿度、天气状况和风速。需要有效的OpenWeatherMap API密钥。”
  • 输入参数
    • city (字符串,必需): 城市名称,如“Beijing”或“London,UK”。支持“城市名,国家码”格式以提高准确性。
    • units (字符串,可选): 温度单位。 metric (摄氏度), imperial (华氏度),默认 metric
  • 输出 :结构化的文本,包含天气信息。如果出错,返回明确的错误信息。

4.2 编写工具实现

打开 src/tools.ts 文件,我们将替换或新增这个工具。首先,确保你已获取一个OpenWeatherMap的免费API密钥。

// src/tools.ts
import { z } from "zod";
// 假设我们有一个调用天气API的函数
import { fetchWeatherData } from "../lib/weather-api";

export function defineWeatherTools(server: McpServer) {
  // 工具:获取当前天气
  server.tool(
    "get_current_weather",
    "获取指定城市的当前天气情况,包括温度、体感温度、湿度、天气状况(如晴、雨)和风速。需要有效的OpenWeatherMap API密钥。",
    {
      city: z
        .string()
        .min(1)
        .describe("城市名称,例如 'Beijing'。为提高准确性,可使用 'London,GB' 格式(城市名,国家码)。"),
      units: z
        .enum(["metric", "imperial", "standard"])
        .default("metric")
        .describe("温度单位:'metric' 为摄氏度,'imperial' 为华氏度,'standard' 为开尔文。默认为 'metric'。"),
    },
    async ({ city, units }) => {
      // 关键步骤1:检查必要的环境变量
      const apiKey = process.env.OPENWEATHER_API_KEY;
      if (!apiKey) {
        return {
          content: [
            {
              type: "text",
              text: `错误:缺少 OpenWeatherMap API 密钥。请设置环境变量 OPENWEATHER_API_KEY。您可以在 https://home.openweathermap.org/api_keys 免费申请一个。`,
            },
          ],
          isError: true,
        };
      }

      try {
        // 关键步骤2:调用外部API
        const weatherData = await fetchWeatherData(apiKey, city, units);

        // 关键步骤3:格式化对AI友好的响应
        const tempUnit = units === "imperial" ? "°F" : "°C";
        const speedUnit = units === "imperial" ? "mph" : "m/s";
        
        const responseText = `
当前 ${weatherData.cityName} 的天气:
- 状况:${weatherData.condition} (${weatherData.description})
- 温度:${weatherData.temp} ${tempUnit}(体感温度 ${weatherData.feelsLike} ${tempUnit})
- 湿度:${weatherData.humidity}%
- 气压:${weatherData.pressure} hPa
- 风速:${weatherData.windSpeed} ${speedUnit},风向 ${weatherData.windDeg}°
- 能见度:${weatherData.visibility / 1000} km
- 数据更新时间:${new Date(weatherData.timestamp * 1000).toLocaleString()}
        `.trim();

        return {
          content: [{ type: "text", text: responseText }],
        };
      } catch (error) {
        // 关键步骤4:精细化错误处理
        let errorMessage = "获取天气数据时发生未知错误。";
        
        if (error instanceof Error) {
          // 可以根据错误信息推断原因,给AI更明确的提示
          if (error.message.includes("401") || error.message.includes("Invalid API key")) {
            errorMessage = `认证失败:API密钥无效。请检查 OPENWEATHER_API_KEY 环境变量是否正确。`;
          } else if (error.message.includes("404") || error.message.includes("city not found")) {
            errorMessage = `未找到城市 '${city}'。请检查城市名拼写,或尝试添加国家代码(如 'London,GB')。`;
          } else if (error.message.includes("429")) {
            errorMessage = `请求过于频繁,已触发API速率限制。请稍后再试。`;
          } else if (error.message.includes("network")) {
            errorMessage = `网络错误:无法连接到天气服务。请检查网络连接。`;
          } else {
            errorMessage = `API请求失败:${error.message}`;
          }
        }

        return {
          content: [{ type: "text", text: `错误:${errorMessage}` }],
          isError: true,
        };
      }
    },
  );

  // 你可以继续在这里添加更多工具,例如 get_weather_forecast
}

代码要点解析

  1. 环境变量检查 :在工具处理函数一开始就检查必要的配置( OPENWEATHER_API_KEY )。如果缺失,立即返回一个对用户和AI都有指导意义的错误,而不是让程序在深层调用中崩溃。
  2. 结构化响应 :将API返回的原始JSON数据,转换成人性化、易于阅读的文本格式。AI模型处理结构化的自然语言远比处理原始的JSON更高效。
  3. 精细化错误处理 try-catch 块捕获所有可能的异常。我们不是简单地抛出或返回一个笼统的错误,而是根据错误信息的内容(如状态码)来推断根本原因,并提供具体的解决建议。这能极大提升AI助手在遇到问题时的应对能力。
  4. 工具分离 :我们将工具的定义集中在一个函数 defineWeatherTools 中。这保持了代码的模块化,便于管理和测试。

接下来,我们需要在 src/index.ts 中注册这个工具集:

// src/index.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
// 导入我们定义的工具集
import { defineWeatherTools } from "./tools.js";

const server = new McpServer({
  name: "weather-server", // 与 package.json 中的 name 一致
  version: "1.0.0",
});

// 注册所有天气相关的工具
defineWeatherTools(server);

// 启动服务器
const transport = new StdioServerTransport();
await server.connect(transport);

4.3 为工具编写单元测试

一个没有测试的工具是不可靠的。脚手架已经为我们配置好了Vitest。现在,为 get_current_weather 工具编写测试。

// tests/tools.test.ts
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
// 注意:我们测试的是工具逻辑,而不是通过服务器调用。
// 因此,我们需要模拟一个工具执行环境。一种方法是直接测试工具处理函数。
// 更清晰的做法是重构工具定义,使其处理函数可独立导出和测试。

// 假设我们重构了 tools.ts,将处理函数独立导出
// import { getCurrentWeatherHandler } from "../src/tools";
// 为了示例,我们这里模拟一个简单的测试逻辑

describe("Weather Tools", () => {
  beforeEach(() => {
    // 在每个测试前,模拟 fetch 函数
    vi.spyOn(globalThis, "fetch").mockImplementation(() =>
      Promise.resolve({
        ok: true,
        json: () =>
          Promise.resolve({
            name: "Beijing",
            main: { temp: 22, feels_like: 21, humidity: 65, pressure: 1013 },
            weather: [{ main: "Clear", description: "clear sky" }],
            wind: { speed: 3.5, deg: 180 },
            visibility: 10000,
            dt: Math.floor(Date.now() / 1000),
          }),
      } as Response)
    );
    // 设置必要的环境变量
    process.env.OPENWEATHER_API_KEY = "test-api-key";
  });

  afterEach(() => {
    vi.restoreAllMocks(); // 清理模拟
    delete process.env.OPENWEATHER_API_KEY;
  });

  it("should return formatted weather data for a valid city", async () => {
    // 这里调用你工具的实际处理函数
    // const result = await getCurrentWeatherHandler({ city: "Beijing", units: "metric" });
    // expect(result.content[0].text).toContain("Beijing");
    // expect(result.content[0].text).toContain("22°C");
    // expect(result.isError).toBeFalsy();
    
    // 由于我们未展示具体重构,此处用通用断言示意
    expect(true).toBe(true); // 占位,实际测试应验证具体逻辑
  });

  it("should return error if API key is missing", async () => {
    delete process.env.OPENWEATHER_API_KEY;
    // const result = await getCurrentWeatherHandler({ city: "Beijing", units: "metric" });
    // expect(result.isError).toBe(true);
    // expect(result.content[0].text).toContain("OPENWEATHER_API_KEY");
    expect(true).toBe(true);
  });

  it("should handle API errors gracefully", async () => {
    vi.spyOn(globalThis, "fetch").mockImplementation(() =>
      Promise.resolve({
        ok: false,
        status: 404,
        json: () => Promise.resolve({ message: "city not found" }),
      } as Response)
    );
    // const result = await getCurrentWeatherHandler({ city: "InvalidCity", units: "metric" });
    // expect(result.isError).toBe(true);
    // expect(result.content[0].text).toContain("未找到城市");
    expect(true).toBe(true);
  });
});

测试策略要点

  • 模拟外部依赖 :使用 vi.spyOn 模拟 fetch ,确保测试不依赖真实的网络和外部API,保证测试的稳定性和速度。
  • 测试成功路径 :验证工具在正常输入下能返回正确格式化的结果。
  • 测试错误路径 :这是更重要的部分。必须测试环境变量缺失、API返回错误(如404、401)、网络异常等场景,确保工具能返回有意义的错误信息。
  • 独立可测试性 :为了便于测试,考虑将工具的处理函数(handler)从 server.tool 注册中分离出来,使其成为一个纯函数,可以独立导入和测试。这是脚手架推荐的最佳实践。

运行 npm test 来执行这些测试。Vitest会提供清晰的输出和覆盖率报告。

5. 连接与配置:让你的工具被AI助手调用

开发并测试完成后,你需要将构建好的服务器连接到AI客户端。脚手架生成的 README.md 已经包含了主流的配置方法,这里再补充一些细节和避坑指南。

5.1 构建与本地测试

首先,构建你的服务器:

npm run build

这将在 dist 目录下生成可执行文件。你可以直接运行它来测试服务器是否能正常启动:

node dist/index.js

如果看到服务器在等待标准输入(通常没有输出或只有一行启动日志),说明它运行正常。按 Ctrl+C 退出。

5.2 配置Claude Desktop

Claude Desktop是Anthropic官方的桌面客户端,支持MCP。

  1. 找到配置文件

    • macOS : ~/Library/Application Support/Claude/claude_desktop_config.json
    • Windows : %APPDATA%\Claude\claude_desktop_config.json
    • Linux : ~/.config/Claude/claude_desktop_config.json
  2. 编辑配置文件 :如果文件不存在,就创建它。添加你的服务器配置:

{
  "mcpServers": {
    "weather-server": {
      "command": "node",
      "args": ["/绝对路径/到/你的/weather-server/dist/index.js"],
      "env": {
        "OPENWEATHER_API_KEY": "你的实际API密钥"
      }
    }
  }
}

重要提示 args 中的路径必须是 绝对路径 。相对路径在Claude Desktop的上下文中可能无法解析。你可以使用 pwd 命令(在项目根目录)来获取绝对路径。

  1. 重启Claude Desktop :保存配置文件后,完全退出并重启Claude Desktop客户端。

  2. 验证连接 :重启后,在Claude的对话窗口中,你可以尝试问:“现在的北京天气怎么样?”。如果配置正确,Claude应该会识别并调用你的 get_current_weather 工具。

5.3 配置Cursor IDE

Cursor是深度集成AI的代码编辑器,也支持MCP。

  1. 在你的 项目根目录 下创建(或编辑)文件: .cursor/mcp.json
  2. 添加配置:
{
  "mcpServers": {
    "weather-server": {
      "command": "node",
      "args": ["${workspaceFolder}/dist/index.js"],
      "env": {
        "OPENWEATHER_API_KEY": "你的实际API密钥"
      }
    }
  }
}

注意:Cursor支持 ${workspaceFolder} 变量,它会自动替换为当前打开的项目根目录路径,这比使用绝对路径更灵活。

  1. 重启Cursor或重新加载窗口。之后,在Cursor的AI对话中,你就可以使用天气查询功能了。

5.4 配置VS Code与GitHub Copilot

如果你使用VS Code并安装了GitHub Copilot Chat,也可以通过MCP集成。

  1. 在项目根目录创建(或编辑)文件: .vscode/mcp.json
  2. 添加配置:
{
  "servers": {
    "weather-server": {
      "type": "stdio",
      "command": "node",
      "args": ["${workspaceFolder}/dist/index.js"],
      "env": {
        "OPENWEATHER_API_KEY": "你的实际API密钥"
      }
    }
  }
}
  1. 重启VS Code。在Copilot Chat中,你就可以调用你的工具了。

5.5 常见连接问题排查

问题1:Claude Desktop提示“无法连接服务器”或没有任何反应。

  • 检查点1:路径问题 。这是最常见的原因。确保 args 中的路径是 绝对路径 ,并且指向的是构建后的 dist/index.js 文件。
  • 检查点2:环境变量 。确保在配置的 env 字段中正确设置了所有必需的环境变量(如 OPENWEATHER_API_KEY )。
  • 检查点3:服务器可执行 。在终端中手动运行 node /你的绝对路径/dist/index.js ,看服务器是否能正常启动而不报错。
  • 检查点4:配置文件格式 。确保JSON格式正确,没有多余的逗号,键名用双引号。

问题2:AI助手看不到我的工具。

  • 检查点1:工具注册 。确认你的工具在 src/index.ts 中通过 server.tool() 正确注册。
  • 检查点2:服务器启动日志 。有些MCP服务器在启动时会打印已注册的工具列表。检查你的 src/index.ts ,可以在 server.connect() 前添加 console.log(server) 来查看(生产环境记得移除)。
  • 检查点3:客户端重启 。修改MCP配置后, 必须完全重启AI客户端 (Claude Desktop、Cursor等),配置才会被重新加载。

问题3:工具被调用但返回错误。

  • 检查点1:AI客户端的错误信息 。仔细阅读AI返回的错误信息,它通常来自你的工具处理器。
  • 检查点2:服务器日志 。在工具处理函数中添加临时的 console.log 或使用调试器,查看执行流程和变量状态。由于MCP服务器运行在stdio模式,其控制台输出可能会被重定向到AI客户端或日志文件,具体取决于客户端实现。一个简单的调试方法是将错误信息写入一个本地文件。

6. 进阶指南与最佳实践

当你掌握了基础开发流程后,以下进阶实践能帮助你构建更强大、更健壮的MCP服务器。

6.1 工具设计哲学:让AI更好地理解你

  1. 单一职责原则 :一个工具只做一件事,并且做好。不要创建一个“万能”的 handle_weather 工具,而是拆分成 get_current_weather get_forecast get_historical_weather 等。这能让AI更准确地判断在什么场景下调用哪个工具。
  2. 描述即契约 :工具和参数的描述是AI理解你的唯一途径。用自然语言清晰地说明:
    • 工具做什么 :例如,“根据城市名和日期范围,查询历史天气统计数据,包括平均温度、最高/最低温和降水量。”
    • 参数是什么 :例如, date 参数描述为“查询日期,格式为 YYYY-MM-DD。只能查询过去5年内的数据。”
    • 可能的输出格式 :虽然描述中不强制,但让AI知道你会返回结构化文本还是JSON,有助于它处理结果。
  3. 善用Zod Schema的约束 :利用Zod的验证功能为AI提供“护栏”。
    z.string().email().describe("用户的电子邮件地址,必须是有效的邮箱格式。")
    z.number().int().min(1).max(100).describe("分页大小,必须在1到100之间。")
    z.enum(["asc", "desc"]).default("desc").describe("排序顺序:'asc' 升序,'desc' 降序。")
    
    这些约束会在AI尝试调用时提供提示,减少无效调用。

6.2 项目结构与规模化

当工具数量增多时, src/tools.ts 文件会变得臃肿。建议采用模块化结构:

src/
├── index.ts
├── tools/
│   ├── index.ts          // 聚合导出所有工具
│   ├── weather.ts        // 天气相关工具
│   ├── finance.ts        // 金融相关工具
│   └── utils.ts          // 通用工具
└── lib/
    ├── api-client.ts     // 封装第三方API调用
    └── formatters.ts     // 数据格式化函数

src/tools/index.ts 中统一导入并注册所有工具模块,保持 src/index.ts 的简洁。

6.3 安全与认证

如果你的工具需要访问敏感数据或受限制的API,认证至关重要。

  1. 环境变量注入 :如我们的天气例子所示,永远不要将API密钥硬编码在代码中。通过客户端的MCP配置 ( env 字段) 注入环境变量。
  2. 令牌验证 :对于需要用户级认证的工具,可以在工具内部设计一个 auth_token 参数,并在处理器开头验证其有效性。或者,更MCP的方式是利用MCP协议未来可能支持的资源(Resources)和提示(Prompts)来进行复杂的认证流。
  3. 输入净化 :永远不要信任来自AI的输入。即使有Zod验证,对于要拼接进URL或数据库查询的参数,也要进行适当的编码或转义。

6.4 性能与可靠性

  1. 设置超时 :在调用外部API时,务必设置请求超时。可以使用 AbortController 或你选择的HTTP客户端库(如 axios )的超时配置。
    const controller = new AbortController();
    const timeoutId = setTimeout(() => controller.abort(), 10000); // 10秒超时
    try {
      const response = await fetch(url, { signal: controller.signal });
      // ... 处理响应
    } finally {
      clearTimeout(timeoutId);
    }
    
  2. 实现重试逻辑 :对于可能因网络波动而失败的临时性错误(如HTTP 5xx),可以加入简单的重试机制。
  3. 缓存策略 :对于频繁查询且数据变化不频繁的接口(如天气数据,更新周期可能数分钟),可以考虑在工具内部实现一个简单的内存缓存(如使用 Map node-cache 库),避免重复调用API,提升响应速度并节省配额。

6.5 调试与监控

  1. 结构化日志 :使用如 pino winston 等日志库,输出结构化的JSON日志。这便于你通过工具(如 jq )过滤和分析日志,尤其是在排查生产环境问题时。
  2. 健康检查工具 :可以创建一个简单的 health_check 工具,返回服务器的状态、依赖的API连通性等信息。这对于运维监控很有帮助。
  3. 性能追踪 :在工具处理函数的开头和结尾记录时间戳,计算耗时并输出到日志。这有助于你发现性能瓶颈。

7. 发布与部署:将你的工具分享给世界

当你对自己的MCP服务器感到满意时,可以将其发布到npm,让其他人也能一键安装使用。

7.1 发布到npm

  1. 准备发布

    • 确保 package.json 中的 name version description author license 字段都已正确填写。
    • package.json 中, bin 字段已经由脚手架配置好(例如 "bin": { "mcp-server-weather": "./dist/index.js" } ),这确保了用户可以通过 npx 直接运行你的服务器。
    • 运行 npm run build 确保最新的代码已构建。
    • 运行 npm test 确保所有测试通过。
  2. 登录npm :如果你还没有npm账户,先去 https://www.npmjs.com/ 注册。然后在终端运行 npm login 并输入你的凭据。

  3. 发布 :运行 npm publish 。这将把你的包发布到npm公共仓库。

7.2 使用semantic-release自动化发布

脚手架已经配置了 semantic-release ,这是一个强大的工具,可以根据你的Git提交信息自动决定版本号、生成变更日志、发布到npm和GitHub。

工作原理

  1. 你的提交信息需要遵循 Conventional Commits 规范(例如: feat: 添加天气预报工具 fix: 修复城市名解析错误 )。
  2. 当你将代码推送到GitHub,并打上一个新标签(如 v1.0.0 )时, .github/workflows/release.yml 定义的工作流会被触发。
  3. semantic-release 会分析自上次发布以来的所有提交,根据 feat fix BREAKING CHANGE 等类型自动计算出下一个版本号(如 1.1.0 1.0.1 2.0.0 )。
  4. 它会自动更新 package.json 中的版本号,生成变更日志(CHANGELOG.md),创建一个GitHub Release,并发布新版本到npm。

如何操作

  1. 确保你的项目已经是一个Git仓库,并且关联了远程GitHub仓库。
  2. 在GitHub仓库的 Settings -> Secrets and variables -> Actions 中,添加 NPM_TOKEN 秘密变量,值为你在npm上生成的访问令牌(需要有发布权限)。
  3. 正常开发,并使用规范的提交信息。
  4. 当你准备发布时,不需要手动打标签。只需将代码合并到主分支(通常是 main ), semantic-release 会在CI流水线中自动完成版本发布。第一次发布后,后续的发布流程将完全自动化。

7.3 编写用户友好的README

脚手架生成的README已经是一个很好的起点。你还需要补充:

  • 更详细的配置示例 :展示如何在不同客户端(Claude, Cursor, VS Code)中配置你的服务器,特别是如何设置环境变量。
  • 可用的工具列表 :用表格列出所有工具,包括名称、描述和示例输入。
  • 故障排除 :将你在开发测试中遇到的常见问题及解决方案写下来。
  • 贡献指南 :如果你希望接受社区贡献,可以添加如何设置开发环境、运行测试、提交代码的指南。

通过 create-mcp-server-pro 搭建的不仅是一个项目骨架,更是一套完整的、经过实战检验的MCP服务器开发工作流。它帮你处理了所有繁琐的工程化配置,让你能专注于创造有价值的工具本身。从今天开始,将你的想法快速变成AI助手可用的能力吧。

Logo

码道开发者社区,聚焦华为云码道 CodeArts 代码智能体,沉淀 Agent、Skill、鸿蒙开发实战内容,供开发者查阅资料、交流技术、分享工程实践

更多推荐