Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
2fca1b4
feat: implement sessionless protocol support via per-request _meta va…
guglielmo-san May 19, 2026
98b2a44
fix: correctly report MethodNotFound error codes in new-protocol requ…
guglielmo-san May 19, 2026
222d145
refactor: remove legacy stateless session handling logic and associat…
guglielmo-san May 19, 2026
4beb079
feat: enforce SEP-2575 protocol version header validation and restric…
guglielmo-san May 20, 2026
23b804b
test: add required Mcp-Method and Mcp-Name headers to streamable inte…
guglielmo-san May 20, 2026
113cc9f
fix: reject initialize, ping, and notifications/initialized methods i…
guglielmo-san May 20, 2026
85a36ed
docs: simplify protocol version requirement comment in streamable.go
guglielmo-san May 20, 2026
aecfae4
docs: update validateRequestMeta comment grammar to present tense
guglielmo-san May 20, 2026
69a549f
refactor: update protocol version retrieval to use context instead of…
guglielmo-san May 20, 2026
67233a6
refactor: add isNil interface method to all param structs and update …
guglielmo-san May 21, 2026
52828cd
refactor: remove redundant meta field from validatedMeta and simplify…
guglielmo-san May 21, 2026
ded4e45
test: update streamable handler tests to inject required client metad…
guglielmo-san May 21, 2026
aba9529
style: align whitespace in isParams method declarations for consisten…
guglielmo-san May 21, 2026
b1a06ba
feat: extract and persist initialize params from new protocol request…
guglielmo-san May 21, 2026
2e2a116
refactor: rename usesNewProtocol variable to validatedMeta for clarit…
guglielmo-san May 21, 2026
8b572a6
refactor: update ServerSessionState using thread-safe updateState hel…
guglielmo-san May 21, 2026
6f1eba0
fix: prevent redundant initialization of server session state when al…
guglielmo-san May 22, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 49 additions & 7 deletions mcp/protocol.go
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,12 @@ func (x *CallToolResult) UnmarshalJSON(data []byte) error {
}

func (x *CallToolParams) isParams() {}
func (x *CallToolParams) isNil() bool { return x == nil }
func (x *CallToolParams) GetProgressToken() any { return getProgressToken(x) }
func (x *CallToolParams) SetProgressToken(t any) { setProgressToken(x, t) }

func (x *CallToolParamsRaw) isParams() {}
func (x *CallToolParamsRaw) isNil() bool { return x == nil }
func (x *CallToolParamsRaw) GetProgressToken() any { return getProgressToken(x) }
func (x *CallToolParamsRaw) SetProgressToken(t any) { setProgressToken(x, t) }

Expand All @@ -187,6 +189,7 @@ type CancelledParams struct {
}

func (x *CancelledParams) isParams() {}
func (x *CancelledParams) isNil() bool { return x == nil }
func (x *CancelledParams) GetProgressToken() any { return getProgressToken(x) }
func (x *CancelledParams) SetProgressToken(t any) { setProgressToken(x, t) }

Expand Down Expand Up @@ -374,7 +377,8 @@ type CompleteParams struct {
Ref *CompleteReference `json:"ref"`
}

func (*CompleteParams) isParams() {}
func (x *CompleteParams) isParams() {}
func (x *CompleteParams) isNil() bool { return x == nil }

