Skip to content

Commit 7f4aa4a

Browse files
mcp: add 2026-07-28 to the supported protocol versions (#1020)
This PR adds the protocol version `2026-07-28` to the list of supported protocol versions and fixes legacy tests hardcoding the legacy protocol version for those use cases.
1 parent b9e6fc5 commit 7f4aa4a

17 files changed

Lines changed: 355 additions & 218 deletions

mcp/capabilities_test.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,12 @@ func TestServerListChangedNotifications(t *testing.T) {
2727
wantNotifyCount int64
2828
}{
2929
{
30-
name: "Default: notification sent",
31-
serverOpts: nil,
30+
name: "Default: notification sent",
31+
serverOpts: &ServerOptions{
32+
Capabilities: &ServerCapabilities{
33+
Tools: &ToolCapabilities{ListChanged: true},
34+
},
35+
},
3236
wantNotifyCount: 1,
3337
},
3438
{

mcp/client.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ func (c *Client) discover(ctx context.Context, cs *ClientSession) (*InitializeRe
403403
Meta: Meta{
404404
MetaKeyProtocolVersion: protocolVersion,
405405
MetaKeyClientInfo: c.impl,
406-
MetaKeyClientCapabilities: caps,
406+
MetaKeyClientCapabilities: caps.toV2(),
407407
},
408408
}
409409
req := &DiscoverRequest{Session: cs, Params: params}
@@ -518,7 +518,7 @@ func injectRequestMeta[T any, P interface {
518518
m[MetaKeyClientInfo] = cs.client.impl
519519
}
520520
if _, ok := m[MetaKeyClientCapabilities]; !ok {
521-
m[MetaKeyClientCapabilities] = cs.client.capabilities(res.ProtocolVersion)
521+
m[MetaKeyClientCapabilities] = cs.client.capabilities(res.ProtocolVersion).toV2()
522522
}
523523
params.SetMeta(m)
524524
return params

mcp/client_test.go

Lines changed: 18 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import (
1010
"context"
1111
"fmt"
1212
"log/slog"
13-
"slices"
1413
"sync/atomic"
1514
"testing"
1615

@@ -644,13 +643,6 @@ func TestClientCapabilitiesOverWire(t *testing.T) {
644643
// don't overlap with the SDK. The test then asserts the resulting session
645644
// state and whether the legacy initialize handshake ran.
646645
func TestClientConnectDiscover(t *testing.T) {
647-
// Temporarily enable 2026-07-28 support in the SDK for this test
648-
oldSupported := supportedProtocolVersions
649-
supportedProtocolVersions = append([]string{protocolVersion20260728}, supportedProtocolVersions...)
650-
t.Cleanup(func() {
651-
supportedProtocolVersions = oldSupported
652-
})
653-
654646
const otherVersionsOnly = "1999-01-01"
655647

656648
tests := []struct {
@@ -686,7 +678,7 @@ func TestClientConnectDiscover(t *testing.T) {
686678
return nil, jsonrpc2.ErrMethodNotFound
687679
},
688680
wantInitialize: true,
689-
wantVersion: latestProtocolVersion,
681+
wantVersion: protocolVersion20251125,
690682
},
691683
{
692684
name: "unsupported protocol version falls back to initialize",
@@ -697,7 +689,7 @@ func TestClientConnectDiscover(t *testing.T) {
697689
}
698690
},
699691
wantInitialize: true,
700-
wantVersion: latestProtocolVersion,
692+
wantVersion: protocolVersion20251125,
701693
},
702694
{
703695
name: "no overlapping supported version falls back to initialize",
@@ -709,7 +701,7 @@ func TestClientConnectDiscover(t *testing.T) {
709701
}, nil
710702
},
711703
wantInitialize: true,
712-
wantVersion: latestProtocolVersion,
704+
wantVersion: protocolVersion20251125,
713705
},
714706
}
715707

@@ -771,10 +763,6 @@ func TestClientConnectDiscover(t *testing.T) {
771763
// request sent by Client.Connect carries the SEP-2575 per-request _meta triple:
772764
// protocolVersion, clientInfo, and clientCapabilities.
773765
func TestClientConnectDiscover_RequestContents(t *testing.T) {
774-
orig := supportedProtocolVersions
775-
supportedProtocolVersions = append([]string{protocolVersion20260728}, slices.Clone(orig)...)
776-
t.Cleanup(func() { supportedProtocolVersions = orig })
777-
778766
ctx := context.Background()
779767

780768
type captured struct {
@@ -850,10 +838,6 @@ func TestClientConnectDiscover_RequestContents(t *testing.T) {
850838
func TestInMemory_E2E_DiscoverSuccess(t *testing.T) {
851839
ctx := context.Background()
852840

853-
orig := supportedProtocolVersions
854-
supportedProtocolVersions = append([]string{protocolVersion20260728}, slices.Clone(orig)...)
855-
t.Cleanup(func() { supportedProtocolVersions = orig })
856-
857841
server := NewServer(&Implementation{Name: "stdio-like-server", Version: "v1"}, nil)
858842
ct, st := NewInMemoryTransports()
859843
ss, err := server.Connect(ctx, st, nil)
@@ -889,15 +873,11 @@ func TestInMemory_E2E_DiscoverSuccess(t *testing.T) {
889873

890874
// TestInMemory_E2E_DiscoverFallback_NoOverlap verifies the fallback path
891875
// over an InMemory (STDIO-equivalent) transport: the client probes with
892-
// _meta.protocolVersion = 2026-07-28, but the server's supported list does
893-
// NOT include that version (the default for an SDK server that hasn't
894-
// shimmed supportedProtocolVersions).
876+
// _meta.protocolVersion = 2026-07-28, but the server overrides discover via
877+
// middleware to advertise only legacy versions, so the client must fall
878+
// back to the legacy initialize handshake.
895879
func TestInMemory_E2E_DiscoverFallback_NoOverlap(t *testing.T) {
896880
ctx := context.Background()
897-
orig := supportedProtocolVersions
898-
supportedProtocolVersions = append([]string{protocolVersion20260728}, slices.Clone(orig)...)
899-
t.Cleanup(func() { supportedProtocolVersions = orig })
900-
901881
server := NewServer(&Implementation{Name: "vpre-like-server", Version: "v1"}, nil)
902882
// Intercept discover and reply as if we were a server that only
903883
// supports legacy versions.
@@ -932,9 +912,11 @@ func TestInMemory_E2E_DiscoverFallback_NoOverlap(t *testing.T) {
932912
if ir == nil {
933913
t.Fatal("InitializeResult is nil after fallback initialize")
934914
}
935-
if ir.ProtocolVersion != latestProtocolVersion {
915+
// The fallback initialize explicitly requests protocolVersion20251125
916+
// (see client.go), so the server negotiates that version.
917+
if ir.ProtocolVersion != protocolVersion20251125 {
936918
t.Errorf("InitializeResult.ProtocolVersion = %q, want %q (legacy fallback after no-overlap discover)",
937-
ir.ProtocolVersion, latestProtocolVersion)
919+
ir.ProtocolVersion, protocolVersion20251125)
938920
}
939921

940922
// Prove the session is usable after fallback.
@@ -949,10 +931,6 @@ func TestInMemory_E2E_DiscoverFallback_NoOverlap(t *testing.T) {
949931
func TestInMemory_E2E_DiscoverFallback_MethodNotFound(t *testing.T) {
950932
ctx := context.Background()
951933

952-
orig := supportedProtocolVersions
953-
supportedProtocolVersions = append([]string{protocolVersion20260728}, slices.Clone(orig)...)
954-
t.Cleanup(func() { supportedProtocolVersions = orig })
955-
956934
server := NewServer(&Implementation{Name: "vpre-server", Version: "v1"}, nil)
957935
server.AddReceivingMiddleware(func(next MethodHandler) MethodHandler {
958936
return func(ctx context.Context, method string, req Request) (Result, error) {
@@ -981,9 +959,11 @@ func TestInMemory_E2E_DiscoverFallback_MethodNotFound(t *testing.T) {
981959
if ir == nil {
982960
t.Fatal("InitializeResult is nil after fallback initialize")
983961
}
984-
if ir.ProtocolVersion != latestProtocolVersion {
962+
// The fallback initialize explicitly requests protocolVersion20251125
963+
// (see client.go), so the server negotiates that version.
964+
if ir.ProtocolVersion != protocolVersion20251125 {
985965
t.Errorf("InitializeResult.ProtocolVersion = %q, want %q (legacy fallback after MethodNotFound)",
986-
ir.ProtocolVersion, latestProtocolVersion)
966+
ir.ProtocolVersion, protocolVersion20251125)
987967
}
988968

989969
if _, err := cs.ListTools(ctx, nil); err != nil {
@@ -998,10 +978,6 @@ func TestInMemory_E2E_DiscoverFallback_MethodNotFound(t *testing.T) {
998978
func TestInMemory_E2E_DiscoverFallback_UnsupportedProtocolVersion(t *testing.T) {
999979
ctx := context.Background()
1000980

1001-
orig := supportedProtocolVersions
1002-
supportedProtocolVersions = append([]string{protocolVersion20260728}, slices.Clone(orig)...)
1003-
t.Cleanup(func() { supportedProtocolVersions = orig })
1004-
1005981
server := NewServer(&Implementation{Name: "strict-server", Version: "v1"}, nil)
1006982
server.AddReceivingMiddleware(func(next MethodHandler) MethodHandler {
1007983
return func(ctx context.Context, method string, req Request) (Result, error) {
@@ -1033,9 +1009,11 @@ func TestInMemory_E2E_DiscoverFallback_UnsupportedProtocolVersion(t *testing.T)
10331009
if ir == nil {
10341010
t.Fatal("InitializeResult is nil after fallback initialize")
10351011
}
1036-
if ir.ProtocolVersion != latestProtocolVersion {
1012+
// The fallback initialize explicitly requests protocolVersion20251125
1013+
// (see client.go), so the server negotiates that version.
1014+
if ir.ProtocolVersion != protocolVersion20251125 {
10371015
t.Errorf("InitializeResult.ProtocolVersion = %q, want %q (legacy fallback after UnsupportedProtocolVersion)",
1038-
ir.ProtocolVersion, latestProtocolVersion)
1016+
ir.ProtocolVersion, protocolVersion20251125)
10391017
}
10401018
}
10411019

@@ -1045,10 +1023,6 @@ func TestInMemory_E2E_DiscoverFallback_UnsupportedProtocolVersion(t *testing.T)
10451023
// UnsupportedProtocolVersionError carries Data.Supported, and the client
10461024
// selects a mutually supported version from that list and retries.
10471025
func TestClientConnectDiscover_UnsupportedVersionNegotiation(t *testing.T) {
1048-
oldSupported := supportedProtocolVersions
1049-
supportedProtocolVersions = append([]string{protocolVersion20260728}, slices.Clone(oldSupported)...)
1050-
t.Cleanup(func() { supportedProtocolVersions = oldSupported })
1051-
10521026
ctx := context.Background()
10531027

10541028
const unsupportedClientVersion = "2099-12-31"

mcp/conformance_test.go

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,31 @@ func incTool(_ context.Context, _ *CallToolRequest, args incInput) (*CallToolRes
143143
return nil, incOutput{args.X + 1}, nil
144144
}
145145

146+
// confirmThenGreet exercises the multi-round-trip pattern in
147+
// conformance fixtures.
148+
func confirmThenGreet(_ context.Context, req *CallToolRequest, _ struct{}) (*CallToolResult, any, error) {
149+
if len(req.Params.InputResponses) == 0 {
150+
return &CallToolResult{
151+
InputRequests: InputRequestMap{
152+
"who": &ElicitParams{
153+
Message: "What is your name?",
154+
RequestedSchema: &jsonschema.Schema{
155+
Type: "object",
156+
Properties: map[string]*jsonschema.Schema{
157+
"name": {Type: "string"},
158+
},
159+
},
160+
},
161+
},
162+
RequestState: "step=1",
163+
}, nil, nil
164+
}
165+
name := req.Params.InputResponses["who"].(*ElicitResult).Content["name"].(string)
166+
return &CallToolResult{
167+
Content: []Content{&TextContent{Text: "hello " + name}},
168+
}, nil, nil
169+
}
170+
146171
var iconObj = Icon{
147172
Source: "foobar",
148173
MIMEType: "image/png",
@@ -193,6 +218,11 @@ func runServerTest(t *testing.T, test *conformanceTest) {
193218
Title: "contentTool",
194219
Description: "return resourceLink content with Icon",
195220
}, contentTool)
221+
case "confirmThenGreet":
222+
AddTool(s, &Tool{
223+
Name: "confirmThenGreet",
224+
Description: "ask the user for their name via elicitation, then greet them",
225+
}, confirmThenGreet)
196226
default:
197227
t.Fatalf("unknown tool %q", tn)
198228
}

mcp/mcp_example_test.go

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,13 @@ import (
1919
func Example_lifecycle() {
2020
ctx := context.Background()
2121

22-
// Create a client and server.
23-
// Wait for the client to initialize the session.
22+
// Create a client and server. Under protocol version 2026-07-28, there
23+
// is no dedicated initialize/initialized handshake: the session is
24+
// implicitly live the moment the client issues its first request.
2425
client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
25-
server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, &mcp.ServerOptions{
26-
InitializedHandler: func(context.Context, *mcp.InitializedRequest) {
27-
fmt.Println("initialized!")
28-
},
29-
})
26+
server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
3027

3128
// Connect the server and client using in-memory transports.
32-
//
33-
// Connect the server first so that it's ready to receive initialization
34-
// messages from the client.
3529
t1, t2 := mcp.NewInMemoryTransports()
3630
serverSession, err := server.Connect(ctx, t1, nil)
3731
if err != nil {
@@ -50,7 +44,7 @@ func Example_lifecycle() {
5044
if err := serverSession.Wait(); err != nil {
5145
log.Fatal(err)
5246
}
53-
// Output: initialized!
47+
// Output:
5448
}
5549

5650
// !-lifecycle

0 commit comments

Comments
 (0)