ARCHIVE / INITIALIZING000%
正在载入档案界面SYS.07
Interview Prep

协议

从 Socket、TCP、HTTP、SSE 与 WebSocket,一路拆到 RPC、JSON-RPC 和 MCP;把分层、连接、流式传输、超时重试与 Agent 工程串成一条线。

目录 · 76 节

协议

Socket、HTTP、SSE、RPC和MCP经常出现在同一问题中,但它们位于不同抽象层级,不能作为五种平级方案直接比较。分析时应先区分传输接口、网络协议、交互模式、调用抽象和Agent能力协议。

先把主线立住:

Socket是操作系统提供的通信端点API;TCP或QUIC负责传输;HTTP定义请求响应语义;SSE和WebSocket定义更具体的通信方式;RPC把消息交换抽象成远程方法调用;MCP用JSON-RPC规定AI应用与能力提供方如何发现和调用工具、资源与提示模板。

从 Socket 到 MCP 的协议栈主线

1. 协议分层与职责边界

概念所在层次主要解决什么不负责什么
Socket API操作系统编程接口创建端点并读写网络数据不定义业务消息含义
TCP传输协议可靠、有序的字节流不保留应用消息边界
QUIC基于UDP构建的传输协议可靠流、多路复用、连接迁移不直接定义业务API
HTTP应用层协议Method、URI、Header、Status、Body及缓存语义不等于某一种底层连接
SSEHTTP上的事件流格式与浏览器APIServer → Client持续推送文本事件不提供原生双向消息通道
WebSocket双向消息协议长连接上的全双工文本或二进制消息不自带业务幂等和回放
RPC调用抽象把请求、响应、序列化和错误包装成远程方法不等于某个固定传输协议
JSON-RPCRPC消息协议用JSON定义Request、Response、Notification和Error不规定必须走HTTP
MCPAI能力交互协议能力发现、工具调用、资源、Prompt及扩展不是新的TCP,也不是模型推理协议

一层可以有多种实现,上层也不必与下层一一绑定。HTTP/1.1和HTTP/2通常运行在TCP上,HTTP/3运行在QUIC上;SSE运行在HTTP上;gRPC通常使用HTTP/2;JSON-RPC既能走HTTP,也能走stdio或别的双向通道;MCP目前把JSON-RPC消息承载在stdio或Streamable HTTP上。

1.1 远程MCP调用链路

一次 MCP Tool Call 的七道协议与失败边界

假设Agent需要调用远程MCP Server的search_logs工具,完整链路不能简化为一次HTTP请求,而应按以下层次分析:

  1. Host根据URL取得域名,先查浏览器、进程或系统DNS缓存;未命中再向Resolver查询A/AAAA记录;
  2. 得到IP后建立TCP连接,或者为HTTP/3建立QUIC连接;
  3. HTTPS继续执行TLS握手,校验证书链、有效期、主机名和信任根,并协商应用协议;
  4. HTTP层发送POST /mcp,代理或网关可能在此完成认证、限流、路由和Body限制;
  5. MCP Server解析JSON-RPC,用id关联请求,按method分发到tools/call;
  6. Executor校验Tool Schema、调用方身份、Scope、租户、资源范围和审批策略;
  7. Tool访问Loki、数据库或第三方API,真正的业务副作用在这一层发生;
  8. Result沿原路返回,Host写Trace,再决定把哪些Observation放回模型上下文。

每一层的“成功”都只覆盖自身边界:DNS成功仅表示获得地址,TCP成功仅表示连接到某个端点,HTTP 200仅表示HTTP交换成功,JSON-RPC Result也不保证工具返回的业务内容正确。因此,“调用成功返回200”不能作为完整的业务成功判定。

1.2 分层故障定位

现象优先检查层典型证据
域名偶发无法解析DNSResolver耗时、缓存TTL、NXDOMAIN、A/AAAA结果
connection refused传输与监听端口IP、Port、Listener、Security Group、RST
Connect Timeout路由或防火墙SYN重传、NAT、Egress规则、丢包
TLS握手失败TLSSNI、证书链、SAN、有效期、ALPN、系统时间
HTTP 401/403认证与授权Token Audience、Scope、Policy Decision
HTTP 502/504Gateway与上游Upstream Connect、Response Header Timeout、网关日志
JSON-RPC -32601协议Method协议版本、Capability、Method名称
Tool返回业务错误Tool Backend参数、依赖状态、业务错误码、Trace Span
客户端超时但服务端产生了副作用分布式结果未知Idempotency Key、业务状态查询、服务端审计记录

排错顺序通常从下往上,但业务证据从上往下关联:先确认域名、连接、TLS、HTTP,再看JSON-RPC与Tool;同时用同一个Trace ID把网关、MCP Server和Tool Backend串起来。

2. Socket:应用访问网络协议栈的接口

Linux中的socket()创建通信端点并返回文件描述符。对TCP服务端,典型调用链是:

socket → bind → listen → accept → read/write → close

客户端通常是:

socket → connect → read/write → close

2.1 Socket不是TCP

Socket是API和内核对象,TCP是协议。创建SOCK_STREAM通常使用TCP,创建SOCK_DGRAM通常使用UDP;Unix Domain Socket也使用Socket API,却根本不经过IP网络。因此“HTTP底层是Socket”只能表示HTTP实现最终借助Socket API收发数据,不能把Socket当成HTTP的直接下层协议。

2.2 TCP没有消息边界

TCP只提供有序字节流。发送方连续两次write(),接收方可能一次read()全部读到,也可能拆成多次读到。这不是粘包Bug,而是应用错误地假设TCP替自己保留了消息边界。

应用必须自行Framing,常见方法有:

  • 固定长度;
  • 分隔符,例如stdio版MCP中的换行分隔;
  • Length Prefix,例如前4字节表示消息长度;
  • 自描述格式,例如HTTP Header中的Content-Length或Chunked Encoding;
  • 协议帧,例如HTTP/2 Frame和WebSocket Frame。

2.3 连接失败与业务结果不确定性

TCP断开只能证明该通信路径已经失效,不能证明服务端未执行请求。客户端发出扣款请求后连接断开,可能是请求未到达,也可能是扣款成功但响应丢失。这种“结果未知”是重试与幂等机制存在的根本原因。

