modelcontextprotocol / modelcontextprotocol/php-sdk

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

オープン
#291 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

bug
主要言語
PHP
スター
1.6k
フォーク
173
平均マージ
2日 49分
マージ済み PR(30日)
23

説明

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.

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

vendor/mcp/sdk/src/Server/Transport/StreamableHttpTransport.php から始め、handleRequest()、handlePostRequest()、createStreamedResponse()、CallbackStream、flushOutgoingMessages() を追跡します。該当する場合は既存のセッション検証とストリーミングパスを再利用し、その後、GET が有効なセッションを要求し、SSE レスポンスを生成し、キューに入ったメッセージを排出し、切断時に正常に終了することを確認します。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
php
領域
api, backend
issue の種類
機能追加
難易度
4/5
見積もり時間
3〜5日
活発さ
静か
明瞭さ
おおむね明確
初心者へのやさしさ
48/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。