用Vibe Coding开发MCP服务:从自然语言到AI工具的完整实战 1. 别再把Vibe Coding当成“动嘴写代码”了最近“Vibe Coding”这个词的热度高得吓人社区里的讨论也极端分化。有人觉得这是程序员的终极偷懒神器对着AI说几句话就能出活也有人嗤之以鼻觉得不过是把一堆错误代码从Stack Overflow复制到了对话窗口里。我自己的感受是这两种看法都没说到点子上。Vibe Coding的正确姿势不是“靠感觉乱写”而是用自然语言作为接口在AI的辅助下完成一套有逻辑、可验证、能交付的工程闭环。它考验的不再是你敲键盘的指速而是你把需求拆解成指令、再把AI输出收敛为可用产物的能力。而这其中最有代表性的一个实践方向就是用Vibe Coding的方式去开发一个MCP服务。MCPModel Context Protocol模型上下文协议是Anthropic在2024年底开源的一套标准化协议它解决的是大模型与外部数据、工具之间的互联互通问题。可以这么理解以前每接一个数据源你都要为它写一套专用适配器对接逻辑各不相同维护成本极高。MCP则提供了一套统一的标准让模型可以通过一种通用方式去调用各种工具、读取各种数据源。它像是给大模型世界装了一个标准USB接口什么设备都能插插上就能用。这篇文章我想用一次完整的实操过程带大家走一遍“用Vibe Coding开发MCP服务”的全流程。从环境准备、项目初始化到服务编写、调试验证再到最后挂到客户端里跑起来每一步我都会讲清楚为什么这么做而不仅仅是怎么填代码。写代码这件事本质上是在做决策。Vibe Coding做得好的人不是因为他们提问提得多好而是因为他们知道自己每一步在做什么决策、为什么做这个决策。而这也是MCP这套协议设计的核心思想把工具调用的决策权交给模型但把工具本身的定义权留给开发者。2. 先搞懂MCP的“长宽高”这个协议到底在解决什么问题很多人上手MCP会遇到一个困惑读文档时觉得概念很简单但真要自己写一个服务又不知道从哪下手。这个问题的根源在于大多数人只记住了MCP的API结构却没有理解它出现的背景和要解决的场景。2.1 从“每个AI应用配一套插件系统”说起在MCP出现之前每个AI应用都在做自己的插件生态。OpenAI有它的Function Calling规范LangChain有它的Tool抽象各种框架都有自己的Agent工具接入方式。看起来百花齐放实际上是一场灾难你给一个项目写好的工具接入逻辑换个框架就要重写换个模型厂商又要重写。我有个朋友在公司做内部AI助手半年时间给同一个数据库查询功能写了三套不同的接入代码分别对接不同的模型服务商。代码本身不复杂但维护三套逻辑的时间成本、上下文切换成本非常可观。而且每次模型厂商更新API三套代码都要跟着改。MCP的核心价值就在这里它定义了一套模型无关、框架无关的工具接入标准。你写好的MCP服务理论上可以被任何支持MCP协议的客户端使用——不管是Claude Desktop、各类IDE插件还是你自己写的Agent应用。2.2 粗读一下协议层的三类核心概念MCP的协议结构不复杂核心就三类概念Tools工具这是最常用的一类本质上是暴露给模型的可调用函数。模型根据用户的指令决定是否调用某个工具、传入什么参数。比如一个查询天气的工具、一个操作数据库的工具。Resources资源这类是给模型提供只读数据用的。比如一个项目的文档说明、一份数据文件的内容。模型可以按需读取这些资源来获取上下文。Prompts提示词模板这类是预定义的对话模板或者工作流模板客户端可以主动触发。比如一个“代码审查”模板、一个“生成测试”模板。打个比方Tools是模型的手和脚用来操作外部世界Resources是模型的眼睛用来获取信息Prompts是模型的工作流程手册告诉它面对某个任务时该按什么步骤走。2.3 传输层本地进程和远端服务各有什么讲究MCP的传输层设计也很值得一提。它提供了两种主要模式stdio标准输入输出本地客户端以子进程方式启动MCP服务通过标准输入输出来与模型进行交互。这是最常用、也是最容易上手的方式适合个人开发调试。它像是你给模型配了一个专属助手这个助手在后台跑着模型通过管道跟它对话。Streamable HTTP可流式HTTP传输把MCP服务部署成远端HTTP接口适合生产环境、多客户端共享的场景。这种方式的部署形态更像一个常规的后端服务。刚开始做MCP开发优先选stdio模式。原因很简单没有网络权限、进程生命周期由客户端管理、日志输出直接打在终端里调试起来极其直观。等你把工具逻辑跑通再考虑要不要迁移到HTTP模式。2.4 Vibe Coding和MCP的天然契合点回到Vibe Coding这个主题。你会发现Vibe Coding的核心是用自然语言表达意图而MCP的核心是让程序理解意图后去调用正确的工具。这两个理念在本质上是一致的。当你用Vibe Coding的方式去开发一个MCP服务时整个过程其实就是一个“AI帮AI造工具”的闭环你向代码模型描述你要什么类型的工具它帮你生成一个标准的MCP服务而这个服务最终又是给另一个模型用户对话的大模型用的。所以这个项目的真正价值不只是学会MCP API怎么用而是通过一次Vibe Coding实战建立“从意图到交付”的完整工程思维。3. 开工前必做的三个决定语言、SDK与项目形态很多教程跳过选型直接让你跑代码这是很不负责任的。选型决定了你后面会遇到多少坑。3.1 为什么优先选TypeScript而不是Python当前MCP官方SDK有Python和TypeScript两个版本社区生态里TypeScript明显更活跃一些。我做这个项目用的是TypeScript原因有三第一TypeScript SDK的类型定义非常完善。开发MCP服务时你需要和Tool的输入输出结构打交道TS的自动补全和类型校验能帮你减少很多低级错误。第二Node.js的进程管理机制让stdio模式跑起来非常顺手Vibe Coding过程中经常要反复重启服务验证Node的启动速度快体验好。第三如果你想把这个MCP服务集成到VS Code生态里TypeScript天然有优势。而目前大多数AI编程工具链都是基于Node/TS构建的。Python当然也能做而且如果团队的主流技术栈是Python选Python反而更合适。工具选型从来不是选最好的而是选最贴合你场景的。3.2 CLI脚手架用stdio还是Npx快捷启动MCP官方提供了一个CLI脚手架帮你初始化项目结构。但我实测下来脚手架生成的代码反而加了太多抽象层对初学者不友好。我更推荐用手动初始化的方式全裸代码跑通一遍所有逻辑都看得见摸得着出问题也好排查。项目目录结构就直接简单三件套src/index.ts——服务入口负责构建服务和启动传输src/tools/——工具定义目录每个工具一个文件package.json——依赖和启动脚本3.3 规划我们要开发的MCP服务功能既然是教程项目工具本身要有业务意义但又不能太复杂。我选了一个“开发辅助工具集”作为演示项目。这个MCP服务提供三个工具获取指定GitHub仓库的基础信息描述、语言、Stars数查询某段代码的圈复杂度衡量代码复杂度的指标生成符合语义化版本规范的版本号建议这三个工具各有侧重第一个演示如何调用第三方HTTP接口第二个演示如何处理本地数据并返回结构化结果第三个演示如何让模型基于规则做决策。覆盖了MCP工具开发的常见模式。4. 手把手实操用Vibe Coding从零写出一个MCP服务从这一步开始我们正式进入Vibe Coding的实战环节。我会把我在实际操作中使用的每一轮对话指令、代码产出和迭代思路展示出来大家可以直接照着做。4.1 项目初始化项目根目录就叫dev-assistant-mcp初始化命令我是直接让AI帮我生成的npm init -y npm install modelcontextprotocol/sdk zod npm install -D typescript ts-node types/node依赖装完配置TypeScript编译选项。这里有一个容易踩坑的点MCP SDK需要Node 18以上版本建议先用node -v确认一下环境。我当时就被Node 16卡过几分钟SDK跑起来报了一个不痛不痒的语法错误排查半天才发现是版本问题。4.2 第一轮Vibe Coding对话让AI生成服务骨架我用这样一条指令启动Vibe Coding“用TypeScript写一个MCP服务骨架使用modelcontextprotocol/sdk基于stdio传输方式注册一个helloWorld工具工具接收name参数返回问候语。代码结构要清晰主入口文件为src/index.ts。”AI返回的代码结构大差不差import { McpServer } from modelcontextprotocol/sdk/server/mcp.js; import { StdioServerTransport } from modelcontextprotocol/sdk/server/stdio.js; import { z } from zod; // 创建MCP服务器实例 const server new McpServer({ name: dev-assistant, version: 0.1.0 }); // 注册一个问候工具 server.tool( helloWorld, { name: z.string().describe(用户名) }, async ({ name }) { return { content: [{ type: text, text: 你好${name}这是你的第一个MCP工具。 }] }; } ); // 通过标准输入输出启动服务 const transport new StdioServerTransport(); await server.connect(transport);看到这里你可能发现了这个结构并不复杂核心就三件事——创建服务、注册工具、连接传输层。但要注意几个细节这些是后续扩展的关键zod的describe不仅用来描述参数语义好的描述能大幅提升模型调用工具的准确率。你希望模型在你说话含混时准确判断该填什么参数就得把参数描述写得清晰准确。工具返回值的类型是MCP规定的结构化格式content数组里可以放多个内容块不只是纯文本还可以放图片、资源引用等。代码末尾那句await server.connect(transport)会一直保持进程存活所以不需要额外的监听代码。4.3 第二轮对话从helloWorld到业务工具骨架跑通之后我开始往里面加业务逻辑。第一轮对话我已经验证了链路是通的第二轮直接让AI扩展工具集“在现有MCP服务基础上新增两个工具。第一个是getRepoInfo接收owner和repo参数调用GitHub公开API获取仓库信息返回描述、语言、Stars数和Fork数。第二个是estimateCodeComplexity接收一段代码字符串返回估算的圈复杂度。注意错误处理要完善。”这里我刻意描述得比较具体因为Vibe Coding的效率很大程度上取决于你给出的信息密度。AI对模糊指令的理解很多时候并不比你第一次提问时想得更周全。你描述得越具体它生成的代码越接近你的预期后续要改动的地方也就越少。getRepoInfo的代码逻辑server.tool( getRepoInfo, { owner: z.string().describe(仓库所属用户或组织名), repo: z.string().describe(仓库名称) }, async ({ owner, repo }) { try { const response await fetch(https://api.github.com/repos/${owner}/${repo}, { headers: { Accept: application/vnd.githubjson } }); if (!response.ok) { throw new Error(GitHub API请求失败: ${response.status}); } const data await response.json(); return { content: [{ type: text, text: JSON.stringify({ name: data.name, description: data.description, language: data.language, stars: data.stargazers_count, forks: data.forks_count }, null, 2) }] }; } catch (error) { return { content: [{ type: text, text: 查询失败: ${error instanceof Error ? error.message : String(error)} }] }; } } );estimateCodeComplexity的实现稍微有点算法意味。圈复杂度的基础计算规则是每个if、for、while、case、catch等分支结构加1。简单实现就是统计这些关键字的出现次数server.tool( estimateCodeComplexity, { code: z.string().describe(待分析的源代码) }, async ({ code }) { // 去掉注释和字符串内容避免误统计 const cleanedCode code .replace(/\/\*[\s\S]*?\*\//g, ) .replace(/\/\/.*$/gm, ) .replace(/[][^]*[]/g, ); const patterns [ /\bif\b/g, /\bfor\b/g, /\bwhile\b/g, /\bcase\b/g, /\bcatch\b/g, /\b\b/g, /\b\|\|\b/g ]; let complexity 1; for (const pattern of patterns) { const matches cleanedCode.match(pattern); if (matches) complexity matches.length; } const level complexity 5 ? 低 : complexity 10 ? 中 : 高; return { content: [{ type: text, text: JSON.stringify({ complexity, level }, null, 2) }] }; } );注意我在清理阶段去掉了注释和字符串内容这一步很关键。如果不做清理代码里只要字符串包含关键词复杂度就会被虚高计算。我在第一版实现时没加这个逻辑测试时发现一段只含普通字符串的代码复杂度居然到了7明显是误报。4.4 第三轮对话补充验证工具三个工具的第三个——语义化版本建议工具这轮主要是想演示让模型基于规则做决策的场景。工具接收当前版本号和变更类型返回建议的下一个版本号server.tool( suggestNextVersion, { currentVersion: z.string().describe(当前版本号如1.4.2), changeType: z.enum([major, minor, patch]).describe(变更类型主版本/次版本/补丁) }, async ({ currentVersion, changeType }) { const parts currentVersion.split(.).map(Number); if (parts.length ! 3 || parts.some(isNaN)) { return { content: [{ type: text, text: 无效的版本号格式请使用MAJOR.MINOR.PATCH格式 }] }; } const [major, minor, patch] parts; if (changeType major) { parts[0] major 1; parts[1] 0; parts[2] 0; } else if (changeType minor) { parts[1] minor 1; parts[2] 0; } else { parts[2] patch 1; } const nextVersion parts.join(.); return { content: [{ type: text, text: JSON.stringify({ currentVersion, changeType, nextVersion }, null, 2) }] }; } );到这里三个工具都写完MCP服务的骨架已经具备了完整的业务能力。5. 本地联调如何验证MCP服务真正可用写完代码不等于完事Vibe Coding的环节里最重要的一步是验证。AI生成的代码在逻辑上可能浮于字面只有真正跑起来才知道能不能用。5.1 用MCP Inspector做可视化调试官方提供了一个叫modelcontextprotocol/inspector的调试工具特别适合做MCP开发时的联调。安装并启动npx modelcontextprotocol/inspector node dist/index.jsInspector会在本地起一个Web页面你可以在里面看到服务注册了哪些Tool、手动触发测试调用、检查返回的JSON结构。它的界面分为几个区左侧是工具列表点击任意工具可以看到它的参数Schema右侧是调用区填入参数后点击Call下方立刻显示返回结果。这个调试工具的价值在于它给你提供了一条观察MCP服务“真实行为”的路径能极大帮助你快速判断工具定义是否合理、结果是否正常、报错信息是否清晰。我去掉了很多“代码看起来对但跑起来是另一回事”的情况基本都是靠Inspector现场抓的。5.2 用MCP Inspector做可视化调试除了图形化工具以外你还可以用命令行方式快速验证。写一个简单的测试脚本模拟MCP客户端连接服务并发起一次调用import { Client } from modelcontextprotocol/sdk/client/index.js; import { StdioClientTransport } from modelcontextprotocol/sdk/client/stdio.js; const transport new StdioClientTransport({ command: node, args: [dist/index.js] }); const client new Client({ name: test-client, version: 0.1.0 }); await client.connect(transport); // 列出所有工具 const tools await client.listTools(); console.log(注册的工具:, tools.tools.map(t t.name)); // 调用getRepoInfo const result await client.callTool({ name: getRepoInfo, arguments: { owner: facebook, repo: react } }); console.log(调用结果:, result.content[0].text); await client.close();运行输出大概是这样注册的工具: [ helloWorld, getRepoInfo, estimateCodeComplexity, suggestNextVersion ] 调用结果: { name: react, description: The library for web and native user interfaces, language: JavaScript, stars: 220000, forks: 45000 }链路通没通、返回数据是否正确一目了然。这种验证方式跟我说的“确认了链路是通的”息息相关。现在项目的功能已经成型但要做成一个可以稳定的服务仍然有一些工程化层面的处理没有做。下一节我重点讲我在真实生产构建里会优先补齐的几个点——这部分的坑我没少踩。6. 从跑通到稳定Vibe Coding后的几个硬核工程问题很多Vibe Coding教程到上一步就结束了仿佛“调通Demo”就是终点。但真实项目里半天的惊喜很快会被硬问题打醒。以下三个问题是我在投入使用前一定会处理的。6.1 超时与错误处理工具不能“卡死”会话MCP的每次工具调用模型侧通常会有超时时间限制。如果你的工具内部调用了外部API而外部API迟迟不返回整个工具调用就会被中断这在体验上非常糟糕。给fetch加超时控制是基本操作。一个实用的做法是用AbortSignal.timeout()const response await fetch(url, { headers: { Accept: application/vnd.githubjson }, signal: AbortSignal.timeout(10000) // 10秒超时 });同时工具内部也要兜底如果外部接口挂了返回给模型的消息要包含“当前服务暂不可用”之类的提示让模型可以明确告诉用户而不是返回一堆崩溃堆栈——模型的自动流程往往会被半懂不懂的错误日志整懵。6.2 参数校验模型有时候会“一本正经地传错参”虽然我们用zod写了详细的参数描述但模型的参数提取仍可能出现偏差。比如用户问“帮我查一下react仓库的信息”模型可能传一个不带owner格式的值也可能传来带空格的全名。我的做法是在工具函数内部再加一道防御性校验。拿getRepoInfo举例const fullName ${owner}/${repo}; if (!/^[a-zA-Z0-9-]\/[a-zA-Z0-9-_.]$/.test(fullName)) { return { content: [{ type: text, text: 仓库格式无效正确格式为owner/repo例如 facebook/react }] }; }这类写进代码里的兜底逻辑比让模型自己犯错了再补救更省事。Vibe Coding阶段AI生成的代码普遍缺少这些“脏活”但这恰恰是工程稳定性的分水岭。6.3 构建脚本与进程管理TypeScript项目开发时用ts-node直接跑很方便但给客户端连接时建议编译成纯JavaScript再跑。一是启动更快二是不依赖额外的运行时避免各种版本兼容问题。在package.json里加上构建脚本{ scripts: { build: tsc, start: node dist/index.js } }还有一个细节MCP服务通过stdio连接时日志输出会污染与模型对话的通道千万别用console.log打印调试信息。调试信息一定要走console.error不然模型会读到一堆乱码。这一点我在第一次联调Claude Desktop时踩了个结结实实的坑折腾了快一个小时才反应过来。7. 把服务挂到客户端以Claude Desktop和VS Code为例工具写好了调试也通过了最后一步就是把服务挂到实际客户端里让模型真正能用上。7.1 Claude Desktop的配置方式Claude Desktop现在原生支持MCP。在配置文件claude_desktop_config.json里加上一行{ mcpServers: { dev-assistant: { command: node, args: [/绝对路径/dev-assistant-mcp/dist/index.js] } } }配置完重启Claude Desktop对话框旁边会出现一个工具图标点开就能看到dev-assistant服务里注册的所有工具。这时候你跟模型说“帮我看看facebook/react的信息再分析一下它README代码片段的复杂度”模型就会自动调用我们写的工具。这里有个路径细节要注意要用绝对路径。相对路径在某些启动场景下会失效因为客户端的工作目录可能跟你的预期不一致。7.2 VS Code里用MCP服务给Agent供能VS Code的AI编程插件现在很多都支持配置MCP服务。以Continue插件为例在它的配置文件里加上类似的server定义AI插件就能在代码编辑时调用你的工具。这意味着你可以把自己的私有工具链接入到日常编码流程中实现定制化的代码辅助。比如我可以让这个MCP服务连接内部文档库资源让AI写代码时能实时查询项目规范。这种把私有知识注入AI工作流的玩法正是MCP协议最有想象力的场景。7.3 让工具返回“模型友好”的结果最后提一个Vibe Coding方法论的进阶技巧MCP工具的返回值不是给用户看的是给模型看的。你在设计返回内容时要让模型容易理解、容易基于它继续回答。比如getRepoInfo返回的不该是一堆原始JSON而是带上简明摘要的结构化文本“XX库是XX框架主要语言为XX当前有XX个Stars”。这样模型拿到结果后能直接用自然语言转述给用户而不是再做一次JSON解析。一开始写工具时我习惯只返回原始数据模型经常理解得又臭又长。后来调整了输出格式整个对话质量提升了一个档次。8. 真正上手Vibe Coding前这几点想清楚了再动手把这个MCP服务完整走下来你会发现Vibe Coding的核心能力模型已经变了它不再考验你记得多少API而是考验你能不能把需求描述清楚、能不能判断AI产出是否可靠、能不能修正和收敛结果。8.1 指令的“信息密度”决定产出质量我在第三轮对话里能一次生成三个工具的正确实现是因为我在指令里不仅给出了工具名称还写清了参数类型、功能范围、甚至实现思路。这套把复杂需求拆成指令细节、再交给AI落地的能力就是Vibe Coding基本功。别指望AI会读心术你给的信息越多、越具体它写得就越接近你的预期。8.2 永远保留一个“验证闭环”写代码可以Vibe但验证不能Vibe。每次生成完代码都要用本地联调把链路完整跑一遍工具注册了没有、参数接收对不对、返回格式是不是标准、异常情况报什么错。这步省不了而且要养成习惯。8.3 MCP是当前Vibe Coding生态里最值得投入的方向为什么这轮AI热潮里MCP能火因为它是大模型和外部世界之间的“公共基础设施”。AI编程工具一个接一个出现但底层都需要一套连接外部能力的方式。MCP这套标准如果跑通会像当年的HTTP协议一样成为AI时代的通用语言。我做这个项目的过程中最大的收获不是掌握了三个工具的写法而是理解了“协议思维”在AI工程中的分量。模型越来越聪明但聪明的模型也需要标准化的出口来接触世界。而掌握了MCP就等于提前拿到了这个出口的建造图纸。所以如果你也想认真对待Vibe Coding这件事别停留在让AI帮你写几个函数去做一个完整的、能被真实消费的服务出来。让你的代码成为另一个AI的“工具”这个体验完全不同。