2.4 TCP三次握手的确认范围

TCP三次握手可以简化为:

Client                     Server
  | -------- SYN(x) --------> |
  | <---- SYN(y), ACK(x+1) --- |
  | -------- ACK(y+1) -------> |
  |         ESTABLISHED         |

它建立双方的初始序列号并确认双向收发能力。为什么不是两次?如果服务端只收到SYN就进入完整连接状态,网络中延迟到达的旧SYN可能制造无效连接;第三次ACK让服务端知道客户端确实收到了它的初始序列号。

三次握手成功仍不代表应用健康:端口可能由错误进程监听,TLS可能随后失败,应用线程池也可能已经耗尽。因此监控要拆开dns_ms、connect_ms、tls_ms、ttfb_ms和完整请求耗时。

2.5 TCP关闭、半关闭与连接状态

TCP两个方向独立关闭,所以正常关闭通常表现为双方分别发送FIN和ACK。主动关闭方进入TIME_WAIT,避免旧报文污染后续使用相同四元组的新连接,并保证最后ACK丢失时还能重发。大量短连接会产生大量TIME_WAIT,应优先使用连接复用和正确配置的连接池,而不是直接修改内核参数。

CLOSE_WAIT表示对端已经发送FIN,本端内核也已通知应用,但应用尚未调用close()。大量CLOSE_WAIT通常指向文件描述符泄漏、异常路径未释放Response Body,或协程长期阻塞;它与TIME_WAIT属于不同问题。

半关闭表示一个方向已经不能继续发送,另一个方向仍可传输。例如客户端使用shutdown(SHUT_WR)通知服务端请求Body已经发送完毕,同时仍可继续读取响应。应用协议通常会封装结束边界,但理解半关闭有助于解释代理和流式连接中的异常断联。

2.6 阻塞、非阻塞与I/O多路复用

阻塞Socket在数据未就绪时使调用线程等待;非阻塞Socket立即返回EAGAIN,由程序稍后重试。select、poll、epoll或语言Runtime统一管理大量文件描述符的就绪事件,因此SSE或WebSocket无需为每条连接分配一个操作系统线程。

但I/O非阻塞不等于业务处理不会阻塞。在事件循环中直接执行大型JSON解析、Embedding或慢数据库查询,仍会使其他连接无法及时获得执行机会。常见结构是:

Event Loop:收发与轻量协议解析
Bounded Worker Pool:CPU或阻塞任务
Bounded Queue:显式制造背压
Cancellation:连接关闭或Deadline到期后停止无用工作

2.7 TCP Keepalive与应用心跳

TCP Keepalive由内核探测空闲连接是否仍然存活,默认周期通常较长;应用心跳携带协议语义,可以更快发现代理Idle Timeout、推动SSE Flush,或确认对端Event Loop仍在正常调度。二者可以同时使用,但不能相互替代。

  • TCP Keepalive:连接级、内核实现、对业务不可见;
  • WebSocket Ping/Pong:协议控制帧;
  • SSE注释行: heartbeat\n\n:应用约定,用于保活和Flush;
  • 业务心跳:可能包含last_event_id、负载或租约信息。

3. HTTP:统一的资源与请求响应语义

HTTP是无状态的应用层协议。一次请求包含Method、Target、Header和可选Body,一次响应包含Status、Header和可选Body:

POST /incidents/inc-7/cancel HTTP/1.1
Host: agent.example.com
Content-Type: application/json
Idempotency-Key: cancel-inc-7-v1

{"reason":"user_requested"}
HTTP/1.1 202 Accepted
Content-Type: application/json

{"incident_id":"inc-7","status":"cancelling"}

“HTTP无状态”是说单个请求的语义不依赖它恰好复用了哪条连接,不是说业务不能有Session。登录态可以放Cookie、Token或服务端Session Store,只是它们属于应用状态。

3.1 HTTP版本的主要差异

版本传输与消息组织关键特征常见误区
HTTP/1.1通常一个TCP连接承载顺序消息Keep-Alive、Chunked、Pipeline语义Keep-Alive不等于永不关闭
HTTP/2TCP上的二进制Frame与多个Stream多路复用、Header Compression、流控多路复用仍会受TCP丢包影响
HTTP/3QUIC上的HTTP语义独立流、连接迁移、减少传输层队头阻塞HTTP/3不是“HTTP改走UDP就不可靠”

HTTP/2把多个逻辑Stream复用在一条TCP连接上,避免HTTP/1.1应用层排队,但底层TCP丢包时,后续字节仍需等待重传。HTTP/3借助QUIC让不同流在传输层更独立。

3.2 Safe、Idempotent与可重试语义

  • Safe表示客户端没有请求修改服务端状态,GET和HEAD属于Safe Method;
  • Idempotent表示相同请求执行一次或多次,期望的服务端效果相同;PUT、DELETE和Safe Method在HTTP语义上是幂等的;
  • POST默认不幂等,但业务可以用Idempotency Key把它做成可安全重试;
  • 幂等只约束请求的预期效果,服务端每次都记访问日志并不违反幂等。

客户端遇到连接中断时,不能见请求就重试。读请求通常可以指数退避重试;写请求必须确认方法语义或携带幂等键,并让服务端持久化key → request_hash → result。

3.3 HTTPS请求延迟构成

冷连接的粗略时间可以拆成:

Trequest=TDNS+Tconnect+TTLS+Tserver+TtransferT_{\text{request}} =T_{\text{DNS}}+T_{\text{connect}}+T_{\text{TLS}} +T_{\text{server}}+T_{\text{transfer}}
符号含义常见优化
TDNST_{\text{DNS}}域名解析本地缓存、合理TTL、稳定Resolver
TconnectT_{\text{connect}}TCP或QUIC建连连接池、就近接入、Happy Eyeballs
TTLST_{\text{TLS}}TLS握手和证书校验Session Resumption、连接复用
TserverT_{\text{server}}网关与业务处理到首字节索引、缓存、并发控制、下游Deadline
TtransferT_{\text{transfer}}Response Body传输压缩、分页、流式返回、减少Payload

