Conform list tools · codingoutloud/github-mcp-server@6f7458a · GitHub
Skip to content

Commit 6f7458a

Browse files
committed
Conform list tools
1 parent 989c43f commit 6f7458a

14 files changed

Lines changed: 239 additions & 153 deletions

‎README.md‎

Lines changed: 20 additions & 20 deletions

‎cmd/mcpcurl/README.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ Use "mcpcurl tools [command] --help" for more information about a command.
7272
Get help for a specific tool:
7373

7474
```bash
75-
% ./mcpcurl --stdio-server-cmd "docker run -i --rm -e GITHUB_PERSONAL_ACCESS_TOKEN mcp/github" tools get_issue --help
75+
% ./mcpcurl --stdio-server-cmd "docker run -i --rm -e GITHUB_PERSONAL_ACCESS_TOKEN mcp/github" tools get_issue --help
7676
Get details of a specific issue in a GitHub repository.
7777

7878
Usage:

‎conformance/conformance_test.go‎

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"bufio"
77
"context"
88
"encoding/json"
9+
"errors"
910
"fmt"
1011
"io"
1112
"os"
@@ -17,6 +18,8 @@ import (
1718
"github.com/docker/docker/api/types/network"
1819
"github.com/docker/docker/client"
1920
"github.com/docker/docker/pkg/stdcopy"
21+
"github.com/google/go-cmp/cmp"
22+
"github.com/google/go-cmp/cmp/cmpopts"
2023
"github.com/stretchr/testify/require"
2124
)
2225

@@ -230,6 +233,69 @@ func diffNonNilFields(a, b interface{}, path string) string {
230233
return sb.String()
231234
}
232235

236+
func TestListTools(t *testing.T) {
237+
anthropicServer := start(t, anthropic)
238+
githubServer := start(t, github)
239+
240+
req := listToolsRequest{
241+
JSONRPC: "2.0",
242+
ID: 1,
243+
Method: "tools/list",
244+
}
245+
246+
require.NoError(t, anthropicServer.send(req))
247+
248+
var anthropicListToolsResponse listToolsResponse
249+
require.NoError(t, anthropicServer.receive(&anthropicListToolsResponse))
250+
251+
require.NoError(t, githubServer.send(req))
252+
253+
var ghListToolsResponse listToolsResponse
254+
require.NoError(t, githubServer.receive(&ghListToolsResponse))
255+
256+
require.NoError(t, isToolListSubset(anthropicListToolsResponse.Result, ghListToolsResponse.Result), "expected the github list tools response to be a subset of the anthropic list tools response")
257+
}
258+
259+
func isToolListSubset(subset, superset listToolsResult) error {
260+
// Build a map from tool name to Tool from the superset
261+
supersetMap := make(map[string]tool)
262+
for _, tool := range superset.Tools {
263+
supersetMap[tool.Name] = tool
264+
}
265+
266+
var err error
267+
for _, tool := range subset.Tools {
268+
sup, ok := supersetMap[tool.Name]
269+
if !ok {
270+
return fmt.Errorf("tool %q not found in superset", tool.Name)
271+
}
272+
273+
// Intentionally ignore the description fields because there are lots of slight differences.
274+
// if tool.Description != sup.Description {
275+
// return fmt.Errorf("description mismatch for tool %q, got %q expected %q", tool.Name, tool.Description, sup.Description)
276+
// }
277+
278+
// Ignore any description fields within the input schema properties for the same reason
279+
ignoreDescOpt := cmp.FilterPath(func(p cmp.Path) bool {
280+
// Look for a field named "Properties" somewhere in the path
281+
for _, ps := range p {
282+
if sf, ok := ps.(cmp.StructField); ok && sf.Name() == "Properties" {
283+
return true
284+
}
285+
}
286+
return false
287+
}, cmpopts.IgnoreMapEntries(func(k string, _ any) bool {
288+
return k == "description"
289+
}))
290+
291+
if diff := cmp.Diff(tool.InputSchema, sup.InputSchema, ignoreDescOpt); diff != "" {
292+
err = errors.Join(err, fmt.Errorf("inputSchema mismatch for tool %q:\n%s", tool.Name, diff))
293+
}
294+
}
295+
296+
return err
297+
}
298+
233299
type serverStartResult struct {
234300
server server
235301
err error
@@ -348,3 +414,22 @@ type serverInfo struct {
348414
Name string `json:"name"`
349415
Version string `json:"version"`
350416
}
417+
418+
type listToolsRequest = jsonRPRCRequest[struct{}]
419+
420+
type listToolsResponse = jsonRPRCResponse[listToolsResult]
421+
422+
type listToolsResult struct {
423+
Tools []tool `json:"tools"`
424+
}
425+
type tool struct {
426+
Name string `json:"name"`
427+
Description string `json:"description,omitempty"`
428+
InputSchema inputSchema `json:"inputSchema"`
429+
}
430+
431+
type inputSchema struct {
432+
Type string `json:"type"`
433+
Properties map[string]any `json:"properties,omitempty"`
434+
Required []string `json:"required,omitempty"`
435+
}

‎go.mod‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ go 1.23.7
55
require (
66
github.com/aws/smithy-go v1.22.3
77
github.com/docker/docker v28.0.4+incompatible
8+
github.com/google/go-cmp v0.7.0
89
github.com/google/go-github/v69 v69.2.0
910
github.com/mark3labs/mcp-go v0.18.0
1011
github.com/migueleliasweb/go-github-mock v1.1.0

‎pkg/github/code_scanning.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ func getCodeScanningAlert(client *github.Client, t translations.TranslationHelpe
2424
mcp.Required(),
2525
mcp.Description("The name of the repository."),
2626
),
27-
mcp.WithNumber("alert_number",
27+
mcp.WithNumber("alertNumber",
2828
mcp.Required(),
2929
mcp.Description("The number of the alert."),
3030
),
@@ -38,7 +38,7 @@ func getCodeScanningAlert(client *github.Client, t translations.TranslationHelpe
3838
if err != nil {
3939
return mcp.NewToolResultError(err.Error()), nil
4040
}
41-
alertNumber, err := requiredInt(request, "alert_number")
41+
alertNumber, err := requiredInt(request, "alertNumber")
4242
if err != nil {
4343
return mcp.NewToolResultError(err.Error()), nil
4444
}

‎pkg/github/code_scanning_test.go‎

Lines changed: 8 additions & 8 deletions

0 commit comments

Comments
 (0)