MCP端到端流程

发布时间:2026/8/7 19:27:27
MCP端到端流程 本文是 MCP 的流程篇讲怎么发生按时间顺序把 MCP 从打开 CodeBuddy 到关闭完整走一遍。以本仓库真实案例为主线CodeBuddy local-time MCP Serverserver.jsNode.js。想看是什么角色、原语、真实报文见 mcp核心概念.md。0. 一分钟看懂全程MCP的整个生命周期其实只干一件事让你的大模型LLM能调用你写的 server.js。把整个流程压成一句话你配置好 server.js → 打开 CodeBuddy 时Host 自动把 server.js 启动起来、跟它打招呼握手、问它有哪些工具tools/list→ 之后每次你提问Host 就替大模型去调对应工具 → 拿到结果再交给大模型组织回答 → 你关闭 CodeBuddyserver.js 随之结束。下图把整个过程画成一整条时间线先看这张图再往下读细节[你写好 server.js 并配置好] ← 准备工作还没人运行它 │ [打开 CodeBuddy] ▼ ╔══════════════ 启动阶段Host 自动做与你是否提问无关 ══════════════╗ ║ ① 启动进程node 运行 server.js它开始监听 stdin进入待命 ║ ║ ② 握手Client ↔ server 约定协议版本、互相报能力做一次 ║ ║ ③ 发现工具Client 问 tools/list把清单注入大模型上下文做一次 ║ ╚═══════════════════════════════════════════════════════════════════╝ │ 此刻一切就绪只等你提问 ▼ [你输入现在几点了] ← 从这里起才由提问触发 ▼ ╔══════════════ 调用阶段每次提问触发一次 ═══════════════════════════╗ ║ ① 大模型 推理#1判断该用 get_current_time只是思考不执行 ║ ║ ② Client 把意图翻译成 tools/call经 stdin 发给 server.js ║ ║ ③ server.js 真正执行工具函数经 stdout 返回结果 ← 等待发生在这 ║ ║ ④ Client 剥壳把结果交给大模型 ║ ║ ⑤ 大模型 推理#2组织成自然语言回答 → 显示给你 ║ ╚═══════════════════════════════════════════════════════════════════╝ │ server.js 一直活着等下一次提问 ▼ [你再提问] → 重复调用阶段 ①~⑤不用重新握手/发现 │ [关闭 CodeBuddy] ▼ server.js 子进程被终止启动阶段Host 一打开就自动完成跟你提不提问没关系。调用阶段每次提问触发一次。两个阶段之间握手和发现只做一次之后的每次提问都复用所以很快。如果你刚接触 MCP建议先读 mcp核心概念.md 里的角色Host/Client/Server和一张图看懂再回来会更容易跟上。1. 案例背景与配置1.1 配置在 CodeBuddy 里启用这个 server靠的是配置文件~/.codebuddy/mcp.json{mcpServers:{local-time:{type:stdio,command:node,args:[D:/MyProjects/local-time-mcp/server.js]}}}简单说这段配置告诉 CodeBuddy“local-time 这个连接器就是用node命令去跑server.js走 stdio 通信。”1.2 配置字段详解配置文件告诉 Host怎么启动server 子进程。字段分两类① MCP 官方标准字段所有 Host 通用字段含义本例值type传输方式。stdio 通过标准输入输出通信Server 作为子进程启动stdiocommand要执行的程序名Host 用它启动子进程nodeargs传给command的参数。[server.js]即让 node 运行 server.js[D:/.../server.js]env可选给子进程设置的环境变量如 API Key本例没用—cwd可选子进程的工作目录本例没用—合起来启动命令就是node D:/MyProjects/local-time-mcp/server.js。② Host 扩展字段不属于 MCP 官方规范是 CodeBuddy 自身的字段含义示例值disabled该连接器是否禁用。false 启用true 禁用Host 不会启动它falseautoApprove自动批准的工具列表。某些工具调用需要用户确认放进此数组的免确认自动执行。空数组 都不自动批准[]timeout调用该 server 的超时时间毫秒。超过未响应则判定调用失败6000060 秒⚠️disabled、autoApprove、timeout不是 MCP 协议的一部分是 CodeBuddy 的宿主级扩展不同 Host 字段可能不同。MCP 官方只规定type/command/args/env等启动与通信字段。 官方还允许env注入环境变量和cwd工作目录以及用${VAR}语法引用环境变量。本案例未用到。2. 启动阶段打开 CodeBuddy 时自动发生启动阶段干三件事启动进程 → 握手 → 发现工具。全部是 Host 自动完成的与你是否提问无关。2.1 准备写好 server.js还没人运行它你定义了 4 个工具get_current_time/format_time/time_diff/list_timezones每个工具都包含名字、描述、参数 schemaZod、执行回调。此刻 server.js 只是磁盘上的一个文件node_modules里躺着它的静态依赖。还没人执行它。2.2 启动进程node 把 server.js 拉起来双击打开 CodeBuddy在你问任何问题之前Host 就自动启动所有配置好的连接器CodeBuddy (Host) 启动 │ 1. 读取 ~/.codebuddy/mcp.json │ 2. 为每个连接器准备一个 Client │ 3. 用配置启动 server 子进程 ▼ 执行node D:/MyProjects/local-time-mcp/server.js ▼ server.js 子进程诞生开始执行server.js 一启动就执行server.connect(transport)监听 stdinNode 事件循环阻塞在读取 stdin成为常驻待命的服务等着收消息白话理解现在 HostCodeBuddy和 server.js 之间的电话线stdin/stdout已经接好了两边都在等着通话。2.3 握手双方先对暗号通信前Client 和 Server 必须先握手initialize确认咱俩说的是同一个版本、你能提供什么能力。这一步官方叫能力协商。握手是三步只有都完成才允许后续请求① Client ──stdin──▶ {jsonrpc:2.0,id:1,method:initialize, params:{protocolVersion:2024-11-05,capabilities:{},clientInfo:{...}}} ② Server ──stdout──▶ {result:{protocolVersion:2024-11-05, capabilities:{tools:{listChanged:true}}, serverInfo:{name:local-time-server,version:1.0.4}},jsonrpc:2.0,id:1} ③ Client ──stdin──▶ {jsonrpc:2.0,method:notifications/initialized} ← 通知初始化完成① Client 报上自己想用的协议版本和能力。② Server 回报它支持的版本并声明自己支持 tools。③ Client 发一个通知确认初始化完成。这个通知没有响应。握手只在连接时做一次不会每次提问都重来一遍。2.4 发现工具问 server 有哪些货Client 想知道 server 有哪些工具可用于是请求工具列表tools/listClient ──stdin──▶ {method:tools/list,params:{}} Server ──stdout──▶ {result:{tools:[ {name:get_current_time,description:...,inputSchema:{properties:{tz:{...}}}}, {name:format_time,description:...,inputSchema:{...}}, {name:time_diff,description:...,inputSchema:{...}}, {name:list_timezones,description:...,inputSchema:{...}} ]},jsonrpc:2.0,id:2}关键点这里的inputSchema就是你在server.tool()里写的 Zod schemaSDK 自动转成了标准 JSON Schema。Client 拿到清单后会把它注入大模型上下文让大模型睁眼就知道有这 4 个工具可用、每个怎么填参数。这也是 tools/list只做一次的原因——之后每次提问都能复用这份清单。补充如果 server 后续动态增删了工具会通过notifications/tools/list_changed通知 Client 刷新——但本项目工具启动时就固定了不会用到。小结启动阶段做完一切就绪进入等待用户状态。你还没开口但背后已经全准备好了。3. 调用阶段每次提问触发一次启动阶段是接线调用阶段才是干活。你每次提问都会走一遍 ①→⑤。3.1 提问大模型想一下用什么工具你输入“现在几点了”——前面的握手和 tools/list 在启动阶段早做好了这里只做调用这一件事。大模型先做第一次推理纯文本思考不执行任何东西你的提问 ──▶ 大模型上下文里已有启动阶段注入的工具清单 │ ▼ 大模型 推理 #1 这个问题需要查当前时间 → 用 get_current_time 参数用默认本地时区即可 ──▶ 输出一个信号调用 get_current_time注意大模型没有任何能力去运行 server.js。它只输出一个调用意图等着 Client 去执行。3.2 传话Client 把意图翻译成协议消息Host 里的 Client 拿到大模型的意图翻译成 JSON-RPC往 server.js 的 stdin 写入Client ──stdin──▶ {jsonrpc:2.0,id:3,method:tools/call, params:{name:get_current_time,arguments:{tz:Asia/Shanghai}}}Client 在这里只是传话不干活。3.3 干活server.js 真正执行工具server.js 一直监听 stdin收到tools/call后真正执行收到 {method:tools/call,name:get_current_time,arguments:{tz:Asia/Shanghai}} │ 1. 解析 JSON看到 methodtools/call │ 2. 从工具注册表server.tool() 登记的 Map按 name 取出回调 │ 3. 把 arguments 传进去真正执行currentTimeInTimezone(Asia/Shanghai) │ 4. Node 运行时算出当前上海时间 ▼ 包装成响应写往 stdout Server ──stdout──▶ {result:{content:[{type:text,text:{...}}]},jsonrpc:2.0,id:3}这里有个容易误会的地方关于谁在等待Client 在这里阻塞等待server.js 返回。大模型并不是在等待——它的第一次推理已经结束。是 Client 拿到结果后会发起第二次推理。3.4 回传Client 剥壳结果交给大模型Client 从 stdout 读到响应后Client 从 stdout 读到响应 │ 剥壳只提取 result.content 里的工具结果丢掉 jsonrpc/id/result 这些协议包装 ▼ 把原问题 工具结果一起再交给大模型 │ ▼ 大模型 推理 #2组织自然语言回答 现在上海时间是 2026年08月06日 10:21:49UTC8 │ ▼ 显示在 CodeBuddy 界面上关键点只有 stdout 里经 Client 剥壳的工具结果才进入大模型。stdin 是上行、stderr 是给开发者看日志的都不进大模型。3.5 连续对话反复走 3.1~3.4server.js 一直活着你继续问“那纽约现在几点”“距离 2026 年底还有多少天”每一次提问都会重复3.1 → 3.4你提问 ──▶ 大模型决策 ──▶ Client 发 tools/call ──▶ server.js 执行 ──▶ 结果回大模型 ──▶ 回答关键点server.js不会处理完一次就退出。它一直活着、持续监听 stdin随时接下一个请求——这就是常驻待命。3.6 关闭server 进程终止官方把 MCP 生命周期划为三段初始化 → 运行 → 关闭。前面 2.x 是初始化3.1~3.5 是运行这里就是关闭。你关闭 CodeBuddyHost有两种情况方式 AHost 正常关闭优雅 Client 取消未完成的任务notifications/cancelled→ 断开连接 → Host 关闭所有连接器对应的子进程 → server.js 子进程被终止 方式 BHost 被强制退出 / 崩溃 → server.js 子进程作为其子进程随之被终止无论哪种方式最后都是server.js 子进程被终止 │ ▼ 操作系统回收该进程的资源文件句柄、内存、派生的子进程关于依赖的归宿node_modules里的静态依赖还在磁盘上——下次启动直接用不重新下载它跟着项目走不跟进程走。如果 server 运行中动态创建了临时文件/连接进程终止不会清理磁盘文件要靠代码自己释放。4. 对本项目的启发 / 扩展方向结合 MCP 的三个原语本项目的扩展空间加 Resource暴露time://now之类的只读时间资源供 LLM 作为上下文读取。加 Prompt预设换算时区计算节假日倒计时等模板。切传输从 stdio 换成 Streamable HTTP支持远程调用。发布 npm 包npx local-time-mcp-serverlatest一键使用。5. 官方参考MCP 规范主页含最新版本https://modelcontextprotocol.io/specification/2024-11-05基础协议握手/生命周期/传输https://modelcontextprotocol.io/specification/2024-11-05/basic/lifecycle服务器原语Resources/Prompts/Toolshttps://modelcontextprotocol.io/specification/2024-11-05/server/工具规范tools/list、tools/callhttps://modelcontextprotocol.io/specification/2024-11-05/server/tools注MCP 仍在演进各版本规范有差异。本文锚定2024-11-05撰写链接也指向该版本本项目使用的 Node SDKmodelcontextprotocol/sdk1.30在握手时会协商并实际采用更新的协议版本2025-06-18。两者在本文涉及的握手流程、tools/list、tools/call上行为一致故不影响理解。若需对照最新规范可将上方链接中的2024-11-05替换为2025-06-18。 感谢阅读想了解更多 我的博客网站 | 记录思考分享干货 我的个人主页 | 关于我、开源项目