Skip to content

Commit fd88ddf

Browse files
authored
Merge branch 'main' into increase_runner_size
2 parents 5e9f1ae + 87d4407 commit fd88ddf

File tree

9 files changed

+168
-188
lines changed

9 files changed

+168
-188
lines changed

.github/ISSUE_TEMPLATE/bug_report.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
name: "\U0001F41B Bug report"
3-
about: Report a bug or unexpected behavior while using GitHub MCP
3+
about: Report a bug or unexpected behavior while using GitHub MCP Server
44
title: ''
55
labels: bug
66
assignees: ''
@@ -13,7 +13,7 @@ A clear and concise description of what the bug is.
1313

1414
### Affected version
1515

16-
Please run `go run cmd/github-mcp-server/main.go --version` and paste the output below.
16+
Please run ` docker run -i --rm ghcr.io/github/github-mcp-server ./github-mcp-server --version` and paste the output below
1717

1818
### Steps to reproduce the behavior
1919

@@ -27,4 +27,4 @@ A clear and concise description of what you expected to happen and what actually
2727

2828
### Logs
2929

30-
Paste any available logs. Redact if needed.
30+
Paste any available logs. Redact if needed.

.github/pull_request_template.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<!--
2-
Thank you for contributing to GitHub MCP!
2+
Thank you for contributing to GitHub MCP Server!
33
Please reference an existing issue: `Closes #NUMBER`
44
55
Screenshots or videos of changed behavior is incredibly helpful and always appreciated.
@@ -8,4 +8,4 @@
88
- Alternatives: Describe alternative approaches you considered and why you discarded them.
99
-->
1010

11-
Closes:
11+
Closes:

README.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description
130130
- `repo`: Repository name (string, required)
131131
- `title`: Issue title (string, required)
132132
- `body`: Issue body content (string, optional)
133-
- `assignees`: Comma-separated list of usernames to assign to this issue (string, optional)
134-
- `labels`: Comma-separated list of labels to apply to this issue (string, optional)
133+
- `assignees`: Usernames to assign to this issue (string[], optional)
134+
- `labels`: Labels to apply to this issue (string[], optional)
135135

136136
- **add_issue_comment** - Add a comment to an issue
137137

@@ -145,7 +145,7 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description
145145
- `owner`: Repository owner (string, required)
146146
- `repo`: Repository name (string, required)
147147
- `state`: Filter by state ('open', 'closed', 'all') (string, optional)
148-
- `labels`: Comma-separated list of labels to filter by (string, optional)
148+
- `labels`: Labels to filter by (string[], optional)
149149
- `sort`: Sort by ('created', 'updated', 'comments') (string, optional)
150150
- `direction`: Sort direction ('asc', 'desc') (string, optional)
151151
- `since`: Filter by date (ISO 8601 timestamp) (string, optional)
@@ -160,8 +160,8 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description
160160
- `title`: New title (string, optional)
161161
- `body`: New description (string, optional)
162162
- `state`: New state ('open' or 'closed') (string, optional)
163-
- `labels`: Comma-separated list of new labels (string, optional)
164-
- `assignees`: Comma-separated list of new assignees (string, optional)
163+
- `labels`: New labels (string[], optional)
164+
- `assignees`: New assignees (string[], optional)
165165
- `milestone`: New milestone number (number, optional)
166166

167167
- **search_issues** - Search for issues and pull requests

pkg/github/helper_test.go

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,52 @@ import (
1010
"github.com/stretchr/testify/require"
1111
)
1212