例:DNS 20 ms、Connect 35 ms、TLS 40 ms、服务端120 ms、传输15 ms,总耗时约230 ms。若复用已有HTTP/2连接,前三段可能大幅下降;因此只观察服务端Handler的120 ms,会遗漏接近一半的用户感知延迟。

实际观测还要区分TTFB和完整响应时间:TTFB低但Body下载很慢,多半不是业务计算;SSE的“完整响应时间”近似无限,更应该看首事件延迟、事件间隔、断线率和积压量。

3.4 连接池容量边界

HTTP Client连接池复用TCP/TLS连接,减少握手成本。核心参数包括:

  • 每个Host的最大空闲连接与最大总连接;
  • Idle Connection Timeout;
  • Response Header Timeout和整体Request Deadline;
  • HTTP/2每连接允许的并发Stream;
  • DNS变化后旧连接的淘汰策略。

连接池过小会在Client侧形成排队,连接池过大则可能使下游数据库或服务过载。连接池解决传输资源复用问题,不能提供无限吞吐。生产指标至少应区分“等待连接耗时”和“获得连接后的请求耗时”,否则无法根据P99上升准确定位瓶颈。

3.5 HTTP缓存、验证与并发更新

Cache-Control决定响应能否缓存以及多久新鲜;ETag和Last-Modified是Validator。缓存过期后,客户端可以带If-None-Match重新验证:

GET /mcp/tool-catalog HTTP/1.1
If-None-Match: "catalog-v42"

未变化时服务端返回304 Not Modified,客户端复用本地Body。MCP 2026版把可发现信息设计得更适合缓存,但Tool结果能否缓存仍由业务语义决定;表面表现为读操作的请求也可能包含租户或权限差异,不能据此直接缓存。

If-Match还能做乐观并发控制:客户端只在资源版本仍等于某个ETag时更新,否则返回412 Precondition Failed。它解决Lost Update,不等于数据库事务。

3.6 Proxy与Gateway的超时边界

一次HTTP请求可能经过CDN、WAF、Ingress、API Gateway、Service Mesh Sidecar和业务服务。每一跳都可能有:

  • Connect Timeout;
  • Response Header Timeout;
  • Idle Timeout;
  • Request/Response Body大小限制;
  • 重试策略;
  • Buffering和压缩;
  • Header改写与认证。

SSE常见故障包括:业务持续写入事件,但代理在Buffer达到阈值后才发送;或者服务端允许连接持续30分钟,而Ingress在60秒无字节传输后断开连接。排查时需要确认超时发生的具体链路,并使心跳间隔小于链路中最短的Idle Timeout。

3.7 CORS与认证不是一回事

CORS是浏览器执行的跨源访问策略,不是服务端认证机制。服务端即使返回Access-Control-Allow-Origin: *,仍然可以要求Bearer Token;反过来,Token正确但Preflight没通过,浏览器依然不会把实际请求交给页面。

带Cookie的跨源请求不能使用通配Origin。远程MCP和SSE端点应使用明确的Origin Allowlist,并独立校验Token的Issuer、Audience、Scope和资源归属。非浏览器客户端直接调用接口时不受CORS约束,因此安全边界必须由服务端实现。

4. SSE:基于HTTP的服务端事件流

SSE使用text/event-stream。客户端先发普通HTTP请求,服务端返回响应头后不断追加UTF-8文本事件:

id: 43
event: agent_done
retry: 3000
data: {"incident_id":"inc-7","agent":"metrics"}

空行表示一个事件结束。浏览器EventSource在连接断开后可以重连,并通过Last-Event-ID告诉服务端最后处理到哪里。

4.1 自动重连不等于消息可靠

Last-Event-ID只是续传游标。服务端若没有保留事件,仍然无从补放。AgentOps这类长任务应做到:

持久事件表 / Redis Stream:负责回放
SSE连接:负责实时投递
Event ID:负责排序、续传与客户端去重
任务状态机:负责最终事实

断开SSE连接不能直接取消Worker;显式取消应通过独立HTTP API发起并传播Cancellation。连接是观察通道,任务是业务实体;若将两者生命周期绑定,短暂的客户端网络切换也会错误终止后台任务。

4.2 SSE容量估算

用Little’s Law估一个粗略并发量:

C≈λWC\approx\lambda W
符号含义
CC平均同时存活的SSE连接数
λ\lambda平均每秒新建的连接数
WW一条连接平均存活秒数

例如每秒启动20个诊断任务,每个页面平均观察5分钟:

C≈20×(5×60)=6000C\approx20\times(5\times60)=6000

这只是数量级估算。真实容量还受文件描述符、内核Buffer、TLS、代理Idle Timeout、心跳频率、单连接事件速率和慢客户端背压影响。服务端不能让每条SSE连接占一个昂贵线程,也不能无限堆积待发送事件。

4.3 SSE事件语义设计

不要只发一串无法区分的message。事件类型应与任务状态机对齐:

task_created
agent_started
tool_started
tool_progress
tool_completed
agent_completed
task_completed
task_failed

每条事件至少携带event_id、task_id、event_type、occurred_at和最小Payload。事件ID必须在同一个可回放范围内单调且唯一;若不同Partition分别编号,应同时携带Partition或使用可比较的复合Cursor,不能将局部序号作为全局顺序。

进度事件可以合并,终态事件不能丢。客户端按event_id幂等应用事件,并以服务端任务快照作为最终事实。SSE是状态变化通知,不应成为唯一状态仓库。

4.4 断联后的连续回放

错误实现通常采用“先查历史,再订阅实时”的顺序。历史查询结束到订阅建立之间会形成事件丢失窗口。更稳妥的流程是:

1. 建立实时订阅并暂存新事件
2. 读取持久事件流当前 watermark
3. 回放 (Last-Event-ID, watermark]
4. 合并暂存区,按 event_id 排序去重
5. 切到实时流

若事件已经超过Retention,应返回resync_required,客户端重新获取任务快照,不要伪造连续性。至少一次投递加幂等消费通常比追求“绝不重复”更实际。

4.5 浏览器EventSource的工程限制

原生EventSource使用方便,但其API不允许像fetch一样任意设置请求Header。常见认证方案包括同源Cookie、短期签名URL,或使用基于fetch的SSE Client读取ReadableStream。长期Access Token不应放入Query,因为它可能进入代理日志和浏览器历史。

