@@ -15,6 +15,7 @@ import (
1515 "math"
1616 "math/rand/v2"
1717 "net/http"
18+ "slices"
1819 "strconv"
1920 "strings"
2021 "sync"
@@ -152,7 +153,7 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
152153
153154 if req .Method == http .MethodDelete {
154155 if sessionID == "" {
155- http .Error (w , "DELETE requires an Mcp-Session-Id header" , http .StatusBadRequest )
156+ http .Error (w , "Bad Request: DELETE requires an Mcp-Session-Id header" , http .StatusBadRequest )
156157 return
157158 }
158159 if transport != nil { // transport may be nil in stateless mode
@@ -172,8 +173,45 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
172173 return
173174 }
174175 default :
175- w .Header ().Set ("Allow" , "GET, POST" )
176- http .Error (w , "unsupported method" , http .StatusMethodNotAllowed )
176+ w .Header ().Set ("Allow" , "GET, POST, DELETE" )
177+ http .Error (w , "Method Not Allowed: streamable MCP servers support GET, POST, and DELETE requests" , http .StatusMethodNotAllowed )
178+ return
179+ }
180+
181+ // Section 2.7 of the spec (2025-06-18) states:
182+ //
183+ // "If using HTTP, the client MUST include the MCP-Protocol-Version:
184+ // <protocol-version> HTTP header on all subsequent requests to the MCP
185+ // server, allowing the MCP server to respond based on the MCP protocol
186+ // version.
187+ //
188+ // For example: MCP-Protocol-Version: 2025-06-18
189+ // The protocol version sent by the client SHOULD be the one negotiated during
190+ // initialization.
191+ //
192+ // For backwards compatibility, if the server does not receive an
193+ // MCP-Protocol-Version header, and has no other way to identify the version -
194+ // for example, by relying on the protocol version negotiated during
195+ // initialization - the server SHOULD assume protocol version 2025-03-26.
196+ //
197+ // If the server receives a request with an invalid or unsupported
198+ // MCP-Protocol-Version, it MUST respond with 400 Bad Request."
199+ //
200+ // Since this wasn't present in the 2025-03-26 version of the spec, this
201+ // effectively means:
202+ // 1. IF the client provides a version header, it must be a supported
203+ // version.
204+ // 2. In stateless mode, where we've lost the state of the initialize
205+ // request, we assume that whatever the client tells us is the truth (or
206+ // assume 2025-03-26 if the client doesn't say anything).
207+ //
208+ // This logic matches the typescript SDK.
209+ protocolVersion := req .Header .Get (protocolVersionHeader )
210+ if protocolVersion == "" {
211+ protocolVersion = protocolVersion20250326
212+ }
213+ if ! slices .Contains (supportedProtocolVersions , protocolVersion ) {
214+ http .Error (w , fmt .Sprintf ("Bad Request: Unsupported protocol version (supported versions: %s)" , strings .Join (supportedProtocolVersions , "," )), http .StatusBadRequest )
177215 return
178216 }
179217
@@ -234,7 +272,9 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
234272 // set the initial state to a default value.
235273 state := new (ServerSessionState )
236274 if ! hasInitialize {
237- state .InitializeParams = new (InitializeParams )
275+ state .InitializeParams = & InitializeParams {
276+ ProtocolVersion : protocolVersion ,
277+ }
238278 }
239279 if ! hasInitialized {
240280 state .InitializedParams = new (InitializedParams )
@@ -377,11 +417,12 @@ type streamableServerConn struct {
377417 eventStore EventStore
378418
379419 incoming chan jsonrpc.Message // messages from the client to the server
380- done chan struct {}
381420
382- mu sync.Mutex
421+ mu sync.Mutex // guards all fields below
422+
383423 // Sessions are closed exactly once.
384424 isDone bool
425+ done chan struct {}
385426
386427 // Sessions can have multiple logical connections (which we call streams),
387428 // corresponding to HTTP requests. Additionally, streams may be resumed by
0 commit comments