ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

MCP Server实战:将91个常用工具打包发布与云端托管全记录

MCP Server实战:将91个常用工具打包发布与云端托管全记录 1. 项目背景为什么我会一口气把91个工具做成MCP先说结论2025年做AI Agent开发手里没有一套趁手的MCP工具集效率至少打折一半。前阵子我在做智能体项目频繁在“让模型调用工具”这件事上折返跑——每次写一个新的工具调用都要重新定义接口、调试参数、处理异常代码堆了一大堆真正能复用的却没多少。后来接触了MCP协议我第一反应是“这不就是把工具API统一成一套标准嘛”但真正上手之后才发现从设计到发布、再到托管每一步都有不少隐性成本。这个项目的目标很直接把手头高频使用的91个工具全部做成MCP Server统一通过npm发布同时把核心包托管到魔搭平台方便团队内部分发和后续集成。工具范围覆盖了日常开发中用到的文件处理、目录遍历、时间日期、网络请求、数据格式化等常见场景——说白了我就是想拥有一个“开箱即用”的通用工具集让任何支持MCP的客户端都能直接调用不用再重复造轮子。如果你现在正在做Claude、Cursor、或其他支持MCP的AI编程工具的插件开发或者你维护着一套内部工具库想把它们暴露给大模型使用那这篇文章应该能帮你省下不少时间。我会把从零搭建MCP Server、npm发布过程中遇到的各种报错、以及魔搭托管时踩过的坑全部记录下来包括那些网上搜不到明确答案的诡异问题。2. 整体方案设计MCP Server架构怎么拆2.1 MCP协议的核心逻辑在动手写代码之前我觉得有必要把MCP的底层逻辑捋一遍。MCPModel Context Protocol本质上是在“大模型应用”和“外部工具”之间定义了一层标准通信协议。它规定了三件事工具怎么描述自己名称、参数schema、工具怎么被调用请求/响应格式、以及结果怎么返回给模型结构化数据。这就好比你把一堆不同的电器插座全部改成了统一的国家标准接口——不管后面接的是电视还是冰箱插头插上去就能用。MCP Server做的事情就是把每个工具包装成符合规范的接口然后告诉客户端“我有哪些工具、每个工具需要什么参数、会返回什么结构”。具体到技术实现上MCP Server目前主要有两种传输方式stdio方式通过标准输入输出和客户端通信适合本地运行比如Claude Desktop调用本地MCP Server基本都是走这个。HTTP/SSE方式通过HTTP协议暴露服务适合远程调用也是魔搭托管时主要采用的模式。我的方案是两者都支持。本地调试用stdio部署到魔搭之后对外暴露HTTP接口这样既能本地快速验证又能远程共享。2.2 91个工具的分组策略91个工具听起来很多但如果全堆在一个Server里无论是启动速度、内存占用、还是后续维护都是灾难。我的做法是按领域分组成几个模块每个模块独立实现、独立测试最后在一个入口文件中统一注册。整体分组如下模块包含工具数量典型工具文件操作18读取文件、写入文件、追加内容、文件复制、移动重命名目录管理12列出目录、递归遍历、创建目录、计算目录大小数据处理22JSON解析、JSON转字符串、Base64编解码、URL编码解码网络请求10HTTP GET/POST、下载文件、请求头处理时间日期9当前时间、时间戳转换、格式化日期、时区换算文本处理12字符串替换、正则匹配、大小写转换、MD5哈希系统信息8系统平台、CPU架构、Node版本、内存信息每个工具都实现为独立的函数函数签名统一为(params) result其中params是JSON对象、result也是JSON对象这样MCP协议封装起来非常干净——不需要为每个工具写适配层只需要做一个通用的调用分发即可。2.3 为什么选择TypeScript npm组合工具集本身用JavaScript写完全没问题但我最终选了TypeScript原因有三第一类型安全。91个工具的参数要暴露给大模型去生成调用如果参数类型不清晰模型经常会产生莫名其妙的值。用TS定义好每个工具的inputSchema可以让模型在生成调用时“有据可循”。第二生成d.ts声明文件。发布到npm后其他开发者安装包时能获得完整的类型提示这对一个面向开发者生态的包非常重要。第三编译后可以同时输出CJS和ESM格式。MCP Server要兼容各种不同环境的调用方有的项目是CommonJS、有的已经切到ES Module两种格式都输出可以避免“格式不支持”这种低级问题。npm是Node生态最成熟的包分发渠道几乎所有的MCP客户端都支持通过npm安装MCP Server依赖这也是我选择npm作为分发介质的关键原因。3. 核心实现MCP Server构建全过程3.1 环境准备与依赖安装开发环境我建议直接用较新的Node LTS版本我这边用的是Node 18.18.0npm对应版本是9.8.1。如果你还在用Node 14或者更老的版本建议先升级否则后续很多依赖会报引擎不兼容。创建项目目录并初始化mkdir mcp-toolkit cd mcp-toolkit npm init -y然后安装核心依赖npm install modelcontextprotocol/sdklatest npm install zod^3.22.0 npm install typescript^5.0.0 --save-dev npm install types/node^18.0.0 --save-dev这里重点说一下modelcontextprotocol/sdk这个包它就是MCP官方提供的TypeScript SDK里面封装了MCP Server的所有底层逻辑——包括协议握手、消息路由、工具注册、请求响应分发。你不用自己实现协议细节只需要调用它的接口把工具注册进去就行。如果你安装时遇到npm err! code cert_has_expired这个报错大概率是npm镜像的HTTPS证书过期了。解决办法是把镜像切回官方源或者换一个证书正常的镜像具体我放到后面的“常见问题”章节详细说。3.2 用MCP SDK创建Server实例接下来看核心代码。创建一个src/server.ts文件用来创建MCP Server实例import { McpServer } from modelcontextprotocol/sdk/server/mcp.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; const server new McpServer({ name: mcp-toolkit, version: 1.0.0 }); // 注册工具的方法后面补充 export { server };这里McpServer是SDK提供的高层封装它内部做了很多协议层的事情。比如客户端连接时会先发初始化请求SDK会自动响应客户端发tools/list会返回当前注册的所有工具列表客户端发tools/call会路由到具体的工具执行函数。如果你想要更底层的控制也可以用Server类手动处理请求但我觉得对于大多数工具集场景直接用McpServer高层封装就够了省心且不容易出错。3.3 工具的注册与参数Schema定义每个工具注册时核心是告诉MCP框架三件事工具名称、参数描述、执行函数。其中参数描述用的JSON Schema格式它是让大模型“知道怎么调用工具”的关键。我举个例子注册一个“读取文件”工具import { z } from zod; server.registerTool( read_file, { title: 读取文件内容, description: 读取指定路径的文本文件内容并返回支持UTF-8编码, inputSchema: z.object({ filePath: z.string().describe(要读取的文件完整路径), encoding: z.string().optional().default(utf-8).describe(文件编码格式默认utf-8) }) }, async ({ filePath, encoding }) { const content await readFile(filePath, encoding); return { content: [{ type: text, text: content }] }; } );这里有个细节值得注意inputSchema用了zod来定义SDK内部会把Zod的schema转换成标准的JSON Schema格式。每个字段一定要写describe()描述因为这些描述会直接暴露给大模型模型会根据描述来理解参数的含义。描述越清晰模型生成的参数就越准确。返回格式方面MCP要求返回一个content数组数组里的每一项可以是text类型纯文本、image类型图片、或resource类型资源链接。我绝大多数工具都返回text类型这是最通用的形式所有客户端都支持。3.4 批量化注册91个工具的实现技巧如果91个工具都像上面那样一个个registerTool代码会非常冗余。我采用了一个“注册表”模式先用一个数组把所有工具的定义集中管理然后循环注册。工具定义的结构统一为interface ToolDefinition { name: string; description: string; handler: (params: any) Promiseany; inputSchema: z.ZodObjectany; }然后在src/tools/目录下按模块组织文件每个文件导出一个工具定义数组// src/tools/fileTools.ts export const fileTools: ToolDefinition[] [ { name: read_file, description: 读取文件内容, handler: async ({ filePath, encoding }) {...}, inputSchema: z.object({...}) }, // 其他17个文件相关工具 ];最后在入口文件里统一导入注册import { fileTools } from ./tools/fileTools.js; import { dirTools } from ./tools/dirTools.js; import { dataTools } from ./tools/dataTools.js; // ... const allTools [...fileTools, ...dirTools, ...dataTools, ...networkTools, ...timeTools, ...textTools, ...systemTools]; for (const tool of allTools) { server.registerTool( tool.name, { title: tool.name, description: tool.description, inputSchema: tool.inputSchema }, async (params) { const result await tool.handler(params); return { content: [{ type: text, text: JSON.stringify(result) }] }; } ); }这样做的另一个好处是方便测试——每个工具模块可以独立导入进行单元测试不需要启动整个MCP Server。3.5 本地验证用MCP Inspector调试代码写完之后本地调试是必须的步骤。MCP SDK官方提供了一个调试工具叫MCP Inspector用它可以直观地看到Server注册了哪些工具、每个工具的输入输出结构。先全局安装Inspectornpm install -g modelcontextprotocol/inspector然后在项目目录启动Inspector并连接我们的Servernpx modelcontextprotocol/inspector node dist/server.js浏览器会自动打开一个调试页面。在Inspector里你可以查看Tools List确认91个工具全部注册成功点击每个工具输入测试参数直接调用执行查看调用返回的数据结构是否符合预期我第一次调试时发现有几个工具的注册顺序不对导致工具ID重复覆盖在Inspector里立刻就能发现比写单测查问题快得多。4. npm发布实战从本地到全球分发的踩坑记录4.1 npm包发布前的准备清单发布npm包之前有几个准备工作不能跳过否则后面会各种报错。第一步检查npm账号并登录npm login如果之前没注册过npm账号需要先去 npmjs.com 注册。登录成功后npm会把这个凭证保存在本地配置文件里后续所有发布操作都基于这个凭证。第二步设置正确的package.json字段{ name: mcp-toolkit, version: 1.0.0, description: 91个常用工具的MCP Server封装支持stdio和HTTP两种传输方式, main: dist/index.js, types: dist/index.d.ts, bin: { mcp-toolkit: dist/cli.js }, files: [dist], scripts: { build: tsc, start: node dist/index.js, prepublishOnly: npm run build }, keywords: [mcp, mcp-server, ai, llm, model-context-protocol], license: MIT }有几个字段需要特别解释files字段很关键它决定哪些文件会被打包上传到npm。这里只放dist目录源码、测试文件、配置文件都不会被上传能大幅减小包体积。bin字段用来暴露命令行入口这样用户安装后可以直接在终端执行mcp-toolkit命令启动Server。前提是你需要有一个src/cli.ts文件里面调用stdio传输启动服务。prepublishOnly脚本会在发布前自动执行构建确保你发布出去的永远是编译后的最新代码。第三步创建.npmignore或利用files字段排除无用文件我建议直接用files白名单模式比.npmignore黑名单模式更可控不容易出现“忘记排除某种文件”的问题。4.2 构建TypeScript并生成声明文件TypeScript编译配置也有讲究。我的tsconfig.json关键配置如下{ compilerOptions: { target: ES2020, module: NodeNext, moduleResolution: NodeNext, outDir: ./dist, rootDir: ./src, strict: true, declaration: true, declarationMap: true, sourceMap: true, esModuleInterop: true, skipLibCheck: true, forceConsistentCasingInFileNames: true }, include: [src/**/*], exclude: [node_modules, dist, **/*.test.ts] }这里declaration: true是必须的它会生成.d.ts类型声明文件用户安装你的包后能获得完整的类型提示。module和moduleResolution设成NodeNext是为了兼容后续可能需要的ESM/CJS双格式输出。执行构建npm run build构建完成后检查一下dist目录内容ls dist # index.js index.d.ts cli.js tools/ ...确认产物正常后就可以打本地包测试了npm packnpm pack会在本地生成一个mcp-toolkit-1.0.0.tgz文件你可以把它安装到其他项目里做验证确认没有问题再真正发布。这一步强烈建议做因为发布到npm上是不可撤回的即使删包已经被别人引用的版本也无法移除。4.3 发布到npm第一次遇到的证书过期问题发布命令本身很简单npm publish但是我很确定如果你使用默认的官方registry大概率会遇到一个坑——证书过期问题。报错信息长这样npm ERR! code cert_has_expired npm ERR! errno cert_has_expired npm ERR! request to https://registry.npm.taobao.org/mcp-toolkit failed, reason: certificate has expired这个报错的原因很直白你本地的npm镜像地址指向了某个已停止维护的镜像源而这个源现在依然在用早期版本的CA证书证书过期后npm的HTTPS请求就完全没法建立连接。解决方式很简单# 查看当前镜像配置 npm config get registry # 如果输出的是 http://registry.npm.taobao.org 或者类似的镜像地址改成官方源 npm config set registry https://registry.npmjs.org改完之后再重新登录并发布这个问题就没了。顺带说一句如果你长期依赖镜像源加速建议关注镜像源的维护状态别等报错了才处理。现在很多镜像站点已经不再提供npm代理服务了与其用各种不稳定的第三方镜像不如直接用官方源配合本地缓存速度其实不差。4.4 实际发布流程与版本迭代策略发布成功后接下来就是版本迭代了。我的迭代策略遵循semver语义化版本规范主版本号major在大功能重构时1次版本号minor在新增工具时1补丁号patch在修复bug时1。每次发版前执行npm version minor # 自动升级次版本号并打tag npm publish这里有个建议npm version命令会自动修改package.json并创建git tag但如果你不想要git tag可以加--no-git-tag-version参数npm version minor --no-git-tag-version另外npm发布是“不可覆盖”的。同一个版本号只能发布一次再补充代码就需要升级版本号。如果发现发布的包有严重bug可以使用npm unpublish撤销但有时间限制——发布后72小时内可以撤销超过72小时就只能发布新版本修复。所以发版前务必在本地自测充分。4.5 从零到上线发布MCP Server到npm的完整流程整理一下发布MCP Server到npm的完整流程方便你照着做确认node/npm版本可用node -v npm -vnpm login登录npm账号修改package.json中name、version、files、bin等字段执行npm run build编译TypeScript执行npm pack本地打包检查内容在临时目录安装tgz包写一个简单测试脚本验证可运行执行npm publish发布发布完成后在另一台机器上执行npm install mcp-toolkit验证安装每一步看起来简单但漏掉任何一步都可能在用户侧产生问题。尤其是第6步——本地包验证很多人跳过它直接发布结果用户安装后才发现bin路径写错、入口文件引用错误等低级问题非常尴尬。5. 魔搭托管实践把MCP Server部署到云端的经验5.1 为什么选择魔搭托管既然MCP Server已经通过npm发布了为什么还要做魔搭托管原因很直接npm包适合开发者本地安装使用但如果你想要一个随时可访问、无需安装、跨设备共享的MCP服务端点就需要云端托管。魔搭平台ModelScope提供了模型和应用的托管能力你可以把MCP Server作为一个AI应用托管到魔搭上它会分配一个公开的HTTP地址任何支持远程MCP的客户端比如Claude Desktop、Cursor等都可以直接通过URL连接使用。从架构上看这解决了“我用npm包方式只能本机自己调试”的局限——团队里其他人想用你的工具集不需要装Node环境不需要npm install直接在客户端配置里填一个URL就能连上。5.2 魔搭托管的MCP Server改造要让MCP Server跑在魔搭的HTTP环境里需要做一个关键的改造从stdio传输切换到HTTP/SSE传输。本地调试时我们用StdioServerTransport它通过标准输入输出通信。但在云端客户端无法直接访问Server的stdin/stdout必须通过HTTP来通信。MCP SDK提供了StreamableHTTPServerTransport和SSEServerTransport两种HTTP传输方式。我的方案是写一个Express服务器用SSE方式暴露MCP接口import express from express; import { SSEServerTransport } from modelcontextprotocol/sdk/server/sse.js; import { server } from ./server.js; const app express(); app.get(/sse, async (req, res) { const transport new SSEServerTransport(/messages, res); await server.connect(transport); }); app.post(/messages, express.json(), async (req, res) { // SSE传输模式下的消息接收端点 }); app.listen(3000, () { console.log(MCP Server listening on port 3000); });在魔搭上托管时应用的端口不是自己决定的而是平台会注入一个环境变量PORT你需要监听这个端口const port process.env.PORT || 3000; app.listen(port, () { console.log(MCP Server listening on port ${port}); });5.3 魔搭托管的实际部署坑点这块是我踩坑最密集的地方整理几个典型的坑点一SSE连接超时问题HTTP远程连接和本地stdio连接最大的不同在于本地进程的持久性有保障而HTTP服务可能会在没有请求时被平台回收空闲连接。如果客户端长时间没有调用工具SSE连接可能会断开需要客户端自动重连。这在魔搭的免费托管环境里特别明显——空闲一定时间后应用会被挂起第一次请求需要等恢复延迟会比较高。解决方法是在客户端配置里减少心跳间隔或者在服务端增加Keep-Alive心跳。坑点二环境变量与配置文件本地调试时你可能在.env文件里配置了各种密钥和参数魔搭托管时这些配置文件不会自动带上去。你需要把环境变量手动配置到魔搭应用的环境变量设置中或者把配置直接写到代码里不推荐容易泄露。坑点三构建流程的差异魔搭的托管平台一般支持从源码构建和部署你需要提供明确的启动命令。比如npm run build npm start同时要注意平台可能不会执行npm install的devDependencies安装——如果构建脚本依赖TypeScript编译器你得确认安装阶段会同时安装devDependencies否则构建会失败。坑点四CORS跨域限制如果你的MCP Server要接Web前端或者某些客户端是在浏览器环境跑的CORS跨域问题就躲不开。需要在Express中间件里统一处理CORS头app.use((req, res, next) { res.setHeader(Access-Control-Allow-Origin, *); res.setHeader(Access-Control-Allow-Methods, GET, POST, OPTIONS); res.setHeader(Access-Control-Allow-Headers, Content-Type); if (req.method OPTIONS) return res.sendStatus(200); next(); });5.4 魔搭托管的MCP服务验证部署成功后怎么验证云端MCP Server能正常被客户端调用我建议用两种方式验证第一种用MCP Inspector连接远程地址在Inspector的配置界面输入魔搭分配的应用URLSSE端点选择远程连接模式确认能加载到工具列表然后调用一个简单工具测试。第二种直接写一个Node脚本调用import { Client } from modelcontextprotocol/sdk/client/index.js; import { SSEClientTransport } from modelcontextprotocol/sdk/client/sse.js; const transport new SSEClientTransport(new URL(https://your-app.modelscope.cn/sse)); const client new Client({ name: test-client, version: 1.0.0 }); await client.connect(transport); const tools await client.listTools(); console.log(工具数量: ${tools.tools.length}); const result await client.callTool({ name: get_current_time, arguments: {} }); console.log(result);如果这段脚本能正常输出工具列表和调用结果说明云端托管完全可用。6. 高频报错排查与避坑指南6.1 Windows环境npm不可用的经典报错及解法无论你是做MCP Server开发还是单纯想在Windows上安装npm包大概率会遇到这个经典报错npm : 无法加载文件 C:\Program Files\nodejs\npm.ps1因为在此系统上禁止运行脚本这个问题根因是PowerShell的执行策略默认不允许运行脚本文件而npm.ps1恰恰是一个PowerShell脚本。npm本身是Node自带的并没有问题。最快的解决方案有两种# 方案一以管理员身份打开PowerShell修改当前用户的执行策略 Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser # 方案二只对当前会话临时放开 Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass方案一会永久修改当前用户的PowerShell策略比较方便方案二只对当前终端窗口有效安全一些但每次要重设。我个人建议用方案一RemoteSigned只允许运行本机创建的脚本和互联网上下载但已签名的脚本安全性已经足够。6.2 npm镜像、证书过期、网络连接异常相关报错汇总开发过程中npm安装和发布相关的报错有一大部分是网络和源配置引起的。我整理了一个速查表报错信息根本原因解决方案npm ERR! code cert_has_expired镜像源证书过期npm config set registry https://registry.npmjs.orgnpm WARN deprecated node-domexception1.0.0依赖了废弃包但仅是警告无需处理等待依赖方更新npm ERR! code EUNSUPPORTEDPROTOCOLpackage.json中依赖的git协议不支持将git://改成https://npm : 无法将“npm”项识别为 cmdlet...npm不在PATH环境变量中重装Node并勾选添加到PATH或手动配置环境变量npm ERR! request to https://registry.npm.taobao.org/... failed镜像源本身不可用切换官方源或其他可用镜像npm WARN using --force recommended protections disabled使用了--force参数正常发布不用加force确认无冲突其中EUNSUPPORTEDPROTOCOL这个错误比较隐晦它一般出现在你安装某个依赖包时该包的package.json里声明了git://协议的依赖地址。新版npm出于安全考虑不支持这种协议解决办法是全局配置git协议替代git config --global url.https://github.com/.insteadOf git://github.com/6.3 NPM环境变量配置的正确姿势很多新人会在Windows上遇到“npm不是内部或外部命令”的报错这是环境变量配置问题。正常安装Node.js时安装器会把C:\Program Files\nodejs\添加到系统PATH。如果你之前安装过不同版本的Node导致PATH混乱或者手动解压了Node压缩包而没有配置环境变量就会触发这个问题。手动配置环境变量的步骤右键“此电脑” → 属性 → 高级系统设置 → 环境变量在“系统变量”中找到Path点击编辑新增一行内容为Node的实际安装路径比如D:\nodejs\确定保存重新打开终端窗口配置完后执行npm -v能正常输出版本号就说明配置成功了。不过我的建议是如果条件允许直接用官方安装包安装Node别用手动解压的方式能少踩很多环境变量相关的坑。6.4 MCP Server运行时的常见问题工具集运行过程中也遇到过一些值得记录的问题问题一工具返回内容过大导致传输失败MCP协议对返回内容大小没有统一的硬性限制但客户端和服务端的实现可能有实际限制。读取大文件时一次性返回整个内容会把消息体积撑爆。我的解决方案是对大文件读取工具做截断处理增加一个maxLength参数默认只返回前100KB内容并提供偏移量参数让调用方分批次读取。问题二异步处理未等待导致返回空结果写工具时如果异步处理回调没有正确await很容易返回一个未解析的Promise对象MCP客户端接到的就是个空壳。排查方法是统一在工具执行函数外面包一层异常捕获和Promise等待处理async function safeExecute(handler: Function, params: any) { try { const result await Promise.resolve(handler(params)); return { success: true, data: result }; } catch (error: any) { return { success: false, error: error.message || String(error) }; } }问题三工具名冲突MCP协议中工具名是全局唯一的标识如果两个工具注册了相同的名字后者会覆盖前者导致调用行为异常。批量注册时一定要加一个名称去重校验逻辑确保没有重复项。问题四SDK版本不一致如果你的Client和Server用的MCP SDK版本跨度太大协议版本协商可能出问题。比如某些旧版SDK的实现不支持新版协议特性。遇到连接失败或tools/list响应为空时先检查两端SDK版本是否兼容。7. 扩展思路这个项目还能怎么玩91个工具做完MCP Server发布到npm也托管到云端了项目到这里已经是一个完整可用的状态。但回头看这个项目的扩展空间还很大。方向一接入更多垂类工具91个工具覆盖的是通用基础操作后续可以针对特定领域扩展。比如数据库工具集MySQL/PostgreSQL查询、浏览器自动化工具集、代码分析工具集、甚至Git操作工具集——每个方向做一套独立的MCP包按需安装比一个大而全的包更灵活。方向二支持多语言客户端我目前的实现是基于Node/TypeScript的但MCP协议是语言无关的。官方SDK除了TypeScript还有Python、Java、Kotlin、C#等语言的版本。如果团队里有Python开发者可以考虑用Python重写一套或者通过HTTP桥接方式让Python客户端也能调用Node实现的Server。方向三工具调用上增加缓存和限流云端托管的MCP Server如果被多人使用无差别的调用可能导致资源竞争。可以给一些耗资源的工具加上Redis缓存相同参数短时间内的重复调用直接走缓存同时给单客户端做速率限制防止少数客户端占用大量资源。方向四把工具集接入到自定义Agent框架最后我觉得这个项目最有价值的地方在于当你有一套标准化的MCP工具集之后任何支持MCP的Agent框架都能直接使用它不需要为不同框架写不同的插件。这是一个标准协议带来的生态红利也是我当初决定投入时间做这个项目的最重要原因。
返回列表