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.
This commit is contained in:
parent
b9e6fc5a66
commit
7f4aa4a0ce
17 changed files with 355 additions and 218 deletions
|
|
@ -27,8 +27,12 @@ func TestServerListChangedNotifications(t *testing.T) {
|
|||
wantNotifyCount int64
|
||||
}{
|
||||
{
|
||||
name: "Default: notification sent",
|
||||
serverOpts: nil,
|
||||
name: "Default: notification sent",
|
||||
serverOpts: &ServerOptions{
|
||||
Capabilities: &ServerCapabilities{
|
||||
Tools: &ToolCapabilities{ListChanged: true},
|
||||
},
|
||||
},
|
||||
wantNotifyCount: 1,
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -403,7 +403,7 @@ func (c *Client) discover(ctx context.Context, cs *ClientSession) (*InitializeRe
|
|||
Meta: Meta{
|
||||
MetaKeyProtocolVersion: protocolVersion,
|
||||
MetaKeyClientInfo: c.impl,
|
||||
MetaKeyClientCapabilities: caps,
|
||||
MetaKeyClientCapabilities: caps.toV2(),
|
||||
},
|
||||
}
|
||||
req := &DiscoverRequest{Session: cs, Params: params}
|
||||
|
|
@ -518,7 +518,7 @@ func injectRequestMeta[T any, P interface {
|
|||
m[MetaKeyClientInfo] = cs.client.impl
|
||||
}
|
||||
if _, ok := m[MetaKeyClientCapabilities]; !ok {
|
||||
m[MetaKeyClientCapabilities] = cs.client.capabilities(res.ProtocolVersion)
|
||||
m[MetaKeyClientCapabilities] = cs.client.capabilities(res.ProtocolVersion).toV2()
|
||||
}
|
||||
params.SetMeta(m)
|
||||
return params
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ import (
|
|||
"context"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"slices"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
|
|
@ -644,13 +643,6 @@ func TestClientCapabilitiesOverWire(t *testing.T) {
|
|||
// don't overlap with the SDK. The test then asserts the resulting session
|
||||
// state and whether the legacy initialize handshake ran.
|
||||
func TestClientConnectDiscover(t *testing.T) {
|
||||
// Temporarily enable 2026-07-28 support in the SDK for this test
|
||||
oldSupported := supportedProtocolVersions
|
||||
supportedProtocolVersions = append([]string{protocolVersion20260728}, supportedProtocolVersions...)
|
||||
t.Cleanup(func() {
|
||||
supportedProtocolVersions = oldSupported
|
||||
})
|
||||
|
||||
const otherVersionsOnly = "1999-01-01"
|
||||
|
||||
tests := []struct {
|
||||
|
|
@ -686,7 +678,7 @@ func TestClientConnectDiscover(t *testing.T) {
|
|||
return nil, jsonrpc2.ErrMethodNotFound
|
||||
},
|
||||
wantInitialize: true,
|
||||
wantVersion: latestProtocolVersion,
|
||||
wantVersion: protocolVersion20251125,
|
||||
},
|
||||
{
|
||||
name: "unsupported protocol version falls back to initialize",
|
||||
|
|
@ -697,7 +689,7 @@ func TestClientConnectDiscover(t *testing.T) {
|
|||
}
|
||||
},
|
||||
wantInitialize: true,
|
||||
wantVersion: latestProtocolVersion,
|
||||
wantVersion: protocolVersion20251125,
|
||||
},
|
||||
{
|
||||
name: "no overlapping supported version falls back to initialize",
|
||||
|
|
@ -709,7 +701,7 @@ func TestClientConnectDiscover(t *testing.T) {
|
|||
}, nil
|
||||
},
|
||||
wantInitialize: true,
|
||||
wantVersion: latestProtocolVersion,
|
||||
wantVersion: protocolVersion20251125,
|
||||
},
|
||||
}
|
||||
|
||||
|
|
@ -771,10 +763,6 @@ func TestClientConnectDiscover(t *testing.T) {
|
|||
// request sent by Client.Connect carries the SEP-2575 per-request _meta triple:
|
||||
// protocolVersion, clientInfo, and clientCapabilities.
|
||||
func TestClientConnectDiscover_RequestContents(t *testing.T) {
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append([]string{protocolVersion20260728}, slices.Clone(orig)...)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
type captured struct {
|
||||
|
|
@ -850,10 +838,6 @@ func TestClientConnectDiscover_RequestContents(t *testing.T) {
|
|||
func TestInMemory_E2E_DiscoverSuccess(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append([]string{protocolVersion20260728}, slices.Clone(orig)...)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
server := NewServer(&Implementation{Name: "stdio-like-server", Version: "v1"}, nil)
|
||||
ct, st := NewInMemoryTransports()
|
||||
ss, err := server.Connect(ctx, st, nil)
|
||||
|
|
@ -889,15 +873,11 @@ func TestInMemory_E2E_DiscoverSuccess(t *testing.T) {
|
|||
|
||||
// TestInMemory_E2E_DiscoverFallback_NoOverlap verifies the fallback path
|
||||
// over an InMemory (STDIO-equivalent) transport: the client probes with
|
||||
// _meta.protocolVersion = 2026-07-28, but the server's supported list does
|
||||
// NOT include that version (the default for an SDK server that hasn't
|
||||
// shimmed supportedProtocolVersions).
|
||||
// _meta.protocolVersion = 2026-07-28, but the server overrides discover via
|
||||
// middleware to advertise only legacy versions, so the client must fall
|
||||
// back to the legacy initialize handshake.
|
||||
func TestInMemory_E2E_DiscoverFallback_NoOverlap(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append([]string{protocolVersion20260728}, slices.Clone(orig)...)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
server := NewServer(&Implementation{Name: "vpre-like-server", Version: "v1"}, nil)
|
||||
// Intercept discover and reply as if we were a server that only
|
||||
// supports legacy versions.
|
||||
|
|
@ -932,9 +912,11 @@ func TestInMemory_E2E_DiscoverFallback_NoOverlap(t *testing.T) {
|
|||
if ir == nil {
|
||||
t.Fatal("InitializeResult is nil after fallback initialize")
|
||||
}
|
||||
if ir.ProtocolVersion != latestProtocolVersion {
|
||||
// The fallback initialize explicitly requests protocolVersion20251125
|
||||
// (see client.go), so the server negotiates that version.
|
||||
if ir.ProtocolVersion != protocolVersion20251125 {
|
||||
t.Errorf("InitializeResult.ProtocolVersion = %q, want %q (legacy fallback after no-overlap discover)",
|
||||
ir.ProtocolVersion, latestProtocolVersion)
|
||||
ir.ProtocolVersion, protocolVersion20251125)
|
||||
}
|
||||
|
||||
// Prove the session is usable after fallback.
|
||||
|
|
@ -949,10 +931,6 @@ func TestInMemory_E2E_DiscoverFallback_NoOverlap(t *testing.T) {
|
|||
func TestInMemory_E2E_DiscoverFallback_MethodNotFound(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append([]string{protocolVersion20260728}, slices.Clone(orig)...)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
server := NewServer(&Implementation{Name: "vpre-server", Version: "v1"}, nil)
|
||||
server.AddReceivingMiddleware(func(next MethodHandler) MethodHandler {
|
||||
return func(ctx context.Context, method string, req Request) (Result, error) {
|
||||
|
|
@ -981,9 +959,11 @@ func TestInMemory_E2E_DiscoverFallback_MethodNotFound(t *testing.T) {
|
|||
if ir == nil {
|
||||
t.Fatal("InitializeResult is nil after fallback initialize")
|
||||
}
|
||||
if ir.ProtocolVersion != latestProtocolVersion {
|
||||
// The fallback initialize explicitly requests protocolVersion20251125
|
||||
// (see client.go), so the server negotiates that version.
|
||||
if ir.ProtocolVersion != protocolVersion20251125 {
|
||||
t.Errorf("InitializeResult.ProtocolVersion = %q, want %q (legacy fallback after MethodNotFound)",
|
||||
ir.ProtocolVersion, latestProtocolVersion)
|
||||
ir.ProtocolVersion, protocolVersion20251125)
|
||||
}
|
||||
|
||||
if _, err := cs.ListTools(ctx, nil); err != nil {
|
||||
|
|
@ -998,10 +978,6 @@ func TestInMemory_E2E_DiscoverFallback_MethodNotFound(t *testing.T) {
|
|||
func TestInMemory_E2E_DiscoverFallback_UnsupportedProtocolVersion(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append([]string{protocolVersion20260728}, slices.Clone(orig)...)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
server := NewServer(&Implementation{Name: "strict-server", Version: "v1"}, nil)
|
||||
server.AddReceivingMiddleware(func(next MethodHandler) MethodHandler {
|
||||
return func(ctx context.Context, method string, req Request) (Result, error) {
|
||||
|
|
@ -1033,9 +1009,11 @@ func TestInMemory_E2E_DiscoverFallback_UnsupportedProtocolVersion(t *testing.T)
|
|||
if ir == nil {
|
||||
t.Fatal("InitializeResult is nil after fallback initialize")
|
||||
}
|
||||
if ir.ProtocolVersion != latestProtocolVersion {
|
||||
// The fallback initialize explicitly requests protocolVersion20251125
|
||||
// (see client.go), so the server negotiates that version.
|
||||
if ir.ProtocolVersion != protocolVersion20251125 {
|
||||
t.Errorf("InitializeResult.ProtocolVersion = %q, want %q (legacy fallback after UnsupportedProtocolVersion)",
|
||||
ir.ProtocolVersion, latestProtocolVersion)
|
||||
ir.ProtocolVersion, protocolVersion20251125)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1045,10 +1023,6 @@ func TestInMemory_E2E_DiscoverFallback_UnsupportedProtocolVersion(t *testing.T)
|
|||
// UnsupportedProtocolVersionError carries Data.Supported, and the client
|
||||
// selects a mutually supported version from that list and retries.
|
||||
func TestClientConnectDiscover_UnsupportedVersionNegotiation(t *testing.T) {
|
||||
oldSupported := supportedProtocolVersions
|
||||
supportedProtocolVersions = append([]string{protocolVersion20260728}, slices.Clone(oldSupported)...)
|
||||
t.Cleanup(func() { supportedProtocolVersions = oldSupported })
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
const unsupportedClientVersion = "2099-12-31"
|
||||
|
|
|
|||
|
|
@ -143,6 +143,31 @@ func incTool(_ context.Context, _ *CallToolRequest, args incInput) (*CallToolRes
|
|||
return nil, incOutput{args.X + 1}, nil
|
||||
}
|
||||
|
||||
// confirmThenGreet exercises the multi-round-trip pattern in
|
||||
// conformance fixtures.
|
||||
func confirmThenGreet(_ context.Context, req *CallToolRequest, _ struct{}) (*CallToolResult, any, error) {
|
||||
if len(req.Params.InputResponses) == 0 {
|
||||
return &CallToolResult{
|
||||
InputRequests: InputRequestMap{
|
||||
"who": &ElicitParams{
|
||||
Message: "What is your name?",
|
||||
RequestedSchema: &jsonschema.Schema{
|
||||
Type: "object",
|
||||
Properties: map[string]*jsonschema.Schema{
|
||||
"name": {Type: "string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
RequestState: "step=1",
|
||||
}, nil, nil
|
||||
}
|
||||
name := req.Params.InputResponses["who"].(*ElicitResult).Content["name"].(string)
|
||||
return &CallToolResult{
|
||||
Content: []Content{&TextContent{Text: "hello " + name}},
|
||||
}, nil, nil
|
||||
}
|
||||
|
||||
var iconObj = Icon{
|
||||
Source: "foobar",
|
||||
MIMEType: "image/png",
|
||||
|
|
@ -193,6 +218,11 @@ func runServerTest(t *testing.T, test *conformanceTest) {
|
|||
Title: "contentTool",
|
||||
Description: "return resourceLink content with Icon",
|
||||
}, contentTool)
|
||||
case "confirmThenGreet":
|
||||
AddTool(s, &Tool{
|
||||
Name: "confirmThenGreet",
|
||||
Description: "ask the user for their name via elicitation, then greet them",
|
||||
}, confirmThenGreet)
|
||||
default:
|
||||
t.Fatalf("unknown tool %q", tn)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,19 +19,13 @@ import (
|
|||
func Example_lifecycle() {
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a client and server.
|
||||
// Wait for the client to initialize the session.
|
||||
// Create a client and server. Under protocol version 2026-07-28, there
|
||||
// is no dedicated initialize/initialized handshake: the session is
|
||||
// implicitly live the moment the client issues its first request.
|
||||
client := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, nil)
|
||||
server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, &mcp.ServerOptions{
|
||||
InitializedHandler: func(context.Context, *mcp.InitializedRequest) {
|
||||
fmt.Println("initialized!")
|
||||
},
|
||||
})
|
||||
server := mcp.NewServer(&mcp.Implementation{Name: "server", Version: "v0.0.1"}, nil)
|
||||
|
||||
// Connect the server and client using in-memory transports.
|
||||
//
|
||||
// Connect the server first so that it's ready to receive initialization
|
||||
// messages from the client.
|
||||
t1, t2 := mcp.NewInMemoryTransports()
|
||||
serverSession, err := server.Connect(ctx, t1, nil)
|
||||
if err != nil {
|
||||
|
|
@ -50,7 +44,7 @@ func Example_lifecycle() {
|
|||
if err := serverSession.Wait(); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
// Output: initialized!
|
||||
// Output:
|
||||
}
|
||||
|
||||
// !-lifecycle
|
||||
|
|
|
|||
|
|
@ -171,7 +171,10 @@ func TestEndToEnd(t *testing.T) {
|
|||
c.AddRoots(&Root{URI: "file://" + rootAbs})
|
||||
|
||||
// Connect the client.
|
||||
cs, err := c.Connect(ctx, ct, nil)
|
||||
//
|
||||
// Pin the session to 2025-11-25 so the legacy
|
||||
// semantics apply.
|
||||
cs, err := c.Connect(ctx, ct, &ClientSessionOptions{protocolVersion: protocolVersion20251125})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -758,7 +761,10 @@ func TestMiddleware(t *testing.T) {
|
|||
c.AddSendingMiddleware(traceCalls[*ClientSession](&cbuf, "S1"), traceCalls[*ClientSession](&cbuf, "S2"))
|
||||
c.AddReceivingMiddleware(traceCalls[*ClientSession](&cbuf, "R1"), traceCalls[*ClientSession](&cbuf, "R2"))
|
||||
|
||||
cs, err := c.Connect(ctx, ct, nil)
|
||||
// Pin to 2025-11-25 because the test's expected wire sequence asserts
|
||||
// the legacy initialize / notifications/initialized handshake, which
|
||||
// 2026-07-28 replaces with server/discover.
|
||||
cs, err := c.Connect(ctx, ct, &ClientSessionOptions{protocolVersion: protocolVersion20251125})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -1903,7 +1909,10 @@ func TestKeepAliveFailure_Logged(t *testing.T) {
|
|||
Logger: slog.New(slog.NewTextHandler(&buf, &slog.HandlerOptions{Level: slog.LevelError})),
|
||||
}
|
||||
c := NewClient(testImpl, clientOpts)
|
||||
cs, err := c.Connect(ctx, ct, nil)
|
||||
// Pin to 2025-11-25: KeepAlive uses the ping RPC, which is removed
|
||||
// in 2026-07-28, so keepalive is only meaningful on legacy protocol
|
||||
// versions.
|
||||
cs, err := c.Connect(ctx, ct, &ClientSessionOptions{protocolVersion: protocolVersion20251125})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -2570,18 +2579,10 @@ func newSubListenServer() *Server {
|
|||
return s
|
||||
}
|
||||
|
||||
func enableNewProtocol(t *testing.T) {
|
||||
t.Helper()
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append([]string{protocolVersion20260728}, slices.Clone(orig)...)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
}
|
||||
|
||||
// TestSubscriptionsListen_InMemory exercises the listen flow over the
|
||||
// session-shared in-memory transport (semantically equivalent to STDIO).
|
||||
// Cancellation here propagates via notifications/cancelled.
|
||||
func TestSubscriptionsListen_InMemory(t *testing.T) {
|
||||
enableNewProtocol(t)
|
||||
events := make(chan subListenEvent, 64)
|
||||
server := newSubListenServer()
|
||||
ct, st := NewInMemoryTransports()
|
||||
|
|
@ -2597,7 +2598,6 @@ func TestSubscriptionsListen_InMemory(t *testing.T) {
|
|||
// stateless HTTP server (SEP-2575). Each listen rides its own SSE response
|
||||
// stream; cs.Close() tears it down.
|
||||
func TestSubscriptionsListen_Streamable(t *testing.T) {
|
||||
enableNewProtocol(t)
|
||||
events := make(chan subListenEvent, 64)
|
||||
server := newSubListenServer()
|
||||
handler := NewStreamableHTTPHandler(
|
||||
|
|
@ -2615,7 +2615,6 @@ func TestSubscriptionsListen_Streamable(t *testing.T) {
|
|||
// auto-listen on connect, and therefore does not receive any acknowledgment
|
||||
// or downstream notifications.
|
||||
func TestSubscriptionsListen_NoHandlersNoListen(t *testing.T) {
|
||||
enableNewProtocol(t)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
|
@ -2694,7 +2693,6 @@ type resourceSubEvent struct {
|
|||
// subscriptionsListen handler so HTTP disconnect is observed directly; this
|
||||
// is tracked separately.
|
||||
func TestResourceSubscriptions_Streamable(t *testing.T) {
|
||||
enableNewProtocol(t)
|
||||
|
||||
subCh := make(chan string, 8)
|
||||
unsubCh := make(chan string, 8)
|
||||
|
|
@ -2764,7 +2762,6 @@ func TestResourceSubscriptions_Streamable(t *testing.T) {
|
|||
// over an in-memory (stdio-equivalent) transport: the per-URI Subscribe path
|
||||
// uses notifications/cancelled rather than HTTP disconnect for teardown.
|
||||
func TestResourceSubscriptions_InMemory(t *testing.T) {
|
||||
enableNewProtocol(t)
|
||||
|
||||
subCh := make(chan string, 8)
|
||||
unsubCh := make(chan string, 8)
|
||||
|
|
@ -2849,7 +2846,6 @@ func TestResourceSubscriptions_InMemory(t *testing.T) {
|
|||
// second call: it returns nil without invoking SubscribeHandler again and
|
||||
// without opening a second listen stream.
|
||||
func TestResourceSubscriptions_Subscribe_Idempotent(t *testing.T) {
|
||||
enableNewProtocol(t)
|
||||
|
||||
subCh := make(chan string, 8)
|
||||
unsubCh := make(chan string, 8)
|
||||
|
|
@ -2914,7 +2910,6 @@ func TestResourceSubscriptions_Subscribe_Idempotent(t *testing.T) {
|
|||
// stream with a distinct subscription ID. Unsubscribing one does not affect
|
||||
// the other.
|
||||
func TestResourceSubscriptions_MultipleURIs(t *testing.T) {
|
||||
enableNewProtocol(t)
|
||||
|
||||
subCh := make(chan string, 8)
|
||||
unsubCh := make(chan string, 8)
|
||||
|
|
@ -3032,7 +3027,6 @@ func TestResourceSubscriptions_MultipleURIs(t *testing.T) {
|
|||
// notification is fanned out to BOTH sessions, each with its own subscription
|
||||
// ID; closing one session does not affect deliveries to the other.
|
||||
func TestSubscriptionsListen_MultipleSessions(t *testing.T) {
|
||||
enableNewProtocol(t)
|
||||
|
||||
server := newSubListenServer()
|
||||
|
||||
|
|
@ -3110,7 +3104,6 @@ func TestSubscriptionsListen_MultipleSessions(t *testing.T) {
|
|||
// auto-listen branch (the other two list-changed types are covered by
|
||||
// runSubscriptionsListenTest, but resources is not).
|
||||
func TestSubscriptionsListen_ResourceListChanged(t *testing.T) {
|
||||
enableNewProtocol(t)
|
||||
events := make(chan subListenEvent, 16)
|
||||
|
||||
server := NewServer(testImpl, nil)
|
||||
|
|
@ -3182,7 +3175,6 @@ func TestSubscriptionsListen_ResourceListChanged(t *testing.T) {
|
|||
// session removes its entries from the server's three per-type subscription
|
||||
// maps via Server.disconnect.
|
||||
func TestSubscriptionsListen_DisconnectScrubsMaps(t *testing.T) {
|
||||
enableNewProtocol(t)
|
||||
events := make(chan subListenEvent, 16)
|
||||
server := newSubListenServer()
|
||||
|
||||
|
|
|
|||
|
|
@ -23,10 +23,6 @@ func TestMultiRoundTrip_ManualRetry(t *testing.T) {
|
|||
Reason string `json:"reason,omitempty"`
|
||||
}
|
||||
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append(slices.Clone(orig), protocolVersion20260728)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
srv := NewServer(testImpl, nil)
|
||||
|
|
@ -95,9 +91,6 @@ func TestMultiRoundTrip_ManualRetry(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMultiRoundTrip_AutoRetry(t *testing.T) {
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append(slices.Clone(orig), protocolVersion20260728)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
@ -220,9 +213,6 @@ func TestMultiRoundTrip_MaxRetries(t *testing.T) {
|
|||
wantRetries: maxMultiRoundTripRetries,
|
||||
},
|
||||
}
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append(slices.Clone(orig), protocolVersion20260728)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
|
|
@ -322,7 +312,7 @@ func TestMultiRoundTrip_ServerMiddleware(t *testing.T) {
|
|||
},
|
||||
})
|
||||
c.AddRoots(&Root{URI: "file:///workspace", Name: "workspace"})
|
||||
cs, err := c.Connect(t.Context(), ct, &ClientSessionOptions{})
|
||||
cs, err := c.Connect(t.Context(), ct, &ClientSessionOptions{protocolVersion: protocolVersion20251125})
|
||||
if err != nil {
|
||||
t.Fatalf("client.Connect() error = %v", err)
|
||||
}
|
||||
|
|
@ -340,9 +330,6 @@ func TestMultiRoundTrip_ServerMiddleware(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMultiRoundTrip_GetPrompt_AutoRetry(t *testing.T) {
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append(slices.Clone(orig), protocolVersion20260728)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
|
|
@ -382,9 +369,6 @@ func TestMultiRoundTrip_GetPrompt_AutoRetry(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMultiRoundTrip_GetPrompt_ManualRetry(t *testing.T) {
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append(slices.Clone(orig), protocolVersion20260728)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
|
|
@ -434,9 +418,6 @@ func TestMultiRoundTrip_GetPrompt_ManualRetry(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMultiRoundTrip_ReadResource_AutoRetry(t *testing.T) {
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append(slices.Clone(orig), protocolVersion20260728)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
|
|
@ -475,9 +456,6 @@ func TestMultiRoundTrip_ReadResource_AutoRetry(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestMultiRoundTrip_ReadResource_ManualRetry(t *testing.T) {
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append(slices.Clone(orig), protocolVersion20260728)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
|
|
|
|||
|
|
@ -856,7 +856,9 @@ func (s *Server) getPrompt(ctx context.Context, req *GetPromptRequest) (*GetProm
|
|||
// instructions, allowing clients to negotiate without performing the legacy
|
||||
// initialize handshake.
|
||||
func (s *Server) discover(_ context.Context, req *ServerRequest[*DiscoverParams]) (*DiscoverResult, error) {
|
||||
req.Session.mu.Lock()
|
||||
versions := req.Session.supportedVersions
|
||||
req.Session.mu.Unlock()
|
||||
if versions == nil {
|
||||
versions = slices.Clone(supportedProtocolVersions)
|
||||
}
|
||||
|
|
@ -1335,7 +1337,17 @@ func (s *Server) Connect(ctx context.Context, t Transport, opts *ServerSessionOp
|
|||
// 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.
|
||||
//
|
||||
// The write is guarded by ss.mu to establish a happens-before edge with
|
||||
// the matching read in Server.discover, which runs on the jsonrpc2 read
|
||||
// goroutine spawned inside connect(). The two are not concurrent in
|
||||
// wall-clock terms (no incoming message is dispatched until the caller
|
||||
// has fed the transport, which happens after Server.Connect returns),
|
||||
// but without the lock the Go memory model gives the read goroutine no
|
||||
// guarantee of seeing this write, and -race flags it.
|
||||
ss.mu.Lock()
|
||||
ss.supportedVersions = filterSupportedVersions(t)
|
||||
ss.mu.Unlock()
|
||||
|
||||
// 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).
|
||||
|
|
@ -1795,7 +1807,7 @@ func (ss *ServerSession) handle(ctx context.Context, req *jsonrpc.Request) (any,
|
|||
}
|
||||
|
||||
switch req.Method {
|
||||
case methodInitialize, methodPing, notificationInitialized, methodSetLevel, methodSubscribe, methodUnsubscribe:
|
||||
case methodInitialize, methodPing, notificationInitialized, notificationRootsListChanged, methodSetLevel, methodSubscribe, methodUnsubscribe:
|
||||
if validatedMeta.usesNewProtocol {
|
||||
ss.server.opts.Logger.Error("method removed in the new protocol", "method", req.Method)
|
||||
return nil, &jsonrpc.Error{
|
||||
|
|
|
|||
|
|
@ -284,7 +284,7 @@ func Example_mrtr() {
|
|||
// uses it to fulfil any input request the tool handler returns.
|
||||
c := mcp.NewClient(&mcp.Implementation{Name: "client", Version: "v0.0.1"}, &mcp.ClientOptions{
|
||||
ElicitationHandler: func(_ context.Context, _ *mcp.ElicitRequest) (*mcp.ElicitResult, error) {
|
||||
return &mcp.ElicitResult{Action: "accept", Content: map[string]any{"name": "Ada"}}, nil
|
||||
return &mcp.ElicitResult{Action: "accept", Content: map[string]any{"name": "MCP Go"}}, nil
|
||||
},
|
||||
})
|
||||
|
||||
|
|
|
|||
|
|
@ -1290,9 +1290,6 @@ func TestServerCapabilitiesOverWire(t *testing.T) {
|
|||
// that opts into the new protocol via `_meta.protocolVersion` must be
|
||||
// rejected with `Method not found` (-32601).
|
||||
func TestServerSessionHandle_RejectsInitializeOnNewProtocol(t *testing.T) {
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append([]string{protocolVersion20260728}, slices.Clone(orig)...)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
|
|
@ -1407,9 +1404,6 @@ func TestServerSessionHandle_RejectsInitializeOnNewProtocol(t *testing.T) {
|
|||
// `ping`) all return Method not found when the request opts into the new
|
||||
// protocol via `_meta.protocolVersion`.
|
||||
func TestServerSessionHandle_RejectsRemovedMethodsOnNewProtocol(t *testing.T) {
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append([]string{protocolVersion20260728}, slices.Clone(orig)...)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
newProtoMeta := map[string]any{
|
||||
"_meta": map[string]any{
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ const (
|
|||
//
|
||||
// It is the version that the client sends in the initialization request, and
|
||||
// the default version used by the server.
|
||||
latestProtocolVersion = protocolVersion20251125
|
||||
latestProtocolVersion = protocolVersion20260728
|
||||
protocolVersion20260728 = "2026-07-28"
|
||||
protocolVersion20251125 = "2025-11-25"
|
||||
protocolVersion20250618 = "2025-06-18"
|
||||
|
|
@ -44,6 +44,7 @@ const (
|
|||
)
|
||||
|
||||
var supportedProtocolVersions = []string{
|
||||
protocolVersion20260728,
|
||||
protocolVersion20251125,
|
||||
protocolVersion20250618,
|
||||
protocolVersion20250326,
|
||||
|
|
@ -58,8 +59,11 @@ func negotiatedVersion(clientVersion string) string {
|
|||
//
|
||||
// This handles the case where a new spec version is released, and the SDK
|
||||
// does not support it yet.
|
||||
// Cap the supported versions at the legacy protocolVersion20251125, as this
|
||||
// method is used by the initialize method which is deprecated in
|
||||
// version protocolVersion20260728.
|
||||
if !slices.Contains(supportedProtocolVersions, clientVersion) {
|
||||
return latestProtocolVersion
|
||||
return protocolVersion20251125
|
||||
}
|
||||
return clientVersion
|
||||
}
|
||||
|
|
@ -536,7 +540,7 @@ func validateRequestMeta(req *jsonrpc.Request) (*validatedMeta, error) {
|
|||
Message: fmt.Sprintf("missing or invalid _meta field %q", MetaKeyClientInfo),
|
||||
}
|
||||
}
|
||||
capabilities, ok := decodeMetaValue[*ClientCapabilities](meta, MetaKeyClientCapabilities)
|
||||
capabilities, ok := decodeMetaValue[*clientCapabilitiesV2](meta, MetaKeyClientCapabilities)
|
||||
if !ok {
|
||||
return nil, &jsonrpc.Error{
|
||||
Code: jsonrpc.CodeInvalidParams,
|
||||
|
|
@ -546,7 +550,7 @@ func validateRequestMeta(req *jsonrpc.Request) (*validatedMeta, error) {
|
|||
logLevel, _ := decodeMetaValue[LoggingLevel](meta, MetaKeyLogLevel)
|
||||
return &validatedMeta{usesNewProtocol: true, initializeParams: &InitializeParams{
|
||||
ProtocolVersion: protocolVersion,
|
||||
Capabilities: capabilities,
|
||||
Capabilities: capabilities.toV1(),
|
||||
ClientInfo: clientInfo,
|
||||
}, logLevel: logLevel}, nil
|
||||
}
|
||||
|
|
@ -659,8 +663,8 @@ func (r *ServerRequest[P]) ClientInfo() *Implementation {
|
|||
// back to the session-level [InitializeParams].
|
||||
func (r *ServerRequest[P]) ClientCapabilities() *ClientCapabilities {
|
||||
if m := getRequestMeta(r); m != nil {
|
||||
if v, ok := decodeMetaValue[*ClientCapabilities](m, MetaKeyClientCapabilities); ok {
|
||||
return v
|
||||
if v, ok := decodeMetaValue[*clientCapabilitiesV2](m, MetaKeyClientCapabilities); ok {
|
||||
return v.toV1()
|
||||
}
|
||||
}
|
||||
if r.Session != nil {
|
||||
|
|
|
|||
|
|
@ -160,7 +160,8 @@ var (
|
|||
Logging: &LoggingCapabilities{},
|
||||
Tools: &ToolCapabilities{ListChanged: true},
|
||||
},
|
||||
ProtocolVersion: latestProtocolVersion,
|
||||
// Pin negotiated version to 2025-11-25
|
||||
ProtocolVersion: protocolVersion20251125,
|
||||
ServerInfo: &Implementation{Name: "testServer", Version: "v1.0.0"},
|
||||
}
|
||||
initResp = resp(1, initResult, nil)
|
||||
|
|
@ -193,13 +194,13 @@ func TestStreamableClientTransportLifecycle(t *testing.T) {
|
|||
},
|
||||
{"POST", "123", notificationInitialized, ""}: {
|
||||
status: http.StatusAccepted,
|
||||
wantProtocolVersion: latestProtocolVersion,
|
||||
wantProtocolVersion: protocolVersion20251125,
|
||||
},
|
||||
{"GET", "123", "", ""}: {
|
||||
header: header{
|
||||
"Content-Type": "text/event-stream",
|
||||
},
|
||||
wantProtocolVersion: latestProtocolVersion,
|
||||
wantProtocolVersion: protocolVersion20251125,
|
||||
},
|
||||
{"DELETE", "123", "", ""}: {},
|
||||
},
|
||||
|
|
@ -210,7 +211,10 @@ func TestStreamableClientTransportLifecycle(t *testing.T) {
|
|||
|
||||
transport := &StreamableClientTransport{Endpoint: httpServer.URL}
|
||||
client := NewClient(testImpl, nil)
|
||||
session, err := client.Connect(ctx, transport, nil)
|
||||
// Pin to 2025-11-25: the fixture's canned initialize response uses
|
||||
// hardcoded id=1, which only matches when initialize is the first
|
||||
// request. Under 2026-07-28 the client probes server/discover first.
|
||||
session, err := client.Connect(ctx, transport, &ClientSessionOptions{protocolVersion: protocolVersion20251125})
|
||||
if err != nil {
|
||||
t.Fatalf("client.Connect() failed: %v", err)
|
||||
}
|
||||
|
|
@ -244,7 +248,7 @@ func TestStreamableClientRedundantDelete(t *testing.T) {
|
|||
},
|
||||
{"POST", "123", notificationInitialized, ""}: {
|
||||
status: http.StatusAccepted,
|
||||
wantProtocolVersion: latestProtocolVersion,
|
||||
wantProtocolVersion: protocolVersion20251125,
|
||||
},
|
||||
{"GET", "123", "", ""}: {
|
||||
status: http.StatusMethodNotAllowed,
|
||||
|
|
@ -260,7 +264,10 @@ func TestStreamableClientRedundantDelete(t *testing.T) {
|
|||
|
||||
transport := &StreamableClientTransport{Endpoint: httpServer.URL}
|
||||
client := NewClient(testImpl, nil)
|
||||
session, err := client.Connect(ctx, transport, nil)
|
||||
// Pin to 2025-11-25: the fixture's canned initialize response uses
|
||||
// hardcoded id=1, which only matches when initialize is the first
|
||||
// request. Under 2026-07-28 the client probes server/discover first.
|
||||
session, err := client.Connect(ctx, transport, &ClientSessionOptions{protocolVersion: protocolVersion20251125})
|
||||
if err != nil {
|
||||
t.Fatalf("client.Connect() failed: %v", err)
|
||||
}
|
||||
|
|
@ -307,14 +314,14 @@ func TestStreamableClientGETHandling(t *testing.T) {
|
|||
},
|
||||
{"POST", "123", notificationInitialized, ""}: {
|
||||
status: http.StatusAccepted,
|
||||
wantProtocolVersion: latestProtocolVersion,
|
||||
wantProtocolVersion: protocolVersion20251125,
|
||||
},
|
||||
{"GET", "123", "", ""}: {
|
||||
header: header{
|
||||
"Content-Type": test.contentType,
|
||||
},
|
||||
status: test.status,
|
||||
wantProtocolVersion: latestProtocolVersion,
|
||||
wantProtocolVersion: protocolVersion20251125,
|
||||
},
|
||||
{"DELETE", "123", "", ""}: {optional: true},
|
||||
},
|
||||
|
|
@ -324,7 +331,9 @@ func TestStreamableClientGETHandling(t *testing.T) {
|
|||
|
||||
transport := &StreamableClientTransport{Endpoint: httpServer.URL}
|
||||
client := NewClient(testImpl, nil)
|
||||
session, err := client.Connect(ctx, transport, nil)
|
||||
session, err := client.Connect(ctx, transport, &ClientSessionOptions{
|
||||
protocolVersion: protocolVersion20251125,
|
||||
})
|
||||
if err == nil {
|
||||
defer session.Close()
|
||||
}
|
||||
|
|
@ -376,14 +385,14 @@ func TestStreamableClientStrictness(t *testing.T) {
|
|||
},
|
||||
{"POST", "123", notificationInitialized, ""}: {
|
||||
status: test.initializedStatus,
|
||||
wantProtocolVersion: latestProtocolVersion,
|
||||
wantProtocolVersion: protocolVersion20251125,
|
||||
},
|
||||
{"GET", "123", "", ""}: {
|
||||
header: header{
|
||||
"Content-Type": "text/event-stream",
|
||||
},
|
||||
status: test.getStatus,
|
||||
wantProtocolVersion: latestProtocolVersion,
|
||||
wantProtocolVersion: protocolVersion20251125,
|
||||
},
|
||||
{"POST", "123", methodListTools, ""}: {
|
||||
header: header{
|
||||
|
|
@ -401,7 +410,11 @@ func TestStreamableClientStrictness(t *testing.T) {
|
|||
|
||||
transport := &StreamableClientTransport{Endpoint: httpServer.URL, strict: test.strict}
|
||||
client := NewClient(testImpl, nil)
|
||||
session, err := client.Connect(ctx, transport, nil)
|
||||
// Pin to 2025-11-25: the fixture's canned initialize response
|
||||
// uses hardcoded id=1, which only matches when initialize is
|
||||
// the first request. Under 2026-07-28 the client probes
|
||||
// server/discover first.
|
||||
session, err := client.Connect(ctx, transport, &ClientSessionOptions{protocolVersion: protocolVersion20251125})
|
||||
if (err != nil) != test.wantConnectError {
|
||||
t.Errorf("client.Connect() returned error %v; want error: %t", err, test.wantConnectError)
|
||||
}
|
||||
|
|
@ -441,7 +454,7 @@ func TestStreamableClientUnresumableRequest(t *testing.T) {
|
|||
|
||||
transport := &StreamableClientTransport{Endpoint: httpServer.URL}
|
||||
client := NewClient(testImpl, nil)
|
||||
cs, err := client.Connect(ctx, transport, nil)
|
||||
cs, err := client.Connect(ctx, transport, &ClientSessionOptions{protocolVersion: protocolVersion20251125})
|
||||
if err == nil {
|
||||
cs.Close()
|
||||
t.Fatalf("Connect succeeded unexpectedly")
|
||||
|
|
@ -515,7 +528,7 @@ func TestStreamableClientResumption_Cancelled(t *testing.T) {
|
|||
},
|
||||
{"POST", "123", notificationInitialized, ""}: {
|
||||
status: http.StatusAccepted,
|
||||
wantProtocolVersion: latestProtocolVersion,
|
||||
wantProtocolVersion: protocolVersion20251125,
|
||||
},
|
||||
{"GET", "123", "", ""}: {
|
||||
header: header{
|
||||
|
|
@ -558,7 +571,11 @@ data: { "jsonrpc": "2.0", "method": "notifications/message", "params": { "level"
|
|||
|
||||
transport := &StreamableClientTransport{Endpoint: httpServer.URL}
|
||||
client := NewClient(testImpl, nil)
|
||||
cs, err := client.Connect(ctx, transport, nil)
|
||||
// Pin to 2025-11-25: the fixture's canned initialize response
|
||||
// uses hardcoded id=1, which only matches when initialize is
|
||||
// the first request. Under 2026-07-28 the client probes
|
||||
// server/discover first.
|
||||
cs, err := client.Connect(ctx, transport, &ClientSessionOptions{protocolVersion: protocolVersion20251125})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -658,7 +675,7 @@ func TestStreamableClientTransientErrors(t *testing.T) {
|
|||
},
|
||||
{"POST", "123", notificationInitialized, ""}: {
|
||||
status: http.StatusAccepted,
|
||||
wantProtocolVersion: latestProtocolVersion,
|
||||
wantProtocolVersion: protocolVersion20251125,
|
||||
},
|
||||
{"GET", "123", "", ""}: {
|
||||
status: http.StatusMethodNotAllowed,
|
||||
|
|
@ -686,7 +703,11 @@ func TestStreamableClientTransientErrors(t *testing.T) {
|
|||
|
||||
transport := &StreamableClientTransport{Endpoint: httpServer.URL}
|
||||
client := NewClient(testImpl, nil)
|
||||
session, err := client.Connect(ctx, transport, nil)
|
||||
// Pin to 2025-11-25: the fixture's canned initialize response
|
||||
// uses hardcoded id=1, which only matches when initialize is
|
||||
// the first request. Under 2026-07-28 the client probes
|
||||
// server/discover first.
|
||||
session, err := client.Connect(ctx, transport, &ClientSessionOptions{protocolVersion: protocolVersion20251125})
|
||||
if err != nil {
|
||||
t.Fatalf("Connect failed: %v", err)
|
||||
}
|
||||
|
|
@ -747,7 +768,7 @@ func TestStreamableClientRetryWithoutProgress(t *testing.T) {
|
|||
},
|
||||
{"POST", "test-session", notificationInitialized, ""}: {
|
||||
status: http.StatusAccepted,
|
||||
wantProtocolVersion: latestProtocolVersion,
|
||||
wantProtocolVersion: protocolVersion20251125,
|
||||
},
|
||||
{"GET", "test-session", "", ""}: {
|
||||
// Disable standalone SSE stream to simplify the test.
|
||||
|
|
@ -789,7 +810,10 @@ data: {"jsonrpc":"2.0","method":"notifications/message","params":{"level":"info"
|
|||
MaxRetries: maxRetries,
|
||||
}
|
||||
client := NewClient(testImpl, nil)
|
||||
session, err := client.Connect(ctx, transport, nil)
|
||||
// Pin to 2025-11-25: the fixture's canned initialize response uses
|
||||
// hardcoded id=1, which only matches when initialize is the first
|
||||
// request. Under 2026-07-28 the client probes server/discover first.
|
||||
session, err := client.Connect(ctx, transport, &ClientSessionOptions{protocolVersion: protocolVersion20251125})
|
||||
if err != nil {
|
||||
t.Fatalf("Connect failed: %v", err)
|
||||
}
|
||||
|
|
@ -850,13 +874,13 @@ func TestStreamableClientDisableStandaloneSSE(t *testing.T) {
|
|||
},
|
||||
{"POST", "123", notificationInitialized, ""}: {
|
||||
status: http.StatusAccepted,
|
||||
wantProtocolVersion: latestProtocolVersion,
|
||||
wantProtocolVersion: protocolVersion20251125,
|
||||
},
|
||||
getRequestKey: {
|
||||
header: header{
|
||||
"Content-Type": "text/event-stream",
|
||||
},
|
||||
wantProtocolVersion: latestProtocolVersion,
|
||||
wantProtocolVersion: protocolVersion20251125,
|
||||
optional: !test.expectGETRequest,
|
||||
},
|
||||
{"DELETE", "123", "", ""}: {
|
||||
|
|
@ -873,7 +897,11 @@ func TestStreamableClientDisableStandaloneSSE(t *testing.T) {
|
|||
DisableStandaloneSSE: test.disableStandaloneSSE,
|
||||
}
|
||||
client := NewClient(testImpl, nil)
|
||||
session, err := client.Connect(ctx, transport, nil)
|
||||
// Pin to 2025-11-25: the fixture's canned initialize response
|
||||
// uses hardcoded id=1, which only matches when initialize is
|
||||
// the first request. Under 2026-07-28 the client probes
|
||||
// server/discover first.
|
||||
session, err := client.Connect(ctx, transport, &ClientSessionOptions{protocolVersion: protocolVersion20251125})
|
||||
if err != nil {
|
||||
t.Fatalf("client.Connect() failed: %v", err)
|
||||
}
|
||||
|
|
@ -967,7 +995,7 @@ func TestStreamableClientOAuth_AuthorizationHeader(t *testing.T) {
|
|||
},
|
||||
{"POST", "123", notificationInitialized, ""}: {
|
||||
status: http.StatusAccepted,
|
||||
wantProtocolVersion: latestProtocolVersion,
|
||||
wantProtocolVersion: protocolVersion20251125,
|
||||
},
|
||||
{"GET", "123", "", ""}: {
|
||||
header: header{
|
||||
|
|
@ -991,7 +1019,10 @@ func TestStreamableClientOAuth_AuthorizationHeader(t *testing.T) {
|
|||
OAuthHandler: oauthHandler,
|
||||
}
|
||||
client := NewClient(testImpl, nil)
|
||||
session, err := client.Connect(ctx, transport, nil)
|
||||
// Pin to 2025-11-25: the fixture's canned initialize response uses
|
||||
// hardcoded id=1, which only matches when initialize is the first
|
||||
// request. Under 2026-07-28 the client probes server/discover first.
|
||||
session, err := client.Connect(ctx, transport, &ClientSessionOptions{protocolVersion: protocolVersion20251125})
|
||||
if err != nil {
|
||||
t.Fatalf("client.Connect() failed: %v", err)
|
||||
}
|
||||
|
|
@ -1253,13 +1284,6 @@ var discoverResult = &DiscoverResult{
|
|||
func TestStreamableClientConnect_DiscoverSuccess(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Temporarily enable 2026-07-28 support in the SDK for this test
|
||||
oldSupported := supportedProtocolVersions
|
||||
supportedProtocolVersions = append([]string{protocolVersion20260728}, supportedProtocolVersions...)
|
||||
t.Cleanup(func() {
|
||||
supportedProtocolVersions = oldSupported
|
||||
})
|
||||
|
||||
var (
|
||||
gotDiscoverMu sync.Mutex
|
||||
gotDiscover *jsonrpc.Request
|
||||
|
|
@ -1378,11 +1402,11 @@ func TestStreamableClientConnect_DiscoverMethodNotFound(t *testing.T) {
|
|||
},
|
||||
{"POST", "fallback", notificationInitialized, ""}: {
|
||||
status: http.StatusAccepted,
|
||||
wantProtocolVersion: latestProtocolVersion,
|
||||
wantProtocolVersion: protocolVersion20251125,
|
||||
},
|
||||
{"GET", "fallback", "", ""}: {
|
||||
header: header{"Content-Type": "text/event-stream"},
|
||||
wantProtocolVersion: latestProtocolVersion,
|
||||
wantProtocolVersion: protocolVersion20251125,
|
||||
optional: true,
|
||||
},
|
||||
{"DELETE", "fallback", "", ""}: {optional: true},
|
||||
|
|
@ -1394,14 +1418,16 @@ func TestStreamableClientConnect_DiscoverMethodNotFound(t *testing.T) {
|
|||
|
||||
transport := &StreamableClientTransport{Endpoint: httpServer.URL}
|
||||
client := NewClient(testImpl, nil)
|
||||
session, err := client.Connect(ctx, transport, nil)
|
||||
session, err := client.Connect(ctx, transport, &ClientSessionOptions{protocolVersion: protocolVersion20251125})
|
||||
if err != nil {
|
||||
t.Fatalf("Connect: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
if got := session.InitializeResult().ProtocolVersion; got != latestProtocolVersion {
|
||||
t.Errorf("InitializeResult.ProtocolVersion = %q, want %q (initialize fallback)", got, latestProtocolVersion)
|
||||
// The fallback initialize explicitly requests protocolVersion20251125
|
||||
// (see client.go), so the server negotiates that version.
|
||||
if got := session.InitializeResult().ProtocolVersion; got != protocolVersion20251125 {
|
||||
t.Errorf("InitializeResult.ProtocolVersion = %q, want %q (initialize fallback)", got, protocolVersion20251125)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1443,11 +1469,11 @@ func TestStreamableClientConnect_DiscoverUnsupportedVersion(t *testing.T) {
|
|||
},
|
||||
{"POST", "fallback", notificationInitialized, ""}: {
|
||||
status: http.StatusAccepted,
|
||||
wantProtocolVersion: latestProtocolVersion,
|
||||
wantProtocolVersion: protocolVersion20251125,
|
||||
},
|
||||
{"GET", "fallback", "", ""}: {
|
||||
header: header{"Content-Type": "text/event-stream"},
|
||||
wantProtocolVersion: latestProtocolVersion,
|
||||
wantProtocolVersion: protocolVersion20251125,
|
||||
optional: true,
|
||||
},
|
||||
{"DELETE", "fallback", "", ""}: {optional: true},
|
||||
|
|
@ -1459,14 +1485,16 @@ func TestStreamableClientConnect_DiscoverUnsupportedVersion(t *testing.T) {
|
|||
|
||||
transport := &StreamableClientTransport{Endpoint: httpServer.URL}
|
||||
client := NewClient(testImpl, nil)
|
||||
session, err := client.Connect(ctx, transport, nil)
|
||||
session, err := client.Connect(ctx, transport, &ClientSessionOptions{protocolVersion: protocolVersion20251125})
|
||||
if err != nil {
|
||||
t.Fatalf("Connect: %v", err)
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
if got := session.InitializeResult().ProtocolVersion; got != latestProtocolVersion {
|
||||
t.Errorf("InitializeResult.ProtocolVersion = %q, want %q (initialize fallback)", got, latestProtocolVersion)
|
||||
// The fallback initialize explicitly requests protocolVersion20251125
|
||||
// (see client.go), so the server negotiates that version.
|
||||
if got := session.InitializeResult().ProtocolVersion; got != protocolVersion20251125 {
|
||||
t.Errorf("InitializeResult.ProtocolVersion = %q, want %q (initialize fallback)", got, protocolVersion20251125)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1503,7 +1531,7 @@ func TestStreamableClientConnect_DiscoverMethodNotFoundVPre(t *testing.T) {
|
|||
},
|
||||
{"POST", "fallback", notificationInitialized, ""}: {
|
||||
status: http.StatusAccepted,
|
||||
wantProtocolVersion: latestProtocolVersion,
|
||||
wantProtocolVersion: protocolVersion20251125,
|
||||
},
|
||||
{"DELETE", "fallback", "", ""}: {optional: true},
|
||||
},
|
||||
|
|
@ -1523,8 +1551,10 @@ func TestStreamableClientConnect_DiscoverMethodNotFoundVPre(t *testing.T) {
|
|||
}
|
||||
defer session.Close()
|
||||
|
||||
if got := session.InitializeResult().ProtocolVersion; got != latestProtocolVersion {
|
||||
t.Errorf("InitializeResult.ProtocolVersion = %q, want %q (initialize fallback)", got, latestProtocolVersion)
|
||||
// The fallback initialize explicitly requests protocolVersion20251125
|
||||
// (see client.go), so the server negotiates that version.
|
||||
if got := session.InitializeResult().ProtocolVersion; got != protocolVersion20251125 {
|
||||
t.Errorf("InitializeResult.ProtocolVersion = %q, want %q (initialize fallback)", got, protocolVersion20251125)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1558,7 +1588,7 @@ func TestStreamableClientConnect_DiscoverUnsupportedVersionVPre(t *testing.T) {
|
|||
},
|
||||
{"POST", "fallback", notificationInitialized, ""}: {
|
||||
status: http.StatusAccepted,
|
||||
wantProtocolVersion: latestProtocolVersion,
|
||||
wantProtocolVersion: protocolVersion20251125,
|
||||
},
|
||||
{"DELETE", "fallback", "", ""}: {optional: true},
|
||||
},
|
||||
|
|
@ -1578,8 +1608,10 @@ func TestStreamableClientConnect_DiscoverUnsupportedVersionVPre(t *testing.T) {
|
|||
}
|
||||
defer session.Close()
|
||||
|
||||
if got := session.InitializeResult().ProtocolVersion; got != latestProtocolVersion {
|
||||
t.Errorf("InitializeResult.ProtocolVersion = %q, want %q (initialize fallback)", got, latestProtocolVersion)
|
||||
// The fallback initialize explicitly requests protocolVersion20251125
|
||||
// (see client.go), so the server negotiates that version.
|
||||
if got := session.InitializeResult().ProtocolVersion; got != protocolVersion20251125 {
|
||||
t.Errorf("InitializeResult.ProtocolVersion = %q, want %q (initialize fallback)", got, protocolVersion20251125)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1590,12 +1622,6 @@ func TestStreamableClientConnect_DiscoverUnsupportedVersionVPre(t *testing.T) {
|
|||
func TestStreamableClientConnect_DiscoverUnsupportedVersionNegotiation(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
oldSupported := supportedProtocolVersions
|
||||
supportedProtocolVersions = append([]string{protocolVersion20260728}, supportedProtocolVersions...)
|
||||
t.Cleanup(func() {
|
||||
supportedProtocolVersions = oldSupported
|
||||
})
|
||||
|
||||
const unsupportedClientVersion = "2099-12-31"
|
||||
|
||||
var discoverCalls atomic.Int32
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ func ExampleStreamableHTTPHandler() {
|
|||
defer httpServer.Close()
|
||||
|
||||
// The SDK is currently permissive of some missing keys in "params".
|
||||
resp := mustPostMessage(`{"jsonrpc": "2.0", "id": 1, "method":"initialize", "params": {}}`, httpServer.URL)
|
||||
resp := mustPostMessage(`{"jsonrpc": "2.0", "id": 1, "method":"initialize", "params": {"protocolVersion":"2025-11-25"}}`, httpServer.URL)
|
||||
fmt.Println(resp)
|
||||
// Output:
|
||||
// {"jsonrpc":"2.0","id":1,"result":{"capabilities":{"logging":{}},"protocolVersion":"2025-11-25","serverInfo":{"name":"server","version":"v0.1.0"}}}
|
||||
|
|
|
|||
|
|
@ -182,7 +182,11 @@ func TestStreamableTransports(t *testing.T) {
|
|||
if sid == "" {
|
||||
t.Fatalf("empty session ID")
|
||||
}
|
||||
if g, w := session.mcpConn.(*streamableClientConn).initializedResult.ProtocolVersion, latestProtocolVersion; g != w {
|
||||
// Stateful HTTP servers reject 2026-07-28 (which requires
|
||||
// StreamableHTTPOptions.Stateless = true), so the client falls
|
||||
// back to the legacy initialize handshake which explicitly
|
||||
// requests 2025-11-25.
|
||||
if g, w := session.mcpConn.(*streamableClientConn).initializedResult.ProtocolVersion, protocolVersion20251125; g != w {
|
||||
t.Fatalf("got protocol version %q, want %q", g, w)
|
||||
}
|
||||
|
||||
|
|
@ -200,7 +204,10 @@ func TestStreamableTransports(t *testing.T) {
|
|||
if g := session.ID(); g != sid {
|
||||
t.Errorf("session ID: got %q, want %q", g, sid)
|
||||
}
|
||||
if g, w := lastHeader.Get(protocolVersionHeader), latestProtocolVersion; g != w {
|
||||
// Stateful HTTP requires the legacy protocol (see the earlier
|
||||
// initializeResult assertion); the client sends the matching
|
||||
// MCP-Protocol-Version header.
|
||||
if g, w := lastHeader.Get(protocolVersionHeader), protocolVersion20251125; g != w {
|
||||
t.Errorf("got protocol version header %q, want %q", g, w)
|
||||
}
|
||||
want := &CallToolResult{
|
||||
|
|
@ -332,10 +339,15 @@ func TestStreamableServerShutdown(t *testing.T) {
|
|||
opts.KeepAlive = 50 * time.Millisecond
|
||||
}
|
||||
client := NewClient(testImpl, &opts)
|
||||
// Pin to 2025-11-25: this test relies on the legacy hanging GET
|
||||
// (stateful case) or Ping-based keepalive (stateless case) for
|
||||
// the client to notice the server shutdown. Both mechanisms are
|
||||
// removed in 2026-07-28 (SEP-2575), so the new protocol would
|
||||
// leave the client waiting forever.
|
||||
clientSession, err := client.Connect(ctx, &StreamableClientTransport{
|
||||
Endpoint: httpServer.URL,
|
||||
MaxRetries: -1, // avoid slow tests during exponential retries
|
||||
}, nil)
|
||||
}, &ClientSessionOptions{protocolVersion: protocolVersion20251125})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
|
@ -680,7 +692,12 @@ func TestServerTransportCleanup(t *testing.T) {
|
|||
}
|
||||
}
|
||||
client.AddReceivingMiddleware(pingMiddleware)
|
||||
clientSession, err := client.Connect(ctx, &StreamableClientTransport{Endpoint: httpServer.URL}, nil)
|
||||
// Pin to 2025-11-25: this test counts server-side sessions, but
|
||||
// the 2026-07-28 client probes server/discover first against a
|
||||
// stateful HTTP server (which rejects the new protocol), opening
|
||||
// an extra HTTP connection and therefore an extra session. Pinning
|
||||
// to 2025-11-25 skips the discover probe.
|
||||
clientSession, err := client.Connect(ctx, &StreamableClientTransport{Endpoint: httpServer.URL}, &ClientSessionOptions{protocolVersion: protocolVersion20251125})
|
||||
if err != nil {
|
||||
t.Fatalf("client.Connect() failed: %v", err)
|
||||
}
|
||||
|
|
@ -1636,7 +1653,7 @@ func TestStreamableStateless(t *testing.T) {
|
|||
Logging: &LoggingCapabilities{},
|
||||
Tools: &ToolCapabilities{ListChanged: true},
|
||||
},
|
||||
ProtocolVersion: latestProtocolVersion,
|
||||
ProtocolVersion: protocolVersion20251125,
|
||||
ServerInfo: &Implementation{Name: "test", Version: "v1.0.0"},
|
||||
}, nil)
|
||||
// This version of sayHi expects
|
||||
|
|
@ -1904,9 +1921,6 @@ func TestStreamableGET(t *testing.T) {
|
|||
// as specified in SEP-2243.
|
||||
func TestStreamableMcpHeaderValidation(t *testing.T) {
|
||||
// Temporarily register the future version so the handler accepts it.
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append(slices.Clone(orig), minVersionForStandardHeaders)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
server := NewServer(&Implementation{Name: "testServer", Version: "v1.0.0"}, nil)
|
||||
server.AddTool(
|
||||
|
|
@ -2055,9 +2069,6 @@ func TestStreamableMcpHeaderValidation(t *testing.T) {
|
|||
// validation errors return a JSON-RPC error with code -32020 and
|
||||
// Content-Type application/json, per SEP-2243.
|
||||
func TestStreamableMcpHeaderValidationErrorFormat(t *testing.T) {
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append(slices.Clone(orig), minVersionForStandardHeaders)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
server := NewServer(&Implementation{Name: "testServer", Version: "v1.0.0"}, nil)
|
||||
server.AddTool(
|
||||
|
|
@ -2204,9 +2215,6 @@ func TestStreamableMcpHeaderVersionGating(t *testing.T) {
|
|||
// TestStreamableParamHeadersClientSetsHeaders verifies that the client sets
|
||||
// Mcp-Param-* headers on tool calls when the tool has x-mcp-header annotations.
|
||||
func TestStreamableParamHeadersClientSetsHeaders(t *testing.T) {
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append(slices.Clone(orig), minVersionForStandardHeaders)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
server := NewServer(&Implementation{Name: "testServer", Version: "v1.0.0"}, nil)
|
||||
server.AddTool(
|
||||
|
|
@ -2306,9 +2314,6 @@ func TestStreamableParamHeadersClientSetsHeaders(t *testing.T) {
|
|||
// tools with invalid x-mcp-header annotations at registration time, and
|
||||
// that valid tools are returned by ListTools.
|
||||
func TestStreamableFilterValidToolsIntegration(t *testing.T) {
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append(slices.Clone(orig), minVersionForStandardHeaders)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
server := NewServer(&Implementation{Name: "testServer", Version: "v1.0.0"}, nil)
|
||||
noop := func(ctx context.Context, req *CallToolRequest) (*CallToolResult, error) {
|
||||
|
|
@ -2624,8 +2629,12 @@ func TestStreamableSessionTimeout(t *testing.T) {
|
|||
defer httpServer.Close()
|
||||
|
||||
// Connect a client to create a session.
|
||||
// Pin to 2025-11-25: under 2026-07-28 the client probes server/discover
|
||||
// first against a stateful HTTP server (which rejects the new
|
||||
// protocol), opening an extra HTTP connection and therefore an extra
|
||||
// session. Pinning to 2025-11-25 skips the discover probe.
|
||||
client := NewClient(testImpl, nil)
|
||||
session, err := client.Connect(ctx, &StreamableClientTransport{Endpoint: httpServer.URL}, nil)
|
||||
session, err := client.Connect(ctx, &StreamableClientTransport{Endpoint: httpServer.URL}, &ClientSessionOptions{protocolVersion: protocolVersion20251125})
|
||||
if err != nil {
|
||||
t.Fatalf("client.Connect() failed: %v", err)
|
||||
}
|
||||
|
|
@ -2915,7 +2924,11 @@ func Test_ExportErrSessionMissing(t *testing.T) {
|
|||
Endpoint: ts.URL,
|
||||
}
|
||||
client := NewClient(impl, nil)
|
||||
session, err := client.Connect(ctx, clientTransport, nil)
|
||||
// Pin to 2025-11-25: under 2026-07-28 the client probes server/discover
|
||||
// first against a stateful HTTP server (which rejects the new
|
||||
// protocol), opening an extra HTTP connection and therefore an extra
|
||||
// session. Pinning to 2025-11-25 skips the discover probe.
|
||||
session, err := client.Connect(ctx, clientTransport, &ClientSessionOptions{protocolVersion: protocolVersion20251125})
|
||||
if err != nil {
|
||||
t.Fatalf("Connect failed: %v", err)
|
||||
}
|
||||
|
|
@ -3355,9 +3368,6 @@ func TestStreamableStateless_NewProtocolSession_NoFakeInit(t *testing.T) {
|
|||
// SEP-2575: the MCP-Protocol-Version header is mandatory for new-protocol
|
||||
// requests and must be a supported version. The 2026-07-28 version is
|
||||
// not yet in the global list, so register it for the duration of the test.
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append(slices.Clone(orig), protocolVersion20260728)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
capture := &statelessHandlerCapture{}
|
||||
mcpServer := NewServer(testImpl, nil)
|
||||
|
|
@ -3432,9 +3442,6 @@ func TestStreamableStateless_NewProtocolSession_NoFakeInit(t *testing.T) {
|
|||
func TestStreamableStateful_RejectsNewProtocol(t *testing.T) {
|
||||
// Make 2026-07-28 a "known" version so that the request reaches servePOST
|
||||
// (otherwise the early header validation at ServeHTTP rejects it).
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append(slices.Clone(orig), protocolVersion20260728)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
server := NewServer(testImpl, nil)
|
||||
AddTool(server, &Tool{Name: "noop"},
|
||||
|
|
@ -3497,9 +3504,6 @@ func TestStreamableStateful_RejectsNewProtocol(t *testing.T) {
|
|||
// (the rejection in TestStreamableStateful_RejectsNewProtocol must not
|
||||
// fire on Stateless: true).
|
||||
func TestStreamableStateless_AcceptsNewProtocol(t *testing.T) {
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append(slices.Clone(orig), protocolVersion20260728)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
server := NewServer(testImpl, nil)
|
||||
AddTool(server, &Tool{Name: "noop"},
|
||||
|
|
@ -3536,15 +3540,22 @@ func TestStreamableStateless_AcceptsNewProtocol(t *testing.T) {
|
|||
}
|
||||
|
||||
// TestStreamableClientUnsupportedVersionFallback exercises the full
|
||||
// SEP-2575 fallback. The client requests protocolVersion20260728, which the server does
|
||||
// not advertise in supportedProtocolVersions. The server therefore rejects
|
||||
// the server/discover POST at the transport-level header validation with a
|
||||
// plain HTTP 400 ("Bad Request: Unsupported protocol version ..."). The
|
||||
// streamable client must recognize this body, keep the connection alive, and
|
||||
// successfully complete the legacy initialize handshake.
|
||||
//
|
||||
// TODO: once 20260630 is part of supportedProtocolVersion on server side, modify the list in the test to keep it consistent.
|
||||
// SEP-2575 fallback. The client requests protocolVersion20260728, which the
|
||||
// server is configured to NOT advertise in supportedProtocolVersions for the
|
||||
// duration of this test. The server therefore rejects the server/discover
|
||||
// POST at the transport-level header validation with a plain HTTP 400 ("Bad
|
||||
// Request: Unsupported protocol version ..."). The streamable client must
|
||||
// recognize this body, keep the connection alive, and successfully complete
|
||||
// the legacy initialize handshake.
|
||||
func TestStreamableClientUnsupportedVersionFallback(t *testing.T) {
|
||||
// Remove protocolVersion20260728 from the server-side supported list so
|
||||
// the fallback path is exercised.
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = slices.DeleteFunc(slices.Clone(orig), func(v string) bool {
|
||||
return v == protocolVersion20260728
|
||||
})
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
ctx := context.Background()
|
||||
|
||||
server := NewServer(testImpl, nil)
|
||||
|
|
@ -3568,9 +3579,11 @@ func TestStreamableClientUnsupportedVersionFallback(t *testing.T) {
|
|||
if ir == nil {
|
||||
t.Fatal("InitializeResult is nil; expected legacy initialize to populate it")
|
||||
}
|
||||
if ir.ProtocolVersion != latestProtocolVersion {
|
||||
// The fallback initialize explicitly requests protocolVersion20251125
|
||||
// (see client.go), so the server negotiates that version.
|
||||
if ir.ProtocolVersion != protocolVersion20251125 {
|
||||
t.Errorf("InitializeResult.ProtocolVersion = %q, want %q (legacy fallback)",
|
||||
ir.ProtocolVersion, latestProtocolVersion)
|
||||
ir.ProtocolVersion, protocolVersion20251125)
|
||||
}
|
||||
|
||||
// Verify the session is fully usable after the fallback by issuing a
|
||||
|
|
@ -3587,9 +3600,6 @@ func TestStreamableClientUnsupportedVersionFallback(t *testing.T) {
|
|||
// the server with the new protocol version to learn which versions are
|
||||
// supported.
|
||||
func TestStreamableStateful_AcceptsDiscover(t *testing.T) {
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append(slices.Clone(orig), protocolVersion20260728)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
server := NewServer(testImpl, nil)
|
||||
handler := NewStreamableHTTPHandler(func(*http.Request) *Server { return server }, nil)
|
||||
|
|
@ -3666,10 +3676,6 @@ func TestStreamableStateful_AcceptsDiscover(t *testing.T) {
|
|||
// SEP-2575 over the streamable HTTP transport.
|
||||
func TestStreamableHTTP_E2E_DiscoverSuccess(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
orig := supportedProtocolVersions
|
||||
supportedProtocolVersions = append([]string{protocolVersion20260728}, slices.Clone(orig)...)
|
||||
t.Cleanup(func() { supportedProtocolVersions = orig })
|
||||
|
||||
server := NewServer(&Implementation{Name: "e2e-server", Version: "v1"}, nil)
|
||||
// Register a simple tool so we can prove the session is usable end-to-end.
|
||||
AddTool(server, &Tool{Name: "echo", Description: "echoes its input"},
|
||||
|
|
|
|||
39
mcp/testdata/conformance/server/discover.txtar
vendored
Normal file
39
mcp/testdata/conformance/server/discover.txtar
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
The client sends a discover request
|
||||
carrying the required per-request _meta triple (protocolVersion, clientInfo,
|
||||
clientCapabilities), and the server replies with the protocol versions it
|
||||
supports, its capabilities, and its identity.
|
||||
|
||||
-- client --
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "server/discover",
|
||||
"params": {
|
||||
"_meta": {
|
||||
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
|
||||
"io.modelcontextprotocol/clientInfo": { "name": "ExampleClient", "version": "1.0.0" },
|
||||
"io.modelcontextprotocol/clientCapabilities": {}
|
||||
}
|
||||
}
|
||||
}
|
||||
-- server --
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": {
|
||||
"supportedVersions": [
|
||||
"2026-07-28",
|
||||
"2025-11-25",
|
||||
"2025-06-18",
|
||||
"2025-03-26",
|
||||
"2024-11-05"
|
||||
],
|
||||
"capabilities": {
|
||||
"logging": {}
|
||||
},
|
||||
"serverInfo": {
|
||||
"name": "testServer",
|
||||
"version": "v1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
85
mcp/testdata/conformance/server/mrtr.txtar
vendored
Normal file
85
mcp/testdata/conformance/server/mrtr.txtar
vendored
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
Check the multi-round-trip request pattern under protocol version
|
||||
2026-07-28. The server's tool handler returns an
|
||||
InputRequiredResult on the first call, requesting elicitation input from the
|
||||
client and providing an opaque RequestState token. The client fulfills the
|
||||
input request and retries the tool call with the responses and the echoed
|
||||
RequestState; the handler then returns the final result.
|
||||
|
||||
-- tools --
|
||||
confirmThenGreet
|
||||
-- client --
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"_meta": {
|
||||
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
|
||||
"io.modelcontextprotocol/clientInfo": { "name": "ExampleClient", "version": "1.0.0" },
|
||||
"io.modelcontextprotocol/clientCapabilities": { "elicitation": {} }
|
||||
},
|
||||
"name": "confirmThenGreet",
|
||||
"arguments": {}
|
||||
}
|
||||
}
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"_meta": {
|
||||
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
|
||||
"io.modelcontextprotocol/clientInfo": { "name": "ExampleClient", "version": "1.0.0" },
|
||||
"io.modelcontextprotocol/clientCapabilities": { "elicitation": {} }
|
||||
},
|
||||
"name": "confirmThenGreet",
|
||||
"arguments": {},
|
||||
"requestState": "step=1",
|
||||
"inputResponses": {
|
||||
"who": {
|
||||
"method": "elicitation/create",
|
||||
"result": { "action": "accept", "content": { "name": "MCP Go" } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
-- server --
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": {
|
||||
"content": null,
|
||||
"requestState": "step=1",
|
||||
"resultType": "input_required",
|
||||
"inputRequests": {
|
||||
"who": {
|
||||
"method": "elicitation/create",
|
||||
"params": {
|
||||
"mode": "",
|
||||
"message": "What is your name?",
|
||||
"requestedSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"result": {
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
"text": "hello MCP Go"
|
||||
}
|
||||
],
|
||||
"resultType": "complete"
|
||||
}
|
||||
}
|
||||
|
|
@ -42,9 +42,8 @@ func ExampleLoggingTransport() {
|
|||
}
|
||||
|
||||
// Output:
|
||||
// read: {"jsonrpc":"2.0","id":1,"result":{"capabilities":{"logging":{}},"protocolVersion":"2025-11-25","serverInfo":{"name":"server","version":"v0.0.1"}}}
|
||||
// write: {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"clientInfo":{"name":"client","version":"v0.0.1"},"protocolVersion":"2025-11-25","capabilities":{"roots":{"listChanged":true}}}}
|
||||
// write: {"jsonrpc":"2.0","method":"notifications/initialized","params":{}}
|
||||
// read: {"jsonrpc":"2.0","id":1,"result":{"supportedVersions":["2026-07-28","2025-11-25","2025-06-18","2025-03-26","2024-11-05"],"capabilities":{"logging":{}},"serverInfo":{"name":"server","version":"v0.0.1"}}}
|
||||
// write: {"jsonrpc":"2.0","id":1,"method":"server/discover","params":{"_meta":{"io.modelcontextprotocol/clientCapabilities":{"roots":{"listChanged":true}},"io.modelcontextprotocol/clientInfo":{"name":"client","version":"v0.0.1"},"io.modelcontextprotocol/protocolVersion":"2026-07-28"}}}
|
||||
}
|
||||
|
||||
// !-loggingtransport
|
||||
|
|
|
|||
Loading…
Reference in a new issue