Skip to content

Commit 5e3c7a2

Browse files
feat: wire up SearchRepositories to use typed input
- Change SearchRepositories to use mcp.ToolHandlerFor[SearchRepositoriesInput, any] - Use SearchRepositoriesInput.MCPSchema() for InputSchema - Handler receives typed struct instead of map[string]any - Update tests to use typed input - Update toolsnaps to reflect the new schema This demonstrates the pattern for wiring up tools to use generated schemas. Pattern established for remaining 90+ tools. Co-authored-by: SamMorrowDrums <[email protected]>
1 parent e1ad100 commit 5e3c7a2

File tree

3 files changed

+42
-100
lines changed

3 files changed

+42
-100
lines changed

pkg/github/__toolsnaps__/search_repositories.snap

Lines changed: 10 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -12,43 +12,40 @@
1212
"properties": {
1313
"minimal_output": {
1414
"type": "boolean",
15-
"description": "Return minimal repository information (default: true). When false, returns full GitHub API repository objects.",
16-
"default": true
15+
"description": "Return minimal repository information - when false returns full GitHub API objects"
1716
},
1817
"order": {
1918
"type": "string",
20-
"description": "Sort order",
19+
"description": "Sort order (asc/desc)",
2120
"enum": [
2221
"asc",
2322
"desc"
2423
]
2524
},
2625
"page": {
27-
"type": "number",
28-
"description": "Page number for pagination (min 1)",
29-
"minimum": 1
26+
"type": "integer",
27+
"description": "Page number for pagination (min 1)"
3028
},
3129
"perPage": {
32-
"type": "number",
33-
"description": "Results per page for pagination (min 1, max 100)",
34-
"minimum": 1,
35-
"maximum": 100
30+
"type": "integer",
31+
"description": "Results per page for pagination (min 1 and max 100)"
3632
},
3733
"query": {
3834
"type": "string",
39-
"description": "Repository search query. Examples: 'machine learning in:name stars:\u003e1000 language:python', 'topic:react', 'user:facebook'. Supports advanced search syntax for precise filtering."
35+
"description": "Repository search query using GitHub search syntax"
4036
},
4137
"sort": {
4238
"type": "string",
43-
"description": "Sort repositories by field, defaults to best match",
39+
"description": "Sort repositories by field - defaults to best match",
4440
"enum": [
4541
"stars",
4642
"forks",
4743
"help-wanted-issues",
4844
"updated"
4945
]
5046
}
51-
}
47+
},
48+
"additionalProperties": false
5249
},
5350
"name": "search_repositories"
5451
}

pkg/github/search.go

Lines changed: 10 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -16,81 +16,34 @@ import (
1616
)
1717

