[Streamable HTTP][Server] GET should open a server→client SSE listener

未关闭
#291 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看

还没有人认领这个 Issue。

评估

难度
4/5
预计耗时
3-5 天
新手友好度
48/100
Issue 类型
功能
描述清晰度
基本清楚
活跃度
冷清
技术栈
php
领域
api, backend

调研方向

从 vendor/mcp/sdk/src/Server/Transport/StreamableHttpTransport.php 开始,跟踪 handleRequest()、handlePostRequest()、createStreamedResponse()、CallbackStream 和 flushOutgoingMessages()。在适用的情况下复用现有的会话验证和流式处理路径,然后验证 GET 需要有效会话、生成 SSE 响应、排空排队的消息,并在断开连接时干净地终止。

由索引模型根据 Issue 内容生成。

描述

bug

Problem

StreamableHttpTransport only handles OPTIONS, POST, and DELETE. GET falls into the match default arm and gets a 405 Method Not Allowed with Allow: POST, DELETE, OPTIONS.

vendor/mcp/sdk/src/Server/Transport/StreamableHttpTransport.php (current main):

return match ($request->getMethod()) {
    'OPTIONS' => $this->handleOptionsRequest(),
    'POST'    => $this->handlePostRequest(),
    'DELETE'  => $this->handleDeleteRequest(),
    default   => $this->createErrorResponse(Error::forInvalidRequest('Method Not Allowed'), 405),
};

This contradicts both the MCP spec and the SDK's own CORS advertisement on the same class:

'Access-Control-Allow-Methods' => 'GET, POST, DELETE, OPTIONS',

Per the MCP Streamable HTTP transport spec, a client may issue GET against the MCP endpoint to open a long-lived SSE channel for server→client messages (notifications and server-initiated requests outside the request/response loop). Refusing the GET breaks that channel.

Observed impact

Spec-conformant clients open this listener immediately after initialize. The 405 kills the channel. In our deployment we see two session rows created in the session store on every fresh client startup — one for the working POST request/response loop, one orphaned from the failed listener that the client retries under a new session id.

Proposal

Add handleGetRequest() to StreamableHttpTransport and route GET to it from the match in handleRequest().

Behavior:

  • Require Mcp-Session-Id; without it return 400 (consistent with handleDeleteRequest()).
  • Validate the session exists in the configured SessionStoreInterface; on miss return 404.
  • Open an SSE response (Content-Type: text/event-stream) and stream:
    • any queued outgoing messages for this session (the same queue flushOutgoingMessages() already drains in createStreamedResponse());
    • server-initiated requests/notifications produced via the existing Protocol/Fiber machinery.
  • Honor Last-Event-ID for resumption per the spec (can land in a follow-up; the initial PR can document the gap).
  • Cleanly terminate when the client disconnects.

The mechanics already exist — CallbackStream and flushOutgoingMessages() from PR #109 do the streaming half inside handlePostRequest(). The new method is essentially the same loop without a triggering POST body.

Backward compatibility

Purely additive. Clients that never issue GET see no change. The Access-Control-Allow-Methods header already advertises GET, so the surface is unchanged from the client's perspective — only the server's response to a method it claims to accept changes from 405 to a valid SSE stream.

Related

  • PR #109 — added CallbackStream + flushOutgoingMessages() for SSE inside handlePostRequest. Provides the streaming primitives this proposal reuses.
  • Issue #226 — fixed the previous 500 response for unsupported methods to 405 + Allow. That work treated GET as legitimately unsupported; this issue argues GET is actually supported by the spec and should be wired up.
  • Issue #275 — separate concurrency race on the per-session outgoing message queue. Independent, but a working GET listener will exercise the same queue and would benefit from the same fix.
主要语言
PHP
星标
1.6k
派生
173
平均合并
2 天 49 分钟
30 天内合并 PR
23

贡献指南

打开贡献指南

从这里开始

  1. 先读完整个 Issue,再读项目的贡献指南。
  2. 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
  3. Fork 仓库,在一个分支上完成修改。
  4. 提交 Pull Request,并在描述里引用这个 Issue 编号。

modelcontextprotocol/php-sdk 的其他 Issue

查看 modelcontextprotocol/php-sdk 的全部 Issue

相似的 Issue

更多 PHP Issue

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。