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:
parent
00d3642840
commit
dd978160ab
1 changed files with 702 additions and 187 deletions
|
|
@ -766,9 +766,45 @@ func (s *Server) getPrompt(ctx context.Context, req *GetPromptRequest) (*GetProm
|
|||
|
||||
// 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
|
||||
// It returns the protocol versions supported by the underlying transport,
|
||||
// the server's capabilities, the server's identity, and the server's
|
||||
// instructions, allowing clients to negotiate without performing the legacy
|
||||
// initialize handshake.
|
||||
func (s *Server) discover(_ context.Context, req *ServerRequest[*DiscoverParams]) (*DiscoverResult, error) {
|
||||
versions := req.Session.supportedVersions
|
||||
if versions == nil {
|
||||
versions = slices.Clone(supportedProtocolVersions)
|
||||
}
|
||||
req.Session.updateState(func(state *ServerSessionState) {
|
||||
state.InitializeParams = &InitializeParams{
|
||||
ProtocolVersion: req.ProtocolVersion(),
|
||||
Capabilities: req.ClientCapabilities(),
|
||||
ClientInfo: req.ClientInfo(),
|
||||
}
|
||||
})
|
||||
return &DiscoverResult{
|
||||
SupportedVersions: versions,
|
||||
Capabilities: s.capabilities(),
|
||||
ServerInfo: s.impl,
|
||||
Instructions: s.opts.Instructions,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// filterSupportedVersions returns the subset of [supportedProtocolVersions]
|
||||
// that the Transport can serve. If t does not implement [ProtocolVersionSupporter], every
|
||||
// SDK-supported version is included.
|
||||
func filterSupportedVersions(t Transport) []string {
|
||||
pvs, ok := t.(ProtocolVersionSupporter)
|
||||
if !ok {
|
||||
return slices.Clone(supportedProtocolVersions)
|
||||
}
|
||||
out := make([]string, 0, len(supportedProtocolVersions))
|
||||
for _, v := range supportedProtocolVersions {
|
||||
if pvs.SupportsProtocolVersion(v) {
|
||||
out = append(out, v)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *Server) listTools(_ context.Context, req *ListToolsRequest) (*ListToolsResult, error) {
|
||||
|
|
@ -1093,6 +1129,11 @@ func (s *Server) Connect(ctx context.Context, t Transport, opts *ServerSessionOp
|
|||
return nil, err
|
||||
}
|
||||
|
||||
// Compute the protocol versions this session can serve, filtered by the
|
||||
// transport's capabilities (if it implements [ProtocolVersionSupporter]).
|
||||
// The list is consumed by the SEP-2575 server/discover handler.
|
||||
ss.supportedVersions = filterSupportedVersions(t)
|
||||
|
||||
// Start keepalive before returning the session to avoid race conditions with Close.
|
||||
// This is safe because the spec allows sending pings before initialization (see ServerSession.handle for details).
|
||||
if s.opts.KeepAlive > 0 {
|
||||
|
|
@ -1177,6 +1218,12 @@ type ServerSession struct {
|
|||
mcpConn Connection
|
||||
keepaliveCancel context.CancelFunc
|
||||
|
||||
// supportedVersions is the subset of [supportedProtocolVersions] that the
|
||||
// transport can actually serve, computed once at connection time from
|
||||
// [ProtocolVersionSupporter] (if implemented by the transport) and used by
|
||||
// the SEP-2575 server/discover handler.
|
||||
supportedVersions []string
|
||||
|
||||
mu sync.Mutex
|
||||
state ServerSessionState
|
||||
}
|
||||
|
|
@ -1511,6 +1558,23 @@ func (ss *ServerSession) handle(ctx context.Context, req *jsonrpc.Request) (any,
|
|||
if perRequestErr != nil {
|
||||
return nil, perRequestErr
|
||||
}
|
||||
// server/discover is exempt from the version-validation gate so that a
|
||||
// client probing with a version the server doesn't implement can still
|
||||
// learn the server's supported versions from the response (whether the
|
||||
// handler returns a DiscoverResult or an UnsupportedProtocolVersionError
|
||||
// with a Data.Supported payload).
|
||||
if validatedMeta.usesNewProtocol &&
|
||||
!slices.Contains(supportedProtocolVersions, validatedMeta.initializeParams.ProtocolVersion) {
|
||||
data, _ := json.Marshal(UnsupportedProtocolVersionData{
|
||||
Supported: supportedProtocolVersions,
|
||||
Requested: validatedMeta.initializeParams.ProtocolVersion,
|
||||
})
|
||||
return nil, &jsonrpc.Error{
|
||||
Code: CodeUnsupportedProtocolVersion,
|
||||
Message: "unsupported protocol version",
|
||||
Data: data,
|
||||
}
|
||||
}
|
||||
|
||||
switch req.Method {
|
||||
case methodInitialize, methodPing, notificationInitialized, methodSetLevel:
|
||||
|
|
|
|||
Loading…
Reference in a new issue