Expand Up @@ -6,6 +6,7 @@ import ( "time" ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/toolsets" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/jsonschema-go/jsonschema" Expand Down Expand Up @@ -36,8 +37,9 @@ type UserDetails struct { } // GetMe creates a tool to get details of the authenticated user. func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) { return mcp.Tool{ func GetMe(t translations.TranslationHelperFunc) toolsets.ServerTool { return NewTool( mcp.Tool{ Name: "get_me", Description: 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."), Annotations: &mcp.ToolAnnotations{ Expand All @@ -48,50 +50,53 @@ func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Too // OpenAI strict mode requires the properties field to be present. InputSchema: json.RawMessage(`{"type":"object","properties":{}}`), }, mcp.ToolHandlerFor[map[string]any, any](func(ctx context.Context, _ *mcp.CallToolRequest, _ map[string]any) (*mcp.CallToolResult, any, error) { client, err := getClient(ctx) if err != nil { return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, nil } func(deps ToolDependencies) mcp.ToolHandlerFor[map[string]any, any] { return func(ctx context.Context, _ *mcp.CallToolRequest, _ map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, nil } user, res, err := client.Users.Get(ctx, "") if err != nil { return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to get user", res, err, ), nil,err } user, res, err := client.Users.Get(ctx, "") if err != nil { return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to get user", res, err, ), nil,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(), }, } // 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, nil }) return MarshalledTextResult(minimalUser), nil, nil } }, ) } type TeamInfo struct { Expand All @@ -105,8 +110,9 @@ type OrganizationTeams struct { Teams []TeamInfo `json:"teams"` } func GetTeams(getClient GetClientFn, getGQLClient GetGQLClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) { return mcp.Tool{ func GetTeams(t translations.TranslationHelperFunc) toolsets.ServerTool { return NewTool( mcp.Tool{ Name: "get_teams", Description: t("TOOL_GET_TEAMS_DESCRIPTION", "Get details of the teams the user is a member of. Limited to organizations accessible with current credentials"), Annotations: &mcp.ToolAnnotations{ Expand All @@ -123,84 +129,88 @@ func GetTeams(getClient GetClientFn, getGQLClient GetGQLClientFn, t translations }, }, }, func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { user, err := OptionalParam[string](args, "user") if err != nil { return utils.NewToolResultError(err.Error()), nil, nil } var username string if user != "" { username = user } else { client, err := getClient(ctx) func(deps ToolDependencies) mcp.ToolHandlerFor[map[string]any, any] { return func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { user, err := OptionalParam[string](args, "user") if err != nil { return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, nil return utils.NewToolResultError( err.Error() ), nil, nil } userResp, res, err := client.Users.Get(ctx, "") var username string if user != "" { username = user } else { client, err := deps.GetClient(ctx) if err != nil { return utils.NewToolResultErrorFromErr("failed to get GitHub client", err), nil, nil } userResp, res, err := client.Users.Get(ctx, "") if err != nil { return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to get user", res, err, ), nil, nil } username = userResp.GetLogin() } gqlClient, err := deps.GetGQLClient(ctx) if err != nil { return ghErrors.NewGitHubAPIErrorResponse(ctx, "failed to get user", res, err, ), nil, nil return utils.NewToolResultErrorFromErr("failed to get GitHub GQL client", err), nil, nil } username = userResp.GetLogin() } gqlClient, err := getGQLClient(ctx) if err != nil { return utils.NewToolResultErrorFromErr("failed to get GitHub GQL client", err), nil, 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, 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, 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)), } 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), }) } 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) } organizations = append (organizations, orgTeams) return MarshalledTextResult (organizations), nil, nil } return MarshalledTextResult(organizations), nil, nil } }, ) } func GetTeamMembers(getGQLClient GetGQLClientFn, t translations.TranslationHelperFunc) (mcp.Tool, mcp.ToolHandlerFor[map[string]any, any]) { return mcp.Tool{ func GetTeamMembers(t translations.TranslationHelperFunc) toolsets.ServerTool { return NewTool( mcp.Tool{ Name: "get_team_members", Description: t("TOOL_GET_TEAM_MEMBERS_DESCRIPTION", "Get member usernames of a specific team in an organization. Limited to organizations accessible with current credentials"), Annotations: &mcp.ToolAnnotations{ Expand All @@ -222,46 +232,49 @@ func GetTeamMembers(getGQLClient GetGQLClientFn, t translations.TranslationHelpe Required: []string{"org", "team_slug"}, }, }, func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { org, err := RequiredParam[string](args, "org") if err != nil { return utils.NewToolResultError(err.Error()), nil, nil } func(deps ToolDependencies) mcp.ToolHandlerFor[map[string]any, any] { return func(ctx context.Context, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { org, err := RequiredParam[string](args, "org") if err != nil { return utils.NewToolResultError(err.Error()), nil, nil } teamSlug, err := RequiredParam[string](args, "team_slug") if err != nil { return utils.NewToolResultError(err.Error()), nil, nil } teamSlug, err := RequiredParam[string](args, "team_slug") if err != nil { return utils.NewToolResultError(err.Error()), nil, nil }gqlClient, err :=getGQLClient (ctx) if err != nil { return utils.NewToolResultErrorFromErr("failed to get GitHub GQL client", err), nil, nil } gqlClient, err :=deps.GetGQLClient (ctx) if err != nil { return utils.NewToolResultErrorFromErr("failed to get GitHub GQL client", err), nil, 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, 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, nil }var members []string for _, member := range q.Organization.Team.Members.Nodes { members = append(members, string(member.Login)) } var members []string for _, member := range q.Organization.Team.Members.Nodes { members = append(members, string(member.Login)) }return MarshalledTextResult(members), nil, nil } return MarshalledTextResult(members), nil, nil } }, ) }