effect-smol 中 McpServer.layerHttp 的 HTTP 语义完善:405 / 400 / 202 状态码处理详解 effect-smol 中 McpServer.layerHttp 的 HTTP 语义完善405 / 400 / 202 状态码处理详解【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code导读本文聚焦 effect-smol 仓库Effect 生态下的 MCP 服务器实现中McpServer.layerHttp这一 HTTP 传输层的关键行为改进对不支持的 HTTP 方法返回405、对不支持的MCP-Protocol-Version请求头返回400、对已接受的纯通知notification与响应response返回空202。通过本文你将理解 Streamable HTTP 拓扑下 MCP 服务器应如何精确区分方法不支持、版本协商失败、消息已受理三类情况并掌握layerHttp的完整请求校验链路与可配置参数可直接用于在生产中构建行为规范、可被标准 MCP 客户端正确识别的 HTTP 版 MCP 服务器。一、背景layerHttp 在 MCP 服务器架构中的位置effect-smol 的 MCP 服务器实现在 McpServer.ts 模块中。该模块既负责承载服务器状态工具、资源、资源模板、提示词、完成器、已初始化的客户端与出站通知也提供了三种形态的运行层layerMcpServer.layerStdio基于标准输入输出的 MCP 服务器适用于本地 CLI 类 MCP 客户端McpServer.layerHttp注册在既有HttpRouter上的 Streamable HTTP 端点适用于远程/网络化 MCP 客户端McpServer.layer不含传输协议的纯服务器层供上层自行接入传输。项目文档 MCP.md 指出正是由于采用了分层Layer架构服务器实现可以轻松地在 stdio 与 HTTP 两种传输之间互换——只需将layerStdio替换为layerHttp工具、资源、提示词的定义与实现层无需任何改动。本次变更所涉及的三个状态码行为正是layerHttp对 MCP Streamable HTTP 传输规范的关键对齐。二、变更总览三个状态码三类明确语义原变更记录calm-servers-share.md对McpServer.layerHttp的语义做了如下收紧场景返回状态码语义不支持的 HTTP 方法如 GET、PUT、PATCH、DELETE、OPTIONS访问 MCP 端点405 Method Not Allowed端点存在但该方法不被接受携带不支持的MCP-Protocol-Version请求头400 Bad Request版本协商失败拒绝处理已接受的纯通知notification与响应response消息202 Accepted空响应体消息已受理无需返回内容这三条规则并非孤立补丁而是与layerHttp的完整请求校验链路深度耦合。下面逐一从源码层面展开。三、405非 POST 方法统一拒绝并声明 Allow 头在 McpServer.ts 中layerHttp首先预构造了一个 405 响应并显式携带Allow: POST头向客户端宣告该端点唯一合法的方法const methodNotAllowedResponse HttpServerResponse.empty({ status: 405, headers: { allow: POST } }) const methodNotAllowed (request: HttpServerRequest.HttpServerRequest) isAllowedMcpOrigin(request, options.allowedOrigins) ? Effect.succeed(methodNotAllowedResponse) : Effect.succeed(HttpServerResponse.empty({ status: 403 })) const routes Layer.mergeAll( HttpRouter.add(GET, options.path, methodNotAllowed), HttpRouter.add(PUT, options.path, methodNotAllowed), HttpRouter.add(PATCH, options.path, methodNotAllowed), HttpRouter.add(DELETE, options.path, methodNotAllowed), HttpRouter.add(OPTIONS, options.path, methodNotAllowed) )要点分析MCP Streamable HTTP 是单端点 POST 协议。所有 JSON-RPC 消息请求、通知、响应都通过 POST 发送到options.path指定的单一端点因此 GET/PUT/PATCH/DELETE/OPTIONS 都不构成合法调用。405 与 404 的区别这里选择 405 而非 404意味着端点本身存在、只是方法不被允许配合Allow: POST头符合 HTTP 语义也便于客户端快速纠正请求方法。Origin 校验优先于方法校验若请求携带的Origin头不在allowedOrigins白名单内即使方法非法也优先返回403而非 405避免向未知来源泄露端点能力信息。这一点在方法校验前统一应用。从测试角度ProtocolAdapters.test.ts 与 McpServer.test.ts 等测试套件会针对该 HTTP 层的行为做断言验证配合 TestUtils/McpServerLayer.ts 提供的测试层构造可对 405 场景进行端到端回归。四、400MCP-Protocol-Version 版本协商的严格化MCP 客户端在请求头中通过MCP-Protocol-Version声明其支持的协议版本。layerHttp在 POST 处理函数McpServer.ts中对版本头做了严格校验const protocolVersion request.headers[MCP_PROTOCOL_VERSION_HEADER] const sessionId request.headers[MCP_SESSION_ID_HEADER] const session sessionId undefined ? undefined : state.sessions.bySessionId.get(sessionId) // ... const protocolVersionHeaderRejected (protocolVersion ! undefined !state.protocolRegistry.protocols.some((protocol) protocol.protocolVersion protocolVersion)) || (session?.protocol.transport.requiresVersionHeader true protocolVersion ! session.protocol.protocolVersion)一旦判定protocolVersionHeaderRejected为真对应请求即被400拒绝。这里有几个值得注意的细节版本必须落在已注册的协议适配器集合内。protocols参数要求传入非空的协议适配器数组Arr.NonEmptyReadonlyArrayMcpProtocol.ProtocolAdapter服务器只接受这些适配器声明的protocolVersion。会话固定的版本不可漂移。当客户端已通过initialize建立会话且该会话所选协议要求版本头requiresVersionHeader true时后续请求携带的版本必须与会话固定版本完全一致否则同样 400。initialize 请求豁免版本头检查。源码注释明确说明若对initialize请求直接返回 400客户端会把端点误判为旧版 HTTPSSE 服务器并用 GET 重试初始化因此版本拒绝逻辑对initialize消息做了放行McpServer.ts避免破坏协议协商流程。批处理消息同样校验。对于 JSON-RPC 批量数组若其中包含initialize消息或当前无有效会话同样返回 400已建立会话时还要求所选协议支持 JSON-RPC 批处理acceptsJsonRpcBatches否则 400。此外POST 处理函数还包含完整的媒体类型与状态码矩阵content-type非application/json时返回415accept头未同时包含application/json与text/event-stream时返回406携带未知sessionId时返回404McpServer.ts。这些行为共同构成了版本协商之外的协议合规防线。五、202已接受的纯通知与响应返回空 202layerHttp的第三个关键语义是对于已受理、但无需回传内容的消息返回202 Accepted。其实现并非在路由层直接书写而是通过 Effect HTTP 的预处理钩子appendPreResponseHandlerUnsafe在响应出口统一改写McpServer.tsappendPreResponseHandlerUnsafe(httpRequest, (_, response) Effect.succeed( response.status 200 response.body._tag Uint8Array response.body.contentLength 0 ? HttpServerResponse.empty({ headers: Headers.remove(response.headers, content-type), status: 202 }) : response ))改写条件为内部生成了200状态、空字节体Uint8Array且contentLength 0的响应。这类空响应对应的正是协议层面的两类消息纯通知notification如notifications/initialized等服务器主动推送、客户端无需回复的通知。MCP 规范对这类消息不要求响应体202 即表示已收到并受理。响应response例如客户端发起的 ping 等无需结构化回包的消息。改写时还会剥离content-type头确保空响应体不被错误标注类型。通过统一在响应出口改写而非在每个消息分支各自处理保证了 202 语义覆盖的一致性——只要最终产生空 200 响应就自动收敛为 202。六、配置项速查如何启动一个 HTTP 版 MCP 服务器layerHttp的完整签名McpServer.ts如下export const layerHttp (options: { readonly name: string readonly version: string readonly description?: string | undefined readonly websiteUrl?: string | undefined readonly icons?: ReadonlyArrayMcpSchema.Icon | undefined readonly path: HttpRouter.PathInput readonly protocols: Arr.NonEmptyReadonlyArrayMcpProtocol.ProtocolAdapter readonly extensions?: ServerExtensions | undefined readonly allowedOrigins?: ReadonlyArraystring | undefined }): Layer.LayerMcpServer | McpServerClient, Cause.IllegalArgumentError, HttpRouter.HttpRouter参数必填说明name是MCP 服务器名称出现在 initialize 握手信息中version是MCP 服务器版本号description/websiteUrl/icons否服务器描述、官网链接与图标集合供客户端展示path是注册到HttpRouter的端点路径如/mcpprotocols是非空协议适配器数组声明支持的 MCP 协议版本顺序敏感extensions否服务器能力扩展声明allowedOrigins否Origin 白名单携带不在名单内的Origin头的请求一律 403无 Origin 的非浏览器客户端不受影响协议适配器目前支持McpProtocol.v2024_11_05、McpProtocol.v2025_03_26、McpProtocol.v2025_06_18三个版本MCP.md。需要注意layerHttp始终实现单端点 Streamable HTTP 拓扑即便选择v2024_11_05适配器也只是复用该版本的 RPC schema 与单端点兼容传输并不实现历史上双端点 HTTPSSE 传输、GET SSE、事件续传、会话过期或客户端会话终止等能力McpServer.ts。一个最小启动示例参照 MCP.md 的结构将 stdio 层替换为 HTTP 层import { Effect, Layer } from effect import { McpProtocol, McpServer, Tool, Toolkit } from effect/unstable/ai const DemoTool Tool.make(DemoTool, { description: A demo tool that echoes back the input, parameters: { message: Schema.String }, success: Schema.String }) const MyToolkit Toolkit.make(DemoTool) const ServerLayer Layer.mergeAll( McpServer.toolkit(MyToolkit).pipe( Layer.provideMerge( MyToolkit.toLayer({ DemoTool: ({ message }) Effect.succeed(Echo: ${message}) }) ) ) ).pipe( Layer.provide( McpServer.layerHttp({ name: Demo MCP Server, version: 1.0.0, path: /mcp, protocols: [McpProtocol.v2025_06_18], allowedOrigins: [https://client.example.com] }) ) )该层仅依赖HttpRouter.HttpRouter作为输入因此实际绑定端口、接口与鉴权由外层 HTTP 服务器负责——源码注释明确指出surrounding HTTP server remains responsible for binding to an appropriate interface and installing authenticationMcpServer.ts即layerHttp只负责端点语义不越权处理网络层安全。七、本次变更的意义与验证方式综合来看这三个状态码的收紧让layerHttp的 HTTP 行为完全可预期405 Allow: POST让任何误用 GET/OPTIONS 等方法的客户端立刻得到机器可读的纠正提示400 版本拒绝在协议协商阶段就把不兼容客户端挡在门外避免进入后续消息解析后才报错同时通过 initialize 豁免避免与旧式 HTTPSSE 客户端的误判纠缠202 空响应准确表达已受理、无回包的语义与标准 200 携带 JSON-RPC 结果的情况严格区分方便客户端/网关按状态码分流处理。仓库内的测试基建可对这一行为做持续回归验证McpServer.test.ts 覆盖服务器核心行为ProtocolAdapters.test.ts 覆盖协议适配层TestUtils/McpServerLayer.ts 提供可组合的测试层此外McpConformance目录下的 McpConformance.ts 与 McpConformanceFixtures.ts 用于协议一致性测试。若要在自己的项目中验证这些行为可按上述配置起一个 HTTP 层端点然后用curl分别发送GET /mcp期望 405、携带非法MCP-Protocol-Version头的 POST期望 400以及一条纯通知 POST期望 202 空响应体。八、小结McpServer.layerHttp通过 405 / 400 / 202 三个状态码为 Streamable HTTP 传输建立了清晰、符合 MCP 规范的错误与受理语义方法层面用 405 Allow 头纠正调用方式协议层面用 400 严卡版本协商消息层面用 202 表达受理成功但无回包。结合allowedOrigins的 403 防护、415/406/404 的媒体类型与会话校验这一层构成了生产可用的远程 MCP 服务器入口。开发者只需对照上文参数表配置path、protocols与allowedOrigins并将其挂载到自己负责端口与鉴权的外层 HTTP 服务器即可。【免费下载链接】t3code项目地址: https://gitcode.com/GitHub_Trending/t3/t3code创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考