Appearance
Q63 · MCP 调用如何保证并发?
用户 U9 问客服:“订单 A123 现在到哪了?‘运输中’又是什么意思?”客服需要查当前订单状态,还要读配送状态说明。两份信息互不依赖,可以同时读取,等都返回再回答。但如果同一时刻又有人发起“取消 A123”,读与写可能相互影响。这里说的“保证并发”,不是让 MCP 自动把所有任务无限制地并行,而是让适合并发的请求能被正确区分和汇合,并用应用与业务层的控制避免过载和竞态。
以下 U9、A123、B456 及时间都是假设示例。A123 的模拟物流结果是“运输中,9 月 26 日 10:00 到达苏州中转站”,说明资料把“运输中”解释为“包裹仍在配送链路中”。这两项都只读;涉及取消订单和退款的情节只用于说明风险,不代表真实业务政策。
先说明术语和符号
**MCP(Model Context Protocol,模型上下文协议)**规定 AI 应用里的 Client 怎样向提供外部能力的 Server 发送请求、接收响应。并发指多个工作在同一段时间内处于进行中;它们可能由不同 CPU 同时执行,也可能在等待网络时交替推进。对本文的客服来说,两次查询同时在途就算并发,不能仅凭“发出了两次调用”推断服务器一定同时执行了两段代码。
| 名称 | 含义 | A123 例子 |
|---|---|---|
| Host / Client / Server | Host 是客服 AI 应用;Client 是它连接 MCP Server 的组件;Server 暴露可调用工具或资料 | 客服 Host 通过 Client 接订单 Server |
tools/call | 调用 Server 上某个工具的 MCP 方法 | 查 A123 最新状态 |
resources/read | 读取一份资源的 MCP 方法 | 读“运输中”的说明 |
JSON-RPC id | 请求与响应的对应编号;同一发送方未完成的请求不能撞号 | 本例用 41、42 标两次查询 |
| 在途请求 | 已发送、尚未得到最终响应的请求 | id=41 尚在查订单系统 |
| Streamable HTTP / POST | MCP 的远端传输 / 一次 HTTP 请求发送方式 | 每条 MCP 消息各用一个 POST |
| SSE | Server-Sent Events,HTTP 响应中的事件流;可在同一请求的最终结果前送进度等消息 | 某次耗时查询可在自己的响应流报进度 |
| stdio | 本地子进程的标准输入输出传输;多条消息共享同一字节流 | 本机 Client 给 Server 写两行请求 |
| 无会话 / stateless | 2026-07-28 规范不靠先前某次请求或连接保存协议上下文;每次请求带必要元数据 | id=42 不靠 id=41 的连接状态才能被理解 |
| 汇合 / barrier | 应用等所需结果到齐,再进入依赖它们的步骤 | 状态和说明都到齐后才组织回答 |
| 限流 / 背压 | 控制单位时间或同时进行的请求数 / 忙时让上游排队或拒绝 | 订单系统一次最多接若干查询 |
| 竞态 | 多个操作受完成先后影响,造成结果不一致 | 查状态同时取消订单,读到的版本可能不同 |
| 幂等键 | 业务操作的去重标识;同一意图重试应只有一次外部效果 | 同一次退款申请重试不产生两笔退款 |
| 取消 | 请求方表示不再等待并希望停止进行中的工作 | 用户关闭提问或超时 |
请求 ID 不是订单号,也不是幂等键。id=41 只让 Client 知道哪份响应属于哪次调用;它不会让 A123 只查一次,更不会让“退款”自动避免重复执行。官方 2026-07-28 基础协议要求请求带字符串或整数 ID,同一发送方仍在等待响应的请求不能使用相同 ID,响应应带回对应 ID。MCP 2026-07-28 基础协议:Requests / Responses
哪些能力来自协议,哪些来自实现
MCP 给并发提供可区分的请求响应结构。在 2026-07-28 版本中,Server 处理每条请求时应只依据该请求携带的协议版本、Client 能力和参数,不从同一连接的上一条请求推断上下文;同一 stdio 进程也可能交错承载不同任务。远端 Streamable HTTP 规定每条 JSON-RPC 消息各走一个 HTTP POST,Server 可为该请求返回单个 JSON,或返回仅属于该请求的 SSE 响应流。MCP 基础协议:Statelessness · MCP Streamable HTTP
这些规则让 Client 能同时保留多个在途请求,并把返回结果放回正确的位置。它们没有规定“每个 Server 必须同时跑 N 个工具”“先发的一定先完成”“同一订单的读写会自动互斥”。是否同时发起由 Host 决定;Server 的处理能力取决于 SDK、运行时、数据库连接池和自己写的逻辑。以官方 TypeScript SDK v2 的 HTTP 入口为例,createMcpHandler 为每个 HTTP 请求创建一个 Server 实例,便于把请求相关上下文放在实例内;这并不替共用的订单数据库或外部 API 建立业务隔离。MCP TypeScript SDK v2:Serve over HTTP
本地 stdio 是另一种形态:多条请求和响应共用一条标准输入输出流,由 JSON-RPC ID 对应响应。程序可以接收多个在途请求,但若 Handler 阻塞事件循环、SDK/Server 选择串行处理或数据库只有一个可用连接,实际吞吐仍可能接近串行。协议允许正确复用通道,不等于保证并行执行。MCP stdio 规范
两次独立读取如何安全汇合
客服 Host 决定:get_order_status(A123) 与 resources/read(guide://shipping/status) 相互不依赖,都只读,所以在同一轮同时发起。此处示意 Client 给订单查询分配 id=41,给说明资源分配 id=42;实际 SDK 一般会管理这些协议 ID,业务代码无需手工写 41。图按左到右阅读:两张请求票据分别走自己的查询,结果按 ID 回到对应位置,Host 等两项都齐再组织答案。

| 示意时刻 | id=41:订单状态 | id=42:配送说明 | Host 能做什么 |
|---|---|---|---|
| 0 毫秒 | 已发出,等待订单系统 | 已发出,等待说明资源 | 记录两项都在途 |
| 120 毫秒 | 返回“运输中,9 月 26 日 10:00 到达苏州中转站” | 仍在途 | 保存状态结果,暂不假定说明内容 |
| 170 毫秒 | 已完成 | 返回“运输中:包裹仍在配送链路中” | 两项齐全,可答“订单仍在配送链路中,最近到达苏州中转站” |
这个时序只是假设。若 id=42 先返回,仍按它的 ID 放进“说明”槽位,等待 id=41;不能拿数组到达顺序猜哪个是订单结果。如果只完成一项就回答,会把“订单现在在哪”与“术语是什么意思”混成一个未经核对的结论。若资源读取失败,而它是回答用户第二个问题的必要依据,应说明这一部分暂时无法核实,或走受控降级路径,不让模型凭空解释。
Host 可以用类似下面的 JavaScript 函数片段表达“同时发起、全部落定、分别检查”。client 表示已经连接 Server 的 SDK Client;orderId 是业务订单号 A123,不是 JSON-RPC ID。Promise.allSettled 在两个 Promise 都成功或失败后给出各自结果;order 与 guide 分别对应固定位置的两个调用,status === 'fulfilled' 表示该调用得到返回,value.isError 表示工具虽有响应但报告了业务失败。ok 是本函数给上层的可用标志,content 和 contents 分别是工具和资源的返回内容。函数只有汇合与错误判断,没有实现鉴权、限流或取消,需要由调用它的应用与 Server 另行加入。
js
async function gatherOrderContext(client, orderId) {
const [order, guide] = await Promise.allSettled([
client.callTool({ name: 'get_order_status', arguments: { order_id: orderId } }),
client.readResource({ uri: 'guide://shipping/status' }),
]);
if (order.status !== 'fulfilled' || guide.status !== 'fulfilled') {
return { ok: false, reason: '查询未全部完成' };
}
if (order.value.isError) {
return { ok: false, reason: '订单查询失败' };
}
return { ok: true, order: order.value.content, guide: guide.value.contents };
}给函数传入已连接的 Client 和 orderId='A123' 时,它会同时提交两项读取,等两项返回后得到 ok: true。若订单查询返回越权工具错误,ok 为 false;若资源请求抛出异常,也得到 false。这只是有依赖的最终回答之前可并发的读取段。项目中还要限制同时调用数,否则一次用户请求可扩散为几十次工具调用,压垮后端。官方 SDK 的 callTool、readResource 分别对应工具调用和资源读取;上面代码不依赖手工构造协议报文。MCP TypeScript SDK v2:Clients / Calling
竞态出现时,把顺序放回业务层
考虑第二种情形:U9 一边问“现在到哪了”,一边点“取消 A123”。这两个请求虽然也能同时发出,却不能仅凭“都成功返回”推断答案与取消结果在同一时间点一致。例如查状态在取消前读到“运输中”,取消后订单系统变成“取消申请中”;客服若把旧状态当成取消后的状态,就给了用户过期信息。解决方法是按业务需要选择顺序:先完成取消并拿到最终结果,再重新查状态;或让订单系统返回版本号/更新时间,发现版本不一致就重查。MCP 请求 ID 只用于匹配各自的响应,不提供跨请求的事务快照或顺序锁。
更危险的是两个“发起退款”调用同时到达。两者都可能先读到“尚未退款”,然后各写一笔。Server 应把业务写入交给有事务或唯一约束的后端,使用针对同一退款意图的幂等键,重复调用返回同一业务结果;Host 也应避免对不确定是否已成功的写入盲目重试。幂等键的作用域、保存期限和是否允许复用由业务系统设计,不能拿 id=41 代替它。若两个操作有先后依赖,就串行;若互不依赖且无冲突,再考虑并发。
并发的只读请求也不必然共享一致快照:订单状态和配送说明可能来自不同更新时间。对时间敏感的解释,应保留各自来源与时间;需要强一致的判断,回到能提供同一版本或事务视图的业务系统。MCP 基础协议:Statelessness
超时、取消和限流怎样配合
假设 U9 关闭页面,id=41 还在查订单。取消是停止继续等待并请求远端停工的信号,不是“已执行动作回滚”的证明。2026-07-28 规范的 stdio 传输要求 Client 发送引用请求 ID 的 notifications/cancelled;Streamable HTTP 对采用 SSE 响应流的请求,关闭该请求的流就是取消信号。Server 应尽快停下可取消的工作并释放资源;取消可能在查询刚完成后才到,因此 Client 还要容忍迟到响应。规范也建议为请求设置超时,超时后取消并停止等待。MCP 取消规范 · MCP Streamable HTTP:Cancellation
HTTP 请求也可能返回一个普通 JSON,而非 SSE 流。此时不要把“关闭 SSE”误当作所有 HTTP 响应形式都存在的步骤;由具体 Client/HTTP 实现处理本地中止和超时,并在业务上按结果未知处理已发出的写操作。尤其退款已经提交但响应在路上丢失时,再发一次前要先查业务状态或用相同幂等键重试。
即使都是只读,Host 也不该每次看到十个候选工具就同时打十个请求。可以给总请求数、每个用户、每个后端分别设在途上限。例如本系统自行规定“全局最多 20、同一用户最多 2、订单后端最多 5”,多出的进入有长度与等待时间限制的队列;队列满了便给出可理解的繁忙反馈。数字是容量规划示例,不是 MCP 标准。Server 还要限制数据库连接、CPU 工作和外部 API 速率,记录每种工具的延迟、超时与拒绝率。只读查询遇到临时失败可按预算做有退避的重试;写操作要先解决幂等与结果确认。背压从 Host 到 Server 再到订单系统逐层生效,否则协议能接收请求也不代表后端承受得住。
面试时可以这样回答
MCP 的 2026-07-28 规范让每个请求独立携带上下文,JSON-RPC ID 对应请求和响应;Streamable HTTP 每条消息独立 POST,可返回单个 JSON 或该请求的 SSE 流。这样 Host 可以对互不依赖的只读工具或资源并发发起调用,按 ID 收结果,在需要两项数据的地方汇合。但协议不保证 Server 无限并行、先发先回、同一订单自动隔离。实现上我会先判断依赖关系,再给 Host 与后端设置并发上限、超时和错误处理;读写冲突交由业务事务、版本检查或串行控制,写操作用业务幂等键。取消是尽力停止,不能当回滚证明,并会测试乱序返回、超时、越权、限流和重复写入。
若追问“同一个 stdio 连接能否有两个在途请求”,可以回答:能按 ID 对应共享通道上的响应,但具体 SDK 和 Server 是否同时执行 Handler、吞吐多少,要通过实现与压测确认。若追问“SSE 是不是并发的前提”,可以回答:不是;每个 HTTP 消息本来就是独立 POST,Server 可以返回单个 JSON。SSE 是某条请求需要流式进度或响应时的承载方式。若追问“取消退款请求后能否立刻重新退款”,可以回答:不能仅凭取消信号判断前一次业务写入是否发生,先查状态或以同一业务幂等键安全重试。
参考资料
- MCP 2026-07-28 基础协议:JSON-RPC ID、响应对应和无会话请求。
- MCP 2026-07-28 Streamable HTTP:独立 POST、JSON 或请求级 SSE、取消。
- MCP 2026-07-28 stdio:共享标准流中的响应对应与取消。
- MCP 2026-07-28 Cancellation:超时、取消和迟到响应。
- MCP TypeScript SDK v2:Serve over HTTP:每请求 Server 工厂与处理范围。
- MCP TypeScript SDK v2:Clients / Calling:调用工具与读取资源的客户端方法。