From be401faef2dfcde1739c016cd6e2346f40be3f80 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Wed, 25 Jun 2025 12:14:47 +0200 Subject: [PATCH 1/5] Add issues type filter --- pkg/github/issues.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/github/issues.go b/pkg/github/issues.go index b4c64c8de..c7a86abc1 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -192,6 +192,9 @@ func SearchIssues(getClient GetClientFn, t translations.TranslationHelperFunc) ( if err != nil { return mcp.NewToolResultError(err.Error()), nil } + if !strings.Contains(query, "is:issue") { + query = query + " is:issue" + } sort, err := OptionalParam[string](request, "sort") if err != nil { return mcp.NewToolResultError(err.Error()), nil From ae5a4d7e8fb42823bb17b377d00ec3129fe201ed Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Wed, 25 Jun 2025 14:42:41 +0200 Subject: [PATCH 2/5] Add e2e test --- e2e/e2e_test.go | 173 +++++++++++++++++++++++++++++++++++++++++++ pkg/github/issues.go | 2 +- 2 files changed, 174 insertions(+), 1 deletion(-) diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index bc5a3fde3..61739aaf5 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -1447,6 +1447,179 @@ func TestPullRequestReviewCommentSubmit(t *testing.T) { require.Equal(t, 3, len(comments), "expected to find three review comments") } +func TestListIssues(t *testing.T) { + t.Parallel() + + mcpClient := setupMCPClient(t) + + ctx := context.Background() + + // First, who am I + getMeRequest := mcp.CallToolRequest{} + getMeRequest.Params.Name = "get_me" + + t.Log("Getting current user...") + resp, err := mcpClient.CallTool(ctx, getMeRequest) + require.NoError(t, err, "expected to call 'get_me' tool successfully") + require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) + + require.False(t, resp.IsError, "expected result not to be an error") + require.Len(t, resp.Content, 1, "expected content to have one item") + + textContent, ok := resp.Content[0].(mcp.TextContent) + require.True(t, ok, "expected content to be of type TextContent") + + var trimmedGetMeText struct { + Login string `json:"login"` + } + err = json.Unmarshal([]byte(textContent.Text), &trimmedGetMeText) + require.NoError(t, err, "expected to unmarshal text content successfully") + + currentOwner := trimmedGetMeText.Login + + // Then create a repository with a README (via autoInit) + repoName := fmt.Sprintf("github-mcp-server-e2e-%s-%d", t.Name(), time.Now().UnixMilli()) + createRepoRequest := mcp.CallToolRequest{} + createRepoRequest.Params.Name = "create_repository" + createRepoRequest.Params.Arguments = map[string]any{ + "name": repoName, + "private": true, + "autoInit": true, + } + + t.Logf("Creating repository %s/%s...", currentOwner, repoName) + _, err = mcpClient.CallTool(ctx, createRepoRequest) + require.NoError(t, err, "expected to call 'create_repository' tool successfully") + require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) + + // Cleanup the repository after the test + t.Cleanup(func() { + // MCP Server doesn't support deletions, but we can use the GitHub Client + ghClient := getRESTClient(t) + t.Logf("Deleting repository %s/%s...", currentOwner, repoName) + ghClient.Repositories.Delete(context.Background(), currentOwner, repoName) + // require.NoError(t, err, "expected to delete repository successfully") + }) + + // Create a branch on which to create a new commit + createBranchRequest := mcp.CallToolRequest{} + createBranchRequest.Params.Name = "create_branch" + createBranchRequest.Params.Arguments = map[string]any{ + "owner": currentOwner, + "repo": repoName, + "branch": "test-branch", + "from_branch": "main", + } + + t.Logf("Creating branch in %s/%s...", currentOwner, repoName) + resp, err = mcpClient.CallTool(ctx, createBranchRequest) + require.NoError(t, err, "expected to call 'create_branch' tool successfully") + require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) + + // Create a commit with a new file + commitRequest := mcp.CallToolRequest{} + commitRequest.Params.Name = "create_or_update_file" + commitRequest.Params.Arguments = map[string]any{ + "owner": currentOwner, + "repo": repoName, + "path": "test-file.txt", + "content": fmt.Sprintf("Created by e2e test %s\nwith multiple lines", t.Name()), + "message": "Add test file", + "branch": "test-branch", + } + + t.Logf("Creating commit with new file in %s/%s...", currentOwner, repoName) + resp, err = mcpClient.CallTool(ctx, commitRequest) + require.NoError(t, err, "expected to call 'create_or_update_file' tool successfully") + require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) + + textContent, ok = resp.Content[0].(mcp.TextContent) + require.True(t, ok, "expected content to be of type TextContent") + + var trimmedCommitText struct { + Commit struct { + SHA string `json:"sha"` + } `json:"commit"` + } + err = json.Unmarshal([]byte(textContent.Text), &trimmedCommitText) + require.NoError(t, err, "expected to unmarshal text content successfully") + + // Create a pull request + prRequest := mcp.CallToolRequest{} + prRequest.Params.Name = "create_pull_request" + prRequest.Params.Arguments = map[string]any{ + "owner": currentOwner, + "repo": repoName, + "title": "Test PR", + "body": "This is a test PR", + "head": "test-branch", + "base": "main", + } + + t.Logf("Creating pull request in %s/%s...", currentOwner, repoName) + resp, err = mcpClient.CallTool(ctx, prRequest) + require.NoError(t, err, "expected to call 'create_pull_request' tool successfully") + require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) + + // Create a review for the pull request, but we can't approve it + // because the current owner also owns the PR. + createPendingPullRequestReviewRequest := mcp.CallToolRequest{} + createPendingPullRequestReviewRequest.Params.Name = "create_pending_pull_request_review" + createPendingPullRequestReviewRequest.Params.Arguments = map[string]any{ + "owner": currentOwner, + "repo": repoName, + "pullNumber": 1, + } + + t.Logf("Creating pending review for pull request in %s/%s...", currentOwner, repoName) + resp, err = mcpClient.CallTool(ctx, createPendingPullRequestReviewRequest) + require.NoError(t, err, "expected to call 'create_pending_pull_request_review' tool successfully") + require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) + + textContent, ok = resp.Content[0].(mcp.TextContent) + require.True(t, ok, "expected content to be of type TextContent") + require.Equal(t, "pending pull request created", textContent.Text) + + t.Logf("Creating an issue in %s/%s...", currentOwner, repoName) + createIssueRequest := mcp.CallToolRequest{} + createIssueRequest.Params.Name = "create_issue" + createIssueRequest.Params.Arguments = map[string]any{ + "owner": currentOwner, + "repo": repoName, + "title": "Test Issue", + "body": "This is a test issue", + } + resp, err = mcpClient.CallTool(ctx, createIssueRequest) + require.NoError(t, err, "expected to call 'create_issue' tool successfully") + require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) + + // List issues in the repository + listIssuesRequest := mcp.CallToolRequest{} + listIssuesRequest.Params.Name = "list_issues" + listIssuesRequest.Params.Arguments = map[string]any{ + "owner": currentOwner, + "repo": repoName, + } + + t.Logf("Listing issues in %s/%s...", currentOwner, repoName) + resp, err = mcpClient.CallTool(ctx, listIssuesRequest) + require.NoError(t, err, "expected to call 'list_issues' tool successfully") + require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) + + textContent, ok = resp.Content[0].(mcp.TextContent) + require.True(t, ok, "expected content to be of type TextContent") + + var issues []struct { + ID int `json:"id"` + Number int `json:"number"` + Title string `json:"title"` + Body string `json:"body"` + } + err = json.Unmarshal([]byte(textContent.Text), &issues) + require.NoError(t, err, "expected to unmarshal text content successfully") + require.Len(t, issues, 1, "expected to find one issue") +} + func TestPullRequestReviewDeletion(t *testing.T) { t.Parallel() diff --git a/pkg/github/issues.go b/pkg/github/issues.go index c7a86abc1..471b2077c 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -193,7 +193,7 @@ func SearchIssues(getClient GetClientFn, t translations.TranslationHelperFunc) ( return mcp.NewToolResultError(err.Error()), nil } if !strings.Contains(query, "is:issue") { - query = query + " is:issue" + query += " is:issue" } sort, err := OptionalParam[string](request, "sort") if err != nil { From 59919f00dfa19d5cbb2ed672985daffd57021e22 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Wed, 25 Jun 2025 14:51:21 +0200 Subject: [PATCH 3/5] Update e2e/e2e_test.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- e2e/e2e_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index 61739aaf5..3b96aa666 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -1488,7 +1488,7 @@ func TestListIssues(t *testing.T) { } t.Logf("Creating repository %s/%s...", currentOwner, repoName) - _, err = mcpClient.CallTool(ctx, createRepoRequest) + resp, err := mcpClient.CallTool(ctx, createRepoRequest) require.NoError(t, err, "expected to call 'create_repository' tool successfully") require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) From 45bfff69841334a4465e35b9d72ffeb35da88899 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Fri, 27 Jun 2025 15:19:37 +0200 Subject: [PATCH 4/5] Add pull requests tool description --- README.md | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f1f3bdaf0..e11efc62a 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,6 @@ The remote GitHub MCP Server is hosted by GitHub and provides the easiest method For quick installation, use one of the one-click install buttons above. Once you complete that flow, toggle Agent mode (located by the Copilot Chat text input) and the server will start. Make sure you're using [VS Code 1.101](https://code.visualstudio.com/updates/v1_101) or [later](https://code.visualstudio.com/updates) for remote MCP and OAuth support. - Alternatively, to manually configure VS Code, choose the appropriate JSON block from the examples below and add it to your host configuration: @@ -176,7 +175,6 @@ Add the following JSON block to your IDE MCP settings. Optionally, you can add a similar example (i.e. without the mcp key) to a file called `.vscode/mcp.json` in your workspace. This will allow you to share the configuration with others. - ```json { "inputs": [ @@ -276,7 +274,6 @@ The following sets of tools are available (all are on by default): | `users` | Anything relating to GitHub Users | | `experiments` | Experimental features (not considered stable) | - #### Specifying Toolsets To specify toolsets you want available to the LLM, you can pass an allow-list in two ways: @@ -288,6 +285,7 @@ To specify toolsets you want available to the LLM, you can pass an allow-list in ``` 2. **Using Environment Variable**: + ```bash GITHUB_TOOLSETS="repos,issues,pull_requests,actions,code_security" ./github-mcp-server ``` @@ -366,6 +364,7 @@ the hostname for GitHub Enterprise Server or GitHub Enterprise Cloud with data r - For GitHub Enterprise Server, prefix the hostname with the `https://` URI scheme, as it otherwise defaults to `http://`, which GitHub Enterprise Server does not support. - For GitHub Enterprise Cloud with data residency, use `https://YOURSUBDOMAIN.ghe.com` as the hostname. + ``` json "github": { "command": "docker", @@ -491,7 +490,7 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description - `assignees`: New assignees (string[], optional) - `milestone`: New milestone number (number, optional) -- **search_issues** - Search for issues and pull requests +- **search_issues** - Search for issues - `query`: Search query (string, required) - `sort`: Sort field (string, optional) - `order`: Sort order (string, optional) @@ -527,6 +526,18 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description - `perPage`: Results per page (number, optional) - `page`: Page number (number, optional) +- **search_pull_requests** - Search for pull requests in GitHub repositories using pull request search syntax + + - `query`: Search query using GitHub pull request search syntax (string, required) + - `owner`: Repository owner (string, optional) + - `repo`: Repository name (string, optional) + - `sort`: Sort field by number of matches of categories, defaults to best match (string, optional) + - Enum: "comments", "reactions", "reactions-+1", "reactions--1", "reactions-smile", "reactions-thinking_face", "reactions-heart", "reactions-tada", "interactions", "created", "updated" + - `order`: Sort order (string, optional) + - Enum: "asc", "desc" + - `page`: Page number (number, optional) + - `perPage`: Results per page (number, optional) + - **merge_pull_request** - Merge a pull request - `owner`: Repository owner (string, required) From f196364d045557105db5ee6a8d2d4a78c01ce1d1 Mon Sep 17 00:00:00 2001 From: JoannaaKL Date: Fri, 27 Jun 2025 15:40:35 +0200 Subject: [PATCH 5/5] Update get_pull_request_files search_pull_requests --- README.md | 8 ++- e2e/e2e_test.go | 173 ------------------------------------------------ 2 files changed, 5 insertions(+), 176 deletions(-) diff --git a/README.md b/README.md index e11efc62a..54cc89fd3 100644 --- a/README.md +++ b/README.md @@ -531,10 +531,8 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description - `query`: Search query using GitHub pull request search syntax (string, required) - `owner`: Repository owner (string, optional) - `repo`: Repository name (string, optional) - - `sort`: Sort field by number of matches of categories, defaults to best match (string, optional) - - Enum: "comments", "reactions", "reactions-+1", "reactions--1", "reactions-smile", "reactions-thinking_face", "reactions-heart", "reactions-tada", "interactions", "created", "updated" + - `sort`: Sort field (string, optional) - `order`: Sort order (string, optional) - - Enum: "asc", "desc" - `page`: Page number (number, optional) - `perPage`: Results per page (number, optional) @@ -552,6 +550,10 @@ export GITHUB_MCP_TOOL_ADD_ISSUE_COMMENT_DESCRIPTION="an alternative description - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `pullNumber`: Pull request number (number, required) + - `sort`: Sort field (string, optional) + - `direction`: Sort direction (string, optional) + - `perPage`: Results per page (number, optional) + - `page`: Page number (number, optional) - **get_pull_request_status** - Get the combined status of all status checks for a pull request diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index 3b96aa666..bc5a3fde3 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -1447,179 +1447,6 @@ func TestPullRequestReviewCommentSubmit(t *testing.T) { require.Equal(t, 3, len(comments), "expected to find three review comments") } -func TestListIssues(t *testing.T) { - t.Parallel() - - mcpClient := setupMCPClient(t) - - ctx := context.Background() - - // First, who am I - getMeRequest := mcp.CallToolRequest{} - getMeRequest.Params.Name = "get_me" - - t.Log("Getting current user...") - resp, err := mcpClient.CallTool(ctx, getMeRequest) - require.NoError(t, err, "expected to call 'get_me' tool successfully") - require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) - - require.False(t, resp.IsError, "expected result not to be an error") - require.Len(t, resp.Content, 1, "expected content to have one item") - - textContent, ok := resp.Content[0].(mcp.TextContent) - require.True(t, ok, "expected content to be of type TextContent") - - var trimmedGetMeText struct { - Login string `json:"login"` - } - err = json.Unmarshal([]byte(textContent.Text), &trimmedGetMeText) - require.NoError(t, err, "expected to unmarshal text content successfully") - - currentOwner := trimmedGetMeText.Login - - // Then create a repository with a README (via autoInit) - repoName := fmt.Sprintf("github-mcp-server-e2e-%s-%d", t.Name(), time.Now().UnixMilli()) - createRepoRequest := mcp.CallToolRequest{} - createRepoRequest.Params.Name = "create_repository" - createRepoRequest.Params.Arguments = map[string]any{ - "name": repoName, - "private": true, - "autoInit": true, - } - - t.Logf("Creating repository %s/%s...", currentOwner, repoName) - resp, err := mcpClient.CallTool(ctx, createRepoRequest) - require.NoError(t, err, "expected to call 'create_repository' tool successfully") - require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) - - // Cleanup the repository after the test - t.Cleanup(func() { - // MCP Server doesn't support deletions, but we can use the GitHub Client - ghClient := getRESTClient(t) - t.Logf("Deleting repository %s/%s...", currentOwner, repoName) - ghClient.Repositories.Delete(context.Background(), currentOwner, repoName) - // require.NoError(t, err, "expected to delete repository successfully") - }) - - // Create a branch on which to create a new commit - createBranchRequest := mcp.CallToolRequest{} - createBranchRequest.Params.Name = "create_branch" - createBranchRequest.Params.Arguments = map[string]any{ - "owner": currentOwner, - "repo": repoName, - "branch": "test-branch", - "from_branch": "main", - } - - t.Logf("Creating branch in %s/%s...", currentOwner, repoName) - resp, err = mcpClient.CallTool(ctx, createBranchRequest) - require.NoError(t, err, "expected to call 'create_branch' tool successfully") - require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) - - // Create a commit with a new file - commitRequest := mcp.CallToolRequest{} - commitRequest.Params.Name = "create_or_update_file" - commitRequest.Params.Arguments = map[string]any{ - "owner": currentOwner, - "repo": repoName, - "path": "test-file.txt", - "content": fmt.Sprintf("Created by e2e test %s\nwith multiple lines", t.Name()), - "message": "Add test file", - "branch": "test-branch", - } - - t.Logf("Creating commit with new file in %s/%s...", currentOwner, repoName) - resp, err = mcpClient.CallTool(ctx, commitRequest) - require.NoError(t, err, "expected to call 'create_or_update_file' tool successfully") - require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) - - textContent, ok = resp.Content[0].(mcp.TextContent) - require.True(t, ok, "expected content to be of type TextContent") - - var trimmedCommitText struct { - Commit struct { - SHA string `json:"sha"` - } `json:"commit"` - } - err = json.Unmarshal([]byte(textContent.Text), &trimmedCommitText) - require.NoError(t, err, "expected to unmarshal text content successfully") - - // Create a pull request - prRequest := mcp.CallToolRequest{} - prRequest.Params.Name = "create_pull_request" - prRequest.Params.Arguments = map[string]any{ - "owner": currentOwner, - "repo": repoName, - "title": "Test PR", - "body": "This is a test PR", - "head": "test-branch", - "base": "main", - } - - t.Logf("Creating pull request in %s/%s...", currentOwner, repoName) - resp, err = mcpClient.CallTool(ctx, prRequest) - require.NoError(t, err, "expected to call 'create_pull_request' tool successfully") - require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) - - // Create a review for the pull request, but we can't approve it - // because the current owner also owns the PR. - createPendingPullRequestReviewRequest := mcp.CallToolRequest{} - createPendingPullRequestReviewRequest.Params.Name = "create_pending_pull_request_review" - createPendingPullRequestReviewRequest.Params.Arguments = map[string]any{ - "owner": currentOwner, - "repo": repoName, - "pullNumber": 1, - } - - t.Logf("Creating pending review for pull request in %s/%s...", currentOwner, repoName) - resp, err = mcpClient.CallTool(ctx, createPendingPullRequestReviewRequest) - require.NoError(t, err, "expected to call 'create_pending_pull_request_review' tool successfully") - require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) - - textContent, ok = resp.Content[0].(mcp.TextContent) - require.True(t, ok, "expected content to be of type TextContent") - require.Equal(t, "pending pull request created", textContent.Text) - - t.Logf("Creating an issue in %s/%s...", currentOwner, repoName) - createIssueRequest := mcp.CallToolRequest{} - createIssueRequest.Params.Name = "create_issue" - createIssueRequest.Params.Arguments = map[string]any{ - "owner": currentOwner, - "repo": repoName, - "title": "Test Issue", - "body": "This is a test issue", - } - resp, err = mcpClient.CallTool(ctx, createIssueRequest) - require.NoError(t, err, "expected to call 'create_issue' tool successfully") - require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) - - // List issues in the repository - listIssuesRequest := mcp.CallToolRequest{} - listIssuesRequest.Params.Name = "list_issues" - listIssuesRequest.Params.Arguments = map[string]any{ - "owner": currentOwner, - "repo": repoName, - } - - t.Logf("Listing issues in %s/%s...", currentOwner, repoName) - resp, err = mcpClient.CallTool(ctx, listIssuesRequest) - require.NoError(t, err, "expected to call 'list_issues' tool successfully") - require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp)) - - textContent, ok = resp.Content[0].(mcp.TextContent) - require.True(t, ok, "expected content to be of type TextContent") - - var issues []struct { - ID int `json:"id"` - Number int `json:"number"` - Title string `json:"title"` - Body string `json:"body"` - } - err = json.Unmarshal([]byte(textContent.Text), &issues) - require.NoError(t, err, "expected to unmarshal text content successfully") - require.Len(t, issues, 1, "expected to find one issue") -} - func TestPullRequestReviewDeletion(t *testing.T) { t.Parallel()