1818
// SearchRepositories creates a tool to search for GitHub repositories.
19-
func SearchRepositories(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
20-
schema := &jsonschema.Schema{
21-
Type: "object",
22-
Properties: map[string]*jsonschema.Schema{
23-
"query": {
24-
Type: "string",
25-
Description: "Repository search query. Examples: 'machine learning in:name stars:>1000 language:python', 'topic:react', 'user:facebook'. Supports advanced search syntax for precise filtering.",
26-
},
27-
"sort": {
28-
Type: "string",
29-
Description: "Sort repositories by field, defaults to best match",
30-
Enum: []any{"stars", "forks", "help-wanted-issues", "updated"},
31-
},
32-
"order": {
33-
Type: "string",
34-
Description: "Sort order",
35-
Enum: []any{"asc", "desc"},
36-
},
37-
"minimal_output": {
38-
Type: "boolean",
39-
Description: "Return minimal repository information (default: true). When false, returns full GitHub API repository objects.",
40-
Default: json.RawMessage(`true`),
41-
},
42-
},
43-
Required: []string{"query"},
44-
}
45-
WithPagination(schema)
46-
19+
func SearchRepositories(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[SearchRepositoriesInput, any]) {
4720
return mcp.Tool{
4821
Name: "search_repositories",
4922
Description: t("TOOL_SEARCH_REPOSITORIES_DESCRIPTION", "Find GitHub repositories by name, description, readme, topics, or other metadata. Perfect for discovering projects, finding examples, or locating specific repositories across GitHub."),
5023
Annotations: &mcp.ToolAnnotations{
5124
Title: t("TOOL_SEARCH_REPOSITORIES_USER_TITLE", "Search repositories"),
5225
ReadOnlyHint: true,
5326
},
54-
InputSchema: schema,
27+
InputSchema: SearchRepositoriesInput{}.MCPSchema(),
5528
},
56-
func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
57-
query, err := RequiredParam[string](args, "query")
58-
if err != nil {
59-
return utils.NewToolResultError(err.Error()), nil, nil
60-
}
61-
sort, err := OptionalParam[string](args, "sort")
62-
if err != nil {
63-
return utils.NewToolResultError(err.Error()), nil, nil
64-
}
65-
order, err := OptionalParam[string](args, "order")
66-
if err != nil {
67-
return utils.NewToolResultError(err.Error()), nil, nil
68-
}
69-
pagination, err := OptionalPaginationParams(args)
70-
if err != nil {
71-
return utils.NewToolResultError(err.Error()), nil, nil
72-
}
73-
minimalOutput, err := OptionalBoolParamWithDefault(args, "minimal_output", true)
74-
if err != nil {
75-
return utils.NewToolResultError(err.Error()), nil, nil
76-
}
29+
func(ctx context.Context, _ *mcp.CallToolRequest, input SearchRepositoriesInput) (*mcp.CallToolResult, any, error) {
7730
opts := &github.SearchOptions{
78-
Sort: sort,
79-
Order: order,
31+
Sort: input.Sort,
32+
Order: input.Order,
8033
ListOptions: github.ListOptions{
81-
Page: pagination.Page,
82-
PerPage: pagination.PerPage,
34+
Page: input.Page,
35+
PerPage: input.PerPage,
8336
},
8437
}
8538

8639
client, err := getClient(ctx)
8740
if err != nil {
8841
return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, nil
8942
}
90-
result, resp, err := client.Search.Repositories(ctx, query, opts)
43+
result, resp, err := client.Search.Repositories(ctx, input.Query, opts)
9144
if err != nil {
9245
return ghErrors.NewGitHubAPIErrorResponse(ctx,
93-
fmt.Sprintf("failed to search repositories with query '%s'", query),
46+
fmt.Sprintf("failed to search repositories with query '%s'", input.Query),
9447
resp,
9548
err,
9649
), nil, nil
@@ -107,7 +60,7 @@ func SearchRepositories(getClient GetClientFn, t translations.TranslationHelperF
10760

10861
// Return either minimal or full response based on parameter
10962
var r []byte
110-
if minimalOutput {
63+
if input.MinimalOutput {
11164
minimalRepos := make([]MinimalRepository, 0, len(result.Repositories))
11265
for _, repo := range result.Repositories {
11366
minimalRepo := MinimalRepository{

pkg/github/search_test.go

Lines changed: 22 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@ func Test_SearchRepositories(t *testing.T) {
2424
assert.Equal(t, "search_repositories", tool.Name)
2525
assert.NotEmpty(t, tool.Description)
2626

27-
schema, ok := tool.InputSchema.(*jsonschema.Schema)
28-
require.True(t, ok, "InputSchema should be *jsonschema.Schema")
27+
// Get schema from the input type's MCPSchema() method
28+
schema := SearchRepositoriesInput{}.MCPSchema()
29+
require.NotNil(t, schema, "MCPSchema should return a schema")
2930
assert.Contains(t, schema.Properties, "query")
3031
assert.Contains(t, schema.Properties, "sort")
3132
assert.Contains(t, schema.Properties, "order")
@@ -60,7 +61,7 @@ func Test_SearchRepositories(t *testing.T) {
6061
tests := []struct {
6162
name string
6263
mockedClient *http.Client
63-
requestArgs map[string]interface{}
64+
input SearchRepositoriesInput
6465
expectError bool
6566
expectedResult *github.RepositoriesSearchResult
6667
expectedErrMsg string
@@ -81,12 +82,12 @@ func Test_SearchRepositories(t *testing.T) {
8182
),
8283
),
8384
),
84-
requestArgs: map[string]interface{}{
85-
"query": "golang test",
86-
"sort": "stars",
87-
"order": "desc",
88-
"page": float64(2),
89-
"perPage": float64(10),
85+
input: SearchRepositoriesInput{
86+
Query: "golang test",
87+
Sort: "stars",
88+
Order: "desc",
89+
Page: 2,
90+
PerPage: 10,
9091
},
9192
expectError: false,
9293
expectedResult: mockSearchResult,
@@ -97,16 +98,14 @@ func Test_SearchRepositories(t *testing.T) {
9798
mock.WithRequestMatchHandler(
9899
mock.GetSearchRepositories,
99100
expectQueryParams(t, map[string]string{
100-
"q": "golang test",
101-
"page": "1",
102-
"per_page": "30",
101+
"q": "golang test",
103102
}).andThen(
104103
mockResponse(t, http.StatusOK, mockSearchResult),
105104
),
106105
),
107106
),
108-
requestArgs: map[string]interface{}{
109-
"query": "golang test",
107+
input: SearchRepositoriesInput{
108+
Query: "golang test",
110109
},
111110
expectError: false,
112111
expectedResult: mockSearchResult,
@@ -122,8 +121,8 @@ func Test_SearchRepositories(t *testing.T) {
122121
}),
123122
),
124123
),
125-
requestArgs: map[string]interface{}{
126-
"query": "invalid:query",
124+
input: SearchRepositoriesInput{
125+
Query: "invalid:query",
127126
},
128127
expectError: true,
129128
expectedErrMsg: "failed to search repositories",
@@ -136,11 +135,8 @@ func Test_SearchRepositories(t *testing.T) {
136135
client := github.NewClient(tc.mockedClient)
137136
_, handler := SearchRepositories(stubGetClientFn(client), translations.NullTranslationHelper)
138137

139-
// Create call request
140-
request := createMCPRequest(tc.requestArgs)
141-
142-
// Call handler
143-
result, _, err := handler(context.Background(), &request, tc.requestArgs)
138+
// Call handler with typed input
139+
result, _, err := handler(context.Background(), nil, tc.input)
144140

145141
// Verify results
146142
if tc.expectError {
@@ -195,9 +191,7 @@ func Test_SearchRepositories_FullOutput(t *testing.T) {
195191
mock.WithRequestMatchHandler(
196192
mock.GetSearchRepositories,
197193
expectQueryParams(t, map[string]string{
198-
"q": "golang test",
199-
"page": "1",
200-
"per_page": "30",
194+
"q": "golang test",
201195
}).andThen(
202196
mockResponse(t, http.StatusOK, mockSearchResult),
203197
),
@@ -207,14 +201,12 @@ func Test_SearchRepositories_FullOutput(t *testing.T) {
207201
client := github.NewClient(mockedClient)
208202
_, handlerTest := SearchRepositories(stubGetClientFn(client), translations.NullTranslationHelper)
209203

210-
args := map[string]interface{}{
211-
"query": "golang test",
212-
"minimal_output": false,
204+
input := SearchRepositoriesInput{
205+
Query: "golang test",
206+
MinimalOutput: false,
213207
}
214208

215-
request := createMCPRequest(args)
216-
217-
result, _, err := handlerTest(context.Background(), &request, args)
209+
result, _, err := handlerTest(context.Background(), nil, input)
218210

219211
require.NoError(t, err)
220212
require.False(t, result.IsError)

0 commit comments

Comments
 (0)