8000 Merge branch 'main' into patch-1 · github/github-mcp-server@0fd4348 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0fd4348

Browse files
authored
Merge branch 'main' into patch-1
2 parents 15dccf1 + e4c2f58 commit 0fd4348

File tree

16 files changed

+507
-203
lines changed

16 files changed

+507
-203
lines changed

.github/workflows/code-scanning.yml

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
name: "CodeQL"
2+
run-name: ${{ github.event.inputs.code_scanning_run_name }}
3+
on: [push, pull_request, workflow_dispatch]
4+
5+
concurrency:
6+
group: ${{ github.workflow }}-${{ github.ref }}
7+
cancel-in-progress: true
8+
9+
env:
10+
CODE_SCANNING_REF: ${{ github.event.inputs.code_scanning_ref }}
11+
CODE_SCANNING_BASE_BRANCH: ${{ github.event.inputs.code_scanning_base_branch }}
12+
CODE_SCANNING_IS_ANALYZING_DEFAULT_BRANCH: ${{ github.event.inputs.code_scanning_is_analyzing_default_branch }}
13+
14+
jobs:
15+
analyze:
16+
name: Analyze (${{ matrix.language }})
17+
runs-on: ${{ fromJSON(matrix.runner) }}
18+
permissions:
19+
actions: read
20+
contents: read
21+
packages: read
22+
security-events: write
23+
continue-on-error: false
24+
strategy:
25+
fail-fast: false
26+
matrix:
27+
include:
28+
- language: actions
29+
category: /language:actions
30+
build-mode: none
31+
runner: '["ubuntu-22.04"]'
32+
- language: go
33+
category: /language:go
34+
build-mode: autobuild
35+
runner: '["ubuntu-22.04"]'
36+
steps:
37+
- name: Checkout repository
38+
uses: actions/checkout@v4
39+
40+
- name: Initialize CodeQL
41+
uses: github/codeql-action/init@v3
42+
with:
43+
languages: ${{ matrix.language }}
44+
build-mode: ${{ matrix.build-mode }}
45+
dependency-caching: ${{ runner.environment == 'github-hosted' }}
46+
queries: "" # Default query suite
47+
packs: github/ccr-${{ matrix.language }}-queries
48+
config: |
49+
default-setup:
50+
org:
51+
model-packs: [ ${{ github.event.inputs.code_scanning_codeql_packs }} ]
52+
threat-models: [ ]
53+
- name: Setup proxy for registries
54+
id: proxy
55+
uses: github/codeql-action/start-proxy@v3
56+
with:
57+
registries_credentials: ${{ secrets.GITHUB_REGISTRIES_PROXY }}
58+
language: ${{ matrix.language }}
59+
60+
- name: Configure
61+
uses: github/codeql-action/resolve-environment@v3
62+
id: resolve-environment
63+
with:
64+
language: ${{ matrix.language }}
65+
- name: Setup Go
66+
uses: actions/setup-go@v5
67+
if: matrix.language == 'go' && fromJSON(steps.resolve-environment.outputs.environment).configuration.go.version
68+
with:
69+
go-version: ${{ fromJSON(steps.resolve-environment.outputs.environment).configuration.go.version }}
70+
cache: false
71+
72+
- name: Autobuild
73+
uses: github/codeql-action/autobuild@v3
74+
75+
- name: Perform CodeQL Analysis
76+
uses: github/codeql-action/analyze@v3
77+
env:
78+
CODEQL_PROXY_HOST: ${{ steps.proxy.outputs.proxy_host }}
79+
CODEQL_PROXY_PORT: ${{ steps.proxy.outputs.proxy_port }}
80+
CODEQL_PROXY_CA_CERTIFICATE: ${{ steps.proxy.outputs.proxy_ca_certificate }}
81+
with:
82+
category: ${{ matrix.category }}

