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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy