使用create-mcp-server-pro脚手架快速构建生产级MCP服务器
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会启动一个友好的交互式问答界面:
- Server name : 输入你的npm包名,例如
mcp-server-weather。如果你打算将来发布到npm,这个名字需要全局唯一。脚手架会提供一个默认值(mcp-server-weather-server),但建议你改得更简洁、达意。 - Description : 用一句话描述你的服务器是做什么的。例如:“A MCP server that provides current weather and forecast data.” 这个描述会出现在
package.json和 README中。 - Authentication : 询问你的服务器是否需要API密钥。对于天气服务器,我们通常需要调用如OpenWeatherMap这样的第三方服务,所以这里选择
Yes。这会在项目中生成处理环境变量的样板代码。 - 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
安装完成后,你可以立即运行几个核心命令来验证项目是否就绪:
- 代码检查 :
npm run lint。这会运行ESLint,确保代码符合预设的规范。 - 类型检查 :
npm run typecheck。运行TypeScript编译器进行检查(不输出文件),确保类型安全。 - 运行测试 :
npm test。执行Vitest单元测试。初始状态下,示例工具的测试应该会通过。 - 构建项目 :
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
}
代码要点解析 :
- 环境变量检查 :在工具处理函数一开始就检查必要的配置(
OPENWEATHER_API_KEY)。如果缺失,立即返回一个对用户和AI都有指导意义的错误,而不是让程序在深层调用中崩溃。 - 结构化响应 :将API返回的原始JSON数据,转换成人性化、易于阅读的文本格式。AI模型处理结构化的自然语言远比处理原始的JSON更高效。
- 精细化错误处理 :
try-catch块捕获所有可能的异常。我们不是简单地抛出或返回一个笼统的错误,而是根据错误信息的内容(如状态码)来推断根本原因,并提供具体的解决建议。这能极大提升AI助手在遇到问题时的应对能力。 - 工具分离 :我们将工具的定义集中在一个函数
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。
-
找到配置文件 :
- macOS :
~/Library/Application Support/Claude/claude_desktop_config.json - Windows :
%APPDATA%\Claude\claude_desktop_config.json - Linux :
~/.config/Claude/claude_desktop_config.json
- macOS :
-
编辑配置文件 :如果文件不存在,就创建它。添加你的服务器配置:
{
"mcpServers": {
"weather-server": {
"command": "node",
"args": ["/绝对路径/到/你的/weather-server/dist/index.js"],
"env": {
"OPENWEATHER_API_KEY": "你的实际API密钥"
}
}
}
}
重要提示 :
args中的路径必须是 绝对路径 。相对路径在Claude Desktop的上下文中可能无法解析。你可以使用pwd命令(在项目根目录)来获取绝对路径。
-
重启Claude Desktop :保存配置文件后,完全退出并重启Claude Desktop客户端。
-
验证连接 :重启后,在Claude的对话窗口中,你可以尝试问:“现在的北京天气怎么样?”。如果配置正确,Claude应该会识别并调用你的
get_current_weather工具。
5.3 配置Cursor IDE
Cursor是深度集成AI的代码编辑器,也支持MCP。
- 在你的 项目根目录 下创建(或编辑)文件:
.cursor/mcp.json。 - 添加配置:
{
"mcpServers": {
"weather-server": {
"command": "node",
"args": ["${workspaceFolder}/dist/index.js"],
"env": {
"OPENWEATHER_API_KEY": "你的实际API密钥"
}
}
}
}
注意:Cursor支持 ${workspaceFolder} 变量,它会自动替换为当前打开的项目根目录路径,这比使用绝对路径更灵活。
- 重启Cursor或重新加载窗口。之后,在Cursor的AI对话中,你就可以使用天气查询功能了。
5.4 配置VS Code与GitHub Copilot
如果你使用VS Code并安装了GitHub Copilot Chat,也可以通过MCP集成。
- 在项目根目录创建(或编辑)文件:
.vscode/mcp.json。 - 添加配置:
{
"servers": {
"weather-server": {
"type": "stdio",
"command": "node",
"args": ["${workspaceFolder}/dist/index.js"],
"env": {
"OPENWEATHER_API_KEY": "你的实际API密钥"
}
}
}
}
- 重启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更好地理解你
- 单一职责原则 :一个工具只做一件事,并且做好。不要创建一个“万能”的
handle_weather工具,而是拆分成get_current_weather、get_forecast、get_historical_weather等。这能让AI更准确地判断在什么场景下调用哪个工具。 - 描述即契约 :工具和参数的描述是AI理解你的唯一途径。用自然语言清晰地说明:
- 工具做什么 :例如,“根据城市名和日期范围,查询历史天气统计数据,包括平均温度、最高/最低温和降水量。”
- 参数是什么 :例如,
date参数描述为“查询日期,格式为 YYYY-MM-DD。只能查询过去5年内的数据。” - 可能的输出格式 :虽然描述中不强制,但让AI知道你会返回结构化文本还是JSON,有助于它处理结果。
- 善用Zod Schema的约束 :利用Zod的验证功能为AI提供“护栏”。
这些约束会在AI尝试调用时提供提示,减少无效调用。z.string().email().describe("用户的电子邮件地址,必须是有效的邮箱格式。") z.number().int().min(1).max(100).describe("分页大小,必须在1到100之间。") z.enum(["asc", "desc"]).default("desc").describe("排序顺序:'asc' 升序,'desc' 降序。")
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,认证至关重要。
- 环境变量注入 :如我们的天气例子所示,永远不要将API密钥硬编码在代码中。通过客户端的MCP配置 (
env字段) 注入环境变量。 - 令牌验证 :对于需要用户级认证的工具,可以在工具内部设计一个
auth_token参数,并在处理器开头验证其有效性。或者,更MCP的方式是利用MCP协议未来可能支持的资源(Resources)和提示(Prompts)来进行复杂的认证流。 - 输入净化 :永远不要信任来自AI的输入。即使有Zod验证,对于要拼接进URL或数据库查询的参数,也要进行适当的编码或转义。
6.4 性能与可靠性
- 设置超时 :在调用外部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); } - 实现重试逻辑 :对于可能因网络波动而失败的临时性错误(如HTTP 5xx),可以加入简单的重试机制。
- 缓存策略 :对于频繁查询且数据变化不频繁的接口(如天气数据,更新周期可能数分钟),可以考虑在工具内部实现一个简单的内存缓存(如使用
Map或node-cache库),避免重复调用API,提升响应速度并节省配额。
6.5 调试与监控
- 结构化日志 :使用如
pino或winston等日志库,输出结构化的JSON日志。这便于你通过工具(如jq)过滤和分析日志,尤其是在排查生产环境问题时。 - 健康检查工具 :可以创建一个简单的
health_check工具,返回服务器的状态、依赖的API连通性等信息。这对于运维监控很有帮助。 - 性能追踪 :在工具处理函数的开头和结尾记录时间戳,计算耗时并输出到日志。这有助于你发现性能瓶颈。
7. 发布与部署:将你的工具分享给世界
当你对自己的MCP服务器感到满意时,可以将其发布到npm,让其他人也能一键安装使用。
7.1 发布到npm
-
准备发布 :
- 确保
package.json中的name、version、description、author、license字段都已正确填写。 - 在
package.json中,bin字段已经由脚手架配置好(例如"bin": { "mcp-server-weather": "./dist/index.js" }),这确保了用户可以通过npx直接运行你的服务器。 - 运行
npm run build确保最新的代码已构建。 - 运行
npm test确保所有测试通过。
- 确保
-
登录npm :如果你还没有npm账户,先去 https://www.npmjs.com/ 注册。然后在终端运行
npm login并输入你的凭据。 -
发布 :运行
npm publish。这将把你的包发布到npm公共仓库。
7.2 使用semantic-release自动化发布
脚手架已经配置了 semantic-release ,这是一个强大的工具,可以根据你的Git提交信息自动决定版本号、生成变更日志、发布到npm和GitHub。
工作原理 :
- 你的提交信息需要遵循 Conventional Commits 规范(例如:
feat: 添加天气预报工具、fix: 修复城市名解析错误)。 - 当你将代码推送到GitHub,并打上一个新标签(如
v1.0.0)时,.github/workflows/release.yml定义的工作流会被触发。 semantic-release会分析自上次发布以来的所有提交,根据feat、fix、BREAKING CHANGE等类型自动计算出下一个版本号(如1.1.0、1.0.1或2.0.0)。- 它会自动更新
package.json中的版本号,生成变更日志(CHANGELOG.md),创建一个GitHub Release,并发布新版本到npm。
如何操作 :
- 确保你的项目已经是一个Git仓库,并且关联了远程GitHub仓库。
- 在GitHub仓库的
Settings -> Secrets and variables -> Actions中,添加NPM_TOKEN秘密变量,值为你在npm上生成的访问令牌(需要有发布权限)。 - 正常开发,并使用规范的提交信息。
- 当你准备发布时,不需要手动打标签。只需将代码合并到主分支(通常是
main),semantic-release会在CI流水线中自动完成版本发布。第一次发布后,后续的发布流程将完全自动化。
7.3 编写用户友好的README
脚手架生成的README已经是一个很好的起点。你还需要补充:
- 更详细的配置示例 :展示如何在不同客户端(Claude, Cursor, VS Code)中配置你的服务器,特别是如何设置环境变量。
- 可用的工具列表 :用表格列出所有工具,包括名称、描述和示例输入。
- 故障排除 :将你在开发测试中遇到的常见问题及解决方案写下来。
- 贡献指南 :如果你希望接受社区贡献,可以添加如何设置开发环境、运行测试、提交代码的指南。
通过 create-mcp-server-pro 搭建的不仅是一个项目骨架,更是一套完整的、经过实战检验的MCP服务器开发工作流。它帮你处理了所有繁琐的工程化配置,让你能专注于创造有价值的工具本身。从今天开始,将你的想法快速变成AI助手可用的能力吧。
更多推荐


所有评论(0)