当前位置: 首页 > news >正文

第 2 篇:手写一个 MCP Server——从零到跑通

第 2 篇手写一个 MCP Server——从零到跑通上一篇讲了 MCP 的概念这一篇直接上手。我们用 Node.js 写一个能用的 MCP 服务器——它能查询天气。目标很简单写完之后任何支持 MCP 的 AI 客户端都可以通过这个 Server 获取天气信息。2.1 准备工作你需要装好Node.js一个趁手的编辑器然后建个目录初始化项目mkdirmcp-weather-servercdmcp-weather-servernpminit-y安装 MCP SDKnpminstallmodelcontextprotocol/sdkSDK 帮我们处理了底层的 JSON-RPC 通信、传输层、协议握手。我们只需要关心业务逻辑。2.2 实现一个最简单的 Server在项目根目录下新建index.jsimport{Server}frommodelcontextprotocol/sdk/server/index.js;import{StdioServerTransport}frommodelcontextprotocol/sdk/server/stdio.js;import{CallToolRequestSchema,ListToolsRequestSchema,}frommodelcontextprotocol/sdk/types.js;// 1. 创建一个 Server 实例constservernewServer({name:weather-server,version:1.0.0,},{capabilities:{tools:{},// 声明这个 Server 支持 Tools},},);// 2. 定义 Tools 的处理逻辑server.setRequestHandler(CallToolRequestSchema,async(request){const{name,arguments:args}request.params;if(nameget_weather){constcityargs?.city||深圳;// 模拟天气数据constweatherData{city:city,temperature:Math.floor(Math.random()*15)20,condition:晴,humidity:Math.floor(Math.random()*30)50,};return{content:[{type:text,text:JSON.stringify(weatherData,null,2),},],};}thrownewError(未知工具:${name});});// 3. 定义 Server 暴露了哪些工具server.setRequestHandler(ListToolsRequestSchema,async(){return{tools:[{name:get_weather,description:查询指定城市的天气,inputSchema:{type:object,properties:{city:{type:string,description:城市名称如 北京、上海、深圳,},},required:[city],},},],};});// 4. 启动 Server通过 stdio 传输consttransportnewStdioServerTransport();awaitserver.connect(transport);代码很简单核心就四件事创建 Server 实例——告诉客户端我是谁、我能做什么。注册tools/list——客户端问我你有什么工具“我回答有一个 get_weather 工具”。注册tools/call——客户端说调用一下 get_weather参数是 city北京我执行查询并返回结果。连接传输层——这里用 stdio就是通过标准输入输出通信。在package.json里加一行type: module或者把文件后缀改成.mjs然后跑一下node./index.js# 启动 mcp service跑起来之后没有任何输出正常。因为它现在通过 stdio 通信不是输出到终端。2.3 在终端手敲 JSON-RPC 调试Server 跑起来没输出怎么知道它工作正常直接打开终端跟它对话。MCP 底层用的是JSON-RPC协议所有消息都是 JSON 格式。我们可以手动输入消息来走一遍完整流程。先启动 Server它会在后台等待输入node./index.js看不到输出对因为它正在 stdin 上等着你发消息过去。现在你在终端里输入逐行复制每行是一条消息第一步初始化握手{jsonrpc:2.0,id:1,method:initialize,params:{protocolVersion:2024-11-05,capabilities:{},clientInfo:{name:LJ-agent,version:0.5.0}}}Server 会返回{result:{protocolVersion:2024-11-05,capabilities:{tools:{}},serverInfo:{name:weather-server,version:1.0.0}},jsonrpc:2.0,id:1}{jsonrpc:2.0,method:notifications/initialized}第二步通知初始化完成{jsonrpc:2.0,method:notifications/initialized}通知不需要返回。第三步问 Server 有什么工具{jsonrpc:2.0,id:2,method:tools/list,params:{}}返回{result:{tools:[{name:get_weather,description:查询指定城市的实时天气,inputSchema:{type:object,properties:{city:{type:string,description:城市名称如 北京}},required:[city]}}]},jsonrpc:2.0,id:2}第四步调用工具查天气{jsonrpc:2.0,id:3,method:tools/call,params:{name:get_weather,arguments:{city:北京}}}返回{result:{content:[{type:text,text:城市: 北京\n温度: 22°C\n体感温度: 25°C\n天气: Sunny\n湿度: 69%\n风速: 4 km/h}]},jsonrpc:2.0,id:3}大功告成。整个过程你看到的就是 MCP 协议的真面目——全是简单的 JSON 消息往来。注意每次粘贴后按回车消息才会发送给 Server。id 字段是请求的唯一标识响应里会带回同样的 id方便你匹配谁是谁。以后你有新工具了用这种方式就能快速验证——tools/list看工具列表tools/call调工具。比开浏览器快得多。2.4 接入真实天气 API刚才的代码返回的是随机数太假了。我们接入一个真实的天气 API 看看。这里以 wttr.in 为例——它是一个免费的命令行天气查询服务不需要 API Key。import{Server}frommodelcontextprotocol/sdk/server/index.js;import{StdioServerTransport}frommodelcontextprotocol/sdk/server/stdio.js;import{CallToolRequestSchema,ListToolsRequestSchema,}frommodelcontextprotocol/sdk/types.js;constservernewServer({name:weather-server,version:1.0.0,},{capabilities:{tools:{}}},);server.setRequestHandler(ListToolsRequestSchema,async()({tools:[{name:get_weather,description:查询指定城市的实时天气,inputSchema:{type:object,properties:{city:{type:string,description:城市名称如 北京},},required:[city],},},],}));server.setRequestHandler(CallToolRequestSchema,async(request){const{name,arguments:args}request.params;if(nameget_weather){constcityargs?.city||深圳;// 调用 wttr.in APIconstresawaitfetch(https://wttr.in/${city}?formatj1);constdataawaitres.json();constcurrentdata.current_condition[0];return{content:[{type:text,text:[城市:${city},温度:${current.temp_C}°C,体感温度:${current.FeelsLikeC}°C,天气:${current.weatherDesc[0].value},湿度:${current.humidity}%,风速:${current.windspeedKmph}km/h,].join(\n),},],};}thrownewError(未知工具:${name});});consttransportnewStdioServerTransport();awaitserver.connect(transport);注意这段需要 Node.js v18 才支持fetch。如果版本低可以用node-fetch包。现在你调用get_weather就能拿到真实的天气数据了。2.5 简化写法McpServer Zod2.2 的代码还有一个更清爽的写法——MCP SDK 提供了更高层的McpServer类配合 Zod 做参数校验代码可以写得更简洁。先安装 Zodnpminstallzod然后用McpServer重写index.jsimport{McpServer,StdioServerTransport,}frommodelcontextprotocol/sdk/server/mcp.js;import{z}fromzod;constservernewMcpServer({name:weather-server,version:1.0.0,});server.registerTool(get_weather,{description:查询指定城市的实时天气,inputSchema:z.object({city:z.string().describe(城市名称如 北京),}),},async({city}){constresawaitfetch(https://wttr.in/${city}?formatj1);constdataawaitres.json();constcurrentdata.current_condition[0];return{content:[{type:text,text:[城市:${city},温度:${current.temp_C}°C,体感温度:${current.FeelsLikeC}°C,天气:${current.weatherDesc[0].value},湿度:${current.humidity}%,风速:${current.windspeedKmph}km/h,].join(\n),},],};},);asyncfunctionmain(){consttransportnewStdioServerTransport();awaitserver.connect(transport);}main();对比 2.2 节的代码区别很明显不需要手动 importCallToolRequestSchema、ListToolsRequestSchema不需要自己写两个setRequestHandler注册Zod 自动帮你做参数校验和类型推导city参数直接有类型提示加新工具就是再加一行server.registerTool(...)想加第二个工具再加一段registerTool就行server.registerTool(get_air_quality,{description:查询空气质量,inputSchema:z.object({city:z.string().describe(城市名称),}),},async({city}){// TODO ...return{content:[{type:text,text:空气质量指数: 42 (优)}],};},);如果你觉得每次写{ content: [{ type: text, text: ... }] }有点啰嗦McpServer也提供了TextContent辅助函数可以直接返回字符串它会帮你包装成标准格式。总之McpServer是 SDK 提供的高层封装日常写 MCP Server 用它就够了。底层的Server Schema 写法适合你需要在更细粒度上控制行为的时候。相比手动注册每个 Handler数据驱动的方式更直观、更不容易出错。2.6 用代码测试 MCP Service前面 2.3 节在 cmd 终端用手敲 JSON-RPC 能验证 Server 是否工作但每次都要复制粘贴也挺累的。更实用的做法是写一个测试脚本用 MCP SDK 的Client来连接和调用。在同目录下新建test.mjsimport{Client}frommodelcontextprotocol/sdk/client/index.js;import{StdioClientTransport}frommodelcontextprotocol/sdk/client/stdio.js;asyncfunctionmain(){// 1. 启动 Server 进程并连接consttransportnewStdioClientTransport({command:node,args:[./index.js],// 类似在终端执行 node ./index.js 启动 mcp service});constclientnewClient({name:test-client,version:1.0.0},{capabilities:{}},);awaitclient.connect(transport);console.log(✅ 连接成功);// 2. 列出所有工具const{tools}awaitclient.listTools();console.log( 可用工具:,tools.map((t)t.name),);// 3. 调用工具constresultawaitclient.callTool({name:get_weather,arguments:{city:北京},});console.log( 查询结果:,result.content[0].text);// 4. 关闭连接awaitclient.close();}main().catch(console.error);然后运行nodetest.mjs输出类似✅ 连接成功 可用工具: [ get_weather ] 查询结果: 城市: 北京 温度: 28°C ...这个脚本覆盖了 MCP 客户端的完整流程连接 → 发现能力 → 调用工具 → 关闭。以后你改 Server 代码跑一遍test.mjs就能确认没坏。如果想测试异常情况比如传一个不存在的城市可以加一段try{awaitclient.callTool({name:get_weather,arguments:{city:}});}catch(err){console.log(❌ 预期内的错误:,err.message);}把test.mjs当作你的调试入口比手敲 JSON 省事多了。提示test.mjs 就是 Agent 连接 MCP Service 的核心代码。2.7 完整代码结构两种写法对应的文件结构mcp-weather-server/ ├── package.json ├── test.mjs └── index.js不管哪种写法核心逻辑都是一样的声明工具——告诉 AI “我能做什么”执行工具——收到调用请求干活返回结果你完全可以把 get_weather 替换成任何你想暴露的能力查数据库、搜文档、调用内部 API……模板是一样的。2.8 小结这一篇我们干了这些事用 MCP SDK 创建了一个Server 实例注册了Tools查询天气通过stdio 传输层启动 Server用手动输入 JSON-RPC在终端调试接入了真实 API最后用数据驱动方式简化了多工具注册现在这个 Server 已经能跑了但只能在终端里测试。下一篇我们会把它接入真正的 AI 客户端让 Claude 能直接通过它查询天气——那才是真正好玩的时刻。上一篇第 1 篇MCP 是什么——AI 世界的万能插座下一篇第 3 篇把 MCP 接入 AI以及生态里有什么
http://www.gsyq.cn/news/1407609.html