README.md

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,12 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description
153153
- `repo`: Repository name (string, required)
154154
- `issue_number`: Issue number (number, required)
155155

156+
- **get_issue_comments** - Get comments for a GitHub issue
157+
158+
- `owner`: Repository owner (string, required)
159+
- `repo`: Repository name (string, required)
160+
- `issue_number`: Issue number (number, required)
161+
156162
- **create_issue** - Create a new issue in a GitHub repository
157163

158164
- `owner`: Repository owner (string, required)
@@ -266,7 +272,9 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description
266272
- `body`: Review comment text (string, optional)
267273
- `event`: Review action ('APPROVE', 'REQUEST_CHANGES', 'COMMENT') (string, required)
268274
- `commitId`: SHA of commit to review (string, optional)
269-
- `comments`: Line-specific comments array of objects, each object with path (string), position (number), and body (string) (array, optional)
275+
- `comments`: Line-specific comments array of objects to place comments on pull request changes (array, optional)
276+
- For inline comments: provide `path`, `position` (or `line`), and `body`
277+
- For multi-line comments: provide `path`, `start_line`, `line`, optional `side`/`start_side`, and `body`
270278

271279
- **create_pull_request** - Create a new pull request
272280

cmd/mcpcurl/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ Available Commands:
5050
fork_repository Fork a GitHub repository to your account or specified organization
5151
get_file_contents Get the contents of a file or directory from a GitHub repository
5252
get_issue Get details of a specific issue in a GitHub repository.
53+
get_issue_comments Get comments for a GitHub issue
5354
list_commits Get list of commits of a branch in a GitHub repository
5455
list_issues List issues in a GitHub repository with filtering options
5556
push_files Push multiple files to a GitHub repository in a single commit

go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ module github.com/github/github-mcp-server
33
go 1.23.7
44

55
require (
6-
github.com/aws/smithy-go v1.22.3
76
github.com/docker/docker v28.0.4+incompatible
87
github.com/google/go-cmp v0.7.0
98
github.com/google/go-github/v69 v69.2.0

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@ github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOEl
22
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E=
33
github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY=
44
github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU=
5-
github.com/aws/smithy-go v1.22.3 h1:Z//5NuZCSW6R4PhQ93hShNbyBbn8BWCmCVCt+Q8Io5k=
6-
github.com/aws/smithy-go v1.22.3/go.mod h1:t1ufH5HMublsJYulve2RKmHDC15xu1f26kHCp/HgceI=
75
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=
86
github.com/cenkalti/backoff/v4 v4.2.1/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
97
github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I=

pkg/github/issues.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -597,6 +597,81 @@ func updateIssue(client *github.Client, t translations.TranslationHelperFunc) (t
597597
}
598598
}
599599

