package github import ( "context" "encoding/json" "fmt" "io" "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v69/github" "github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/server" ) // SearchRepositories creates a tool to search for GitHub repositories. func SearchRepositories(t translations.TranslationHelperFunc) Tool { return Tool{ Definition: mcp.NewTool( "search_repositories", mcp.WithDescription(t("TOOL_SEARCH_REPOSITORIES_DESCRIPTION", "Search for GitHub repositories")), mcp.WithString("query", mcp.Required(), mcp.Description("Search query"), ), WithPagination(), ), Handler: func(getClient GetClientFn) server.ToolHandlerFunc { return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { query, err := requiredParam[string](request, "query") if err != nil { return mcp.NewToolResultError(err.Error()), nil } pagination, err := OptionalPaginationParams(request) if err != nil { return mcp.NewToolResultError(err.Error()), nil } opts := &github.SearchOptions{ ListOptions: github.ListOptions{ Page: pagination.page, PerPage: pagination.perPage, }, } client, err := getClient(ctx) if err != nil { return nil, fmt.Errorf("failed to get GitHub client: %w", err) } result, resp, err := client.Search.Repositories(ctx, query, opts) if err != nil { return nil, fmt.Errorf("failed to search repositories: %w", err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != 200 { body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response body: %w", err) } return mcp.NewToolResultError(fmt.Sprintf("failed to search repositories: %s", string(body))), nil } r, err := json.Marshal(result) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) } return mcp.NewToolResultText(string(r)), nil } }, Access: ReadOnly, Category: CategorySearch, } } // SearchCode creates a tool to search for code across GitHub repositories. func SearchCode(t translations.TranslationHelperFunc) Tool { return Tool{ Definition: mcp.NewTool( "search_code", mcp.WithDescription(t("TOOL_SEARCH_CODE_DESCRIPTION", "Search for code across GitHub repositories")), mcp.WithString("q", mcp.Required(), mcp.Description("Search query using GitHub code search syntax"), ), mcp.WithString("sort", mcp.Description("Sort field ('indexed' only)"), ), mcp.WithString("order", mcp.Description("Sort order ('asc' or 'desc')"), mcp.Enum("asc", "desc"), ), WithPagination(), ), Handler: func(getClient GetClientFn) server.ToolHandlerFunc { return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { query, err := requiredParam[string](request, "q") if err != nil { return mcp.NewToolResultError(err.Error()), nil } sort, err := OptionalParam[string](request, "sort") if err != nil { return mcp.NewToolResultError(err.Error()), nil } order, err := OptionalParam[string](request, "order") if err != nil { return mcp.NewToolResultError(err.Error()), nil } pagination, err := OptionalPaginationParams(request) if err != nil { return mcp.NewToolResultError(err.Error()), nil } opts := &github.SearchOptions{ Sort: sort, Order: order, ListOptions: github.ListOptions{ PerPage: pagination.perPage, Page: pagination.page, }, } client, err := getClient(ctx) if err != nil { return nil, fmt.Errorf("failed to get GitHub client: %w", err) } result, resp, err := client.Search.Code(ctx, query, opts) if err != nil { return nil, fmt.Errorf("failed to search code: %w", err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != 200 { body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response body: %w", err) } return mcp.NewToolResultError(fmt.Sprintf("failed to search code: %s", string(body))), nil } r, err := json.Marshal(result) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) } return mcp.NewToolResultText(string(r)), nil } }, Access: ReadOnly, Category: CategorySearch, } } // SearchUsers creates a tool to search for GitHub users. func SearchUsers(t translations.TranslationHelperFunc) Tool { return Tool{ Definition: mcp.NewTool( "search_users", mcp.WithDescription(t("TOOL_SEARCH_USERS_DESCRIPTION", "Search for GitHub users")), mcp.WithString("q", mcp.Required(), mcp.Description("Search query using GitHub users search syntax"), ), mcp.WithString("sort", mcp.Description("Sort field (followers, repositories, joined)"), mcp.Enum("followers", "repositories", "joined"), ), mcp.WithString("order", mcp.Description("Sort order ('asc' or 'desc')"), mcp.Enum("asc", "desc"), ), WithPagination(), ), Handler: func(getClient GetClientFn) server.ToolHandlerFunc { return func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { query, err := requiredParam[string](request, "q") if err != nil { return mcp.NewToolResultError(err.Error()), nil } sort, err := OptionalParam[string](request, "sort") if err != nil { return mcp.NewToolResultError(err.Error()), nil } order, err := OptionalParam[string](request, "order") if err != nil { return mcp.NewToolResultError(err.Error()), nil } pagination, err := OptionalPaginationParams(request) if err != nil { return mcp.NewToolResultError(err.Error()), nil } opts := &github.SearchOptions{ Sort: sort, Order: order, ListOptions: github.ListOptions{ PerPage: pagination.perPage, Page: pagination.page, }, } client, err := getClient(ctx) if err != nil { return nil, fmt.Errorf("failed to get GitHub client: %w", err) } result, resp, err := client.Search.Users(ctx, query, opts) if err != nil { return nil, fmt.Errorf("failed to search users: %w", err) } defer func() { _ = resp.Body.Close() }() if resp.StatusCode != 200 { body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("failed to read response body: %w", err) } return mcp.NewToolResultError(fmt.Sprintf("failed to search users: %s", string(body))), nil } r, err := json.Marshal(result) if err != nil { return nil, fmt.Errorf("failed to marshal response: %w", err) } return mcp.NewToolResultText(string(r)), nil } }, Access: ReadOnly, Category: CategorySearch, } }