Skip to content

Commit 4fc70e3

Browse files
separate org and user search
1 parent 1495115 commit 4fc70e3

File tree

3 files changed

+304
-88
lines changed

3 files changed

+304
-88
lines changed

pkg/github/search.go

Lines changed: 177 additions & 88 deletions
Original file line numberDiff line numberDiff line change
@@ -159,105 +159,194 @@ type MinimalSearchUsersResult struct {
159159
Items []MinimalUser `json:"items"`
160160
}
161161

162-
// SearchUsers creates a tool to search for GitHub users.
163-
func SearchUsers(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
164-
return mcp.NewTool("search_users",
165-
mcp.WithDescription(t("TOOL_SEARCH_USERS_DESCRIPTION", "Search for GitHub users")),
166-
mcp.WithToolAnnotation(mcp.ToolAnnotation{
167-
Title: t("TOOL_SEARCH_USERS_USER_TITLE", "Search users"),
168-
ReadOnlyHint: toBoolPtr(true),
169-
}),
170-
mcp.WithString("q",
171-
mcp.Required(),
172-
mcp.Description("Search query using GitHub users search syntax"),
173-
),
174-
mcp.WithString("sort",
175-
mcp.Description("Sort field by category"),
176-
mcp.Enum("followers", "repositories", "joined"),
177-
),
178-
mcp.WithString("order",
179-
mcp.Description("Sort order"),
180-
mcp.Enum("asc", "desc"),
181-
),
182-
WithPagination(),
183-
),
184-
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
185-
query, err := requiredParam[string](request, "q")
186-
if err != nil {
187-
return mcp.NewToolResultError(err.Error()), nil
188-
}
189-
sort, err := OptionalParam[string](request, "sort")
190-
if err != nil {
191-
return mcp.NewToolResultError(err.Error()), nil
162+
// shared handler for user/org search
163+
func minimalAccountSearchHandler(accountType string, query string, getClient GetClientFn, ctx context.Context, sort, order string, pagination PaginationParams) (*MinimalSearchUsersResult, error) {
164+
opts := &github.SearchOptions{
165+
Sort: sort,
166+
Order: order,
167+
ListOptions: github.ListOptions{
168+
PerPage: pagination.perPage,
169+
Page: pagination.page,
170+
},
171+
}
172+
173+
client, err := getClient(ctx)
174+
if err != nil {
175+
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
176+
}
177+
178+
searchQuery := "type:" + accountType + " " + query
179+
result, resp, err := client.Search.Users(ctx, searchQuery, opts)
180+
if err != nil {
181+
return nil, fmt.Errorf("failed to search %ss: %w", accountType, err)
182+
}
183+
defer func() { _ = resp.Body.Close() }()
184+
185+
if resp.StatusCode != 200 {
186+
body, err := io.ReadAll(resp.Body)
187+
if err != nil {
188+
return nil, fmt.Errorf("failed to read response body: %w", err)
189+
}
190+
return nil, fmt.Errorf("failed to search %ss: %s", accountType, string(body))
191+
}
192+
193+
minimalUsers := make([]MinimalUser, 0, len(result.Users))
194+
for _, user := range result.Users {
195+
if user.Login != nil {
196+
mu := MinimalUser{Login: *user.Login}
197+
if user.ID != nil {
198+
mu.ID = *user.ID
192199
}
193-
order, err := OptionalParam[string](request, "order")
194-
if err != nil {
195-
return mcp.NewToolResultError(err.Error()), nil
200+
if user.HTMLURL != nil {
201+
mu.ProfileURL = *user.HTMLURL
196202
}
197-
pagination, err := OptionalPaginationParams(request)
198-
if err != nil {
199-
return mcp.NewToolResultError(err.Error()), nil
203+
if user.AvatarURL != nil {
204+
mu.AvatarURL = *user.AvatarURL
200205
}
206+
minimalUsers = append(minimalUsers, mu)
207+
}
208+
}
209+
minimalResp := &MinimalSearchUsersResult{
210+
TotalCount: result.GetTotal(),
211+
IncompleteResults: result.GetIncompleteResults(),
212+
Items: minimalUsers,
213+
}
214+
if result.Total != nil {
215+
minimalResp.TotalCount = *result.Total
216+
}
217+
if result.IncompleteResults != nil {
218+
minimalResp.IncompleteResults = *result.IncompleteResults
219+
}
220+
return minimalResp, nil
221+
}
201222

202-
opts := &github.SearchOptions{
203-
Sort: sort,
204-
Order: order,
205-
ListOptions: github.ListOptions{
206-
PerPage: pagination.perPage,
207-
Page: pagination.page,
208-
},
209-
}
223+
// SearchUsers creates a tool to search for GitHub users.
224+
func userOrOrgHandler(accountType string, getClient GetClientFn) server.ToolHandlerFunc {
225+
return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
226+
query, err := requiredParam[string](request, "q")
227+
if err != nil {
228+
return mcp.NewToolResultError(err.Error()), nil
229+
}
230+
sort, err := OptionalParam[string](request, "sort")
231+
if err != nil {
232+
return mcp.NewToolResultError(err.Error()), nil
233+
}
234+
order, err := OptionalParam[string](request, "order")
235+
if err != nil {
236+
return mcp.NewToolResultError(err.Error()), nil
237+
}
238+
pagination, err := OptionalPaginationParams(request)
239+
if err != nil {
240+
return mcp.NewToolResultError(err.Error()), nil
241+
}
210242

211-
client, err := getClient(ctx)
212-
if err != nil {
213-
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
214-
}
243+
opts := &github.SearchOptions{
244+
Sort: sort,
245+
Order: order,
246+
ListOptions: github.ListOptions{
247+
PerPage: pagination.perPage,
248+
Page: pagination.page,
249+
},
250+
}
251+
252+
client, err := getClient(ctx)
253+
if err != nil {
254+
return nil, fmt.Errorf("failed to get GitHub client: %w", err)
255+
}
215256

216-
result, resp, err := client.Search.Users(ctx, "type:user "+query, opts)
257+
searchQuery := "type:" + accountType + " " + query
258+
result, resp, err := client.Search.Users(ctx, searchQuery, opts)
259+
if err != nil {
260+
return nil, fmt.Errorf("failed to search %ss: %w", accountType, err)
261+
}
262+
defer func() { _ = resp.Body.Close() }()
263+
264+
if resp.StatusCode != 200 {
265+
body, err := io.ReadAll(resp.Body)
217266
if err != nil {
218-
return nil, fmt.Errorf("failed to search users: %w", err)
267+
return nil, fmt.Errorf("failed to read response body: %w", err)
219268
}
220-
defer func() { _ = resp.Body.Close() }()
269+
return mcp.NewToolResultError(fmt.Sprintf("failed to search %ss: %s", accountType, string(body))), nil
270+
}
221271

222-
if resp.StatusCode != 200 {
223-
body, err := io.ReadAll(resp.Body)
224-
if err != nil {
225-
return nil, fmt.Errorf("failed to read response body: %w", err)
272+
minimalUsers := make([]MinimalUser, 0, len(result.Users))
273+
for _, user := range result.Users {
274+
if user.Login != nil {
275+
mu := MinimalUser{Login: *user.Login}
276+
if user.ID != nil {
277+
mu.ID = *user.ID
226278
}
227-
return mcp.NewToolResultError(fmt.Sprintf("failed to search users: %s", string(body))), nil
228-
}
229-
230-
minimalUsers := make([]MinimalUser, 0, len(result.Users))
231-
for _, user := range result.Users {
232-
if user.Login != nil {
233-
mu := MinimalUser{Login: *user.Login}
234-
if user.ID != nil {
235-
mu.ID = *user.ID
236-
}
237-
if user.HTMLURL != nil {
238-
mu.ProfileURL = *user.HTMLURL
239-
}
240-
if user.AvatarURL != nil {
241-
mu.AvatarURL = *user.AvatarURL
242-
}
243-
minimalUsers = append(minimalUsers, mu)
279+
if user.HTMLURL != nil {
280+
mu.ProfileURL = *user.HTMLURL
244281
}
282+
if user.AvatarURL != nil {
283+
mu.AvatarURL = *user.AvatarURL
284+
}
285+
minimalUsers = append(minimalUsers, mu)
245286
}
246-
minimalResp := MinimalSearchUsersResult{
247-
TotalCount: result.GetTotal(),
248-
IncompleteResults: result.GetIncompleteResults(),
249-
Items: minimalUsers,
250-
}
251-
if result.Total != nil {
252-
minimalResp.TotalCount = *result.Total
253-
}
254-
if result.IncompleteResults != nil {
255-
minimalResp.IncompleteResults = *result.IncompleteResults
256-
}
257-
r, err := json.Marshal(minimalResp)
258-
if err != nil {
259-
return nil, fmt.Errorf("failed to marshal response: %w", err)
260-
}
261-
return mcp.NewToolResultText(string(r)), nil
262287
}
288+
minimalResp := &MinimalSearchUsersResult{
289+
TotalCount: result.GetTotal(),
290+
IncompleteResults: result.GetIncompleteResults(),
291+
Items: minimalUsers,
292+
}
293+
if result.Total != nil {
294+
minimalResp.TotalCount = *result.Total
295+
}
296+
if result.IncompleteResults != nil {
297+
minimalResp.IncompleteResults = *result.IncompleteResults
298+
}
299+
r, err := json.Marshal(minimalResp)
300+
if err != nil {
301+
return nil, fmt.Errorf("failed to marshal response: %w", err)
302+
}
303+
return mcp.NewToolResultText(string(r)), nil
304+
}
305+
}
306+
307+
func SearchUsers(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
308+
return mcp.NewTool("search_users",
309+
mcp.WithDescription(t("TOOL_SEARCH_USERS_DESCRIPTION", "Search for GitHub users exlusively")),
310+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
311+
Title: t("TOOL_SEARCH_USERS_USER_TITLE", "Search users"),
312+
ReadOnlyHint: toBoolPtr(true),
313+
}),
314+
mcp.WithString("q",
315+
mcp.Required(),
316+
mcp.Description("Search query using GitHub users search syntax scoped to type:user"),
317+
),
318+
mcp.WithString("sort",
319+
mcp.Description("Sort field by category"),
320+
mcp.Enum("followers", "repositories", "joined"),
321+
),
322+
mcp.WithString("order",
323+
mcp.Description("Sort order"),
324+
mcp.Enum("asc", "desc"),
325+
),
326+
WithPagination(),
327+
), userOrOrgHandler("user", getClient)
328+
}
329+
330+
// SearchOrgs creates a tool to search for GitHub organizations.
331+
func SearchOrgs(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
332+
return mcp.NewTool("search_orgs",
333+
mcp.WithDescription(t("TOOL_SEARCH_ORGS_DESCRIPTION", "Search for GitHub organizations exclusively")),
334+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
335+
Title: t("TOOL_SEARCH_ORGS_USER_TITLE", "Search organizations"),
336+
ReadOnlyHint: toBoolPtr(true),
337+
}),
338+
mcp.WithString("q",
339+
mcp.Required(),
340+
mcp.Description("Search query using GitHub organizations search syntax scoped to type:org"),
341+
),
342+
mcp.WithString("sort",
343+
mcp.Description("Sort field by category"),
344+
mcp.Enum("followers", "repositories", "joined"),
345+
),
346+
mcp.WithString("order",
347+
mcp.Description("Sort order"),
348+
mcp.Enum("asc", "desc"),
349+
),
350+
WithPagination(),
351+
), userOrOrgHandler("org", getClient)
263352
}

0 commit comments

Comments
 (0)