Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 1 addition & 8 deletions mcp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -850,18 +850,11 @@ func (ss *ServerSession) initialize(ctx context.Context, params *InitializeParam
state.InitializeParams = params
})

// If we support the client's version, reply with it. Otherwise, reply with our
// latest version.
version := params.ProtocolVersion
if !slices.Contains(supportedProtocolVersions, params.ProtocolVersion) {
version = latestProtocolVersion
}

s := ss.server
return &InitializeResult{
// TODO(rfindley): alter behavior when falling back to an older version:
// reject unsupported features.
ProtocolVersion: version,
ProtocolVersion: negotiatedVersion(params.ProtocolVersion),
Capabilities: s.capabilities(),
Instructions: s.opts.Instructions,
ServerInfo: s.impl,
Expand Down
34 changes: 28 additions & 6 deletions mcp/shared.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,36 @@ import (
"github.com/modelcontextprotocol/go-sdk/jsonrpc"
)

// latestProtocolVersion is the latest protocol version that this version of the SDK supports.
// It is the version that the client sends in the initialization request.
const latestProtocolVersion = "2025-06-18"
const (
// latestProtocolVersion is the latest protocol version that this version of
// the SDK supports.
//
// It is the version that the client sends in the initialization request, and
// the default version used by the server.
latestProtocolVersion = protocolVersion20250618
protocolVersion20250618 = "2025-06-18"
protocolVersion20250326 = "2025-03-26"
protocolVersion20251105 = "2024-11-05"
)

var supportedProtocolVersions = []string{
latestProtocolVersion,
"2025-03-26",
"2024-11-05",
protocolVersion20250618,
protocolVersion20250326,
protocolVersion20251105,
}

// negotiatedVersion returns the effective protocol version to use, given a
// client version.
func negotiatedVersion(clientVersion string) string {
// In general, prefer to use the clientVersion, but if we don't support the
// client's version, use the latest version.
//
// This handles the case where a new spec version is released, and the SDK
// does not support it yet.
if !slices.Contains(supportedProtocolVersions, clientVersion) {
return latestProtocolVersion
}
return clientVersion
}

// A MethodHandler handles MCP messages.
Expand Down
53 changes: 47 additions & 6 deletions mcp/streamable.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"math"
"math/rand/v2"
"net/http"
"slices"
"strconv"
"strings"
"sync"
Expand Down Expand Up @@ -152,7 +153,7 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque

if req.Method == http.MethodDelete {
if sessionID == "" {
http.Error(w, "DELETE requires an Mcp-Session-Id header", http.StatusBadRequest)
http.Error(w, "Bad Request: DELETE requires an Mcp-Session-Id header", http.StatusBadRequest)
return
}
if transport != nil { // transport may be nil in stateless mode
Expand All @@ -172,8 +173,45 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
return
}
default:
w.Header().Set("Allow", "GET, POST")
http.Error(w, "unsupported method", http.StatusMethodNotAllowed)
w.Header().Set("Allow", "GET, POST, DELETE")
http.Error(w, "Method Not Allowed: streamable MCP servers support GET, POST, and DELETE requests", http.StatusMethodNotAllowed)
return
}

// Section 2.7 of the spec (2025-06-18) states:
//
// "If using HTTP, the client MUST include the MCP-Protocol-Version:
// <protocol-version> HTTP header on all subsequent requests to the MCP
// server, allowing the MCP server to respond based on the MCP protocol
// version.
//
// For example: MCP-Protocol-Version: 2025-06-18
// The protocol version sent by the client SHOULD be the one negotiated during
// initialization.
//
// For backwards compatibility, if the server does not receive an
// MCP-Protocol-Version header, and has no other way to identify the version -
// for example, by relying on the protocol version negotiated during
// initialization - the server SHOULD assume protocol version 2025-03-26.
//
// If the server receives a request with an invalid or unsupported
// MCP-Protocol-Version, it MUST respond with 400 Bad Request."
//
// Since this wasn't present in the 2025-03-26 version of the spec, this
// effectively means:
// 1. IF the client provides a version header, it must be a supported
// version.
// 2. In stateless mode, where we've lost the state of the initialize
// request, we assume that whatever the client tells us is the truth (or
// assume 2025-03-26 if the client doesn't say anything).
//
// This logic matches the typescript SDK.
protocolVersion := req.Header.Get(protocolVersionHeader)
if protocolVersion == "" {
protocolVersion = protocolVersion20250326
}
if !slices.Contains(supportedProtocolVersions, protocolVersion) {
http.Error(w, fmt.Sprintf("Bad Request: Unsupported protocol version (supported versions: %s)", strings.Join(supportedProtocolVersions, ",")), http.StatusBadRequest)
return
}

Expand Down Expand Up @@ -234,7 +272,9 @@ func (h *StreamableHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Reque
// set the initial state to a default value.
state := new(ServerSessionState)
if !hasInitialize {
state.InitializeParams = new(InitializeParams)
state.InitializeParams = &InitializeParams{
ProtocolVersion: protocolVersion,
}
}
if !hasInitialized {
state.InitializedParams = new(InitializedParams)
Expand Down Expand Up @@ -377,11 +417,12 @@ type streamableServerConn struct {
eventStore EventStore

incoming chan jsonrpc.Message // messages from the client to the server
done chan struct{}

mu sync.Mutex
mu sync.Mutex // guards all fields below

// Sessions are closed exactly once.
isDone bool
done chan struct{}

// Sessions can have multiple logical connections (which we call streams),
// corresponding to HTTP requests. Additionally, streams may be resumed by
Expand Down
Loading