基于MCP协议构建AI工具:从TypeScript模板到生产部署全流程
1. 项目概述一个为AI时代量身定制的MCP模板如果你正在为大型语言模型LLM构建一个自定义工具或数据源并且希望它能被 Claude、Cursor 或其他兼容 MCPModel Context Protocol协议的智能体无缝调用那么你很可能已经体会过从零开始的繁琐。配置协议、编写服务器、处理认证、定义工具模式……这些底层工作虽然必要却极大地分散了我们在核心业务逻辑上的精力。bsmi021/custom-mcp-template这个项目正是为了解决这个痛点而生的。它不是一个简单的“Hello World”示例而是一个功能完整、结构清晰、开箱即用的 MCP 服务器开发模板旨在让开发者能像搭积木一样快速构建和部署属于自己的 MCP 服务。简单来说MCP 是一个由 Anthropic 提出的开放协议它允许外部服务器MCP Server向 AI 客户端如 Claude Desktop安全地暴露工具Tools和资源Resources。而custom-mcp-template为你准备好了构建这样一个服务器的所有基础设施从项目骨架、依赖管理、开发服务器、到生产环境构建和容器化部署。无论你是想将公司内部 API、私有数据库还是一个独特的算法服务封装成 AI 可用的工具这个模板都能让你在几分钟内启动开发将精力完全聚焦在实现你的核心业务逻辑上。2. 核心架构与设计思路拆解2.1 为什么选择模板化开发在 MCP 生态的早期开发者往往需要从官方最基础的示例开始手动搭建项目结构、配置构建工具、集成测试框架。这个过程不仅重复而且容易在配置上出错尤其是对于不熟悉 Node.js/TypeScript 全栈开发的 AI 应用开发者而言。custom-mcp-template的核心设计思路是“约定优于配置”和“关注点分离”。它预设了一套经过验证的最佳实践目录结构将协议通信、工具定义、业务逻辑、配置管理清晰地分离开。开发者只需要在指定的位置如src/tools/目录添加自己的工具实现模板会自动处理剩下的所有事情将工具注册到 MCP 服务器、生成正确的 TypeScript 类型定义、打包为可执行文件。这极大地降低了入门门槛也保证了项目代码的可维护性和一致性。2.2 模板技术栈选型解析该模板的技术选型充分考虑了 MCP 服务器开发的常见需求高效、类型安全、易于部署。语言与运行时TypeScript Node.jsTypeScript这是模板的默认和推荐语言。MCP 协议涉及复杂的 JSON-RPC 消息结构和工具输入输出模式JSON Schema。TypeScript 的静态类型系统能提供无与伦比的开发体验在编码阶段就能捕获接口不匹配、参数错误等问题这对于构建稳定的 MCP 服务至关重要。模板内置了完善的tsconfig.json配置。Node.js作为 MCP 官方 SDK 的主要支持环境Node.js 拥有庞大的生态系统。模板利用其非阻塞 I/O 特性能够高效处理 AI 客户端可能并发发起的多个工具调用请求。核心依赖modelcontextprotocol/sdk这是 Anthropic 官方维护的 MCP 服务器 SDK是模板的基石。它封装了 MCP 协议的所有底层细节包括 STDIO 传输层的管理、请求路由、错误处理等。开发者无需关心协议报文如何序列化与反序列化只需使用 SDK 提供的高级 API 来创建服务器、注册工具和资源。构建与开发工具链ESBuild模板使用esbuild进行代码打包。它速度极快非常适合开发环境的热重载和生产环境的快速构建。在package.json中dev脚本利用esbuild的 watch 模式实现了源码变动后自动重启服务器提升了开发效率。tsx在开发时模板使用tsx来直接执行 TypeScript 代码无需等待编译步骤让开发调试流程更加流畅。pkg可选地模板支持使用pkg将你的服务器打包成一个独立的可执行文件二进制这个文件可以在没有安装 Node.js 环境的机器上运行。这对于分发你的 MCP 工具给最终用户非常方便。项目质量与规范Prettier ESLint模板集成了代码格式化Prettier和代码检查ESLint工具并预设了通用的规则配置。这确保了团队协作时代码风格的一致性并帮助避免常见的错误模式。目录结构一个清晰的目录结构是项目的骨架。模板通常包含src/源代码、dist/构建输出、config/配置文件等目录并在src下细分出tools/、resources/、server/等模块使得定位和添加新功能变得直观。注意虽然模板提供了完整的脚手架但你仍然需要对 Node.js/TypeScript 有基础了解。如果你的工具需要调用特定的第三方库如数据库驱动、机器学习框架你需要自行安装并管理这些依赖。3. 从零到一使用模板创建你的第一个 MCP 工具3.1 环境准备与模板初始化假设你已经安装了 Node.js版本 18 或以上和 npm/yarn/pnpm 等包管理器。首先你需要获取模板代码。通常这类模板会发布在 npm 上或作为一个 GitHub 模板仓库。方法一使用 degit 或直接克隆如果项目是 GitHub 模板# 使用 degit 工具推荐只克隆最新文件没有git历史 npx degit bsmi021/custom-mcp-template my-mcp-server cd my-mcp-server # 或者如果它是 GitHub 模板仓库 git clone repository-url my-mcp-server cd my-mcp-server rm -rf .git # 移除原有的git记录以便初始化你自己的仓库方法二从 npm 初始化如果模板发布了 npm 包# 假设模板包名为 create-mcp-app npx create-mcp-app my-mcp-server cd my-mcp-server进入项目目录后安装依赖npm install # 或 yarn install 或 pnpm install此时你的项目结构应该大致如下my-mcp-server/ ├── package.json ├── tsconfig.json ├── .eslintrc.js ├── .prettierrc ├── src/ │ ├── index.ts # 服务器主入口 │ ├── server/ │ │ └── mcp-server.ts # MCP服务器核心封装 │ ├── tools/ # 【核心】存放所有自定义工具 │ │ └── example-tool.ts │ └── types/ # 全局类型定义 ├── config/ # 配置文件 └── scripts/ # 构建、打包脚本3.2 解剖一个标准工具的实现让我们打开src/tools/example-tool.ts这是模板自带的示例也是我们学习的蓝本。// src/tools/example-tool.ts import { z } from zod; // 用于定义和验证输入模式的库 import { McpServer } from modelcontextprotocol/sdk/server/mcp.js; import { Tool } from modelcontextprotocol/sdk/types.js; /** * 定义一个“问候”工具。 * 这个工具接收一个名字参数返回一句问候语。 */ export function registerGreetTool(server: McpServer) { // 使用 zod 定义工具输入参数的 JSON Schema const greetArgsSchema z.object({ name: z.string().describe(需要问候的人的名字), }); // 调用 server.tool() 方法注册工具 server.tool( // 工具的唯一标识符通常用命名空间风格 greet, // 工具的描述AI客户端会看到这个描述来决定是否使用该工具 向指定的人发送一句友好的问候。, // 工具的输入参数模式基于上面定义的zod schema { argsSchema: greetArgsSchema }, // 工具的实际执行函数处理器 async ({ name }) { // 这里是你的核心业务逻辑 const greeting Hello, ${name}! Welcome to the world of MCP.; // 返回一个结构化的结果content部分可以是文本或多媒体 return { content: [ { type: text, text: greeting, }, ], }; } ); }关键点解析工具注册函数每个工具通常被封装成一个函数如registerGreetTool接收McpServer实例作为参数。这使得工具模块化易于管理和测试。参数模式定义Schema使用zod库定义工具输入。这是至关重要的一步。清晰的 Schema 不仅能在运行时验证输入更重要的是它能向 AI 客户端清晰地描述这个工具“吃什么”、“怎么吃”。describe()方法用于为每个字段添加自然语言描述极大地帮助 LLM 理解如何调用这个工具。工具描述server.tool()的第二个参数是工具的描述。请用一句简洁、准确的话说明这个工具的功能。好的描述能显著提升 AI 调用工具的准确率。处理器函数这是你编写业务逻辑的地方。参数是从客户端传入的、已经过 Schema 验证的对象。返回值必须遵循 MCP 协议规定的CallToolResult格式其中content数组是核心。3.3 将工具集成到主服务器定义好工具后需要在服务器入口文件通常是src/index.ts中导入并注册它。// src/index.ts import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { createMcpServer } from ./server/mcp-server.js; import { registerGreetTool } from ./tools/example-tool.js; // 未来你可以在这里导入更多工具 // import { registerWeatherTool } from ./tools/weather.js; // import { registerQueryDBTool } from ./tools/database-query.js; async function main() { // 1. 创建 MCP 服务器实例 const server createMcpServer({ name: my-custom-mcp-server, // 你的服务器名称 version: 1.0.0, }); // 2. 注册所有工具 registerGreetTool(server); // registerWeatherTool(server); // registerQueryDBTool(server); // 3. 创建传输层这里使用标准输入输出适用于Claude Desktop等客户端 const transport new StdioServerTransport(); // 4. 连接并启动服务器 await server.connect(transport); console.error(MCP server running on stdio...); } main().catch((error) { console.error(Server fatal error:, error); process.exit(1); });3.4 本地运行与调试模板的package.json中已经配置好了开发脚本。# 启动开发服务器支持文件变动热重载 npm run dev运行后你的服务器就开始通过 STDIO 监听请求了。但这还不够你需要一个 MCP 客户端来测试它。最方便的方式是使用Claude Desktop进行集成测试。找到 Claude Desktop 的配置文件夹。macOS:~/Library/Application Support/Claude/claude_desktop_config.jsonWindows:%APPDATA%\Claude\claude_desktop_config.json编辑该 JSON 文件添加你的 MCP 服务器配置{ mcpServers: { my-custom-server: { command: node, args: [ /ABSOLUTE/PATH/TO/YOUR/PROJECT/dist/index.js ], env: { MY_API_KEY: your_secret_key_here // 可选传递环境变量 } } } }重要提示在开发初期建议先使用npm run build生成dist/index.js然后配置这个路径进行测试。因为dev脚本可能涉及复杂的进程管理直接配置tsx有时不够稳定。生产部署时可以考虑使用pkg打包成二进制文件命令会更简洁。保存配置并重启 Claude Desktop。在 Claude 的聊天界面你现在应该能看到一个“工具”图标。点击它如果配置成功你的greet工具会出现在列表中。你可以直接对 Claude 说“请使用 greet 工具向 Alice 问好。” Claude 就会调用你的服务器并返回结果。4. 进阶实战构建一个实用的天气查询工具让我们超越示例构建一个更真实、更有用的工具一个天气查询工具。它将演示如何处理异步操作、调用外部 API、管理敏感配置如 API Key以及处理错误。4.1 设计工具接口首先规划工具的功能和输入输出。功能根据城市名称查询当前天气。输入城市名字符串。输出结构化的天气信息包括温度、天气状况、湿度等。依赖需要一个第三方天气 API如 OpenWeatherMap。4.2 实现天气工具模块创建新文件src/tools/weather.ts。// src/tools/weather.ts import { z } from zod; import { McpServer } from modelcontextprotocol/sdk/server/mcp.js; // 定义输入参数模式 const weatherArgsSchema z.object({ city: z.string().describe(要查询天气的城市名称例如Beijing, London, Tokyo), }); // 定义从天气API返回的数据结构根据OpenWeatherMap API响应调整 interface WeatherApiResponse { main: { temp: number; // 开尔文温度 humidity: number; }; weather: Array{ main: string; // 如 Clear, Rain description: string; }; name: string; } export function registerWeatherTool(server: McpServer) { server.tool( get_weather, 查询指定城市的当前天气信息。, { argsSchema: weatherArgsSchema }, async ({ city }) { // --- 核心业务逻辑开始 --- const apiKey process.env.OPENWEATHER_API_KEY; if (!apiKey) { throw new Error(OPENWEATHER_API_KEY 环境变量未设置。请配置天气API密钥。); } // 构建 API 请求 URL const url https://api.openweathermap.org/data/2.5/weather?q${encodeURIComponent(city)}appid${apiKey}unitsmetric; // 使用摄氏单位 let response: Response; try { response await fetch(url); } catch (error) { // 处理网络错误 return { content: [{ type: text, text: 无法连接到天气服务${error instanceof Error ? error.message : 未知网络错误}。请检查网络连接。, }], isError: true, }; } if (!response.ok) { // 处理 API 返回的错误如城市不存在、密钥无效 let errorMsg 天气查询失败状态码${response.status}; try { const errorBody await response.json(); errorMsg , 信息${errorBody.message || 未知}; } catch (e) {} return { content: [{ type: text, text: errorMsg, }], isError: true, }; } const data: WeatherApiResponse await response.json(); // 格式化输出使其对 AI 和用户都友好 const tempC data.main.temp.toFixed(1); const description data.weather[0]?.description || 未知; const humidity data.main.humidity; const resultText 城市${data.name}\n温度${tempC}°C\n天气状况${description}\n湿度${humidity}%; // --- 核心业务逻辑结束 --- return { content: [{ type: text, text: resultText, }], }; } ); }4.3 配置管理与安全实践在上面的代码中我们通过process.env.OPENWEATHER_API_KEY获取 API 密钥。这是处理敏感信息的标准做法。永远不要将密钥硬编码在源代码中。如何配置环境变量开发环境在项目根目录创建.env文件确保该文件已被添加到.gitignore中。OPENWEATHER_API_KEYyour_actual_openweather_api_key_here然后你需要一个工具来在运行时加载这个文件。模板可能已经集成了dotenv如果没有可以安装npm install dotenv。并在你的入口文件src/index.ts的最顶部添加import * as dotenv from dotenv; dotenv.config();生产环境/Claude Desktop 配置在 Claude Desktop 的配置文件中通过env字段传递。{ mcpServers: { my-weather-server: { command: /path/to/your/binary, args: [], env: { OPENWEATHER_API_KEY: your_production_api_key } } } }4.4 在主服务器中注册新工具别忘了在src/index.ts中导入并注册这个新工具。// src/index.ts // ... 其他导入 ... import { registerWeatherTool } from ./tools/weather.js; async function main() { const server createMcpServer({ name: my-advanced-mcp-server, version: 1.0.0 }); registerGreetTool(server); registerWeatherTool(server); // 注册天气工具 const transport new StdioServerTransport(); await server.connect(transport); console.error(Advanced MCP server is running...); } // ...现在重新构建 (npm run build) 并更新 Claude Desktop 配置后你就可以直接问 Claude“上海今天天气怎么样” 它会自动调用你的get_weather工具并返回结果。5. 生产部署与性能优化指南当你的工具开发完成并测试通过后就需要考虑如何将其交付给其他用户或部署到稳定环境中。5.1 构建生产版本模板通常提供了build脚本它使用esbuild将 TypeScript 代码打包、压缩并输出到dist目录。npm run build检查dist/index.js文件这应该是一个独立的、优化过的 JavaScript 文件包含了所有依赖除了 Node.js 本身和原生模块。5.2 打包为独立可执行文件可选但推荐使用pkg打包可以彻底摆脱对目标机器 Node.js 环境的依赖。首先确保package.json中包含了pkg的配置或脚本。{ scripts: { build:pkg: pkg . --out-path ./build --targets node18-linux-x64,node18-macos-x64,node18-win-x64 }, pkg: { scripts: dist/index.js, assets: [], // 如果有静态文件如配置文件、证书需要在这里指定 targets: [node18-linux-x64, node18-macos-x64, node18-win-x64] } }运行打包命令npm run build:pkg这会在build目录下生成三个或你指定的目标平台可执行文件例如my-mcp-server-linux,my-mcp-server-macos,my-mcp-server-win.exe。用户下载对应的文件赋予执行权限Unix系统就可以直接运行了。5.3 部署方式与考量本地集成最常见将打包后的二进制文件分发给最终用户让他们按照上述方法配置到 Claude Desktop 中。这种方式简单直接数据在本地处理隐私性好。服务器部署高级场景如果你的工具需要访问强大的后端计算资源或集中式数据库你可以将 MCP 服务器部署在一台远程服务器上并使用SSEServer-Sent Events或WebSocket传输层而不是 STDIO。优势集中管理、资源强大、便于更新。挑战需要处理网络认证、安全HTTPS、以及客户端的网络配置。MCP 协议支持这些传输方式但配置比 STDIO 复杂。适用场景企业内部的 AI 助手接入公司知识库、需要 GPU 加速的模型推理服务等。5.4 性能与稳定性优化错误处理与日志确保所有工具都有完善的try-catch错误处理并向客户端返回友好的错误信息使用isError: true。同时在服务器端记录详细的日志可以使用winston或pino库便于排查问题。资源管理如果你的工具会创建数据库连接、文件句柄等资源务必在使用后正确关闭避免内存泄漏。请求限流与超时考虑在服务器层面或工具层面添加限流逻辑防止被恶意或错误的客户端请求打垮。为长时间运行的操作设置超时。工具描述的优化花时间精心编写工具的名称和描述。清晰、准确的描述能极大提高 LLM 调用工具的准确性和效率。可以思考 LLM 在什么场景下会需要这个工具并用自然语言描述出来。6. 常见问题与排查技巧实录在实际开发和部署中你难免会遇到一些问题。以下是一些常见问题的排查思路和解决方案。6.1 工具在 Claude Desktop 中不显示问题现象可能原因排查步骤与解决方案配置后重启 Claude工具列表为空或没有你的工具。1. Claude Desktop 配置路径错误。2. 配置文件 JSON 格式错误。3. MCP 服务器启动失败。1.检查路径确认claude_desktop_config.json文件位置正确且已被 Claude 读取可以尝试在配置中故意写错一个字段看 Claude 启动是否报错。2.验证 JSON使用在线 JSON 校验工具或jq命令检查配置文件语法。3.查看日志运行你的 MCP 服务器看是否有错误输出。在终端直接运行node dist/index.js观察是否有报错信息如缺少环境变量、模块未找到。4.简化测试在配置中将args指向一个简单的测试脚本echo {jsonrpc:2.0,method:tools/list}看 Claude 是否能调用并返回结果以排除服务器本身的问题。6.2 工具调用失败或返回错误问题现象可能原因排查步骤与解决方案Claude 尝试调用工具但提示调用失败或返回内部错误。1. 工具处理器函数抛出未捕获的异常。2. 输入参数不符合 Schema。3. 依赖的外部服务如天气 API不可用或返回错误。1.服务器端日志这是最重要的信息源。确保你的服务器在开发时运行在终端并查看详细的错误堆栈。2.验证输入在工具处理器开头打印传入的参数确认 AI 传递的数据格式符合预期。有时 LLM 会对参数进行不必要的转义或格式化。3.隔离测试编写一个简单的 Node.js 脚本模拟 MCP 客户端调用你的工具函数直接测试其逻辑排除协议层面的干扰。4.检查网络和权限如果是调用外部 API确保服务器有网络访问权限且 API 密钥有效、未过期。6.3 打包后执行文件无法运行问题现象可能原因排查步骤与解决方案使用pkg打包的可执行文件在目标机器上运行时报错如“文件未找到”或动态链接库错误。1.pkg未正确打包原生模块或静态资源。2. 代码中使用了__dirname或__filename等动态路径。1.检查pkg配置在package.json的pkg.assets字段中明确指定需要打包进去的静态文件路径如证书、配置文件模板。2.处理路径问题pkg运行时文件系统是虚拟的。避免使用fs.readFileSync(__dirname /file.json)。改用path.join(process.cwd(), config.json)来读取与可执行文件同目录的配置文件或者将配置完全通过环境变量或命令行参数传入。3.测试跨平台在目标平台如 Linux上打包或者使用 Docker 模拟目标环境进行打包。6.4 性能瓶颈与优化问题现象可能原因排查步骤与解决方案工具调用响应缓慢尤其是在处理复杂任务或大量数据时。1. 工具逻辑本身计算密集或 I/O 慢。2. 没有利用缓存。3. 服务器是单线程一个慢请求会阻塞其他请求。1.性能分析使用 Node.js 的--inspect标志或node --prof进行性能剖析找出代码中的热点。2.引入缓存对于频繁查询且结果变化不快的工具如天气可以缓存几分钟使用内存缓存如node-cache或外部缓存如 Redis来存储结果避免重复调用外部 API 或复杂计算。3.异步与非阻塞确保所有 I/O 操作文件读写、网络请求、数据库查询都使用异步 API避免使用同步函数阻塞事件循环。4.考虑拆分如果某个工具确实非常耗时可以考虑将其拆分为一个独立的、支持 SSE/WebSocket 的远程服务让 MCP 服务器作为轻量级代理去调用避免影响其他快速工具的响应。开发 MCP 服务器的过程本质上是在为 AI 构建一套标准化的“手”和“眼”。bsmi021/custom-mcp-template提供的这套脚手架极大地简化了从想法到可运行服务之间的工程化路径。它迫使你遵循良好的代码组织规范并提前考虑了开发、调试、构建、部署的全流程。当你熟练使用这个模板后你会发现为你的 AI 助手增添新能力就像编写一个简单的函数一样自然。剩下的就是尽情发挥你的想象力去创造那些真正能提升效率、解决实际问题的智能工具了。