相关文章:

  • (双85测试)温度85℃、相对湿度85% RH 环境可靠性模拟试验
  • 亲测丝滑,体验跃迁|AllData数据模型管理,解锁高效建模新姿势
  • Unity游戏视觉修复:6种智能去马赛克插件技术架构完全解析
  • 【ChatGPT技术文档写作权威认证路径】:从零构建ISO/IEC 26514兼容文档体系(含审计checklist)
  • 保姆级避坑指南:在AMD Ryzen电脑上用VMware 16.1.2装macOS BigSur(附unlocker工具和镜像)
  • SAP 物料主数据MRP2视图增强
  • 独立开发者如何借助Taotoken低成本接入多模型构建AI应用
  • 论文党救星!okbiye 毕业论文 AI 写作功能实测:从 0 到 1 搞定全流程
  • PhpStorm 2026年5月新版本 2026.1.1 更新内容,安装激活使用教程
  • 如何快速实现电话号码定位:一键查询地理位置的开源解决方案
  • 哪个降AI工具能去ai痕迹?2026年5月4款主流软件深度推荐 - 我要发一区
  • 把会议变成行动:会议纪要 Agent 如何自动派发任务
  • 保姆级教程:用QSWAT+3.10.6从DEM到出流量曲线,水文模拟避坑指南
  • 价值锚点错位,ROI归零!ChatGPT项目90%夭折的根源,及价值主张重构四象限诊断法
  • 为什么你的ChatGPT总“答非所问”?——基于1276份用户日志分析的8类语义断层陷阱及修复公式
  • 猫抓浏览器扩展:三步掌握网页资源嗅探与媒体下载核心技能
  • 2026财务分析师岗位必备能力及培养技巧
  • 深耕建筑施工质量管控,解读GB/T 50430行业核心规范
  • AI Agent Harness Engineering 的“寒武纪大爆发”即将到来?
  • P3877 [TJOI2010] 打扫房间 - Link
  • P1437 [HNOI2004] 敲砖块 题解
  • RL-ARM TCPNET PPP客户端IPCP协议支持解析与工程实践
  • 基于鸿蒙系统与Hi3861的WiFi小车:从零搭建跨平台遥控系统
  • 流量计生产商实战经验大公开:2026年排行预测及亲测案例分享
  • 3大核心功能解密:LizzieYzy如何成为围棋AI分析领域的瑞士军刀
  • 抖音内容批量下载工具:5分钟掌握高效数据采集技巧
  • SE-Net:从通道注意力到模型性能跃迁的深度解析
  • 哔哩下载姬DownKyi:如何轻松免费下载B站8K高清视频的完整指南
  • Visio导出矢量图总带白边?一个隐藏的‘打印属性’设置就能搞定(保姆级避坑教程)
  • ChatGPT vs Claude 4 vs Gemini 2.5 Pro vs Qwen3 vs DeepSeek-R1:谁在中文长文本理解、代码生成与合规性上真正胜出?