
MCP TypeScript SDK 请求体大小限制全面解析Streamable HTTP 全入口 4 MiB 上限与 413 防护机制【免费下载链接】typescript-sdkThe official TypeScript SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/GitHub_Trending/ty/typescript-sdk导读本文基于modelcontextprotocol/typescript-sdk仓库中.changeset/request-body-size-limit.md变更记录系统讲解 SDK 为 Streamable HTTP 服务端引入的请求体大小限制机制从WebStandardStreamableHTTPServerTransport到createMcpHandler、toNodeHandler、createMcpHonoApp等所有 SDK 自有的 body 读取路径如今统一在解析任何内容之前以默认4 MiB上限拦截超大请求并回答413 Payload Too Large同时配套引入 JSON-RPC 批量消息数量上限100 条与 Host/Origin 校验前置的硬化改造。读完本文你将掌握每个入口的maxRequestBodySize配置方法、readRequestBody有界读取器的实现原理、parsedBody逃逸通道的边界以及 Node 适配器双层上限的调优要点。一、背景为什么需要统一的请求体大小限制MCPModel Context Protocol服务端通过 Streamable HTTP 接收客户端 POST 上来的 JSON-RPC 消息。在过去SDK 的各个 HTTP 入口对请求体大小缺乏一致的约束一个超大的 POST body 会被完整读入内存后再尝试 JSON 解析攻击者或故障客户端可以借此耗尽服务器内存DoS 风险不同入口web-standard 传输层、createMcpHandler入口、Node/Hono 适配器各自为政行为不统一运维难以预期旧版 SSE 传输早已存在读取上限而 Streamable HTTP 路径却没有对齐。本次变更的目标十分明确让所有 SDK 自有的 body 读取都在解析之前就受限于统一大小超限直接回答413 Payload Too Large不浪费任何解析与调度资源。核心实现位于 packages/server/src/server/requestBody.ts常量与工具函数则从modelcontextprotocol/server包公开导出见 packages/server/src/index.ts。二、统一上限DEFAULT_MAX_REQUEST_BODY_SIZE 4 MiB在 requestBody.ts 中定义了两个核心常量/** Default upper bound, in bytes, on a request body read by the HTTP entry points (4 MiB). */ export const DEFAULT_MAX_REQUEST_BODY_SIZE 4 * 1024 * 1024; /** Upper bound on the number of messages accepted in one JSON-RPC batch array. */ export const MAX_BATCH_SIZE 100;DEFAULT_MAX_REQUEST_BODY_SIZE4 * 1024 * 1024字节即4 MiB。这个值并非凭空而来——它正是旧版 SSE 传输早已使用的限制本次变更让 Streamable HTTP 各入口与其对齐。Express 适配器与 stdio 传输此前也已各自对读取设限因此全 SDK 的 HTTP 读取路径现在处于同一防护水位。MAX_BATCH_SIZE单次 JSON-RPC 批量batch数组最多接受100 条消息超过则整体拒绝详见下文第六节。上限可通过新的maxRequestBodySize选项单位字节默认即DEFAULT_MAX_REQUEST_BODY_SIZE配置覆盖以下全部入口入口选项挂载点源码位置WebStandardStreamableHTTPServerTransport含基于它构建的 Node transportWebStandardStreamableHTTPServerTransportOptions.maxRequestBodySizestreamableHttp.tscreateMcpHandler转发到其无状态 legacy 分支CreateMcpHandlerOptions.maxRequestBodySizecreateMcpHandler.tsisLegacyRequest/legacyStatelessFallback相同的选项保证判定与处理用同一把尺子createMcpHandler.tscreateMcpHonoAppJSON 预解析CreateMcpHonoAppOptions.maxRequestBodySizehono.tstoNodeHandler/toWebRequestNode 适配器ToNodeHandlerOptions/ToWebRequestOptionstoNodeHandler.ts选项值的校验由resolveMaxRequestBodySize完成requestBody.ts省略时回落默认值传入的值必须为正的有限数值否则在配置期即抛出RangeErrormaxRequestBodySize must be a positive number of bytes杜绝运行时才暴露的配置错误。三、核心实现readRequestBody有界读取器maxRequestBodySize之所以能在解析之前生效靠的是导出的有界读取器readRequestBodyrequestBody.tsexport async function readRequestBody( request: Request, maxBytes: number DEFAULT_MAX_REQUEST_BODY_SIZE ): Promise{ tooLarge: true } | { tooLarge: false; text: string } { if (Number(request.headers.get(content-length)) maxBytes) { return { tooLarge: true }; } if (request.body null) { return { tooLarge: false, text: }; } const reader request.body.getReader(); const decoder new TextDecoder(); let received 0; let text ; try { for (;;) { const { done, value } await reader.read(); if (done) break; received value.byteLength; if (received maxBytes) { return { tooLarge: true }; } text decoder.decode(value, { stream: true }); } } finally { reader.releaseLock(); } return { tooLarge: false, text: text decoder.decode() }; }其设计要点有三Content-Length预检查零读取拒绝若请求头声明的Content-Length已经超过上限一个字节都不读直接返回tooLarge: true。这是最高效的拦截路径。流式累积检查边读边断对于没有可靠Content-Length或使用 chunked transfer的请求通过ReadableStream的getReader()逐块读取累计字节数一旦超过上限立即返回tooLarge不会等到把整个 body 读完。流式读取失败网络中断等会原样向上传播。释放锁与 UTF-8 安全解码finally中保证reader.releaseLock()文本用TextDecoder流式解码并在末尾 flush避免多字节字符在块边界被截断损坏。该函数作为readRequestBody从modelcontextprotocol/server导出index.ts其定位是像isJsonContentType一样的基础构件供需要自行预解析 body 的适配器作者复用从而让第三方适配器也能获得与 SDK 一致的有界读取语义。超限后的统一响应由requestBodyTooLargeMessage生成requestBody.tsPayload Too Large: Request body must not exceed {maxBytes} bytes对应响应为 HTTP413JSON-RPC error code-32000且在 body 被解析、任何服务器实例被创建之前就已返回。四、各入口的行为与配置详解4.1WebStandardStreamableHTTPServerTransport及 Node transport这是核心的 Web 标准 Streamable HTTP 传输层可在任意支持 Web 标准的运行时运行Node.js 18、Cloudflare Workers、Deno、Bun 等。Node 环境下的NodeStreamableHTTPServerTransport正是对其的封装其 JSDoc 中明确说明 wraps this transport因此同样继承了上限。在构造函数中streamableHttp.tsmaxRequestBodySize经resolveMaxRequestBodySize解析后存入实例。handlePostRequest的处理流程streamableHttp.ts为校验Accept头与Content-Type415 检查若调用方未提供parsedBody则调用readRequestBody(req, this._maxRequestBodySize)tooLarge时通过createJsonErrorResponse返回413并触发onerror报告通过后才JSON.parse然后做 batch 数量检查与JSONRPCMessageSchema校验。有状态/无状态会话语义、SSE 流式响应等既有行为均不受影响——限制只在读 body这一步之前生效。4.2createMcpHandler/isLegacyRequest/legacyStatelessFallbackcreateMcpHandler是服务 2026-07-28 协议修订版并默认回落到 2025 时代无状态服务的 HTTP 入口。它的maxRequestBodySize同时作用于两条腿现代分支modern leg请求分类步骤classifyEntryRequestcreateMcpHandler.ts读取 body 时使用同一上限读取结果tooLarge会返回{ step: body-too-large }随后入口直接以413/-32000回答createMcpHandler.ts不创建任何服务器实例、不进入分类阶梯无状态 legacy 分支legacy legcreateLegacyStatelessFallback构造的 per-request transport 会收到同样的maxRequestBodySize并透传给WebStandardStreamableHTTPServerTransportcreateMcpHandler.ts保证两个时代的服务对超大请求行为一致。isLegacyRequest判定函数同样接受maxRequestBodySize选项createMcpHandler.ts并用它驱动自己的分类读取因此判定与处理永远在同一把尺子下——从源码看它就是createMcpHandler内部分类步骤的导出形态二者共用classifyEntryRequest不会出现判定为可处理、实际却被 413 拒绝的分歧。对于 body 超限的请求isLegacyRequest会将其报告为非 legacy返回false于是它被路由到现代 handler由现代路径回答413。legacyStatelessFallback同样提供LegacyStatelessFallbackOptions.maxRequestBodySizecreateMcpHandler.ts。4.3toNodeHandler/toWebRequestRequestBodyTooLargeErrorNode 适配器toNodeHandler将 web-standard 的{ fetch, close, notify, bus }handler 适配为 Node 的(req, res, parsedBody?)形态。当没有传入预解析 body时适配器需要自行从 Node 流读取 body 并转换为 web-standardRequest这一步由toWebRequest完成toNodeHandler.ts。现在当读取过程中 body 超过上限时toWebRequestreject 一个错误其name为RequestBodyTooLargeError、status为413toNodeHandler.tstoNodeHandler捕获该错误后回答413响应体为 JSON-RPC 错误-32000并额外携带connection: close头——注释说明这是为了让 HTTP/1.1 服务器在回答后直接关闭 socket而不是挂起一个请求流只被读了一半的连接toNodeHandler.ts。手动调用toWebRequest的调用方例如自组装isLegacyRequest路由时需要自行处理该 rejection要么 catch 后返回 413要么直接传入已解析的 bodyparsedBody绕开读取。这一点在 toWebRequest 的 JSDoc 中有明确说明。4.4createMcpHonoApp与createMcpExpressAppHost/Origin 校验前置两个框架入口还伴随一项顺序性硬化Host/Origin 校验现在先于 JSON body 解析器运行。HonocreateMcpHonoApp依次注册 DNS rebinding 防护hostHeaderValidation/localhostHostValidation→ Origin 校验 → JSON body 解析中间件hono.ts。body 解析中间件对Content-Type为application/json的请求从 clone 上调用readRequestBody上限即maxRequestBodySize默认 4 MiB超限回答413并把解析结果存入c.set(parsedBody, ...)供 MCP 适配器使用。ExpresscreateMcpExpressApp同样先注册 Host/Origin 校验再app.use(express.json(...))express.ts其jsonLimit选项直接透传给 Express 的express.json({ limit })默认即 Express 内置的100kb。由此带来的可观察行为变化来自不允许的 Host 或 Origin、且携带无效 JSON body 的请求现在回答403而非400并且其 body 根本不会被读取——校验失败即短路解析器永远没有机会接触 payload。五、双层上限的调优要点Node 适配器使用toNodeHandler时存在两层上限需要特别注意ToNodeHandlerOptions JSDoc 明确提示适配器层toNodeHandler的maxRequestBodySize在toWebRequest从 Node 流缓冲 body 时应用handler 层createMcpHandler的maxRequestBodySize在 web-standardfetch内部读取时应用。适配器的 bound 先于 handler 的 bound 生效——请求必须先通过适配器的读取才可能到达 handler。因此若想调大上限两层必须同时提高否则无论 handler 层设置多大适配器层仍会以 4 MiB或你设置的更小值先行拦截。六、JSON-RPC 批量消息上限100 条400/-32600与 body 大小限制配套SDK 在所有入口统一施加了 batch 数量约束MAX_BATCH_SIZE 100。在WebStandardStreamableHTTPServerTransport.handlePostRequest中streamableHttp.tsif (Array.isArray(rawMessage) rawMessage.length MAX_BATCH_SIZE) { this.onerror?.(new Error(Invalid Request: Batch must not exceed ${MAX_BATCH_SIZE} messages)); return this.createJsonErrorResponse(400, -32_600, Invalid Request: Batch must not exceed ${MAX_BATCH_SIZE} messages); }超过 100 条消息的 batch 被整体回答400/-32600Invalid Request其中任何一条都不会被调度执行检查发生在消息分发之前该约束不区分是否传入parsedBody即使调用方预解析了 body 从而跳过 SDK 的 body 读取与大小限制batch 数量上限依然生效——这是本文第三节parsedBody逃逸通道之外的唯一例外它是无条件的。七、parsedBody逃逸通道何时跳过限制HandleRequestOptions.parsedBodystreamableHttp.ts与McpHandlerRequestOptions.parsedBodycreateMcpHandler.ts是 SDK 为上游中间件已经解析好 body的场景预留的通道当调用方传入parsedBody时SDK不再自行读取请求体因此maxRequestBodySize的大小限制不会作用于该路径——这一般是合理的因为 body 已经在你的 body-parser如express.json()、Hono 内置解析阶段被有界处理过了但如第六节所述batch 数量上限在任何情况下都适用。典型用法来自toNodeHandler的 JSDoc 示例toNodeHandler.tsconst handler createMcpHandler(factory); app.all(/mcp, toNodeHandler(handler)); // 或当 body parser 已消费流时 const node toNodeHandler(handler); app.all(/mcp, (req, res) void node(req, res, req.body));八、测试与验证路径本次变更在仓库中配有完整测试可据此验证各入口行为packages/server/test/server/streamableHttp.test.ts验证 transport 层 413 响应、batch 限制与parsedBody通道packages/server/test/server/createMcpHandler.test.ts验证入口层 body 超限返回 413、isLegacyRequest分类行为packages/middleware/node/test/toNodeHandler.test.ts验证RequestBodyTooLargeError与toNodeHandler的 413 回答packages/middleware/hono/test/hono.test.ts验证 Hono 应用 JSON 预解析的 413 与 Host/Origin 前置。相关使用文档可进一步参考 docs/serving/http.md、docs/serving/express.md、docs/serving/hono.md 与 docs/serving/web-standard.md。九、迁移与配置速查默认值maxRequestBodySize缺省即 4 MiBDEFAULT_MAX_REQUEST_BODY_SIZE与旧版 SSE 传输的历史限制对齐该常量从modelcontextprotocol/server导出。单位与校验一律为字节数非正数或非有限值在配置期抛RangeError。Node 双层toNodeHandler与createMcpHandler的上限都要配置适配器层先生效。parsedBody传入后 SDK 不读 body、不套大小限制batch 的 100 条上限依旧。Host/Origin 前置Hono 与 Express 应用中不允许的 Host/Origin 请求在 body 解析前即被 403 拒绝。行为一致性createMcpHandler、isLegacyRequest、legacyStatelessFallback共享同一分类代码路径与同一上限判定与处理不会互相矛盾。总体而言这次变更把请求体大小防护从各传输层零散的隐式约束收敛为 SDK 所有 HTTP 入口默认统一、可在配置期显式调整、并在解析前短路拒绝的一等公民能力配合 batch 数量上限与 Host/Origin 前置校验显著降低了 MCP 服务端暴露在公网时遭受超大 payload 攻击与解析资源浪费的风险面。【免费下载链接】typescript-sdkThe official TypeScript SDK for Model Context Protocol servers and clients项目地址: https://gitcode.com/GitHub_Trending/ty/typescript-sdk创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考