Skip to content

Commit 5b1dac6

Browse files
authored
Merge branch 'main' into bump-sdk-0.36.0
2 parents d86a092 + a70cd1b commit 5b1dac6

16 files changed

+1293
-39
lines changed

.github/dependabot.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@ updates:
1313
directory: "/"
1414
schedule:
1515
interval: "weekly"
16+
- package-ecosystem: "github-actions"
17+
directory: "/"
18+
schedule:
19+
interval: "weekly"

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ To keep your GitHub PAT secure and reusable across different MCP hosts:
144144
- **Minimum scopes**: Only grant necessary permissions
145145
- `repo` - Repository operations
146146
- `read:packages` - Docker image access
147+
- `read:org` - Organization team access
147148
- **Separate tokens**: Use different PATs for different projects/environments
148149
- **Regular rotation**: Update tokens periodically
149150
- **Never commit**: Keep tokens out of version control
@@ -421,6 +422,13 @@ The following sets of tools are available (all are on by default):
421422
- **get_me** - Get my user profile
422423
- No parameters required
423424

425+
- **get_team_members** - Get team members
426+
- `org`: Organization login (owner) that contains the team. (string, required)
427+
- `team_slug`: Team slug (string, required)
428+
429+
- **get_teams** - Get teams
430+
- `user`: Username to get teams for. If not provided, uses the authenticated user. (string, optional)
431+
424432
</details>
425433

426434
<details>
@@ -525,6 +533,7 @@ The following sets of tools are available (all are on by default):
525533
- `owner`: Repository owner (string, required)
526534
- `repo`: Repository name (string, required)
527535
- `title`: Issue title (string, required)
536+
- `type`: Type of this issue (string, optional)
528537

529538
- **get_issue** - Get issue details
530539
- `issue_number`: The number of the issue (number, required)
@@ -592,6 +601,7 @@ The following sets of tools are available (all are on by default):
592601
- `repo`: Repository name (string, required)
593602
- `state`: New state (string, optional)
594603
- `title`: New title (string, optional)
604+
- `type`: New issue type (string, optional)
595605

596606
</details>
597607

pkg/github/__toolsnaps__/create_issue.snap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,10 @@
3939
"title": {
4040
"description": "Issue title",
4141
"type": "string"
42+
},
43+
"type": {
44+
"description": "Type of this issue",
45+
"type": "string"
4246
}
4347
},
4448
"required": [
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"annotations": {
3+
"title": "Get team members",
4+
"readOnlyHint": true
5+
},
6+
"description": "Get member usernames of a specific team in an organization. Limited to organizations accessible with current credentials",
7+
"inputSchema": {
8+
"properties": {
9+
"org": {
10+
"description": "Organization login (owner) that contains the team.",
11+
"type": "string"
12+
},
13+
"team_slug": {
14+
"description": "Team slug",
15+
"type": "string"
16+
}
17+
},
18+
"required": [
19+
"org",
20+
"team_slug"
21+
],
22+
"type": "object"
23+
},
24+
"name": "get_team_members"
25+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
{
2+
"annotations": {
3+
"title": "Get teams",
4+
"readOnlyHint": true
5+
},
6+
"description": "Get details of the teams the user is a member of. Limited to organizations accessible with current credentials",
7+
"inputSchema": {
8+
"properties": {
9+
"user": {
10+
"description": "Username to get teams for. If not provided, uses the authenticated user.",
11+
"type": "string"
12+
}
13+
},
14+
"type": "object"
15+
},
16+
"name": "get_teams"
17+
}

pkg/github/__toolsnaps__/update_issue.snap

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@
5151
"title": {
5252
"description": "New title",
5353
"type": "string"
54+
},
55+
"type": {
56+
"description": "New issue type",
57+
"type": "string"
5458
}
5559
},
5660
"required": [

pkg/github/context_tools.go

Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/github/github-mcp-server/pkg/translations"
99
"github.com/mark3labs/mcp-go/mcp"
1010
"github.com/mark3labs/mcp-go/server"
11+
"github.com/shurcooL/githubv4"
1112
)
1213

