背景
OpenAIのメインのAPIであるResponsesAPIではMCPをサポートしており、internetからアクセス可能なMCPサーバに対してOpenAIが直接アクセスすることにより、APIを呼ぶエージェントアプリ側はMCPサーバのエンドポイントのみを指定すれば、MCPサーバとOpenAI間でやり取りされることになります。
なお、anthropicのMessages APIでも全く同様のようです。
先日、公式のgoのSDK実装である、https://github.com/modelcontextprotocol/go-sdk が、stableになった(現在も最新はv1.0.0)ので、早速go-sdkでMCPサーバを構築し、ResponsesAPIと接続したところ、以下のエラーがおきました。
"error": { "type": "mcp_protocol_error", "code": 32600, "message": "Session terminated" }
エラーメッセージで検索すると、こちらなど同様のことで困っている人が大量にみつかりますが、GitHubなどのMCPサーバ提供側が何かしらの対応をして解決してしまったり、問題が整理されてないような印象をうけたのでブログとして整理することにしました。
なお、前提として、transportはStreamable HTTPとします。
https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#streamable-http を理解していると好ましいですが、詳しくない人向けに簡潔にStreamable HTTPを説明しておきます。
Streamable HTTPでは、
- 基本的にはメーセージやり取りは、POSTで行われ、サーバはContent-Typeに
text/event-stream
を指定してSSEストリームを介して返却するか、application/json
を指定してJSONを返すことを選択できます - クライアントは、オプショナルで、GETをよぶことができ、サーバは、サーバ起点でのメッセージやり取りをこのSSEストリーム上で行うことが可能です。このストリームは、サーバからの通知、サーバからの追加の情報収集に使われたり、POSTでのSSEストリームでのレスポンスが途中で途切れてしまったときの再送に使われます。クライアントがこのサーバからのリクエスト(HTTPとしてはレスポンス)に応答するには、POSTでレスポンス(HTTPとしてはリクエスト)し、サーバはそれに202で空のbodyでレスポンスします。
- セッションと言う概念があり、
InitializeResult
時にオプショナルでサーバがMcp-Session-Id
ヘッダを返すことで開始できます。セッションがあるおかげで、開始から終了までが明確になったり、rpcメッセージのidやevent streamIDのスコープがセッション単位となり、管理がしやすくなります
結論
サーバのオプションのStateless
をTrueにするだけです。
// StreamableHTTPOptions configures the StreamableHTTPHandler.
type StreamableHTTPOptions struct {
// Stateless controls whether the session is 'stateless'.
//
// A stateless server does not validate the Mcp-Session-Id header, and uses a
// temporary session with default initialization parameters. Any
// server->client request is rejected immediately as there's no way for the
// client to respond. Server->Client notifications may reach the client if
// they are made in the context of an incoming request, as described in the
// documentation for [StreamableServerTransport].
Stateless bool
...
}
上記のように、Mcp-Session-Id
ヘッダーをvalidateしなくなり、サーバからクライアントへのリクエストをサポートしなくなります。説明は後ほど。
注意点は、Statelessという概念自体はMCPの仕様に存在しません。なので、どちらかというと、MCP server実装におけるよく使われるモード名と思われます。
FastMCPでも同様のオプションがあるようですが、実際に利用しているMCPサーバのドキュメントや実装を確認したほうがいいです。
ログ
そもそもなんでエラーがおきてしまっているのかというのを把握するために、以下のmiddlewareを仕込みます。
Mcp-Session-Idや、content-type,bodyを出力しているだけです。
import (
"bytes"
"io"
"log"
"net/http"
"strings"
)
// responseWriter wraps http.ResponseWriter to capture the status code and body.
type responseWriter struct {
http.ResponseWriter
statusCode int
body bytes.Buffer
}
func (rw *responseWriter) Write(b []byte) (int, error) {
rw.body.Write(b)
return rw.ResponseWriter.Write(b)
}
func (rw *responseWriter) WriteHeader(code int) {
rw.statusCode = code
rw.ResponseWriter.WriteHeader(code)
}
func loggingHandler(handler http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Read and log request body if present
var bodyBytes []byte
if r.Body != nil {
bodyBytes, _ = io.ReadAll(r.Body)
r.Body = io.NopCloser(bytes.NewBuffer(bodyBytes))
}
// Create a response writer wrapper to capture status code.
wrapped := &responseWriter{ResponseWriter: w, statusCode: http.StatusOK}
// Log request details with session ID header if present.
sessionID := r.Header.Get("Mcp-Session-Id")
contentType := r.Header.Get("Content-Type")
requestBody := strings.ReplaceAll(string(bodyBytes), "\n", "\\n")
log.Printf(strings.Repeat("-", 80))
log.Printf("[REQUEST] %s | %s %s | Session-ID: %s | Content-Type: %s | Body: %s",
r.RemoteAddr,
r.Method,
r.URL.Path,
sessionID,
contentType,
requestBody)
// Call the actual handler.
handler.ServeHTTP(wrapped, r)
// Log response details with session ID header if set.
responseSessionID := wrapped.Header().Get("Mcp-Session-Id")
responseContentType := wrapped.Header().Get("Content-Type")
responseBody := strings.ReplaceAll(wrapped.body.String(), "\n", "\\n")
log.Printf("[RESPONSE] %s | %s %s | Status: %d | Session-ID: %s | Content-Type: %s | Body: %s",
r.RemoteAddr,
r.Method,
r.URL.Path,
wrapped.statusCode,
responseSessionID,
responseContentType,
responseBody)
})
}
で、なんでもいいのですが、以下のtoolを用意して、適当にResponsesAPI経由で、「昨日のビデオをみせて」と聞きます。
// GetMediaDataParams defines the parameters for the media tool.
type GetMediaDataParams struct {
Begin string `json:"begin" jsonschema:"The starting timestamp for which you want to retrieve data. The timestamp should follow RFC3339."`
End *string `json:"end" jsonschema:"The ending timestamp for which you want to retrieve data. The timestamp should follow RFC3339."`
MediaType string `json:"mediaType" jsonschema:"what type of media do you want to retrieve (video or image or unknown)"`
}
mcp.AddTool(server, &mcp.Tool{
Name: "fetch_media_data",
Description: "Retrieve media data for a given time range and media type",
InputSchema: schema,
}, getMedia)
2025/10/14 01:12:24 --------------------------------------------------------------------------------
2025/10/14 01:12:24 [REQUEST] 127.0.0.1:61418 | POST /mcp | Session-ID: | Content-Type: application/json | Body: {"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"openai-mcp","version":"1.0.0"}}}
2025/10/14 01:12:25 [RESPONSE] 127.0.0.1:61418 | POST /mcp | Status: 200 | Session-ID: C6A7GQ3BWJNSZEBXCUSJXTJGUX | Content-Type: text/event-stream | Body: event: message\nid: 4OERXDZC7SILUEX5IK2SLOOZNU_0\ndata: {"jsonrpc":"2.0","id":1,"result":{"capabilities":{"logging":{},"tools":{"listChanged":true}},"protocolVersion":"2025-03-26","serverInfo":{"name":"general-server","version":"1.0.0"}}}\n\n
2025/10/14 01:12:25 --------------------------------------------------------------------------------
2025/10/14 01:12:25 [REQUEST] 127.0.0.1:61420 | POST /mcp | Session-ID: | Content-Type: application/json | Body: {"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"openai-mcp","version":"1.0.0"}},"jsonrpc":"2.0","id":0}
2025/10/14 01:12:25 [RESPONSE] 127.0.0.1:61420 | POST /mcp | Status: 200 | Session-ID: DYA7LJDDDO2RI3WVFN5V5JUFEV | Content-Type: text/event-stream | Body: event: message\nid: ODDFVDJP5HF6AV2OPR7VTXLGPR_0\ndata: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{"logging":{},"tools":{"listChanged":true}},"protocolVersion":"2025-06-18","serverInfo":{"name":"general-server","version":"1.0.0"}}}\n\n
2025/10/14 01:12:26 --------------------------------------------------------------------------------
2025/10/14 01:12:26 [REQUEST] 127.0.0.1:61422 | GET /mcp | Session-ID: DYA7LJDDDO2RI3WVFN5V5JUFEV | Content-Type: application/json | Body:
2025/10/14 01:12:26 --------------------------------------------------------------------------------
2025/10/14 01:12:26 [REQUEST] 127.0.0.1:61424 | POST /mcp | Session-ID: DYA7LJDDDO2RI3WVFN5V5JUFEV | Content-Type: application/json | Body: {"method":"notifications/initialized","jsonrpc":"2.0"}
2025/10/14 01:12:26 [RESPONSE] 127.0.0.1:61424 | POST /mcp | Status: 202 | Session-ID: | Content-Type: | Body:
2025/10/14 01:12:26 --------------------------------------------------------------------------------
2025/10/14 01:12:26 [REQUEST] 127.0.0.1:61426 | POST /mcp | Session-ID: DYA7LJDDDO2RI3WVFN5V5JUFEV | Content-Type: application/json | Body: {"method":"tools/list","jsonrpc":"2.0","id":1}
2025/10/14 01:12:26 [RESPONSE] 127.0.0.1:61426 | POST /mcp | Status: 200 | Session-ID: | Content-Type: text/event-stream | Body: event: message\nid: GHJTZWBZI7AKB4UGH7H5FRS67K_0\ndata: {"jsonrpc":"2.0","id":1,"result":{"tools":[{"description":"Retrieve media data for a given time range and media type","inputSchema":{"type":"object","required":["begin","mediaType"],"properties":{"begin":{"type":"string","description":"The starting timestamp for which you want to retrieve data. The timestamp should follow RFC3339."},"end":{"type":["null","string"],"description":"The ending timestamp for which you want to retrieve data. The timestamp should follow RFC3339."},"mediaType":{"type":"string","description":"what type of media do you want to retrieve (video or image or unknown)"}},"additionalProperties":false},"name":"fetch_media_data"}]}}\n\n
2025/10/14 01:12:27 --------------------------------------------------------------------------------
2025/10/14 01:12:27 [REQUEST] 127.0.0.1:61428 | DELETE /mcp | Session-ID: DYA7LJDDDO2RI3WVFN5V5JUFEV | Content-Type: application/json | Body:
2025/10/14 01:12:27 [RESPONSE] 127.0.0.1:61428 | DELETE /mcp | Status: 204 | Session-ID: | Content-Type: | Body:
2025/10/14 01:12:27 [RESPONSE] 127.0.0.1:61422 | GET /mcp | Status: 200 | Session-ID: | Content-Type: text/event-stream | Body:
2025/10/14 01:12:29 --------------------------------------------------------------------------------
2025/10/14 01:12:29 [REQUEST] 127.0.0.1:61430 | POST /mcp | Session-ID: DYA7LJDDDO2RI3WVFN5V5JUFEV | Content-Type: application/json | Body: {"method":"tools/call","params":{"name":"fetch_media_data","arguments":{"begin":"2024-06-26T00:00:00+09:00","end":"2024-06-27T00:00:00+09:00","mediaType":"video"}},"jsonrpc":"2.0","id":0}
2025/10/14 01:12:29 [RESPONSE] 127.0.0.1:61430 | POST /mcp | Status: 404 | Session-ID: | Content-Type: text/plain; charset=utf-8 | Body: session not found\n
ちょっとわかりづらいかもですが、log.Printf(strings.Repeat("-", 80))
をリクエストログの直前にいれてます。
ざっと、
- "protocolVersion":"2025-03-26"でinitialize -> サーバは セッションID
C6A7GQ3B..
を返却 - "protocolVersion":"2025-06-18"でinitialize -> サーバは セッションID
DYA7LJDD..
を返却 - GETリクエストをクライアントがセッションID
DYA7LJDD..
とともに送り、レスポンスは返却されないままopen(基本的に常時接続) -
notifications/initialized
,tools/list
がセッションIDDYA7LJDD..
とともにクライアントが送り、サーバはレスポンス - DELETEをセッションID
DYA7LJDD..
とともにクライアントが送り、サーバは204を返す。このとき、GETも200で一緒に終了 -
tools/call
をセッションIDDYA7LJDD..
とともにクライアントが送り、サーバが404を返却
上記の結果、OpenAIのResponsesAPIは、MCP通信を失敗として処理します。
{
"id": "mcp_0e1a978c02e862090068ed24ed62f48197b4e758fafa6bf944",
"content": null,
"role": null,
"status": "failed",
"type": "mcp_call",
"approval_request_id": null,
"arguments": "{\"begin\":\"2024-06-26T00:00:00+09:00\",\"end\":\"2024-06-27T00:00:00+09:00\",\"mediaType\":\"video\"}",
"error": {
"type": "mcp_protocol_error",
"code": 32600,
"message": "Session terminated"
},
"name": "fetch_media_data",
"output": null,
"server_label": "time_range_test"
},
statelessモードでのログ
2025/10/14 02:07:05 --------------------------------------------------------------------------------
2025/10/14 02:07:05 [REQUEST] 127.0.0.1:62022 | POST /mcp | Session-ID: | Content-Type: application/json | Body: {"jsonrpc":"2.0","method":"initialize","id":1,"params":{"protocolVersion":"2025-03-26","capabilities":{},"clientInfo":{"name":"openai-mcp","version":"1.0.0"}}}
2025/10/14 02:07:05 [RESPONSE] 127.0.0.1:62022 | POST /mcp | Status: 200 | Session-ID: W2OJQ44UJXV22PTZKTOLPZBNY2 | Content-Type: text/event-stream | Body: event: message\nid: QY2XKRLQOIN4FXWR7WI2527OGG_0\ndata: {"jsonrpc":"2.0","id":1,"result":{"capabilities":{"logging":{},"tools":{"listChanged":true}},"protocolVersion":"2025-03-26","serverInfo":{"name":"general-server","version":"1.0.0"}}}\n\n
2025/10/14 02:07:06 --------------------------------------------------------------------------------
2025/10/14 02:07:06 [REQUEST] 127.0.0.1:62024 | POST /mcp | Session-ID: | Content-Type: application/json | Body: {"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"openai-mcp","version":"1.0.0"}},"jsonrpc":"2.0","id":0}
2025/10/14 02:07:06 [RESPONSE] 127.0.0.1:62024 | POST /mcp | Status: 200 | Session-ID: MPACILUO5Y3M5JHTT4WMLW2MLO | Content-Type: text/event-stream | Body: event: message\nid: OID4U45HA3FK5LFO6HQLT5EMA2_0\ndata: {"jsonrpc":"2.0","id":0,"result":{"capabilities":{"logging":{},"tools":{"listChanged":true}},"protocolVersion":"2025-06-18","serverInfo":{"name":"general-server","version":"1.0.0"}}}\n\n
2025/10/14 02:07:06 --------------------------------------------------------------------------------
2025/10/14 02:07:06 [REQUEST] 127.0.0.1:62026 | POST /mcp | Session-ID: MPACILUO5Y3M5JHTT4WMLW2MLO | Content-Type: application/json | Body: {"method":"notifications/initialized","jsonrpc":"2.0"}
2025/10/14 02:07:06 [RESPONSE] 127.0.0.1:62026 | POST /mcp | Status: 202 | Session-ID: | Content-Type: | Body:
2025/10/14 02:07:07 --------------------------------------------------------------------------------
2025/10/14 02:07:07 [REQUEST] 127.0.0.1:62028 | GET /mcp | Session-ID: MPACILUO5Y3M5JHTT4WMLW2MLO | Content-Type: application/json | Body:
2025/10/14 02:07:07 [RESPONSE] 127.0.0.1:62028 | GET /mcp | Status: 405 | Session-ID: | Content-Type: text/plain; charset=utf-8 | Body: GET requires an active session\n
2025/10/14 02:07:07 --------------------------------------------------------------------------------
2025/10/14 02:07:07 [REQUEST] 127.0.0.1:62030 | POST /mcp | Session-ID: MPACILUO5Y3M5JHTT4WMLW2MLO | Content-Type: application/json | Body: {"method":"tools/list","jsonrpc":"2.0","id":1}
2025/10/14 02:07:07 [RESPONSE] 127.0.0.1:62030 | POST /mcp | Status: 200 | Session-ID: | Content-Type: text/event-stream | Body: event: message\nid: VG2SGD35SQY4OWF2KFFUGBYZN2_0\ndata: {"jsonrpc":"2.0","id":1,"result":{"tools":[{"description":"Retrieve media data for a given time range and media type","inputSchema":{"type":"object","required":["begin","mediaType"],"properties":{"begin":{"type":"string","description":"The starting timestamp for which you want to retrieve data. The timestamp should follow RFC3339."},"end":{"type":["null","string"],"description":"The ending timestamp for which you want to retrieve data. The timestamp should follow RFC3339."},"mediaType":{"type":"string","description":"what type of media do you want to retrieve (video or image or unknown)"}},"additionalProperties":false},"name":"fetch_media_data"}]}}\n\n
2025/10/14 02:07:08 --------------------------------------------------------------------------------
2025/10/14 02:07:08 [REQUEST] 127.0.0.1:62032 | DELETE /mcp | Session-ID: MPACILUO5Y3M5JHTT4WMLW2MLO | Content-Type: application/json | Body:
2025/10/14 02:07:08 [RESPONSE] 127.0.0.1:62032 | DELETE /mcp | Status: 204 | Session-ID: | Content-Type: | Body:
2025/10/14 02:07:10 --------------------------------------------------------------------------------
2025/10/14 02:07:10 [REQUEST] 127.0.0.1:62034 | POST /mcp | Session-ID: MPACILUO5Y3M5JHTT4WMLW2MLO | Content-Type: application/json | Body: {"method":"tools/call","params":{"name":"fetch_media_data","arguments":{"begin":"2024-06-14T00:00:00+09:00","end":"2024-06-14T23:59:59+09:00","mediaType":"video"}},"jsonrpc":"2.0","id":0}
getMedia called with params: &{2024-06-14T00:00:00+09:00 0x14000032630 video}
2025/10/14 02:07:10 [RESPONSE] 127.0.0.1:62034 | POST /mcp | Status: 200 | Session-ID: | Content-Type: text/event-stream | Body: event: message\nid: 7AQNXTI3PBUSH4AVZM5XB7TCFO_0\ndata: {"jsonrpc":"2.0","id":0,"result":{"content":[{"type":"text","text":"Here is the link to the video media data from 2024-06-14T00:00:00+09:00 to 2024-06-14T23:59:59+09:00\n https://example.com/video/2024-06-14T00%3A00%3A00%2B09%3A00/2024-06-14T23%3A59%3A59%2B09%3A00"}]}}\n\n
前回との差分のみ、書くと、
- GET /mcpに対して、即座に、Status code:405 Method Not Allowed、Body: GET requires an active session にて返却しています
-
tools/call
を削除したセッションIDとともにクライアントが送り、サーバはツール実行の結果を正常に返却
となり、
昨日(2024年6月14日)のビデオはこちらからご覧いただけます:
[昨日のビデオを見る](https://example.com/video/2024-06-14T00%3A00%3A00%2B09%3A00/2024-06-14T23%3A59%3A59%2B09%3A00)
といった回答をResponsesAPIで受け取ることができました。
仕様違反?
2025-06-18のspecをもとに確認していきます。
通常
statelessではない通常のモードで、MCP clientであるOpenAIとMCP serverであるgo-sdkの挙動がStreamable HTTPのspecに沿っているか確認しましょう。
A server using the Streamable HTTP transport MAY assign a session ID at initialization time, by including it in an Mcp-Session-Id header on the HTTP response containing the InitializeResult.
initializeのときに、サーバがMcp-Session-Idを任意で返すことにより、sessionの利用を強制することになります。ポイントはクライアントには選択権がないことです。
If an Mcp-Session-Id is returned by the server during initialization, clients using the Streamable HTTP transport MUST include it in the Mcp-Session-Id header on all of their subsequent HTTP requests.
常に、そのIDを含めてリクエストしないといけません。今回のケースでは、GETと、notifications/initialized,tools/listではクライアントはIDを含めてました。
Clients that no longer need a particular session (e.g., because the user is leaving the client application) SHOULD send an HTTP DELETE to the MCP endpoint with the Mcp-Session-Id header, to explicitly terminate the session.
The server MAY respond to this request with HTTP 405 Method Not Allowed, indicating that the server does not allow clients to terminate sessions.
が、その後、DELETEします。これに対して、serverは202を返しているので、サーバ側もsessionの終了に合意したことになります。
The server MAY terminate the session at any time, after which it MUST respond to requests containing that session ID with HTTP 404 Not Found.
server側がセッションを終了した後、tools/call
呼び出し時に終了したIDを指定しています。これに対して、サーバは404を仕様通り、返してます。
When a client receives HTTP 404 in response to a request containing an Mcp-Session-Id, it MUST start a new session by sending a new InitializeRequest without a session ID attached.
クライアントは、必ずinitializeからやり直さないといけません。が、実際には、クライアントはこれをprotocol違反として扱ってます。
というわけで、MCP clientであるOpenAI側が違反していると言っていいと思います。ただ、ここでinitializeからやり直したら永遠にtool/call
呼び出しまでにたどり着かないので、意図をもって、セッションをつかわないまま処理をつづけるようOpenAIは期待していると思われます。
statelessモード
The server MUST either return Content-Type: text/event-stream in response to this HTTP GET, or else return HTTP 405 Method Not Allowed, indicating that the server does not offer an SSE stream at this endpoint.
GET /mcpに対して、即座に、Status code:405 にて返却することにより、サーバ起因のリクエストをしないことを伝えてます。これは合法です。
ただし、tools/call
を削除したセッションIDとともに送られたときには、本来はすでに述べたように404を返すのが正しいです。
というわけで、statelessモードでは、MCP server側が仕様に違反していることになってしまってます。
実験
上記の「通常」の場合(statelessモードではない場合)、tools/call呼び出し時に終了したIDを指定しているために、404が返ってました。これは直感的にもだめな気がしますね。
では、セッションをクライアントから、DELETEした後に、仮にクライアントからのtools/call呼び出し時にセッションIDを指定しなかった場合、どうなるでしょう?
これは、middlewareにて、DELETEを受け取った後の全てのリクエストのSession-IDをDELETEすれば確認することができます。以下がその結果です。
2025/10/15 01:02:24 --------------------------------------------------------------------------------
2025/10/15 01:02:24 [REQUEST] 127.0.0.1:60973 | DELETE /mcp | Session-ID: GJAFCY4QAD3M2MRVZB7KNOVBR6 | Content-Type: application/json | Body:
2025/10/15 01:02:24 [RESPONSE] 127.0.0.1:60973 | DELETE /mcp | Status: 204 | Session-ID: | Content-Type: | Body:
2025/10/15 01:02:24 [RESPONSE] 127.0.0.1:60967 | GET /mcp | Status: 200 | Session-ID: | Content-Type: text/event-stream | Body:
2025/10/15 01:02:26 --------------------------------------------------------------------------------
2025/10/15 01:02:26 [REQUEST] 127.0.0.1:60975 | POST /mcp | Session-ID: | Content-Type: application/json | Body: {"method":"tools/call","params":{"name":"fetch_media_data","arguments":{"begin":"2024-06-22T00:00:00+09:00","end":"2024-06-22T23:59:59+09:00","mediaType":"video"}},"jsonrpc":"2.0","id":0}
2025/10/15 01:02:26 [RESPONSE] 127.0.0.1:60975 | POST /mcp | Status: 200 | Session-ID: | Content-Type: text/event-stream | Body: event: message\nid: ZZKLNGLBTQBR2AIRR3G73FAQ7C_0\ndata: {"jsonrpc":"2.0","id":0,"error":{"code":0,"message":"method \"tools/call\" is invalid during session initialization"}}\n\n
見てわかるように、
"method "tools/call" is invalid during session initialization"
と返却されていることがわかります。
このことからも、セッションを使うかどうかは最初にServerがMcp-Session-Id
を返却した時点で決定されており、その後ClientがDELETEしたとしても、セッションなしでその後のフローに行くことは許容されてないということでしょう。
Specにこの際の挙動は明示的に書かれていませんでしたが、自然に導かれる挙動だと思います。
statelessとは?
改めて、statelessモードとはどのようなものだったかというと、
- Specには違反することになる可能性があるが、Mcp-Session-Id ヘッダーをvalidateしなくなる
- サーバからクライアントへのリクエストをサポートしない
となります。最初の印象としては、1だけで2も必要なのかという疑問がありました。
serverからリクエスト(GETレスポンス) -> clientからレスポンス(POSTリクエスト) -> serverが202で返却
のフローの中で、server側で、clientからのレスポンスがどのリクエストに対応するのか知りたいケースを想定して、statelessの際にはGetでのサーバからのリクエストをサポートしてないのだと推測しますが、よく考えると、rpcメッセージのidやevent streamIDではセッションごとに一意である必要があります。なので、セッションIDがなくても突合できるのではと。しかし、さらによく考えると、サーバ側はセッションを使う想定でいるはずなので、グローバルに一意なIDの振り方をしていない可能性があり、その場合は突合の際にduplicateしてしまう可能性があるので、やはり、サポートしていないのだろうと自分の中では納得しました。
おわりに
冒頭にもかきましたが、"statelessモード"は仕様に存在する概念ではなく、ライブラリ実装によって挙動が変わる可能性があるので注意してください。