MCP 传输层:stdio 与 Streamable HTTP
搞清楚 MCP 的两种消息传输方式——本地用 stdio、远程用 Streamable HTTP,以及怎么选。
搞清楚 MCP 里 Host、Client、Server 各自干什么、怎么连接、消息怎么流转。
内容摘要
MCP 的整体设计是一个**客户端-服务端架构**,但和你平时理解的"浏览器-服务器"不太一样——它中间多了一个"Host"的角色。一共三个角色:**Host(宿主)、Client(客户端)、Server(服务端)**。
MCP 的整体设计是一个客户端-服务端架构,但和你平时理解的"浏览器-服务器"不太一样——它中间多了一个"Host"的角色。一共三个角色:Host(宿主)、Client(客户端)、Server(服务端)。
先用一个生活类比帮你建立直觉:
把 MCP 想象成一家餐厅。Host 是餐厅经理,负责协调一切;Client 是服务员,每个服务员对接一个厨房窗口;Server 是厨房,每个厨房专门做一类菜。当客人(用户)说"我想吃牛排",经理判断该去哪个厨房,派对应的服务员去下单,厨房做好菜之后通过同一个服务员端回来。
三者之间的核心关系:一个 Host 管多个 Client,一个 Client 对应一个 Server,Client 和 Server 是一对一的专线连接。
| 角色 | 是什么 | 职责 |
|---|---|---|
| Host | AI 应用本体(Claude Desktop、Cursor、VS Code 等) | 运行大模型、管理对话、创建和管理所有 Client 实例、决定何时调用工具 |
| Client | Host 内部的连接器组件 | 与一个特定的 Server 保持专线连接、转发 Host 的请求、接收 Server 的响应和通知 |
| Server | 提供能力的外部程序 | 暴露 Tools / Resources / Prompts 给 Client、处理调用请求、返回结果 |
关键理解:Client 不是一个独立的应用程序,它是 Host 内部的一个组件。当你在 Claude Desktop 配置里写了 3 个 Server,Host 启动时会在内部创建 3 个 Client 实例,分别和对应的 Server 建立连接。用户看到的只有 Host(Claude Desktop 窗口),Client 完全是幕后工作。
Host 是整个 MCP 交互的"总指挥",它的工作比你想象的要多:
MCP 里的"Server"不一定跑在远方的云服务器上。实际上大多数日常用的 Server 都是本地进程:
| 对比 | 本地 Server | 远程 Server |
|---|---|---|
| 运行位置 | 和 Host 在同一台电脑上 | 跑在云端或其他机器上 |
| 传输方式 | stdio(标准输入输出) | Streamable HTTP |
| 启动方式 | Host 自动 spawn 子进程 | Server 已在远端运行,Host 通过 URL 连接 |
| 网络需求 | 无 | 需要网络,可能需要鉴权(OAuth / API Key) |
| 典型例子 | Filesystem、Git、SQLite | Sentry、Cloudflare、Linear |
| 并发 | 通常一个 Client 独占一个 Server 进程 | 一个远程 Server 可以同时服务多个 Client |
每个 Client-Server 连接都有一个明确的生命周期:
初始化阶段有一个能力协商的步骤:Client 告诉 Server "我支持 Sampling 和 Elicitation",Server 告诉 Client "我提供 Tools 和 Resources"。协商结果决定了这个连接可以做哪些事。后续的调用不会超出协商范围。
理解了三角色之后,最直观的实践就是在 Claude Desktop 里同时配多个 Server,看它们如何协同工作。
下面是一个配置了 3 个本地 Server 的 claude_desktop_config.json:
{
"mcpServers": {
"filesystem": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-filesystem", "/Users/me/projects"]
},
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_xxxxxxxxxxxx"
}
},
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
"env": {
"DATABASE_URL": "postgresql://user:pass@localhost:5432/mydb"
}
}
}
}
启动 Claude Desktop 后,Host 做了这些事情:
npx @modelcontextprotocol/server-xxx)当你对 Claude 说"帮我把 GitHub 上 my-repo 的 README 下载到本地 projects 文件夹",Claude 可能会先调 GitHub Server 的 get_file_contents 读取内容,再调 Filesystem Server 的 write_file 写入本地,两个 Server 各司其职、互不干扰。
MCP 的 Host-Client-Server 架构和其他"让 AI 用工具"的方案相比,差异在哪?
| 维度 | MCP 三角色架构 | OpenAI Assistants API | LangChain Agent |
|---|---|---|---|
| 工具注册方式 | Server 通过 tools/list 动态暴露,Host 自动发现 | 创建 Assistant 时在 API 里写死 tools 列表 | 在 Python 代码里硬编码 Tool 对象 |
| 工具跨应用复用 | ✅ 一个 Server 可被任何 Host 使用 | ❌ 绑定 OpenAI 平台 | ❌ 绑定 LangChain |
| 进程隔离 | ✅ 每个 Server 是独立进程,崩溃不影响 Host | ❌ 工具在 API 服务端运行,用户无法控制 | ❌ 工具在同一 Python 进程里 |
| 动态更新工具 | ✅ Server 可发通知让 Client 重新拉取工具列表 | ❌ 需要重新创建 Assistant | ❌ 需要重启进程 |
| 多工具源组合 | ✅ 一个 Host 同时挂多个 Server | 需要自己在 tools 数组里合并 | 需要自己在代码里合并 |
核心区别:
| 误区 | 准确理解 |
|---|---|
| 以为 Client 是一个独立的应用程序 | Client 是 Host 内部的组件,用户看不到也操作不了。你打开 Claude Desktop 看到的整个窗口就是 Host,Client 藏在里面 |
| 以为一个 Client 可以连多个 Server | 不行,严格一对一。一个 Client 实例只维护和一个 Server 的连接。要连 3 个 Server,Host 就创建 3 个 Client |
| 以为 Server 崩溃会拖垮整个 AI 应用 | 因为进程隔离,一个 Server 进程崩了只影响它对应的那个 Client 连接,其他 Server 照常工作 |
| 以为远程 Server 和本地 Server 的协议不一样 | 数据层(JSON-RPC 消息格式)完全一样,只是传输方式不同(stdio vs HTTP)。Server 代码通常只需换一行启动配置就能从本地切到远程 |
| 以为 Host 只是个"透传器" | Host 的角色远比透传复杂:它负责安全管控(是否允许调用)、上下文管理(哪些信息送进模型)、工具路由(分发到正确的 Server) |
| 优势 | 劣势 |
|---|---|
| 进程隔离:Server 崩溃不影响 Host,安全边界清晰 | 配置门槛:新手需要手动编辑 JSON 配置文件,没有图形化的 Server 管理界面(部分客户端已在改善) |
| 一对一专线:每个 Client-Server 连接独立,互不干扰,方便调试 | 资源开销:每个本地 Server 都是一个独立进程,挂 10 个 Server 就有 10 个 Node.js 进程在跑 |
| 动态发现:Server 可以运行时改变工具列表并通知 Client 更新,不需要重启 | 没有内置的 Server 间通信:两个 Server 之间无法直接交换数据,必须通过 Host 中转 |
| 本地/远程统一抽象:同一套 JSON-RPC 消息格式,切换部署方式不需要改业务逻辑 | 能力协商是一次性的:初始化时协商好的能力在连接生命周期内不会重新协商(但工具列表可以动态更新) |
参考答案:
分开的核心原因是职责分离。Host 的职责是"管理和协调"——管理对话、运行模型、做安全管控;Client 的职责是"维护连接"——和一个特定的 Server 保持通信。
如果合在一起,Host 直接连 10 个 Server 的话,所有连接管理、消息路由、错误处理的逻辑会全部堆在一块,代码会变得又大又脆弱。拆出 Client 后,每个 Client 只关心自己那条连接,Host 只需要管理一组 Client 对象,职责清晰,出问题也好排查。
打个比方:公司 CEO(Host)不会自己跑去和每个供应商谈细节,而是派不同的采购员(Client)分别对接。CEO 只需要知道"采购员 A 负责原材料,B 负责物流",具体沟通细节交给采购员去做。
参考答案:
主要需要考虑三个方面:
状态隔离:每个 Client 连接应该有独立的会话状态。如果 Server 内部用了全局变量来存状态,5 个 Client 的请求会互相干扰。正确的做法是用 session ID 来隔离每个连接的状态。
并发与资源竞争:如果 Server 背后操作的是同一份数据(比如同一个数据库),多个 Client 同时调用写操作可能产生冲突。Server 需要有适当的并发控制机制(锁、事务、队列等)。
鉴权与权限:不同 Host 可能代表不同的用户或组织,远程 Server 需要对每个连接做独立的身份验证和权限检查。Streamable HTTP transport 支持 OAuth 和 Bearer Token 来实现这一点。
本地 Server(stdio)通常不会遇到这些问题,因为它们是 Host spawn 的子进程,天然一对一。多连接并发是远程 Server 的特有挑战。
优先展示同分类且标签更接近的内容,方便继续串联学习。
搞清楚 MCP 的两种消息传输方式——本地用 stdio、远程用 Streamable HTTP,以及怎么选。
搞清楚 MCP Server 能暴露的三种能力——工具、资源、提示词模板,以及什么时候该用哪个。
MCP 的关键不是“连上就能用”,而是先走严格生命周期:版本协商、能力交换、initialized 通知,然后才能进入正常通信。