尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
MCP架构深度拆解:Host、Client、Server三层职责与JSON-RPC通信原理
1. 为什么值得把 MCP 架构拆到骨头里第一次接触 MCP 的人十有八九会被三个词绕晕Host、Client、Server。看官方图觉得挺清楚一动手就发现——Host 到底管什么Client 和 Server 之间那条 JSON-RPC 通道是谁在维护为什么我本地跑一个 Server换个 Host 就连不上我最初也是这么过来的。当时为了把一个内部工具接进编辑器前后折腾了三天最后发现问题根本不在代码而在我对这三层职责的理解是错的。所以这篇不打算复述官方文档而是按我自己的理解路径把 MCP 架构一层层剥开剥到你能拿着这套认知去排查任何连接问题为止。MCP全称 Model Context Protocol本质是一套让模型应用Host安全地调用外部能力Server的通信规范。它解决的核心问题是模型本身只会生成文本但真实任务需要读文件、查数据库、调接口、操作设计稿这些能力不可能全塞进模型里必须外挂。MCP 就是那个外挂标准接口。这篇文章适合三类人一是刚听说 MCP、想知道它到底怎么运转的开发者二是已经在用某个 MCP Server 但一遇到报错就懵的人三是准备自己写一个 MCP Server 接进现有工具链的工程师。我会从整体设计讲到 JSON-RPC 报文再讲到 Host、Client、Server 各自的边界最后给一份排查清单。全程按为什么这么设计来讲而不是文档这么写。先把一句话结论放这儿MCP 的分层不是为了好看是为了让能力提供方和能力使用方彻底解耦。理解了这句话后面所有细节都是它的推论。2. 整体设计三层结构到底在解耦什么2.1 Host、Client、Server 的职责边界很多人把 MCP 理解成客户端-服务端两段式这是第一个误区。MCP 实际是三段式中间那层 Client 经常被忽略但它恰恰是整套架构的关键。我用一个生活化的类比Host 是公司Client 是公司派出去对接供应商的采购员Server 是供应商。公司不会直接跟供应商打交道所有需求都通过采购员传达供应商也只认采购员不关心背后是哪家公司。这个中间人设计带来两个直接好处一是公司换供应商采购流程不变二是供应商换客户供货方式不变。具体到职责Host模型应用本体比如编辑器、IDE 插件、桌面助手。它负责管理会话、决定什么时候该调用哪个能力、把模型输出和工具结果拼起来。Host 是唯一有状态的角色它知道用户是谁、当前在做什么任务。ClientHost 内部为每个 Server 创建的一个连接器。一个 Host 可以同时挂多个 Client每个 Client 对应一个 Server 连接。Client 负责协议握手、消息收发、能力协商是纯粹的通信管道 协议翻译。Server能力提供方暴露具体的工具tools、资源resources、提示模板prompts。Server 不关心谁在调用只按协议响应请求。注意一个 Host 对多个 Server 时是一个 Server 一个 Client不是共用一个 Client。这点在排查为什么 A 工具能用 B 工具不能用时特别关键——问题往往出在某个 Client 的连接上而不是 Host 整体。2.2 为什么中间要插一层 Client有人会问Host 直接连 Server 不行吗非要加个 Client 增加复杂度答案在于连接生命周期和能力协商的隔离。Host 是长期运行的应用Server 可能随时上下线、可能崩溃、可能响应超时。如果 Host 直接管理这些连接一旦某个 Server 卡住整个 Host 都可能被拖死。把连接管理下沉到 Client 层Host 只需要面对Client 可用/不可用这个抽象不用关心底层是 stdio 还是 HTTP、是本地进程还是远程服务。另一个原因是多 Server 并行。现代 Host 往往同时挂好几个 Server一个管文件系统一个管数据库一个管设计工具。每个 Server 的启动方式、认证方式、协议版本可能都不同。Client 层把这些差异吸收掉Host 拿到的就是统一的调用接口。我实测下来这个设计在稳定性上的收益非常明显。之前有个 Server 因为网络问题频繁超时如果 Host 直接管连接整个应用会周期性卡顿有了 Client 隔离后只有那个 Client 报错其他工具照常工作。2.3 传输层stdio 与 HTTP 的取舍MCP 目前主流两种传输方式stdio标准输入输出和HTTP含 SSE 流式。stdio 适合本地 ServerHost 直接以子进程方式启动 Server通过 stdin/stdout 交换 JSON-RPC 消息。优点是零网络配置、启动快、天然隔离缺点是只能本地、进程崩溃即断连、不适合多 Host 共享。HTTP 适合远程 ServerServer 独立部署多个 Host 通过网络访问。优点是解耦彻底、可水平扩展缺点是要处理认证、超时、重连、CORS 等一堆网络问题。选型逻辑很简单能力在本机、只给自己用选 stdio能力要共享、要给团队用选 HTTP。我见过有人为了看起来专业把本地文件工具做成 HTTP Server结果每次启动都要配端口和证书纯属自找麻烦。维度stdioHTTP部署位置本地子进程远程独立服务启动方式Host 拉起进程预先部署多 Host 共享不支持支持认证复杂度无需要崩溃影响单连接断开需重连机制适用场景本地工具、个人使用团队共享、生产环境3. 核心细节JSON-RPC 报文里藏着什么3.1 JSON-RPC 2.0 是 MCP 的骨架MCP 所有通信都基于JSON-RPC 2.0。这个选择不是随便定的——JSON-RPC 足够简单就四种消息类型、足够通用任何语言都能实现、足够无状态每条消息自包含。对协议设计来说简单意味着实现成本低、调试容易。JSON-RPC 2.0 只有四种消息Request带id期待响应。比如tools/call。Response带相同id返回结果或错误。Notification不带id不期待响应。比如日志、进度通知。Batch消息数组批量发送。一条典型的工具调用请求长这样{ jsonrpc: 2.0, id: 42, method: tools/call, params: { name: read_file, arguments: { path: /tmp/demo.txt } } }对应的响应{ jsonrpc: 2.0, id: 42, result: { content: [ { type: text, text: 文件内容... } ] } }注意id的作用它是请求和响应的配对标识。因为 MCP 允许并发请求响应可能乱序返回靠id才能对上号。我踩过的坑是自己写 Server 时忘了回填idHost 收到响应后无法匹配表现为调用一直挂起直到超时。3.2 初始化握手能力协商发生在什么时候MCP 连接建立后第一件事不是直接调工具而是initialize 握手。这一步决定了双方能用哪些能力。流程是Client 发送initialize请求带上自己的协议版本和 capabilities。Server 返回自己的协议版本、capabilities 和 serverInfo。Client 发送notifications/initialized通知握手完成。Server 的 capabilities 里会声明它支持哪些能力比如{ capabilities: { tools: { listChanged: true }, resources: { subscribe: true }, prompts: {} } }tools.listChanged: true意味着 Server 支持在运行中动态增删工具并通过notifications/tools/list_changed通知 Client 刷新。这个细节很多人不知道导致工具更新后 Host 里还是旧的。提示如果握手阶段协议版本不匹配不同实现的处理方式不一样。有的直接断开有的降级兼容。排查连上了但调不了工具时先看握手日志里的版本号。3.3 三大能力原语tools、resources、promptsMCP Server 暴露的能力分三类理解它们的区别是用好 MCP 的前提。Tools工具模型可以主动调用的函数。有输入参数、有返回值、可能产生副作用写文件、发请求。这是最常用的一类也是模型能干活的核心。工具调用是模型决定调什么、传什么参数Host 负责执行并把结果回给模型。Resources资源可以被读取的数据。类似文件或数据库记录是只读的。资源用 URI 标识比如file:///path/to/file或db://users/123。模型不能直接调用资源而是通过 Host 把资源内容注入上下文。Prompts提示模板预定义的提示词模板可以带参数。用户或 Host 主动选择使用不是模型自动触发。适合把常用任务固化成模板比如代码审查生成周报。三者的关键区别在于触发方tools 由模型触发resources 由 Host/用户选择注入prompts 由用户主动选用。搞混这三者设计出来的 Server 就会很别扭——比如把只读数据做成 tool每次都要模型调用一下才能读纯属浪费。原语触发方是否只读典型用途Tools模型否执行操作、查询ResourcesHost/用户是注入上下文数据Prompts用户是固化任务模板3.4 消息流一次工具调用的完整旅程把上面这些串起来一次完整的工具调用是这样的用户在 Host 里提问模型判断需要调用某个工具。Host 找到对应 Server 的 ClientClient 发送tools/call请求。Server 执行工具逻辑返回结果。Client 收到响应交给 Host。Host 把结果拼进模型上下文模型继续生成。如果工具执行时间长Server 可以发notifications/progress通知进度Host 可以据此更新 UI。如果 Server 想主动告诉 Client我的工具列表变了发notifications/tools/list_changed。这条链路里任何一环出问题表现都是工具调用失败。所以排查时要有意识地从 Host 日志、Client 日志、Server 日志三个层面分别看而不是只盯着报错信息。4. 实操过程从零搭一个最小 MCP Server4.1 环境准备与依赖选择理论讲完动手才有感觉。我用 Python 搭一个最小 Server暴露一个echo工具走 stdio 传输。选 Python 是因为官方 SDK 成熟选 stdio 是因为本地调试最省事。依赖很简单pip install mcp官方 SDK 封装了 JSON-RPC 的收发、握手、能力协商你只需要关注工具逻辑本身。这也是 MCP 设计的一个好处——协议细节被 SDK 吸收业务代码保持干净。注意SDK 版本要和 Host 支持的协议版本对齐。我遇到过 SDK 太新、Host 太旧导致握手失败的情况降一个版本就好了。生产环境建议锁定版本。4.2 定义工具与注册最小 Server 的核心就是定义工具 注册 启动。工具定义包括名称、描述、参数 schema。描述很重要——模型是靠描述来判断该不该调这个工具的写得含糊模型就不会用。from mcp.server import Server from mcp.server.stdio import stdio_server from mcp.types import Tool, TextContent app Server(echo-server) app.list_tools() async def list_tools(): return [ Tool( nameecho, description回显输入的文本用于测试连接是否正常, inputSchema{ type: object, properties: { text: {type: string, description: 要回显的文本} }, required: [text] } ) ] app.call_tool() async def call_tool(name: str, arguments: dict): if name echo: return [TextContent(typetext, textarguments[text])] raise ValueError(f未知工具: {name}) async def main(): async with stdio_server() as (read, write): await app.run(read, write, app.create_initialization_options()) if __name__ __main__: import asyncio asyncio.run(main())这段代码里list_tools告诉 Client 有哪些工具call_tool处理实际调用。inputSchema用 JSON Schema 描述参数Host 会据此校验模型生成的参数。4.3 接入 Host 与验证把 Server 接进 Host通常是在 Host 的配置文件里加一段。以常见编辑器类 Host 为例配置大致是{ mcpServers: { echo: { command: python, args: [/path/to/echo_server.py] } } }保存后重启 Host它就会以子进程方式拉起这个 Server完成握手。验证方法是让模型调用 echo 工具看是否返回你输入的文本。如果没反应按这个顺序查Server 进程有没有起来、握手日志有没有报错、工具列表有没有加载出来、调用时参数对不对。我实测下来八成问题出在路径写错或 Python 环境不对——Host 用的是系统 Python而你装依赖用的是虚拟环境。4.4 参数校验与错误处理工具的参数校验不能只靠 schema。schema 是给模型看的说明书但模型可能生成不符合 schema 的参数Host 的校验也可能不严格。所以 Server 内部必须再校验一遍。错误处理要遵循 JSON-RPC 规范业务错误用isError: true标记在结果里协议错误才用 error 对象。区别在于——业务错误比如文件不存在是模型可以理解和处理的应该作为正常结果返回协议错误比如方法不存在是通信层面的问题应该走 error 通道。app.call_tool() async def call_tool(name: str, arguments: dict): if name echo: text arguments.get(text) if not isinstance(text, str): return [TextContent(typetext, text参数 text 必须是字符串)] return [TextContent(typetext, texttext)] raise ValueError(f未知工具: {name})这个区分很关键。如果把业务错误当成协议错误抛出去模型看不到具体原因就没法自我纠正。5. 常见问题与排查技巧实录5.1 连接类问题速查连接问题是最常见的表现五花八门。我整理了一份速查表现象可能原因排查方向Host 里看不到工具Server 未启动或握手失败看 Host 日志、手动跑 Server调用一直挂起响应未回填 id 或未返回检查 Server 响应逻辑连上后立刻断开协议版本不匹配对齐 SDK 与 Host 版本部分工具可用部分不可用某个 Client 连接异常逐个 Server 排查工具列表不更新未发 list_changed 通知检查通知逻辑5.2 那些文档不会写的坑坑一stdio 模式下不要往 stdout 打日志。stdio 传输用 stdout 传 JSON-RPC 消息你如果print调试信息会污染协议流导致 Host 解析失败。日志一律走 stderr。坑二Server 启动慢会被误判为失败。有些 Host 有启动超时Server 初始化太慢比如要连数据库会被杀掉。解决办法是把耗时初始化做成懒加载握手时快速返回。坑三路径要用绝对路径。Host 拉起 Server 时的工作目录不一定是你的项目目录相对路径会找不到文件。这个坑我踩过不止一次。坑四并发调用要注意状态。如果 Server 内部有共享状态多个工具并发调用可能出问题。要么加锁要么设计成无状态。坑五错误信息要写给模型看。错误信息不只是给开发者看的模型也会读。写操作失败模型没法处理写文件 /tmp/a.txt 不存在请检查路径模型就能自我纠正。5.3 调试工具与方法调试 MCP 最有效的方法是分层看日志。Host 日志看连接和调度Client 日志看协议收发Server 日志看业务逻辑。三层对照问题定位很快。另一个技巧是用最小 Server 验证 Host。当你怀疑是 Host 配置问题时接一个最简单的 echo Server 上去。如果 echo 能用说明 Host 没问题问题在你的 Server如果 echo 也不能用问题在 Host 配置或环境。还有个土办法但很管用手动构造 JSON-RPC 消息。用管道把 initialize 请求喂给 Server看它返回什么。这能绕过 Host直接验证 Server 的协议实现是否正确。echo {jsonrpc:2.0,id:1,method:initialize,params:{protocolVersion:2024-11-05,capabilities:{},clientInfo:{name:test,version:1.0}}} | python echo_server.py看输出里有没有正确的 initialize 响应就能判断 Server 的握手实现对不对。5.4 性能与稳定性建议工具调用是同步等待的长耗时工具会阻塞。如果工具有耗时操作建议拆成提交任务 查询结果两步或者用进度通知让 Host 知道还在跑。Server 要能扛住异常。工具内部抛异常要捕获并转成业务错误返回不能让异常穿透到协议层导致连接断开。我见过一个 Server 因为没捕获异常一次调用失败后整个连接就废了所有工具都用不了。资源占用也要注意。stdio 模式下每个 Host 会拉起一个 Server 进程如果 Server 内存占用大多开几个 Host 就吃不消。这种情况考虑改成 HTTP 共享部署。6. 我对 MCP 架构的一点个人体会把 MCP 拆到这一层你会发现它的设计其实很克制三层职责清晰、协议简单、能力原语只有三个。这种克制带来的好处是你不需要记住一堆复杂规则只要抓住Host 管状态、Client 管连接、Server 管能力这条主线大部分问题都能自己推出来。我自己最大的收获是理解了解耦的价值。以前写工具集成总是把业务逻辑和通信逻辑搅在一起改一处动全身。MCP 逼着你把能力提供和能力使用分开短期看是多写了一层长期看维护成本低得多。如果你正准备写自己的 MCP Server我的建议是先跑通最小 echo 版本确认握手和调用链路没问题再往里加业务逻辑。别一上来就写复杂工具那样出了问题你分不清是协议层还是业务层。另外日志一定走 stderr路径一定用绝对路径错误信息一定写给模型看——这三条能帮你省下大量调试时间。
RELATED

