Skip to content

Commit ad64c47

Browse files
feat: wire up all search tools to use typed inputs
- SearchRepositories: SearchRepositoriesInput (already done) - SearchCode: SearchCodeInput - SearchUsers: SearchUsersInput (with generic userOrOrgHandlerTyped) - SearchOrgs: SearchOrgsInput (with generic userOrOrgHandlerTyped) Created userOrOrgSearchInput interface with generic handler for shared user/org search functionality. Updated all search tests to use typed inputs. Updated toolsnaps for all search tools. Co-authored-by: SamMorrowDrums <[email protected]>
1 parent 5e3c7a2 commit ad64c47

File tree

5 files changed

+130
-234
lines changed

5 files changed

+130
-234
lines changed

pkg/github/__toolsnaps__/search_code.snap

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,32 +12,30 @@
1212
"properties": {
1313
"order": {
1414
"type": "string",
15-
"description": "Sort order for results",
15+
"description": "Sort order for results (asc/desc)",
1616
"enum": [
1717
"asc",
1818
"desc"
1919
]
2020
},
2121
"page": {
22-
"type": "number",
23-
"description": "Page number for pagination (min 1)",
24-
"minimum": 1
22+
"type": "integer",
23+
"description": "Page number for pagination (min 1)"
2524
},
2625
"perPage": {
27-
"type": "number",
28-
"description": "Results per page for pagination (min 1, max 100)",
29-
"minimum": 1,
30-
"maximum": 100
26+
"type": "integer",
27+
"description": "Results per page for pagination (min 1 and max 100)"
3128
},
3229
"query": {
3330
"type": "string",
34-
"description": "Search query using GitHub's powerful code search syntax. Examples: 'content:Skill language:Java org:github', 'NOT is:archived language:Python OR language:go', 'repo:github/github-mcp-server'. Supports exact matching, language filters, path filters, and more."
31+
"description": "Search query using GitHub code search syntax"
3532
},
3633
"sort": {
3734
"type": "string",
38-
"description": "Sort field ('indexed' only)"
35+
"description": "Sort field (indexed only)"
3936
}
40-
}
37+
},
38+
"additionalProperties": false
4139
},
4240
"name": "search_code"
4341
}

pkg/github/__toolsnaps__/search_orgs.snap

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19,30 +19,28 @@
1919
]
2020
},
2121
"page": {
22-
"type": "number",
23-
"description": "Page number for pagination (min 1)",
24-
"minimum": 1
22+
"type": "integer",
23+
"description": "Page number for pagination (min 1)"
2524
},
2625
"perPage": {
27-
"type": "number",
28-
"description": "Results per page for pagination (min 1, max 100)",
29-
"minimum": 1,
30-
"maximum": 100
26+
"type": "integer",
27+
"description": "Results per page for pagination (min 1 and max 100)"
3128
},
3229
"query": {
3330
"type": "string",
34-
"description": "Organization search query. Examples: 'microsoft', 'location:california', 'created:\u003e=2025-01-01'. Search is automatically scoped to type:org."
31+
"description": "Organization search query using GitHub search syntax"
3532
},
3633
"sort": {
3734
"type": "string",
38-
"description": "Sort field by category",
35+
"description": "Sort field",
3936
"enum": [
4037
"followers",
4138
"repositories",
4239
"joined"
4340
]
4441
}
45-
}
42+
},
43+
"additionalProperties": false
4644
},
4745
"name": "search_orgs"
4846
}

