@@ -15,6 +15,7 @@ import (
1515 "math"
1616 "math/rand/v2"
1717 "net/http"
18+ "slices"
1819 "strconv"
1920 "strings"
2021 "sync"
@@ -153,7 +154,7 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
153154
154155 if req .Method == http .MethodDelete {
155156 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 )
157158 return
158159 }
159160 if transport != nil { // transport may be nil in stateless mode
@@ -173,8 +174,45 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
173174 return
174175 }
175176 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 )
178216 return
179217 }
180218
@@ -235,7 +273,9 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
235273 // set the initial state to a default value.
236274 state := new (ServerSessionState )
237275 if ! hasInitialize {
238- state .InitializeParams = new (InitializeParams )
276+ state .InitializeParams = & InitializeParams {
277+ ProtocolVersion : protocolVersion ,
278+ }
239279 }
240280 if ! hasInitialized {
241281 state .InitializedParams = new (InitializedParams )
@@ -378,11 +418,12 @@ type streamableServerConn struct {
378418 eventStore EventStore
379419
380420 incoming chan jsonrpc.Message // messages from the client to the server
381- done chan struct {}
382421
383- mu sync.Mutex
422+ mu sync.Mutex // guards all fields below
423+
384424 // Sessions are closed exactly once.
385425 isDone bool
426+ done chan struct {}
386427
387428 // Sessions can have multiple logical connections (which we call streams),
388429 // corresponding to HTTP requests. Additionally, streams may be resumed by
0 commit comments