相关推荐

表达退化:从原理诊断到白化与对比学习解决方案

表达退化:从原理诊断到白化与对比学习解决方案

1. 表达退化到底是什么:从一次模型翻车说起如果你做过一段时间的自然语言处理,大概率遇到过这种诡异现象:模型在训练集上表现得好好的,loss 曲线也漂亮,但一放到真实场景里,生成的句子就开始“车轱辘话来回…

📅 2026/10/16 20:47:18
AI Agent产品推荐系统开发实战:从需求拆解到上线踩坑

AI Agent产品推荐系统开发实战:从需求拆解到上线踩坑

1. 为什么“AI Agent 产品推荐”值得做:从用户痛点说起先讲一个我真实遇到过的场景。某次给一个做内容社区的产品做咨询,对方说推荐位点击率连续三个月下滑,算法团队调参调到头秃也没救回来。我看了一眼他们的推荐系统,典型的“老…

📅 2026/10/16 20:47:18
SuperMap-Cesium-3D加载倾斜摄影模型:OSGB转3D Tiles与性能调优实战

SuperMap-Cesium-3D加载倾斜摄影模型:OSGB转3D Tiles与性能调优实战

简介:本资源面向GIS开发与三维可视化方向的开发者,提供一套将SuperMap与Cesium结合的3D地图展示方案,重点解决倾斜摄影测量数据在Web端的三维加载与交互问题。项目以Vue.js为前端框架,借助Babel完成语法转换,并通过Sup…