13+
// expectQueryParams is a helper function to create a partial mock that expects a
14+
// request with the given query parameters, with the ability to chain a response handler.
15+
func expectQueryParams(t *testing.T, expectedQueryParams map[string]string) *partialMock {
16+
return &partialMock{
17+
t: t,
18+
expectedQueryParams: expectedQueryParams,
19+
}
20+
}
21+
22+
// expectRequestBody is a helper function to create a partial mock that expects a
23+
// request with the given body, with the ability to chain a response handler.
24+
func expectRequestBody(t *testing.T, expectedRequestBody any) *partialMock {
25+
return &partialMock{
26+
t: t,
27+
expectedRequestBody: expectedRequestBody,
28+
}
29+
}
30+
31+
type partialMock struct {
32+
t *testing.T
33+
expectedQueryParams map[string]string
34+
expectedRequestBody any
35+
}
36+
37+
func (p *partialMock) andThen(responseHandler http.HandlerFunc) http.HandlerFunc {
38+
p.t.Helper()
39+
return func(w http.ResponseWriter, r *http.Request) {
40+
if p.expectedRequestBody != nil {
41+
var unmarshaledRequestBody any
42+
err := json.NewDecoder(r.Body).Decode(&unmarshaledRequestBody)
43+
require.NoError(p.t, err)
44+
45+
require.Equal(p.t, p.expectedRequestBody, unmarshaledRequestBody)
46+
}
47+
48+
if p.expectedQueryParams != nil {
49+
require.Equal(p.t, len(p.expectedQueryParams), len(r.URL.Query()))
50+
for k, v := range p.expectedQueryParams {
51+
require.Equal(p.t, v, r.URL.Query().Get(k))
52+
}
53+
}
54+
55+
responseHandler(w, r)
56+
}
57+
}
58+
1359
// mockResponse is a helper function to create a mock HTTP response handler
1460
// that returns a specified status code and marshaled body.
1561
func mockResponse(t *testing.T, code int, body interface{}) http.HandlerFunc {

pkg/github/issues.go

Lines changed: 68 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -144,15 +144,32 @@ func searchIssues(client *github.Client, t translations.TranslationHelperFunc) (
144144
),
145145
mcp.WithString("sort",
146146
mcp.Description("Sort field (comments, reactions, created, etc.)"),
147+
mcp.Enum(
148+
"comments",
149+
"reactions",
150+
"reactions-+1",
151+
"reactions--1",
152+
"reactions-smile",
153+
"reactions-thinking_face",
154+
"reactions-heart",
155+
"reactions-tada",
156+
"interactions",
157+
"created",
158+
"updated",
159+
),
147160
),
148161
mcp.WithString("order",
149162
mcp.Description("Sort order ('asc' or 'desc')"),
163+
mcp.Enum("asc", "desc"),
150164
),
151165
mcp.WithNumber("per_page",
152166
mcp.Description("Results per page (max 100)"),
167+
mcp.Min(1),
168+
mcp.Max(100),
153169
),
154170
mcp.WithNumber("page",
155171
mcp.Description("Page number"),
172+
mcp.Min(1),
156173
),
157174
),
158175
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
@@ -228,11 +245,21 @@ func createIssue(client *github.Client, t translations.TranslationHelperFunc) (t
228245
mcp.WithString("body",
229246
mcp.Description("Issue body content"),
230247
),
231-
mcp.WithString("assignees",
232-
mcp.Description("Comma-separate list of usernames to assign to this issue"),
233-
),
234-
mcp.WithString("labels",
235-
mcp.Description("Comma-separate list of labels to apply to this issue"),
248+
mcp.WithArray("assignees",
249+
mcp.Description("Usernames to assign to this issue"),
250+
mcp.Items(
251+
map[string]interface{}{
252+
"type": "string",
253+
},
254+
),
255+
),
256+
mcp.WithArray("labels",
257+
mcp.Description("Labels to apply to this issue"),
258+
mcp.Items(
259+
map[string]interface{}{
260+
"type": "string",
261+
},
262+
),
236263
),
237264
),
238265
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
@@ -256,12 +283,13 @@ func createIssue(client *github.Client, t translations.TranslationHelperFunc) (t
256283
}
257284

258285
// Get assignees
259-
assignees, err := optionalCommaSeparatedListParam(request, "assignees")
286+
assignees, err := optionalParam[[]string](request, "assignees")
260287
if err != nil {
261288
return mcp.NewToolResultError(err.Error()), nil
262289
}
290+
263291
// Get labels
264-
labels, err := optionalCommaSeparatedListParam(request, "labels")
292+
labels, err := optionalParam[[]string](request, "labels")
265293
if err != nil {
266294
return mcp.NewToolResultError(err.Error()), nil
267295
}
@@ -311,15 +339,23 @@ func listIssues(client *github.Client, t translations.TranslationHelperFunc) (to
311339
),
312340
mcp.WithString("state",
313341
mcp.Description("Filter by state ('open', 'closed', 'all')"),
342+
mcp.Enum("open", "closed", "all"),
314343
),
315-
mcp.WithString("labels",
316-
mcp.Description("Comma-separated list of labels to filter by"),
344+
mcp.WithArray("labels",
345+
mcp.Description("Filter by labels"),
346+
mcp.Items(
347+
map[string]interface{}{
348+
"type": "string",
349+
},
350+
),
317351
),
318352
mcp.WithString("sort",
319353
mcp.Description("Sort by ('created', 'updated', 'comments')"),
354+
mcp.Enum("created", "updated", "comments"),
320355
),
321356
mcp.WithString("direction",
322357
mcp.Description("Sort direction ('asc', 'desc')"),
358+
mcp.Enum("asc", "desc"),
323359
),
324360
mcp.WithString("since",
325361
mcp.Description("Filter by date (ISO 8601 timestamp)"),
@@ -349,7 +385,8 @@ func listIssues(client *github.Client, t translations.TranslationHelperFunc) (to
349385
return mcp.NewToolResultError(err.Error()), nil
350386
}
351387

352-
opts.Labels, err = optionalCommaSeparatedListParam(request, "labels")
388+
// Get labels
389+
opts.Labels, err = optionalParam[[]string](request, "labels")
353390
if err != nil {
354391
return mcp.NewToolResultError(err.Error()), nil
355392
}
@@ -431,12 +468,23 @@ func updateIssue(client *github.Client, t translations.TranslationHelperFunc) (t
431468
),
432469
mcp.WithString("state",
433470
mcp.Description("New state ('open' or 'closed')"),
434-
),
435-
mcp.WithString("labels",
436-
mcp.Description("Comma-separated list of new labels"),
437-
),
438-
mcp.WithString("assignees",
439-
mcp.Description("Comma-separated list of new assignees"),
471+
mcp.Enum("open", "closed"),
472+
),
473+
mcp.WithArray("labels",
474+
mcp.Description("New labels"),
475+
mcp.Items(
476+
map[string]interface{}{
477+
"type": "string",
478+
},
479+
),
480+
),
481+
mcp.WithArray("assignees",
482+
mcp.Description("New assignees"),
483+
mcp.Items(
484+
map[string]interface{}{
485+
"type": "string",
486+
},
487+
),
440488
),
441489
mcp.WithNumber("milestone",
442490
mcp.Description("New milestone number"),
@@ -484,15 +532,17 @@ func updateIssue(client *github.Client, t translations.TranslationHelperFunc) (t
484532
issueRequest.State = github.Ptr(state)
485533
}
486534

487-
labels, err := optionalCommaSeparatedListParam(request, "labels")
535+
// Get labels
536+
labels, err := optionalParam[[]string](request, "labels")
488537
if err != nil {
489538
return mcp.NewToolResultError(err.Error()), nil
490539
}
491540
if len(labels) > 0 {
492541
issueRequest.Labels = &labels
493542
}
494543

495-
assignees, err := optionalCommaSeparatedListParam(request, "assignees")
544+
// Get assignees
545+
assignees, err := optionalParam[[]string](request, "assignees")
496546
if err != nil {
497547
return mcp.NewToolResultError(err.Error()), nil
498548
}

pkg/github/issues_test.go

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -418,16 +418,23 @@ func Test_CreateIssue(t *testing.T) {
418418
mockedClient: mock.NewMockedHTTPClient(
419419
mock.WithRequestMatchHandler(
420420
mock.PostReposIssuesByOwnerByRepo,
421-
mockResponse(t, http.StatusCreated, mockIssue),
421+
expectRequestBody(t, map[string]any{
422+
"title": "Test Issue",
423+
"body": "This is a test issue",
424+
"labels": []any{"bug", "help wanted"},
425+
"assignees": []any{"user1", "user2"},
426+
}).andThen(
427+
mockResponse(t, http.StatusCreated, mockIssue),
428+
),
422429
),
423430
),
424431
requestArgs: map[string]interface{}{
425432
"owner": "owner",
426433
"repo": "repo",
427434
"title": "Test Issue",
428435
"body": "This is a test issue",
429-
"assignees": "user1, user2",
430-
"labels": "bug, help wanted",
436+
"assignees": []string{"user1", "user2"},
437+
"labels": []string{"bug", "help wanted"},
431438
},
432439
expectError: false,
433440
expectedIssue: mockIssue,
@@ -606,16 +613,26 @@ func Test_ListIssues(t *testing.T) {
606613
{
607614
name: "list issues with all parameters",
608615
mockedClient: mock.NewMockedHTTPClient(
609-
mock.WithRequestMatch(
616+
mock.WithRequestMatchHandler(
610617
mock.GetReposIssuesByOwnerByRepo,
611-
mockIssues,
618+
expectQueryParams(t, map[string]string{
619+
"state": "open",
620+
"labels": "bug,enhancement",
621+
"sort": "created",
622+
"direction": "desc",
623+
"since": "2023-01-01T00:00:00Z",
624+
"page": "1",
625+
"per_page": "30",
626+
}).andThen(
627+
mockResponse(t, http.StatusOK, mockIssues),
628+
),
612629
),
613630
),
614631
requestArgs: map[string]interface{}{
615632
"owner": "owner",
616633
"repo": "repo",
617634
"state": "open",
618-
"labels": "bug,enhancement",
635+
"labels": []string{"bug", "enhancement"},
619636
"sort": "created",
620637
"direction": "desc",
621638
"since": "2023-01-01T00:00:00Z",
@@ -750,7 +767,16 @@ func Test_UpdateIssue(t *testing.T) {
750767
mockedClient: mock.NewMockedHTTPClient(
751768
mock.WithRequestMatchHandler(
752769
mock.PatchReposIssuesByOwnerByRepoByIssueNumber,
753-
mockResponse(t, http.StatusOK, mockIssue),
770+
expectRequestBody(t, map[string]any{
771+
"title": "Updated Issue Title",
772+
"body": "Updated issue description",
773+
"state": "closed",
774+
"labels": []any{"bug", "priority"},
775+
"assignees": []any{"assignee1", "assignee2"},
776+
"milestone": float64(5),
777+
}).andThen(
778+
mockResponse(t, http.StatusOK, mockIssue),
779+
),
754780
),
755781
),
756782
requestArgs: map[string]interface{}{
@@ -760,8 +786,8 @@ func Test_UpdateIssue(t *testing.T) {
760786
"title": "Updated Issue Title",
761787
"body": "Updated issue description",
762788
"state": "closed",
763-
"labels": "bug,priority",
764-
"assignees": "assignee1,assignee2",
789+
"labels": []string{"bug", "priority"},
790+
"assignees": []string{"assignee1", "assignee2"},
765791
"milestone": float64(5),
766792
},
767793
expectError: false,

0 commit comments

Comments
 (0)