600+
// getIssueComments creates a tool to get comments for a GitHub issue.
601+
func getIssueComments(client *github.Client, t translations.TranslationHelperFunc) (tool mcp.Tool, handler server.ToolHandlerFunc) {
602+
return mcp.NewTool("get_issue_comments",
603+
mcp.WithDescription(t("TOOL_GET_ISSUE_COMMENTS_DESCRIPTION", "Get comments for a GitHub issue")),
604+
mcp.WithString("owner",
605+
mcp.Required(),
606+
mcp.Description("Repository owner"),
607+
),
608+
mcp.WithString("repo",
609+
mcp.Required(),
610+
mcp.Description("Repository name"),
611+
),
612+
mcp.WithNumber("issue_number",
613+
mcp.Required(),
614+
mcp.Description("Issue number"),
615+
),
616+
mcp.WithNumber("page",
617+
mcp.Description("Page number"),
618+
),
619+
mcp.WithNumber("per_page",
620+
mcp.Description("Number of records per page"),
621+
),
622+
),
623+
func(ctx context.Context, request mcp.CallToolRequest) (*mcp.CallToolResult, error) {
624+
owner, err := requiredParam[string](request, "owner")
625+
if err != nil {
626+
return mcp.NewToolResultError(err.Error()), nil
627+
}
628+
repo, err := requiredParam[string](request, "repo")
629+
if err != nil {
630+
return mcp.NewToolResultError(err.Error()), nil
631+
}
632+
issueNumber, err := requiredInt(request, "issue_number")
633+
if err != nil {
634+
return mcp.NewToolResultError(err.Error()), nil
635+
}
636+
page, err := optionalIntParamWithDefault(request, "page", 1)
637+
if err != nil {
638+
return mcp.NewToolResultError(err.Error()), nil
639+
}
640+
perPage, err := optionalIntParamWithDefault(request, "per_page", 30)
641+
if err != nil {
642+
return mcp.NewToolResultError(err.Error()), nil
643+
}
644+
645+
opts := &github.IssueListCommentsOptions{
646+
ListOptions: github.ListOptions{
647+
Page: page,
648+
PerPage: perPage,
649+
},
650+
}
651+
652+
comments, resp, err := client.Issues.ListComments(ctx, owner, repo, issueNumber, opts)
653+
if err != nil {
654+
return nil, fmt.Errorf("failed to get issue comments: %w", err)
655+
}
656+
defer func() { _ = resp.Body.Close() }()
657+
658+
if resp.StatusCode != http.StatusOK {
659+
body, err := io.ReadAll(resp.Body)
660+
if err != nil {
661+
return nil, fmt.Errorf("failed to read response body: %w", err)
662+
}
663+
return mcp.NewToolResultError(fmt.Sprintf("failed to get issue comments: %s", string(body))), nil
664+
}
665+
666+
r, err := json.Marshal(comments)
667+
if err != nil {
668+
return nil, fmt.Errorf("failed to marshal response: %w", err)
669+
}
670+
671+
return mcp.NewToolResultText(string(r)), nil
672+
}
673+
}
674+
600675
// parseISOTimestamp parses an ISO 8601 timestamp string into a time.Time object.
601676
// Returns the parsed time or an error if parsing fails.
602677
// Example formats supported: "2023-01-15T14:30:00Z", "2023-01-15"

