mcp: Implement server-side support for discover method (SEP-2575) (#987)

## Summary
Part 3 of the SEP-2575 ("Make MCP Stateless") implementation. Builds on
PR #975 which landed the client-side discover/fallback flow and the
per-request `_meta` plumbing. This PR adds the **server-side**
counterparts:

- `server/discover` handler (replaces the
stub),ProtocolVersionSupporter`
- transport hook for per-transport version filtering,
- transport-agnostic version validation and method-removal enforcement
in `ServerSession.handle`,
- HTTP-level wire compliance for SEP-2575 error codes/status codes,
- bug fix in `internal/jsonrpc2`,`processResult` with an `MCPGODEBUG`
opt-out for backward compatibility.
This commit is contained in:
Guglielmo Colombo 2026-06-08 17:52:10 +02:00 • committed by GitHub
commit dd978160ab
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 702 additions and 187 deletions

View file

@ -14,8 +14,25 @@ import (
"time"
"github.com/modelcontextprotocol/go-sdk/internal/json"
"github.com/modelcontextprotocol/go-sdk/internal/mcpgodebug"
)
// nomethodnotfoundcodeinerror is a compatibility parameter that restores the
// pre-fix behavior of [processResult], where wrapped [ErrNotHandled] or
// [ErrMethodNotFound] errors returned by request handlers were not
// recognized as "method not found" signals. The original switch statement
// compared sentinel errors with ==, which never matched errors returned via
// fmt.Errorf("%w: ...", ErrNotHandled, ...) — including the ones produced
// by checkRequest. As a result the wire error response carried code 0
// instead of code -32601. The fix uses errors.Is to recognize wrapped
// sentinels and append the method name to the message.
//
// To restore the previous behavior, set MCPGODEBUG=nomethodnotfoundcodeinerror=1.
// This option will be removed in a future SDK version.
// See the documentation for the mcpgodebug package for instructions on how
// to use it.
var nomethodnotfoundcodeinerror = mcpgodebug.Value("nomethodnotfoundcodeinerror")
// Connection manages the jsonrpc2 protocol, connecting responses back to their
// calls. Connection is bidirectional; it does not have a designated server or
// client end.
@ -648,9 +665,7 @@ func (c *Connection) handleAsync() {
// processResult processes the result of a request and, if appropriate, sends a response.
func (c *Connection) processResult(from any, req *incomingRequest, result any, err error) error {
switch err {
case ErrNotHandled, ErrMethodNotFound:
// Add detail describing the unhandled method.
if nomethodnotfoundcodeinerror != "1" && (errors.Is(err, ErrNotHandled) || errors.Is(err, ErrMethodNotFound)) {
err = fmt.Errorf("%w: %q", ErrMethodNotFound, req.Method)
}

View file

@ -33,7 +33,7 @@ var (
// ErrServerClosing is returned for calls that arrive while the server is closing.
ErrServerClosing = NewError(-32006, "server is closing")
// ErrClientClosing is a dummy error returned for calls initiated while the client is closing.
ErrClientClosing = NewError(-32003, "client is closing")
ErrClientClosing = NewError(-32007, "client is closing")
// The following errors have special semantics for MCP transports