pkg/github/__toolsnaps__/search_users.snap

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,37 +12,35 @@
1212
"properties": {
1313
"order": {
1414
"type": "string",
15-
"description": "Sort order",
15+
"description": "Sort order (asc/desc)",
1616
"enum": [
1717
"asc",
1818
"desc"
1919
]
2020
},
2121
"page": {
22-
"type": "number",
23-
"description": "Page number for pagination (min 1)",
24-
"minimum": 1
22+
"type": "integer",
23+
"description": "Page number for pagination (min 1)"
2524
},
2625
"perPage": {
27-
"type": "number",
28-
"description": "Results per page for pagination (min 1, max 100)",
29-
"minimum": 1,
30-
"maximum": 100
26+
"type": "integer",
27+
"description": "Results per page for pagination (min 1 and max 100)"
3128
},
3229
"query": {
3330
"type": "string",
34-
"description": "User search query. Examples: 'john smith', 'location:seattle', 'followers:\u003e100'. Search is automatically scoped to type:user."
31+
"description": "User search query using GitHub search syntax"
3532
},
3633
"sort": {
3734
"type": "string",
38-
"description": "Sort users by number of followers or repositories, or when the person joined GitHub.",
35+
"description": "Sort users by followers/repositories/joined",
3936
"enum": [
4037
"followers",
4138
"repositories",
4239
"joined"
4340
]
4441
}
45-
}
42+
},
43+
"additionalProperties": false
4644
},
4745
"name": "search_users"
4846
}

pkg/github/search.go

Lines changed: 47 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ import (
1111
"github.com/github/github-mcp-server/pkg/translations"
1212
"github.com/github/github-mcp-server/pkg/utils"
1313
"github.com/google/go-github/v79/github"
14-
"github.com/google/jsonschema-go/jsonschema"
1514
"github.com/modelcontextprotocol/go-sdk/mcp"
1615
)
1716

@@ -114,61 +113,23 @@ func SearchRepositories(getClient GetClientFn, t translations.TranslationHelperF
114113
}
115114

