diff --git a/mcp/client.go b/mcp/client.go index 8ca4600..d4dfba6 100644 --- a/mcp/client.go +++ b/mcp/client.go @@ -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. diff --git a/mcp/generate.go b/mcp/generate.go index cec5b0e..37089ab 100644 --- a/mcp/generate.go +++ b/mcp/generate.go @@ -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"}}, diff --git a/mcp/mcp_test.go b/mcp/mcp_test.go index acb638f..2d494b3 100644 --- a/mcp/mcp_test.go +++ b/mcp/mcp_test.go @@ -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) diff --git a/mcp/protocol.go b/mcp/protocol.go index ca36815..5b78a9e 100644 --- a/mcp/protocol.go +++ b/mcp/protocol.go @@ -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. diff --git a/mcp/server.go b/mcp/server.go index 9ea3214..ce6ef81 100644 --- a/mcp/server.go +++ b/mcp/server.go @@ -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) } diff --git a/mcp/sse_test.go b/mcp/sse_test.go index ad45e64..5cbd2cc 100644 --- a/mcp/sse_test.go +++ b/mcp/sse_test.go @@ -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