Skip to content

Commit 4cbdd6a

Browse files
mcp: Implement stateless server (SEP-2275) (#965)
## Description This PR lays the foundational server-side groundwork for the `>= 2026-06-30` sessionless and stateless feature introduced by [SEP-2575](modelcontextprotocol/modelcontextprotocol#2575) and SEP-2567, tracked in [design/stateless.md](https://github.com/modelcontextprotocol/go-sdk/blob/614460a2253e7772eff6c78f142bfa0428530dc5/design/stateless.md). ### SEP-2575: Stateless MCP * **Per-request protocol detection in `ServerSession.handle()`** Unmarshal `_meta` from the raw JSON-RPC params determines whether each request follows the new protocol. * **Per-request typed accessors on `ServerRequest[P]`** ```go func (r *ServerRequest[P]) ProtocolVersion() string func (r *ServerRequest[P]) ClientInfo() *Implementation func (r *ServerRequest[P]) ClientCapabilities() *ClientCapabilities ``` * **Reject client->server `initialize`, `initialized` and `ping` for new-protocol requests** * **Per-request `_meta` field name constants** Three constants for the wire-protocol field names (`MetaKeyProtocolVersion`, `MetaKeyClientInfo`, `MetaKeyClientCapabilities`) * **Stop synthesizing fake `InitializeParams` for new-protocol requests** Fixes: #966
1 parent c044cdb commit 4cbdd6a

7 files changed

Lines changed: 1113 additions & 62 deletions

File tree

mcp/protocol.go

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -165,10 +165,12 @@ func (x *CallToolResult) UnmarshalJSON(data []byte) error {
165165
}
166166

167167
func (x *CallToolParams) isParams() {}
168+
func (x *CallToolParams) isNil() bool { return x == nil }
168169
func (x *CallToolParams) GetProgressToken() any { return getProgressToken(x) }
169170
func (x *CallToolParams) SetProgressToken(t any) { setProgressToken(x, t) }
170171

171172
func (x *CallToolParamsRaw) isParams() {}
173+
func (x *CallToolParamsRaw) isNil() bool { return x == nil }
172174
func (x *CallToolParamsRaw) GetProgressToken() any { return getProgressToken(x) }
173175
func (x *CallToolParamsRaw) SetProgressToken(t any) { setProgressToken(x, t) }
174176

@@ -187,6 +189,7 @@ type CancelledParams struct {
187189
}
188190

189191
func (x *CancelledParams) isParams() {}
192+
func (x *CancelledParams) isNil() bool { return x == nil }
190193
func (x *CancelledParams) GetProgressToken() any { return getProgressToken(x) }
191194
func (x *CancelledParams) SetProgressToken(t any) { setProgressToken(x, t) }
192195

@@ -374,7 +377,8 @@ type CompleteParams struct {
374377
Ref *CompleteReference `json:"ref"`
375378
}
376379

377-
func (*CompleteParams) isParams() {}
380+
func (x *CompleteParams) isParams() {}
381+
func (x *CompleteParams) isNil() bool { return x == nil }
378382

379383
type CompletionResultDetails struct {
380384
HasMore bool `json:"hasMore,omitempty"`
@@ -422,6 +426,7 @@ type CreateMessageParams struct {
422426
}
423427

424428
func (x *CreateMessageParams) isParams() {}
429+
func (x *CreateMessageParams) isNil() bool { return x == nil }
425430
func (x *CreateMessageParams) GetProgressToken() any { return getProgressToken(x) }
426431
func (x *CreateMessageParams) SetProgressToken(t any) { setProgressToken(x, t) }
427432

@@ -448,6 +453,7 @@ type CreateMessageWithToolsParams struct {
448453
}
449454

450455
func (x *CreateMessageWithToolsParams) isParams() {}
456+
func (x *CreateMessageWithToolsParams) isNil() bool { return x == nil }
451457
func (x *CreateMessageWithToolsParams) GetProgressToken() any { return getProgressToken(x) }
452458
func (x *CreateMessageWithToolsParams) SetProgressToken(t any) { setProgressToken(x, t) }
453459

@@ -654,6 +660,7 @@ type GetPromptParams struct {
654660
}
655661

656662
func (x *GetPromptParams) isParams() {}
663+
func (x *GetPromptParams) isNil() bool { return x == nil }
657664
func (x *GetPromptParams) GetProgressToken() any { return getProgressToken(x) }
658665
func (x *GetPromptParams) SetProgressToken(t any) { setProgressToken(x, t) }
659666

@@ -706,6 +713,7 @@ func (p *initializeParamsV2) toV1() *InitializeParams {
706713
}
707714

708715
func (x *InitializeParams) isParams() {}
716+
func (x *InitializeParams) isNil() bool { return x == nil }
709717
func (x *InitializeParams) GetProgressToken() any { return getProgressToken(x) }
710718
func (x *InitializeParams) SetProgressToken(t any) { setProgressToken(x, t) }
711719

@@ -739,6 +747,7 @@ type InitializedParams struct {
739747
}
740748

741749
func (x *InitializedParams) isParams() {}
750+
func (x *InitializedParams) isNil() bool { return x == nil }
742751
func (x *InitializedParams) GetProgressToken() any { return getProgressToken(x) }
743752
func (x *InitializedParams) SetProgressToken(t any) { setProgressToken(x, t) }
744753

@@ -752,6 +761,7 @@ type ListPromptsParams struct {
752761
}
753762

754763
func (x *ListPromptsParams) isParams() {}
764+
func (x *ListPromptsParams) isNil() bool { return x == nil }
755765
func (x *ListPromptsParams) GetProgressToken() any { return getProgressToken(x) }
756766
func (x *ListPromptsParams) SetProgressToken(t any) { setProgressToken(x, t) }
757767
func (x *ListPromptsParams) cursorPtr() *string { return &x.Cursor }
@@ -780,6 +790,7 @@ type ListResourceTemplatesParams struct {
780790
}
781791

782792
func (x *ListResourceTemplatesParams) isParams() {}
793+
func (x *ListResourceTemplatesParams) isNil() bool { return x == nil }
783794
func (x *ListResourceTemplatesParams) GetProgressToken() any { return getProgressToken(x) }
784795
func (x *ListResourceTemplatesParams) SetProgressToken(t any) { setProgressToken(x, t) }
785796
func (x *ListResourceTemplatesParams) cursorPtr() *string { return &x.Cursor }
@@ -808,6 +819,7 @@ type ListResourcesParams struct {
808819
}
809820

810821
func (x *ListResourcesParams) isParams() {}
822+
func (x *ListResourcesParams) isNil() bool { return x == nil }
811823
func (x *ListResourcesParams) GetProgressToken() any { return getProgressToken(x) }
812824
func (x *ListResourcesParams) SetProgressToken(t any) { setProgressToken(x, t) }
813825
func (x *ListResourcesParams) cursorPtr() *string { return &x.Cursor }
@@ -833,6 +845,7 @@ type ListRootsParams struct {
833845
}
834846

835847
func (x *ListRootsParams) isParams() {}
848+
func (x *ListRootsParams) isNil() bool { return x == nil }
836849
func (x *ListRootsParams) GetProgressToken() any { return getProgressToken(x) }
837850
func (x *ListRootsParams) SetProgressToken(t any) { setProgressToken(x, t) }
838851

@@ -858,6 +871,7 @@ type ListToolsParams struct {
858871
}
859872

860873
func (x *ListToolsParams) isParams() {}
874+
func (x *ListToolsParams) isNil() bool { return x == nil }
861875
func (x *ListToolsParams) GetProgressToken() any { return getProgressToken(x) }
862876
func (x *ListToolsParams) SetProgressToken(t any) { setProgressToken(x, t) }
863877
func (x *ListToolsParams) cursorPtr() *string { return &x.Cursor }
@@ -896,6 +910,7 @@ type LoggingMessageParams struct {
896910
}
897911

898912
func (x *LoggingMessageParams) isParams() {}
913+
func (x *LoggingMessageParams) isNil() bool { return x == nil }
899914
func (x *LoggingMessageParams) GetProgressToken() any { return getProgressToken(x) }
900915
func (x *LoggingMessageParams) SetProgressToken(t any) { setProgressToken(x, t) }
901916

@@ -958,6 +973,7 @@ type PingParams struct {
958973
}
959974

960975
func (x *PingParams) isParams() {}
976+
func (x *PingParams) isNil() bool { return x == nil }
961977
func (x *PingParams) GetProgressToken() any { return getProgressToken(x) }
962978
func (x *PingParams) SetProgressToken(t any) { setProgressToken(x, t) }
963979

@@ -978,7 +994,8 @@ type ProgressNotificationParams struct {
978994
Total float64 `json:"total,omitempty"`
979995
}
980996

981-
func (*ProgressNotificationParams) isParams() {}
997+
func (x *ProgressNotificationParams) isParams() {}
998+
func (x *ProgressNotificationParams) isNil() bool { return x == nil }
982999

9831000
// IconTheme specifies the theme an icon is designed for.
9841001
type IconTheme string
@@ -1048,6 +1065,7 @@ type PromptListChangedParams struct {
10481065
}
10491066

10501067
func (x *PromptListChangedParams) isParams() {}
1068+
func (x *PromptListChangedParams) isNil() bool { return x == nil }
10511069
func (x *PromptListChangedParams) GetProgressToken() any { return getProgressToken(x) }
10521070
func (x *PromptListChangedParams) SetProgressToken(t any) { setProgressToken(x, t) }
10531071

@@ -1089,6 +1107,7 @@ type ReadResourceParams struct {
10891107
}
10901108

10911109
func (x *ReadResourceParams) isParams() {}
1110+
func (x *ReadResourceParams) isNil() bool { return x == nil }
10921111
func (x *ReadResourceParams) GetProgressToken() any { return getProgressToken(x) }
10931112
func (x *ReadResourceParams) SetProgressToken(t any) { setProgressToken(x, t) }
10941113

@@ -1145,6 +1164,7 @@ type ResourceListChangedParams struct {
11451164
}
11461165

11471166
func (x *ResourceListChangedParams) isParams() {}
1167+
func (x *ResourceListChangedParams) isNil() bool { return x == nil }
11481168
func (x *ResourceListChangedParams) GetProgressToken() any { return getProgressToken(x) }
11491169
func (x *ResourceListChangedParams) SetProgressToken(t any) { setProgressToken(x, t) }
11501170

@@ -1205,6 +1225,7 @@ type RootsListChangedParams struct {
12051225
}
12061226

12071227
func (x *RootsListChangedParams) isParams() {}
1228+
func (x *RootsListChangedParams) isNil() bool { return x == nil }
12081229
func (x *RootsListChangedParams) GetProgressToken() any { return getProgressToken(x) }
12091230
func (x *RootsListChangedParams) SetProgressToken(t any) { setProgressToken(x, t) }
12101231

@@ -1288,6 +1309,7 @@ type SetLoggingLevelParams struct {
12881309
}
12891310

12901311
func (x *SetLoggingLevelParams) isParams() {}
1312+
func (x *SetLoggingLevelParams) isNil() bool { return x == nil }
12911313
func (x *SetLoggingLevelParams) GetProgressToken() any { return getProgressToken(x) }
12921314
func (x *SetLoggingLevelParams) SetProgressToken(t any) { setProgressToken(x, t) }
12931315

@@ -1416,6 +1438,7 @@ type ToolListChangedParams struct {
14161438
}
14171439

14181440
func (x *ToolListChangedParams) isParams() {}
1441+
func (x *ToolListChangedParams) isNil() bool { return x == nil }
14191442
func (x *ToolListChangedParams) GetProgressToken() any { return getProgressToken(x) }
14201443
func (x *ToolListChangedParams) SetProgressToken(t any) { setProgressToken(x, t) }
14211444

@@ -1429,7 +1452,8 @@ type SubscribeParams struct {
14291452
URI string `json:"uri"`
14301453
}
14311454

1432-
func (*SubscribeParams) isParams() {}
1455+
func (x *SubscribeParams) isParams() {}
1456+
func (x *SubscribeParams) isNil() bool { return x == nil }
14331457

14341458
// Sent from the client to request cancellation of resources/updated
14351459
// notifications from the server. This should follow a previous
@@ -1442,7 +1466,8 @@ type UnsubscribeParams struct {
14421466
URI string `json:"uri"`
14431467
}
14441468

1445-
func (*UnsubscribeParams) isParams() {}
1469+
func (x *UnsubscribeParams) isParams() {}
1470+
func (x *UnsubscribeParams) isNil() bool { return x == nil }
14461471

14471472
// A notification from the server to the client, informing it that a resource
14481473
// has changed and may need to be read again. This should only be sent if the
@@ -1455,7 +1480,8 @@ type ResourceUpdatedNotificationParams struct {
14551480
URI string `json:"uri"`
14561481
}
14571482

1458-
func (*ResourceUpdatedNotificationParams) isParams() {}
1483+
func (x *ResourceUpdatedNotificationParams) isParams() {}
1484+
func (x *ResourceUpdatedNotificationParams) isNil() bool { return x == nil }
14591485

14601486
// TODO(jba): add CompleteRequest and related types.
14611487

@@ -1494,7 +1520,8 @@ type ElicitParams struct {
14941520
ElicitationID string `json:"elicitationId,omitempty"`
14951521
}
14961522

1497-
func (x *ElicitParams) isParams() {}
1523+
func (x *ElicitParams) isParams() {}
1524+
func (x *ElicitParams) isNil() bool { return x == nil }
14981525

14991526
func (x *ElicitParams) GetProgressToken() any { return getProgressToken(x) }
15001527
func (x *ElicitParams) SetProgressToken(t any) { setProgressToken(x, t) }
@@ -1526,7 +1553,8 @@ type ElicitationCompleteParams struct {
15261553
ElicitationID string `json:"elicitationId"`
15271554
}
15281555

1529-
func (*ElicitationCompleteParams) isParams() {}
1556+
func (x *ElicitationCompleteParams) isParams() {}
1557+
func (x *ElicitationCompleteParams) isNil() bool { return x == nil }
15301558

15311559
// An Implementation describes the name and version of an MCP implementation, with an optional
15321560
// title for UI representation.
@@ -1656,3 +1684,17 @@ const (
16561684
notificationToolListChanged = "notifications/tools/list_changed"
16571685
methodUnsubscribe = "resources/unsubscribe"
16581686
)
1687+
1688+
// Per-request _meta field names for the >= 2026-06-30 protocol version.
1689+
//
1690+
// These keys appear inside a Params._meta map and carry information that
1691+
// previously came from the initialization handshake (SEP-2575).
1692+
const (
1693+
// MetaKeyProtocolVersion identifies the MCP protocol version that the
1694+
// request follows.
1695+
MetaKeyProtocolVersion = "io.modelcontextprotocol/protocolVersion"
1696+
// MetaKeyClientInfo carries the client's [Implementation].
1697+
MetaKeyClientInfo = "io.modelcontextprotocol/clientInfo"
1698+
// MetaKeyClientCapabilities carries the client's [ClientCapabilities].
1699+
MetaKeyClientCapabilities = "io.modelcontextprotocol/clientCapabilities"
1700+
)

mcp/server.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1450,13 +1450,32 @@ func (ss *ServerSession) handle(ctx context.Context, req *jsonrpc.Request) (any,
14501450
initialized := ss.state.InitializeParams != nil
14511451
ss.mu.Unlock()
14521452

1453-
// From the spec:
1454-
// "The client SHOULD NOT send requests other than pings before the server
1455-
// has responded to the initialize request."
1453+
// Per-request protocol detection (SEP-2575): if the request carries
1454+
// `io.modelcontextprotocol/protocolVersion` in its `_meta` field, it
1455+
// follows the new sessionless protocol. The initialization gate is
1456+
// skipped for such requests.
1457+
validatedMeta, perRequestErr := validateRequestMeta(req)
1458+
if perRequestErr != nil {
1459+
return nil, perRequestErr
1460+
}
1461+
1462+
if !initialized && validatedMeta.usesNewProtocol && validatedMeta.initializeParams != nil {
1463+
ss.updateState(func(state *ServerSessionState) {
1464+
state.InitializeParams = validatedMeta.initializeParams
1465+
})
1466+
}
1467+
14561468
switch req.Method {
14571469
case methodInitialize, methodPing, notificationInitialized:
1470+
if validatedMeta.usesNewProtocol {
1471+
ss.server.opts.Logger.Error("method removed in the new protocol", "method", req.Method)
1472+
return nil, &jsonrpc.Error{
1473+
Code: jsonrpc.CodeMethodNotFound,
1474+
Message: fmt.Sprintf("%q is not supported in the new protocol", req.Method),
1475+
}
1476+
}
14581477
default:
1459-
if !initialized {
1478+
if !initialized && !validatedMeta.usesNewProtocol {
14601479
ss.server.opts.Logger.Error("method invalid during initialization", "method", req.Method)
14611480
return nil, fmt.Errorf("method %q is invalid during session initialization", req.Method)
14621481
}

0 commit comments

Comments
 (0)