116115
// SearchCode creates a tool to search for code across GitHub repositories.
117-
func SearchCode(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
118-
schema := &jsonschema.Schema{
119-
Type: "object",
120-
Properties: map[string]*jsonschema.Schema{
121-
"query": {
122-
Type: "string",
123-
Description: "Search query using GitHub's powerful code search syntax. Examples: 'content:Skill language:Java org:github', 'NOT is:archived language:Python OR language:go', 'repo:github/github-mcp-server'. Supports exact matching, language filters, path filters, and more.",
124-
},
125-
"sort": {
126-
Type: "string",
127-
Description: "Sort field ('indexed' only)",
128-
},
129-
"order": {
130-
Type: "string",
131-
Description: "Sort order for results",
132-
Enum: []any{"asc", "desc"},
133-
},
134-
},
135-
Required: []string{"query"},
136-
}
137-
WithPagination(schema)
138-
116+
func SearchCode(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[SearchCodeInput, any]) {
139117
return mcp.Tool{
140118
Name: "search_code",
141119
Description: t("TOOL_SEARCH_CODE_DESCRIPTION", "Fast and precise code search across ALL GitHub repositories using GitHub's native search engine. Best for finding exact symbols, functions, classes, or specific code patterns."),
142120
Annotations: &mcp.ToolAnnotations{
143121
Title: t("TOOL_SEARCH_CODE_USER_TITLE", "Search code"),
144122
ReadOnlyHint: true,
145123
},
146-
InputSchema: schema,
124+
InputSchema: SearchCodeInput{}.MCPSchema(),
147125
},
148-
func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
149-
query, err := RequiredParam[string](args, "query")
150-
if err != nil {
151-
return utils.NewToolResultError(err.Error()), nil, nil
152-
}
153-
sort, err := OptionalParam[string](args, "sort")
154-
if err != nil {
155-
return utils.NewToolResultError(err.Error()), nil, nil
156-
}
157-
order, err := OptionalParam[string](args, "order")
158-
if err != nil {
159-
return utils.NewToolResultError(err.Error()), nil, nil
160-
}
161-
pagination, err := OptionalPaginationParams(args)
162-
if err != nil {
163-
return utils.NewToolResultError(err.Error()), nil, nil
164-
}
165-
126+
func(ctx context.Context, _ *mcp.CallToolRequest, input SearchCodeInput) (*mcp.CallToolResult, any, error) {
166127
opts := &github.SearchOptions{
167-
Sort: sort,
168-
Order: order,
128+
Sort: input.Sort,
129+
Order: input.Order,
169130
ListOptions: github.ListOptions{
170-
PerPage: pagination.PerPage,
171-
Page: pagination.Page,
131+
PerPage: input.PerPage,
132+
Page: input.Page,
172133
},
173134
}
174135

@@ -177,10 +138,10 @@ func SearchCode(getClient GetClientFn, t translations.TranslationHelperFunc) (mc
177138
return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, nil
178139
}
179140

180-
result, resp, err := client.Search.Code(ctx, query, opts)
141+
result, resp, err := client.Search.Code(ctx, input.Query, opts)
181142
if err != nil {
182143
return ghErrors.NewGitHubAPIErrorResponse(ctx,
183-
fmt.Sprintf("failed to search code with query '%s'", query),
144+
fmt.Sprintf("failed to search code with query '%s'", input.Query),
184145
resp,
185146
err,
186147
), nil, nil
@@ -204,31 +165,37 @@ func SearchCode(getClient GetClientFn, t translations.TranslationHelperFunc) (mc
204165
}
205166
}
206167

207-
func userOrOrgHandler(accountType string, getClient GetClientFn) mcp.ToolHandlerFor[map[string]any, any] {
208-
return func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) {
209-
query, err := RequiredParam[string](args, "query")
210-
if err != nil {
211-
return utils.NewToolResultError(err.Error()), nil, nil
212-
}
213-
sort, err := OptionalParam[string](args, "sort")
214-
if err != nil {
215-
return utils.NewToolResultError(err.Error()), nil, nil
216-
}
217-
order, err := OptionalParam[string](args, "order")
218-
if err != nil {
219-
return utils.NewToolResultError(err.Error()), nil, nil
220-
}
221-
pagination, err := OptionalPaginationParams(args)
222-
if err != nil {
223-
return utils.NewToolResultError(err.Error()), nil, nil
224-
}
168+
// userOrOrgSearchInput is a common interface for user/org search inputs
169+
type userOrOrgSearchInput interface {
170+
GetQuery() string
171+
GetSort() string
172+
GetOrder() string
173+
GetPage() int
174+
GetPerPage() int
175+
}
176+
177+
// Add getters to SearchUsersInput
178+
func (s SearchUsersInput) GetQuery() string { return s.Query }
179+
func (s SearchUsersInput) GetSort() string { return s.Sort }
180+
func (s SearchUsersInput) GetOrder() string { return s.Order }
181+
func (s SearchUsersInput) GetPage() int { return s.Page }
182+
func (s SearchUsersInput) GetPerPage() int { return s.PerPage }
183+
184+
// Add getters to SearchOrgsInput
185+
func (s SearchOrgsInput) GetQuery() string { return s.Query }
186+
func (s SearchOrgsInput) GetSort() string { return s.Sort }
187+
func (s SearchOrgsInput) GetOrder() string { return s.Order }
188+
func (s SearchOrgsInput) GetPage() int { return s.Page }
189+
func (s SearchOrgsInput) GetPerPage() int { return s.PerPage }
225190

191+
func userOrOrgHandlerTyped[T userOrOrgSearchInput](accountType string, getClient GetClientFn) mcp.ToolHandlerFor[T, any] {
192+
return func(ctx context.Context, _ *mcp.CallToolRequest, input T) (*mcp.CallToolResult, any, error) {
226193
opts := &github.SearchOptions{
227-
Sort: sort,
228-
Order: order,
194+
Sort: input.GetSort(),
195+
Order: input.GetOrder(),
229196
ListOptions: github.ListOptions{
230-
PerPage: pagination.PerPage,
231-
Page: pagination.Page,
197+
PerPage: input.GetPerPage(),
198+
Page: input.GetPage(),
232199
},
233200
}
234201

@@ -237,14 +204,14 @@ func userOrOrgHandler(accountType string, getClient GetClientFn) mcp.ToolHandler
237204
return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, nil
238205
}
239206

240-
searchQuery := query
241-
if !hasTypeFilter(query) {
242-
searchQuery = "type:" + accountType + " " + query
207+
searchQuery := input.GetQuery()
208+
if !hasTypeFilter(searchQuery) {
209+
searchQuery = "type:" + accountType + " " + searchQuery
243210
}
244211
result, resp, err := client.Search.Users(ctx, searchQuery, opts)
245212
if err != nil {
246213
return ghErrors.NewGitHubAPIErrorResponse(ctx,
247-
fmt.Sprintf("failed to search %ss with query '%s'", accountType, query),
214+
fmt.Sprintf("failed to search %ss with query '%s'", accountType, input.GetQuery()),
248215
resp,
249216
err,
250217
), nil, nil
@@ -293,71 +260,27 @@ func userOrOrgHandler(accountType string, getClient GetClientFn) mcp.ToolHandler
293260
}
294261

295262
// SearchUsers creates a tool to search for GitHub users.
296-
func SearchUsers(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
297-
schema := &jsonschema.Schema{
298-
Type: "object",
299-
Properties: map[string]*jsonschema.Schema{
300-
"query": {
301-
Type: "string",
302-
Description: "User search query. Examples: 'john smith', 'location:seattle', 'followers:>100'. Search is automatically scoped to type:user.",
303-
},
304-
"sort": {
305-
Type: "string",
306-
Description: "Sort users by number of followers or repositories, or when the person joined GitHub.",
307-
Enum: []any{"followers", "repositories", "joined"},
308-
},
309-
"order": {
310-
Type: "string",
311-
Description: "Sort order",
312-
Enum: []any{"asc", "desc"},
313-
},
314-
},
315-
Required: []string{"query"},
316-
}
317-
WithPagination(schema)
318-
263+
func SearchUsers(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[SearchUsersInput, any]) {
319264
return mcp.Tool{
320265
Name: "search_users",
321266
Description: t("TOOL_SEARCH_USERS_DESCRIPTION", "Find GitHub users by username, real name, or other profile information. Useful for locating developers, contributors, or team members."),
322267
Annotations: &mcp.ToolAnnotations{
323268
Title: t("TOOL_SEARCH_USERS_USER_TITLE", "Search users"),
324269
ReadOnlyHint: true,
325270
},
326-
InputSchema: schema,
327-
}, userOrOrgHandler("user", getClient)
271+
InputSchema: SearchUsersInput{}.MCPSchema(),
272+
}, userOrOrgHandlerTyped[SearchUsersInput]("user", getClient)
328273
}
329274

330275
// SearchOrgs creates a tool to search for GitHub organizations.
331-
func SearchOrgs(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) {
332-
schema := &jsonschema.Schema{
333-
Type: "object",
334-
Properties: map[string]*jsonschema.Schema{
335-
"query": {
336-
Type: "string",
337-
Description: "Organization search query. Examples: 'microsoft', 'location:california', 'created:>=2025-01-01'. Search is automatically scoped to type:org.",
338-
},
339-
"sort": {
340-
Type: "string",
341-
Description: "Sort field by category",
342-
Enum: []any{"followers", "repositories", "joined"},
343-
},
344-
"order": {
345-
Type: "string",
346-
Description: "Sort order",
347-
Enum: []any{"asc", "desc"},
348-
},
349-
},
350-
Required: []string{"query"},
351-
}
352-
WithPagination(schema)
353-
276+
func SearchOrgs(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[SearchOrgsInput, any]) {
354277
return mcp.Tool{
355278
Name: "search_orgs",
356279
Description: t("TOOL_SEARCH_ORGS_DESCRIPTION", "Find GitHub organizations by name, location, or other organization metadata. Ideal for discovering companies, open source foundations, or teams."),
357280
Annotations: &mcp.ToolAnnotations{
358281
Title: t("TOOL_SEARCH_ORGS_USER_TITLE", "Search organizations"),
359282
ReadOnlyHint: true,
360283
},
361-
InputSchema: schema,
362-
}, userOrOrgHandler("org", getClient)
284+
InputSchema: SearchOrgsInput{}.MCPSchema(),
285+
}, userOrOrgHandlerTyped[SearchOrgsInput]("org", getClient)
363286
}

0 commit comments

Comments
 (0)