type CompletionResultDetails struct {
HasMore bool `json:"hasMore,omitempty"`
Expand Down Expand Up @@ -422,6 +426,7 @@ type CreateMessageParams struct {
}

func (x *CreateMessageParams) isParams() {}
func (x *CreateMessageParams) isNil() bool { return x == nil }
func (x *CreateMessageParams) GetProgressToken() any { return getProgressToken(x) }
func (x *CreateMessageParams) SetProgressToken(t any) { setProgressToken(x, t) }

Expand All @@ -448,6 +453,7 @@ type CreateMessageWithToolsParams struct {
}

func (x *CreateMessageWithToolsParams) isParams() {}
func (x *CreateMessageWithToolsParams) isNil() bool { return x == nil }
func (x *CreateMessageWithToolsParams) GetProgressToken() any { return getProgressToken(x) }
func (x *CreateMessageWithToolsParams) SetProgressToken(t any) { setProgressToken(x, t) }

Expand Down Expand Up @@ -654,6 +660,7 @@ type GetPromptParams struct {
}

func (x *GetPromptParams) isParams() {}
func (x *GetPromptParams) isNil() bool { return x == nil }
func (x *GetPromptParams) GetProgressToken() any { return getProgressToken(x) }
func (x *GetPromptParams) SetProgressToken(t any) { setProgressToken(x, t) }

Expand Down Expand Up @@ -706,6 +713,7 @@ func (p *initializeParamsV2) toV1() *InitializeParams {
}

func (x *InitializeParams) isParams() {}
func (x *InitializeParams) isNil() bool { return x == nil }
func (x *InitializeParams) GetProgressToken() any { return getProgressToken(x) }
func (x *InitializeParams) SetProgressToken(t any) { setProgressToken(x, t) }

Expand Down Expand Up @@ -739,6 +747,7 @@ type InitializedParams struct {
}

func (x *InitializedParams) isParams() {}
func (x *InitializedParams) isNil() bool { return x == nil }
func (x *InitializedParams) GetProgressToken() any { return getProgressToken(x) }
func (x *InitializedParams) SetProgressToken(t any) { setProgressToken(x, t) }

Expand All @@ -752,6 +761,7 @@ type ListPromptsParams struct {
}

func (x *ListPromptsParams) isParams() {}
func (x *ListPromptsParams) isNil() bool { return x == nil }
func (x *ListPromptsParams) GetProgressToken() any { return getProgressToken(x) }
func (x *ListPromptsParams) SetProgressToken(t any) { setProgressToken(x, t) }
func (x *ListPromptsParams) cursorPtr() *string { return &x.Cursor }
Expand Down Expand Up @@ -780,6 +790,7 @@ type ListResourceTemplatesParams struct {
}

func (x *ListResourceTemplatesParams) isParams() {}
func (x *ListResourceTemplatesParams) isNil() bool { return x == nil }
func (x *ListResourceTemplatesParams) GetProgressToken() any { return getProgressToken(x) }
func (x *ListResourceTemplatesParams) SetProgressToken(t any) { setProgressToken(x, t) }
func (x *ListResourceTemplatesParams) cursorPtr() *string { return &x.Cursor }
Expand Down Expand Up @@ -808,6 +819,7 @@ type ListResourcesParams struct {
}

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

func (x *ListRootsParams) isParams() {}
func (x *ListRootsParams) isNil() bool { return x == nil }
func (x *ListRootsParams) GetProgressToken() any { return getProgressToken(x) }
func (x *ListRootsParams) SetProgressToken(t any) { setProgressToken(x, t) }

Expand All @@ -858,6 +871,7 @@ type ListToolsParams struct {
}

func (x *ListToolsParams) isParams() {}
func (x *ListToolsParams) isNil() bool { return x == nil }
func (x *ListToolsParams) GetProgressToken() any { return getProgressToken(x) }
func (x *ListToolsParams) SetProgressToken(t any) { setProgressToken(x, t) }
func (x *ListToolsParams) cursorPtr() *string { return &x.Cursor }
Expand Down Expand Up @@ -896,6 +910,7 @@ type LoggingMessageParams struct {
}

func (x *LoggingMessageParams) isParams() {}
func (x *LoggingMessageParams) isNil() bool { return x == nil }
func (x *LoggingMessageParams) GetProgressToken() any { return getProgressToken(x) }
func (x *LoggingMessageParams) SetProgressToken(t any) { setProgressToken(x, t) }

Expand Down Expand Up @@ -958,6 +973,7 @@ type PingParams struct {
}

func (x *PingParams) isParams() {}
func (x *PingParams) isNil() bool { return x == nil }
func (x *PingParams) GetProgressToken() any { return getProgressToken(x) }
func (x *PingParams) SetProgressToken(t any) { setProgressToken(x, t) }

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

func (*ProgressNotificationParams) isParams() {}
func (x *ProgressNotificationParams) isParams() {}
func (x *ProgressNotificationParams) isNil() bool { return x == nil }

// IconTheme specifies the theme an icon is designed for.
type IconTheme string
Expand Down Expand Up @@ -1048,6 +1065,7 @@ type PromptListChangedParams struct {
}

func (x *PromptListChangedParams) isParams() {}
func (x *PromptListChangedParams) isNil() bool { return x == nil }
func (x *PromptListChangedParams) GetProgressToken() any { return getProgressToken(x) }
func (x *PromptListChangedParams) SetProgressToken(t any) { setProgressToken(x, t) }

Expand Down Expand Up @@ -1089,6 +1107,7 @@ type ReadResourceParams struct {
}

func (x *ReadResourceParams) isParams() {}
func (x *ReadResourceParams) isNil() bool { return x == nil }
func (x *ReadResourceParams) GetProgressToken() any { return getProgressToken(x) }
func (x *ReadResourceParams) SetProgressToken(t any) { setProgressToken(x, t) }

Expand Down Expand Up @@ -1145,6 +1164,7 @@ type ResourceListChangedParams struct {
}

func (x *ResourceListChangedParams) isParams() {}
func (x *ResourceListChangedParams) isNil() bool { return x == nil }
func (x *ResourceListChangedParams) GetProgressToken() any { return getProgressToken(x) }
func (x *ResourceListChangedParams) SetProgressToken(t any) { setProgressToken(x, t) }

Expand Down Expand Up @@ -1205,6 +1225,7 @@ type RootsListChangedParams struct {
}

func (x *RootsListChangedParams) isParams() {}
func (x *RootsListChangedParams) isNil() bool { return x == nil }
func (x *RootsListChangedParams) GetProgressToken() any { return getProgressToken(x) }
func (x *RootsListChangedParams) SetProgressToken(t any) { setProgressToken(x, t) }

Expand Down Expand Up @@ -1288,6 +1309,7 @@ type SetLoggingLevelParams struct {
}

func (x *SetLoggingLevelParams) isParams() {}
func (x *SetLoggingLevelParams) isNil() bool { return x == nil }
func (x *SetLoggingLevelParams) GetProgressToken() any { return getProgressToken(x) }
func (x *SetLoggingLevelParams) SetProgressToken(t any) { setProgressToken(x, t) }

Expand Down Expand Up @@ -1390,6 +1412,7 @@ type ToolListChangedParams struct {
}

func (x *ToolListChangedParams) isParams() {}
func (x *ToolListChangedParams) isNil() bool { return x == nil }
func (x *ToolListChangedParams) GetProgressToken() any { return getProgressToken(x) }
func (x *ToolListChangedParams) SetProgressToken(t any) { setProgressToken(x, t) }

Expand All @@ -1403,7 +1426,8 @@ type SubscribeParams struct {
URI string `json:"uri"`
}

func (*SubscribeParams) isParams() {}
func (x *SubscribeParams) isParams() {}
func (x *SubscribeParams) isNil() bool { return x == nil }

// Sent from the client to request cancellation of resources/updated
// notifications from the server. This should follow a previous
Expand All @@ -1416,7 +1440,8 @@ type UnsubscribeParams struct {
URI string `json:"uri"`
}

func (*UnsubscribeParams) isParams() {}
func (x *UnsubscribeParams) isParams() {}
func (x *UnsubscribeParams) isNil() bool { return x == nil }

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

func (*ResourceUpdatedNotificationParams) isParams() {}
func (x *ResourceUpdatedNotificationParams) isParams() {}
func (x *ResourceUpdatedNotificationParams) isNil() bool { return x == nil }

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

Expand Down Expand Up @@ -1468,7 +1494,8 @@ type ElicitParams struct {
ElicitationID string `json:"elicitationId,omitempty"`
}

func (x *ElicitParams) isParams() {}
func (x *ElicitParams) isParams() {}
func (x *ElicitParams) isNil() bool { return x == nil }

func (x *ElicitParams) GetProgressToken() any { return getProgressToken(x) }
func (x *ElicitParams) SetProgressToken(t any) { setProgressToken(x, t) }
Expand Down Expand Up @@ -1500,7 +1527,8 @@ type ElicitationCompleteParams struct {
ElicitationID string `json:"elicitationId"`
}

func (*ElicitationCompleteParams) isParams() {}
func (x *ElicitationCompleteParams) isParams() {}
func (x *ElicitationCompleteParams) isNil() bool { return x == nil }

// An Implementation describes the name and version of an MCP implementation, with an optional
// title for UI representation.
Expand Down Expand Up @@ -1630,3 +1658,17 @@ const (
notificationToolListChanged = "notifications/tools/list_changed"
methodUnsubscribe = "resources/unsubscribe"
)

// Per-request _meta field names for the >= 2026-06-30 protocol version.
//
// These keys appear inside a Params._meta map and carry information that
// previously came from the initialization handshake (SEP-2575).
const (
// MetaKeyProtocolVersion identifies the MCP protocol version that the
// request follows.
MetaKeyProtocolVersion = "io.modelcontextprotocol/protocolVersion"
// MetaKeyClientInfo carries the client's [Implementation].
MetaKeyClientInfo = "io.modelcontextprotocol/clientInfo"
// MetaKeyClientCapabilities carries the client's [ClientCapabilities].
MetaKeyClientCapabilities = "io.modelcontextprotocol/clientCapabilities"
)
27 changes: 23 additions & 4 deletions mcp/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -1450,13 +1450,32 @@ func (ss *ServerSession) handle(ctx context.Context, req *jsonrpc.Request) (any,
initialized := ss.state.InitializeParams != nil
ss.mu.Unlock()

// From the spec:
// "The client SHOULD NOT send requests other than pings before the server
// has responded to the initialize request."
// Per-request protocol detection (SEP-2575): if the request carries
// `io.modelcontextprotocol/protocolVersion` in its `_meta` field, it
// follows the new sessionless protocol. The initialization gate is
// skipped for such requests.
validatedMeta, perRequestErr := validateRequestMeta(req)
if perRequestErr != nil {
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 {
ss.server.opts.Logger.Error("method removed in the new protocol", "method", req.Method)
return nil, &jsonrpc.Error{
Code: jsonrpc.CodeMethodNotFound,
Message: fmt.Sprintf("%q is not supported in the new protocol", req.Method),
}
}
default:
if !initialized {
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)
}
Expand Down
Loading
Loading