mcp: Implement support for SEP-2575 on client side (#975)

Implements the client half of the SEP-2575 / `2026-06-30` protocol. This
CL is client-only; server-side `server/discover` is still a
`MethodNotFound` stub.
### Changes
- **`server/discover` probe in `Client.Connect()`** (`client.go`). When
the caller opts into `protocolVersion >= 2026-06-30`, the client probes
the server with `server/discover` before falling back to the legacy
`initialize`
  handshake.
- **Per-request `_meta` injection** (`shared.go`, `injectRequestMeta`).
For`>= 2026-06-30` sessions every outgoing request gets the SEP-2575
triple (`protocolVersion`, `clientInfo`, `clientCapabilities`).
- **Server-side fix**: `ServerSession.handle` no longer pre-populates
`state.InitializeParams` from a `server/discover` request's `_meta`, so
a subsequent fallback `initialize` is not rejected as a duplicate.
### Fallback rules
Client falls back to the legacy `initialize` handshake when discover
returns:
- `MethodNotFound` (-32601),
- `UnsupportedProtocolVersionError` (-32004),
- an HTTP `Bad Request` (substring-matched on the error message, to
interop
  with pre-2026-06-30 servers).

Fixes #966
This commit is contained in:
Guglielmo Colombo 2026-06-02 14:17:01 +02:00 • committed by GitHub
commit c60a318d22
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -764,6 +764,13 @@ func (s *Server) getPrompt(ctx context.Context, req *GetPromptRequest) (*GetProm
return res, err
}
// discover is the server-side handler for the SEP-2575 "server/discover" RPC.
//
// TODO: Complete implementation.
func (s *Server) discover(context.Context, *ServerRequest[*DiscoverParams]) (*DiscoverResult, error) {
return nil, jsonrpc2.ErrMethodNotFound
}
func (s *Server) listTools(_ context.Context, req *ListToolsRequest) (*ListToolsResult, error) {
s.mu.Lock()
defer s.mu.Unlock()
@ -1416,6 +1423,7 @@ func (s *Server) AddReceivingMiddleware(middleware ...Middleware) {
// curating these method flags.
var serverMethodInfos = map[string]methodInfo{
methodComplete: newServerMethodInfo(serverMethod((*Server).complete), 0),
methodDiscover: newServerMethodInfo(serverMethod((*Server).discover), missingParamsOK),
methodInitialize: initializeMethodInfo(),
methodPing: newServerMethodInfo(serverSessionMethod((*ServerSession).ping), missingParamsOK),
methodListPrompts: newServerMethodInfo(serverMethod((*Server).listPrompts), missingParamsOK),
@ -1489,12 +1497,6 @@ func (ss *ServerSession) handle(ctx context.Context, req *jsonrpc.Request) (any,
return nil, perRequestErr
}
if !initialized && validatedMeta.usesNewProtocol && validatedMeta.initializeParams != nil {
ss.updateState(func(state *ServerSessionState) {
state.InitializeParams = validatedMeta.initializeParams
})
}
switch req.Method {
case methodInitialize, methodPing, notificationInitialized:
if validatedMeta.usesNewProtocol {
@ -1504,11 +1506,20 @@ func (ss *ServerSession) handle(ctx context.Context, req *jsonrpc.Request) (any,
Message: fmt.Sprintf("%q is not supported in the new protocol", req.Method),
}
}
case methodDiscover:
// In case of methodDiscover call the state.initializeParams is populated
// within the discover handle function to make sure the method is supported
// when the user is probing a pre-2026-06-30 server.
default:
if !initialized && !validatedMeta.usesNewProtocol {
ss.server.opts.Logger.Error("method invalid during initialization", "method", req.Method)
return nil, fmt.Errorf("method %q is invalid during session initialization", req.Method)
}
if !initialized && validatedMeta.usesNewProtocol && validatedMeta.initializeParams != nil {
ss.updateState(func(state *ServerSessionState) {
state.InitializeParams = validatedMeta.initializeParams
})
}
}
// modelcontextprotocol/go-sdk#26: handle calls asynchronously, and