Spaces:
Build error
Build error
| package github | |
| import ( | |
| "context" | |
| "encoding/json" | |
| "fmt" | |
| "io" | |
| ghErrors "github.com/github/github-mcp-server/pkg/errors" | |
| "github.com/github/github-mcp-server/pkg/translations" | |
| "github.com/google/go-github/v74/github" | |
| "github.com/mark3labs/mcp-go/mcp" | |
| "github.com/mark3labs/mcp-go/server" | |
| ) | |
| // SearchRepositories creates a tool to search for GitHub repositories. | |
| func SearchRepositories(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { | |
| return mcp.NewTool("search_repositories", | |
| mcp.WithDescription(t("TOOL_SEARCH_REPOSITORIES_DESCRIPTION", "Find GitHub repositories by name, description, readme, topics, or other metadata. Perfect for discovering projects, finding examples, or locating specific repositories across GitHub.")), | |
| mcp.WithToolAnnotation(mcp.ToolAnnotation{ | |
| Title: t("TOOL_SEARCH_REPOSITORIES_USER_TITLE", "Search repositories"), | |
| ReadOnlyHint: ToBoolPtr(true), | |
| }), | |
| mcp.WithString("query", | |
| mcp.Required(), | |
| mcp.Description("Repository search query. Examples: 'machine learning in:name stars:>1000 language:python', 'topic:react', 'user:facebook'. Supports advanced search syntax for precise filtering."), | |
| ), | |
| WithPagination(), | |
| ), | |
| 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 ghErrors.NewGitHubAPIErrorResponse(ctx, | |
| fmt.Sprintf("failed to search repositories with query '%s'", query), | |
| resp, | |
| err, | |
| ), nil | |
| } | |
| 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 | |
| } | |
| } | |
| // SearchCode creates a tool to search for code across GitHub repositories. | |
| func SearchCode(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { | |
| return mcp.NewTool("search_code", | |
| mcp.WithDescription(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.")), | |
| mcp.WithToolAnnotation(mcp.ToolAnnotation{ | |
| Title: t("TOOL_SEARCH_CODE_USER_TITLE", "Search code"), | |
| ReadOnlyHint: ToBoolPtr(true), | |
| }), | |
| mcp.WithString("query", | |
| mcp.Required(), | |
| mcp.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."), | |
| ), | |
| mcp.WithString("sort", | |
| mcp.Description("Sort field ('indexed' only)"), | |
| ), | |
| mcp.WithString("order", | |
| mcp.Description("Sort order for results"), | |
| mcp.Enum("asc", "desc"), | |
| ), | |
| WithPagination(), | |
| ), | |
| 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 | |
| } | |
| 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 ghErrors.NewGitHubAPIErrorResponse(ctx, | |
| fmt.Sprintf("failed to search code with query '%s'", query), | |
| resp, | |
| err, | |
| ), nil | |
| } | |
| 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 | |
| } | |
| } | |
| // MinimalUser is the output type for user and organization search results. | |
| type MinimalUser struct { | |
| Login string `json:"login"` | |
| ID int64 `json:"id,omitempty"` | |
| ProfileURL string `json:"profile_url,omitempty"` | |
| AvatarURL string `json:"avatar_url,omitempty"` | |
| Details *UserDetails `json:"details,omitempty"` // Optional field for additional user details | |
| } | |
| type MinimalSearchUsersResult struct { | |
| TotalCount int `json:"total_count"` | |
| IncompleteResults bool `json:"incomplete_results"` | |
| Items []MinimalUser `json:"items"` | |
| } | |
| func userOrOrgHandler(accountType string, 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 | |
| } | |
| 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) | |
| } | |
| searchQuery := query | |
| if !hasTypeFilter(query) { | |
| searchQuery = "type:" + accountType + " " + query | |
| } | |
| result, resp, err := client.Search.Users(ctx, searchQuery, opts) | |
| if err != nil { | |
| return ghErrors.NewGitHubAPIErrorResponse(ctx, | |
| fmt.Sprintf("failed to search %ss with query '%s'", accountType, query), | |
| resp, | |
| err, | |
| ), nil | |
| } | |
| 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 %ss: %s", accountType, string(body))), nil | |
| } | |
| minimalUsers := make([]MinimalUser, 0, len(result.Users)) | |
| for _, user := range result.Users { | |
| if user.Login != nil { | |
| mu := MinimalUser{ | |
| Login: user.GetLogin(), | |
| ID: user.GetID(), | |
| ProfileURL: user.GetHTMLURL(), | |
| AvatarURL: user.GetAvatarURL(), | |
| } | |
| minimalUsers = append(minimalUsers, mu) | |
| } | |
| } | |
| minimalResp := &MinimalSearchUsersResult{ | |
| TotalCount: result.GetTotal(), | |
| IncompleteResults: result.GetIncompleteResults(), | |
| Items: minimalUsers, | |
| } | |
| if result.Total != nil { | |
| minimalResp.TotalCount = *result.Total | |
| } | |
| if result.IncompleteResults != nil { | |
| minimalResp.IncompleteResults = *result.IncompleteResults | |
| } | |
| r, err := json.Marshal(minimalResp) | |
| if err != nil { | |
| return nil, fmt.Errorf("failed to marshal response: %w", err) | |
| } | |
| return mcp.NewToolResultText(string(r)), nil | |
| } | |
| } | |
| // SearchUsers creates a tool to search for GitHub users. | |
| func SearchUsers(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { | |
| return mcp.NewTool("search_users", | |
| mcp.WithDescription(t("TOOL_SEARCH_USERS_DESCRIPTION", "Find GitHub users by username, real name, or other profile information. Useful for locating developers, contributors, or team members.")), | |
| mcp.WithToolAnnotation(mcp.ToolAnnotation{ | |
| Title: t("TOOL_SEARCH_USERS_USER_TITLE", "Search users"), | |
| ReadOnlyHint: ToBoolPtr(true), | |
| }), | |
| mcp.WithString("query", | |
| mcp.Required(), | |
| mcp.Description("User search query. Examples: 'john smith', 'location:seattle', 'followers:>100'. Search is automatically scoped to type:user."), | |
| ), | |
| mcp.WithString("sort", | |
| mcp.Description("Sort users by number of followers or repositories, or when the person joined GitHub."), | |
| mcp.Enum("followers", "repositories", "joined"), | |
| ), | |
| mcp.WithString("order", | |
| mcp.Description("Sort order"), | |
| mcp.Enum("asc", "desc"), | |
| ), | |
| WithPagination(), | |
| ), userOrOrgHandler("user", getClient) | |
| } | |
| // SearchOrgs creates a tool to search for GitHub organizations. | |
| func SearchOrgs(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) { | |
| return mcp.NewTool("search_orgs", | |
| mcp.WithDescription(t("TOOL_SEARCH_ORGS_DESCRIPTION", "Find GitHub organizations by name, location, or other organization metadata. Ideal for discovering companies, open source foundations, or teams.")), | |
| mcp.WithToolAnnotation(mcp.ToolAnnotation{ | |
| Title: t("TOOL_SEARCH_ORGS_USER_TITLE", "Search organizations"), | |
| ReadOnlyHint: ToBoolPtr(true), | |
| }), | |
| mcp.WithString("query", | |
| mcp.Required(), | |
| mcp.Description("Organization search query. Examples: 'microsoft', 'location:california', 'created:>=2025-01-01'. Search is automatically scoped to type:org."), | |
| ), | |
| mcp.WithString("sort", | |
| mcp.Description("Sort field by category"), | |
| mcp.Enum("followers", "repositories", "joined"), | |
| ), | |
| mcp.WithString("order", | |
| mcp.Description("Sort order"), | |
| mcp.Enum("asc", "desc"), | |
| ), | |
| WithPagination(), | |
| ), userOrOrgHandler("org", getClient) | |
| } | |