使用create-mcp脚手架快速构建AI模型扩展工具:MCP服务器开发指南
1. 项目概述:一个MCP服务器创建工具
最近在折腾AI应用开发,特别是想给Claude、Cursor这类智能助手扩展能力时,总绕不开一个概念:MCP(Model Context Protocol)。简单说,MCP就是一套标准协议,它能让AI模型安全、可控地访问外部工具、数据源和API,比如查数据库、读文件、调用第三方服务。这相当于给AI装上了“手”和“眼睛”,让它不再只是空谈,而是能真正帮你干活。
但每次想新建一个MCP服务器项目,从零搭建框架、配置协议、处理类型定义……这一套流程下来,着实有些繁琐。直到我发现了fefergrgrgrg/create-mcp这个工具。它本质上是一个项目脚手架生成器,专门用于快速初始化一个符合MCP协议标准的服务器项目。无论你是想开发一个连接内部数据库的MCP服务器,还是想把公司某个老旧API包装成AI可用的工具,这个工具都能帮你省下大量重复性工作,直接聚焦在核心业务逻辑上。
它适合所有对AI Agent开发、模型功能扩展感兴趣的开发者,尤其是那些希望将现有系统能力快速、标准化地暴露给AI模型的团队。接下来,我就结合自己的使用经验,带你彻底拆解这个工具,看看它如何运作,以及如何用它高效启动你的第一个MCP服务器项目。
2. 核心设计思路与方案选型
2.1 为什么需要专门的MCP项目脚手架?
在MCP生态中,一个服务器需要严格遵守特定的协议规范,包括SSE(Server-Sent Events)通信、标准化的请求/响应格式(通常基于JSON-RPC)、资源(Resources)与工具(Tools)的定义方式等。手动实现这些底层通信和协议解析,不仅容易出错,而且重复造轮子效率极低。
create-mcp的设计思路非常明确:将通用的、样板化的代码和配置抽象为模板,通过交互式命令行问答,收集用户项目的特定信息(如项目名、描述、要实现的工具类型等),然后动态生成一个完整、可立即运行的基础项目结构。这背后是“约定大于配置”和“快速启动”理念的体现。
它通常基于流行的Node.js技术栈(这是MCP官方SDK的主要语言环境),集成@modelcontextprotocol/sdk作为核心依赖,预设好TypeScript配置、基础的服务器类、示例工具和资源定义。用户拿到生成的项目后,只需要在预设的骨架里填充自己工具的具体逻辑即可,比如“查询天气”工具就去调用天气API,“读取数据库”工具就去编写SQL查询。
2.2 技术栈与工具链的考量
从fefergrgrgrg/create-mcp这个命名空间推测,它很可能是一个基于npm init或类似机制的工具。这类工具常见的实现方式是:
- 核心引擎:使用像
plop.js、yeoman或自定义的基于Inquirer的交互式命令行工具。它们负责提问、收集答案、管理模板文件。 - 模板系统:包含一系列预定义的目录和文件模板(如
package.json、tsconfig.json、src/index.ts、README.md等),模板中嵌入变量占位符(如{{projectName}})。 - 依赖管理:预置好
@modelcontextprotocol/sdk、typescript、tsx(或ts-node)、@types/node等开发MCP服务器必需的依赖。
选择这样的技术栈,是为了最大化开发者体验和生态兼容性。Node.js环境普及,TypeScript能提供优秀的类型安全,这对于与AI模型交互的复杂数据结构尤为重要。官方SDK则确保了协议层面的正确性。
注意:使用这类脚手架时,务必确认其生成的模板是否与官方MCP SDK的最新版本保持同步。协议仍在演进中,过时的模板可能导致兼容性问题。
3. 从零到一:使用 create-mcp 创建你的项目
3.1 环境准备与工具安装
首先,确保你的本地开发环境已经就绪:
- Node.js:建议安装最新的LTS版本(如18.x或20.x)。你可以在终端运行
node --version和npm --version来检查。 - 包管理工具:npm或yarn、pnpm均可。本文以npm为例。
- 代码编辑器:VS Code或其他你熟悉的IDE,建议安装TypeScript插件。
接下来,安装create-mcp工具本身。通常,这类脚手架工具被设计为通过npm init直接触发,或者作为一个全局可执行命令。假设它的使用方式是后者,安装命令可能类似于:
npm install -g @fefergrgrgrg/create-mcp # 或者,如果它发布在特定registry,可能需要指定registry # npm install -g @fefergrgrgrg/create-mcp --registry=<your-registry-url>如果它是通过npm init调用的,那么使用方式可能是:
npm init @fefergrgrgrg/mcp # 或 npx @fefergrgrgrg/create-mcp具体命令需要查看该工具的官方文档。安装成功后,你就可以在终端使用create-mcp命令了。
3.2 交互式初始化流程详解
运行初始化命令后,你将进入一个交互式命令行界面(CLI)。这个过程会询问你一系列问题,来定制化你的项目。以下是一个典型的问答流程模拟及每个选项的解读:
$ create-mcp ? 请输入你的MCP服务器项目名称 (my-mcp-server): weather-agent ? 请简要描述该项目功能: 一个提供实时天气查询和预报的MCP服务器 ? 选择要初始化的模板类型: (Use arrow keys) ❯ Basic Server (基础工具和资源示例) Database Connector (包含数据库连接示例) REST API Proxy (包含HTTP客户端示例) Custom (完全自定义结构) ? 是否包含示例工具代码? (Y/n): Y ? 是否包含示例资源代码? (Y/n): Y ? 是否初始化Git仓库? (Y/n): Y ? 是否现在安装依赖? (Y/n): Y关键选择解析:
- 项目名称:这将成为你的包名和目录名。遵循kebab-case(短横线连接)是社区惯例。
- 模板类型:
- Basic Server:最适合新手。生成一个包含一两个简单工具(如“echo”)和资源示例的服务器。
- Database Connector:如果你要连接PostgreSQL、MySQL等,这个模板会预置数据库连接池配置和查询工具示例。
- REST API Proxy:适合包装外部HTTP API。会预置axios或fetch客户端、错误处理和API密钥管理示例。
- Custom:提供一个最精简的结构,适合有明确架构设计的老手。
- 包含示例代码:强烈建议选择“是”,尤其是第一次使用。这些示例是理解MCP工具(Tools)和资源(Resources)如何定义和注册的最佳学习材料。
- 初始化Git和安装依赖:通常都选“是”,让工具帮你完成这些琐事。
回答完所有问题后,工具会开始它的工作:创建项目目录,根据模板和你的答案生成文件,初始化git,运行npm install。整个过程大概需要几十秒到一分钟。
3.3 生成的项目结构深度解析
初始化完成后,进入项目目录cd weather-agent,你会看到一个结构清晰的项目文件夹。我们来逐一解读核心文件:
weather-agent/ ├── package.json ├── tsconfig.json ├── src/ │ ├── index.ts # 服务器主入口文件 │ ├── tools/ # 工具定义目录 │ │ ├── index.ts # 工具集中导出文件 │ │ └── exampleTool.ts # 示例工具实现 │ ├── resources/ # 资源定义目录(可选) │ │ ├── index.ts │ │ └── exampleResource.ts │ └── types/ # 自定义类型定义 │ └── index.ts ├── .env.example # 环境变量示例文件 ├── .gitignore └── README.md1.package.json:项目心脏这是最重要的配置文件。脚手架会为你填充好基础信息、脚本命令和依赖。
{ "name": "weather-agent", "version": "0.1.0", "description": "一个提供实时天气查询和预报的MCP服务器", "main": "dist/index.js", "scripts": { "build": "tsc", "dev": "tsx watch src/index.ts", "start": "node dist/index.js" }, "dependencies": { "@modelcontextprotocol/sdk": "^0.4.0" // 核心SDK }, "devDependencies": { "typescript": "^5.0.0", "tsx": "^4.0.0", "@types/node": "^20.0.0" } }- scripts:
dev命令使用tsx在开发时进行热重载,修改代码后服务器会自动重启,极大提升开发效率。build用于编译TypeScript到JavaScript。start用于运行编译后的生产版本。 - dependencies:确保
@modelcontextprotocol/sdk的版本是较新的,以支持最新的协议特性。
2.src/index.ts:服务器入口这是服务器的启动和配置中心。让我们看一个生成后的简化版核心代码:
import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; // 导入工具和资源定义 import { tools } from './tools/index.js'; import { resources } from './resources/index.js'; // 如果存在 // 创建Server实例,声明其能力 const server = new Server( { name: 'weather-agent', // 你的项目名 version: '0.1.0', }, { capabilities: { tools: {}, // 告知客户端本服务器支持Tools功能 resources: {}, // 告知客户端本服务器支持Resources功能 }, } ); // 注册所有工具和资源 for (const [name, tool] of Object.entries(tools)) { server.setRequestHandler(tool.definition, tool.handler); } // 类似地注册资源... // 建立传输层并启动服务器 async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error('MCP服务器已启动,通过stdio传输'); // 日志输出到stderr,避免污染协议通信 } main().catch((error) => { console.error('服务器启动失败:', error); process.exit(1); });这段代码清晰地展示了MCP服务器的生命周期:创建 -> 配置能力 -> 注册功能处理器 -> 建立传输层 -> 启动。StdioServerTransport是最常用的传输方式,意味着服务器通过标准输入输出与AI客户端(如Claude Desktop)通信。
3.src/tools/exampleTool.ts:第一个工具示例这是你学习的蓝本。一个典型的工具定义包含两部分:definition(元数据)和handler(处理函数)。
import { Tool } from '@modelcontextprotocol/sdk/types.js'; export const echoTool: Tool = { definition: { name: 'echo', // 工具的唯一标识,AI将通过此名称调用 description: '回显输入的任何文本。这是一个示例工具。', inputSchema: { type: 'object', properties: { message: { type: 'string', description: '需要回显的文本信息', }, }, required: ['message'], }, }, handler: async (request) => { // request.params 包含了客户端调用时传入的参数 const { message } = request.params as { message: string }; // 返回结果必须符合特定格式 return { content: [ { type: 'text', text: `你输入的是:${message}`, }, ], }; }, };- definition:用JSON Schema定义了工具的“接口”。
description非常重要,AI模型(如Claude)会阅读它来理解何时以及如何使用这个工具。inputSchema定义了输入参数的格式和类型,这为AI调用提供了强约束和指引。 - handler:异步函数,包含实际的业务逻辑。它接收一个
request对象,从中解析参数,执行操作(可能是计算、调用API、查询DB等),最后返回标准化的结果。返回的content数组支持多种类型(文本、图像等),这里是简单的文本。
4.src/tools/index.ts:统一导出这是一个简单的聚合文件,便于在index.ts中统一注册。
import { echoTool } from './exampleTool.js'; // 未来你可以在这里导入更多工具,如 weatherTool, forecastTool... export const tools = { echo: echoTool, // weather: weatherTool, };5.tsconfig.json与.env.example
tsconfig.json配置了TypeScript编译选项,通常脚手架会设置为target: "ES2022",module: "NodeNext",并包含必要的类型定义路径。.env.example文件列出了项目可能需要的环境变量(如API密钥、数据库连接字符串),提醒你创建.env文件并填入实际值,然后在代码中通过process.env读取。切记将.env加入.gitignore,不要提交敏感信息!
4. 开发你的第一个自定义工具:天气查询
理解了项目结构后,我们来实战,将示例项目改造成真正的“天气查询服务器”。我们将创建一个getWeather工具。
4.1 定义工具接口与获取API密钥
首先,我们需要一个天气数据源。这里以 OpenWeatherMap 为例(你也可以选择心知天气、和风天气等国内服务)。
- 注册并获取API Key:前往 OpenWeatherMap 官网注册,在控制台获取你的免费API Key。
- 创建
.env文件:在项目根目录创建.env文件,写入:OPENWEATHER_API_KEY=你的_实际_API_密钥 - 安装HTTP客户端:我们将使用
axios。npm install axios
4.2 实现天气查询工具逻辑
在src/tools/目录下创建weatherTool.ts:
import { Tool } from '@modelcontextprotocol/sdk/types.js'; import axios from 'axios'; // 定义工具输入参数的类型接口 interface WeatherInput { city: string; countryCode?: string; // 可选,用于避免城市名歧义,如“CN”代表中国 } // 定义从OpenWeatherMap API返回的数据结构(简化版) interface OpenWeatherResponse { weather: Array<{ description: string; }>; main: { temp: number; // 开尔文温度 feels_like: number; humidity: number; }; name: string; sys: { country: string; }; } export const weatherTool: Tool = { definition: { name: 'get_weather', description: '查询指定城市的当前天气情况,包括温度、体感温度、湿度和天气状况。请提供城市名,可额外提供国家代码以提高准确性(例如:Beijing 或 Beijing,CN)。', inputSchema: { type: 'object', properties: { city: { type: 'string', description: '城市名称,例如:Beijing, London, New York', }, countryCode: { type: 'string', description: 'ISO 3166国家代码(可选),例如:CN, US, GB。当城市名有歧义时建议提供。', }, }, required: ['city'], // city是必填项 }, }, handler: async (request) => { const { city, countryCode } = request.params as WeatherInput; const apiKey = process.env.OPENWEATHER_API_KEY; if (!apiKey) { throw new Error('服务器未配置天气API密钥。请检查OPENWEATHER_API_KEY环境变量。'); } // 构建查询参数 const query = countryCode ? `${city},${countryCode}` : city; try { const response = await axios.get<OpenWeatherResponse>( `https://api.openweathermap.org/data/2.5/weather`, { params: { q: query, appid: apiKey, units: 'metric', // 使用摄氏度,国内更常用。如需华氏度可改为 'imperial' lang: 'zh_cn', // 获取中文天气描述 }, timeout: 10000, // 10秒超时 } ); const data = response.data; // 温度转换:从开尔文到摄氏度(如果API直接返回metric单位,则data.main.temp已为摄氏度) // 注意:上面请求中已指定units: 'metric',所以返回的温度直接是摄氏度。 const tempC = data.main.temp; const feelsLikeC = data.main.feels_like; const weatherDesc = data.weather[0]?.description || '未知'; const resultText = `城市:${data.name}, ${data.sys.country} 当前天气:${weatherDesc} 温度:${tempC.toFixed(1)}°C 体感温度:${feelsLikeC.toFixed(1)}°C 湿度:${data.main.humidity}%`; return { content: [ { type: 'text', text: resultText, }, ], }; } catch (error: any) { // 错误处理:网络错误、API错误、城市未找到等 let errorMessage = '查询天气失败。'; if (axios.isAxiosError(error)) { if (error.response?.status === 404) { errorMessage = `未找到城市“${city}”。请检查城市名拼写,或尝试添加国家代码。`; } else if (error.response?.data?.message) { errorMessage = `天气API错误:${error.response.data.message}`; } else if (error.code === 'ECONNABORTED') { errorMessage = '请求超时,请稍后重试。'; } } // 在MCP协议中,抛出错误会被服务器捕获并返回给客户端标准的错误响应 throw new Error(errorMessage); } }, };4.3 注册新工具并更新导出
修改src/tools/index.ts,导入并导出我们的新工具:
import { echoTool } from './exampleTool.js'; import { weatherTool } from './weatherTool.js'; export const tools = { echo: echoTool, get_weather: weatherTool, // 键名与工具definition中的name保持一致是良好实践 };4.4 本地运行与测试
启动开发服务器:
npm run dev终端会显示“MCP服务器已启动,通过stdio传输”。
配置AI客户端进行测试:
- Claude Desktop:编辑其配置文件(位于
~/Library/Application Support/Claude/claude_desktop_config.json或类似路径)。 - 在
mcpServers部分添加你的服务器配置:{ "mcpServers": { "weather-agent": { "command": "node", "args": ["/绝对路径/到/你的/weather-agent/dist/index.js"], "env": { "OPENWEATHER_API_KEY": "你的_实际_API_密钥" } } } } - 重启Claude Desktop。在聊天框中,Claude现在应该能意识到它可以使用
get_weather工具了。你可以尝试说:“请帮我查一下北京的天气。”
- Claude Desktop:编辑其配置文件(位于
直接使用Stdio测试(可选): 你也可以编写一个简单的测试脚本,模拟客户端向服务器的stdio发送JSON-RPC请求,但这通常更复杂。使用现成的AI客户端是更直观的测试方式。
5. 进阶配置与最佳实践
5.1 环境变量管理与安全性
在MCP服务器中处理API密钥、数据库密码等敏感信息,必须遵循安全最佳实践:
- 永远不要硬编码:绝对不要将密钥直接写在源代码里。
- 使用
.env文件:如前所述,在开发中使用.env文件,并通过dotenv包在开发时加载(如果脚手架没集成,可以自己安装npm install dotenv,并在index.ts顶部添加import 'dotenv/config';)。 - 生产环境配置:在Docker容器、服务器环境变量或云平台的密钥管理服务(如AWS Secrets Manager, Azure Key Vault)中设置环境变量。
env.example的维护:确保.env.example包含所有必需的变量名(不含值),作为项目文档的一部分。
5.2 错误处理与日志记录
健壮的生产级MCP服务器需要完善的错误处理和日志。
- 结构化日志:使用
winston或pino替代console.error。可以按级别(info, warn, error)记录日志,并输出到文件或日志服务。npm install winston// src/logger.ts import winston from 'winston'; export const logger = winston.createLogger({ level: 'info', format: winston.format.json(), transports: [new winston.transports.File({ filename: 'mcp-server.log' })], }); // 在handler中使用:logger.error('查询天气API失败', { error, city }); - 精细化错误处理:在工具
handler中,区分不同类型的错误(用户输入错误、网络错误、第三方服务错误、内部服务器错误),并抛出带有清晰信息的Error对象。MCP SDK会将其转换为标准的JSON-RPC错误响应。
5.3 性能优化与资源管理
- 连接池与缓存:如果你的工具需要连接数据库或频繁调用某个API,考虑在服务器启动时初始化连接池,并在各个工具handler中复用。对于不常变的数据,可以引入内存缓存(如
node-cache)或分布式缓存,减少对外部服务的请求。 - 异步操作与超时:所有I/O操作(网络请求、数据库查询)都必须是异步的(使用
async/await)。为所有外部调用设置合理的超时(如上面axios的timeout配置),避免一个慢请求阻塞整个服务器。 - 批处理工具:如果业务场景允许,可以设计一个能接受批量参数的工具,减少客户端与服务器之间的往返次数。
5.4 版本控制与发布
- 语义化版本:在
package.json中遵循语义化版本控制。当你添加新工具但不破坏现有接口时,增加次版本号(0.2.0);如果修改了现有工具的输入输出Schema,则增加主版本号(1.0.0)。 - 编译与打包:在发布或部署前,运行
npm run build将TypeScript编译为JavaScript到dist/目录。确保.gitignore忽略了dist/和node_modules/。 - Docker化:创建
Dockerfile可以简化部署。一个简单的Dockerfile示例如下:FROM node:20-slim WORKDIR /app COPY package*.json ./ RUN npm ci --only=production COPY dist/ ./dist/ COPY .env.production ./.env # 假设生产环境变量文件 USER node CMD ["node", "dist/index.js"]
6. 常见问题与调试技巧实录
在实际开发和集成过程中,你肯定会遇到各种问题。以下是我踩过的一些坑和解决方案:
6.1 服务器启动失败
- 问题:运行
npm run dev后立即报错退出。 - 排查:
- 检查Node.js版本:
node --version。确保版本符合package.json中的engines要求(如果有的话)。MCP SDK可能需要较新的Node版本。 - 检查依赖安装:删除
node_modules和package-lock.json,重新运行npm install。 - 检查TypeScript配置:确保
tsconfig.json中的target和module设置正确。对于Node.js环境,ES2022和NodeNext是安全的选择。 - 检查入口文件:确认
src/index.ts没有语法错误,特别是导入路径是否正确。生成的模板中可能使用.js扩展名导入,这是为了兼容ES模块,在TypeScript中需要正确配置。
- 检查Node.js版本:
6.2 AI客户端无法发现或调用工具
- 问题:配置了MCP服务器,但Claude等客户端似乎不知道工具存在,或调用时失败。
- 排查:
- 检查客户端配置:确认客户端配置文件路径正确,JSON格式无误,
command和args指向的是编译后的JS文件(如果使用生产模式)或正确的开发启动命令。 - 检查环境变量:客户端配置中的
env部分是否传递了正确的环境变量?服务器代码中process.env.XXX是否能读到? - 查看服务器日志:启动服务器时,所有
console.error日志都会输出到stderr。仔细查看是否有初始化错误、工具注册失败等信息。 - 工具定义检查:
- name字段:工具名是否使用了蛇形命名(snake_case)?这是MCP协议的常见约定,虽然不强制,但最好遵循。
- description字段:描述是否清晰?AI模型严重依赖描述来决定是否以及如何调用工具。确保描述准确说明了工具的功能、适用场景和输入要求。
- inputSchema:是否正确定义?
required数组是否包含了必要的参数?属性描述是否清晰?
- 协议兼容性:确保你使用的
@modelcontextprotocol/sdk版本与AI客户端支持的协议版本兼容。有时需要升级或降级SDK。
- 检查客户端配置:确认客户端配置文件路径正确,JSON格式无误,
6.3 工具调用时报错“Invalid params”或内部错误
- 问题:AI客户端尝试调用工具,但服务器返回参数错误或内部错误。
- 排查:
- 在handler中加日志:在工具handler的第一行添加
console.error(‘收到请求参数:’, request.params);。查看实际收到的参数是否与你的预期一致。AI有时可能会生成格式略有偏差的参数。 - 强化参数校验:即使有
inputSchema,在handler内部也应对参数进行二次校验和类型转换(例如,确保数字是数字,字符串非空)。 - 错误处理:确保你的handler中所有可能的异常都被捕获,并抛出带有用户友好信息的
Error。未捕获的异常会导致服务器返回晦涩的内部错误。 - 第三方API问题:如果是调用外部API失败,检查网络连通性、API密钥有效性、请求频率限制、以及外部API的响应格式是否发生变化。
- 在handler中加日志:在工具handler的第一行添加
6.4 性能问题:工具响应缓慢
- 问题:工具调用需要很长时间才能返回结果。
- 优化:
- 分析耗时环节:在handler中记录时间戳,找出是代码逻辑慢、网络请求慢还是数据库查询慢。
- 引入缓存:对于结果变化不频繁的查询(如天气预报在未来几小时内相对稳定),可以在内存中缓存结果一段时间(例如10分钟),对于相同的请求直接返回缓存。
- 设置超时和重试:对外部依赖设置严格的超时。对于可能因网络抖动失败的请求,实现简单的重试机制(但要注意幂等性)。
- 优化数据库/API调用:检查查询语句或API调用参数,确保使用了索引,只获取必要的数据。
6.5 如何调试复杂的工具逻辑
- 使用VS Code调试器:在
package.json的scripts中增加一个调试脚本:"debug": "node --inspect -r tsx/src/index.ts"。然后在VS Code中创建调试配置,附加到Node.js进程。 - 编写单元测试:为你的工具handler编写单元测试。使用Jest或Mocha等框架,模拟
request对象,测试各种正常和异常输入下的行为。这能极大提升代码质量和调试效率。 - 模拟客户端请求:可以创建一个简单的测试脚本,直接模拟MCP客户端向你的服务器发送JSON-RPC请求,这对于隔离测试特定工具非常有用。
通过create-mcp脚手架快速搭建起项目骨架,然后聚焦于实现具体的工具逻辑,你就能高效地构建出功能强大的MCP服务器。记住,清晰的工具描述、健壮的错误处理和安全的秘密管理,是打造一个好用、可靠的服务器的关键。
