@@ -15,6 +15,7 @@ import (
15
15
"math"
16
16
"math/rand/v2"
17
17
"net/http"
18
+ "slices"
18
19
"strconv"
19
20
"strings"
20
21
"sync"
@@ -153,7 +154,7 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
153
154
154
155
if req .Method == http .MethodDelete {
155
156
if sessionID == "" {
156
- http .Error (w , "DELETE requires an Mcp-Session-Id header" , http .StatusBadRequest )
157
+ http .Error (w , "Bad Request: DELETE requires an Mcp-Session-Id header" , http .StatusBadRequest )
157
158
return
158
159
}
159
160
if transport != nil { // transport may be nil in stateless mode
@@ -173,8 +174,45 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
173
174
return
174
175
}
175
176
default :
176
- w .Header ().Set ("Allow" , "GET, POST" )
177
- http .Error (w , "unsupported method" , http .StatusMethodNotAllowed )
177
+ w .Header ().Set ("Allow" , "GET, POST, DELETE" )
178
+ http .Error (w , "Method Not Allowed: streamable MCP servers support GET, POST, and DELETE requests" , http .StatusMethodNotAllowed )
179
+ return
180
+ }
181
+
182
+ // Section 2.7 of the spec (2025-06-18) states:
183
+ //
184
+ // "If using HTTP, the client MUST include the MCP-Protocol-Version:
185
+ // <protocol-version> HTTP header on all subsequent requests to the MCP
186
+ // server, allowing the MCP server to respond based on the MCP protocol
187
+ // version.
188
+ //
189
+ // For example: MCP-Protocol-Version: 2025-06-18
190
+ // The protocol version sent by the client SHOULD be the one negotiated during
191
+ // initialization.
192
+ //
193
+ // For backwards compatibility, if the server does not receive an
194
+ // MCP-Protocol-Version header, and has no other way to identify the version -
195
+ // for example, by relying on the protocol version negotiated during
196
+ // initialization - the server SHOULD assume protocol version 2025-03-26.
197
+ //
198
+ // If the server receives a request with an invalid or unsupported
199
+ // MCP-Protocol-Version, it MUST respond with 400 Bad Request."
200
+ //
201
+ // Since this wasn't present in the 2025-03-26 version of the spec, this
202
+ // effectively means:
203
+ // 1. IF the client provides a version header, it must be a supported
204
+ // version.
205
+ // 2. In stateless mode, where we've lost the state of the initialize
206
+ // request, we assume that whatever the client tells us is the truth (or
207
+ // assume 2025-03-26 if the client doesn't say anything).
208
+ //
209
+ // This logic matches the typescript SDK.
210
+ protocolVersion := req .Header .Get (protocolVersionHeader )
211
+ if protocolVersion == "" {
212
+ protocolVersion = protocolVersion20250326
213
+ }
214
+ if ! slices .Contains (supportedProtocolVersions , protocolVersion ) {
215
+ http .Error (w , fmt .Sprintf ("Bad Request: Unsupported protocol version (supported versions: %s)" , strings .Join (supportedProtocolVersions , "," )), http .StatusBadRequest )
178
216
return
179
217
}
180
218
@@ -235,7 +273,9 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
235
273
// set the initial state to a default value.
236
274
state := new (ServerSessionState )
237
275
if ! hasInitialize {
238
- state .InitializeParams = new (InitializeParams )
276
+ state .InitializeParams = & InitializeParams {
277
+ ProtocolVersion : protocolVersion ,
278
+ }
239
279
}
240
280
if ! hasInitialized {
241
281
state .InitializedParams = new (InitializedParams )
@@ -378,11 +418,12 @@ type streamableServerConn struct {
378
418
eventStore EventStore
379
419
380
420
incoming chan jsonrpc.Message // messages from the client to the server
381
- done chan struct {}
382
421
383
- mu sync.Mutex
422
+ mu sync.Mutex // guards all fields below
423
+
384
424
// Sessions are closed exactly once.
385
425
isDone bool
426
+ done chan struct {}
386
427
387
428
// Sessions can have multiple logical connections (which we call streams),
388
429
// corresponding to HTTP requests. Additionally, streams may be resumed by
0 commit comments