还要处理:

  • 页面隐藏或网络切换后的重复连接;
  • 多Tab同时订阅造成连接数放大;
  • HTTP/1.1浏览器对同Origin并发连接的限制;
  • 代理压缩或Buffering导致事件不能及时Flush;
  • 服务端Shutdown时发送server_draining并引导重连;
  • 客户端消费速度落后时的Queue上限和断开策略。

5. WebSocket:双向长连接消息

WebSocket先通过HTTP握手,随后在同一连接上交换独立的Text、Binary和Control Frame。客户端和服务端都能主动发消息,适合协同编辑、游戏、双向语音控制或高频交互。

需求SSEWebSocket
服务端持续推送,客户端偶尔用普通HTTP发命令合适能做,但更重
双方都需高频主动发消息不合适合适
浏览器自动重连EventSource提供基础能力应用自己实现
消息回放仍需服务端事件存储仍需服务端事件存储
二进制消息不擅长原生支持
HTTP代理与观测工具兼容性通常较自然需要确认Upgrade、Idle Timeout等配置

WebSocket是双向通道,不是可靠消息队列。断线重连、Sequence、ACK、去重、离线消息和背压都要由应用协议补齐。

5.1 握手、Frame与心跳

经典WebSocket通过HTTP/1.1 Upgrade握手,服务端返回101 Switching Protocols后进入WebSocket Frame协议。HTTP/2上的Extended CONNECT是另一套建立方式,不能把所有实现都解释成字面上的HTTP/1.1 Upgrade。

Frame分为Text、Binary和控制帧。Ping/Pong用于协议级保活和测量存活,Close Frame携带关闭码与原因。应用仍应定义消息Envelope:

{
  "type": "tool_progress",
  "message_id": "msg-91",
  "sequence": 42,
  "task_id": "inc-7",
  "payload": {"percent": 80}
}

message_id用于去重,sequence用于发现缺口,task_id用于路由。WebSocket保证同一连接上的Frame顺序,不保证断线重连后的业务消息连续,也不保证消息已经写进Socket就被对端业务处理。

5.2 扩容与背压

长连接会落在某个实例上。若消息由其他实例产生,需要Redis Pub/Sub、Stream、Kafka或专用Realtime Gateway把消息路由到持有连接的节点。Sticky Session可以降低路由复杂度,却不能代替状态持久化;节点崩溃后连接仍要在别处恢复。

慢客户端会导致Send Buffer增长。服务端应限制单连接待发送字节数、消息数、单消息大小和最大停滞时间。超过阈值时,可以丢弃可合并进度、降低发送频率,或者使用明确的Close Code断开连接;无限Buffer可能导致内存耗尽。

6. RPC:远程调用抽象

RPC通常包含以下部分:

IDL / Schema
→ 生成或手写 Client Stub
→ 序列化 Method + Arguments + Metadata
→ Transport 发送
→ Server Dispatch
→ 执行业务逻辑
→ 返回 Result / Error

RPC只能在接口形式上接近本地函数。远程调用还涉及网络延迟、部分失败、超时、重试、序列化、版本兼容,以及服务端已执行但客户端未收到结果等问题。实现RPC时必须显式处理这些分布式系统语义。

6.1 gRPC

gRPC常用Protocol Buffers定义服务,并在HTTP/2上提供四种调用模式:Unary、Server Streaming、Client Streaming和Bidirectional Streaming。

service DiagnosisService {
  rpc StartDiagnosis(StartRequest) returns (StartReply);
  rpc WatchDiagnosis(WatchRequest) returns (stream DiagnosisEvent);
}

它适合内部强Schema、多语言服务、代码生成和高吞吐调用。每次调用应显式设置Deadline,并将剩余预算向下游传播。取消RPC后,业务代码也需要停止数据库查询或模型调用;框架发出取消通知,不代表后台协程会自动终止。

四种调用模式及其语义

模式请求响应适合
Unary一个一个普通查询与命令
Server Streaming一个多个搜索结果、进度、模型Token流
Client Streaming多个一个分片上传、批量聚合
Bidirectional Streaming多个多个双向协作、实时控制

Streaming减少反复建调用的开销,也引入更长资源占用、流控、取消和部分结果语义。一个Server Stream中途失败时,客户端需要知道前面收到的数据能否保留、从哪个Cursor续传,不能只凭一个Unavailable决定全量重试。

Protobuf兼容性

Protobuf Wire Format用Field Number识别字段,而不是字段名:

message DiagnosisEvent {
  string task_id = 1;
  int64 sequence = 2;
  string type = 3;
  reserved 4, 5;
  reserved "legacy_payload";
}

演进规则至少记住:

  • 已上线字段不能改Number;
  • 新增Optional字段通常Wire-safe,旧代码会忽略Unknown Field;
  • 删除字段后应Reserve旧Number,不能重新赋予其他含义;
  • 不要随意改变字段类型、拆进已有oneof或重排Enum语义;
  • Wire兼容不等于业务兼容,新字段默认值可能改变旧客户端行为;
  • ProtoJSON的兼容规则与二进制Wire Format并不完全相同。

gRPC错误模型与HTTP状态码的区别

常见状态包括INVALID_ARGUMENT、NOT_FOUND、ALREADY_EXISTS、PERMISSION_DENIED、RESOURCE_EXHAUSTED、UNAVAILABLE和DEADLINE_EXCEEDED。错误码必须表达调用者下一步能做什么:改参数、重新认证、稍后重试,还是查询最终状态。

只对明确暂态且幂等的调用自动重试。DEADLINE_EXCEEDED表示客户端不再等待,不证明服务端没执行;UNAVAILABLE也可能发生在请求已经送达以后。写RPC仍需要Idempotency Key或业务唯一约束。

6.2 JSON-RPC

JSON-RPC 2.0用jsonrpc、id、method和params表示请求:

{
  "jsonrpc": "2.0",
  "id": 17,
  "method": "tools/call",
  "params": {
    "name": "search_logs",
    "arguments": {"service": "payment", "minutes": 15}
  }
}

响应使用相同id关联:

{
  "jsonrpc": "2.0",
  "id": 17,
  "result": {"content": [{"type": "text", "text": "..."}]}
}

