mcp: fix broken client root capabilities (#698)
To address #607, add ClientCapabilities.RootsV2, and populate it when constructing sending and receiving InitializeParams. Fixes #607
This commit is contained in:
parent
307e32c2b3
commit
a5e369ed12
10 changed files with 288 additions and 57 deletions
|
|
@ -30,3 +30,9 @@ v2.
|
|||
|
||||
- `AudioContent.MarshalJSON` should have had a pointer receiver, to be
|
||||
consistent with other content types.
|
||||
|
||||
- `ClientCapabilities.Roots` should have been a distinguished struct pointer
|
||||
([see #607](https://github.com/modelcontextprotocol/go-sdk/issues/607)).
|
||||
|
||||
**Workaround**: use `ClientCapabilities.RootsV2`, which aligns with the
|
||||
semantics of other capability fields.
|
||||
|
|
|
|||
|
|
@ -51,7 +51,7 @@ func ExampleLoggingTransport() {
|
|||
|
||||
// Output:
|
||||
// read: {"jsonrpc":"2.0","id":1,"result":{"capabilities":{"logging":{}},"protocolVersion":"2025-06-18","serverInfo":{"name":"server","version":"v0.0.1"}}}
|
||||
// write: {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{"roots":{"listChanged":true}},"clientInfo":{"name":"client","version":"v0.0.1"},"protocolVersion":"2025-06-18"}}
|
||||
// write: {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"clientInfo":{"name":"client","version":"v0.0.1"},"protocolVersion":"2025-06-18","capabilities":{"roots":{"listChanged":true}}}}
|
||||
// write: {"jsonrpc":"2.0","method":"notifications/initialized","params":{}}
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -29,3 +29,9 @@ v2.
|
|||
|
||||
- `AudioContent.MarshalJSON` should have had a pointer receiver, to be
|
||||
consistent with other content types.
|
||||
|
||||
- `ClientCapabilities.Roots` should have been a distinguished struct pointer
|
||||
([see #607](https://github.com/modelcontextprotocol/go-sdk/issues/607)).
|
||||
|
||||
**Workaround**: use `ClientCapabilities.RootsV2`, which aligns with the
|
||||
semantics of other capability fields.
|
||||
|
|
|
|||
|
|
@ -131,7 +131,11 @@ type ClientSessionOptions struct {
|
|||
|
||||
func (c *Client) capabilities() *ClientCapabilities {
|
||||
caps := &ClientCapabilities{}
|
||||
// Due to an oversight (#607), roots require special handling.
|
||||
caps.Roots.ListChanged = true
|
||||
caps.RootsV2 = &RootCapabilities{
|
||||
ListChanged: true,
|
||||
}
|
||||
if c.opts.CreateMessageHandler != nil {
|
||||
caps.Sampling = &SamplingCapabilities{}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -202,9 +202,8 @@ func TestClientCapabilities(t *testing.T) {
|
|||
name: "With initial capabilities",
|
||||
configureClient: func(s *Client) {},
|
||||
wantCapabilities: &ClientCapabilities{
|
||||
Roots: struct {
|
||||
ListChanged bool "json:\"listChanged,omitempty\""
|
||||
}{ListChanged: true},
|
||||
Roots: RootCapabilities{ListChanged: true},
|
||||
RootsV2: &RootCapabilities{ListChanged: true},
|
||||
},
|
||||
},
|
||||
{
|
||||
|
|
@ -216,9 +215,8 @@ func TestClientCapabilities(t *testing.T) {
|
|||
},
|
||||
},
|
||||
wantCapabilities: &ClientCapabilities{
|
||||
Roots: struct {
|
||||
ListChanged bool "json:\"listChanged,omitempty\""
|
||||
}{ListChanged: true},
|
||||
Roots: RootCapabilities{ListChanged: true},
|
||||
RootsV2: &RootCapabilities{ListChanged: true},
|
||||
Sampling: &SamplingCapabilities{},
|
||||
},
|
||||
},
|
||||
|
|
@ -232,9 +230,8 @@ func TestClientCapabilities(t *testing.T) {
|
|||
},
|
||||
},
|
||||
wantCapabilities: &ClientCapabilities{
|
||||
Roots: struct {
|
||||
ListChanged bool "json:\"listChanged,omitempty\""
|
||||
}{ListChanged: true},
|
||||
Roots: RootCapabilities{ListChanged: true},
|
||||
RootsV2: &RootCapabilities{ListChanged: true},
|
||||
Elicitation: &ElicitationCapabilities{
|
||||
Form: &FormElicitationCapabilities{},
|
||||
},
|
||||
|
|
@ -250,9 +247,8 @@ func TestClientCapabilities(t *testing.T) {
|
|||
},
|
||||
},
|
||||
wantCapabilities: &ClientCapabilities{
|
||||
Roots: struct {
|
||||
ListChanged bool "json:\"listChanged,omitempty\""
|
||||
}{ListChanged: true},
|
||||
Roots: RootCapabilities{ListChanged: true},
|
||||
RootsV2: &RootCapabilities{ListChanged: true},
|
||||
Elicitation: &ElicitationCapabilities{
|
||||
URL: &URLElicitationCapabilities{},
|
||||
},
|
||||
|
|
@ -268,9 +264,8 @@ func TestClientCapabilities(t *testing.T) {
|
|||
},
|
||||
},
|
||||
wantCapabilities: &ClientCapabilities{
|
||||
Roots: struct {
|
||||
ListChanged bool "json:\"listChanged,omitempty\""
|
||||
}{ListChanged: true},
|
||||
Roots: RootCapabilities{ListChanged: true},
|
||||
RootsV2: &RootCapabilities{ListChanged: true},
|
||||
Elicitation: &ElicitationCapabilities{
|
||||
Form: &FormElicitationCapabilities{},
|
||||
URL: &URLElicitationCapabilities{},
|
||||
|
|
|
|||
152
mcp/protocol.go
152
mcp/protocol.go
|
|
@ -177,23 +177,62 @@ func (x *CancelledParams) isParams() {}
|
|||
func (x *CancelledParams) GetProgressToken() any { return getProgressToken(x) }
|
||||
func (x *CancelledParams) SetProgressToken(t any) { setProgressToken(x, t) }
|
||||
|
||||
// RootCapabilities describes a client's support for roots.
|
||||
type RootCapabilities struct {
|
||||
// ListChanged reports whether the client supports notifications for
|
||||
// changes to the roots list.
|
||||
ListChanged bool `json:"listChanged,omitempty"`
|
||||
}
|
||||
|
||||
// Capabilities a client may support. Known capabilities are defined here, in
|
||||
// this schema, but this is not a closed set: any client can define its own,
|
||||
// additional capabilities.
|
||||
type ClientCapabilities struct {
|
||||
// Experimental, non-standard capabilities that the client supports.
|
||||
// Experimental reports non-standard capabilities that the client supports.
|
||||
Experimental map[string]any `json:"experimental,omitempty"`
|
||||
// Present if the client supports listing roots.
|
||||
// Roots describes the client's support for roots.
|
||||
//
|
||||
// Deprecated: use RootsV2. As described in #607, Roots should have been a
|
||||
// pointer to a RootCapabilities value. Roots will be continue to be
|
||||
// populated, but any new fields will only be added in the RootsV2 field.
|
||||
Roots struct {
|
||||
// Whether the client supports notifications for changes to the roots list.
|
||||
// ListChanged reports whether the client supports notifications for
|
||||
// changes to the roots list.
|
||||
ListChanged bool `json:"listChanged,omitempty"`
|
||||
} `json:"roots,omitempty"`
|
||||
// Present if the client supports sampling from an LLM.
|
||||
// RootsV2 is present if the client supports roots.
|
||||
RootsV2 *RootCapabilities `json:"-"`
|
||||
// Sampling is present if the client supports sampling from an LLM.
|
||||
Sampling *SamplingCapabilities `json:"sampling,omitempty"`
|
||||
// Present if the client supports elicitation from the server.
|
||||
// Elicitation is present if the client supports elicitation from the server.
|
||||
Elicitation *ElicitationCapabilities `json:"elicitation,omitempty"`
|
||||
}
|
||||
|
||||
func (c *ClientCapabilities) toV2() *clientCapabilitiesV2 {
|
||||
return &clientCapabilitiesV2{
|
||||
ClientCapabilities: *c,
|
||||
Roots: c.RootsV2,
|
||||
}
|
||||
}
|
||||
|
||||
// clientCapabilitiesV2 is a version of ClientCapabilities that fixes the bug
|
||||
// described in #607: Roots should have been a pointer to value type
|
||||
// RootCapabilities.
|
||||
type clientCapabilitiesV2 struct {
|
||||
ClientCapabilities
|
||||
Roots *RootCapabilities `json:"roots,omitempty"`
|
||||
}
|
||||
|
||||
func (c *clientCapabilitiesV2) toV1() *ClientCapabilities {
|
||||
caps := c.ClientCapabilities
|
||||
caps.RootsV2 = c.Roots
|
||||
// Sync Roots from RootsV2 for backward compatibility (#607).
|
||||
if caps.RootsV2 != nil {
|
||||
caps.Roots = *caps.RootsV2
|
||||
}
|
||||
return &caps
|
||||
}
|
||||
|
||||
type CompleteParamsArgument struct {
|
||||
// The name of the argument
|
||||
Name string `json:"name"`
|
||||
|
|
@ -373,27 +412,53 @@ type GetPromptResult struct {
|
|||
|
||||
func (*GetPromptResult) isResult() {}
|
||||
|
||||
// InitializeParams is sent by the client to initialize the session.
|
||||
type InitializeParams struct {
|
||||
// This property is reserved by the protocol to allow clients and servers to
|
||||
// attach additional metadata to their responses.
|
||||
Meta `json:"_meta,omitempty"`
|
||||
Meta `json:"_meta,omitempty"`
|
||||
// Capabilities describes the client's capabilities.
|
||||
Capabilities *ClientCapabilities `json:"capabilities"`
|
||||
ClientInfo *Implementation `json:"clientInfo"`
|
||||
// The latest version of the Model Context Protocol that the client supports.
|
||||
// The client may decide to support older versions as well.
|
||||
// ClientInfo provides information about the client.
|
||||
ClientInfo *Implementation `json:"clientInfo"`
|
||||
// ProtocolVersion is the latest version of the Model Context Protocol that
|
||||
// the client supports.
|
||||
ProtocolVersion string `json:"protocolVersion"`
|
||||
}
|
||||
|
||||
func (p *InitializeParams) toV2() *initializeParamsV2 {
|
||||
return &initializeParamsV2{
|
||||
InitializeParams: *p,
|
||||
Capabilities: p.Capabilities.toV2(),
|
||||
}
|
||||
}
|
||||
|
||||
// initializeParamsV2 works around the mistake in #607: Capabilities.Roots
|
||||
// should have been a pointer.
|
||||
type initializeParamsV2 struct {
|
||||
InitializeParams
|
||||
Capabilities *clientCapabilitiesV2 `json:"capabilities"`
|
||||
}
|
||||
|
||||
func (p *initializeParamsV2) toV1() *InitializeParams {
|
||||
p1 := p.InitializeParams
|
||||
if p.Capabilities != nil {
|
||||
p1.Capabilities = p.Capabilities.toV1()
|
||||
}
|
||||
return &p1
|
||||
}
|
||||
|
||||
func (x *InitializeParams) isParams() {}
|
||||
func (x *InitializeParams) GetProgressToken() any { return getProgressToken(x) }
|
||||
func (x *InitializeParams) SetProgressToken(t any) { setProgressToken(x, t) }
|
||||
|
||||
// After receiving an initialize request from the client, the server sends this
|
||||
// response.
|
||||
// InitializeResult is sent by the server in response to an initialize request
|
||||
// from the client.
|
||||
type InitializeResult struct {
|
||||
// This property is reserved by the protocol to allow clients and servers to
|
||||
// attach additional metadata to their responses.
|
||||
Meta `json:"_meta,omitempty"`
|
||||
Meta `json:"_meta,omitempty"`
|
||||
// Capabilities describes the server's capabilities.
|
||||
Capabilities *ServerCapabilities `json:"capabilities"`
|
||||
// Instructions describing how to use the server and its features.
|
||||
//
|
||||
|
|
@ -411,8 +476,8 @@ type InitializeResult struct {
|
|||
func (*InitializeResult) isResult() {}
|
||||
|
||||
type InitializedParams struct {
|
||||
// This property is reserved by the protocol to allow clients and servers to
|
||||
// attach additional metadata to their responses.
|
||||
// Meta is reserved by the protocol to allow clients and servers to attach
|
||||
// additional metadata to their responses.
|
||||
Meta `json:"_meta,omitempty"`
|
||||
}
|
||||
|
||||
|
|
@ -875,7 +940,10 @@ func (x *RootsListChangedParams) isParams() {}
|
|||
func (x *RootsListChangedParams) GetProgressToken() any { return getProgressToken(x) }
|
||||
func (x *RootsListChangedParams) SetProgressToken(t any) { setProgressToken(x, t) }
|
||||
|
||||
// SamplingCapabilities describes the capabilities for sampling.
|
||||
// TODO: to be consistent with ServerCapabilities, move the capability types
|
||||
// below directly above ClientCapabilities.
|
||||
|
||||
// SamplingCapabilities describes the client's support for sampling.
|
||||
type SamplingCapabilities struct{}
|
||||
|
||||
// ElicitationCapabilities describes the capabilities for elicitation.
|
||||
|
|
@ -1160,48 +1228,50 @@ type Implementation struct {
|
|||
Icons []Icon `json:"icons,omitempty"`
|
||||
}
|
||||
|
||||
// Present if the server supports argument autocompletion suggestions.
|
||||
// CompletionCapabilities describes the server's support for argument autocompletion.
|
||||
type CompletionCapabilities struct{}
|
||||
|
||||
// Present if the server supports sending log messages to the client.
|
||||
// LoggingCapabilities describes the server's support for sending log messages to the client.
|
||||
type LoggingCapabilities struct{}
|
||||
|
||||
// Present if the server offers any prompt templates.
|
||||
// PromptCapabilities describes the server's support for prompts.
|
||||
type PromptCapabilities struct {
|
||||
// Whether this server supports notifications for changes to the prompt list.
|
||||
ListChanged bool `json:"listChanged,omitempty"`
|
||||
}
|
||||
|
||||
// Present if the server offers any resources to read.
|
||||
// ResourceCapabilities describes the server's support for resources.
|
||||
type ResourceCapabilities struct {
|
||||
// Whether this server supports notifications for changes to the resource list.
|
||||
// ListChanged reports whether the client supports notifications for
|
||||
// changes to the resource list.
|
||||
ListChanged bool `json:"listChanged,omitempty"`
|
||||
// Whether this server supports subscribing to resource updates.
|
||||
// Subscribe reports whether this server supports subscribing to resource
|
||||
// updates.
|
||||
Subscribe bool `json:"subscribe,omitempty"`
|
||||
}
|
||||
|
||||
// Capabilities that a server may support. Known capabilities are defined here,
|
||||
// in this schema, but this is not a closed set: any server can define its own,
|
||||
// additional capabilities.
|
||||
type ServerCapabilities struct {
|
||||
// Present if the server supports argument autocompletion suggestions.
|
||||
Completions *CompletionCapabilities `json:"completions,omitempty"`
|
||||
// Experimental, non-standard capabilities that the server supports.
|
||||
Experimental map[string]any `json:"experimental,omitempty"`
|
||||
// Present if the server supports sending log messages to the client.
|
||||
Logging *LoggingCapabilities `json:"logging,omitempty"`
|
||||
// Present if the server offers any prompt templates.
|
||||
Prompts *PromptCapabilities `json:"prompts,omitempty"`
|
||||
// Present if the server offers any resources to read.
|
||||
Resources *ResourceCapabilities `json:"resources,omitempty"`
|
||||
// Present if the server offers any tools to call.
|
||||
Tools *ToolCapabilities `json:"tools,omitempty"`
|
||||
// ToolCapabilities describes the server's support for tools.
|
||||
type ToolCapabilities struct {
|
||||
// ListChanged reports whether the client supports notifications for
|
||||
// changes to the tool list.
|
||||
ListChanged bool `json:"listChanged,omitempty"`
|
||||
}
|
||||
|
||||
// Present if the server offers any tools to call.
|
||||
type ToolCapabilities struct {
|
||||
// Whether this server supports notifications for changes to the tool list.
|
||||
ListChanged bool `json:"listChanged,omitempty"`
|
||||
// ServerCapabilities describes capabilities that a server supports.
|
||||
type ServerCapabilities struct {
|
||||
// Experimental reports non-standard capabilities that the server supports.
|
||||
Experimental map[string]any `json:"experimental,omitempty"`
|
||||
// Completions is present if the server supports argument autocompletion
|
||||
// suggestions.
|
||||
Completions *CompletionCapabilities `json:"completions,omitempty"`
|
||||
// Logging is present if the server supports log messages.
|
||||
Logging *LoggingCapabilities `json:"logging,omitempty"`
|
||||
// Prompts is present if the server supports prompts.
|
||||
Prompts *PromptCapabilities `json:"prompts,omitempty"`
|
||||
// Resources is present if the server supports resourcs.
|
||||
Resources *ResourceCapabilities `json:"resources,omitempty"`
|
||||
// Tools is present if the supports tools.
|
||||
Tools *ToolCapabilities `json:"tools,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -1138,7 +1138,7 @@ func (s *Server) AddReceivingMiddleware(middleware ...Middleware) {
|
|||
// curating these method flags.
|
||||
var serverMethodInfos = map[string]methodInfo{
|
||||
methodComplete: newServerMethodInfo(serverMethod((*Server).complete), 0),
|
||||
methodInitialize: newServerMethodInfo(serverSessionMethod((*ServerSession).initialize), 0),
|
||||
methodInitialize: initializeMethodInfo(),
|
||||
methodPing: newServerMethodInfo(serverSessionMethod((*ServerSession).ping), missingParamsOK),
|
||||
methodListPrompts: newServerMethodInfo(serverMethod((*Server).listPrompts), missingParamsOK),
|
||||
methodGetPrompt: newServerMethodInfo(serverMethod((*Server).getPrompt), 0),
|
||||
|
|
@ -1156,6 +1156,25 @@ var serverMethodInfos = map[string]methodInfo{
|
|||
notificationProgress: newServerMethodInfo(serverSessionMethod((*ServerSession).callProgressNotificationHandler), notification),
|
||||
}
|
||||
|
||||
// initializeMethodInfo handles the workaround for #607: we must set
|
||||
// params.Capabilities.RootsV2.
|
||||
func initializeMethodInfo() methodInfo {
|
||||
info := newServerMethodInfo(serverSessionMethod((*ServerSession).initialize), 0)
|
||||
info.unmarshalParams = func(m json.RawMessage) (Params, error) {
|
||||
var params *initializeParamsV2
|
||||
if m != nil {
|
||||
if err := json.Unmarshal(m, ¶ms); err != nil {
|
||||
return nil, fmt.Errorf("unmarshaling %q into a %T: %w", m, params, err)
|
||||
}
|
||||
}
|
||||
if params == nil {
|
||||
return nil, fmt.Errorf(`missing required "params"`)
|
||||
}
|
||||
return params.toV1(), nil
|
||||
}
|
||||
return info
|
||||
}
|
||||
|
||||
func (ss *ServerSession) sendingMethodInfos() map[string]methodInfo { return clientMethodInfos }
|
||||
|
||||
func (ss *ServerSession) receivingMethodInfos() map[string]methodInfo { return serverMethodInfos }
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import (
|
|||
|
||||
"github.com/google/go-cmp/cmp"
|
||||
"github.com/google/jsonschema-go/jsonschema"
|
||||
"github.com/modelcontextprotocol/go-sdk/internal/jsonrpc2"
|
||||
)
|
||||
|
||||
type testItem struct {
|
||||
|
|
@ -600,6 +601,128 @@ func testToolForSchema[In, Out any](t *testing.T, tool *Tool, in string, out Out
|
|||
}
|
||||
}
|
||||
|
||||
// TestClientRootCapabilities verifies that the server correctly observes
|
||||
// RootsV2 for various client capability configurations. This tests the fix
|
||||
// for #607.
|
||||
func TestClientRootCapabilities(t *testing.T) {
|
||||
testCases := []struct {
|
||||
name string
|
||||
capabilities *string // JSON for the capabilities field; nil means omit the field
|
||||
wantRootsV2 *RootCapabilities
|
||||
}{
|
||||
{
|
||||
name: "capabilities field omitted",
|
||||
capabilities: nil,
|
||||
wantRootsV2: nil,
|
||||
},
|
||||
{
|
||||
name: "empty capabilities",
|
||||
capabilities: ptr(`{}`),
|
||||
wantRootsV2: nil,
|
||||
},
|
||||
{
|
||||
name: "capabilities with no roots",
|
||||
capabilities: ptr(`{"sampling": {}}`),
|
||||
wantRootsV2: nil,
|
||||
},
|
||||
{
|
||||
name: "capabilities with empty roots",
|
||||
capabilities: ptr(`{"roots": {}}`),
|
||||
wantRootsV2: &RootCapabilities{ListChanged: false},
|
||||
},
|
||||
{
|
||||
name: "capabilities with roots without listChanged",
|
||||
capabilities: ptr(`{"roots": {"listChanged": false}}`),
|
||||
wantRootsV2: &RootCapabilities{ListChanged: false},
|
||||
},
|
||||
{
|
||||
name: "capabilities with roots with listChanged",
|
||||
capabilities: ptr(`{"roots": {"listChanged": true}}`),
|
||||
wantRootsV2: &RootCapabilities{ListChanged: true},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
|
||||
// Create a minimal server.
|
||||
impl := &Implementation{Name: "testServer", Version: "v1.0.0"}
|
||||
s := NewServer(impl, nil)
|
||||
|
||||
// Connect the server.
|
||||
cTransport, sTransport := NewInMemoryTransports()
|
||||
ss, err := s.Connect(ctx, sTransport, nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Connect the client JSON-RPC connection (raw, no client).
|
||||
cConn, err := cTransport.Connect(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Build initialize params, optionally including capabilities.
|
||||
var initParams json.RawMessage
|
||||
if tc.capabilities != nil {
|
||||
initParams = json.RawMessage(`{
|
||||
"protocolVersion": "2025-06-18",
|
||||
"capabilities": ` + *tc.capabilities + `,
|
||||
"clientInfo": {"name": "TestClient", "version": "1.0.0"}
|
||||
}`)
|
||||
} else {
|
||||
initParams = json.RawMessage(`{
|
||||
"protocolVersion": "2025-06-18",
|
||||
"clientInfo": {"name": "TestClient", "version": "1.0.0"}
|
||||
}`)
|
||||
}
|
||||
|
||||
initReq, err := jsonrpc2.NewCall(jsonrpc2.Int64ID(1), "initialize", initParams)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if err := cConn.Write(ctx, initReq); err != nil {
|
||||
t.Fatalf("Write failed: %v", err)
|
||||
}
|
||||
|
||||
// Read the initialize response.
|
||||
msg, err := cConn.Read(ctx)
|
||||
if err != nil {
|
||||
t.Fatalf("Read failed: %v", err)
|
||||
}
|
||||
resp, ok := msg.(*jsonrpc2.Response)
|
||||
if !ok {
|
||||
t.Fatalf("expected Response, got %T", msg)
|
||||
}
|
||||
if resp.Error != nil {
|
||||
t.Fatalf("initialize failed: %v", resp.Error)
|
||||
}
|
||||
|
||||
// Verify that the server session has the correct RootsV2 value.
|
||||
params := ss.InitializeParams()
|
||||
if params == nil {
|
||||
t.Fatal("InitializeParams is nil")
|
||||
}
|
||||
|
||||
var gotRootsV2 *RootCapabilities
|
||||
if params.Capabilities != nil {
|
||||
gotRootsV2 = params.Capabilities.RootsV2
|
||||
}
|
||||
if diff := cmp.Diff(tc.wantRootsV2, gotRootsV2); diff != "" {
|
||||
t.Errorf("RootsV2 mismatch (-want +got):\n%s", diff)
|
||||
}
|
||||
|
||||
// Close the client connection.
|
||||
if err := cConn.Close(); err != nil {
|
||||
t.Fatalf("Stream.Close failed: %v", err)
|
||||
}
|
||||
ss.Wait()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move this to tool_test.go
|
||||
func TestToolForSchemas(t *testing.T) {
|
||||
// Validate that toolForErr handles schemas properly.
|
||||
|
|
|
|||
|
|
@ -94,14 +94,22 @@ func defaultSendingMethodHandler(ctx context.Context, method string, req Request
|
|||
// This can be called from user code, with an arbitrary value for method.
|
||||
return nil, jsonrpc2.ErrNotHandled
|
||||
}
|
||||
params := req.GetParams()
|
||||
if initParams, ok := params.(*InitializeParams); ok {
|
||||
// Fix the marshaling of initialize params, to work around #607.
|
||||
//
|
||||
// The initialize params we produce should never be nil, nor have nil
|
||||
// capabilities, so any panic here is a bug.
|
||||
params = initParams.toV2()
|
||||
}
|
||||
// Notifications don't have results.
|
||||
if strings.HasPrefix(method, "notifications/") {
|
||||
return nil, req.GetSession().getConn().Notify(ctx, method, req.GetParams())
|
||||
return nil, req.GetSession().getConn().Notify(ctx, method, params)
|
||||
}
|
||||
// Create the result to unmarshal into.
|
||||
// The concrete type of the result is the return type of the receiving function.
|
||||
res := info.newResult()
|
||||
if err := call(ctx, req.GetSession().getConn(), method, req.GetParams(), res); err != nil {
|
||||
if err := call(ctx, req.GetSession().getConn(), method, params, res); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ func ExampleLoggingTransport() {
|
|||
|
||||
// Output:
|
||||
// read: {"jsonrpc":"2.0","id":1,"result":{"capabilities":{"logging":{}},"protocolVersion":"2025-06-18","serverInfo":{"name":"server","version":"v0.0.1"}}}
|
||||
// write: {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"capabilities":{"roots":{"listChanged":true}},"clientInfo":{"name":"client","version":"v0.0.1"},"protocolVersion":"2025-06-18"}}
|
||||
// write: {"jsonrpc":"2.0","id":1,"method":"initialize","params":{"clientInfo":{"name":"client","version":"v0.0.1"},"protocolVersion":"2025-06-18","capabilities":{"roots":{"listChanged":true}}}}
|
||||
// write: {"jsonrpc":"2.0","method":"notifications/initialized","params":{}}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue