internal/mcp: change function signatures match design doc

To ensure backward compatibility, every method in the spec now takes a
*Params struct and returns a *Result struct.

CallTools is an exception: we'll change that in the next CL.

Change-Id: I58835239c67582401b8c9c2d070702d270131c08
Reviewed-on: https://go-review.googlesource.com/c/tools/+/672595
Reviewed-by: Robert Findley <rfindley@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
This commit is contained in:
Jonathan Amsterdam 2025-05-14 05:50:50 -04:00
commit 4721f501df
6 changed files with 38 additions and 48 deletions

View file

@ -154,47 +154,23 @@ func (c *Client) handle(ctx context.Context, req *jsonrpc2.Request) (any, error)
}
// Ping makes an MCP "ping" request to the server.
func (c *Client) Ping(ctx context.Context) error {
return call(ctx, c.conn, "ping", nil, nil)
func (c *Client) Ping(ctx context.Context, params *PingParams) error {
return call(ctx, c.conn, "ping", params, nil)
}
// ListPrompts lists prompts that are currently available on the server.
func (c *Client) ListPrompts(ctx context.Context) ([]*Prompt, error) {
var (
params = &ListPromptsParams{}
result ListPromptsResult
)
if err := call(ctx, c.conn, "prompts/list", params, &result); err != nil {
return nil, err
}
return result.Prompts, nil
func (c *Client) ListPrompts(ctx context.Context, params *ListPromptsParams) (*ListPromptsResult, error) {
return standardCall[ListPromptsResult](ctx, c.conn, "prompts/list", params)
}
// GetPrompt gets a prompt from the server.
func (c *Client) GetPrompt(ctx context.Context, name string, args map[string]string) (*GetPromptResult, error) {
var (
params = &GetPromptParams{
Name: name,
Arguments: args,
}
result = &GetPromptResult{}
)
if err := call(ctx, c.conn, "prompts/get", params, result); err != nil {
return nil, err
}
return result, nil
func (c *Client) GetPrompt(ctx context.Context, params *GetPromptParams) (*GetPromptResult, error) {
return standardCall[GetPromptResult](ctx, c.conn, "prompts/get", params)
}
// ListTools lists tools that are currently available on the server.
func (c *Client) ListTools(ctx context.Context) ([]*Tool, error) {
var (
params = &ListToolsParams{}
result ListToolsResult
)
if err := call(ctx, c.conn, "tools/list", params, &result); err != nil {
return nil, err
}
return result.Tools, nil
func (c *Client) ListTools(ctx context.Context, params *ListToolsParams) (*ListToolsResult, error) {
return standardCall[ListToolsResult](ctx, c.conn, "tools/list", params)
}
// CallTool calls the tool with the given name and arguments.

View file

@ -97,11 +97,15 @@ var declarations = config{
Fields: config{"Params": {Name: "ListToolsParams"}},
},
"ListToolsResult": {},
"Prompt": {},
"PromptMessage": {},
"PromptArgument": {},
"ProgressToken": {Name: "-", Substitute: "any"}, // null|number|string
"RequestId": {Name: "-", Substitute: "any"}, // null|number|string
"PingRequest": {
Name: "-",
Fields: config{"Params": {Name: "PingParams"}},
},
"Prompt": {},
"PromptMessage": {},
"PromptArgument": {},
"ProgressToken": {Name: "-", Substitute: "any"}, // null|number|string
"RequestId": {Name: "-", Substitute: "any"}, // null|number|string
"ReadResourceRequest": {
Name: "-",
Fields: config{"Params": {Name: "ReadResourceParams"}},

View file

@ -25,7 +25,7 @@ type hiParams struct {
}
func sayHi(ctx context.Context, cc *ServerConnection, v hiParams) ([]*Content, error) {
if err := cc.Ping(ctx); err != nil {
if err := cc.Ping(ctx, nil); err != nil {
return nil, fmt.Errorf("ping failed: %v", err)
}
return []*Content{NewTextContent("hi " + v.Name)}, nil
@ -89,11 +89,11 @@ func TestEndToEnd(t *testing.T) {
t.Fatal(err)
}
if err := c.Ping(ctx); err != nil {
if err := c.Ping(ctx, nil); err != nil {
t.Fatalf("ping failed: %v", err)
}
t.Run("prompts", func(t *testing.T) {
gotPrompts, err := c.ListPrompts(ctx)
res, err := c.ListPrompts(ctx, nil)
if err != nil {
t.Errorf("prompts/list failed: %v", err)
}
@ -105,11 +105,11 @@ func TestEndToEnd(t *testing.T) {
},
{Name: "fail"},
}
if diff := cmp.Diff(wantPrompts, gotPrompts); diff != "" {
if diff := cmp.Diff(wantPrompts, res.Prompts); diff != "" {
t.Fatalf("prompts/list mismatch (-want +got):\n%s", diff)
}
gotReview, err := c.GetPrompt(ctx, "code_review", map[string]string{"Code": "1+1"})
gotReview, err := c.GetPrompt(ctx, &GetPromptParams{Name: "code_review", Arguments: map[string]string{"Code": "1+1"}})
if err != nil {
t.Fatal(err)
}
@ -124,13 +124,13 @@ func TestEndToEnd(t *testing.T) {
t.Errorf("prompts/get 'code_review' mismatch (-want +got):\n%s", diff)
}
if _, err := c.GetPrompt(ctx, "fail", map[string]string{}); err == nil || !strings.Contains(err.Error(), failure.Error()) {
if _, err := c.GetPrompt(ctx, &GetPromptParams{Name: "fail"}); err == nil || !strings.Contains(err.Error(), failure.Error()) {
t.Errorf("fail returned unexpected error: got %v, want containing %v", err, failure)
}
})
t.Run("tools", func(t *testing.T) {
gotTools, err := c.ListTools(ctx)
res, err := c.ListTools(ctx, nil)
if err != nil {
t.Errorf("tools/list failed: %v", err)
}
@ -156,7 +156,7 @@ func TestEndToEnd(t *testing.T) {
},
},
}
if diff := cmp.Diff(wantTools, gotTools, cmpopts.IgnoreUnexported(jsonschema.Schema{})); diff != "" {
if diff := cmp.Diff(wantTools, res.Tools, cmpopts.IgnoreUnexported(jsonschema.Schema{})); diff != "" {
t.Fatalf("tools/list mismatch (-want +got):\n%s", diff)
}
@ -350,7 +350,7 @@ func TestBatching(t *testing.T) {
errs := make(chan error, batchSize)
for i := range batchSize {
go func() {
_, err := c.ListTools(ctx)
_, err := c.ListTools(ctx, nil)
errs <- err
}()
time.Sleep(2 * time.Millisecond)

View file

@ -170,6 +170,16 @@ type ListToolsResult struct {
Tools []*Tool `json:"tools"`
}
type PingParams struct {
Meta struct {
// If specified, the caller is requesting out-of-band progress notifications for
// this request (as represented by notifications/progress). The value of this
// parameter is an opaque token that will be attached to any subsequent
// notifications. The receiver is not obligated to provide these notifications.
ProgressToken any `json:"progressToken,omitempty"`
} `json:"_meta,omitempty"`
}
// A prompt or prompt template that the server offers.
type Prompt struct {
// A list of arguments to use for templating the prompt.

View file

@ -303,7 +303,7 @@ type ServerConnection struct {
}
// Ping makes an MCP "ping" request to the client.
func (cc *ServerConnection) Ping(ctx context.Context) error {
func (cc *ServerConnection) Ping(ctx context.Context, _ *PingParams) error {
return call(ctx, cc.conn, "ping", nil, nil)
}

View file

@ -39,7 +39,7 @@ func TestSSEServer(t *testing.T) {
if err := c.Start(ctx); err != nil {
t.Fatal(err)
}
if err := c.Ping(ctx); err != nil {
if err := c.Ping(ctx, nil); err != nil {
t.Fatal(err)
}
cc := <-conns