Request有id并期待Response;Notification没有id,接收方不返回Response。JSON-RPC规定消息形状和关联方式,不规定URL、HTTP Method、鉴权、连接复用或服务发现,因此它可以运行在HTTP、WebSocket、stdio等通道上。

6.3 JSON-RPC错误模型

协议错误放在error字段,不能同时返回result:

{
  "jsonrpc": "2.0",
  "id": 17,
  "error": {
    "code": -32602,
    "message": "Invalid params",
    "data": {"field": "minutes", "reason": "must be positive"}
  }
}

-32700表示Parse Error,-32600表示Invalid Request,-32601表示Method Not Found,-32602表示Invalid Params,-32603表示Internal Error;Server自定义错误使用约定范围。面试要区分三层:

HTTP Error:请求没正常到达或被HTTP层拒绝
JSON-RPC Error:消息被解析,但协议调用失败
Tool Business Error:工具成功返回一个业务失败结果

例如,HTTP 200响应中可以包含JSON-RPC Error;HTTP 401发生时通常尚未进入JSON-RPC Method。若Trace和告警没有按层分类,鉴权失败、协议错误和Loki查询失败可能都被记录为tool_call_failed,从而降低故障定位效率。

6.4 REST、gRPC与JSON-RPC的选择

维度HTTP REST风格gRPCJSON-RPC
核心抽象Resource + Method强类型Service MethodMethod + Params
SchemaOpenAPI可选或约定Proto强Schema与代码生成JSON Schema或文档约定
浏览器直连最自然通常需要gRPC-Web或Gateway可走HTTP,工具支持一般
StreamingSSE、Chunked、WebSocket等组合四种原生RPC模式取决于承载Transport
缓存HTTP语义成熟通常由应用或代理设计协议本身不定义
内部多语言调用可以很合适简单场景合适
动态工具生态需要额外发现规范偏静态契约MCP在其上补了发现与能力语义

不是所有内部调用都该改gRPC,也不是所有Tool都该改MCP。契约稳定、吞吐高、多语言的内部服务偏gRPC;公开资源API偏HTTP;需要动态发现并让Agent受控调用的能力偏MCP。

7. MCP:AI应用的上下文与能力协议

MCP采用Client–Server结构:Host中的MCP Client连接一个或多个MCP Server。Server可以暴露:

  • Tools:可执行动作,例如查日志、调用API、写数据库;
  • Resources:可读取的上下文,例如文件、Schema或知识对象;
  • Prompts:可参数化的提示模板;
  • Extensions:在基础协议之外协商的附加能力。

模型不会直接获得网络连接或系统权限。Host选择向模型暴露哪些能力,模型提出调用意图,受控Executor校验Schema、权限和审批后,MCP Client才把消息发给Server。

7.1 Host、Client与Server角色

  • Host:用户实际使用的AI应用,管理模型、上下文、权限、UI和多个Client;
  • MCP Client:Host内部与某一个Server建立逻辑关系的协议组件;
  • MCP Server:向Client暴露一组Capability;
  • Tool Backend:MCP Server背后真正执行查询或副作用的系统,它可能根本不知道MCP存在。

一个Host可以连接多个Server,通常每个Server对应独立Client实例和安全边界。Server返回的Tool Description、Resource内容和Annotation都属于不可信输入,Host不能因为数据来自协议层就直接写入System Prompt或免审批执行。

7.2 工具调用链路

用户问题
→ Host选择MCP Server与可见工具
→ Client获取或使用已缓存的工具Schema
→ 模型生成工具名与参数
→ Executor做参数、权限、预算与审批校验
→ MCP Client发送tools/call
→ MCP Server执行受控业务逻辑
→ 结构化Result返回
→ Host写入Trace并把结果交还模型

tools/list解决发现,tools/call解决调用;JSON Schema约束输入,结构化输出约束下游消费。Schema合法不表示业务安全,delete_repository即使参数完全正确,也仍然需要最小权限和人工确认。

一次调用至少应记录:

trace_id, request_id, protocol_version
server_name, tool_name, tool_schema_hash
arguments_hash, arguments_redacted
principal, tenant_id, granted_scopes, approval_id
started_at, deadline, latency_ms
jsonrpc_error, tool_error, retry_count
result_hash, result_uri, side_effect_id

JSON-RPC id负责单条协议请求的关联,Trace ID负责跨HTTP、MCP Server和Tool Backend追踪,Idempotency Key负责写操作去重。三个ID具有不同语义,不能使用同一个UUID替代全部标识。

7.3 Tools、Resources和Prompts的控制权

三类能力在语义和生命周期上存在明确差异:

能力典型控制方语义项目例子
ToolsModel-controlled执行动作并返回结果query_loki、restart_workload
ResourcesApplication-controlled按URI读取或订阅上下文Runbook、Schema、Trace Artifact
PromptsUser-controlled用户显式选择的参数化模板“生成事故复盘”模板

实际Host可以改变交互方式,但安全设计仍要遵守这个方向感:Tool可能产生副作用,需要Policy与Approval;Resource要做租户和版本过滤;Prompt内容也可能携带Prompt Injection,不能自动获得System级信任。

Tool的inputSchema约束输入,outputSchema可以约束结构化输出。调用成功后仍可能返回isError: true表示Tool执行错误;这与JSON-RPC Error不同,后者说明协议调用本身失败。这个区分对重试和评测非常重要。

7.4 MCP与Function Calling的关系

Function Calling是模型输出结构化调用意图的能力;MCP是Host与外部能力提供方之间的协议。典型链路是:

LLM Function Calling
→ Host / Executor
→ MCP Client
→ MCP Server
→ 实际 Tool / API

二者可以组合,也能各自单独存在。本地函数可以直接注册给模型而不走MCP;MCP Client也可以由确定性Workflow驱动,不必让模型决定每次调用。

7.5 stdio与Streamable HTTP

传输进程关系优点代价适用场景
stdioClient启动本地Server子进程简单、低暴露面、继承本机权限边界生命周期绑定本机,不适合远程共享IDE、本地文件与开发工具
Streamable HTTPServer是独立网络服务可远程部署、鉴权、网关治理和多Client共享需要处理网络安全、超时与扩缩容团队级或平台级工具服务

Streamable HTTP使用单一MCP Endpoint。Client用POST发送JSON-RPC消息;Server可以直接返回JSON,也可以用SSE流返回多个消息。支持服务端流不等于每次请求都必须开SSE。

stdio模式中,每行是一个完整JSON-RPC消息,stdout只能写协议数据,普通日志必须写stderr。Host负责拉起子进程、关闭stdin、等待退出,必要时再Terminate。stdio省掉了网络鉴权,却继承Host进程的本机权限,因此工作目录、环境变量、可读Root和命令白名单更要收紧。

Streamable HTTP模式要回答四个问题:

  1. POST返回单个JSON Response还是text/event-stream;
  2. 长结果怎样分段、恢复和限制总大小;
  3. 负载均衡后请求是否能落到任意实例;
  4. Origin、Authorization、Protocol Version和Body限制由谁校验。

如果Server使用SSE返回多个消息,每个JSON-RPC消息仍必须完整地包含在一个SSE Event中,不能将不完整JSON作为“流式JSON”交给Client。网络流可以分块,但应用消息边界必须完整。

早期2024-11-05版本的HTTP+SSE传输已被Streamable HTTP替代。面试若只背“远程MCP就是一条SSE收、一条POST发”,说的是旧版本。

7.6 协议版本差异

MCP仍在快速演进,不能将某个SDK版本的行为视为稳定不变的协议规范:

协议版本生命周期与会话重点
2025-06-18 / 2025-11-25initialize → initialized协商版本和Capability;HTTP模式可使用Session ID
2026-07-28协议核心改为无握手、无会话;版本、Client信息与Capability随请求携带,可选server/discover

新版本的无状态核心允许任意实例处理自包含请求,更适合常规负载均衡、缓存和水平扩容。应用仍然可以维护长任务与持久状态,但这些状态不能仅保存在某条连接或单个Server实例的内存中。

版本兼容策略不应依赖解析失败后的推断:

  • 明确携带MCP-Protocol-Version;
  • 新Client可先调用server/discover了解版本和Capability;
  • 按协议版本选择Codec和Method集合;
  • Tool Schema缓存键包含Server Identity、Protocol Version和Schema Version;
  • 收到未协商Capability的消息应拒绝,而不是尽力执行;
  • 升级先做双版本Contract Test,再逐步切流。

7.7 Progress、取消与长任务

普通同步Tool Call适合在Deadline内完成的工作。长时间训练、代码分析或批量检索若一直占住HTTP请求,会遇到代理Timeout、重试歧义和资源占用。2026版MCP通过Tasks Extension支持异步生命周期:Server可以返回Task Handle,Client再查询状态、更新或取消。

项目实现可以抽象成:

tools/call
→ accepted + task_id
→ Worker持久化执行
→ tasks/get 查询状态
→ terminal result / error
→ TTL后清理结果

任务必须绑定Principal和Tenant,Task ID需要不可猜或有授权校验;取消是请求,不保证副作用能回滚;结果必须有TTL和大小上限。协议无状态不等于任务无状态,任务状态应放持久库或可靠队列,而不是某台MCP Server的Map里。

对于短调用,Progress Notification可以表明任务仍在执行,但实现仍应设置硬性最大Deadline,不能因为持续收到进度事件而无限延长任务生命周期。

7.8 远程MCP授权与安全

远程MCP的授权是HTTP传输层能力,通常围绕OAuth 2.0 / OIDC资源服务器模型。至少检查:

  • TLS与Server Identity;
  • Token的Issuer、Audience、有效期与Signature;
  • Scope是否覆盖具体Tool和资源;
  • Client Credential与签发Issuer绑定,防止凭据串用;
  • Step-up Authorization只增加当前动作需要的Scope;
  • Redirect URI和Authorization Response的iss校验;
  • Tool调用的Tenant、对象归属与行级权限;
  • 高风险写操作的Human-in-the-loop Approval。

认证回答“主体是谁”,授权回答“主体具备哪些权限”,Tool Policy回答“当前上下文是否允许执行”,Approval回答“高风险动作是否得到本次明确同意”。四个层次不能合并为单一isAdmin布尔值,否则无法表达上下文、资源和动作级权限。

MCP Server还要防SSRF、Prompt Injection、超大Result、恶意Tool Description、日志泄密和Confused Deputy。Host不应把用户Token原样转发给任意Server,也不能让Server诱导Host访问未经授权的另一个资源。

7.9 MCP错误分层

层例子是否适合重试
TransportDNS失败、连接重置、HTTP 502只对暂态错误,受总Deadline约束
Authorization401、Scope不足刷新或Step-up后再试,不要盲重试
JSON-RPCParse Error、Method Not Found修协议或版本,不重试同一错误消息
MCP Capability未声明Tools、Method不属于该版本重新发现或降级
Tool Validation参数不满足Schema修参数
Tool ExecutionLoki超时、数据库冲突看幂等性和业务错误类型
Result Consumption输出过大、Schema不符、引用失效截断、转Resource或标记Server缺陷

把错误分层后,告警才能带行动:tool_backend_unavailable可以重试和熔断,tool_permission_denied应查Policy,invalid_tool_arguments应改Planner或Schema,不能全部归咎于“模型不稳定”。

8. Agent项目中的协议组合

以AgentOps故障诊断为例,各协议各守一段边界:

Browser
  ├─ HTTP POST:创建、取消诊断任务
  └─ SSE:接收可续传的诊断进度

Go API / Orchestrator
  ├─ 进程内函数:稳定、低成本的本地能力
  ├─ HTTP / gRPC:调用内部或第三方服务
  └─ MCP Client:统一发现并调用可插拔AI工具

MCP Servers
  ├─ stdio:本地CodeGraph、工作区工具
  └─ Streamable HTTP:共享的日志、指标、知识检索能力

Tool Backend
  ├─ HTTP API:Prometheus、Loki、外部平台
  └─ Driver / Socket:PostgreSQL、Redis、Neo4j、Milvus

设计目标不是将所有接口统一改为MCP。稳定内部服务之间若已有成熟gRPC契约,没有必要再增加MCP封装;需要被多个Agent Host动态发现、隔离和治理的工具更适合使用MCP。浏览器使用SSE查看任务进度也不需要经过MCP。明确协议边界有助于故障定位。

8.1 一次“查支付服务错误日志”的调用

1. Browser POST /incidents 创建任务,拿到 incident_id
2. Browser建立 /events SSE,只负责观察
3. Planner选择 Experience Agent 与 search_logs Tool
4. Executor检查 Tool Allowlist、时间范围和租户
5. MCP Client调用 tools/call(request_id=17)
6. Log MCP Server把参数转换成受限Loki Query
7. Loki返回日志,Server做大小限制、脱敏和结构化
8. MCP Result关联 request_id=17 回到Host
9. Trace Recorder记录原始查询Hash、结果URI与Span
10. Worker写 task_completed 事件,SSE实时推送或稍后补放

如果第8步前连接断开,Client只知道结果未知。由于日志查询是只读,可以用同一参数和Deadline重试;如果Tool是restart_workload,则必须用incident_id + action + target + generation形成Idempotency Key,并先查询动作最终状态。

8.2 协议级指标与Dashboard

层指标
DNSLookup P50/P95、Failure Rate、Cache Hit
Connect/TLSConnect P95、Handshake P95、Certificate Error
HTTPStatus分布、TTFB、Body Bytes、Pool Wait、Retry Count
SSE/WebSocketActive Connections、Reconnect、Lag、Dropped Events
RPCMethod Latency、Status Code、Deadline Exceeded、Inflight
MCPServer/Tool维度Success、JSON-RPC Error、Schema Failure
Tool业务成功率、Side Effect ID、依赖耗时、Result Size

Trace负责解释单次请求,Metric负责发现群体异常,Log负责补充离散事件。若只记录模型Token和最终答案,Agent链路中的大部分关键诊断信息将不可见。

9. 超时、重试、幂等、背压与取消

选择协议只能解决通信能力,生产可靠性还取决于以下机制。

9.1 Deadline预算传播

设端到端剩余预算为DD,本层预留清理与序列化时间为δ\delta,则下游Deadline至多为:

Ddownstream=max⁡(0,D−δ)D_{\text{downstream}}=\max(0,D-\delta)

例如,上游只剩800 ms,本层预留100 ms,则下游调用最多分配700 ms。若每层都重新设置固定30秒Timeout,请求的实际生命周期可能远超上游预算。

9.2 暂态错误重试

适合有限重试的通常是连接重置、明确的Unavailable、限流或网关暂态失败;参数错误、权限拒绝和确定性业务冲突不应重试。重试需要:

  • Exponential Backoff与Jitter;
  • 最大次数和总Deadline;
  • 幂等请求或Idempotency Key;
  • Retry Budget,防止故障时请求风暴;
  • 明确区分“没执行”“执行失败”和“执行结果未知”。

可以使用重试矩阵将重试策略定义为明确规则:

操作错误默认策略
只读、幂等Connect Timeout有预算时退避重试
只读、幂等HTTP 429遵守Retry-After并加Jitter
只读、幂等HTTP 500/503有限重试,可配熔断
写操作、有幂等键响应前断联用同一Key重试或查询结果
写操作、无幂等键响应前断联结果未知,先查状态或人工处理
任意操作400 / Invalid Params修请求,不重试
任意操作401 / 403重新认证或申请权限,不盲重试
任意操作Deadline耗尽立即停止,不得在预算耗尽后继续执行

Hedged Request在首个请求变慢时向另一个副本发送第二个请求,以降低尾延迟。它仅适合幂等读,并需要全局额外流量预算;系统已经过载时,无约束的Hedge会进一步增加下游负载。

9.3 背压需要有界缓冲

Producer快于Consumer时,需要Bounded Queue、流控、降采样、合并进度事件或断开慢消费者。SSE进度可以丢弃可合并的token_delta,但不能丢失最终completed状态;RPC Stream应利用框架流控并限制单消息大小;MCP Tool Result过大时应返回Resource Link或对象存储URI,而不是将大量JSON直接写入模型上下文。

9.4 取消是跨层协作

客户端取消HTTP或RPC仅表示不再等待。服务端必须将Cancellation传播到数据库查询、下游HTTP、模型推理与Worker。对于已经产生外部副作用的工具,取消无法撤销既有结果,应进入补偿、查询最终状态或人工接管流程。

9.5 幂等键设计

一个可靠的写接口通常将幂等记录与业务写放在同一事务边界:

Idempotency-Key: inc-7:restart:payment:v3

key
request_hash
status: PROCESSING | SUCCEEDED | FAILED
response_code
response_hash / response_body_uri
side_effect_id
expires_at

相同Key但request_hash不同必须拒绝,避免调用方误复用Key。首个请求处于PROCESSING时,重复请求可以返回409/202 + status_uri;完成后返回已保存结果。TTL要覆盖客户端最大重试窗口和消息延迟。

数据库唯一约束防止重复插入,Idempotency Record防止重复业务执行,Outbox保证业务提交后事件最终可投递。三者位于不同层次,任何单一机制都不能独立保证端到端Exactly Once语义。

9.6 熔断、限流与隔离

  • Timeout限制单次等待时间;
  • Retry应对少量暂态失败;
  • Circuit Breaker在依赖持续失败时快速拒绝,给它恢复空间;
  • Rate Limit限制调用方速率;
  • Bulkhead把不同Tool或租户隔离在独立并发池;
  • Load Shedding在过载时优先拒绝低价值请求。

通常应先设置合理的Timeout和有界并发,再引入重试与熔断。熔断器无法替代线程池、队列和并发量的容量边界。

9.7 大型结果传输

模型上下文不是对象存储。Tool结果应设置:

  • 最大JSON Body与单字段长度;
  • 行数、时间范围和分页Cursor;
  • PII与Secret脱敏;
  • content_hash与来源版本;
  • 大对象落对象存储,返回Resource Link或URI;
  • Host按Token Budget选择摘要、片段或原文。

HTTP压缩减少网络字节,不减少解压后的内存和模型Token。一个10 MB gzip响应解压成200 MB JSON,照样能把进程请走。

10. 典型故障定位

10.1 SSE每60秒准时断一次

