Skip to content

Commit acba284

Browse files
cleanup search_users response (#486)
1 parent 23b16cf commit acba284

File tree

4 files changed

+261
-93
lines changed

4 files changed

+261
-93
lines changed

pkg/github/__toolsnaps__/search_users.snap

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
"title": "Search users",
44
"readOnlyHint": true
55
},
6-
"description": "Search for GitHub users",
6+
"description": "Search for GitHub users exclusively",
77
"inputSchema": {
88
"properties": {
99
"order": {
@@ -25,8 +25,8 @@
2525
"minimum": 1,
2626
"type": "number"
2727
},
28-
"q": {
29-
"description": "Search query using GitHub users search syntax",
28+
"query": {
29+
"description": "Search query using GitHub users search syntax scoped to type:user",
3030
"type": "string"
3131
},
3232
"sort": {
@@ -40,7 +40,7 @@
4040
}
4141
},
4242
"required": [
43-
"q"
43+
"query"
4444
],
4545
"type": "object"
4646
},

pkg/github/search.go

Lines changed: 123 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -168,100 +168,139 @@ type MinimalSearchUsersResult struct {
168168
Items []MinimalUser `json:"items"`
169169
}
170170

171-
// SearchUsers creates a tool to search for GitHub users.
172-
func SearchUsers(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
173-
return mcp.NewTool("search_users",
174-
mcp.WithDescription(t("TOOL_SEARCH_USERS_DESCRIPTION", "Search for GitHub users")),
175-
mcp.WithToolAnnotation(mcp.ToolAnnotation{
176-
Title: t("TOOL_SEARCH_USERS_USER_TITLE", "Search users"),
177-
ReadOnlyHint: ToBoolPtr(true),
178-
}),
179-
mcp.WithString("q",
180-
mcp.Required(),
181-
mcp.Description("Search query using GitHub users search syntax"),
182-
),
183-
mcp.WithString("sort",
184-
mcp.Description("Sort field by category"),
185-
mcp.Enum("followers", "repositories", "joined"),
186-
),
187-
mcp.WithString("order",
188-
mcp.Description("Sort order"),
189-
mcp.Enum("asc", "desc"),
190-
),
191-
WithPagination(),
192-
),
193-
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
194-
query, err := RequiredParam[string](request, "q")
195-
if err != nil {
196-
return mcp.NewToolResultError(err.Error()), nil
197-
}
198-
sort, err := OptionalParam[string](request, "sort")
199-
if err != nil {
200-
return mcp.NewToolResultError(err.Error()), nil
201-
}
202-
order, err := OptionalParam[string](request, "order")
203-
if err != nil {
204-
return mcp.NewToolResultError(err.Error()), nil
205-
}
206-
pagination, err := OptionalPaginationParams(request)
207-
if err != nil {
208-
return mcp.NewToolResultError(err.Error()), nil
209-
}
171+
func userOrOrgHandler(accountType string, getClient GetClientFn) server.ToolHandlerFunc {
172+
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
173+
query, err := RequiredParam[string](request, "query")
174+
if err != nil {
175+
return mcp.NewToolResultError(err.Error()), nil
176+
}
177+
sort, err := OptionalParam[string](request, "sort")
178+
if err != nil {
179+
return mcp.NewToolResultError(err.Error()), nil
180+
}
181+
order, err := OptionalParam[string](request, "order")
182+
if err != nil {
183+
return mcp.NewToolResultError(err.Error()), nil
184+
}
185+
pagination, err := OptionalPaginationParams(request)
186+
if err != nil {
187+
return mcp.NewToolResultError(err.Error()), nil
188+
}
210189

211-
opts := &github.SearchOptions{
212-
Sort: sort,
213-
Order: order,
214-
ListOptions: github.ListOptions{
215-
PerPage: pagination.perPage,
216-
Page: pagination.page,
217-
},
218-
}
190+
opts := &github.SearchOptions{
191+
Sort: sort,
192+
Order: order,
193+
ListOptions: github.ListOptions{
194+
PerPage: pagination.perPage,
195+
Page: pagination.page,
196+
},
197+
}
219198

220-
client, err := getClient(ctx)
221-
if err != nil {
222-
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
223-
}
199+
client, err := getClient(ctx)
200+
if err != nil {
201+
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
202+
}
224203

225-
result, resp, err := client.Search.Users(ctx, "type:user "+query, opts)
204+
searchQuery := "type:" + accountType + " " + query
205+
result, resp, err := client.Search.Users(ctx, searchQuery, opts)
206+
if err != nil {
207+
return ghErrors.NewGitHubAPIErrorResponse(ctx,
208+
fmt.Sprintf("failed to search %ss with query '%s'", accountType, query),
209+
resp,
210+
err,
211+
), nil
212+
}
213+
defer func() { _ = resp.Body.Close() }()
214+
215+
if resp.StatusCode != 200 {
216+
body, err := io.ReadAll(resp.Body)
226217
if err != nil {
227-
return ghErrors.NewGitHubAPIErrorResponse(ctx,
228-
fmt.Sprintf("failed to search users with query '%s'", query),
229-
resp,
230-
err,
231-
), nil
218+
return nil, fmt.Errorf("failed to read response body: %w", err)
232219
}
233-
defer func() { _ = resp.Body.Close() }()
220+
return mcp.NewToolResultError(fmt.Sprintf("failed to search %ss: %s", accountType, string(body))), nil
221+
}
234222

235-
if resp.StatusCode != 200 {
236-
body, err := io.ReadAll(resp.Body)
237-
if err != nil {
238-
return nil, fmt.Errorf("failed to read response body: %w", err)
239-
}
240-
return mcp.NewToolResultError(fmt.Sprintf("failed to search users: %s", string(body))), nil
241-
}
223+
minimalUsers := make([]MinimalUser, 0, len(result.Users))
242224

243-
minimalUsers := make([]MinimalUser, 0, len(result.Users))
244-
for _, user := range result.Users {
245-
mu := MinimalUser{
246-
Login: user.GetLogin(),
247-
ID: user.GetID(),
248-
ProfileURL: user.GetHTMLURL(),
249-
AvatarURL: user.GetAvatarURL(),
225+
for _, user := range result.Users {
226+
if user.Login != nil {
227+
mu := MinimalUser{Login: *user.Login}
228+
if user.ID != nil {
229+
mu.ID = *user.ID
230+
}
231+
if user.HTMLURL != nil {
232+
mu.ProfileURL = *user.HTMLURL
233+
}
234+
if user.AvatarURL != nil {
235+
mu.AvatarURL = *user.AvatarURL
250236
}
251-
252237
minimalUsers = append(minimalUsers, mu)
253238
}
239+
}
240+
minimalResp := &MinimalSearchUsersResult{
241+
TotalCount: result.GetTotal(),
242+
IncompleteResults: result.GetIncompleteResults(),
243+
Items: minimalUsers,
244+
}
245+
if result.Total != nil {
246+
minimalResp.TotalCount = *result.Total
247+
}
248+
if result.IncompleteResults != nil {
249+
minimalResp.IncompleteResults = *result.IncompleteResults
250+
}
254251

255-
minimalResp := MinimalSearchUsersResult{
256-
TotalCount: result.GetTotal(),
257-
IncompleteResults: result.GetIncompleteResults(),
258-
Items: minimalUsers,
259-
}
260-
261-
r, err := json.Marshal(minimalResp)
262-
if err != nil {
263-
return nil, fmt.Errorf("failed to marshal response: %w", err)
264-
}
265-
return mcp.NewToolResultText(string(r)), nil
252+
r, err := json.Marshal(minimalResp)
253+
if err != nil {
254+
return nil, fmt.Errorf("failed to marshal response: %w", err)
266255
}
256+
return mcp.NewToolResultText(string(r)), nil
257+
}
258+
}
259+
260+
// SearchUsers creates a tool to search for GitHub users.
261+
func SearchUsers(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
262+
return mcp.NewTool("search_users",
263+
mcp.WithDescription(t("TOOL_SEARCH_USERS_DESCRIPTION", "Search for GitHub users exclusively")),
264+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
265+
Title: t("TOOL_SEARCH_USERS_USER_TITLE", "Search users"),
266+
ReadOnlyHint: ToBoolPtr(true),
267+
}),
268+
mcp.WithString("query",
269+
mcp.Required(),
270+
mcp.Description("Search query using GitHub users search syntax scoped to type:user"),
271+
),
272+
mcp.WithString("sort",
273+
mcp.Description("Sort field by category"),
274+
mcp.Enum("followers", "repositories", "joined"),
275+
),
276+
mcp.WithString("order",
277+
mcp.Description("Sort order"),
278+
mcp.Enum("asc", "desc"),
279+
),
280+
WithPagination(),
281+
), userOrOrgHandler("user", getClient)
282+
}
283+
284+
// SearchOrgs creates a tool to search for GitHub organizations.
285+
func SearchOrgs(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
286+
return mcp.NewTool("search_orgs",
287+
mcp.WithDescription(t("TOOL_SEARCH_ORGS_DESCRIPTION", "Search for GitHub organizations exclusively")),
288+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
289+
Title: t("TOOL_SEARCH_ORGS_USER_TITLE", "Search organizations"),
290+
ReadOnlyHint: ToBoolPtr(true),
291+
}),
292+
mcp.WithString("query",
293+
mcp.Required(),
294+
mcp.Description("Search query using GitHub organizations search syntax scoped to type:org"),
295+
),
296+
mcp.WithString("sort",
297+
mcp.Description("Sort field by category"),
298+
mcp.Enum("followers", "repositories", "joined"),
299+
),
300+
mcp.WithString("order",
301+
mcp.Description("Sort order"),
302+
mcp.Enum("asc", "desc"),
303+
),
304+
WithPagination(),
305+
), userOrOrgHandler("org", getClient)
267306
}

0 commit comments

Comments
 (0)