📅 2026/10/16 20:47:18
MORE NEWS

更多资讯

📰

龙岩请月嫂还是请育婴师,两者到底差在哪,什么时候该请哪一个

家里要添人了,或者宝宝已经满月了,很多人会卡在同一个问题上。到底是请月嫂,还是请育婴师。 这两个词平时经常混着用,甚至有人以为是同一件事换个说法。其实差别不小,请错了要么白花钱,要么该有的照顾没到位…

📰

降本增效新引擎:制造企业如何应用数据中台优化供应链

引言:供应链不缺系统,缺的是“一条链路”制造企业在供应链数字化上的投入并不少。ERP管订单、SRM管供应商、MES管生产、WMS管仓储、TMS管运输——每一环都有自己的系统。但一个普遍困境随之浮现:系统建了不少,数据接进来了&#x…

📰

Python PIL实现图像素描转换:从梯度计算到边缘增强

1. 项目概述:为什么一张普通照片能变成素描?这背后不是滤镜,而是图像梯度的数学表达你有没有试过把手机里刚拍的风景照,几秒钟内变成铅笔手绘风格的素描?不是靠美图秀秀一键套用,也不是打开Photoshop调十几…

📰

R语言爬虫实战:从数据抓取到清洗的完整指南

1. 为什么我至今还在用 R 写爬虫很多人一听到“爬虫”,第一反应就是 Python,觉得 R 语言只能做统计分析、画图,干不了抓数据这种“脏活累活”。我一开始也是这么想的,直到有一次接了个活儿,需要从几十个结构类似的网页…

📰

停车场信息管理系统实战:Spring Boot+Vue3设计与实现

做了三四个停车场管理系统项目、也帮人改过不少课程设计代码之后,我越来越确定一件事:这类“管理系统”项目,真正难的从来不是写代码,而是把业务规则理清楚、再把技术栈选明白。你看这个标题——基于PHP、asp.net、java、Springbo…

📰

AlphaZero五子棋实战:从零实现自我对弈与MCTS决策系统

简介:本资源是基于AlphaZero算法原理实现的五子棋AI教学代码包,面向深度学习初学者与人工智能实践者,聚焦强化学习中自对弈训练、蒙特卡洛树搜索(MCTS)与策略价值网络联合建模等核心环节。压缩包共10个文件&#xff0c…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