☰
MCP 面试高频考点:用 Streamable HTTP 控制返回大小并降低上下文消耗
2026/10/11 15:20:29 网站建设 项目流程

MCP 面试高频考点:用 Streamable HTTP 控制返回大小并降低上下文消耗

面试场景

某业务团队准备把企业知识库检索能力接入 AI 应用,计划通过 MCP 对 Host 暴露检索 Tool。线上试用后发现两个问题:第一,检索结果过长,单轮返回动辄上万字,很快占满模型上下文窗口;第二,远程部署时偶发网络抖动,客户端等待完整响应期间容易超时,用户体感卡顿。面试官希望候选人基于 MCP Client、TypeScript MCP SDK 与 Streamable HTTP 传输,设计一套可控、可观测、可降级的方案。


基础问题

面试官:如果要让 MCP 返回内容更短、更省上下文,你会先从哪里下手?

候选人:我的结论是先做“能力边界分层”,不要一上来就压缩传输层。MCP Server 对外主要有 Tools、Resources、Prompts 三类能力,语义不同,控制方式也不同 [资料2]。

对于知识库检索这种场景,我会把“按关键词或向量召回片段”设计成 Tool,而不是把整份文档作为 Resource 一次性暴露。原因是 Tool 可以由模型按需调用,输入参数里直接带上topK、maxCharsPerChunk、needSummary这类约束;Resource 更适合稳定、可寻址的只读内容,如果直接把长文档挂成 Resource,Host 或模型很可能一次读取过多内容,反而浪费上下文 [资料2]。

在 Tool 实现层,我会先做三件事:第一,检索阶段只保留语义完整的小块,避免把不相关段落塞进去;第二,返回值里只放必要字段,例如片段文本、来源 URI、标题、页码,不返回原始 Markdown、调试日志和全文元数据;第三,给每个 Tool 明确设置返回大小上限,并在 schema 中写清楚“超过上限会截断并返回 continuation 标记”。RAG 场景里本来就强调切块要保留语义完整性,同时避免无关内容占用上下文,这和 MCP 的能力设计是一致的 [资料2]。

面试官点评:-考察点:候选人是否理解 MCP 的能力语义,能否先从协议设计和业务边界入手,而不是把问题全部丢给网络层或压缩算法。 -合格回答:能区分 Tool、Resource、Prompt 的职责,说明“少返回”应优先在服务端业务逻辑里完成,而不是依赖传输层硬截断。 -加分项:能结合 RAG 的切块原则解释为什么不能简单按字节截断,以及为什么只读资料不应强行设计成有副作用的 Tool。


第一轮追问:为什么选 Streamable HTTP,而不是继续用 stdio?

面试官:你刚才说服务端要控制返回大小,那传输层为什么选 Streamable HTTP?如果用 stdio 不是更简单吗?

候选人:结论是分场景。stdio 适合 Host 在本机启动子进程的场景,例如本地 IDE 插件启动一个随用随启的 MCP Server;远程部署、多用户共享、需要统一鉴权和限流时,Streamable HTTP 更合适 [资料2]。

这个业务场景是企业知识库远程服务,Host 不会在用户机器上启动检索进程,所以需要走 HTTP。选择 Streamable HTTP 的核心价值不是“能发 HTTP 请求”,而是它更适合边生成边返回、边检索边下发:客户端不需要等服务端把所有召回片段、重排结果、摘要全部拼完再一次性读取,而是可以按块接收,先拿到高相关片段,再决定是否继续读取后续内容。这样既降低首包等待时间,也给客户端提供了提前终止的机会。

如果使用 TypeScript MCP SDK,官方提供了面向 Node.js 原生http、Express、Fastify、Hono 的薄适配层,这些中间件包只是把 MCP 接入运行时或 Web 框架,不会额外引入 MCP 特性或业务逻辑,适合在现有服务里嵌入 [资料1]。我会选和团队现有 Web 框架一致的适配层,例如已有 Fastify 网关就用@modelcontextprotocol/fastify,避免为了 MCP 单独维护一套 HTTP 栈 [资料1]。

这里有一个容易踩坑的细节:stdio 模式下调试日志必须写到标准错误,如果写到标准输出会破坏协议消息 [资料2]。Streamable HTTP 虽然没有这个问题,但如果服务端把调试日志、堆栈、鉴权信息写进响应体,同样会污染 JSON-RPC 消息,甚至把敏感凭据带进模型上下文,这是安全红线 [资料2]。

面试官:如果流式返回过程中网络断了怎么办?客户端已经收到一半内容,是直接丢弃还是继续用?

候选人:不能默认继续用,也不能简单全部丢弃。我的做法是给每个流式响应加逻辑边界:每个 chunk 带序号、片段 ID 和是否结束的标记;服务端在完成一个语义完整的片段后再 flush,例如一个检索结果块、一段摘要、一组来源信息,而不是按任意字节流切割。客户端 MCP Client 收到后,先把完整片段写入临时缓冲区,只有当该片段通过完整性校验后才纳入可用上下文。

如果连接中断,客户端根据已确认的最后一个片段 ID 做两种处理:如果业务允许部分结果,就使用已完整接收的高相关片段,并在返回给上层时标注“结果不完整”;如果业务要求强一致,例如合规审计场景,则走重试或显式报错。重试时不能盲目重放所有 Tool 调用,要带上幂等键,避免重复写入或重复计费。远程 MCP 本来就需要考虑认证、授权、会话管理、限流和超时,异常恢复属于这条链路里必须补的工程项 [资料2]。

面试官点评:-考察点:传输选型是否和业务目标匹配,是否理解“流式”的价值不只是性能,还包括可控消费和异常恢复。 -合格回答:能说清 stdio 与 Streamable HTTP 的适用边界,并解释流式返回为什么能帮助控制上下文、降低首包等待时间。 -加分项:能指出 SDK 适配层只是薄封装,不承载业务逻辑;提前考虑半包、断流、幂等和日志污染问题。


第二轮追问:MCP Client 端怎么配合,才能真正降低上下文消耗?

面试官:很多候选人只讲服务端裁剪,你再说说 MCP Client 这一侧应该做什么。

候选人:结论是客户端要做“守门人”,不能假设服务端一定返回得很克制。MCP 是客户端—服务端架构,Host 里的 MCP Client 负责和 Server 建立会话,通信消息使用 JSON-RPC [资料2]。因此客户端至少要承担四类职责。

第一,参数约束。即使 Tool schema 里写了maxCharsPerChunk、topK,Host 在调用前也要根据当前上下文剩余预算动态计算参数,例如上下文窗口已经很满时,把topK调低,把摘要模式打开,而不是固定传一个大值。Tool 的参数 schema 只是结构约束,不能代替服务端校验和授权,所以客户端传参属于体验优化,不是安全边界 [资料2]。

第二,结果接收与截断策略。使用 Streamable HTTP 时,客户端可以边接收边统计 token/字符预算,达到阈值后主动取消后续读取。这里的关键取舍是“继续读完更完整”还是“及时停止省上下文”。我的建议是按优先级消费:先读来源与标题,再读高相关片段,最后读可选摘要;一旦预算耗尽,就取消流,并保留已接收片段的来源元数据。RAG 场景下检索结果必须保留来源,才能支持引用和排错,不能为了省上下文把来源丢掉 [资料2]。

第三,不可信内容隔离。MCP Server 返回的检索文档本质上是不可信数据,里面的指令不能覆盖系统规则 [资料2]。客户端在把结果拼进模型上下文前,要统一加上引用边界,例如明确标记这是来自哪个 URI 的外部资料,避免文档里的“忽略以上指令”之类内容污染主流程。

第四,可观测性。客户端要记录每次 Tool 调用的请求参数、实际接收字节数、被截断的片段数、提前取消原因和错误码。服务端审计日志要记录谁在什么时间调用了哪个 Tool、关键资源范围与结果状态,并对敏感字段脱敏 [资料2]。两端日志通过请求 ID 关联,才能定位到底是召回过长、网络中断还是客户端主动取消。

下面给一个版本无关的 TypeScript 接口设计示例,用来表达职责划分,不绑定具体 SDK 内部方法名:

interface SearchToolInput { query: string; topK: number; maxCharsPerChunk: number; needSummary: boolean; idempotencyKey: string; } interface SearchResultChunk { chunkId: string; sourceUri: string; title: string; snippet: string; seq: number; done: boolean; } interface BudgetController { remainingChars(): number; shouldStopAfter(chunk: SearchResultChunk): boolean; } async function callSearchTool( client: McpClient, input: SearchToolInput, budget: BudgetController ): Promise<{ chunks: SearchResultChunk[]; truncated: boolean }> { const chunks: SearchResultChunk[] = []; let truncated = false; const stream = client.startStreamableToolCall<SearchResultChunk>( "enterprise_search", input ); for await (const chunk of stream) { if (budget.shouldStopAfter(chunk)) { truncated = true; await stream.cancel(); break; } chunks.push(chunk); if (chunk.done) break; } return { chunks, truncated }; }

如果要落地到 TypeScript MCP SDK,应通过官方提供的 Node HTTP 或对应框架中间件承载 Streamable HTTP 传输,并在服务端 Tool 实现内部按块输出;具体 API 以 SDK 文档为准,不建议在业务代码里依赖未公开的内部方法 [资料1]。

面试官:还有什么取舍和边界?

候选人:有三个必须提前讲清楚的边界。第一,不是所有 Tool 都适合流式返回。例如强事务操作、一次性计算结果、必须完整校验才能使用的签名数据,强行拆流会增加复杂度;检索、日志读取、长文本生成这类可分块消费的能力更适合。第二,截断不是无代价的。过度裁剪会丢失语义,导致模型回答缺少依据;具体的topK、字符上限、超时阈值都应该通过业务 SLA、风险评估和压测确定,不能拍脑袋写死。第三,安全不能因为流式就放松。服务端必须把模型传入的文本视为不可信输入,对文件路径、URL、资源标识符做约束;远程服务每次请求都要执行授权检查,不能因为是流式长连接就只在建连时鉴权一次 [资料2]。

一个很容易踩坑的点是:服务端如果为了“省上下文”只返回片段文本,不返回来源和 continuation 信息,短期看 token 用得少,但用户无法追溯答案依据,客户端也无法断点续取,最终反而要重复检索,总成本更高。

面试官点评:-考察点:客户端与服务端的协作意识,是否把“降低上下文消耗”看成端到端治理问题,而不是单点裁剪。 -合格回答:会覆盖动态预算、主动取消、来源保留、不可信内容隔离和可观测性,并明确参数 schema 不等于安全边界。 -加分项:能明确说出哪些能力不适合流式,能指出“只返文本不返来源”这类看似省 token、实际增加总成本的反模式。


总结

控制 MCP 返回大小并降低上下文消耗,不是单点优化,而是协议语义、传输方式、客户端预算控制和安全治理共同作用的结果:

  1. 先用对 MCP 能力:把检索这类按需操作设计为 Tool,避免把长内容不加约束地作为 Resource 暴露 [资料2]。
  2. 服务端负责“源头瘦身”:按语义切块、精简字段、设置上限、保留来源,并在 Tool 内做服务端校验与授权 [资料2]。
  3. 远程场景优先使用 Streamable HTTP:借助可流式传输的特性降低首包延迟,并给客户端提供提前停止的机会;TypeScript 侧使用官方薄适配层接入现有 Web 框架,不把业务逻辑塞进中间件 [资料1][资料2]。
  4. MCP Client 负责“按预算消费”:动态计算参数、边接边统计、超预算主动取消、隔离不可信检索内容,并记录可观测信息 [资料2]。
  5. 异常处理不能缺位:断流、半包、重试、幂等、敏感信息脱敏和审计日志都要纳入设计,否则线上一旦波动,省下来的上下文会被排障成本和安全风险吞掉 [资料2]。

这套方案适用于远程 RAG、日志检索、长文读取等结果可分块消费的 MCP 场景;对于强事务、短结果、必须完整验真的操作,则应优先保证完整性,再评估是否需要流式与动态截断。


参考资料

  • MCP TypeScript SDK
  • [MCP 基础知识]
  • MCP Java SDK
  • MCP Python SDK

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询