Spaces:
Build error
Build error
| package github | |
| import ( | |
| "context" | |
| "time" | |
| ghErrors "github.com/github/github-mcp-server/pkg/errors" | |
| "github.com/github/github-mcp-server/pkg/translations" | |
| "github.com/mark3labs/mcp-go/mcp" | |
| "github.com/mark3labs/mcp-go/server" | |
| "github.com/shurcooL/githubv4" | |
| ) | |
| // UserDetails contains additional fields about a GitHub user not already | |
| // present in MinimalUser. Used by get_me context tool but omitted from search_users. | |
| type UserDetails struct { | |
| Name string `json:"name,omitempty"` | |
| Company string `json:"company,omitempty"` | |
| Blog string `json:"blog,omitempty"` | |
| Location string `json:"location,omitempty"` | |
| Email string `json:"email,omitempty"` | |
| Hireable bool `json:"hireable,omitempty"` | |
| Bio string `json:"bio,omitempty"` | |
| TwitterUsername string `json:"twitter_username,omitempty"` | |
| PublicRepos int `json:"public_repos"` | |
| PublicGists int `json:"public_gists"` | |
| Followers int `json:"followers"` | |
| Following int `json:"following"` | |
| CreatedAt time.Time `json:"created_at"` | |
| UpdatedAt time.Time `json:"updated_at"` | |
| PrivateGists int `json:"private_gists,omitempty"` | |
| TotalPrivateRepos int64 `json:"total_private_repos,omitempty"` | |
| OwnedPrivateRepos int64 `json:"owned_private_repos,omitempty"` | |
| } | |
| // GetMe creates a tool to get details of the authenticated user. | |
| func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, server.ToolHandlerFunc) { | |
| tool := mcp.NewTool("get_me", | |
| mcp.WithDescription(t("TOOL_GET_ME_DESCRIPTION", "Get details of the authenticated GitHub user. Use this when a request is about the user's own profile for GitHub. Or when information is missing to build other tool calls.")), | |
| mcp.WithToolAnnotation(mcp.ToolAnnotation{ | |
| Title: t("TOOL_GET_ME_USER_TITLE", "Get my user profile"), | |
| ReadOnlyHint: ToBoolPtr(true), | |
| }), | |
| ) | |
| type args struct{} | |
| handler := mcp.NewTypedToolHandler(func(ctx context.Context, _ mcp.CallToolRequest, _ args) (*mcp.CallToolResult, error) { | |
| client, err := getClient(ctx) | |
| if err != nil { | |
| return mcp.NewToolResultErrorFromErr("failed to get GitHub client", err), nil | |
| } | |
| user, res, err := client.Users.Get(ctx, "") | |
| if err != nil { | |
| return ghErrors.NewGitHubAPIErrorResponse(ctx, | |
| "failed to get user", | |
| res, | |
| err, | |
| ), nil | |
| } | |
| // Create minimal user representation instead of returning full user object | |
| minimalUser := MinimalUser{ | |
| Login: user.GetLogin(), | |
| ID: user.GetID(), | |
| ProfileURL: user.GetHTMLURL(), | |
| AvatarURL: user.GetAvatarURL(), | |
| Details: &UserDetails{ | |
| Name: user.GetName(), | |
| Company: user.GetCompany(), | |
| Blog: user.GetBlog(), | |
| Location: user.GetLocation(), | |
| Email: user.GetEmail(), | |
| Hireable: user.GetHireable(), | |
| Bio: user.GetBio(), | |
| TwitterUsername: user.GetTwitterUsername(), | |
| PublicRepos: user.GetPublicRepos(), | |
| PublicGists: user.GetPublicGists(), | |
| Followers: user.GetFollowers(), | |
| Following: user.GetFollowing(), | |
| CreatedAt: user.GetCreatedAt().Time, | |
| UpdatedAt: user.GetUpdatedAt().Time, | |
| PrivateGists: user.GetPrivateGists(), | |
| TotalPrivateRepos: user.GetTotalPrivateRepos(), | |
| OwnedPrivateRepos: user.GetOwnedPrivateRepos(), | |
| }, | |
| } | |
| return MarshalledTextResult(minimalUser), nil | |
| }) | |
| return tool, handler | |
| } | |
| type TeamInfo struct { | |
| Name string `json:"name"` | |
| Slug string `json:"slug"` | |
| Description string `json:"description"` | |
| } | |
| type OrganizationTeams struct { | |
| Org string `json:"org"` | |
| Teams []TeamInfo `json:"teams"` | |
| } | |
| func GetTeams(getClient GetClientFn, getGQLClient GetGQLClientFn, t translations.TranslationHelperFunc) (mcp.Tool, server.ToolHandlerFunc) { | |
| return mcp.NewTool("get_teams", | |
| mcp.WithDescription(t("TOOL_GET_TEAMS_DESCRIPTION", "Get details of the teams the user is a member of. Limited to organizations accessible with current credentials")), | |
| mcp.WithString("user", | |
| mcp.Description(t("TOOL_GET_TEAMS_USER_DESCRIPTION", "Username to get teams for. If not provided, uses the authenticated user.")), | |
| ), | |
| mcp.WithToolAnnotation(mcp.ToolAnnotation{ | |
| Title: t("TOOL_GET_TEAMS_TITLE", "Get teams"), | |
| ReadOnlyHint: ToBoolPtr(true), | |
| }), | |
| ), | |
| func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { | |
| user, err := OptionalParam[string](request, "user") | |
| if err != nil { | |
| return mcp.NewToolResultError(err.Error()), nil | |
| } | |
| var username string | |
| if user != "" { | |
| username = user | |
| } else { | |
| client, err := getClient(ctx) | |
| if err != nil { | |
| return mcp.NewToolResultErrorFromErr("failed to get GitHub client", err), nil | |
| } | |
| userResp, res, err := client.Users.Get(ctx, "") | |
| if err != nil { | |
| return ghErrors.NewGitHubAPIErrorResponse(ctx, | |
| "failed to get user", | |
| res, | |
| err, | |
| ), nil | |
| } | |
| username = userResp.GetLogin() | |
| } | |
| gqlClient, err := getGQLClient(ctx) | |
| if err != nil { | |
| return mcp.NewToolResultErrorFromErr("failed to get GitHub GQL client", err), nil | |
| } | |
| var q struct { | |
| User struct { | |
| Organizations struct { | |
| Nodes []struct { | |
| Login githubv4.String | |
| Teams struct { | |
| Nodes []struct { | |
| Name githubv4.String | |
| Slug githubv4.String | |
| Description githubv4.String | |
| } | |
| } `graphql:"teams(first: 100, userLogins: [$login])"` | |
| } | |
| } `graphql:"organizations(first: 100)"` | |
| } `graphql:"user(login: $login)"` | |
| } | |
| vars := map[string]interface{}{ | |
| "login": githubv4.String(username), | |
| } | |
| if err := gqlClient.Query(ctx, &q, vars); err != nil { | |
| return ghErrors.NewGitHubGraphQLErrorResponse(ctx, "Failed to find teams", err), nil | |
| } | |
| var organizations []OrganizationTeams | |
| for _, org := range q.User.Organizations.Nodes { | |
| orgTeams := OrganizationTeams{ | |
| Org: string(org.Login), | |
| Teams: make([]TeamInfo, 0, len(org.Teams.Nodes)), | |
| } | |
| for _, team := range org.Teams.Nodes { | |
| orgTeams.Teams = append(orgTeams.Teams, TeamInfo{ | |
| Name: string(team.Name), | |
| Slug: string(team.Slug), | |
| Description: string(team.Description), | |
| }) | |
| } | |
| organizations = append(organizations, orgTeams) | |
| } | |
| return MarshalledTextResult(organizations), nil | |
| } | |
| } | |
| func GetTeamMembers(getGQLClient GetGQLClientFn, t translations.TranslationHelperFunc) (mcp.Tool, server.ToolHandlerFunc) { | |
| return mcp.NewTool("get_team_members", | |
| mcp.WithDescription(t("TOOL_GET_TEAM_MEMBERS_DESCRIPTION", "Get member usernames of a specific team in an organization. Limited to organizations accessible with current credentials")), | |
| mcp.WithString("org", | |
| mcp.Description(t("TOOL_GET_TEAM_MEMBERS_ORG_DESCRIPTION", "Organization login (owner) that contains the team.")), | |
| mcp.Required(), | |
| ), | |
| mcp.WithString("team_slug", | |
| mcp.Description(t("TOOL_GET_TEAM_MEMBERS_TEAM_SLUG_DESCRIPTION", "Team slug")), | |
| mcp.Required(), | |
| ), | |
| mcp.WithToolAnnotation(mcp.ToolAnnotation{ | |
| Title: t("TOOL_GET_TEAM_MEMBERS_TITLE", "Get team members"), | |
| ReadOnlyHint: ToBoolPtr(true), | |
| }), | |
| ), | |
| func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) { | |
| org, err := RequiredParam[string](request, "org") | |
| if err != nil { | |
| return mcp.NewToolResultError(err.Error()), nil | |
| } | |
| teamSlug, err := RequiredParam[string](request, "team_slug") | |
| if err != nil { | |
| return mcp.NewToolResultError(err.Error()), nil | |
| } | |
| gqlClient, err := getGQLClient(ctx) | |
| if err != nil { | |
| return mcp.NewToolResultErrorFromErr("failed to get GitHub GQL client", err), nil | |
| } | |
| var q struct { | |
| Organization struct { | |
| Team struct { | |
| Members struct { | |
| Nodes []struct { | |
| Login githubv4.String | |
| } | |
| } `graphql:"members(first: 100)"` | |
| } `graphql:"team(slug: $teamSlug)"` | |
| } `graphql:"organization(login: $org)"` | |
| } | |
| vars := map[string]interface{}{ | |
| "org": githubv4.String(org), | |
| "teamSlug": githubv4.String(teamSlug), | |
| } | |
| if err := gqlClient.Query(ctx, &q, vars); err != nil { | |
| return ghErrors.NewGitHubGraphQLErrorResponse(ctx, "Failed to get team members", err), nil | |
| } | |
| var members []string | |
| for _, member := range q.Organization.Team.Members.Nodes { | |
| members = append(members, string(member.Login)) | |
| } | |
| return MarshalledTextResult(members), nil | |
| } | |
| } | |