mcp: update return meta in DiscoverResult (SEP-2575) (#1097)
This PR implements the latest changes introduced by https://github.com/modelcontextprotocol/modelcontextprotocol/pull/3002
This commit is contained in:
parent
fa548d7401
commit
5cb8339210
17 changed files with 210 additions and 68 deletions
|
|
@ -1,7 +1,21 @@
|
|||
server: [] # All tests pass!
|
||||
server:
|
||||
# The `server-stateless` scenario ships with the `@modelcontextprotocol/conformance@0.2.0-alpha.9`
|
||||
# suite pinned by the workflow, which still enforces the pre-PR-3002
|
||||
# (https://github.com/modelcontextprotocol/modelcontextprotocol/pull/3002) spec:
|
||||
# - `clientInfo` in the per-request `_meta` is required (MUST).
|
||||
# - `serverInfo` is a body field on `DiscoverResult`.
|
||||
# PR #3002 relaxes `clientInfo` to SHOULD and moves `serverInfo` into every
|
||||
# response's `_meta`. The Go SDK follows the updated spec, so the following
|
||||
# checks in the `server-stateless` scenario fail against the pinned suite:
|
||||
# - sep-2575-request-meta-invalid-missing-client-info
|
||||
# - sep-2575-http-server-meta-invalid-400
|
||||
# - sep-2575-server-implements-discover
|
||||
# Remove this entry when the conformance suite is bumped to a release that
|
||||
# incorporates PR #3002.
|
||||
- server-stateless
|
||||
|
||||
client:
|
||||
# OAuth mechanisms not yet implemented by the Go MCP SDK.
|
||||
# OAuth mechanisms not implemented by the Go MCP SDK.
|
||||
- auth/client-credentials-basic
|
||||
- auth/client-credentials-jwt
|
||||
- auth/enterprise-managed-authorization
|
||||
|
|
@ -10,4 +24,6 @@ client:
|
|||
# server/discover handler advertises "2026-07-28" but delegates every
|
||||
# other method to a pinned @modelcontextprotocol/sdk that rejects that
|
||||
# version at the HTTP layer.
|
||||
# Remove this entry when the conformance suite is bumped to a release that
|
||||
# fixes this issue.
|
||||
- json-schema-ref-no-deref
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
1. [Lifecycle](#lifecycle)
|
||||
1. [Discovery (`server/discover`)](#discovery-(server/discover))
|
||||
1. [Per-request `_meta` keys](#per-request-meta-keys)
|
||||
1. [Per-response `_meta` keys](#per-response-meta-keys)
|
||||
1. [Subscriptions (`subscriptions/listen`)](#subscriptions-(subscriptions/listen))
|
||||
1. [Transports](#transports)
|
||||
1. [Stdio Transport](#stdio-transport)
|
||||
|
|
@ -40,8 +41,10 @@ transparently based on the negotiated protocol version:
|
|||
- A **stateless** model introduced in `2026-07-28` by
|
||||
[SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575),
|
||||
in which there is no `initialize`/`notifications/initialized` handshake, and
|
||||
each request carries its protocol version, client identity, and client
|
||||
capabilities in `_meta`.
|
||||
each request carries its protocol version and client capabilities in
|
||||
`_meta`. Clients SHOULD also include their identity (`clientInfo`) on every
|
||||
request, and servers SHOULD include their identity (`serverInfo`) on every
|
||||
response.
|
||||
|
||||
In both models, the SDK exposes the same API:
|
||||
|
||||
|
|
@ -130,17 +133,28 @@ When the negotiated protocol version is `2026-07-28` or later, every request
|
|||
carries these keys inside its `_meta` map (constants live in
|
||||
[`mcp/protocol.go`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#pkg-constants)):
|
||||
|
||||
| Constant | Wire key | Type |
|
||||
|---|---|---|
|
||||
| `MetaKeyProtocolVersion` | `io.modelcontextprotocol/protocolVersion` | `string` |
|
||||
| `MetaKeyClientInfo` | `io.modelcontextprotocol/clientInfo` | `*Implementation` |
|
||||
| `MetaKeyClientCapabilities` | `io.modelcontextprotocol/clientCapabilities` | `*ClientCapabilities` |
|
||||
| `MetaKeyLogLevel` | `io.modelcontextprotocol/logLevel` | `LoggingLevel` (deprecated by SEP-2577) |
|
||||
| Constant | Wire key | Type | Required |
|
||||
|---|---|---|---|
|
||||
| `MetaKeyProtocolVersion` | `io.modelcontextprotocol/protocolVersion` | `string` | Yes |
|
||||
| `MetaKeyClientCapabilities` | `io.modelcontextprotocol/clientCapabilities` | `*ClientCapabilities` | Yes |
|
||||
| `MetaKeyClientInfo` | `io.modelcontextprotocol/clientInfo` | `*Implementation` | No |
|
||||
| `MetaKeyLogLevel` | `io.modelcontextprotocol/logLevel` | `LoggingLevel` (deprecated by SEP-2577) | No |
|
||||
|
||||
The client populates these keys automatically on every outgoing request.
|
||||
Server-side handlers can read them
|
||||
through `ServerRequest[P].ProtocolVersion()`, `ServerRequest[P].ClientInfo()`,
|
||||
and `ServerRequest[P].ClientCapabilities()`.
|
||||
The client populates the required keys automatically on every outgoing
|
||||
request, and populates `clientInfo` when configured with an `*Implementation`
|
||||
(the default). Server-side handlers can read them through
|
||||
`ServerRequest[P].ProtocolVersion()`, `ServerRequest[P].ClientInfo()`, and
|
||||
`ServerRequest[P].ClientCapabilities()`.
|
||||
|
||||
### Per-response `_meta` keys
|
||||
|
||||
Under the same protocol version, servers SHOULD identify themselves on every
|
||||
response. The SDK populates this
|
||||
key automatically on every outgoing response:
|
||||
|
||||
| Constant | Wire key | Type | Required |
|
||||
|---|---|---|---|
|
||||
| `MetaKeyServerInfo` | `io.modelcontextprotocol/serverInfo` | `*Implementation` | No |
|
||||
|
||||
### Subscriptions (`subscriptions/listen`)
|
||||
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ func ExampleLoggingTransport() {
|
|||
}
|
||||
|
||||
// Output:
|
||||
// read: {"jsonrpc":"2.0","id":1,"result":{"resultType":"complete","ttlMs":0,"cacheScope":"public","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"}}}
|
||||
// read: {"jsonrpc":"2.0","id":1,"result":{"resultType":"complete","_meta":{"io.modelcontextprotocol/serverInfo":{"name":"server","version":"v0.0.1"}},"ttlMs":0,"cacheScope":"public","supportedVersions":["2026-07-28","2025-11-25","2025-06-18","2025-03-26","2024-11-05"],"capabilities":{"logging":{}}}}
|
||||
// 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"}}}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -17,8 +17,10 @@ transparently based on the negotiated protocol version:
|
|||
- A **stateless** model introduced in `2026-07-28` by
|
||||
[SEP-2575](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/2575),
|
||||
in which there is no `initialize`/`notifications/initialized` handshake, and
|
||||
each request carries its protocol version, client identity, and client
|
||||
capabilities in `_meta`.
|
||||
each request carries its protocol version and client capabilities in
|
||||
`_meta`. Clients SHOULD also include their identity (`clientInfo`) on every
|
||||
request, and servers SHOULD include their identity (`serverInfo`) on every
|
||||
response.
|
||||
|
||||
In both models, the SDK exposes the same API:
|
||||
|
||||
|
|
@ -76,17 +78,28 @@ When the negotiated protocol version is `2026-07-28` or later, every request
|
|||
carries these keys inside its `_meta` map (constants live in
|
||||
[`mcp/protocol.go`](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk/mcp#pkg-constants)):
|
||||
|
||||
| Constant | Wire key | Type |
|
||||
|---|---|---|
|
||||
| `MetaKeyProtocolVersion` | `io.modelcontextprotocol/protocolVersion` | `string` |
|
||||
| `MetaKeyClientInfo` | `io.modelcontextprotocol/clientInfo` | `*Implementation` |
|
||||
| `MetaKeyClientCapabilities` | `io.modelcontextprotocol/clientCapabilities` | `*ClientCapabilities` |
|
||||
| `MetaKeyLogLevel` | `io.modelcontextprotocol/logLevel` | `LoggingLevel` (deprecated by SEP-2577) |
|
||||
| Constant | Wire key | Type | Required |
|
||||
|---|---|---|---|
|
||||
| `MetaKeyProtocolVersion` | `io.modelcontextprotocol/protocolVersion` | `string` | Yes |
|
||||
| `MetaKeyClientCapabilities` | `io.modelcontextprotocol/clientCapabilities` | `*ClientCapabilities` | Yes |
|
||||
| `MetaKeyClientInfo` | `io.modelcontextprotocol/clientInfo` | `*Implementation` | No |
|
||||
| `MetaKeyLogLevel` | `io.modelcontextprotocol/logLevel` | `LoggingLevel` (deprecated by SEP-2577) | No |
|
||||
|
||||
The client populates these keys automatically on every outgoing request.
|
||||
Server-side handlers can read them
|
||||
through `ServerRequest[P].ProtocolVersion()`, `ServerRequest[P].ClientInfo()`,
|
||||
and `ServerRequest[P].ClientCapabilities()`.
|
||||
The client populates the required keys automatically on every outgoing
|
||||
request, and populates `clientInfo` when configured with an `*Implementation`
|
||||
(the default). Server-side handlers can read them through
|
||||
`ServerRequest[P].ProtocolVersion()`, `ServerRequest[P].ClientInfo()`, and
|
||||
`ServerRequest[P].ClientCapabilities()`.
|
||||
|
||||
### Per-response `_meta` keys
|
||||
|
||||
Under the same protocol version, servers SHOULD identify themselves on every
|
||||
response. The SDK populates this
|
||||
key automatically on every outgoing response:
|
||||
|
||||
| Constant | Wire key | Type | Required |
|
||||
|---|---|---|---|
|
||||
| `MetaKeyServerInfo` | `io.modelcontextprotocol/serverInfo` | `*Implementation` | No |
|
||||
|
||||
### Subscriptions (`subscriptions/listen`)
|
||||
|
||||
|
|
|
|||
|
|
@ -443,11 +443,15 @@ func (c *Client) discover(ctx context.Context, cs *ClientSession) (*InitializeRe
|
|||
}
|
||||
}
|
||||
|
||||
var serverInfo *Implementation
|
||||
if v, ok := decodeMetaValue[*Implementation](res.GetMeta(), MetaKeyServerInfo); ok {
|
||||
serverInfo = v
|
||||
}
|
||||
return &InitializeResult{
|
||||
Capabilities: res.Capabilities,
|
||||
Instructions: res.Instructions,
|
||||
ProtocolVersion: negotiated,
|
||||
ServerInfo: res.ServerInfo,
|
||||
ServerInfo: serverInfo,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -509,9 +513,12 @@ func (cs *ClientSession) usesNewProtocol() bool {
|
|||
return res != nil && res.ProtocolVersion >= protocolVersion20260728
|
||||
}
|
||||
|
||||
// injectRequestMeta populates the SEP-2575 per-request `_meta` triple
|
||||
// (protocolVersion, clientInfo, clientCapabilities) on the given outgoing
|
||||
// request params. Keys already present in params.Meta are not overwritten.
|
||||
// injectRequestMeta populates the SEP-2575 per-request `_meta` fields
|
||||
// (protocolVersion, optional clientInfo, clientCapabilities) on the given
|
||||
// outgoing request params. Keys already present in params.Meta are not
|
||||
// overwritten. Per PR modelcontextprotocol/modelcontextprotocol#3002
|
||||
// clientInfo is SHOULD (not MUST), and is omitted when the client has no
|
||||
// [Implementation] configured.
|
||||
func injectRequestMeta[T any, P interface {
|
||||
*T
|
||||
Params
|
||||
|
|
@ -527,7 +534,7 @@ func injectRequestMeta[T any, P interface {
|
|||
if _, ok := m[MetaKeyProtocolVersion]; !ok {
|
||||
m[MetaKeyProtocolVersion] = res.ProtocolVersion
|
||||
}
|
||||
if _, ok := m[MetaKeyClientInfo]; !ok {
|
||||
if _, ok := m[MetaKeyClientInfo]; !ok && cs.client.impl != nil {
|
||||
m[MetaKeyClientInfo] = cs.client.impl
|
||||
}
|
||||
if _, ok := m[MetaKeyClientCapabilities]; !ok {
|
||||
|
|
|
|||
|
|
@ -662,11 +662,13 @@ func TestClientConnectDiscover(t *testing.T) {
|
|||
name: "discover success skips initialize",
|
||||
discoverHandler: func() (Result, error) {
|
||||
return &DiscoverResult{
|
||||
Meta: Meta{
|
||||
MetaKeyServerInfo: &Implementation{Name: "discoverServer", Version: "v1.0.0"},
|
||||
},
|
||||
SupportedVersions: []string{protocolVersion20260728},
|
||||
Capabilities: &ServerCapabilities{
|
||||
Tools: &ToolCapabilities{ListChanged: true},
|
||||
},
|
||||
ServerInfo: &Implementation{Name: "discoverServer", Version: "v1.0.0"},
|
||||
}, nil
|
||||
},
|
||||
wantInitialize: false,
|
||||
|
|
@ -695,9 +697,11 @@ func TestClientConnectDiscover(t *testing.T) {
|
|||
name: "no overlapping supported version falls back to initialize",
|
||||
discoverHandler: func() (Result, error) {
|
||||
return &DiscoverResult{
|
||||
Meta: Meta{
|
||||
MetaKeyServerInfo: &Implementation{Name: "discoverServer", Version: "v1.0.0"},
|
||||
},
|
||||
SupportedVersions: []string{otherVersionsOnly},
|
||||
Capabilities: &ServerCapabilities{},
|
||||
ServerInfo: &Implementation{Name: "discoverServer", Version: "v1.0.0"},
|
||||
}, nil
|
||||
},
|
||||
wantInitialize: true,
|
||||
|
|
@ -885,9 +889,11 @@ func TestInMemory_E2E_DiscoverFallback_NoOverlap(t *testing.T) {
|
|||
return func(ctx context.Context, method string, req Request) (Result, error) {
|
||||
if method == methodDiscover {
|
||||
return &DiscoverResult{
|
||||
Meta: Meta{
|
||||
MetaKeyServerInfo: &Implementation{Name: "vpre-like-server", Version: "v1"},
|
||||
},
|
||||
SupportedVersions: []string{protocolVersion20251125},
|
||||
Capabilities: &ServerCapabilities{},
|
||||
ServerInfo: &Implementation{Name: "vpre-like-server", Version: "v1"},
|
||||
}, nil
|
||||
}
|
||||
return next(ctx, method, req)
|
||||
|
|
|
|||
|
|
@ -224,4 +224,12 @@ func TestContentUnmarshalNilWithInvalidContent(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
var ctrCmpOpts = []cmp.Option{cmpopts.IgnoreUnexported(mcp.CallToolResult{}, mcp.GetPromptResult{}, mcp.ReadResourceResult{})}
|
||||
var ctrCmpOpts = []cmp.Option{
|
||||
cmpopts.IgnoreUnexported(mcp.CallToolResult{}, mcp.GetPromptResult{}, mcp.ReadResourceResult{}),
|
||||
// Server responses under the >= 2026-07-28 protocol carry an auto-populated
|
||||
// [mcp.MetaKeyServerInfo] entry; tests that compare result bodies against
|
||||
// hand-crafted expected values should ignore it.
|
||||
cmpopts.IgnoreFields(mcp.CallToolResult{}, "Meta"),
|
||||
cmpopts.IgnoreFields(mcp.GetPromptResult{}, "Meta"),
|
||||
cmpopts.IgnoreFields(mcp.ReadResourceResult{}, "Meta"),
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2475,7 +2475,15 @@ func TestSetErrorPreservesContent(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
var ctrCmpOpts = []cmp.Option{cmpopts.IgnoreUnexported(CallToolResult{}, GetPromptResult{}, ReadResourceResult{})}
|
||||
var ctrCmpOpts = []cmp.Option{
|
||||
cmpopts.IgnoreUnexported(CallToolResult{}, GetPromptResult{}, ReadResourceResult{}),
|
||||
// Server responses under the >= 2026-07-28 protocol carry an auto-populated
|
||||
// [MetaKeyServerInfo] entry; tests that compare result bodies against
|
||||
// hand-crafted expected values should ignore it.
|
||||
cmpopts.IgnoreFields(CallToolResult{}, "Meta"),
|
||||
cmpopts.IgnoreFields(GetPromptResult{}, "Meta"),
|
||||
cmpopts.IgnoreFields(ReadResourceResult{}, "Meta"),
|
||||
}
|
||||
|
||||
// runSubscriptionsListenTest exercises the SEP-2575 auto-listen flow end-to-end
|
||||
// against the supplied transport pair. It captures every notification and the
|
||||
|
|
|
|||
|
|
@ -1143,8 +1143,6 @@ type DiscoverResult struct {
|
|||
SupportedVersions []string `json:"supportedVersions"`
|
||||
// The server's capabilities.
|
||||
Capabilities *ServerCapabilities `json:"capabilities"`
|
||||
// Information about the server implementation.
|
||||
ServerInfo *Implementation `json:"serverInfo"`
|
||||
// Instructions describing how to use the server and its features.
|
||||
Instructions string `json:"instructions,omitempty"`
|
||||
}
|
||||
|
|
@ -2365,6 +2363,8 @@ const (
|
|||
MetaKeyProtocolVersion = "io.modelcontextprotocol/protocolVersion"
|
||||
// MetaKeyClientInfo carries the client's [Implementation].
|
||||
MetaKeyClientInfo = "io.modelcontextprotocol/clientInfo"
|
||||
// MetaKeyServerInfo carries the server's [Implementation] on responses.
|
||||
MetaKeyServerInfo = "io.modelcontextprotocol/serverInfo"
|
||||
// MetaKeyClientCapabilities carries the client's [ClientCapabilities].
|
||||
MetaKeyClientCapabilities = "io.modelcontextprotocol/clientCapabilities"
|
||||
// MetaKeyLogLevel identifies the desired log level for the request.
|
||||
|
|
|
|||
|
|
@ -888,17 +888,21 @@ func (s *Server) discover(_ context.Context, req *ServerRequest[*DiscoverParams]
|
|||
if versions == nil {
|
||||
versions = slices.Clone(supportedProtocolVersions)
|
||||
}
|
||||
// Read the request-scoped identity/capabilities before acquiring the
|
||||
// session lock: these accessors may fall back to Session.InitializeParams
|
||||
// (which also locks Session.mu), so calling them from inside updateState
|
||||
// would self-deadlock.
|
||||
init := &InitializeParams{
|
||||
ProtocolVersion: req.ProtocolVersion(),
|
||||
Capabilities: req.ClientCapabilities(),
|
||||
ClientInfo: req.ClientInfo(),
|
||||
}
|
||||
req.Session.updateState(func(state *ServerSessionState) {
|
||||
state.InitializeParams = &InitializeParams{
|
||||
ProtocolVersion: req.ProtocolVersion(),
|
||||
Capabilities: req.ClientCapabilities(),
|
||||
ClientInfo: req.ClientInfo(),
|
||||
}
|
||||
state.InitializeParams = init
|
||||
})
|
||||
res := &DiscoverResult{
|
||||
SupportedVersions: versions,
|
||||
Capabilities: s.capabilities(),
|
||||
ServerInfo: s.impl,
|
||||
Instructions: s.opts.Instructions,
|
||||
}
|
||||
res.setDefaultCacheableValues()
|
||||
|
|
@ -1924,10 +1928,32 @@ func (ss *ServerSession) handle(ctx context.Context, req *jsonrpc.Request) (any,
|
|||
}
|
||||
if validatedMeta.usesNewProtocol {
|
||||
setCompleteResultType(res)
|
||||
annotateServerInfo(res, ss.server.impl)
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// annotateServerInfo sets [MetaKeyServerInfo] on the result's `_meta` unless
|
||||
// it is already present. Per SEP-2575, servers should identify themselves in
|
||||
// each result's `_meta`.
|
||||
func annotateServerInfo(res Result, impl *Implementation) {
|
||||
if res == nil || impl == nil {
|
||||
return
|
||||
}
|
||||
if _, isEmpty := res.(*emptyResult); isEmpty {
|
||||
return
|
||||
}
|
||||
m := res.GetMeta()
|
||||
if m == nil {
|
||||
m = map[string]any{}
|
||||
}
|
||||
if _, ok := m[MetaKeyServerInfo]; ok {
|
||||
return
|
||||
}
|
||||
m[MetaKeyServerInfo] = impl
|
||||
res.SetMeta(m)
|
||||
}
|
||||
|
||||
// InitializeParams returns the InitializeParams provided during the client's
|
||||
// initial connection.
|
||||
func (ss *ServerSession) InitializeParams() *InitializeParams {
|
||||
|
|
|
|||
|
|
@ -535,9 +535,10 @@ type validatedMeta struct {
|
|||
// the >= 2026-07-28 protocol via the `_meta` field.
|
||||
// If the request has no _meta, or no protocolVersion in _meta, it returns a non-nil
|
||||
// validatedMeta with usesNewProtocol set to false, and a nil error.
|
||||
// If the request has a protocolVersion in _meta it validates the presence of clientInfo
|
||||
// and clientCapabilities in _meta. If either is missing or invalid, it returns nil and
|
||||
// a non-nil error. Otherwise, it returns usesNewProtocol set to true and the populated
|
||||
// If the request has a protocolVersion in _meta it validates the presence of
|
||||
// clientCapabilities in _meta. If it is missing or invalid, it returns nil and
|
||||
// a non-nil error. clientInfo is optional; if present but invalid, an error is
|
||||
// returned. Otherwise, it returns usesNewProtocol set to true and the populated
|
||||
// initializeParams.
|
||||
func validateRequestMeta(req *jsonrpc.Request) (*validatedMeta, error) {
|
||||
meta := extractRequestMeta(req.Params)
|
||||
|
|
@ -548,11 +549,15 @@ func validateRequestMeta(req *jsonrpc.Request) (*validatedMeta, error) {
|
|||
if !ok || protocolVersion < protocolVersion20260728 {
|
||||
return &validatedMeta{usesNewProtocol: false, initializeParams: nil}, nil
|
||||
}
|
||||
clientInfo, ok := decodeMetaValue[*Implementation](meta, MetaKeyClientInfo)
|
||||
if !ok {
|
||||
return nil, &jsonrpc.Error{
|
||||
Code: jsonrpc.CodeInvalidParams,
|
||||
Message: fmt.Sprintf("missing or invalid _meta field %q", MetaKeyClientInfo),
|
||||
var clientInfo *Implementation
|
||||
if _, present := meta[MetaKeyClientInfo]; present {
|
||||
var ok bool
|
||||
clientInfo, ok = decodeMetaValue[*Implementation](meta, MetaKeyClientInfo)
|
||||
if !ok {
|
||||
return nil, &jsonrpc.Error{
|
||||
Code: jsonrpc.CodeInvalidParams,
|
||||
Message: fmt.Sprintf("invalid _meta field %q", MetaKeyClientInfo),
|
||||
}
|
||||
}
|
||||
}
|
||||
capabilities, ok := decodeMetaValue[*clientCapabilitiesV2](meta, MetaKeyClientCapabilities)
|
||||
|
|
|
|||
|
|
@ -67,6 +67,19 @@ func TestValidateRequestMeta(t *testing.T) {
|
|||
},
|
||||
"name": "x",
|
||||
},
|
||||
wantUsesNew: true,
|
||||
},
|
||||
{
|
||||
name: "new protocol invalid clientInfo",
|
||||
method: methodCallTool,
|
||||
params: map[string]any{
|
||||
"_meta": map[string]any{
|
||||
MetaKeyProtocolVersion: protocolVersion20260728,
|
||||
MetaKeyClientInfo: "not an object",
|
||||
MetaKeyClientCapabilities: map[string]any{},
|
||||
},
|
||||
"name": "x",
|
||||
},
|
||||
wantUsesNew: false,
|
||||
wantErrContains: MetaKeyClientInfo,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1265,11 +1265,13 @@ func TestStreamableClientOAuth_RetrieveError(t *testing.T) {
|
|||
// discoverResult is the canned successful DiscoverResult returned by
|
||||
// fakeStreamableServer setups in the tests below.
|
||||
var discoverResult = &DiscoverResult{
|
||||
Meta: Meta{
|
||||
MetaKeyServerInfo: &Implementation{Name: "discoverServer", Version: "v1.0.0"},
|
||||
},
|
||||
SupportedVersions: []string{protocolVersion20260728},
|
||||
Capabilities: &ServerCapabilities{
|
||||
Tools: &ToolCapabilities{ListChanged: true},
|
||||
},
|
||||
ServerInfo: &Implementation{Name: "discoverServer", Version: "v1.0.0"},
|
||||
Instructions: "test discover",
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -794,6 +794,15 @@ func completeCallToolResult() *CallToolResult {
|
|||
return r
|
||||
}
|
||||
|
||||
// completeCallToolResultWithServerInfo returns a completeCallToolResult
|
||||
// annotated with the auto-populated [MetaKeyServerInfo] entry that servers
|
||||
// add to every new-protocol response.
|
||||
func completeCallToolResultWithServerInfo(impl *Implementation) *CallToolResult {
|
||||
r := completeCallToolResult()
|
||||
r.Meta = Meta{MetaKeyServerInfo: impl}
|
||||
return r
|
||||
}
|
||||
|
||||
func resp(id int64, result any, err error) *jsonrpc.Response {
|
||||
return &jsonrpc.Response{
|
||||
ID: jsonrpc2.Int64ID(id),
|
||||
|
|
@ -1958,6 +1967,7 @@ func TestStreamableMcpHeaderValidation(t *testing.T) {
|
|||
MetaKeyClientInfo: map[string]any{"name": "testClient", "version": "v1.0.0"},
|
||||
MetaKeyClientCapabilities: map[string]any{},
|
||||
}
|
||||
serverImpl := &Implementation{Name: "testServer", Version: "v1.0.0"}
|
||||
|
||||
testStreamableHandler(t, handler, []streamableRequest{
|
||||
{
|
||||
|
|
@ -1969,7 +1979,7 @@ func TestStreamableMcpHeaderValidation(t *testing.T) {
|
|||
},
|
||||
messages: []jsonrpc.Message{req(2, "tools/call", &CallToolParams{Meta: testMeta, Name: "my-tool"})},
|
||||
wantStatusCode: http.StatusOK,
|
||||
wantMessages: []jsonrpc.Message{resp(2, completeCallToolResult(), nil)},
|
||||
wantMessages: []jsonrpc.Message{resp(2, completeCallToolResultWithServerInfo(serverImpl), nil)},
|
||||
},
|
||||
{
|
||||
method: "POST",
|
||||
|
|
@ -2013,7 +2023,7 @@ func TestStreamableMcpHeaderValidation(t *testing.T) {
|
|||
},
|
||||
messages: []jsonrpc.Message{req(6, "tools/call", &CallToolParams{Meta: testMeta, Name: "my-tool"})},
|
||||
wantStatusCode: http.StatusOK,
|
||||
wantMessages: []jsonrpc.Message{resp(6, completeCallToolResult(), nil)},
|
||||
wantMessages: []jsonrpc.Message{resp(6, completeCallToolResultWithServerInfo(serverImpl), nil)},
|
||||
},
|
||||
{
|
||||
method: "POST",
|
||||
|
|
@ -2029,7 +2039,7 @@ func TestStreamableMcpHeaderValidation(t *testing.T) {
|
|||
Arguments: map[string]any{"region": "us-west1", "query": "SELECT 1"},
|
||||
})},
|
||||
wantStatusCode: http.StatusOK,
|
||||
wantMessages: []jsonrpc.Message{resp(7, completeCallToolResult(), nil)},
|
||||
wantMessages: []jsonrpc.Message{resp(7, completeCallToolResultWithServerInfo(serverImpl), nil)},
|
||||
},
|
||||
{
|
||||
method: "POST",
|
||||
|
|
|
|||
18
mcp/testdata/conformance/server/discover.txtar
vendored
18
mcp/testdata/conformance/server/discover.txtar
vendored
|
|
@ -1,7 +1,7 @@
|
|||
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.
|
||||
The client sends a discover request carrying the per-request _meta
|
||||
(protocolVersion, clientInfo, clientCapabilities), and the server
|
||||
replies with the protocol versions it supports, its capabilities, and its
|
||||
identity via `_meta.io.modelcontextprotocol/serverInfo`.
|
||||
|
||||
-- client --
|
||||
{
|
||||
|
|
@ -22,6 +22,12 @@ supports, its capabilities, and its identity.
|
|||
"id": 1,
|
||||
"result": {
|
||||
"resultType": "complete",
|
||||
"_meta": {
|
||||
"io.modelcontextprotocol/serverInfo": {
|
||||
"name": "testServer",
|
||||
"version": "v1.0.0"
|
||||
}
|
||||
},
|
||||
"ttlMs": 0,
|
||||
"cacheScope": "public",
|
||||
"supportedVersions": [
|
||||
|
|
@ -33,10 +39,6 @@ supports, its capabilities, and its identity.
|
|||
],
|
||||
"capabilities": {
|
||||
"logging": {}
|
||||
},
|
||||
"serverInfo": {
|
||||
"name": "testServer",
|
||||
"version": "v1.0.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
12
mcp/testdata/conformance/server/mrtr.txtar
vendored
12
mcp/testdata/conformance/server/mrtr.txtar
vendored
|
|
@ -45,6 +45,12 @@ confirmThenGreet
|
|||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": {
|
||||
"_meta": {
|
||||
"io.modelcontextprotocol/serverInfo": {
|
||||
"name": "testServer",
|
||||
"version": "v1.0.0"
|
||||
}
|
||||
},
|
||||
"content": null,
|
||||
"requestState": "step=1",
|
||||
"resultType": "input_required",
|
||||
|
|
@ -71,6 +77,12 @@ confirmThenGreet
|
|||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"result": {
|
||||
"_meta": {
|
||||
"io.modelcontextprotocol/serverInfo": {
|
||||
"name": "testServer",
|
||||
"version": "v1.0.0"
|
||||
}
|
||||
},
|
||||
"content": [
|
||||
{
|
||||
"type": "text",
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ func ExampleLoggingTransport() {
|
|||
}
|
||||
|
||||
// Output:
|
||||
// read: {"jsonrpc":"2.0","id":1,"result":{"resultType":"complete","ttlMs":0,"cacheScope":"public","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"}}}
|
||||
// read: {"jsonrpc":"2.0","id":1,"result":{"resultType":"complete","_meta":{"io.modelcontextprotocol/serverInfo":{"name":"server","version":"v0.0.1"}},"ttlMs":0,"cacheScope":"public","supportedVersions":["2026-07-28","2025-11-25","2025-06-18","2025-03-26","2024-11-05"],"capabilities":{"logging":{}}}}
|
||||
// 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"}}}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue