mcp: Remove resumability and introduce MissingRequiredClientCapability error data (#1005)

Fixes #966
This commit is contained in:
Guglielmo Colombo 2026-06-15 09:47:42 +02:00 • committed by GitHub
commit 4dc99b48e1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 31 additions and 5 deletions

View file

@ -2116,3 +2116,18 @@ type UnsupportedProtocolVersionData struct {
// Requested is the protocol version the client asked for.
Requested string `json:"requested"`
}
// MissingRequiredClientCapabilityData is the SEP-2575 payload carried in the
// `data` field of a JSON-RPC error response with code
// [CodeMissingRequiredClientCapabilities]. The server uses it to indicate
// which client capabilities are required to process the request but were not
// declared by the client in its per-request `_meta` field.
//
// Handlers that require a specific client capability should inspect the
// per-request [ServerRequest.ClientCapabilities] and return a JSON-RPC error
// populated with this structure when the required capability is missing.
type MissingRequiredClientCapabilityData struct {
// RequiredCapabilities is the set of capabilities the server requires
// from the client to process the request.
RequiredCapabilities *ClientCapabilities `json:"requiredCapabilities"`
}

View file

@ -585,6 +585,8 @@ type RequestExtra struct {
// to configure the reconnection delay.
//
// [SEP-1699]: https://github.com/modelcontextprotocol/modelcontextprotocol/issues/1699
// This mechanism is deprecated in protocol version 2026-06-30 as the resumability
// feature is removed.
CloseSSEStream func(CloseSSEStreamArgs)
}

View file

@ -1059,7 +1059,7 @@ func (s *stream) doneLocked() bool {
}
func (c *streamableServerConn) newStream(ctx context.Context, requests map[jsonrpc.ID]struct{}, id string) (*stream, error) {
if c.eventStore != nil {
if c.eventStore != nil && protocolVersionFromContext(ctx) < protocolVersion20260630 {
if err := c.eventStore.Open(ctx, c.sessionID, id); err != nil {
return nil, err
}
@ -1366,14 +1366,13 @@ func (c *streamableServerConn) servePOST(w http.ResponseWriter, req *http.Reques
tokenInfo := auth.TokenInfoFromContext(req.Context())
isInitialize := false
var initializeProtocolVersion string
headerVersion := protocolVersionFromContext(req.Context())
for _, msg := range incoming {
if jreq, ok := msg.(*jsonrpc.Request); ok {
// Preemptively check that this is a valid request, so that we can fail
// the HTTP request. If we didn't do this, a request with a bad method or
// missing ID could be silently swallowed.
if _, err := checkRequest(jreq, serverMethodInfos); err != nil {
if headerVersion >= protocolVersion20260630 && errors.Is(err, jsonrpc2.ErrNotHandled) && jreq.IsCall() {
if protocolVersion >= protocolVersion20260630 && errors.Is(err, jsonrpc2.ErrNotHandled) && jreq.IsCall() {
writeJSONRPCError(w, http.StatusNotFound, jreq.ID, &jsonrpc.Error{
Code: jsonrpc.CodeMethodNotFound,
Message: err.Error(),
@ -1403,6 +1402,9 @@ func (c *streamableServerConn) servePOST(w http.ResponseWriter, req *http.Reques
metaVersion, _ = meta[MetaKeyProtocolVersion].(string)
}
if protocolVersion >= protocolVersion20260630 || metaVersion != "" {
// Extract again the protcol version from the context to see what the client
// is advertising in the Mcp-Protocol-Version HTTP header.
headerVersion := protocolVersionFromContext(req.Context())
// server/discover is exempt from the stateful
// rejection as it should learn about the supported protocols from the
// DiscoverResult response.
@ -1452,6 +1454,12 @@ func (c *streamableServerConn) servePOST(w http.ResponseWriter, req *http.Reques
// See the doc for CloseSSEStream: allow the request handler to
// explicitly close the ongoing stream.
jreq.Extra.(*RequestExtra).CloseSSEStream = func(args CloseSSEStreamArgs) {
// This mechanism was designed to trigger client reconnection with
// Last-Event-ID for server-initiated disconnect scenarios. It is
// deprecated in protocol version 2026-06-30.
if protocolVersion >= protocolVersion20260630 {
return
}
c.mu.Lock()
streamID, ok := c.requestStreams[jreq.ID]
var stream *stream
@ -1729,7 +1737,8 @@ func (c *streamableServerConn) Write(ctx context.Context, msg jsonrpc.Message) e
// pushing down into the delivery layer.
delivered := false
var errs []error
if c.eventStore != nil {
protocolVersion := protocolVersionFromContext(ctx)
if c.eventStore != nil && protocolVersion < protocolVersion20260630 {
if err := c.eventStore.Append(ctx, c.sessionID, s.id, data); err != nil {
errs = append(errs, err)
} else {
@ -1740,7 +1749,7 @@ func (c *streamableServerConn) Write(ctx context.Context, msg jsonrpc.Message) e
// Compute eventID for SSE streams with event store.
// Use s.lastIdx + 1 because deliverLocked increments before writing.
var eventID string
if c.eventStore != nil {
if c.eventStore != nil && protocolVersion < protocolVersion20260630 {
eventID = formatEventID(s.id, s.lastIdx+1)
}