1314
// UserDetails contains additional fields about a GitHub user not already
@@ -90,3 +91,161 @@ func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (mcp.Too
9091

9192
return tool, handler
9293
}
94+
95+
type TeamInfo struct {
96+
Name string `json:"name"`
97+
Slug string `json:"slug"`
98+
Description string `json:"description"`
99+
}
100+
101+
type OrganizationTeams struct {
102+
Org string `json:"org"`
103+
Teams []TeamInfo `json:"teams"`
104+
}
105+
106+
func GetTeams(getClient GetClientFn, getGQLClient GetGQLClientFn, t translations.TranslationHelperFunc) (mcp.Tool, server.ToolHandlerFunc) {
107+
return mcp.NewTool("get_teams",
108+
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")),
109+
mcp.WithString("user",
110+
mcp.Description(t("TOOL_GET_TEAMS_USER_DESCRIPTION", "Username to get teams for. If not provided, uses the authenticated user.")),
111+
),
112+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
113+
Title: t("TOOL_GET_TEAMS_TITLE", "Get teams"),
114+
ReadOnlyHint: ToBoolPtr(true),
115+
}),
116+
),
117+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
118+
user, err := OptionalParam[string](request, "user")
119+
if err != nil {
120+
return mcp.NewToolResultError(err.Error()), nil
121+
}
122+
123+
var username string
124+
if user != "" {
125+
username = user
126+
} else {
127+
client, err := getClient(ctx)
128+
if err != nil {
129+
return mcp.NewToolResultErrorFromErr("failed to get GitHub client", err), nil
130+
}
131+
132+
userResp, res, err := client.Users.Get(ctx, "")
133+
if err != nil {
134+
return ghErrors.NewGitHubAPIErrorResponse(ctx,
135+
"failed to get user",
136+
res,
137+
err,
138+
), nil
139+
}
140+
username = userResp.GetLogin()
141+
}
142+
143+
gqlClient, err := getGQLClient(ctx)
144+
if err != nil {
145+
return mcp.NewToolResultErrorFromErr("failed to get GitHub GQL client", err), nil
146+
}
147+
148+
var q struct {
149+
User struct {
150+
Organizations struct {
151+
Nodes []struct {
152+
Login githubv4.String
153+
Teams struct {
154+
Nodes []struct {
155+
Name githubv4.String
156+
Slug githubv4.String
157+
Description githubv4.String
158+
}
159+
} `graphql:"teams(first: 100, userLogins: [$login])"`
160+
}
161+
} `graphql:"organizations(first: 100)"`
162+
} `graphql:"user(login: $login)"`
163+
}
164+
vars := map[string]interface{}{
165+
"login": githubv4.String(username),
166+
}
167+
if err := gqlClient.Query(ctx, &q, vars); err != nil {
168+
return ghErrors.NewGitHubGraphQLErrorResponse(ctx, "Failed to find teams", err), nil
169+
}
170+
171+
var organizations []OrganizationTeams
172+
for _, org := range q.User.Organizations.Nodes {
173+
orgTeams := OrganizationTeams{
174+
Org: string(org.Login),
175+
Teams: make([]TeamInfo, 0, len(org.Teams.Nodes)),
176+
}
177+
178+
for _, team := range org.Teams.Nodes {
179+
orgTeams.Teams = append(orgTeams.Teams, TeamInfo{
180+
Name: string(team.Name),
181+
Slug: string(team.Slug),
182+
Description: string(team.Description),
183+
})
184+
}
185+
186+
organizations = append(organizations, orgTeams)
187+
}
188+
189+
return MarshalledTextResult(organizations), nil
190+
}
191+
}
192+
193+
func GetTeamMembers(getGQLClient GetGQLClientFn, t translations.TranslationHelperFunc) (mcp.Tool, server.ToolHandlerFunc) {
194+
return mcp.NewTool("get_team_members",
195+
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")),
196+
mcp.WithString("org",
197+
mcp.Description(t("TOOL_GET_TEAM_MEMBERS_ORG_DESCRIPTION", "Organization login (owner) that contains the team.")),
198+
mcp.Required(),
199+
),
200+
mcp.WithString("team_slug",
201+
mcp.Description(t("TOOL_GET_TEAM_MEMBERS_TEAM_SLUG_DESCRIPTION", "Team slug")),
202+
mcp.Required(),
203+
),
204+
mcp.WithToolAnnotation(mcp.ToolAnnotation{
205+
Title: t("TOOL_GET_TEAM_MEMBERS_TITLE", "Get team members"),
206+
ReadOnlyHint: ToBoolPtr(true),
207+
}),
208+
),
209+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
210+
org, err := RequiredParam[string](request, "org")
211+
if err != nil {
212+
return mcp.NewToolResultError(err.Error()), nil
213+
}
214+
215+
teamSlug, err := RequiredParam[string](request, "team_slug")
216+
if err != nil {
217+
return mcp.NewToolResultError(err.Error()), nil
218+
}
219+
220+
gqlClient, err := getGQLClient(ctx)
221+
if err != nil {
222+
return mcp.NewToolResultErrorFromErr("failed to get GitHub GQL client", err), nil
223+
}
224+
225+
var q struct {
226+
Organization struct {
227+
Team struct {
228+
Members struct {
229+
Nodes []struct {
230+
Login githubv4.String
231+
}
232+
} `graphql:"members(first: 100)"`
233+
} `graphql:"team(slug: $teamSlug)"`
234+
} `graphql:"organization(login: $org)"`
235+
}
236+
vars := map[string]interface{}{
237+
"org": githubv4.String(org),
238+
"teamSlug": githubv4.String(teamSlug),
239+
}
240+
if err := gqlClient.Query(ctx, &q, vars); err != nil {
241+
return ghErrors.NewGitHubGraphQLErrorResponse(ctx, "Failed to get team members", err), nil
242+
}
243+
244+
var members []string
245+
for _, member := range q.Organization.Team.Members.Nodes {
246+
members = append(members, string(member.Login))
247+
}
248+
249+
return MarshalledTextResult(members), nil
250+
}
251+
}

0 commit comments

Comments
 (0)