pkg/github/issues_test.go

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -984,3 +984,137 @@ func Test_ParseISOTimestamp(t *testing.T) {
984984
})
985985
}
986986
}
987+
988+
func Test_GetIssueComments(t *testing.T) {
989+
// Verify tool definition once
990+
mockClient := github.NewClient(nil)
991+
tool, _ := getIssueComments(mockClient, translations.NullTranslationHelper)
992+
993+
assert.Equal(t, "get_issue_comments", tool.Name)
994+
assert.NotEmpty(t, tool.Description)
995+
assert.Contains(t, tool.InputSchema.Properties, "owner")
996+
assert.Contains(t, tool.InputSchema.Properties, "repo")
997+
assert.Contains(t, tool.InputSchema.Properties, "issue_number")
998+
assert.Contains(t, tool.InputSchema.Properties, "page")
999+
assert.Contains(t, tool.InputSchema.Properties, "per_page")
1000+
assert.ElementsMatch(t, tool.InputSchema.Required, []string{"owner", "repo", "issue_number"})
1001+
1002+
// Setup mock comments for success case
1003+
mockComments := []*github.IssueComment{
1004+
{
1005+
ID: github.Ptr(int64(123)),
1006+
Body: github.Ptr("This is the first comment"),
1007+
User: &github.User{
1008+
Login: github.Ptr("user1"),
1009+
},
1010+
CreatedAt: &github.Timestamp{Time: time.Now().Add(-time.Hour * 24)},
1011+
},
1012+
{
1013+
ID: github.Ptr(int64(456)),
1014+
Body: github.Ptr("This is the second comment"),
1015+
User: &github.User{
1016+
Login: github.Ptr("user2"),
1017+
},
1018+
CreatedAt: &github.Timestamp{Time: time.Now().Add(-time.Hour)},
1019+
},
1020+
}
1021+
1022+
tests := []struct {
1023+
name string
1024+
mockedClient *http.Client
1025+
requestArgs map[string]interface{}
1026+
expectError bool
1027+
expectedComments []*github.IssueComment
1028+
expectedErrMsg string
1029+
}{
1030+
{
1031+
name: "successful comments retrieval",
1032+
mockedClient: mock.NewMockedHTTPClient(
1033+
mock.WithRequestMatch(
1034+
mock.GetReposIssuesCommentsByOwnerByRepoByIssueNumber,
1035+
mockComments,
1036+
),
1037+
),
1038+
requestArgs: map[string]interface{}{
1039+
"owner": "owner",
1040+
"repo": "repo",
1041+
"issue_number": float64(42),
1042+
},
1043+
expectError: false,
1044+
expectedComments: mockComments,
1045+
},
1046+
{
1047+
name: "successful comments retrieval with pagination",
1048+
mockedClient: mock.NewMockedHTTPClient(
1049+
mock.WithRequestMatchHandler(
1050+
mock.GetReposIssuesCommentsByOwnerByRepoByIssueNumber,
1051+
expectQueryParams(t, map[string]string{
1052+
"page": "2",
1053+
"per_page": "10",
1054+
}).andThen(
1055+
mockResponse(t, http.StatusOK, mockComments),
1056+
),
1057+
),
1058+
),
1059+
requestArgs: map[string]interface{}{
1060+
"owner": "owner",
1061+
"repo": "repo",
1062+
"issue_number": float64(42),
1063+
"page": float64(2),
1064+
"per_page": float64(10),
1065+
},
1066+
expectError: false,
1067+
expectedComments: mockComments,
1068+
},
1069+
{
1070+
name: "issue not found",
1071+
mockedClient: mock.NewMockedHTTPClient(
1072+
mock.WithRequestMatchHandler(
1073+
mock.GetReposIssuesCommentsByOwnerByRepoByIssueNumber,
1074+
mockResponse(t, http.StatusNotFound, `{"message": "Issue not found"}`),
1075+
),
1076+
),
1077+
requestArgs: map[string]interface{}{
1078+
"owner": "owner",
1079+
"repo": "repo",
1080+
"issue_number": float64(999),
1081+
},
1082+
expectError: true,
1083+
expectedErrMsg: "failed to get issue comments",
1084+
},
1085+
}
1086+
1087+
for _, tc := range tests {
1088+
t.Run(tc.name, func(t *testing.T) {
1089+
// Setup client with mock
1090+
client := github.NewClient(tc.mockedClient)
1091+
_, handler := getIssueComments(client, translations.NullTranslationHelper)
1092+
1093+
// Create call request
1094+
request := createMCPRequest(tc.requestArgs)
1095+
1096+
// Call handler
1097+
result, err := handler(context.Background(), request)
1098+
1099+
// Verify results
1100+
if tc.expectError {
1101+
require.Error(t, err)
1102+
assert.Contains(t, err.Error(), tc.expectedErrMsg)
1103+
return
1104+
}
1105+
1106+
require.NoError(t, err)
1107+
textContent := getTextResult(t, result)
1108+
1109+
// Unmarshal and verify the result
1110+
var returnedComments []*github.IssueComment
1111+
err = json.Unmarshal([]byte(textContent.Text), &returnedComments)
1112+
require.NoError(t, err)
1113+
assert.Equal(t, len(tc.expectedComments), len(returnedComments))
1114+
if len(returnedComments) > 0 {
1115+
assert.Equal(t, *tc.expectedComments[0].Body, *returnedComments[0].Body)
1116+
assert.Equal(t, *tc.expectedComments[0].User.Login, *returnedComments[0].User.Login)
1117+
}
1118+
})
1119+
}
1120+
}

0 commit comments

Comments
 (0)
0