Skip to content

Commit 611f5c7

Browse files
authored
Merge branch 'main' into add-issue-types-and-pr-types
2 parents fc57141 + 587272b commit 611f5c7

26 files changed

+1561
-67
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

go.mod

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,22 @@ go 1.23.7
55
require (
66
github.com/google/go-github/v74 v74.0.0
77
github.com/josephburnett/jd v1.9.2
8-
github.com/mark3labs/mcp-go v0.32.0
8+
github.com/mark3labs/mcp-go v0.36.0
99
github.com/migueleliasweb/go-github-mock v1.3.0
1010
github.com/spf13/cobra v1.9.1
1111
github.com/spf13/viper v1.20.1
1212
github.com/stretchr/testify v1.10.0
1313
)
1414

1515
require (
16+
github.com/bahlo/generic-list-go v0.2.0 // indirect
17+
github.com/buger/jsonparser v1.1.1 // indirect
1618
github.com/go-openapi/jsonpointer v0.19.5 // indirect
1719
github.com/go-openapi/swag v0.21.1 // indirect
20+
github.com/invopop/jsonschema v0.13.0 // indirect
1821
github.com/josharian/intern v1.0.0 // indirect
1922
github.com/mailru/easyjson v0.7.7 // indirect
23+
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
2024
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect
2125
golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect
2226
gopkg.in/yaml.v2 v2.4.0 // indirect

go.sum

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
2+
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
3+
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
4+
github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
15
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
26
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
37
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@@ -30,6 +34,8 @@ github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI=
3034
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
3135
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
3236
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
37+
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
38+
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
3339
github.com/josephburnett/jd v1.9.2 h1:ECJRRFXCCqbtidkAHckHGSZm/JIaAxS1gygHLF8MI5Y=
3440
github.com/josephburnett/jd v1.9.2/go.mod h1:bImDr8QXpxMb3SD+w1cDRHp97xP6UwI88xUAuxwDQfM=
3541
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
@@ -47,8 +53,8 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
4753
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
4854
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
4955
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
50-
github.com/mark3labs/mcp-go v0.32.0 h1:fgwmbfL2gbd67obg57OfV2Dnrhs1HtSdlY/i5fn7MU8=
51-
github.com/mark3labs/mcp-go v0.32.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
56+
github.com/mark3labs/mcp-go v0.36.0 h1:rIZaijrRYPeSbJG8/qNDe0hWlGrCJ7FWHNMz2SQpTis=
57+
github.com/mark3labs/mcp-go v0.36.0/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZHRymCn39g=
5258
github.com/migueleliasweb/go-github-mock v1.3.0 h1:2sVP9JEMB2ubQw1IKto3/fzF51oFC6eVWOOFDgQoq88=
5359
github.com/migueleliasweb/go-github-mock v1.3.0/go.mod h1:ipQhV8fTcj/G6m7BKzin08GaJ/3B5/SonRAkgrk0zCY=
5460
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
@@ -85,6 +91,8 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
8591
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
8692
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
8793
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
94+
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
95+
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
8896
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
8997
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
9098
github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M=

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)