应优先检查Ingress、Load Balancer或Service Mesh的Idle Timeout,而不是将问题归因于随机TCP故障。检查项包括:

  1. 服务端是否真的定期写心跳;
  2. 写完是否Flush;
  3. 代理是否Buffer或压缩事件流;
  4. 每一跳Idle Timeout是多少;
  5. 重连是否携带Last-Event-ID并完成补放。

若断开间隔非常稳定,先查配置计时器;若与网络切换相关,再查移动网络、NAT和客户端生命周期。

10.2 gRPC P99高但服务端Handler很快

检查Client连接池等待、DNS、负载均衡、HTTP/2并发Stream上限、Flow Control、序列化、大消息、Sidecar和下游Deadline。Handler Histogram只覆盖请求进入业务代码之后,排队和传输全被它无情开除。

10.3 MCP Tool偶发重复执行

先查Client或Gateway是否自动重试,再查请求是否携带稳定Idempotency Key。JSON-RPC id不能天然防重复:重连后Client可能生成新id,同一个id也可能在不同连接或生命周期里复用。去重必须使用业务幂等键或唯一约束。

10.4 HTTP 200但Agent仍报工具失败

依次看HTTP Body是否为JSON-RPC Error、Tool Result是否isError、outputSchema是否校验失败、Result是否被Host因大小或安全策略拒绝。HTTP层成功只说明拿到了合法HTTP响应。

10.5 同一个MCP Server本地正常、远程失败

stdio本地模式没有经历DNS、TLS、Gateway、OAuth、Origin校验和网络Body限制。远程失败要增加这些层的证据,不能拿本地tools/call成功证明公网部署没问题。

10.6 大量TIME_WAIT连接

首先检查是否存在短连接风暴、Client是否在每次请求时新建Transport、Keep-Alive是否被代理关闭,以及连接池参数是否错误。TIME_WAIT本身是正常状态;只有确认端口或连接资源已经成为瓶颈后,才应在评估风险的基础上调整系统参数。

11. 协议选型摘要

场景优先选择回答中的边界
浏览器看Agent长任务进度HTTP创建任务 + SSE看进度事件持久化、Last-Event-ID、断线不取消任务
浏览器双向高频交互WebSocket自己补重连、Sequence、ACK和背压
内部强类型服务调用gRPCDeadline、状态码、Proto兼容和负载均衡
公开资源型APIHTTP/JSONMethod语义、缓存、幂等和可观测性
本地Agent工具MCP over stdio子进程生命周期、stdout只写协议消息
共享远程Agent工具MCP over Streamable HTTP鉴权、Origin校验、版本、超时和无状态扩容
简单跨语言方法调用JSON-RPC或HTTP APIJSON-RPC只定义消息,不自动解决治理
极致定制二进制协议Raw Socket / 自定义Framing维护成本、安全、兼容性全由自己承担

12. 高频追问

HTTP是基于TCP吗

不能统一概括。HTTP/1.1和HTTP/2通常基于TCP,HTTP/3基于QUIC;HTTP语义已经与具体传输实现解耦。

SSE与长轮询有什么区别

长轮询是服务端有数据后结束一次响应,客户端再发下一次请求;SSE是一条持续响应,服务端连续追加事件。两者都需要处理代理超时、重连和消息游标。

SSE与WebSocket谁性能更高

问题缺少负载模型。单向低频通知更看重SSE的HTTP兼容和简单性;双向高频消息更适合WebSocket。连接数、消息大小、压缩、代理、TLS和背压策略通常比协议名字本身更决定性能。

RPC一定比REST快吗

不一定。gRPC的二进制序列化、HTTP/2复用和代码生成常有优势,但小流量下业务处理与数据库延迟可能占主导。REST是架构风格,RPC是调用抽象,二者也不是纯粹的性能对手。

MCP为什么用JSON-RPC而不是直接REST

MCP需要在同一逻辑协议中表达Request、Response、Notification、进度、取消和双向能力;JSON-RPC提供稳定的消息关联模型,而且不绑定HTTP资源路由。MCP再在其上补能力协商、工具、资源、Prompt、授权和传输约束。

MCP和API Gateway是什么关系

MCP定义AI能力交互语义,Gateway负责流量入口、认证、限流、路由、审计和策略执行。远程MCP Server可以部署在Gateway后面,但Gateway不会自动理解工具的业务风险,仍需Tool级权限和审批。

连接复用与会话是什么关系

连接是传输资源,会话是应用或协议状态。多个请求可以复用一条连接,一个会话也可能跨多条连接。把用户身份或任务状态只绑在连接上,会破坏负载均衡、断线恢复和水平扩容。

为什么TCP可靠,应用还会丢消息

TCP只保证一条连接内已确认字节的可靠、有序传输。进程可能在读取后、落库前崩溃;响应可能在业务提交后丢失;断线后新连接也不会自动补旧连接的应用消息。业务可靠性仍需要持久化、ACK、幂等和回放。

HTTP/2多路复用为什么还有队头阻塞

HTTP/2的Stream消除了HTTP/1.1应用层顺序响应的队头阻塞,但多个Stream仍共享一条TCP字节流。某个TCP Segment丢失时,后续字节要等待重传,其他Stream也会受影响。HTTP/3利用QUIC独立流缓解传输层队头阻塞。

Deadline和Timeout有什么区别

Timeout通常表示“最多等多久”,Deadline表示“不晚于哪个绝对时刻”。跨服务传播时使用Deadline更容易扣除已经消耗的时间;但要处理机器时钟误差,所以很多RPC框架在线路上换算成剩余Timeout。

MCP Server无状态以后,长任务放哪

无状态指协议请求可以由任意实例处理,不表示业务不能持久化。长任务应使用Task Handle,把状态放数据库、可靠队列或对象存储;任何实例都能根据Task ID查状态和取结果。

13. 分层总结

Socket为程序提供字节收发接口,TCP和QUIC负责端到端传输,HTTP定义Web消息语义,SSE与WebSocket提供不同交互模式,RPC将消息抽象为调用,JSON-RPC定义调用消息结构,MCP进一步定义Agent能力的发现与调用方式。按层分析可以明确各协议的职责、状态和故障边界;跨层混用概念会导致断线恢复、错误分类和重试语义无法准确说明。

参考规范