From 1decd77b69b568cd70f8b627c909cf4be1da35b1 Mon Sep 17 00:00:00 2001 From: tommaso-moro Date: Wed, 24 Dec 2025 15:19:11 +0100 Subject: [PATCH 001/138] remove claude web --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 059256aaa..e2b5af23a 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,7 @@ Alternatively, to manually configure VS Code, choose the appropriate JSON block ### Install in other MCP hosts - **[GitHub Copilot in other IDEs](/docs/installation-guides/install-other-copilot-ides.md)** - Installation for JetBrains, Visual Studio, Eclipse, and Xcode with GitHub Copilot -- **[Claude Applications](/docs/installation-guides/install-claude.md)** - Installation guide for Claude Web, Claude Desktop and Claude Code CLI +- **[Claude Applications](/docs/installation-guides/install-claude.md)** - Installation guide for Claude Desktop and Claude Code CLI - **[Cursor](/docs/installation-guides/install-cursor.md)** - Installation guide for Cursor IDE - **[Windsurf](/docs/installation-guides/install-windsurf.md)** - Installation guide for Windsurf IDE From 43bea59a195da31e1797d709b81fee79ed933de5 Mon Sep 17 00:00:00 2001 From: Matt Holloway Date: Mon, 29 Dec 2025 10:25:17 +0000 Subject: [PATCH 002/138] Change list workflow runs to allow empty resource id to list all runs in repo (#1682) * change list workflow runs to allow empty resource id to list all runs in repo * update docs --- README.md | 2 +- pkg/github/__toolsnaps__/actions_list.snap | 22 ++++++------- pkg/github/actions.go | 20 ++++++------ pkg/github/actions_test.go | 36 +++++++++++++++++++--- 4 files changed, 55 insertions(+), 25 deletions(-) diff --git a/README.md b/README.md index e2b5af23a..9376b6c09 100644 --- a/README.md +++ b/README.md @@ -509,7 +509,7 @@ The following sets of tools are available: - `repo`: Repository name (string, required) - `resource_id`: The unique identifier of the resource. This will vary based on the "method" provided, so ensure you provide the correct ID: - Do not provide any resource ID for 'list_workflows' method. - - Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'list_workflow_runs' method. + - Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'list_workflow_runs' method, or omit to list all workflow runs in the repository. - Provide a workflow run ID for 'list_workflow_jobs' and 'list_workflow_run_artifacts' methods. (string, optional) - `workflow_jobs_filter`: Filters for workflow jobs. **ONLY** used when method is 'list_workflow_jobs' (object, optional) diff --git a/pkg/github/__toolsnaps__/actions_list.snap b/pkg/github/__toolsnaps__/actions_list.snap index 3968a6eae..4bd029388 100644 --- a/pkg/github/__toolsnaps__/actions_list.snap +++ b/pkg/github/__toolsnaps__/actions_list.snap @@ -6,11 +6,6 @@ "description": "Tools for listing GitHub Actions resources.\nUse this tool to list workflows in a repository, or list workflow runs, jobs, and artifacts for a specific workflow or workflow run.\n", "inputSchema": { "type": "object", - "required": [ - "method", - "owner", - "repo" - ], "properties": { "method": { "type": "string", @@ -43,11 +38,10 @@ }, "resource_id": { "type": "string", - "description": "The unique identifier of the resource. This will vary based on the \"method\" provided, so ensure you provide the correct ID:\n- Do not provide any resource ID for 'list_workflows' method.\n- Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'list_workflow_runs' method.\n- Provide a workflow run ID for 'list_workflow_jobs' and 'list_workflow_run_artifacts' methods.\n" + "description": "The unique identifier of the resource. This will vary based on the \"method\" provided, so ensure you provide the correct ID:\n- Do not provide any resource ID for 'list_workflows' method.\n- Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'list_workflow_runs' method, or omit to list all workflow runs in the repository.\n- Provide a workflow run ID for 'list_workflow_jobs' and 'list_workflow_run_artifacts' methods.\n" }, "workflow_jobs_filter": { "type": "object", - "description": "Filters for workflow jobs. **ONLY** used when method is 'list_workflow_jobs'", "properties": { "filter": { "type": "string", @@ -57,11 +51,11 @@ "all" ] } - } + }, + "description": "Filters for workflow jobs. **ONLY** used when method is 'list_workflow_jobs'" }, "workflow_runs_filter": { "type": "object", - "description": "Filters for workflow runs. **ONLY** used when method is 'list_workflow_runs'", "properties": { "actor": { "type": "string", @@ -120,9 +114,15 @@ "waiting" ] } - } + }, + "description": "Filters for workflow runs. **ONLY** used when method is 'list_workflow_runs'" } - } + }, + "required": [ + "method", + "owner", + "repo" + ] }, "name": "actions_list" } \ No newline at end of file diff --git a/pkg/github/actions.go b/pkg/github/actions.go index 6c7cdc367..1547c3251 100644 --- a/pkg/github/actions.go +++ b/pkg/github/actions.go @@ -1463,7 +1463,7 @@ Use this tool to list workflows in a repository, or list workflow runs, jobs, an Type: "string", Description: `The unique identifier of the resource. This will vary based on the "method" provided, so ensure you provide the correct ID: - Do not provide any resource ID for 'list_workflows' method. -- Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'list_workflow_runs' method. +- Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'list_workflow_runs' method, or omit to list all workflow runs in the repository. - Provide a workflow run ID for 'list_workflow_jobs' and 'list_workflow_run_artifacts' methods. `, }, @@ -1586,18 +1586,18 @@ Use this tool to list workflows in a repository, or list workflow runs, jobs, an switch method { case actionsMethodListWorkflows: // Do nothing, no resource ID needed + case actionsMethodListWorkflowRuns: + // resource_id is optional for list_workflow_runs + // If not provided, list all workflow runs in the repository default: if resourceID == "" { return utils.NewToolResultError(fmt.Sprintf("missing required parameter for method %s: resource_id", method)), nil, nil } - // For list_workflow_runs, resource_id could be a filename or numeric ID - // For other actions, resource ID must be an integer - if method != actionsMethodListWorkflowRuns { - resourceIDInt, parseErr = strconv.ParseInt(resourceID, 10, 64) - if parseErr != nil { - return utils.NewToolResultError(fmt.Sprintf("invalid resource_id, must be an integer for method %s: %v", method, parseErr)), nil, nil - } + // resource ID must be an integer for jobs and artifacts + resourceIDInt, parseErr = strconv.ParseInt(resourceID, 10, 64) + if parseErr != nil { + return utils.NewToolResultError(fmt.Sprintf("invalid resource_id, must be an integer for method %s: %v", method, parseErr)), nil, nil } } @@ -2063,7 +2063,9 @@ func listWorkflowRuns(ctx context.Context, client *github.Client, args map[strin var workflowRuns *github.WorkflowRuns var resp *github.Response - if workflowIDInt, parseErr := strconv.ParseInt(resourceID, 10, 64); parseErr == nil { + if resourceID == "" { + workflowRuns, resp, err = client.Actions.ListRepositoryWorkflowRuns(ctx, owner, repo, listWorkflowRunsOptions) + } else if workflowIDInt, parseErr := strconv.ParseInt(resourceID, 10, 64); parseErr == nil { workflowRuns, resp, err = client.Actions.ListWorkflowRunsByID(ctx, owner, repo, workflowIDInt, listWorkflowRunsOptions) } else { workflowRuns, resp, err = client.Actions.ListWorkflowRunsByFileName(ctx, owner, repo, resourceID, listWorkflowRunsOptions) diff --git a/pkg/github/actions_test.go b/pkg/github/actions_test.go index f2d336e21..7319feddf 100644 --- a/pkg/github/actions_test.go +++ b/pkg/github/actions_test.go @@ -1997,8 +1997,33 @@ func Test_ActionsList_ListWorkflowRuns(t *testing.T) { assert.NotNil(t, response.TotalCount) }) - t.Run("missing resource_id for list_workflow_runs", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient() + t.Run("list all workflow runs without resource_id", func(t *testing.T) { + mockedClient := mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.GetReposActionsRunsByOwnerByRepo, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + runs := &github.WorkflowRuns{ + TotalCount: github.Ptr(2), + WorkflowRuns: []*github.WorkflowRun{ + { + ID: github.Ptr(int64(123)), + Name: github.Ptr("CI"), + Status: github.Ptr("completed"), + Conclusion: github.Ptr("success"), + }, + { + ID: github.Ptr(int64(456)), + Name: github.Ptr("Deploy"), + Status: github.Ptr("in_progress"), + Conclusion: nil, + }, + }, + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(runs) + }), + ), + ) client := github.NewClient(mockedClient) deps := BaseDeps{ @@ -2014,10 +2039,13 @@ func Test_ActionsList_ListWorkflowRuns(t *testing.T) { result, err := handler(ContextWithDeps(context.Background(), deps), &request) require.NoError(t, err) - require.True(t, result.IsError) + require.False(t, result.IsError) textContent := getTextResult(t, result) - assert.Contains(t, textContent.Text, "missing required parameter") + var response github.WorkflowRuns + err = json.Unmarshal([]byte(textContent.Text), &response) + require.NoError(t, err) + assert.Equal(t, 2, *response.TotalCount) }) } From 73a8f98bdcefad5d6cb9c351a178535b49cfc097 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 29 Dec 2025 16:22:33 +0000 Subject: [PATCH 003/138] build(deps): bump actions/github-script from 7 to 8 Bumps [actions/github-script](https://github.com/actions/github-script) from 7 to 8. - [Release notes](https://github.com/actions/github-script/releases) - [Commits](https://github.com/actions/github-script/compare/v7...v8) --- updated-dependencies: - dependency-name: actions/github-script dependency-version: '8' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/license-check.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/license-check.yml b/.github/workflows/license-check.yml index ce2fa26fb..916eb5f28 100644 --- a/.github/workflows/license-check.yml +++ b/.github/workflows/license-check.yml @@ -63,7 +63,7 @@ jobs: - name: Check if already commented if: steps.changes.outcome == 'failure' && steps.push.outcome == 'failure' id: check_comment - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | const { data: comments } = await github.rest.issues.listComments({ @@ -81,7 +81,7 @@ jobs: - name: Comment with instructions if cannot push if: steps.changes.outcome == 'failure' && steps.push.outcome == 'failure' && steps.check_comment.outputs.already_commented == 'false' - uses: actions/github-script@v7 + uses: actions/github-script@v8 with: script: | await github.rest.issues.createComment({ From af5a6dff42ad2f7349e76e1239297c7a00614deb Mon Sep 17 00:00:00 2001 From: s-sanjay <7111850+s-sanjay@users.noreply.github.com> Date: Sun, 28 Dec 2025 21:51:06 +0530 Subject: [PATCH 004/138] Update README.md to hyperlink Open AI Codex installation guide This guide was already added as part of https://github.com/github/github-mcp-server/pull/1340. Update README.md to point to this --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 9376b6c09..0f17b0a3a 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ Alternatively, to manually configure VS Code, choose the appropriate JSON block ### Install in other MCP hosts - **[GitHub Copilot in other IDEs](/docs/installation-guides/install-other-copilot-ides.md)** - Installation for JetBrains, Visual Studio, Eclipse, and Xcode with GitHub Copilot - **[Claude Applications](/docs/installation-guides/install-claude.md)** - Installation guide for Claude Desktop and Claude Code CLI +- **[Codex](/docs/installation-guides/install-codex.md)** - Installation guide for Open AI Codex - **[Cursor](/docs/installation-guides/install-cursor.md)** - Installation guide for Cursor IDE - **[Windsurf](/docs/installation-guides/install-windsurf.md)** - Installation guide for Windsurf IDE From 587d829a1b2d7b53654fc261fcda69f4616f6ba5 Mon Sep 17 00:00:00 2001 From: majiayu000 <1835304752@qq.com> Date: Tue, 30 Dec 2025 21:25:34 +0800 Subject: [PATCH 005/138] docs: add Docker image name to Prerequisites section Add explicit Docker image URL (ghcr.io/github/github-mcp-server) to the Prerequisites section for better discoverability. Fixes #1505 Signed-off-by: majiayu000 <1835304752@qq.com> --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 0f17b0a3a..8ab95c8dc 100644 --- a/README.md +++ b/README.md @@ -132,7 +132,7 @@ GitHub Enterprise Server does not support remote server hosting. Please refer to ### Prerequisites 1. To run the server in a container, you will need to have [Docker](https://www.docker.com/) installed. -2. Once Docker is installed, you will also need to ensure Docker is running. The image is public; if you get errors on pull, you may have an expired token and need to `docker logout ghcr.io`. +2. Once Docker is installed, you will also need to ensure Docker is running. The Docker image is available at `ghcr.io/github/github-mcp-server`. The image is public; if you get errors on pull, you may have an expired token and need to `docker logout ghcr.io`. 3. Lastly you will need to [Create a GitHub Personal Access Token](https://github.com/settings/personal-access-tokens/new). The MCP server can use many of the GitHub APIs, so enable the permissions that you feel comfortable granting your AI tools (to learn more about access tokens, please check out the [documentation](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens)). From 953d26f9c082dff842073f57fbc6207cbc918bf8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 31 Dec 2025 18:39:44 +0000 Subject: [PATCH 006/138] fix: use gh pr checkout to handle fork PRs in license-check workflow Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com> --- .github/workflows/license-check.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/license-check.yml b/.github/workflows/license-check.yml index 916eb5f28..940773275 100644 --- a/.github/workflows/license-check.yml +++ b/.github/workflows/license-check.yml @@ -25,8 +25,12 @@ jobs: steps: - name: Check out code uses: actions/checkout@v6 - with: - ref: ${{ github.head_ref }} + + # Check out the actual PR branch so we can push changes back if needed + - name: Check out PR branch + env: + GH_TOKEN: ${{ github.token }} + run: gh pr checkout ${{ github.event.pull_request.number }} - name: Set up Go uses: actions/setup-go@v6 From 2cc6911d4b389736f2683477bff87624289acb73 Mon Sep 17 00:00:00 2001 From: majiayu000 <1835304752@qq.com> Date: Tue, 30 Dec 2025 21:31:24 +0800 Subject: [PATCH 007/138] refactor: use consistent snake_case for issue_number parameter Change the parameter name in assign_copilot_to_issue tool from 'issueNumber' (camelCase) to 'issue_number' (snake_case) to match the naming convention used by all other tools in the issues toolset. This improves API consistency and makes the tool parameters more predictable for users and AI models. Fixes #1239 Signed-off-by: majiayu000 <1835304752@qq.com> --- .../assign_copilot_to_issue.snap | 14 +++++----- pkg/github/issues.go | 10 +++---- pkg/github/issues_test.go | 28 +++++++++---------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/pkg/github/__toolsnaps__/assign_copilot_to_issue.snap b/pkg/github/__toolsnaps__/assign_copilot_to_issue.snap index 22c380055..354600147 100644 --- a/pkg/github/__toolsnaps__/assign_copilot_to_issue.snap +++ b/pkg/github/__toolsnaps__/assign_copilot_to_issue.snap @@ -6,13 +6,8 @@ "description": "Assign Copilot to a specific issue in a GitHub repository.\n\nThis tool can help with the following outcomes:\n- a Pull Request created with source code changes to resolve the issue\n\n\nMore information can be found at:\n- https://docs.github.com/en/copilot/using-github-copilot/using-copilot-coding-agent-to-work-on-tasks/about-assigning-tasks-to-copilot\n", "inputSchema": { "type": "object", - "required": [ - "owner", - "repo", - "issueNumber" - ], "properties": { - "issueNumber": { + "issue_number": { "type": "number", "description": "Issue number" }, @@ -24,7 +19,12 @@ "type": "string", "description": "Repository name" } - } + }, + "required": [ + "owner", + "repo", + "issue_number" + ] }, "name": "assign_copilot_to_issue", "icons": [ diff --git a/pkg/github/issues.go b/pkg/github/issues.go index f06dc2d9d..626e2c2e9 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -1626,19 +1626,19 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server Type: "string", Description: "Repository name", }, - "issueNumber": { + "issue_number": { Type: "number", Description: "Issue number", }, }, - Required: []string{"owner", "repo", "issueNumber"}, + Required: []string{"owner", "repo", "issue_number"}, }, }, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { var params struct { - Owner string - Repo string - IssueNumber int32 + Owner string `mapstructure:"owner"` + Repo string `mapstructure:"repo"` + IssueNumber int32 `mapstructure:"issue_number"` } if err := mapstructure.Decode(args, ¶ms); err != nil { return utils.NewToolResultError(err.Error()), nil, nil diff --git a/pkg/github/issues_test.go b/pkg/github/issues_test.go index b810cede3..694b991dc 100644 --- a/pkg/github/issues_test.go +++ b/pkg/github/issues_test.go @@ -2175,8 +2175,8 @@ func TestAssignCopilotToIssue(t *testing.T) { assert.NotEmpty(t, tool.Description) assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "owner") assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "repo") - assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "issueNumber") - assert.ElementsMatch(t, tool.InputSchema.(*jsonschema.Schema).Required, []string{"owner", "repo", "issueNumber"}) + assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "issue_number") + assert.ElementsMatch(t, tool.InputSchema.(*jsonschema.Schema).Required, []string{"owner", "repo", "issue_number"}) var pageOfFakeBots = func(n int) []struct{} { // We don't _really_ need real bots here, just objects that count as entries for the page @@ -2197,9 +2197,9 @@ func TestAssignCopilotToIssue(t *testing.T) { { name: "successful assignment when there are no existing assignees", requestArgs: map[string]any{ - "owner": "owner", - "repo": "repo", - "issueNumber": float64(123), + "owner": "owner", + "repo": "repo", + "issue_number": float64(123), }, mockedClient: githubv4mock.NewMockedHTTPClient( githubv4mock.NewQueryMatcher( @@ -2286,9 +2286,9 @@ func TestAssignCopilotToIssue(t *testing.T) { { name: "successful assignment when there are existing assignees", requestArgs: map[string]any{ - "owner": "owner", - "repo": "repo", - "issueNumber": float64(123), + "owner": "owner", + "repo": "repo", + "issue_number": float64(123), }, mockedClient: githubv4mock.NewMockedHTTPClient( githubv4mock.NewQueryMatcher( @@ -2386,9 +2386,9 @@ func TestAssignCopilotToIssue(t *testing.T) { { name: "copilot bot not on first page of suggested actors", requestArgs: map[string]any{ - "owner": "owner", - "repo": "repo", - "issueNumber": float64(123), + "owner": "owner", + "repo": "repo", + "issue_number": float64(123), }, mockedClient: githubv4mock.NewMockedHTTPClient( // First page of suggested actors @@ -2512,9 +2512,9 @@ func TestAssignCopilotToIssue(t *testing.T) { { name: "copilot not a suggested actor", requestArgs: map[string]any{ - "owner": "owner", - "repo": "repo", - "issueNumber": float64(123), + "owner": "owner", + "repo": "repo", + "issue_number": float64(123), }, mockedClient: githubv4mock.NewMockedHTTPClient( githubv4mock.NewQueryMatcher( From 30712de5a6e313d05f822d35c36c684ddcabf97b Mon Sep 17 00:00:00 2001 From: lif <1835304752@qq.com> Date: Wed, 31 Dec 2025 23:28:45 +0800 Subject: [PATCH 008/138] docs: regenerate README after parameter rename Update auto-generated documentation to reflect the issueNumber -> issue_number parameter rename in assign_copilot_to_issue tool. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8ab95c8dc..29795dc8f 100644 --- a/README.md +++ b/README.md @@ -758,7 +758,7 @@ The following sets of tools are available: - `repo`: Repository name (string, required) - **assign_copilot_to_issue** - Assign Copilot to issue - - `issueNumber`: Issue number (number, required) + - `issue_number`: Issue number (number, required) - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) From 762845a8b74ca0e3c6f84ae1f1c25c4ee6a0dd1a Mon Sep 17 00:00:00 2001 From: Adam Holt Date: Fri, 2 Jan 2026 15:19:06 +0100 Subject: [PATCH 009/138] Add API Error annotations to GitHub issue errors (#1566) * Add API Error annotations to GitHub issue errors * Return an error back. --------- Co-authored-by: Matt Holloway --- pkg/errors/error.go | 8 ++++++++ pkg/github/issues.go | 16 ++++++++++++---- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/pkg/errors/error.go b/pkg/errors/error.go index d17fedd92..93ea852a8 100644 --- a/pkg/errors/error.go +++ b/pkg/errors/error.go @@ -120,6 +120,14 @@ func NewGitHubAPIErrorToCtx(ctx context.Context, message string, resp *github.Re return ctx, nil } +func NewGitHubGraphQLErrorToCtx(ctx context.Context, message string, err error) (context.Context, error) { + graphQLErr := newGitHubGraphQLError(message, err) + if ctx != nil { + _, _ = addGitHubGraphQLErrorToContext(ctx, graphQLErr) // Explicitly ignore error for graceful handling + } + return ctx, nil +} + func addGitHubAPIErrorToContext(ctx context.Context, err *GitHubAPIError) (context.Context, error) { if val, ok := ctx.Value(GitHubErrorKey{}).(*GitHubCtxErrors); ok { val.api = append(val.api, err) // append the error to the existing slice in the context diff --git a/pkg/github/issues.go b/pkg/github/issues.go index 626e2c2e9..23d16b172 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -1175,7 +1175,11 @@ func CreateIssue(ctx context.Context, client *github.Client, owner string, repo issue, resp, err := client.Issues.Create(ctx, owner, repo, issueRequest) if err != nil { - return utils.NewToolResultErrorFromErr("failed to create issue", err), nil + return ghErrors.NewGitHubAPIErrorResponse(ctx, + "failed to create issue", + resp, + err, + ), nil } defer func() { _ = resp.Body.Close() }() @@ -1522,7 +1526,11 @@ func ListIssues(t translations.TranslationHelperFunc) inventory.ServerTool { issueQuery := getIssueQueryType(hasLabels, hasSince) if err := client.Query(ctx, issueQuery, vars); err != nil { - return utils.NewToolResultError(err.Error()), nil, nil + return ghErrors.NewGitHubGraphQLErrorResponse( + ctx, + "failed to list issues", + err, + ), nil, nil } // Extract and convert all issue nodes using the common interface @@ -1683,7 +1691,7 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server var query suggestedActorsQuery err := client.Query(ctx, &query, variables) if err != nil { - return nil, nil, err + return ghErrors.NewGitHubGraphQLErrorResponse(ctx, "failed to get suggested actors", err), nil, nil } // Iterate all the returned nodes looking for the copilot bot, which is supposed to have the @@ -1729,7 +1737,7 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server } if err := client.Query(ctx, &getIssueQuery, variables); err != nil { - return utils.NewToolResultError(fmt.Sprintf("failed to get issue ID: %v", err)), nil, nil + return ghErrors.NewGitHubGraphQLErrorResponse(ctx, "failed to get issue ID", err), nil, nil } // Finally, do the assignment. Just for reference, assigning copilot to an issue that it is already From 92bdc286bb60aea0548b0de9e4e7ca625f394132 Mon Sep 17 00:00:00 2001 From: Kun Chen Date: Wed, 13 Aug 2025 13:39:11 -0700 Subject: [PATCH 010/138] add docs for Rovo Dev CLI installation --- README.md | 1 + .../install-rovo-dev-cli.md | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 docs/installation-guides/install-rovo-dev-cli.md diff --git a/README.md b/README.md index 29795dc8f..f2ef810ce 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,7 @@ Alternatively, to manually configure VS Code, choose the appropriate JSON block - **[Codex](/docs/installation-guides/install-codex.md)** - Installation guide for Open AI Codex - **[Cursor](/docs/installation-guides/install-cursor.md)** - Installation guide for Cursor IDE - **[Windsurf](/docs/installation-guides/install-windsurf.md)** - Installation guide for Windsurf IDE +- **[Rovo Dev CLI](/docs/installation-guides/install-rovo-dev-cli.md)** - Installation guide for Rovo Dev CLI > **Note:** Each MCP host application needs to configure a GitHub App or OAuth App to support remote access via OAuth. Any host application that supports remote MCP servers should support the remote GitHub server with PAT authentication. Configuration details and support levels vary by host. Make sure to refer to the host application's documentation for more info. diff --git a/docs/installation-guides/install-rovo-dev-cli.md b/docs/installation-guides/install-rovo-dev-cli.md new file mode 100644 index 000000000..e6660bfe4 --- /dev/null +++ b/docs/installation-guides/install-rovo-dev-cli.md @@ -0,0 +1,32 @@ +# Install GitHub MCP Server in Rovo Dev CLI + +## Prerequisites + +1. Rovo Dev CLI installed (latest version) +2. [GitHub Personal Access Token](https://github.com/settings/personal-access-tokens/new) with appropriate scopes + +## MCP Server Setup + +Uses GitHub's hosted server at https://api.githubcopilot.com/mcp/. + +### Install steps + +1. Run `acli rovodev mcp` to open the MCP configuration for Rovo Dev CLI +2. Add configuration by following example below. +3. Replace `YOUR_GITHUB_PAT` with your actual [GitHub Personal Access Token](https://github.com/settings/tokens) +4. Save the file and restart Rovo Dev CLI with `acli rovodev` + +### Example configuration + +```json +{ + "mcpServers": { + "github": { + "url": "https://api.githubcopilot.com/mcp/", + "headers": { + "Authorization": "Bearer YOUR_GITHUB_PAT" + } + } + } +} +``` From 116c5742553ca36fc09ba683aa42a9fdfd25ff89 Mon Sep 17 00:00:00 2001 From: majiayu000 <1835304752@qq.com> Date: Fri, 2 Jan 2026 07:59:32 +0800 Subject: [PATCH 011/138] fix: filterToolsByName returns all matching tools for feature flag filtering When multiple tools share the same name but have different feature flags (like GetJobLogs and ActionsGetJobLogs both named "get_job_logs"), filterToolsByName was only returning the first match. This caused the remote server to fail with "unknown tool" error when the first matching tool was disabled by feature flags, even though another variant was enabled. The fix modifies filterToolsByName to return ALL tools with matching names, allowing the feature flag filtering in AvailableTools to select the correct variant based on the enabled flags. Fixes #1714 Signed-off-by: majiayu000 <1835304752@qq.com> --- pkg/inventory/filters.go | 15 +++++++++--- pkg/inventory/registry_test.go | 45 ++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/pkg/inventory/filters.go b/pkg/inventory/filters.go index 991001a64..c5156e61a 100644 --- a/pkg/inventory/filters.go +++ b/pkg/inventory/filters.go @@ -178,22 +178,29 @@ func (r *Inventory) AvailablePrompts(ctx context.Context) []ServerPrompt { // filterToolsByName returns tools matching the given name, checking deprecated aliases. // Uses linear scan - optimized for single-lookup per-request scenarios (ForMCPRequest). +// Returns ALL tools matching the name to support feature-flagged tool variants +// (e.g., GetJobLogs and ActionsGetJobLogs both use name "get_job_logs" but are +// controlled by different feature flags). func (r *Inventory) filterToolsByName(name string) []ServerTool { - // First check for exact match + var result []ServerTool + // Check for exact matches - multiple tools may share the same name with different feature flags for i := range r.tools { if r.tools[i].Tool.Name == name { - return []ServerTool{r.tools[i]} + result = append(result, r.tools[i]) } } + if len(result) > 0 { + return result + } // Check if name is a deprecated alias if canonical, isAlias := r.deprecatedAliases[name]; isAlias { for i := range r.tools { if r.tools[i].Tool.Name == canonical { - return []ServerTool{r.tools[i]} + result = append(result, r.tools[i]) } } } - return []ServerTool{} + return result } // filterResourcesByURI returns resource templates matching the given URI pattern. diff --git a/pkg/inventory/registry_test.go b/pkg/inventory/registry_test.go index 41e94b8d9..742ad3646 100644 --- a/pkg/inventory/registry_test.go +++ b/pkg/inventory/registry_test.go @@ -1643,3 +1643,48 @@ func TestFilteringOrder(t *testing.T) { } } } + +func TestForMCPRequest_ToolsCall_FeatureFlaggedVariants(t *testing.T) { + // Simulate the get_job_logs scenario: two tools with the same name but different feature flags + // - "get_job_logs" with FeatureFlagDisable (available when flag is OFF) + // - "get_job_logs" with FeatureFlagEnable (available when flag is ON) + tools := []ServerTool{ + mockToolWithFlags("get_job_logs", "actions", true, "", "consolidated_flag"), // disabled when flag is ON + mockToolWithFlags("get_job_logs", "actions", true, "consolidated_flag", ""), // enabled when flag is ON + mockTool("other_tool", "actions", true), + } + + // Test 1: Flag is OFF - first tool variant should be available + regFlagOff := NewBuilder(). + SetTools(tools). + WithToolsets([]string{"all"}). + Build() + filteredOff := regFlagOff.ForMCPRequest(MCPMethodToolsCall, "get_job_logs") + availableOff := filteredOff.AvailableTools(context.Background()) + if len(availableOff) != 1 { + t.Fatalf("Flag OFF: Expected 1 tool, got %d", len(availableOff)) + } + if availableOff[0].FeatureFlagDisable != "consolidated_flag" { + t.Errorf("Flag OFF: Expected tool with FeatureFlagDisable, got FeatureFlagEnable=%q, FeatureFlagDisable=%q", + availableOff[0].FeatureFlagEnable, availableOff[0].FeatureFlagDisable) + } + + // Test 2: Flag is ON - second tool variant should be available + checker := func(_ context.Context, flag string) (bool, error) { + return flag == "consolidated_flag", nil + } + regFlagOn := NewBuilder(). + SetTools(tools). + WithToolsets([]string{"all"}). + WithFeatureChecker(checker). + Build() + filteredOn := regFlagOn.ForMCPRequest(MCPMethodToolsCall, "get_job_logs") + availableOn := filteredOn.AvailableTools(context.Background()) + if len(availableOn) != 1 { + t.Fatalf("Flag ON: Expected 1 tool, got %d", len(availableOn)) + } + if availableOn[0].FeatureFlagEnable != "consolidated_flag" { + t.Errorf("Flag ON: Expected tool with FeatureFlagEnable, got FeatureFlagEnable=%q, FeatureFlagDisable=%q", + availableOn[0].FeatureFlagEnable, availableOn[0].FeatureFlagDisable) + } +} From cac11f2d342775e0905c84ab8f81c82e377bbc7f Mon Sep 17 00:00:00 2001 From: Matt Holloway Date: Fri, 2 Jan 2026 10:54:21 +0000 Subject: [PATCH 012/138] exclude tools requiring ff from docs --- README.md | 43 -------------------------- cmd/github-mcp-server/generate_docs.go | 5 +-- pkg/inventory/registry.go | 27 ++++++++++++++++ 3 files changed, 30 insertions(+), 45 deletions(-) diff --git a/README.md b/README.md index f2ef810ce..af92cfd0b 100644 --- a/README.md +++ b/README.md @@ -492,40 +492,6 @@ The following sets of tools are available: workflow Actions -- **actions_get** - Get details of GitHub Actions resources (workflows, workflow runs, jobs, and artifacts) - - `method`: The method to execute (string, required) - - `owner`: Repository owner (string, required) - - `repo`: Repository name (string, required) - - `resource_id`: The unique identifier of the resource. This will vary based on the "method" provided, so ensure you provide the correct ID: - - Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'get_workflow' method. - - Provide a workflow run ID for 'get_workflow_run', 'get_workflow_run_usage', and 'get_workflow_run_logs_url' methods. - - Provide an artifact ID for 'download_workflow_run_artifact' method. - - Provide a job ID for 'get_workflow_job' method. - (string, required) - -- **actions_list** - List GitHub Actions workflows in a repository - - `method`: The action to perform (string, required) - - `owner`: Repository owner (string, required) - - `page`: Page number for pagination (default: 1) (number, optional) - - `per_page`: Results per page for pagination (default: 30, max: 100) (number, optional) - - `repo`: Repository name (string, required) - - `resource_id`: The unique identifier of the resource. This will vary based on the "method" provided, so ensure you provide the correct ID: - - Do not provide any resource ID for 'list_workflows' method. - - Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'list_workflow_runs' method, or omit to list all workflow runs in the repository. - - Provide a workflow run ID for 'list_workflow_jobs' and 'list_workflow_run_artifacts' methods. - (string, optional) - - `workflow_jobs_filter`: Filters for workflow jobs. **ONLY** used when method is 'list_workflow_jobs' (object, optional) - - `workflow_runs_filter`: Filters for workflow runs. **ONLY** used when method is 'list_workflow_runs' (object, optional) - -- **actions_run_trigger** - Trigger GitHub Actions workflow actions - - `inputs`: Inputs the workflow accepts. Only used for 'run_workflow' method. (object, optional) - - `method`: The method to execute (string, required) - - `owner`: Repository owner (string, required) - - `ref`: The git reference for the workflow. The reference can be a branch or tag name. Required for 'run_workflow' method. (string, optional) - - `repo`: Repository name (string, required) - - `run_id`: The ID of the workflow run. Required for all methods except 'run_workflow'. (number, optional) - - `workflow_id`: The workflow ID (numeric) or workflow file name (e.g., main.yml, ci.yaml). Required for 'run_workflow' method. (string, optional) - - **cancel_workflow_run** - Cancel workflow run - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) @@ -550,15 +516,6 @@ The following sets of tools are available: - `run_id`: Workflow run ID (required when using failed_only) (number, optional) - `tail_lines`: Number of lines to return from the end of the log (number, optional) -- **get_job_logs** - Get GitHub Actions workflow job logs - - `failed_only`: When true, gets logs for all failed jobs in the workflow run specified by run_id. Requires run_id to be provided. (boolean, optional) - - `job_id`: The unique identifier of the workflow job. Required when getting logs for a single job. (number, optional) - - `owner`: Repository owner (string, required) - - `repo`: Repository name (string, required) - - `return_content`: Returns actual log content instead of URLs (boolean, optional) - - `run_id`: The unique identifier of the workflow run. Required when failed_only is true to get logs for all failed jobs in the run. (number, optional) - - `tail_lines`: Number of lines to return from the end of the log (number, optional) - - **get_workflow_run** - Get workflow run - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) diff --git a/cmd/github-mcp-server/generate_docs.go b/cmd/github-mcp-server/generate_docs.go index b40e3e2f4..4be0076bc 100644 --- a/cmd/github-mcp-server/generate_docs.go +++ b/cmd/github-mcp-server/generate_docs.go @@ -153,9 +153,10 @@ func generateToolsetsDoc(i *inventory.Inventory) string { } func generateToolsDoc(r *inventory.Inventory) string { - // AllTools() returns tools sorted by toolset ID then tool name. + // AllToolsForDocs() returns tools sorted by toolset ID then tool name, + // excluding tools that require feature flags (not available to regular users). // We iterate once, grouping by toolset as we encounter them. - tools := r.AllTools() + tools := r.AllToolsForDocs() if len(tools) == 0 { return "" } diff --git a/pkg/inventory/registry.go b/pkg/inventory/registry.go index f3691e38a..d9e73370b 100644 --- a/pkg/inventory/registry.go +++ b/pkg/inventory/registry.go @@ -266,6 +266,33 @@ func (r *Inventory) AllTools() []ServerTool { return result } +// AllToolsForDocs returns tools suitable for documentation, sorted deterministically. +// This excludes tools that require a feature flag to be enabled (FeatureFlagEnable), +// since those are not available to regular users and shouldn't appear in public docs. +// Tools that are disabled by a feature flag (FeatureFlagDisable) are still included +// since they are available by default. +func (r *Inventory) AllToolsForDocs() []ServerTool { + var result []ServerTool + for i := range r.tools { + tool := &r.tools[i] + // Skip tools that require a feature flag to enable + if tool.FeatureFlagEnable != "" { + continue + } + result = append(result, *tool) + } + + // Sort deterministically: by toolset ID, then by tool name + sort.Slice(result, func(i, j int) bool { + if result[i].Toolset.ID != result[j].Toolset.ID { + return result[i].Toolset.ID < result[j].Toolset.ID + } + return result[i].Tool.Name < result[j].Tool.Name + }) + + return result +} + // AvailableToolsets returns the unique toolsets that have tools, in sorted order. // This is the ordered intersection of toolsets with reality - only toolsets that // actually contain tools are returned, sorted by toolset ID. From 6f7bf271edb61353d65d389de0ce0a1c72c10fd9 Mon Sep 17 00:00:00 2001 From: Matt Holloway Date: Fri, 2 Jan 2026 13:24:01 +0000 Subject: [PATCH 013/138] refactor docs toolset gen --- cmd/github-mcp-server/generate_docs.go | 15 ++++++++------ pkg/inventory/registry.go | 27 -------------------------- 2 files changed, 9 insertions(+), 33 deletions(-) diff --git a/cmd/github-mcp-server/generate_docs.go b/cmd/github-mcp-server/generate_docs.go index 4be0076bc..7590666b5 100644 --- a/cmd/github-mcp-server/generate_docs.go +++ b/cmd/github-mcp-server/generate_docs.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "net/url" "os" @@ -50,8 +51,10 @@ func generateReadmeDocs(readmePath string) error { // Create translation helper t, _ := translations.TranslationHelper() - // Build inventory - stateless, no dependencies needed for doc generation - r := github.NewInventory(t).Build() + // Build inventory with all toolsets enabled and no feature checker (all flags return false). + // This includes tools from all toolsets, but excludes tools with FeatureFlagEnable + // (not available to regular users) while including tools with FeatureFlagDisable. + r := github.NewInventory(t).WithToolsets([]string{"all"}).Build() // Generate toolsets documentation toolsetsDoc := generateToolsetsDoc(r) @@ -153,10 +156,10 @@ func generateToolsetsDoc(i *inventory.Inventory) string { } func generateToolsDoc(r *inventory.Inventory) string { - // AllToolsForDocs() returns tools sorted by toolset ID then tool name, - // excluding tools that require feature flags (not available to regular users). - // We iterate once, grouping by toolset as we encounter them. - tools := r.AllToolsForDocs() + // Use AvailableTools with the inventory's feature checker (returns false for all flags), + // which excludes tools requiring a feature flag (FeatureFlagEnable) while keeping + // tools that are disabled by feature flags (available by default). + tools := r.AvailableTools(context.Background()) if len(tools) == 0 { return "" } diff --git a/pkg/inventory/registry.go b/pkg/inventory/registry.go index d9e73370b..f3691e38a 100644 --- a/pkg/inventory/registry.go +++ b/pkg/inventory/registry.go @@ -266,33 +266,6 @@ func (r *Inventory) AllTools() []ServerTool { return result } -// AllToolsForDocs returns tools suitable for documentation, sorted deterministically. -// This excludes tools that require a feature flag to be enabled (FeatureFlagEnable), -// since those are not available to regular users and shouldn't appear in public docs. -// Tools that are disabled by a feature flag (FeatureFlagDisable) are still included -// since they are available by default. -func (r *Inventory) AllToolsForDocs() []ServerTool { - var result []ServerTool - for i := range r.tools { - tool := &r.tools[i] - // Skip tools that require a feature flag to enable - if tool.FeatureFlagEnable != "" { - continue - } - result = append(result, *tool) - } - - // Sort deterministically: by toolset ID, then by tool name - sort.Slice(result, func(i, j int) bool { - if result[i].Toolset.ID != result[j].Toolset.ID { - return result[i].Toolset.ID < result[j].Toolset.ID - } - return result[i].Tool.Name < result[j].Tool.Name - }) - - return result -} - // AvailableToolsets returns the unique toolsets that have tools, in sorted order. // This is the ordered intersection of toolsets with reality - only toolsets that // actually contain tools are returned, sorted by toolset ID. From 67f3427d39b9477a3693485ab7709470bd198c86 Mon Sep 17 00:00:00 2001 From: Matt Holloway Date: Fri, 2 Jan 2026 13:47:17 +0000 Subject: [PATCH 014/138] Update cmd/github-mcp-server/generate_docs.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- cmd/github-mcp-server/generate_docs.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/cmd/github-mcp-server/generate_docs.go b/cmd/github-mcp-server/generate_docs.go index 7590666b5..f7adc822a 100644 --- a/cmd/github-mcp-server/generate_docs.go +++ b/cmd/github-mcp-server/generate_docs.go @@ -51,8 +51,6 @@ func generateReadmeDocs(readmePath string) error { // Create translation helper t, _ := translations.TranslationHelper() - // Build inventory with all toolsets enabled and no feature checker (all flags return false). - // This includes tools from all toolsets, but excludes tools with FeatureFlagEnable // (not available to regular users) while including tools with FeatureFlagDisable. r := github.NewInventory(t).WithToolsets([]string{"all"}).Build() From 905a08f8fbca93fe1d2d07b2fc3ff01f7d055e71 Mon Sep 17 00:00:00 2001 From: Matt Holloway Date: Fri, 2 Jan 2026 13:47:26 +0000 Subject: [PATCH 015/138] Update cmd/github-mcp-server/generate_docs.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- cmd/github-mcp-server/generate_docs.go | 3 --- 1 file changed, 3 deletions(-) diff --git a/cmd/github-mcp-server/generate_docs.go b/cmd/github-mcp-server/generate_docs.go index f7adc822a..65c01c8fa 100644 --- a/cmd/github-mcp-server/generate_docs.go +++ b/cmd/github-mcp-server/generate_docs.go @@ -154,9 +154,6 @@ func generateToolsetsDoc(i *inventory.Inventory) string { } func generateToolsDoc(r *inventory.Inventory) string { - // Use AvailableTools with the inventory's feature checker (returns false for all flags), - // which excludes tools requiring a feature flag (FeatureFlagEnable) while keeping - // tools that are disabled by feature flags (available by default). tools := r.AvailableTools(context.Background()) if len(tools) == 0 { return "" From 2b352ab0b914cde8c16d44200a7cace9f400be71 Mon Sep 17 00:00:00 2001 From: Ksenia Bobrova Date: Mon, 5 Jan 2026 13:27:32 +0100 Subject: [PATCH 016/138] Improvements to push_files tool (#1676) * Fallback to default branch in get_file_contents when main doesn't exist * Addressing review comments * Improvements to push_files tool * Fixed copilot comments * Addressing review comments * Remove debug statement --- pkg/github/repositories.go | 329 ++++++------------------ pkg/github/repositories_helper.go | 329 ++++++++++++++++++++++++ pkg/github/repositories_test.go | 404 +++++++++++++++++++++++++++++- 3 files changed, 801 insertions(+), 261 deletions(-) create mode 100644 pkg/github/repositories_helper.go diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index c31bb7df2..1ab33a57c 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -12,7 +12,6 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" "github.com/github/github-mcp-server/pkg/octicons" - "github.com/github/github-mcp-server/pkg/raw" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -1279,28 +1278,74 @@ func PushFiles(t translations.TranslationHelperFunc) inventory.ServerTool { } // Get the reference for the branch + var repositoryIsEmpty bool + var branchNotFound bool ref, resp, err := client.Git.GetRef(ctx, owner, repo, "refs/heads/"+branch) if err != nil { - return ghErrors.NewGitHubAPIErrorResponse(ctx, - "failed to get branch reference", - resp, - err, - ), nil, nil + ghErr, isGhErr := err.(*github.ErrorResponse) + if isGhErr { + if ghErr.Response.StatusCode == http.StatusConflict && ghErr.Message == "Git Repository is empty." { + repositoryIsEmpty = true + } else if ghErr.Response.StatusCode == http.StatusNotFound { + branchNotFound = true + } + } + + if !repositoryIsEmpty && !branchNotFound { + return ghErrors.NewGitHubAPIErrorResponse(ctx, + "failed to get branch reference", + resp, + err, + ), nil, nil + } + } + // Only close resp if it's not nil and not an error case where resp might be nil + if resp != nil && resp.Body != nil { + defer func() { _ = resp.Body.Close() }() } - defer func() { _ = resp.Body.Close() }() - // Get the commit object that the branch points to - baseCommit, resp, err := client.Git.GetCommit(ctx, owner, repo, *ref.Object.SHA) - if err != nil { - return ghErrors.NewGitHubAPIErrorResponse(ctx, - "failed to get base commit", - resp, - err, - ), nil, nil + var baseCommit *github.Commit + if !repositoryIsEmpty { + if branchNotFound { + ref, err = createReferenceFromDefaultBranch(ctx, client, owner, repo, branch) + if err != nil { + return utils.NewToolResultError(fmt.Sprintf("failed to create branch from default: %v", err)), nil, nil + } + } + + // Get the commit object that the branch points to + baseCommit, resp, err = client.Git.GetCommit(ctx, owner, repo, *ref.Object.SHA) + if err != nil { + return ghErrors.NewGitHubAPIErrorResponse(ctx, + "failed to get base commit", + resp, + err, + ), nil, nil + } + if resp != nil && resp.Body != nil { + defer func() { _ = resp.Body.Close() }() + } + } else { + var base *github.Commit + // Repository is empty, need to initialize it first + ref, base, err = initializeRepository(ctx, client, owner, repo) + if err != nil { + return utils.NewToolResultError(fmt.Sprintf("failed to initialize repository: %v", err)), nil, nil + } + + defaultBranch := strings.TrimPrefix(*ref.Ref, "refs/heads/") + if branch != defaultBranch { + // Create the requested branch from the default branch + ref, err = createReferenceFromDefaultBranch(ctx, client, owner, repo, branch) + if err != nil { + return utils.NewToolResultError(fmt.Sprintf("failed to create branch from default: %v", err)), nil, nil + } + } + + baseCommit = base } - defer func() { _ = resp.Body.Close() }() - // Create tree entries for all files + // Create tree entries for all files (or remaining files if empty repo) var entries []*github.TreeEntry for _, file := range filesObj { @@ -1328,7 +1373,7 @@ func PushFiles(t translations.TranslationHelperFunc) inventory.ServerTool { }) } - // Create a new tree with the file entries + // Create a new tree with the file entries (baseCommit is now guaranteed to exist) newTree, resp, err := client.Git.CreateTree(ctx, owner, repo, *baseCommit.Tree.SHA, entries) if err != nil { return ghErrors.NewGitHubAPIErrorResponse(ctx, @@ -1337,9 +1382,11 @@ func PushFiles(t translations.TranslationHelperFunc) inventory.ServerTool { err, ), nil, nil } - defer func() { _ = resp.Body.Close() }() + if resp != nil && resp.Body != nil { + defer func() { _ = resp.Body.Close() }() + } - // Create a new commit + // Create a new commit (baseCommit always has a value now) commit := github.Commit{ Message: github.Ptr(message), Tree: newTree, @@ -1353,7 +1400,9 @@ func PushFiles(t translations.TranslationHelperFunc) inventory.ServerTool { err, ), nil, nil } - defer func() { _ = resp.Body.Close() }() + if resp != nil && resp.Body != nil { + defer func() { _ = resp.Body.Close() }() + } // Update the reference to point to the new commit ref.Object.SHA = newCommit.SHA @@ -1770,244 +1819,6 @@ func GetReleaseByTag(t translations.TranslationHelperFunc) inventory.ServerTool ) } -// matchFiles searches for files in the Git tree that match the given path. -// It's used when GetContents fails or returns unexpected results. -func matchFiles(ctx context.Context, client *github.Client, owner, repo, ref, path string, rawOpts *raw.ContentOpts, rawAPIResponseCode int) (*mcp.CallToolResult, any, error) { - // Step 1: Get Git Tree recursively - tree, response, err := client.Git.GetTree(ctx, owner, repo, ref, true) - if err != nil { - return ghErrors.NewGitHubAPIErrorResponse(ctx, - "failed to get git tree", - response, - err, - ), nil, nil - } - defer func() { _ = response.Body.Close() }() - - // Step 2: Filter tree for matching paths - const maxMatchingFiles = 3 - matchingFiles := filterPaths(tree.Entries, path, maxMatchingFiles) - if len(matchingFiles) > 0 { - matchingFilesJSON, err := json.Marshal(matchingFiles) - if err != nil { - return utils.NewToolResultError(fmt.Sprintf("failed to marshal matching files: %s", err)), nil, nil - } - resolvedRefs, err := json.Marshal(rawOpts) - if err != nil { - return utils.NewToolResultError(fmt.Sprintf("failed to marshal resolved refs: %s", err)), nil, nil - } - if rawAPIResponseCode > 0 { - return utils.NewToolResultText(fmt.Sprintf("Resolved potential matches in the repository tree (resolved refs: %s, matching files: %s), but the content API returned an unexpected status code %d.", string(resolvedRefs), string(matchingFilesJSON), rawAPIResponseCode)), nil, nil - } - return utils.NewToolResultText(fmt.Sprintf("Resolved potential matches in the repository tree (resolved refs: %s, matching files: %s).", string(resolvedRefs), string(matchingFilesJSON))), nil, nil - } - return utils.NewToolResultError("Failed to get file contents. The path does not point to a file or directory, or the file does not exist in the repository."), nil, nil -} - -// filterPaths filters the entries in a GitHub tree to find paths that -// match the given suffix. -// maxResults limits the number of results returned to first maxResults entries, -// a maxResults of -1 means no limit. -// It returns a slice of strings containing the matching paths. -// Directories are returned with a trailing slash. -func filterPaths(entries []*github.TreeEntry, path string, maxResults int) []string { - // Remove trailing slash for matching purposes, but flag whether we - // only want directories. - dirOnly := false - if strings.HasSuffix(path, "/") { - dirOnly = true - path = strings.TrimSuffix(path, "/") - } - - matchedPaths := []string{} - for _, entry := range entries { - if len(matchedPaths) == maxResults { - break // Limit the number of results to maxResults - } - if dirOnly && entry.GetType() != "tree" { - continue // Skip non-directory entries if dirOnly is true - } - entryPath := entry.GetPath() - if entryPath == "" { - continue // Skip empty paths - } - if strings.HasSuffix(entryPath, path) { - if entry.GetType() == "tree" { - entryPath += "/" // Return directories with a trailing slash - } - matchedPaths = append(matchedPaths, entryPath) - } - } - return matchedPaths -} - -// looksLikeSHA returns true if the string appears to be a Git commit SHA. -// A SHA is a 40-character hexadecimal string. -func looksLikeSHA(s string) bool { - if len(s) != 40 { - return false - } - for _, c := range s { - if (c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F') { - return false - } - } - return true -} - -// resolveGitReference takes a user-provided ref and sha and resolves them into a -// definitive commit SHA and its corresponding fully-qualified reference. -// -// The resolution logic follows a clear priority: -// -// 1. If a specific commit `sha` is provided, it takes precedence and is used directly, -// and all reference resolution is skipped. -// -// 1a. If `sha` is empty but `ref` looks like a commit SHA (40 hexadecimal characters), -// it is returned as-is without any API calls or reference resolution. -// -// 2. If no `sha` is provided and `ref` does not look like a SHA, the function resolves -// the `ref` string into a fully-qualified format (e.g., "refs/heads/main") by trying -// the following steps in order: -// a). **Empty Ref:** If `ref` is empty, the repository's default branch is used. -// b). **Fully-Qualified:** If `ref` already starts with "refs/", it's considered fully -// qualified and used as-is. -// c). **Partially-Qualified:** If `ref` starts with "heads/" or "tags/", it is -// prefixed with "refs/" to make it fully-qualified. -// d). **Short Name:** Otherwise, the `ref` is treated as a short name. The function -// first attempts to resolve it as a branch ("refs/heads/"). If that -// returns a 404 Not Found error, it then attempts to resolve it as a tag -// ("refs/tags/"). -// -// 3. **Final Lookup:** Once a fully-qualified ref is determined, a final API call -// is made to fetch that reference's definitive commit SHA. -// -// Any unexpected (non-404) errors during the resolution process are returned -// immediately. All API errors are logged with rich context to aid diagnostics. -func resolveGitReference(ctx context.Context, githubClient *github.Client, owner, repo, ref, sha string) (*raw.ContentOpts, bool, error) { - // 1) If SHA explicitly provided, it's the highest priority. - if sha != "" { - return &raw.ContentOpts{Ref: "", SHA: sha}, false, nil - } - - // 1a) If sha is empty but ref looks like a SHA, return it without changes - if looksLikeSHA(ref) { - return &raw.ContentOpts{Ref: "", SHA: ref}, false, nil - } - - originalRef := ref // Keep original ref for clearer error messages down the line. - - // 2) If no SHA is provided, we try to resolve the ref into a fully-qualified format. - var reference *github.Reference - var resp *github.Response - var err error - var fallbackUsed bool - - switch { - case originalRef == "": - // 2a) If ref is empty, determine the default branch. - reference, err = resolveDefaultBranch(ctx, githubClient, owner, repo) - if err != nil { - return nil, false, err // Error is already wrapped in resolveDefaultBranch. - } - ref = reference.GetRef() - case strings.HasPrefix(originalRef, "refs/"): - // 2b) Already fully qualified. The reference will be fetched at the end. - case strings.HasPrefix(originalRef, "heads/") || strings.HasPrefix(originalRef, "tags/"): - // 2c) Partially qualified. Make it fully qualified. - ref = "refs/" + originalRef - default: - // 2d) It's a short name, so we try to resolve it to either a branch or a tag. - branchRef := "refs/heads/" + originalRef - reference, resp, err = githubClient.Git.GetRef(ctx, owner, repo, branchRef) - - if err == nil { - ref = branchRef // It's a branch. - } else { - // The branch lookup failed. Check if it was a 404 Not Found error. - ghErr, isGhErr := err.(*github.ErrorResponse) - if isGhErr && ghErr.Response.StatusCode == http.StatusNotFound { - tagRef := "refs/tags/" + originalRef - reference, resp, err = githubClient.Git.GetRef(ctx, owner, repo, tagRef) - if err == nil { - ref = tagRef // It's a tag. - } else { - // The tag lookup also failed. Check if it was a 404 Not Found error. - ghErr2, isGhErr2 := err.(*github.ErrorResponse) - if isGhErr2 && ghErr2.Response.StatusCode == http.StatusNotFound { - if originalRef == "main" { - reference, err = resolveDefaultBranch(ctx, githubClient, owner, repo) - if err != nil { - return nil, false, err // Error is already wrapped in resolveDefaultBranch. - } - // Update ref to the actual default branch ref so the note can be generated - ref = reference.GetRef() - fallbackUsed = true - break - } - return nil, false, fmt.Errorf("could not resolve ref %q as a branch or a tag", originalRef) - } - - // The tag lookup failed for a different reason. - _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get reference (tag)", resp, err) - return nil, false, fmt.Errorf("failed to get reference for tag '%s': %w", originalRef, err) - } - } else { - // The branch lookup failed for a different reason. - _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get reference (branch)", resp, err) - return nil, false, fmt.Errorf("failed to get reference for branch '%s': %w", originalRef, err) - } - } - } - - if reference == nil { - reference, resp, err = githubClient.Git.GetRef(ctx, owner, repo, ref) - if err != nil { - if ref == "refs/heads/main" { - reference, err = resolveDefaultBranch(ctx, githubClient, owner, repo) - if err != nil { - return nil, false, err // Error is already wrapped in resolveDefaultBranch. - } - // Update ref to the actual default branch ref so the note can be generated - ref = reference.GetRef() - fallbackUsed = true - } else { - _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get final reference", resp, err) - return nil, false, fmt.Errorf("failed to get final reference for %q: %w", ref, err) - } - } - } - - sha = reference.GetObject().GetSHA() - return &raw.ContentOpts{Ref: ref, SHA: sha}, fallbackUsed, nil -} - -func resolveDefaultBranch(ctx context.Context, githubClient *github.Client, owner, repo string) (*github.Reference, error) { - repoInfo, resp, err := githubClient.Repositories.Get(ctx, owner, repo) - if err != nil { - _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get repository info", resp, err) - return nil, fmt.Errorf("failed to get repository info: %w", err) - } - - if resp != nil && resp.Body != nil { - _ = resp.Body.Close() - } - - defaultBranch := repoInfo.GetDefaultBranch() - - defaultRef, resp, err := githubClient.Git.GetRef(ctx, owner, repo, "heads/"+defaultBranch) - if err != nil { - _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get default branch reference", resp, err) - return nil, fmt.Errorf("failed to get default branch reference: %w", err) - } - - if resp != nil && resp.Body != nil { - defer func() { _ = resp.Body.Close() }() - } - - return defaultRef, nil -} - // ListStarredRepositories creates a tool to list starred repositories for the authenticated user or a specified user. func ListStarredRepositories(t translations.TranslationHelperFunc) inventory.ServerTool { return NewTool( diff --git a/pkg/github/repositories_helper.go b/pkg/github/repositories_helper.go new file mode 100644 index 000000000..de5065d48 --- /dev/null +++ b/pkg/github/repositories_helper.go @@ -0,0 +1,329 @@ +package github + +import ( + "context" + "encoding/json" + "fmt" + "net/http" + "strings" + + ghErrors "github.com/github/github-mcp-server/pkg/errors" + "github.com/github/github-mcp-server/pkg/raw" + "github.com/github/github-mcp-server/pkg/utils" + "github.com/google/go-github/v79/github" + "github.com/modelcontextprotocol/go-sdk/mcp" +) + +// initializeRepository creates an initial commit in an empty repository and returns the default branch ref and base commit +func initializeRepository(ctx context.Context, client *github.Client, owner, repo string) (ref *github.Reference, baseCommit *github.Commit, err error) { + // First, we need to check what the default branch in this empty repo should be: + repository, resp, err := client.Repositories.Get(ctx, owner, repo) + if err != nil { + _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get repository", resp, err) + return nil, nil, fmt.Errorf("failed to get repository: %w", err) + } + if resp != nil && resp.Body != nil { + defer func() { _ = resp.Body.Close() }() + } + + defaultBranch := repository.GetDefaultBranch() + + fileOpts := &github.RepositoryContentFileOptions{ + Message: github.Ptr("Initial commit"), + Content: []byte(""), + Branch: github.Ptr(defaultBranch), + } + + // Create an initial empty commit to create the default branch + createResp, resp, err := client.Repositories.CreateFile(ctx, owner, repo, "README.md", fileOpts) + if err != nil { + _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to create initial file", resp, err) + return nil, nil, fmt.Errorf("failed to create initial file: %w", err) + } + if resp != nil && resp.Body != nil { + defer func() { _ = resp.Body.Close() }() + } + + // Get the commit that was just created to use as base for remaining files + baseCommit, resp, err = client.Git.GetCommit(ctx, owner, repo, *createResp.Commit.SHA) + if err != nil { + _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get initial commit", resp, err) + return nil, nil, fmt.Errorf("failed to get initial commit: %w", err) + } + if resp != nil && resp.Body != nil { + defer func() { _ = resp.Body.Close() }() + } + + ref, resp, err = client.Git.GetRef(ctx, owner, repo, "refs/heads/"+defaultBranch) + if err != nil { + _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get final reference", resp, err) + return nil, nil, fmt.Errorf("failed to get branch reference after initial commit: %w", err) + } + if resp != nil && resp.Body != nil { + defer func() { _ = resp.Body.Close() }() + } + + return ref, baseCommit, nil +} + +// createReferenceFromDefaultBranch creates a new branch reference from the repository's default branch +func createReferenceFromDefaultBranch(ctx context.Context, client *github.Client, owner, repo, branch string) (*github.Reference, error) { + defaultRef, err := resolveDefaultBranch(ctx, client, owner, repo) + if err != nil { + _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to resolve default branch", nil, err) + return nil, fmt.Errorf("failed to resolve default branch: %w", err) + } + + // Create the new branch reference + createdRef, resp, err := client.Git.CreateRef(ctx, owner, repo, github.CreateRef{ + Ref: "refs/heads/" + branch, + SHA: *defaultRef.Object.SHA, + }) + if err != nil { + _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to create new branch reference", resp, err) + return nil, fmt.Errorf("failed to create new branch reference: %w", err) + } + if resp != nil && resp.Body != nil { + defer func() { _ = resp.Body.Close() }() + } + + return createdRef, nil +} + +// matchFiles searches for files in the Git tree that match the given path. +// It's used when GetContents fails or returns unexpected results. +func matchFiles(ctx context.Context, client *github.Client, owner, repo, ref, path string, rawOpts *raw.ContentOpts, rawAPIResponseCode int) (*mcp.CallToolResult, any, error) { + // Step 1: Get Git Tree recursively + tree, response, err := client.Git.GetTree(ctx, owner, repo, ref, true) + if err != nil { + return ghErrors.NewGitHubAPIErrorResponse(ctx, + "failed to get git tree", + response, + err, + ), nil, nil + } + defer func() { _ = response.Body.Close() }() + + // Step 2: Filter tree for matching paths + const maxMatchingFiles = 3 + matchingFiles := filterPaths(tree.Entries, path, maxMatchingFiles) + if len(matchingFiles) > 0 { + matchingFilesJSON, err := json.Marshal(matchingFiles) + if err != nil { + return utils.NewToolResultError(fmt.Sprintf("failed to marshal matching files: %s", err)), nil, nil + } + resolvedRefs, err := json.Marshal(rawOpts) + if err != nil { + return utils.NewToolResultError(fmt.Sprintf("failed to marshal resolved refs: %s", err)), nil, nil + } + if rawAPIResponseCode > 0 { + return utils.NewToolResultText(fmt.Sprintf("Resolved potential matches in the repository tree (resolved refs: %s, matching files: %s), but the content API returned an unexpected status code %d.", string(resolvedRefs), string(matchingFilesJSON), rawAPIResponseCode)), nil, nil + } + return utils.NewToolResultText(fmt.Sprintf("Resolved potential matches in the repository tree (resolved refs: %s, matching files: %s).", string(resolvedRefs), string(matchingFilesJSON))), nil, nil + } + return utils.NewToolResultError("Failed to get file contents. The path does not point to a file or directory, or the file does not exist in the repository."), nil, nil +} + +// filterPaths filters the entries in a GitHub tree to find paths that +// match the given suffix. +// maxResults limits the number of results returned to first maxResults entries, +// a maxResults of -1 means no limit. +// It returns a slice of strings containing the matching paths. +// Directories are returned with a trailing slash. +func filterPaths(entries []*github.TreeEntry, path string, maxResults int) []string { + // Remove trailing slash for matching purposes, but flag whether we + // only want directories. + dirOnly := false + if strings.HasSuffix(path, "/") { + dirOnly = true + path = strings.TrimSuffix(path, "/") + } + + matchedPaths := []string{} + for _, entry := range entries { + if len(matchedPaths) == maxResults { + break // Limit the number of results to maxResults + } + if dirOnly && entry.GetType() != "tree" { + continue // Skip non-directory entries if dirOnly is true + } + entryPath := entry.GetPath() + if entryPath == "" { + continue // Skip empty paths + } + if strings.HasSuffix(entryPath, path) { + if entry.GetType() == "tree" { + entryPath += "/" // Return directories with a trailing slash + } + matchedPaths = append(matchedPaths, entryPath) + } + } + return matchedPaths +} + +// looksLikeSHA returns true if the string appears to be a Git commit SHA. +// A SHA is a 40-character hexadecimal string. +func looksLikeSHA(s string) bool { + if len(s) != 40 { + return false + } + for _, c := range s { + if (c < '0' || c > '9') && (c < 'a' || c > 'f') && (c < 'A' || c > 'F') { + return false + } + } + return true +} + +// resolveGitReference takes a user-provided ref and sha and resolves them into a +// definitive commit SHA and its corresponding fully-qualified reference. +// +// The resolution logic follows a clear priority: +// +// 1. If a specific commit `sha` is provided, it takes precedence and is used directly, +// and all reference resolution is skipped. +// +// 1a. If `sha` is empty but `ref` looks like a commit SHA (40 hexadecimal characters), +// it is returned as-is without any API calls or reference resolution. +// +// 2. If no `sha` is provided and `ref` does not look like a SHA, the function resolves +// the `ref` string into a fully-qualified format (e.g., "refs/heads/main") by trying +// the following steps in order: +// a). **Empty Ref:** If `ref` is empty, the repository's default branch is used. +// b). **Fully-Qualified:** If `ref` already starts with "refs/", it's considered fully +// qualified and used as-is. +// c). **Partially-Qualified:** If `ref` starts with "heads/" or "tags/", it is +// prefixed with "refs/" to make it fully-qualified. +// d). **Short Name:** Otherwise, the `ref` is treated as a short name. The function +// first attempts to resolve it as a branch ("refs/heads/"). If that +// returns a 404 Not Found error, it then attempts to resolve it as a tag +// ("refs/tags/"). +// +// 3. **Final Lookup:** Once a fully-qualified ref is determined, a final API call +// is made to fetch that reference's definitive commit SHA. +// +// Any unexpected (non-404) errors during the resolution process are returned +// immediately. All API errors are logged with rich context to aid diagnostics. +func resolveGitReference(ctx context.Context, githubClient *github.Client, owner, repo, ref, sha string) (*raw.ContentOpts, bool, error) { + // 1) If SHA explicitly provided, it's the highest priority. + if sha != "" { + return &raw.ContentOpts{Ref: "", SHA: sha}, false, nil + } + + // 1a) If sha is empty but ref looks like a SHA, return it without changes + if looksLikeSHA(ref) { + return &raw.ContentOpts{Ref: "", SHA: ref}, false, nil + } + + originalRef := ref // Keep original ref for clearer error messages down the line. + + // 2) If no SHA is provided, we try to resolve the ref into a fully-qualified format. + var reference *github.Reference + var resp *github.Response + var err error + var fallbackUsed bool + + switch { + case originalRef == "": + // 2a) If ref is empty, determine the default branch. + reference, err = resolveDefaultBranch(ctx, githubClient, owner, repo) + if err != nil { + return nil, false, err // Error is already wrapped in resolveDefaultBranch. + } + ref = reference.GetRef() + case strings.HasPrefix(originalRef, "refs/"): + // 2b) Already fully qualified. The reference will be fetched at the end. + case strings.HasPrefix(originalRef, "heads/") || strings.HasPrefix(originalRef, "tags/"): + // 2c) Partially qualified. Make it fully qualified. + ref = "refs/" + originalRef + default: + // 2d) It's a short name, so we try to resolve it to either a branch or a tag. + branchRef := "refs/heads/" + originalRef + reference, resp, err = githubClient.Git.GetRef(ctx, owner, repo, branchRef) + + if err == nil { + ref = branchRef // It's a branch. + } else { + // The branch lookup failed. Check if it was a 404 Not Found error. + ghErr, isGhErr := err.(*github.ErrorResponse) + if isGhErr && ghErr.Response.StatusCode == http.StatusNotFound { + tagRef := "refs/tags/" + originalRef + reference, resp, err = githubClient.Git.GetRef(ctx, owner, repo, tagRef) + if err == nil { + ref = tagRef // It's a tag. + } else { + // The tag lookup also failed. Check if it was a 404 Not Found error. + ghErr2, isGhErr2 := err.(*github.ErrorResponse) + if isGhErr2 && ghErr2.Response.StatusCode == http.StatusNotFound { + if originalRef == "main" { + reference, err = resolveDefaultBranch(ctx, githubClient, owner, repo) + if err != nil { + return nil, false, err // Error is already wrapped in resolveDefaultBranch. + } + // Update ref to the actual default branch ref so the note can be generated + ref = reference.GetRef() + fallbackUsed = true + break + } + return nil, false, fmt.Errorf("could not resolve ref %q as a branch or a tag", originalRef) + } + + // The tag lookup failed for a different reason. + _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get reference (tag)", resp, err) + return nil, false, fmt.Errorf("failed to get reference for tag '%s': %w", originalRef, err) + } + } else { + // The branch lookup failed for a different reason. + _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get reference (branch)", resp, err) + return nil, false, fmt.Errorf("failed to get reference for branch '%s': %w", originalRef, err) + } + } + } + + if reference == nil { + reference, resp, err = githubClient.Git.GetRef(ctx, owner, repo, ref) + if err != nil { + if ref == "refs/heads/main" { + reference, err = resolveDefaultBranch(ctx, githubClient, owner, repo) + if err != nil { + return nil, false, err // Error is already wrapped in resolveDefaultBranch. + } + // Update ref to the actual default branch ref so the note can be generated + ref = reference.GetRef() + fallbackUsed = true + } else { + _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get final reference", resp, err) + return nil, false, fmt.Errorf("failed to get final reference for %q: %w", ref, err) + } + } + } + + sha = reference.GetObject().GetSHA() + return &raw.ContentOpts{Ref: ref, SHA: sha}, fallbackUsed, nil +} + +func resolveDefaultBranch(ctx context.Context, githubClient *github.Client, owner, repo string) (*github.Reference, error) { + repoInfo, resp, err := githubClient.Repositories.Get(ctx, owner, repo) + if err != nil { + _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get repository info", resp, err) + return nil, fmt.Errorf("failed to get repository info: %w", err) + } + + if resp != nil && resp.Body != nil { + _ = resp.Body.Close() + } + + defaultBranch := repoInfo.GetDefaultBranch() + + defaultRef, resp, err := githubClient.Git.GetRef(ctx, owner, repo, "heads/"+defaultBranch) + if err != nil { + _, _ = ghErrors.NewGitHubAPIErrorToCtx(ctx, "failed to get default branch reference", resp, err) + return nil, fmt.Errorf("failed to get default branch reference: %w", err) + } + + if resp != nil && resp.Body != nil { + defer func() { _ = resp.Body.Close() }() + } + + return defaultRef, nil +} diff --git a/pkg/github/repositories_test.go b/pkg/github/repositories_test.go index 8b5dab098..1e81d8c53 100644 --- a/pkg/github/repositories_test.go +++ b/pkg/github/repositories_test.go @@ -2,6 +2,7 @@ package github import ( "context" + "encoding/base64" "encoding/json" "net/http" "net/url" @@ -1883,6 +1884,11 @@ func Test_PushFiles(t *testing.T) { mock.GetReposGitRefByOwnerByRepoByRef, mockResponse(t, http.StatusNotFound, nil), ), + // Mock Repositories.Get to fail when trying to create branch from default + mock.WithRequestMatchHandler( + mock.GetReposByOwnerByRepo, + mockResponse(t, http.StatusNotFound, nil), + ), ), requestArgs: map[string]interface{}{ "owner": "owner", @@ -1896,8 +1902,8 @@ func Test_PushFiles(t *testing.T) { }, "message": "Update file", }, - expectError: true, - expectedErrMsg: "failed to get branch reference", + expectError: false, + expectedErrMsg: "failed to create branch from default", }, { name: "fails to get base commit", @@ -1962,6 +1968,400 @@ func Test_PushFiles(t *testing.T) { expectError: true, expectedErrMsg: "failed to create tree", }, + { + name: "successful push to empty repository", + mockedClient: mock.NewMockedHTTPClient( + // Get branch reference - first returns 409 for empty repo, second returns success after init + mock.WithRequestMatchHandler( + mock.GetReposGitRefByOwnerByRepoByRef, + func() http.HandlerFunc { + callCount := 0 + return func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + callCount++ + if callCount == 1 { + // First call: empty repo + w.WriteHeader(http.StatusConflict) + response := map[string]interface{}{ + "message": "Git Repository is empty.", + } + _ = json.NewEncoder(w).Encode(response) + } else { + // Second call: return the created reference + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(mockRef) + } + } + }(), + ), + // Mock Repositories.Get to return default branch for initialization + mock.WithRequestMatch( + mock.GetReposByOwnerByRepo, + &github.Repository{ + DefaultBranch: github.Ptr("main"), + }, + ), + // Create initial file using Contents API + mock.WithRequestMatchHandler( + mock.PutReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var body map[string]interface{} + err := json.NewDecoder(r.Body).Decode(&body) + require.NoError(t, err) + require.Equal(t, "Initial commit", body["message"]) + require.Equal(t, "main", body["branch"]) + w.WriteHeader(http.StatusCreated) + response := &github.RepositoryContentResponse{ + Commit: github.Commit{SHA: github.Ptr("abc123")}, + } + b, _ := json.Marshal(response) + _, _ = w.Write(b) + }), + ), + // Get the commit after initialization + mock.WithRequestMatch( + mock.GetReposGitCommitsByOwnerByRepoByCommitSha, + mockCommit, + ), + // Create tree + mock.WithRequestMatch( + mock.PostReposGitTreesByOwnerByRepo, + mockTree, + ), + // Create commit + mock.WithRequestMatch( + mock.PostReposGitCommitsByOwnerByRepo, + mockNewCommit, + ), + // Update reference + mock.WithRequestMatch( + mock.PatchReposGitRefsByOwnerByRepoByRef, + mockUpdatedRef, + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "branch": "main", + "files": []interface{}{ + map[string]interface{}{ + "path": "README.md", + "content": "# Initial README\n\nFirst commit to empty repository.", + }, + }, + "message": "Initial commit", + }, + expectError: false, + expectedRef: mockUpdatedRef, + }, + { + name: "successful push multiple files to empty repository", + mockedClient: mock.NewMockedHTTPClient( + // Get branch reference - called twice: first for empty check, second after file creation + mock.WithRequestMatchHandler( + mock.GetReposGitRefByOwnerByRepoByRef, + func() http.HandlerFunc { + callCount := 0 + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + callCount++ + if callCount == 1 { + // First call: returns 409 Conflict for empty repo + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusConflict) + response := map[string]interface{}{ + "message": "Git Repository is empty.", + } + _ = json.NewEncoder(w).Encode(response) + } else { + // Second call: returns the updated reference after first file creation + w.WriteHeader(http.StatusOK) + b, _ := json.Marshal(&github.Reference{ + Ref: github.Ptr("refs/heads/main"), + Object: &github.GitObject{SHA: github.Ptr("init456")}, + }) + _, _ = w.Write(b) + } + }) + }(), + ), + // Mock Repositories.Get to return default branch for initialization + mock.WithRequestMatch( + mock.GetReposByOwnerByRepo, + &github.Repository{ + DefaultBranch: github.Ptr("main"), + }, + ), + // Create initial empty README.md file using Contents API to initialize repo + mock.WithRequestMatchHandler( + mock.PutReposContentsByOwnerByRepoByPath, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + var body map[string]interface{} + err := json.NewDecoder(r.Body).Decode(&body) + require.NoError(t, err) + require.Equal(t, "Initial commit", body["message"]) + require.Equal(t, "main", body["branch"]) + // Verify it's an empty file + expectedContent := base64.StdEncoding.EncodeToString([]byte("")) + require.Equal(t, expectedContent, body["content"]) + w.WriteHeader(http.StatusCreated) + response := &github.RepositoryContentResponse{ + Content: &github.RepositoryContent{ + SHA: github.Ptr("readme123"), + }, + Commit: github.Commit{ + SHA: github.Ptr("init456"), + Tree: &github.Tree{ + SHA: github.Ptr("tree456"), + }, + }, + } + b, _ := json.Marshal(response) + _, _ = w.Write(b) + }), + ), + // Get the commit to retrieve parent SHA + mock.WithRequestMatchHandler( + mock.GetReposGitCommitsByOwnerByRepoByCommitSha, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + response := &github.Commit{ + SHA: github.Ptr("init456"), + Tree: &github.Tree{ + SHA: github.Ptr("tree456"), + }, + } + b, _ := json.Marshal(response) + _, _ = w.Write(b) + }), + ), + // Create tree with all user files + mock.WithRequestMatchHandler( + mock.PostReposGitTreesByOwnerByRepo, + expectRequestBody(t, map[string]interface{}{ + "base_tree": "tree456", + "tree": []interface{}{ + map[string]interface{}{ + "path": "README.md", + "mode": "100644", + "type": "blob", + "content": "# Project\n\nProject README", + }, + map[string]interface{}{ + "path": ".gitignore", + "mode": "100644", + "type": "blob", + "content": "node_modules/\n*.log\n", + }, + map[string]interface{}{ + "path": "src/main.js", + "mode": "100644", + "type": "blob", + "content": "console.log('Hello World');\n", + }, + }, + }).andThen( + mockResponse(t, http.StatusCreated, mockTree), + ), + ), + // Create commit with all user files + mock.WithRequestMatchHandler( + mock.PostReposGitCommitsByOwnerByRepo, + expectRequestBody(t, map[string]interface{}{ + "message": "Initial project setup", + "tree": "ghi789", + "parents": []interface{}{"init456"}, + }).andThen( + mockResponse(t, http.StatusCreated, mockNewCommit), + ), + ), + // Update reference + mock.WithRequestMatchHandler( + mock.PatchReposGitRefsByOwnerByRepoByRef, + expectRequestBody(t, map[string]interface{}{ + "sha": "jkl012", + "force": false, + }).andThen( + mockResponse(t, http.StatusOK, mockUpdatedRef), + ), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "branch": "main", + "files": []interface{}{ + map[string]interface{}{ + "path": "README.md", + "content": "# Project\n\nProject README", + }, + map[string]interface{}{ + "path": ".gitignore", + "content": "node_modules/\n*.log\n", + }, + map[string]interface{}{ + "path": "src/main.js", + "content": "console.log('Hello World');\n", + }, + }, + "message": "Initial project setup", + }, + expectError: false, + expectedRef: mockUpdatedRef, + }, + { + name: "fails to create initial file in empty repository", + mockedClient: mock.NewMockedHTTPClient( + // Get branch reference returns 409 Conflict for empty repo + mock.WithRequestMatchHandler( + mock.GetReposGitRefByOwnerByRepoByRef, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusConflict) + response := map[string]interface{}{ + "message": "Git Repository is empty.", + } + _ = json.NewEncoder(w).Encode(response) + }), + ), + // Mock Repositories.Get to return default branch + mock.WithRequestMatch( + mock.GetReposByOwnerByRepo, + &github.Repository{ + DefaultBranch: github.Ptr("main"), + }, + ), + // Fail to create initial file using Contents API + mock.WithRequestMatchHandler( + mock.PutReposContentsByOwnerByRepoByPath, + mockResponse(t, http.StatusInternalServerError, nil), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "branch": "main", + "files": []interface{}{ + map[string]interface{}{ + "path": "README.md", + "content": "# README", + }, + }, + "message": "Initial commit", + }, + expectError: false, + expectedErrMsg: "failed to initialize repository", + }, + { + name: "fails to get reference after creating initial file in empty repository", + mockedClient: mock.NewMockedHTTPClient( + // Get branch reference - called twice + mock.WithRequestMatchHandler( + mock.GetReposGitRefByOwnerByRepoByRef, + func() http.HandlerFunc { + callCount := 0 + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + callCount++ + if callCount == 1 { + // First call: returns 409 Conflict for empty repo + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusConflict) + response := map[string]interface{}{ + "message": "Git Repository is empty.", + } + _ = json.NewEncoder(w).Encode(response) + } else { + // Second call: fails + w.WriteHeader(http.StatusInternalServerError) + } + }) + }(), + ), + // Mock Repositories.Get to return default branch + mock.WithRequestMatch( + mock.GetReposByOwnerByRepo, + &github.Repository{ + DefaultBranch: github.Ptr("main"), + }, + ), + // Create initial file using Contents API + mock.WithRequestMatch( + mock.PutReposContentsByOwnerByRepoByPath, + &github.RepositoryContentResponse{ + Content: &github.RepositoryContent{SHA: github.Ptr("readme123")}, + Commit: github.Commit{SHA: github.Ptr("init456")}, + }, + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "branch": "main", + "files": []interface{}{ + map[string]interface{}{ + "path": "README.md", + "content": "# README", + }, + }, + "message": "Initial commit", + }, + expectError: false, + expectedErrMsg: "failed to initialize repository", + }, + { + name: "fails to get commit in empty repository with multiple files", + mockedClient: mock.NewMockedHTTPClient( + // Get branch reference returns 409 Conflict for empty repo + mock.WithRequestMatchHandler( + mock.GetReposGitRefByOwnerByRepoByRef, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusConflict) + response := map[string]interface{}{ + "message": "Git Repository is empty.", + } + _ = json.NewEncoder(w).Encode(response) + }), + ), + // Mock Repositories.Get to return default branch + mock.WithRequestMatch( + mock.GetReposByOwnerByRepo, + &github.Repository{ + DefaultBranch: github.Ptr("main"), + }, + ), + // Create initial file using Contents API + mock.WithRequestMatch( + mock.PutReposContentsByOwnerByRepoByPath, + &github.RepositoryContentResponse{ + Content: &github.RepositoryContent{SHA: github.Ptr("readme123")}, + Commit: github.Commit{SHA: github.Ptr("init456")}, + }, + ), + // Fail to get commit + mock.WithRequestMatchHandler( + mock.GetReposGitCommitsByOwnerByRepoByCommitSha, + mockResponse(t, http.StatusInternalServerError, nil), + ), + ), + requestArgs: map[string]interface{}{ + "owner": "owner", + "repo": "repo", + "branch": "main", + "files": []interface{}{ + map[string]interface{}{ + "path": "README.md", + "content": "# README", + }, + map[string]interface{}{ + "path": "LICENSE", + "content": "MIT", + }, + }, + "message": "Initial commit", + }, + expectError: false, + expectedErrMsg: "failed to initialize repository", + }, } for _, tc := range tests { From eb6db0fb5da6a9a2c962d8eae85d0716d4c84b58 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 14:58:33 +0000 Subject: [PATCH 017/138] Add scopes package and update ServerTool struct with scope fields - Created pkg/scopes package with OAuth scope constants - Added RequiredScopes and AcceptedScopes fields to ServerTool - Added NewToolWithScopes helpers in dependencies.go - Updated context tools (get_me, get_teams, get_team_members) with scopes Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com> --- pkg/github/context_tools.go | 13 ++++-- pkg/github/dependencies.go | 36 +++++++++++++++++ pkg/inventory/server_tool.go | 9 +++++ pkg/scopes/scopes.go | 77 ++++++++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 pkg/scopes/scopes.go diff --git a/pkg/github/context_tools.go b/pkg/github/context_tools.go index e0df82c88..77c9ce10a 100644 --- a/pkg/github/context_tools.go +++ b/pkg/github/context_tools.go @@ -7,6 +7,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/jsonschema-go/jsonschema" @@ -38,7 +39,7 @@ type UserDetails struct { // GetMe creates a tool to get details of the authenticated user. func GetMe(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataContext, mcp.Tool{ Name: "get_me", @@ -51,6 +52,8 @@ func GetMe(t translations.TranslationHelperFunc) inventory.ServerTool { // OpenAI strict mode requires the properties field to be present. InputSchema: json.RawMessage(`{"type":"object","properties":{}}`), }, + nil, // no required scopes + nil, // no accepted scopes func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, _ map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -110,7 +113,7 @@ type OrganizationTeams struct { } func GetTeams(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataContext, mcp.Tool{ Name: "get_teams", @@ -129,6 +132,8 @@ func GetTeams(t translations.TranslationHelperFunc) inventory.ServerTool { }, }, }, + scopes.ToStringSlice(scopes.ReadOrg), + scopes.ToStringSlice(scopes.ReadOrg, scopes.WriteOrg, scopes.AdminOrg), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { user, err := OptionalParam[string](args, "user") if err != nil { @@ -207,7 +212,7 @@ func GetTeams(t translations.TranslationHelperFunc) inventory.ServerTool { } func GetTeamMembers(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataContext, mcp.Tool{ Name: "get_team_members", @@ -231,6 +236,8 @@ func GetTeamMembers(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"org", "team_slug"}, }, }, + scopes.ToStringSlice(scopes.ReadOrg), + scopes.ToStringSlice(scopes.ReadOrg, scopes.WriteOrg, scopes.AdminOrg), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { org, err := RequiredParam[string](args, "org") if err != nil { diff --git a/pkg/github/dependencies.go b/pkg/github/dependencies.go index d23e993c3..e15efb1d5 100644 --- a/pkg/github/dependencies.go +++ b/pkg/github/dependencies.go @@ -155,6 +155,24 @@ func NewTool[In, Out any](toolset inventory.ToolsetMetadata, tool mcp.Tool, hand }) } +// NewToolWithScopes creates a ServerTool with OAuth scope requirements. +// This is like NewTool but also accepts required and accepted scopes. +func NewToolWithScopes[In, Out any]( + toolset inventory.ToolsetMetadata, + tool mcp.Tool, + requiredScopes []string, + acceptedScopes []string, + handler func(ctx context.Context, deps ToolDependencies, req *mcp.CallToolRequest, args In) (*mcp.CallToolResult, Out, error), +) inventory.ServerTool { + st := inventory.NewServerToolWithContextHandler(tool, toolset, func(ctx context.Context, req *mcp.CallToolRequest, args In) (*mcp.CallToolResult, Out, error) { + deps := MustDepsFromContext(ctx) + return handler(ctx, deps, req, args) + }) + st.RequiredScopes = requiredScopes + st.AcceptedScopes = acceptedScopes + return st +} + // NewToolFromHandler creates a ServerTool that retrieves ToolDependencies from context at call time. // Use this when you have a handler that conforms to mcp.ToolHandler directly. // @@ -166,3 +184,21 @@ func NewToolFromHandler(toolset inventory.ToolsetMetadata, tool mcp.Tool, handle return handler(ctx, deps, req) }) } + +// NewToolFromHandlerWithScopes creates a ServerTool with OAuth scope requirements. +// This is like NewToolFromHandler but also accepts required and accepted scopes. +func NewToolFromHandlerWithScopes( + toolset inventory.ToolsetMetadata, + tool mcp.Tool, + requiredScopes []string, + acceptedScopes []string, + handler func(ctx context.Context, deps ToolDependencies, req *mcp.CallToolRequest) (*mcp.CallToolResult, error), +) inventory.ServerTool { + st := inventory.NewServerToolWithRawContextHandler(tool, toolset, func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { + deps := MustDepsFromContext(ctx) + return handler(ctx, deps, req) + }) + st.RequiredScopes = requiredScopes + st.AcceptedScopes = acceptedScopes + return st +} diff --git a/pkg/inventory/server_tool.go b/pkg/inventory/server_tool.go index 362ee2643..095bedf2b 100644 --- a/pkg/inventory/server_tool.go +++ b/pkg/inventory/server_tool.go @@ -70,6 +70,15 @@ type ServerTool struct { // The context carries request-scoped information for the consumer to use. // Returns (enabled, error). On error, the tool should be treated as disabled. Enabled func(ctx context.Context) (bool, error) + + // RequiredScopes specifies the minimum OAuth scopes required for this tool. + // These are the scopes that must be present for the tool to function. + RequiredScopes []string + + // AcceptedScopes specifies all OAuth scopes that can be used with this tool. + // This includes the required scopes plus any higher-level scopes that provide + // the necessary permissions due to scope hierarchy. + AcceptedScopes []string } // IsReadOnly returns true if this tool is marked as read-only via annotations. diff --git a/pkg/scopes/scopes.go b/pkg/scopes/scopes.go new file mode 100644 index 000000000..d6272a0a0 --- /dev/null +++ b/pkg/scopes/scopes.go @@ -0,0 +1,77 @@ +package scopes + +// Scope represents a GitHub OAuth scope. +// These constants define all OAuth scopes used by the GitHub MCP server tools. +// See https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps +type Scope string + +const ( +// Repo grants full control of private repositories +Repo Scope = "repo" + +// PublicRepo grants access to public repositories +PublicRepo Scope = "public_repo" + +// ReadOrg grants read-only access to organization membership, teams, and projects +ReadOrg Scope = "read:org" + +// WriteOrg grants write access to organization membership and teams +WriteOrg Scope = "write:org" + +// AdminOrg grants full control of organizations and teams +AdminOrg Scope = "admin:org" + +// Gist grants write access to gists +Gist Scope = "gist" + +// Notifications grants access to notifications +Notifications Scope = "notifications" + +// ReadProject grants read-only access to projects +ReadProject Scope = "read:project" + +// Project grants full control of projects +Project Scope = "project" + +// SecurityEvents grants read and write access to security events +SecurityEvents Scope = "security_events" +) + +// ScopeSet represents a set of OAuth scopes. +type ScopeSet map[Scope]bool + +// NewScopeSet creates a new ScopeSet from the given scopes. +func NewScopeSet(scopes ...Scope) ScopeSet { +set := make(ScopeSet) +for _, scope := range scopes { +set[scope] = true +} +return set +} + +// ToSlice converts a ScopeSet to a slice of Scope values. +func (s ScopeSet) ToSlice() []Scope { +scopes := make([]Scope, 0, len(s)) +for scope := range s { +scopes = append(scopes, scope) +} +return scopes +} + +// ToStringSlice converts a ScopeSet to a slice of string values. +func (s ScopeSet) ToStringSlice() []string { +scopes := make([]string, 0, len(s)) +for scope := range s { +scopes = append(scopes, string(scope)) +} +return scopes +} + +// ToStringSlice converts a slice of Scopes to a slice of strings. +func ToStringSlice(scopes ...Scope) []string { +result := make([]string, len(scopes)) +for i, scope := range scopes { +result[i] = string(scope) +} +return result +} From 1e5931a2ab61077c6c6a96b3b3df984eda101147 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 15:07:10 +0000 Subject: [PATCH 018/138] Update most tools with OAuth scope information - Updated 60+ tools with required and accepted OAuth scopes - Added scopes to: gists, git, notifications, projects, code scanning, dependabot, secret scanning, security advisories, actions, discussions, issues (partial), labels, pull requests (partial), repositories (partial), search (partial) - Remaining: ~20 tools in issues, pullrequests, repositories, and search files Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com> --- pkg/github/actions.go | 1 + pkg/github/code_scanning.go | 9 +++- pkg/github/dependabot.go | 9 +++- pkg/github/discussions.go | 17 ++++++-- pkg/github/gists.go | 17 ++++++-- pkg/github/git.go | 5 ++- pkg/github/issues.go | 17 ++++++-- pkg/github/labels.go | 13 ++++-- pkg/github/notifications.go | 25 ++++++++--- pkg/github/projects.go | 37 +++++++++++++---- pkg/github/pullrequests.go | 1 + pkg/github/repositories.go | 69 +++++++++++++++++++++++-------- pkg/github/search.go | 1 + pkg/github/secret_scanning.go | 9 +++- pkg/github/security_advisories.go | 17 ++++++-- 15 files changed, 189 insertions(+), 58 deletions(-) diff --git a/pkg/github/actions.go b/pkg/github/actions.go index 1547c3251..cffab4b50 100644 --- a/pkg/github/actions.go +++ b/pkg/github/actions.go @@ -13,6 +13,7 @@ import ( buffer "github.com/github/github-mcp-server/pkg/buffer" ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" diff --git a/pkg/github/code_scanning.go b/pkg/github/code_scanning.go index 5e25d0501..af232937c 100644 --- a/pkg/github/code_scanning.go +++ b/pkg/github/code_scanning.go @@ -8,6 +8,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -16,7 +17,7 @@ import ( ) func GetCodeScanningAlert(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataCodeSecurity, mcp.Tool{ Name: "get_code_scanning_alert", @@ -44,6 +45,8 @@ func GetCodeScanningAlert(t translations.TranslationHelperFunc) inventory.Server Required: []string{"owner", "repo", "alertNumber"}, }, }, + scopes.ToStringSlice(scopes.SecurityEvents), + scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -92,7 +95,7 @@ func GetCodeScanningAlert(t translations.TranslationHelperFunc) inventory.Server } func ListCodeScanningAlerts(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataCodeSecurity, mcp.Tool{ Name: "list_code_scanning_alerts", @@ -135,6 +138,8 @@ func ListCodeScanningAlerts(t translations.TranslationHelperFunc) inventory.Serv Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.SecurityEvents), + scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/dependabot.go b/pkg/github/dependabot.go index db6352dab..9a0c09a8f 100644 --- a/pkg/github/dependabot.go +++ b/pkg/github/dependabot.go @@ -9,6 +9,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -17,7 +18,7 @@ import ( ) func GetDependabotAlert(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataDependabot, mcp.Tool{ Name: "get_dependabot_alert", @@ -45,6 +46,8 @@ func GetDependabotAlert(t translations.TranslationHelperFunc) inventory.ServerTo Required: []string{"owner", "repo", "alertNumber"}, }, }, + scopes.ToStringSlice(scopes.SecurityEvents), + scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -93,7 +96,7 @@ func GetDependabotAlert(t translations.TranslationHelperFunc) inventory.ServerTo } func ListDependabotAlerts(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataDependabot, mcp.Tool{ Name: "list_dependabot_alerts", @@ -128,6 +131,8 @@ func ListDependabotAlerts(t translations.TranslationHelperFunc) inventory.Server Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.SecurityEvents), + scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/discussions.go b/pkg/github/discussions.go index c891ba294..d91722e6e 100644 --- a/pkg/github/discussions.go +++ b/pkg/github/discussions.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/go-viper/mapstructure/v2" @@ -123,7 +124,7 @@ func getQueryType(useOrdering bool, categoryID *githubv4.ID) any { } func ListDiscussions(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataDiscussions, mcp.Tool{ Name: "list_discussions", @@ -161,6 +162,8 @@ func ListDiscussions(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -275,7 +278,7 @@ func ListDiscussions(t translations.TranslationHelperFunc) inventory.ServerTool } func GetDiscussion(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataDiscussions, mcp.Tool{ Name: "get_discussion", @@ -303,6 +306,8 @@ func GetDiscussion(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "discussionNumber"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { // Decode params var params struct { @@ -378,7 +383,7 @@ func GetDiscussion(t translations.TranslationHelperFunc) inventory.ServerTool { } func GetDiscussionComments(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataDiscussions, mcp.Tool{ Name: "get_discussion_comments", @@ -406,6 +411,8 @@ func GetDiscussionComments(t translations.TranslationHelperFunc) inventory.Serve Required: []string{"owner", "repo", "discussionNumber"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { // Decode params var params struct { @@ -504,7 +511,7 @@ func GetDiscussionComments(t translations.TranslationHelperFunc) inventory.Serve } func ListDiscussionCategories(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataDiscussions, mcp.Tool{ Name: "list_discussion_categories", @@ -528,6 +535,8 @@ func ListDiscussionCategories(t translations.TranslationHelperFunc) inventory.Se Required: []string{"owner"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/gists.go b/pkg/github/gists.go index 4d741b88d..cd268c6a1 100644 --- a/pkg/github/gists.go +++ b/pkg/github/gists.go @@ -9,6 +9,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -18,7 +19,7 @@ import ( // ListGists creates a tool to list gists for a user func ListGists(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataGists, mcp.Tool{ Name: "list_gists", @@ -41,6 +42,8 @@ func ListGists(t translations.TranslationHelperFunc) inventory.ServerTool { }, }), }, + nil, // no required scopes + nil, // no accepted scopes func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { username, err := OptionalParam[string](args, "username") if err != nil { @@ -104,7 +107,7 @@ func ListGists(t translations.TranslationHelperFunc) inventory.ServerTool { // GetGist creates a tool to get the content of a gist func GetGist(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataGists, mcp.Tool{ Name: "get_gist", @@ -124,6 +127,8 @@ func GetGist(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"gist_id"}, }, }, + nil, // no required scopes + nil, // no accepted scopes func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { gistID, err := RequiredParam[string](args, "gist_id") if err != nil { @@ -161,7 +166,7 @@ func GetGist(t translations.TranslationHelperFunc) inventory.ServerTool { // CreateGist creates a tool to create a new gist func CreateGist(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataGists, mcp.Tool{ Name: "create_gist", @@ -194,6 +199,8 @@ func CreateGist(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"filename", "content"}, }, }, + scopes.ToStringSlice(scopes.Gist), + scopes.ToStringSlice(scopes.Gist), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { description, err := OptionalParam[string](args, "description") if err != nil { @@ -263,7 +270,7 @@ func CreateGist(t translations.TranslationHelperFunc) inventory.ServerTool { // UpdateGist creates a tool to edit an existing gist func UpdateGist(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataGists, mcp.Tool{ Name: "update_gist", @@ -295,6 +302,8 @@ func UpdateGist(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"gist_id", "filename", "content"}, }, }, + scopes.ToStringSlice(scopes.Gist), + scopes.ToStringSlice(scopes.Gist), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { gistID, err := RequiredParam[string](args, "gist_id") if err != nil { diff --git a/pkg/github/git.go b/pkg/github/git.go index 7b93c3675..06c766163 100644 --- a/pkg/github/git.go +++ b/pkg/github/git.go @@ -8,6 +8,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -39,7 +40,7 @@ type TreeResponse struct { // GetRepositoryTree creates a tool to get the tree structure of a GitHub repository. func GetRepositoryTree(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataGit, mcp.Tool{ Name: "get_repository_tree", @@ -76,6 +77,8 @@ func GetRepositoryTree(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/issues.go b/pkg/github/issues.go index 23d16b172..406e980fe 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -11,6 +11,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/lockdown" "github.com/github/github-mcp-server/pkg/octicons" "github.com/github/github-mcp-server/pkg/sanitize" @@ -545,7 +546,7 @@ func GetIssueLabels(ctx context.Context, client *githubv4.Client, owner string, // ListIssueTypes creates a tool to list defined issue types for an organization. This can be used to understand supported issue type values for creating or updating issues. func ListIssueTypes(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataIssues, mcp.Tool{ Name: "list_issue_types", @@ -565,6 +566,8 @@ func ListIssueTypes(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner"}, }, }, + scopes.ToStringSlice(scopes.ReadOrg), + scopes.ToStringSlice(scopes.ReadOrg, scopes.WriteOrg, scopes.AdminOrg), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -600,7 +603,7 @@ func ListIssueTypes(t translations.TranslationHelperFunc) inventory.ServerTool { // AddIssueComment creates a tool to add a comment to an issue. func AddIssueComment(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataIssues, mcp.Tool{ Name: "add_issue_comment", @@ -632,6 +635,8 @@ func AddIssueComment(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo", "issue_number", "body"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -683,7 +688,7 @@ func AddIssueComment(t translations.TranslationHelperFunc) inventory.ServerTool // SubIssueWrite creates a tool to add a sub-issue to a parent issue. func SubIssueWrite(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataIssues, mcp.Tool{ Name: "sub_issue_write", @@ -736,6 +741,8 @@ Options are: Required: []string{"method", "owner", "repo", "issue_number", "sub_issue_id"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { method, err := RequiredParam[string](args, "method") if err != nil { @@ -971,7 +978,7 @@ func SearchIssues(t translations.TranslationHelperFunc) inventory.ServerTool { // IssueWrite creates a tool to create a new or update an existing issue in a GitHub repository. func IssueWrite(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataIssues, mcp.Tool{ Name: "issue_write", @@ -1052,6 +1059,8 @@ Options are: Required: []string{"method", "owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { method, err := RequiredParam[string](args, "method") if err != nil { diff --git a/pkg/github/labels.go b/pkg/github/labels.go index 2811cf66e..d55b605fe 100644 --- a/pkg/github/labels.go +++ b/pkg/github/labels.go @@ -8,6 +8,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/jsonschema-go/jsonschema" @@ -17,7 +18,7 @@ import ( // GetLabel retrieves a specific label by name from a GitHub repository func GetLabel(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataIssues, mcp.Tool{ Name: "get_label", @@ -45,6 +46,8 @@ func GetLabel(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "name"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -118,7 +121,7 @@ func GetLabelForLabelsToolset(t translations.TranslationHelperFunc) inventory.Se // ListLabels lists labels from a repository func ListLabels(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetLabels, mcp.Tool{ Name: "list_label", @@ -142,6 +145,8 @@ func ListLabels(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -208,7 +213,7 @@ func ListLabels(t translations.TranslationHelperFunc) inventory.ServerTool { // LabelWrite handles create, update, and delete operations for GitHub labels func LabelWrite(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetLabels, mcp.Tool{ Name: "label_write", @@ -253,6 +258,8 @@ func LabelWrite(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"method", "owner", "repo", "name"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { // Get and validate required parameters method, err := RequiredParam[string](args, "method") diff --git a/pkg/github/notifications.go b/pkg/github/notifications.go index 1e2011fa3..b3008362b 100644 --- a/pkg/github/notifications.go +++ b/pkg/github/notifications.go @@ -11,6 +11,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -26,7 +27,7 @@ const ( // ListNotifications creates a tool to list notifications for the current user. func ListNotifications(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataNotifications, mcp.Tool{ Name: "list_notifications", @@ -62,6 +63,8 @@ func ListNotifications(t translations.TranslationHelperFunc) inventory.ServerToo }, }), }, + scopes.ToStringSlice(scopes.Notifications), + scopes.ToStringSlice(scopes.Notifications), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -162,7 +165,7 @@ func ListNotifications(t translations.TranslationHelperFunc) inventory.ServerToo // DismissNotification creates a tool to mark a notification as read/done. func DismissNotification(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataNotifications, mcp.Tool{ Name: "dismiss_notification", @@ -187,6 +190,8 @@ func DismissNotification(t translations.TranslationHelperFunc) inventory.ServerT Required: []string{"threadID", "state"}, }, }, + scopes.ToStringSlice(scopes.Notifications), + scopes.ToStringSlice(scopes.Notifications), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -243,7 +248,7 @@ func DismissNotification(t translations.TranslationHelperFunc) inventory.ServerT // MarkAllNotificationsRead creates a tool to mark all notifications as read. func MarkAllNotificationsRead(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataNotifications, mcp.Tool{ Name: "mark_all_notifications_read", @@ -270,6 +275,8 @@ func MarkAllNotificationsRead(t translations.TranslationHelperFunc) inventory.Se }, }, }, + scopes.ToStringSlice(scopes.Notifications), + scopes.ToStringSlice(scopes.Notifications), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -334,7 +341,7 @@ func MarkAllNotificationsRead(t translations.TranslationHelperFunc) inventory.Se // GetNotificationDetails creates a tool to get details for a specific notification. func GetNotificationDetails(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataNotifications, mcp.Tool{ Name: "get_notification_details", @@ -354,6 +361,8 @@ func GetNotificationDetails(t translations.TranslationHelperFunc) inventory.Serv Required: []string{"notificationID"}, }, }, + scopes.ToStringSlice(scopes.Notifications), + scopes.ToStringSlice(scopes.Notifications), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -402,7 +411,7 @@ const ( // ManageNotificationSubscription creates a tool to manage a notification subscription (ignore, watch, delete) func ManageNotificationSubscription(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataNotifications, mcp.Tool{ Name: "manage_notification_subscription", @@ -427,6 +436,8 @@ func ManageNotificationSubscription(t translations.TranslationHelperFunc) invent Required: []string{"notificationID", "action"}, }, }, + scopes.ToStringSlice(scopes.Notifications), + scopes.ToStringSlice(scopes.Notifications), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -497,7 +508,7 @@ const ( // ManageRepositoryNotificationSubscription creates a tool to manage a repository notification subscription (ignore, watch, delete) func ManageRepositoryNotificationSubscription(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataNotifications, mcp.Tool{ Name: "manage_repository_notification_subscription", @@ -526,6 +537,8 @@ func ManageRepositoryNotificationSubscription(t translations.TranslationHelperFu Required: []string{"owner", "repo", "action"}, }, }, + scopes.ToStringSlice(scopes.Notifications), + scopes.ToStringSlice(scopes.Notifications), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { diff --git a/pkg/github/projects.go b/pkg/github/projects.go index 0536bed99..4edef178e 100644 --- a/pkg/github/projects.go +++ b/pkg/github/projects.go @@ -10,6 +10,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -26,7 +27,7 @@ const ( ) func ListProjects(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataProjects, mcp.Tool{ Name: "list_projects", @@ -67,6 +68,8 @@ func ListProjects(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner_type", "owner"}, }, }, + scopes.ToStringSlice(scopes.ReadProject), + scopes.ToStringSlice(scopes.ReadProject, scopes.Project), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") @@ -143,7 +146,7 @@ func ListProjects(t translations.TranslationHelperFunc) inventory.ServerTool { } func GetProject(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataProjects, mcp.Tool{ Name: "get_project", @@ -172,6 +175,8 @@ func GetProject(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"project_number", "owner_type", "owner"}, }, }, + scopes.ToStringSlice(scopes.ReadProject), + scopes.ToStringSlice(scopes.ReadProject, scopes.Project), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { projectNumber, err := RequiredInt(args, "project_number") @@ -231,7 +236,7 @@ func GetProject(t translations.TranslationHelperFunc) inventory.ServerTool { } func ListProjectFields(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataProjects, mcp.Tool{ Name: "list_project_fields", @@ -272,6 +277,8 @@ func ListProjectFields(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"owner_type", "owner", "project_number"}, }, }, + scopes.ToStringSlice(scopes.ReadProject), + scopes.ToStringSlice(scopes.ReadProject, scopes.Project), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") @@ -337,7 +344,7 @@ func ListProjectFields(t translations.TranslationHelperFunc) inventory.ServerToo } func GetProjectField(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataProjects, mcp.Tool{ Name: "get_project_field", @@ -370,6 +377,8 @@ func GetProjectField(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner_type", "owner", "project_number", "field_id"}, }, }, + scopes.ToStringSlice(scopes.ReadProject), + scopes.ToStringSlice(scopes.ReadProject, scopes.Project), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") @@ -429,7 +438,7 @@ func GetProjectField(t translations.TranslationHelperFunc) inventory.ServerTool } func ListProjectItems(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataProjects, mcp.Tool{ Name: "list_project_items", @@ -481,6 +490,8 @@ func ListProjectItems(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner_type", "owner", "project_number"}, }, }, + scopes.ToStringSlice(scopes.ReadProject), + scopes.ToStringSlice(scopes.ReadProject, scopes.Project), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") @@ -565,7 +576,7 @@ func ListProjectItems(t translations.TranslationHelperFunc) inventory.ServerTool } func GetProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataProjects, mcp.Tool{ Name: "get_project_item", @@ -605,6 +616,8 @@ func GetProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner_type", "owner", "project_number", "item_id"}, }, }, + scopes.ToStringSlice(scopes.ReadProject), + scopes.ToStringSlice(scopes.ReadProject, scopes.Project), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") @@ -671,7 +684,7 @@ func GetProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { } func AddProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataProjects, mcp.Tool{ Name: "add_project_item", @@ -709,6 +722,8 @@ func AddProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner_type", "owner", "project_number", "item_type", "item_id"}, }, }, + scopes.ToStringSlice(scopes.Project), + scopes.ToStringSlice(scopes.Project), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") @@ -782,7 +797,7 @@ func AddProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { } func UpdateProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataProjects, mcp.Tool{ Name: "update_project_item", @@ -819,6 +834,8 @@ func UpdateProjectItem(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"owner_type", "owner", "project_number", "item_id", "updated_field"}, }, }, + scopes.ToStringSlice(scopes.Project), + scopes.ToStringSlice(scopes.Project), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") @@ -894,7 +911,7 @@ func UpdateProjectItem(t translations.TranslationHelperFunc) inventory.ServerToo } func DeleteProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataProjects, mcp.Tool{ Name: "delete_project_item", @@ -928,6 +945,8 @@ func DeleteProjectItem(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"owner_type", "owner", "project_number", "item_id"}, }, }, + scopes.ToStringSlice(scopes.Project), + scopes.ToStringSlice(scopes.Project), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") diff --git a/pkg/github/pullrequests.go b/pkg/github/pullrequests.go index d51c14fa4..400918858 100644 --- a/pkg/github/pullrequests.go +++ b/pkg/github/pullrequests.go @@ -15,6 +15,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/lockdown" "github.com/github/github-mcp-server/pkg/octicons" "github.com/github/github-mcp-server/pkg/sanitize" diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index 1ab33a57c..f50babc32 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -11,6 +11,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/octicons" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" @@ -20,7 +21,7 @@ import ( ) func GetCommit(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataRepos, mcp.Tool{ Name: "get_commit", @@ -53,6 +54,8 @@ func GetCommit(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "sha"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -117,7 +120,7 @@ func GetCommit(t translations.TranslationHelperFunc) inventory.ServerTool { // ListCommits creates a tool to get commits of a branch in a repository. func ListCommits(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataRepos, mcp.Tool{ Name: "list_commits", @@ -149,6 +152,8 @@ func ListCommits(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -224,7 +229,7 @@ func ListCommits(t translations.TranslationHelperFunc) inventory.ServerTool { // ListBranches creates a tool to list branches in a GitHub repository. func ListBranches(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataRepos, mcp.Tool{ Name: "list_branches", @@ -248,6 +253,8 @@ func ListBranches(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -508,7 +515,7 @@ If the SHA is not provided, the tool will attempt to acquire it by fetching the // CreateRepository creates a tool to create a new GitHub repository. func CreateRepository(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataRepos, mcp.Tool{ Name: "create_repository", @@ -544,6 +551,8 @@ func CreateRepository(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"name"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { name, err := RequiredParam[string](args, "name") if err != nil { @@ -613,7 +622,7 @@ func CreateRepository(t translations.TranslationHelperFunc) inventory.ServerTool // GetFileContents creates a tool to get the contents of a file or directory from a GitHub repository. func GetFileContents(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataRepos, mcp.Tool{ Name: "get_file_contents", @@ -650,6 +659,8 @@ func GetFileContents(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -804,7 +815,7 @@ func GetFileContents(t translations.TranslationHelperFunc) inventory.ServerTool // ForkRepository creates a tool to fork a repository. func ForkRepository(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataRepos, mcp.Tool{ Name: "fork_repository", @@ -833,6 +844,8 @@ func ForkRepository(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -902,7 +915,7 @@ func ForkRepository(t translations.TranslationHelperFunc) inventory.ServerTool { // The approach implemented here gets automatic commit signing when used with either the github-actions user or as an app, // both of which suit an LLM well. func DeleteFile(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataRepos, mcp.Tool{ Name: "delete_file", @@ -939,6 +952,8 @@ func DeleteFile(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "path", "message", "branch"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1086,7 +1101,7 @@ func DeleteFile(t translations.TranslationHelperFunc) inventory.ServerTool { // CreateBranch creates a tool to create a new branch. func CreateBranch(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataRepos, mcp.Tool{ Name: "create_branch", @@ -1118,6 +1133,8 @@ func CreateBranch(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "branch"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1198,7 +1215,7 @@ func CreateBranch(t translations.TranslationHelperFunc) inventory.ServerTool { // PushFiles creates a tool to push multiple files in a single commit to a GitHub repository. func PushFiles(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataRepos, mcp.Tool{ Name: "push_files", @@ -1248,6 +1265,8 @@ func PushFiles(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "branch", "files", "message"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1431,7 +1450,7 @@ func PushFiles(t translations.TranslationHelperFunc) inventory.ServerTool { // ListTags creates a tool to list tags in a GitHub repository. func ListTags(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataRepos, mcp.Tool{ Name: "list_tags", @@ -1455,6 +1474,8 @@ func ListTags(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1509,7 +1530,7 @@ func ListTags(t translations.TranslationHelperFunc) inventory.ServerTool { // GetTag creates a tool to get details about a specific tag in a GitHub repository. func GetTag(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataRepos, mcp.Tool{ Name: "get_tag", @@ -1537,6 +1558,8 @@ func GetTag(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "tag"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1606,7 +1629,7 @@ func GetTag(t translations.TranslationHelperFunc) inventory.ServerTool { // ListReleases creates a tool to list releases in a GitHub repository. func ListReleases(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataRepos, mcp.Tool{ Name: "list_releases", @@ -1630,6 +1653,8 @@ func ListReleases(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1680,7 +1705,7 @@ func ListReleases(t translations.TranslationHelperFunc) inventory.ServerTool { // GetLatestRelease creates a tool to get the latest release in a GitHub repository. func GetLatestRelease(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataRepos, mcp.Tool{ Name: "get_latest_release", @@ -1704,6 +1729,8 @@ func GetLatestRelease(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1744,7 +1771,7 @@ func GetLatestRelease(t translations.TranslationHelperFunc) inventory.ServerTool } func GetReleaseByTag(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataRepos, mcp.Tool{ Name: "get_release_by_tag", @@ -1772,6 +1799,8 @@ func GetReleaseByTag(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo", "tag"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1821,7 +1850,7 @@ func GetReleaseByTag(t translations.TranslationHelperFunc) inventory.ServerTool // ListStarredRepositories creates a tool to list starred repositories for the authenticated user or a specified user. func ListStarredRepositories(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataStargazers, mcp.Tool{ Name: "list_starred_repositories", @@ -1850,6 +1879,8 @@ func ListStarredRepositories(t translations.TranslationHelperFunc) inventory.Ser }, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { username, err := OptionalParam[string](args, "username") if err != nil { @@ -1952,7 +1983,7 @@ func ListStarredRepositories(t translations.TranslationHelperFunc) inventory.Ser // StarRepository creates a tool to star a repository. func StarRepository(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataStargazers, mcp.Tool{ Name: "star_repository", @@ -1977,6 +2008,8 @@ func StarRepository(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.PublicRepo), + scopes.ToStringSlice(scopes.PublicRepo, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -2017,7 +2050,7 @@ func StarRepository(t translations.TranslationHelperFunc) inventory.ServerTool { // UnstarRepository creates a tool to unstar a repository. func UnstarRepository(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataStargazers, mcp.Tool{ Name: "unstar_repository", @@ -2041,6 +2074,8 @@ func UnstarRepository(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.PublicRepo), + scopes.ToStringSlice(scopes.PublicRepo, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/search.go b/pkg/github/search.go index 9a8b971e2..e207479d6 100644 --- a/pkg/github/search.go +++ b/pkg/github/search.go @@ -9,6 +9,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" diff --git a/pkg/github/secret_scanning.go b/pkg/github/secret_scanning.go index 0de5166ba..fb259758e 100644 --- a/pkg/github/secret_scanning.go +++ b/pkg/github/secret_scanning.go @@ -9,6 +9,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -17,7 +18,7 @@ import ( ) func GetSecretScanningAlert(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataSecretProtection, mcp.Tool{ Name: "get_secret_scanning_alert", @@ -45,6 +46,8 @@ func GetSecretScanningAlert(t translations.TranslationHelperFunc) inventory.Serv Required: []string{"owner", "repo", "alertNumber"}, }, }, + scopes.ToStringSlice(scopes.SecurityEvents), + scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -93,7 +96,7 @@ func GetSecretScanningAlert(t translations.TranslationHelperFunc) inventory.Serv } func ListSecretScanningAlerts(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataSecretProtection, mcp.Tool{ Name: "list_secret_scanning_alerts", @@ -131,6 +134,8 @@ func ListSecretScanningAlerts(t translations.TranslationHelperFunc) inventory.Se Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.SecurityEvents), + scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/security_advisories.go b/pkg/github/security_advisories.go index f898de61d..44858dbd9 100644 --- a/pkg/github/security_advisories.go +++ b/pkg/github/security_advisories.go @@ -9,6 +9,7 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -17,7 +18,7 @@ import ( ) func ListGlobalSecurityAdvisories(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataSecurityAdvisories, mcp.Tool{ Name: "list_global_security_advisories", @@ -83,6 +84,8 @@ func ListGlobalSecurityAdvisories(t translations.TranslationHelperFunc) inventor }, }, }, + scopes.ToStringSlice(scopes.SecurityEvents), + scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -207,7 +210,7 @@ func ListGlobalSecurityAdvisories(t translations.TranslationHelperFunc) inventor } func ListRepositorySecurityAdvisories(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataSecurityAdvisories, mcp.Tool{ Name: "list_repository_security_advisories", @@ -246,6 +249,8 @@ func ListRepositorySecurityAdvisories(t translations.TranslationHelperFunc) inve Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.SecurityEvents), + scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -310,7 +315,7 @@ func ListRepositorySecurityAdvisories(t translations.TranslationHelperFunc) inve } func GetGlobalSecurityAdvisory(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataSecurityAdvisories, mcp.Tool{ Name: "get_global_security_advisory", @@ -330,6 +335,8 @@ func GetGlobalSecurityAdvisory(t translations.TranslationHelperFunc) inventory.S Required: []string{"ghsaId"}, }, }, + scopes.ToStringSlice(scopes.SecurityEvents), + scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -366,7 +373,7 @@ func GetGlobalSecurityAdvisory(t translations.TranslationHelperFunc) inventory.S } func ListOrgRepositorySecurityAdvisories(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataSecurityAdvisories, mcp.Tool{ Name: "list_org_repository_security_advisories", @@ -401,6 +408,8 @@ func ListOrgRepositorySecurityAdvisories(t translations.TranslationHelperFunc) i Required: []string{"org"}, }, }, + scopes.ToStringSlice(scopes.SecurityEvents), + scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { org, err := RequiredParam[string](args, "org") if err != nil { From db9f4e8ddf808449b7cda93b8721f17d77b288d6 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 15:16:11 +0000 Subject: [PATCH 019/138] Complete OAuth scope implementation for all tools - Updated all remaining tools with OAuth scope information - Added scope documentation generation to generate-docs command - Documentation now shows Required and Accepted OAuth scopes for each tool - All 100+ tools now have scope information defined - Tests pass, linter passes, documentation generated successfully Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com> --- README.md | 245 +++++++++++++++++++++++++ cmd/github-mcp-server/generate_docs.go | 21 ++- pkg/github/actions.go | 72 ++++++-- pkg/github/issues.go | 18 +- pkg/github/pullrequests.go | 42 +++-- pkg/github/repositories.go | 7 +- pkg/github/search.go | 16 +- pkg/scopes/scopes.go | 80 ++++---- 8 files changed, 415 insertions(+), 86 deletions(-) diff --git a/README.md b/README.md index af92cfd0b..0111ba6d6 100644 --- a/README.md +++ b/README.md @@ -492,22 +492,73 @@ The following sets of tools are available: workflow Actions +<<<<<<< HEAD +======= +- **actions_get** - Get details of GitHub Actions resources (workflows, workflow runs, jobs, and artifacts) + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` + - `method`: The method to execute (string, required) + - `owner`: Repository owner (string, required) + - `repo`: Repository name (string, required) + - `resource_id`: The unique identifier of the resource. This will vary based on the "method" provided, so ensure you provide the correct ID: + - Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'get_workflow' method. + - Provide a workflow run ID for 'get_workflow_run', 'get_workflow_run_usage', and 'get_workflow_run_logs_url' methods. + - Provide an artifact ID for 'download_workflow_run_artifact' method. + - Provide a job ID for 'get_workflow_job' method. + (string, required) + +- **actions_list** - List GitHub Actions workflows in a repository + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` + - `method`: The action to perform (string, required) + - `owner`: Repository owner (string, required) + - `page`: Page number for pagination (default: 1) (number, optional) + - `per_page`: Results per page for pagination (default: 30, max: 100) (number, optional) + - `repo`: Repository name (string, required) + - `resource_id`: The unique identifier of the resource. This will vary based on the "method" provided, so ensure you provide the correct ID: + - Do not provide any resource ID for 'list_workflows' method. + - Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'list_workflow_runs' method. + - Provide a workflow run ID for 'list_workflow_jobs' and 'list_workflow_run_artifacts' methods. + (string, optional) + - `workflow_jobs_filter`: Filters for workflow jobs. **ONLY** used when method is 'list_workflow_jobs' (object, optional) + - `workflow_runs_filter`: Filters for workflow runs. **ONLY** used when method is 'list_workflow_runs' (object, optional) + +- **actions_run_trigger** - Trigger GitHub Actions workflow actions + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` + - `inputs`: Inputs the workflow accepts. Only used for 'run_workflow' method. (object, optional) + - `method`: The method to execute (string, required) + - `owner`: Repository owner (string, required) + - `ref`: The git reference for the workflow. The reference can be a branch or tag name. Required for 'run_workflow' method. (string, optional) + - `repo`: Repository name (string, required) + - `run_id`: The ID of the workflow run. Required for all methods except 'run_workflow'. (number, optional) + - `workflow_id`: The workflow ID (numeric) or workflow file name (e.g., main.yml, ci.yaml). Required for 'run_workflow' method. (string, optional) + +>>>>>>> 4ed0310 (Complete OAuth scope implementation for all tools) - **cancel_workflow_run** - Cancel workflow run + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `run_id`: The unique identifier of the workflow run (number, required) - **delete_workflow_run_logs** - Delete workflow logs + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `run_id`: The unique identifier of the workflow run (number, required) - **download_workflow_run_artifact** - Download workflow artifact + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `artifact_id`: The unique identifier of the artifact (number, required) - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - **get_job_logs** - Get job logs + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `failed_only`: When true, gets logs for all failed jobs in run_id (boolean, optional) - `job_id`: The unique identifier of the workflow job (required for single job logs) (number, optional) - `owner`: Repository owner (string, required) @@ -516,22 +567,44 @@ The following sets of tools are available: - `run_id`: Workflow run ID (required when using failed_only) (number, optional) - `tail_lines`: Number of lines to return from the end of the log (number, optional) +<<<<<<< HEAD +======= +- **get_job_logs** - Get GitHub Actions workflow job logs + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` + - `failed_only`: When true, gets logs for all failed jobs in the workflow run specified by run_id. Requires run_id to be provided. (boolean, optional) + - `job_id`: The unique identifier of the workflow job. Required when getting logs for a single job. (number, optional) + - `owner`: Repository owner (string, required) + - `repo`: Repository name (string, required) + - `return_content`: Returns actual log content instead of URLs (boolean, optional) + - `run_id`: The unique identifier of the workflow run. Required when failed_only is true to get logs for all failed jobs in the run. (number, optional) + - `tail_lines`: Number of lines to return from the end of the log (number, optional) + +>>>>>>> 4ed0310 (Complete OAuth scope implementation for all tools) - **get_workflow_run** - Get workflow run + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `run_id`: The unique identifier of the workflow run (number, required) - **get_workflow_run_logs** - Get workflow run logs + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `run_id`: The unique identifier of the workflow run (number, required) - **get_workflow_run_usage** - Get workflow usage + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `run_id`: The unique identifier of the workflow run (number, required) - **list_workflow_jobs** - List workflow jobs + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `filter`: Filters jobs by their completed_at timestamp (string, optional) - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) @@ -540,6 +613,8 @@ The following sets of tools are available: - `run_id`: The unique identifier of the workflow run (number, required) - **list_workflow_run_artifacts** - List workflow artifacts + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) @@ -547,6 +622,8 @@ The following sets of tools are available: - `run_id`: The unique identifier of the workflow run (number, required) - **list_workflow_runs** - List workflow runs + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `actor`: Returns someone's workflow runs. Use the login for the user who created the workflow run. (string, optional) - `branch`: Returns workflow runs associated with a branch. Use the name of the branch. (string, optional) - `event`: Returns workflow runs for a specific event type (string, optional) @@ -558,22 +635,30 @@ The following sets of tools are available: - `workflow_id`: The workflow ID or workflow file name (string, required) - **list_workflows** - List workflows + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) - `repo`: Repository name (string, required) - **rerun_failed_jobs** - Rerun failed jobs + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `run_id`: The unique identifier of the workflow run (number, required) - **rerun_workflow_run** - Rerun workflow run + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `run_id`: The unique identifier of the workflow run (number, required) - **run_workflow** - Run workflow + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `inputs`: Inputs the workflow accepts (object, optional) - `owner`: Repository owner (string, required) - `ref`: The git reference for the workflow. The reference can be a branch or tag name. (string, required) @@ -587,11 +672,15 @@ The following sets of tools are available: codescan Code Security - **get_code_scanning_alert** - Get code scanning alert + - **Required OAuth Scopes**: `security_events` + - **Accepted OAuth Scopes**: `security_events`, `repo` - `alertNumber`: The number of the alert. (number, required) - `owner`: The owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) - **list_code_scanning_alerts** - List code scanning alerts + - **Required OAuth Scopes**: `security_events` + - **Accepted OAuth Scopes**: `security_events`, `repo` - `owner`: The owner of the repository. (string, required) - `ref`: The Git reference for the results you want to list. (string, optional) - `repo`: The name of the repository. (string, required) @@ -609,10 +698,14 @@ The following sets of tools are available: - No parameters required - **get_team_members** - Get team members + - **Required OAuth Scopes**: `read:org` + - **Accepted OAuth Scopes**: `read:org`, `write:org`, `admin:org` - `org`: Organization login (owner) that contains the team. (string, required) - `team_slug`: Team slug (string, required) - **get_teams** - Get teams + - **Required OAuth Scopes**: `read:org` + - **Accepted OAuth Scopes**: `read:org`, `write:org`, `admin:org` - `user`: Username to get teams for. If not provided, uses the authenticated user. (string, optional) @@ -622,11 +715,15 @@ The following sets of tools are available: dependabot Dependabot - **get_dependabot_alert** - Get dependabot alert + - **Required OAuth Scopes**: `security_events` + - **Accepted OAuth Scopes**: `security_events`, `repo` - `alertNumber`: The number of the alert. (number, required) - `owner`: The owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) - **list_dependabot_alerts** - List dependabot alerts + - **Required OAuth Scopes**: `security_events` + - **Accepted OAuth Scopes**: `security_events`, `repo` - `owner`: The owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) - `severity`: Filter dependabot alerts by severity (string, optional) @@ -639,11 +736,15 @@ The following sets of tools are available: comment-discussion Discussions - **get_discussion** - Get discussion + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `discussionNumber`: Discussion Number (number, required) - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - **get_discussion_comments** - Get discussion comments + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `after`: Cursor for pagination. Use the endCursor from the previous page's PageInfo for GraphQL APIs. (string, optional) - `discussionNumber`: Discussion Number (number, required) - `owner`: Repository owner (string, required) @@ -651,10 +752,14 @@ The following sets of tools are available: - `repo`: Repository name (string, required) - **list_discussion_categories** - List discussion categories + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name. If not provided, discussion categories will be queried at the organisation level. (string, optional) - **list_discussions** - List discussions + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `after`: Cursor for pagination. Use the endCursor from the previous page's PageInfo for GraphQL APIs. (string, optional) - `category`: Optional filter by discussion category ID. If provided, only discussions with this category are listed. (string, optional) - `direction`: Order direction. (string, optional) @@ -670,6 +775,8 @@ The following sets of tools are available: logo-gist Gists - **create_gist** - Create Gist + - **Required OAuth Scopes**: `gist` + - **Accepted OAuth Scopes**: `gist` - `content`: Content for simple single-file gist creation (string, required) - `description`: Description of the gist (string, optional) - `filename`: Filename for simple single-file gist creation (string, required) @@ -685,6 +792,8 @@ The following sets of tools are available: - `username`: GitHub username (omit for authenticated user's gists) (string, optional) - **update_gist** - Update Gist + - **Required OAuth Scopes**: `gist` + - **Accepted OAuth Scopes**: `gist` - `content`: Content for the file (string, required) - `description`: Updated description of the gist (string, optional) - `filename`: Filename to update or create (string, required) @@ -697,6 +806,8 @@ The following sets of tools are available: git-branch Git - **get_repository_tree** - Get repository tree + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (username or organization) (string, required) - `path_filter`: Optional path prefix to filter the tree results (e.g., 'src/' to only show files in the src directory) (string, optional) - `recursive`: Setting this parameter to true returns the objects or subtrees referenced by the tree. Default is false (boolean, optional) @@ -710,22 +821,34 @@ The following sets of tools are available: issue-opened Issues - **add_issue_comment** - Add comment to issue + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `body`: Comment content (string, required) - `issue_number`: Issue number to comment on (number, required) - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - **assign_copilot_to_issue** - Assign Copilot to issue +<<<<<<< HEAD - `issue_number`: Issue number (number, required) +======= + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` + - `issueNumber`: Issue number (number, required) +>>>>>>> 4ed0310 (Complete OAuth scope implementation for all tools) - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - **get_label** - Get a specific label from a repository. + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `name`: Label name. (string, required) - `owner`: Repository owner (username or organization name) (string, required) - `repo`: Repository name (string, required) - **issue_read** - Get issue details + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `issue_number`: The number of the issue (number, required) - `method`: The read operation to perform on a single issue. Options are: @@ -740,6 +863,8 @@ The following sets of tools are available: - `repo`: The name of the repository (string, required) - **issue_write** - Create or update issue. + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `assignees`: Usernames to assign to this issue (string[], optional) - `body`: Issue body content (string, optional) - `duplicate_of`: Issue number that this issue is a duplicate of. Only used when state_reason is 'duplicate'. (number, optional) @@ -759,9 +884,13 @@ The following sets of tools are available: - `type`: Type of this issue. Only use if the repository has issue types configured. Use list_issue_types tool to get valid type values for the organization. If the repository doesn't support issue types, omit this parameter. (string, optional) - **list_issue_types** - List available issue types + - **Required OAuth Scopes**: `read:org` + - **Accepted OAuth Scopes**: `read:org`, `write:org`, `admin:org` - `owner`: The organization owner of the repository (string, required) - **list_issues** - List issues + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `after`: Cursor for pagination. Use the endCursor from the previous page's PageInfo for GraphQL APIs. (string, optional) - `direction`: Order direction. If provided, the 'orderBy' also needs to be provided. (string, optional) - `labels`: Filter by labels (string[], optional) @@ -773,6 +902,8 @@ The following sets of tools are available: - `state`: Filter by state, by default both open and closed issues are returned when not provided (string, optional) - **search_issues** - Search issues + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `order`: Sort order (string, optional) - `owner`: Optional repository owner. If provided with repo, only issues for this repository are listed. (string, optional) - `page`: Page number for pagination (min 1) (number, optional) @@ -782,6 +913,8 @@ The following sets of tools are available: - `sort`: Sort field by number of matches of categories, defaults to best match (string, optional) - **sub_issue_write** - Change sub-issue + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `after_id`: The ID of the sub-issue to be prioritized after (either after_id OR before_id should be specified) (number, optional) - `before_id`: The ID of the sub-issue to be prioritized before (either after_id OR before_id should be specified) (number, optional) - `issue_number`: The number of the parent issue (number, required) @@ -803,11 +936,15 @@ The following sets of tools are available: tag Labels - **get_label** - Get a specific label from a repository. + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `name`: Label name. (string, required) - `owner`: Repository owner (username or organization name) (string, required) - `repo`: Repository name (string, required) - **label_write** - Write operations on repository labels. + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `color`: Label color as 6-character hex code without '#' prefix (e.g., 'f29513'). Required for 'create', optional for 'update'. (string, optional) - `description`: Label description text. Optional for 'create' and 'update'. (string, optional) - `method`: Operation to perform: 'create', 'update', or 'delete' (string, required) @@ -817,6 +954,8 @@ The following sets of tools are available: - `repo`: Repository name (string, required) - **list_label** - List labels from a repository + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (username or organization name) - required for all operations (string, required) - `repo`: Repository name - required for all operations (string, required) @@ -827,13 +966,19 @@ The following sets of tools are available: bell Notifications - **dismiss_notification** - Dismiss notification + - **Required OAuth Scopes**: `notifications` + - **Accepted OAuth Scopes**: `notifications` - `state`: The new state of the notification (read/done) (string, required) - `threadID`: The ID of the notification thread (string, required) - **get_notification_details** - Get notification details + - **Required OAuth Scopes**: `notifications` + - **Accepted OAuth Scopes**: `notifications` - `notificationID`: The ID of the notification (string, required) - **list_notifications** - List notifications + - **Required OAuth Scopes**: `notifications` + - **Accepted OAuth Scopes**: `notifications` - `before`: Only show notifications updated before the given time (ISO 8601 format) (string, optional) - `filter`: Filter notifications to, use default unless specified. Read notifications are ones that have already been acknowledged by the user. Participating notifications are those that the user is directly involved in, such as issues or pull requests they have commented on or created. (string, optional) - `owner`: Optional repository owner. If provided with repo, only notifications for this repository are listed. (string, optional) @@ -843,15 +988,21 @@ The following sets of tools are available: - `since`: Only show notifications updated after the given time (ISO 8601 format) (string, optional) - **manage_notification_subscription** - Manage notification subscription + - **Required OAuth Scopes**: `notifications` + - **Accepted OAuth Scopes**: `notifications` - `action`: Action to perform: ignore, watch, or delete the notification subscription. (string, required) - `notificationID`: The ID of the notification thread. (string, required) - **manage_repository_notification_subscription** - Manage repository notification subscription + - **Required OAuth Scopes**: `notifications` + - **Accepted OAuth Scopes**: `notifications` - `action`: Action to perform: ignore, watch, or delete the repository notification subscription. (string, required) - `owner`: The account owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) - **mark_all_notifications_read** - Mark all notifications as read + - **Required OAuth Scopes**: `notifications` + - **Accepted OAuth Scopes**: `notifications` - `lastReadAt`: Describes the last point that notifications were checked (optional). Default: Now (string, optional) - `owner`: Optional repository owner. If provided with repo, only notifications for this repository are marked as read. (string, optional) - `repo`: Optional repository name. If provided with owner, only notifications for this repository are marked as read. (string, optional) @@ -863,6 +1014,8 @@ The following sets of tools are available: organization Organizations - **search_orgs** - Search organizations + - **Required OAuth Scopes**: `read:org` + - **Accepted OAuth Scopes**: `read:org`, `write:org`, `admin:org` - `order`: Sort order (string, optional) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) @@ -876,6 +1029,8 @@ The following sets of tools are available: project Projects - **add_project_item** - Add project item + - **Required OAuth Scopes**: `project` + - **Accepted OAuth Scopes**: `project` - `item_id`: The numeric ID of the issue or pull request to add to the project. (number, required) - `item_type`: The item's type, either issue or pull_request. (string, required) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) @@ -883,23 +1038,31 @@ The following sets of tools are available: - `project_number`: The project's number. (number, required) - **delete_project_item** - Delete project item + - **Required OAuth Scopes**: `project` + - **Accepted OAuth Scopes**: `project` - `item_id`: The internal project item ID to delete from the project (not the issue or pull request ID). (number, required) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) - `owner_type`: Owner type (string, required) - `project_number`: The project's number. (number, required) - **get_project** - Get project + - **Required OAuth Scopes**: `read:project` + - **Accepted OAuth Scopes**: `read:project`, `project` - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) - `owner_type`: Owner type (string, required) - `project_number`: The project's number (number, required) - **get_project_field** - Get project field + - **Required OAuth Scopes**: `read:project` + - **Accepted OAuth Scopes**: `read:project`, `project` - `field_id`: The field's id. (number, required) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) - `owner_type`: Owner type (string, required) - `project_number`: The project's number. (number, required) - **get_project_item** - Get project item + - **Required OAuth Scopes**: `read:project` + - **Accepted OAuth Scopes**: `read:project`, `project` - `fields`: Specific list of field IDs to include in the response (e.g. ["102589", "985201", "169875"]). If not provided, only the title field is included. (string[], optional) - `item_id`: The item's ID. (number, required) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) @@ -907,6 +1070,8 @@ The following sets of tools are available: - `project_number`: The project's number. (number, required) - **list_project_fields** - List project fields + - **Required OAuth Scopes**: `read:project` + - **Accepted OAuth Scopes**: `read:project`, `project` - `after`: Forward pagination cursor from previous pageInfo.nextCursor. (string, optional) - `before`: Backward pagination cursor from previous pageInfo.prevCursor (rare). (string, optional) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) @@ -915,6 +1080,8 @@ The following sets of tools are available: - `project_number`: The project's number. (number, required) - **list_project_items** - List project items + - **Required OAuth Scopes**: `read:project` + - **Accepted OAuth Scopes**: `read:project`, `project` - `after`: Forward pagination cursor from previous pageInfo.nextCursor. (string, optional) - `before`: Backward pagination cursor from previous pageInfo.prevCursor (rare). (string, optional) - `fields`: Field IDs to include (e.g. ["102589", "985201"]). CRITICAL: Always provide to get field values. Without this, only titles returned. (string[], optional) @@ -925,6 +1092,8 @@ The following sets of tools are available: - `query`: Query string for advanced filtering of project items using GitHub's project filtering syntax. (string, optional) - **list_projects** - List projects + - **Required OAuth Scopes**: `read:project` + - **Accepted OAuth Scopes**: `read:project`, `project` - `after`: Forward pagination cursor from previous pageInfo.nextCursor. (string, optional) - `before`: Backward pagination cursor from previous pageInfo.prevCursor (rare). (string, optional) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) @@ -933,6 +1102,8 @@ The following sets of tools are available: - `query`: Filter projects by title text and open/closed state; permitted qualifiers: is:open, is:closed; examples: "roadmap is:open", "is:open feature planning". (string, optional) - **update_project_item** - Update project item + - **Required OAuth Scopes**: `project` + - **Accepted OAuth Scopes**: `project` - `item_id`: The unique identifier of the project item. This is not the issue or pull request ID. (number, required) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) - `owner_type`: Owner type (string, required) @@ -946,6 +1117,8 @@ The following sets of tools are available: git-pull-request Pull Requests - **add_comment_to_pending_review** - Add review comment to the requester's latest pending pull request review + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `body`: The text of the review comment (string, required) - `line`: The line of the blob in the pull request diff that the comment applies to. For multi-line comments, the last line of the range (number, optional) - `owner`: Repository owner (string, required) @@ -958,6 +1131,8 @@ The following sets of tools are available: - `subjectType`: The level at which the comment is targeted (string, required) - **create_pull_request** - Open new pull request + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `base`: Branch to merge into (string, required) - `body`: PR description (string, optional) - `draft`: Create as draft PR (boolean, optional) @@ -968,6 +1143,8 @@ The following sets of tools are available: - `title`: PR title (string, required) - **list_pull_requests** - List pull requests + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `base`: Filter by base branch (string, optional) - `direction`: Sort direction (string, optional) - `head`: Filter by head user/org and branch (string, optional) @@ -979,6 +1156,8 @@ The following sets of tools are available: - `state`: Filter by state (string, optional) - **merge_pull_request** - Merge pull request + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `commit_message`: Extra detail for merge commit (string, optional) - `commit_title`: Title for merge commit (string, optional) - `merge_method`: Merge method (string, optional) @@ -987,6 +1166,8 @@ The following sets of tools are available: - `repo`: Repository name (string, required) - **pull_request_read** - Get details for a single pull request + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `method`: Action to specify what pull request data needs to be retrieved from GitHub. Possible options: 1. get - Get details of a specific pull request. @@ -1004,6 +1185,8 @@ The following sets of tools are available: - `repo`: Repository name (string, required) - **pull_request_review_write** - Write operations (create, submit, delete) on pull request reviews. + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `body`: Review comment text (string, optional) - `commitID`: SHA of commit to review (string, optional) - `event`: Review action to perform. (string, optional) @@ -1013,11 +1196,15 @@ The following sets of tools are available: - `repo`: Repository name (string, required) - **request_copilot_review** - Request Copilot review + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `pullNumber`: Pull request number (number, required) - `repo`: Repository name (string, required) - **search_pull_requests** - Search pull requests + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `order`: Sort order (string, optional) - `owner`: Optional repository owner. If provided with repo, only pull requests for this repository are listed. (string, optional) - `page`: Page number for pagination (min 1) (number, optional) @@ -1027,6 +1214,8 @@ The following sets of tools are available: - `sort`: Sort field by number of matches of categories, defaults to best match (string, optional) - **update_pull_request** - Edit pull request + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `base`: New base branch name (string, optional) - `body`: New description (string, optional) - `draft`: Mark pull request as draft (true) or ready for review (false) (boolean, optional) @@ -1039,6 +1228,8 @@ The following sets of tools are available: - `title`: New title (string, optional) - **update_pull_request_branch** - Update pull request branch + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `expectedHeadSha`: The expected SHA of the pull request's HEAD ref (string, optional) - `owner`: Repository owner (string, required) - `pullNumber`: Pull request number (number, required) @@ -1051,12 +1242,16 @@ The following sets of tools are available: repo Repositories - **create_branch** - Create branch + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `branch`: Name for new branch (string, required) - `from_branch`: Source branch (defaults to repo default) (string, optional) - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - **create_or_update_file** - Create or update file + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `branch`: Branch to create/update the file in (string, required) - `content`: Content of the file (string, required) - `message`: Commit message (string, required) @@ -1066,6 +1261,8 @@ The following sets of tools are available: - `sha`: The blob SHA of the file being replaced. (string, optional) - **create_repository** - Create repository + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `autoInit`: Initialize with README (boolean, optional) - `description`: Repository description (string, optional) - `name`: Repository name (string, required) @@ -1073,6 +1270,8 @@ The following sets of tools are available: - `private`: Whether repo should be private (boolean, optional) - **delete_file** - Delete file + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `branch`: Branch to delete the file from (string, required) - `message`: Commit message (string, required) - `owner`: Repository owner (username or organization) (string, required) @@ -1080,11 +1279,15 @@ The following sets of tools are available: - `repo`: Repository name (string, required) - **fork_repository** - Fork repository + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `organization`: Organization to fork to (string, optional) - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - **get_commit** - Get commit details + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `include_diff`: Whether to include file diffs and stats in the response. Default is true. (boolean, optional) - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) @@ -1093,6 +1296,8 @@ The following sets of tools are available: - `sha`: Commit SHA, branch name, or tag name (string, required) - **get_file_contents** - Get file or directory contents + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (username or organization) (string, required) - `path`: Path to file/directory (string, optional) - `ref`: Accepts optional git refs such as `refs/tags/{tag}`, `refs/heads/{branch}` or `refs/pull/{pr_number}/head` (string, optional) @@ -1100,26 +1305,36 @@ The following sets of tools are available: - `sha`: Accepts optional commit SHA. If specified, it will be used instead of ref (string, optional) - **get_latest_release** - Get latest release + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - **get_release_by_tag** - Get a release by tag name + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `tag`: Tag name (e.g., 'v1.0.0') (string, required) - **get_tag** - Get tag details + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `tag`: Tag name (string, required) - **list_branches** - List branches + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) - `repo`: Repository name (string, required) - **list_commits** - List commits + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `author`: Author username or email address to filter commits by (string, optional) - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) @@ -1128,18 +1343,24 @@ The following sets of tools are available: - `sha`: Commit SHA, branch or tag name to list commits of. If not provided, uses the default branch of the repository. If a commit SHA is provided, will list commits up to that SHA. (string, optional) - **list_releases** - List releases + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) - `repo`: Repository name (string, required) - **list_tags** - List tags + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) - `repo`: Repository name (string, required) - **push_files** - Push files to repository + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `branch`: Branch to push to (string, required) - `files`: Array of file objects to push, each object with path (string) and content (string) (object[], required) - `message`: Commit message (string, required) @@ -1147,6 +1368,8 @@ The following sets of tools are available: - `repo`: Repository name (string, required) - **search_code** - Search code + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `order`: Sort order for results (string, optional) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) @@ -1154,6 +1377,8 @@ The following sets of tools are available: - `sort`: Sort field ('indexed' only) (string, optional) - **search_repositories** - Search repositories + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `minimal_output`: Return minimal repository information (default: true). When false, returns full GitHub API repository objects. (boolean, optional) - `order`: Sort order (string, optional) - `page`: Page number for pagination (min 1) (number, optional) @@ -1168,11 +1393,15 @@ The following sets of tools are available: shield-lock Secret Protection - **get_secret_scanning_alert** - Get secret scanning alert + - **Required OAuth Scopes**: `security_events` + - **Accepted OAuth Scopes**: `security_events`, `repo` - `alertNumber`: The number of the alert. (number, required) - `owner`: The owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) - **list_secret_scanning_alerts** - List secret scanning alerts + - **Required OAuth Scopes**: `security_events` + - **Accepted OAuth Scopes**: `security_events`, `repo` - `owner`: The owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) - `resolution`: Filter by resolution (string, optional) @@ -1186,9 +1415,13 @@ The following sets of tools are available: shield Security Advisories - **get_global_security_advisory** - Get a global security advisory + - **Required OAuth Scopes**: `security_events` + - **Accepted OAuth Scopes**: `security_events`, `repo` - `ghsaId`: GitHub Security Advisory ID (format: GHSA-xxxx-xxxx-xxxx). (string, required) - **list_global_security_advisories** - List global security advisories + - **Required OAuth Scopes**: `security_events` + - **Accepted OAuth Scopes**: `security_events`, `repo` - `affects`: Filter advisories by affected package or version (e.g. "package1,package2@1.0.0"). (string, optional) - `cveId`: Filter by CVE ID. (string, optional) - `cwes`: Filter by Common Weakness Enumeration IDs (e.g. ["79", "284", "22"]). (string[], optional) @@ -1202,12 +1435,16 @@ The following sets of tools are available: - `updated`: Filter by update date or date range (ISO 8601 date or range). (string, optional) - **list_org_repository_security_advisories** - List org repository security advisories + - **Required OAuth Scopes**: `security_events` + - **Accepted OAuth Scopes**: `security_events`, `repo` - `direction`: Sort direction. (string, optional) - `org`: The organization login. (string, required) - `sort`: Sort field. (string, optional) - `state`: Filter by advisory state. (string, optional) - **list_repository_security_advisories** - List repository security advisories + - **Required OAuth Scopes**: `security_events` + - **Accepted OAuth Scopes**: `security_events`, `repo` - `direction`: Sort direction. (string, optional) - `owner`: The owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) @@ -1221,6 +1458,8 @@ The following sets of tools are available: star Stargazers - **list_starred_repositories** - List starred repositories + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `direction`: The direction to sort the results by. (string, optional) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) @@ -1228,10 +1467,14 @@ The following sets of tools are available: - `username`: Username to list starred repositories for. Defaults to the authenticated user. (string, optional) - **star_repository** - Star repository + - **Required OAuth Scopes**: `public_repo` + - **Accepted OAuth Scopes**: `public_repo`, `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - **unstar_repository** - Unstar repository + - **Required OAuth Scopes**: `public_repo` + - **Accepted OAuth Scopes**: `public_repo`, `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) @@ -1242,6 +1485,8 @@ The following sets of tools are available: people Users - **search_users** - Search users + - **Required OAuth Scopes**: `repo` + - **Accepted OAuth Scopes**: `repo` - `order`: Sort order (string, optional) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) diff --git a/cmd/github-mcp-server/generate_docs.go b/cmd/github-mcp-server/generate_docs.go index 65c01c8fa..f28aa1bc2 100644 --- a/cmd/github-mcp-server/generate_docs.go +++ b/cmd/github-mcp-server/generate_docs.go @@ -12,7 +12,6 @@ import ( "github.com/github/github-mcp-server/pkg/inventory" "github.com/github/github-mcp-server/pkg/translations" "github.com/google/jsonschema-go/jsonschema" - "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/spf13/cobra" ) @@ -189,7 +188,7 @@ func generateToolsDoc(r *inventory.Inventory) string { currentToolsetID = tool.Toolset.ID currentToolsetIcon = tool.Toolset.Icon } - writeToolDoc(&toolBuf, tool.Tool) + writeToolDoc(&toolBuf, tool) toolBuf.WriteString("\n\n") } @@ -223,16 +222,26 @@ func formatToolsetName(name string) string { } } -func writeToolDoc(buf *strings.Builder, tool mcp.Tool) { +func writeToolDoc(buf *strings.Builder, tool inventory.ServerTool) { // Tool name (no icon - section header already has the toolset icon) - fmt.Fprintf(buf, "- **%s** - %s\n", tool.Name, tool.Annotations.Title) + fmt.Fprintf(buf, "- **%s** - %s\n", tool.Tool.Name, tool.Tool.Annotations.Title) + + // OAuth scopes if present + if len(tool.RequiredScopes) > 0 || len(tool.AcceptedScopes) > 0 { + if len(tool.RequiredScopes) > 0 { + fmt.Fprintf(buf, " - **Required OAuth Scopes**: `%s`\n", strings.Join(tool.RequiredScopes, "`, `")) + } + if len(tool.AcceptedScopes) > 0 { + fmt.Fprintf(buf, " - **Accepted OAuth Scopes**: `%s`\n", strings.Join(tool.AcceptedScopes, "`, `")) + } + } // Parameters - if tool.InputSchema == nil { + if tool.Tool.InputSchema == nil { buf.WriteString(" - No parameters required") return } - schema, ok := tool.InputSchema.(*jsonschema.Schema) + schema, ok := tool.Tool.InputSchema.(*jsonschema.Schema) if !ok || schema == nil { buf.WriteString(" - No parameters required") return diff --git a/pkg/github/actions.go b/pkg/github/actions.go index cffab4b50..c4ba97983 100644 --- a/pkg/github/actions.go +++ b/pkg/github/actions.go @@ -51,7 +51,7 @@ const ( // ListWorkflows creates a tool to list workflows in a repository func ListWorkflows(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewTool( + tool := NewToolWithScopes( ToolsetMetadataActions, mcp.Tool{ Name: "list_workflows", @@ -75,6 +75,8 @@ func ListWorkflows(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -122,7 +124,7 @@ func ListWorkflows(t translations.TranslationHelperFunc) inventory.ServerTool { // ListWorkflowRuns creates a tool to list workflow runs for a specific workflow func ListWorkflowRuns(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewTool( + tool := NewToolWithScopes( ToolsetMetadataActions, mcp.Tool{ Name: "list_workflow_runs", @@ -201,6 +203,8 @@ func ListWorkflowRuns(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo", "workflow_id"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -276,7 +280,7 @@ func ListWorkflowRuns(t translations.TranslationHelperFunc) inventory.ServerTool // RunWorkflow creates a tool to run an Actions workflow func RunWorkflow(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewTool( + tool := NewToolWithScopes( ToolsetMetadataActions, mcp.Tool{ Name: "run_workflow", @@ -312,6 +316,8 @@ func RunWorkflow(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "workflow_id", "ref"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -388,7 +394,7 @@ func RunWorkflow(t translations.TranslationHelperFunc) inventory.ServerTool { // GetWorkflowRun creates a tool to get details of a specific workflow run func GetWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewTool( + tool := NewToolWithScopes( ToolsetMetadataActions, mcp.Tool{ Name: "get_workflow_run", @@ -416,6 +422,8 @@ func GetWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "run_id"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -456,7 +464,7 @@ func GetWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerTool { // GetWorkflowRunLogs creates a tool to download logs for a specific workflow run func GetWorkflowRunLogs(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewTool( + tool := NewToolWithScopes( ToolsetMetadataActions, mcp.Tool{ Name: "get_workflow_run_logs", @@ -484,6 +492,8 @@ func GetWorkflowRunLogs(t translations.TranslationHelperFunc) inventory.ServerTo Required: []string{"owner", "repo", "run_id"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -534,7 +544,7 @@ func GetWorkflowRunLogs(t translations.TranslationHelperFunc) inventory.ServerTo // ListWorkflowJobs creates a tool to list jobs for a specific workflow run func ListWorkflowJobs(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewTool( + tool := NewToolWithScopes( ToolsetMetadataActions, mcp.Tool{ Name: "list_workflow_jobs", @@ -567,6 +577,8 @@ func ListWorkflowJobs(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo", "run_id"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -634,7 +646,7 @@ func ListWorkflowJobs(t translations.TranslationHelperFunc) inventory.ServerTool // GetJobLogs creates a tool to download logs for a specific workflow job or efficiently get all failed job logs for a workflow run func GetJobLogs(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewTool( + tool := NewToolWithScopes( ToolsetMetadataActions, mcp.Tool{ Name: "get_job_logs", @@ -679,6 +691,8 @@ func GetJobLogs(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -899,7 +913,7 @@ func downloadLogContent(ctx context.Context, logURL string, tailLines int, maxLi // RerunWorkflowRun creates a tool to re-run an entire workflow run func RerunWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewTool( + tool := NewToolWithScopes( ToolsetMetadataActions, mcp.Tool{ Name: "rerun_workflow_run", @@ -927,6 +941,8 @@ func RerunWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo", "run_id"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -974,7 +990,7 @@ func RerunWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerTool // RerunFailedJobs creates a tool to re-run only the failed jobs in a workflow run func RerunFailedJobs(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewTool( + tool := NewToolWithScopes( ToolsetMetadataActions, mcp.Tool{ Name: "rerun_failed_jobs", @@ -1002,6 +1018,8 @@ func RerunFailedJobs(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo", "run_id"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -1049,7 +1067,7 @@ func RerunFailedJobs(t translations.TranslationHelperFunc) inventory.ServerTool // CancelWorkflowRun creates a tool to cancel a workflow run func CancelWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewTool( + tool := NewToolWithScopes( ToolsetMetadataActions, mcp.Tool{ Name: "cancel_workflow_run", @@ -1077,6 +1095,8 @@ func CancelWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"owner", "repo", "run_id"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -1126,7 +1146,7 @@ func CancelWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerToo // ListWorkflowRunArtifacts creates a tool to list artifacts for a workflow run func ListWorkflowRunArtifacts(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewTool( + tool := NewToolWithScopes( ToolsetMetadataActions, mcp.Tool{ Name: "list_workflow_run_artifacts", @@ -1154,6 +1174,8 @@ func ListWorkflowRunArtifacts(t translations.TranslationHelperFunc) inventory.Se Required: []string{"owner", "repo", "run_id"}, }), }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -1206,7 +1228,7 @@ func ListWorkflowRunArtifacts(t translations.TranslationHelperFunc) inventory.Se // DownloadWorkflowRunArtifact creates a tool to download a workflow run artifact func DownloadWorkflowRunArtifact(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewTool( + tool := NewToolWithScopes( ToolsetMetadataActions, mcp.Tool{ Name: "download_workflow_run_artifact", @@ -1234,6 +1256,8 @@ func DownloadWorkflowRunArtifact(t translations.TranslationHelperFunc) inventory Required: []string{"owner", "repo", "artifact_id"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -1283,7 +1307,7 @@ func DownloadWorkflowRunArtifact(t translations.TranslationHelperFunc) inventory // DeleteWorkflowRunLogs creates a tool to delete logs for a workflow run func DeleteWorkflowRunLogs(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewTool( + tool := NewToolWithScopes( ToolsetMetadataActions, mcp.Tool{ Name: "delete_workflow_run_logs", @@ -1312,6 +1336,8 @@ func DeleteWorkflowRunLogs(t translations.TranslationHelperFunc) inventory.Serve Required: []string{"owner", "repo", "run_id"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -1359,7 +1385,7 @@ func DeleteWorkflowRunLogs(t translations.TranslationHelperFunc) inventory.Serve // GetWorkflowRunUsage creates a tool to get usage metrics for a workflow run func GetWorkflowRunUsage(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewTool( + tool := NewToolWithScopes( ToolsetMetadataActions, mcp.Tool{ Name: "get_workflow_run_usage", @@ -1387,6 +1413,8 @@ func GetWorkflowRunUsage(t translations.TranslationHelperFunc) inventory.ServerT Required: []string{"owner", "repo", "run_id"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -1427,7 +1455,7 @@ func GetWorkflowRunUsage(t translations.TranslationHelperFunc) inventory.ServerT // ActionsList returns the tool and handler for listing GitHub Actions resources. func ActionsList(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewTool( + tool := NewToolWithScopes( ToolsetMetadataActions, mcp.Tool{ Name: "actions_list", @@ -1551,6 +1579,8 @@ Use this tool to list workflows in a repository, or list workflow runs, jobs, an Required: []string{"method", "owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1622,7 +1652,7 @@ Use this tool to list workflows in a repository, or list workflow runs, jobs, an // ActionsGet returns the tool and handler for getting GitHub Actions resources. func ActionsGet(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewTool( + tool := NewToolWithScopes( ToolsetMetadataActions, mcp.Tool{ Name: "actions_get", @@ -1669,6 +1699,8 @@ Use this tool to get details about individual workflows, workflow runs, jobs, an Required: []string{"method", "owner", "repo", "resource_id"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1730,7 +1762,7 @@ Use this tool to get details about individual workflows, workflow runs, jobs, an // ActionsRunTrigger returns the tool and handler for triggering GitHub Actions workflows. func ActionsRunTrigger(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewTool( + tool := NewToolWithScopes( ToolsetMetadataActions, mcp.Tool{ Name: "actions_run_trigger", @@ -1782,6 +1814,8 @@ func ActionsRunTrigger(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"method", "owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1848,7 +1882,7 @@ func ActionsRunTrigger(t translations.TranslationHelperFunc) inventory.ServerToo // ActionsGetJobLogs returns the tool and handler for getting workflow job logs. func ActionsGetJobLogs(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewTool( + tool := NewToolWithScopes( ToolsetMetadataActions, mcp.Tool{ Name: "get_job_logs", @@ -1896,6 +1930,8 @@ For single job logs, provide job_id. For all failed jobs in a run, provide run_i Required: []string{"owner", "repo"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/issues.go b/pkg/github/issues.go index 406e980fe..9ecf4d264 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -11,10 +11,10 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" - "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/lockdown" "github.com/github/github-mcp-server/pkg/octicons" "github.com/github/github-mcp-server/pkg/sanitize" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/go-viper/mapstructure/v2" @@ -264,7 +264,7 @@ Options are: } WithPagination(schema) - return NewTool( + return NewToolWithScopes( ToolsetMetadataIssues, mcp.Tool{ Name: "issue_read", @@ -275,6 +275,8 @@ Options are: }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { method, err := RequiredParam[string](args, "method") if err != nil { @@ -959,7 +961,7 @@ func SearchIssues(t translations.TranslationHelperFunc) inventory.ServerTool { } WithPagination(schema) - return NewTool( + return NewToolWithScopes( ToolsetMetadataIssues, mcp.Tool{ Name: "search_issues", @@ -970,6 +972,8 @@ func SearchIssues(t translations.TranslationHelperFunc) inventory.ServerTool { }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { result, err := searchHandler(ctx, deps.GetClient, args, "issue", "failed to search issues") return result, nil, err @@ -1383,7 +1387,7 @@ func ListIssues(t translations.TranslationHelperFunc) inventory.ServerTool { } WithCursorPagination(schema) - return NewTool( + return NewToolWithScopes( ToolsetMetadataIssues, mcp.Tool{ Name: "list_issues", @@ -1394,6 +1398,8 @@ func ListIssues(t translations.TranslationHelperFunc) inventory.ServerTool { }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1621,7 +1627,7 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server }, } - return NewTool( + return NewToolWithScopes( ToolsetMetadataIssues, mcp.Tool{ Name: "assign_copilot_to_issue", @@ -1651,6 +1657,8 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server Required: []string{"owner", "repo", "issue_number"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { var params struct { Owner string `mapstructure:"owner"` diff --git a/pkg/github/pullrequests.go b/pkg/github/pullrequests.go index 400918858..e81f87ca0 100644 --- a/pkg/github/pullrequests.go +++ b/pkg/github/pullrequests.go @@ -15,10 +15,10 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" - "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/lockdown" "github.com/github/github-mcp-server/pkg/octicons" "github.com/github/github-mcp-server/pkg/sanitize" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" ) @@ -59,7 +59,7 @@ Possible options: } WithPagination(schema) - return NewTool( + return NewToolWithScopes( ToolsetMetadataPullRequests, mcp.Tool{ Name: "pull_request_read", @@ -70,6 +70,8 @@ Possible options: }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { method, err := RequiredParam[string](args, "method") if err != nil { @@ -508,7 +510,7 @@ func CreatePullRequest(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"owner", "repo", "title", "head", "base"}, } - return NewTool( + return NewToolWithScopes( ToolsetMetadataPullRequests, mcp.Tool{ Name: "create_pull_request", @@ -519,6 +521,8 @@ func CreatePullRequest(t translations.TranslationHelperFunc) inventory.ServerToo }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -659,7 +663,7 @@ func UpdatePullRequest(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"owner", "repo", "pullNumber"}, } - return NewTool( + return NewToolWithScopes( ToolsetMetadataPullRequests, mcp.Tool{ Name: "update_pull_request", @@ -670,6 +674,8 @@ func UpdatePullRequest(t translations.TranslationHelperFunc) inventory.ServerToo }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -940,7 +946,7 @@ func ListPullRequests(t translations.TranslationHelperFunc) inventory.ServerTool } WithPagination(schema) - return NewTool( + return NewToolWithScopes( ToolsetMetadataPullRequests, mcp.Tool{ Name: "list_pull_requests", @@ -951,6 +957,8 @@ func ListPullRequests(t translations.TranslationHelperFunc) inventory.ServerTool }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1075,7 +1083,7 @@ func MergePullRequest(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo", "pullNumber"}, } - return NewTool( + return NewToolWithScopes( ToolsetMetadataPullRequests, mcp.Tool{ Name: "merge_pull_request", @@ -1087,6 +1095,8 @@ func MergePullRequest(t translations.TranslationHelperFunc) inventory.ServerTool }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1193,7 +1203,7 @@ func SearchPullRequests(t translations.TranslationHelperFunc) inventory.ServerTo } WithPagination(schema) - return NewTool( + return NewToolWithScopes( ToolsetMetadataPullRequests, mcp.Tool{ Name: "search_pull_requests", @@ -1204,6 +1214,8 @@ func SearchPullRequests(t translations.TranslationHelperFunc) inventory.ServerTo }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { result, err := searchHandler(ctx, deps.GetClient, args, "pr", "failed to search pull requests") return result, nil, err @@ -1235,7 +1247,7 @@ func UpdatePullRequestBranch(t translations.TranslationHelperFunc) inventory.Ser Required: []string{"owner", "repo", "pullNumber"}, } - return NewTool( + return NewToolWithScopes( ToolsetMetadataPullRequests, mcp.Tool{ Name: "update_pull_request_branch", @@ -1246,6 +1258,8 @@ func UpdatePullRequestBranch(t translations.TranslationHelperFunc) inventory.Ser }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1355,7 +1369,7 @@ func PullRequestReviewWrite(t translations.TranslationHelperFunc) inventory.Serv Required: []string{"method", "owner", "repo", "pullNumber"}, } - return NewTool( + return NewToolWithScopes( ToolsetMetadataPullRequests, mcp.Tool{ Name: "pull_request_review_write", @@ -1372,6 +1386,8 @@ Available methods: }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { var params PullRequestReviewWriteParams if err := mapstructure.Decode(args, ¶ms); err != nil { @@ -1684,7 +1700,7 @@ func AddCommentToPendingReview(t translations.TranslationHelperFunc) inventory.S Required: []string{"owner", "repo", "pullNumber", "path", "body", "subjectType"}, } - return NewTool( + return NewToolWithScopes( ToolsetMetadataPullRequests, mcp.Tool{ Name: "add_comment_to_pending_review", @@ -1695,6 +1711,8 @@ func AddCommentToPendingReview(t translations.TranslationHelperFunc) inventory.S }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { var params struct { Owner string @@ -1835,7 +1853,7 @@ func RequestCopilotReview(t translations.TranslationHelperFunc) inventory.Server Required: []string{"owner", "repo", "pullNumber"}, } - return NewTool( + return NewToolWithScopes( ToolsetMetadataPullRequests, mcp.Tool{ Name: "request_copilot_review", @@ -1847,6 +1865,8 @@ func RequestCopilotReview(t translations.TranslationHelperFunc) inventory.Server }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index f50babc32..b0c59e417 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -11,8 +11,9 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" - "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/octicons" + "github.com/github/github-mcp-server/pkg/raw" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" @@ -317,7 +318,7 @@ func ListBranches(t translations.TranslationHelperFunc) inventory.ServerTool { // CreateOrUpdateFile creates a tool to create or update a file in a GitHub repository. func CreateOrUpdateFile(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + return NewToolWithScopes( ToolsetMetadataRepos, mcp.Tool{ Name: "create_or_update_file", @@ -368,6 +369,8 @@ If the SHA is not provided, the tool will attempt to acquire it by fetching the Required: []string{"owner", "repo", "path", "content", "message", "branch"}, }, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/search.go b/pkg/github/search.go index e207479d6..d95214e55 100644 --- a/pkg/github/search.go +++ b/pkg/github/search.go @@ -46,7 +46,7 @@ func SearchRepositories(t translations.TranslationHelperFunc) inventory.ServerTo } WithPagination(schema) - return NewTool( + return NewToolWithScopes( ToolsetMetadataRepos, mcp.Tool{ Name: "search_repositories", @@ -57,6 +57,8 @@ func SearchRepositories(t translations.TranslationHelperFunc) inventory.ServerTo }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { query, err := RequiredParam[string](args, "query") if err != nil { @@ -188,7 +190,7 @@ func SearchCode(t translations.TranslationHelperFunc) inventory.ServerTool { } WithPagination(schema) - return NewTool( + return NewToolWithScopes( ToolsetMetadataRepos, mcp.Tool{ Name: "search_code", @@ -199,6 +201,8 @@ func SearchCode(t translations.TranslationHelperFunc) inventory.ServerTool { }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { query, err := RequiredParam[string](args, "query") if err != nil { @@ -369,7 +373,7 @@ func SearchUsers(t translations.TranslationHelperFunc) inventory.ServerTool { } WithPagination(schema) - return NewTool( + return NewToolWithScopes( ToolsetMetadataUsers, mcp.Tool{ Name: "search_users", @@ -380,6 +384,8 @@ func SearchUsers(t translations.TranslationHelperFunc) inventory.ServerTool { }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.Repo), + scopes.ToStringSlice(scopes.Repo), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { return userOrOrgHandler(ctx, "user", deps, args) }, @@ -410,7 +416,7 @@ func SearchOrgs(t translations.TranslationHelperFunc) inventory.ServerTool { } WithPagination(schema) - return NewTool( + return NewToolWithScopes( ToolsetMetadataOrgs, mcp.Tool{ Name: "search_orgs", @@ -421,6 +427,8 @@ func SearchOrgs(t translations.TranslationHelperFunc) inventory.ServerTool { }, InputSchema: schema, }, + scopes.ToStringSlice(scopes.ReadOrg), + scopes.ToStringSlice(scopes.ReadOrg, scopes.WriteOrg, scopes.AdminOrg), func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { return userOrOrgHandler(ctx, "org", deps, args) }, diff --git a/pkg/scopes/scopes.go b/pkg/scopes/scopes.go index d6272a0a0..268a24d99 100644 --- a/pkg/scopes/scopes.go +++ b/pkg/scopes/scopes.go @@ -6,35 +6,35 @@ package scopes type Scope string const ( -// Repo grants full control of private repositories -Repo Scope = "repo" + // Repo grants full control of private repositories + Repo Scope = "repo" -// PublicRepo grants access to public repositories -PublicRepo Scope = "public_repo" + // PublicRepo grants access to public repositories + PublicRepo Scope = "public_repo" -// ReadOrg grants read-only access to organization membership, teams, and projects -ReadOrg Scope = "read:org" + // ReadOrg grants read-only access to organization membership, teams, and projects + ReadOrg Scope = "read:org" -// WriteOrg grants write access to organization membership and teams -WriteOrg Scope = "write:org" + // WriteOrg grants write access to organization membership and teams + WriteOrg Scope = "write:org" -// AdminOrg grants full control of organizations and teams -AdminOrg Scope = "admin:org" + // AdminOrg grants full control of organizations and teams + AdminOrg Scope = "admin:org" -// Gist grants write access to gists -Gist Scope = "gist" + // Gist grants write access to gists + Gist Scope = "gist" -// Notifications grants access to notifications -Notifications Scope = "notifications" + // Notifications grants access to notifications + Notifications Scope = "notifications" -// ReadProject grants read-only access to projects -ReadProject Scope = "read:project" + // ReadProject grants read-only access to projects + ReadProject Scope = "read:project" -// Project grants full control of projects -Project Scope = "project" + // Project grants full control of projects + Project Scope = "project" -// SecurityEvents grants read and write access to security events -SecurityEvents Scope = "security_events" + // SecurityEvents grants read and write access to security events + SecurityEvents Scope = "security_events" ) // ScopeSet represents a set of OAuth scopes. @@ -42,36 +42,36 @@ type ScopeSet map[Scope]bool // NewScopeSet creates a new ScopeSet from the given scopes. func NewScopeSet(scopes ...Scope) ScopeSet { -set := make(ScopeSet) -for _, scope := range scopes { -set[scope] = true -} -return set + set := make(ScopeSet) + for _, scope := range scopes { + set[scope] = true + } + return set } // ToSlice converts a ScopeSet to a slice of Scope values. func (s ScopeSet) ToSlice() []Scope { -scopes := make([]Scope, 0, len(s)) -for scope := range s { -scopes = append(scopes, scope) -} -return scopes + scopes := make([]Scope, 0, len(s)) + for scope := range s { + scopes = append(scopes, scope) + } + return scopes } // ToStringSlice converts a ScopeSet to a slice of string values. func (s ScopeSet) ToStringSlice() []string { -scopes := make([]string, 0, len(s)) -for scope := range s { -scopes = append(scopes, string(scope)) -} -return scopes + scopes := make([]string, 0, len(s)) + for scope := range s { + scopes = append(scopes, string(scope)) + } + return scopes } // ToStringSlice converts a slice of Scopes to a slice of strings. func ToStringSlice(scopes ...Scope) []string { -result := make([]string, len(scopes)) -for i, scope := range scopes { -result[i] = string(scope) -} -return result + result := make([]string, len(scopes)) + for i, scope := range scopes { + result[i] = string(scope) + } + return result } From db179be4c3fc59343b5aa76a57f3448c2c7b9fe8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 23 Dec 2025 15:27:33 +0000 Subject: [PATCH 020/138] Simplify scope implementation per feedback - Replace NewToolWithScopes with updated NewTool that includes scope parameters - All tools now use the same constructor with explicit scope requirements - Documentation only shows accepted scopes when they differ from required - Removed NewToolWithScopes and NewToolFromHandlerWithScopes helper functions Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com> --- README.md | 74 -------------------------- cmd/github-mcp-server/generate_docs.go | 32 +++++++++-- pkg/github/actions.go | 36 ++++++------- pkg/github/code_scanning.go | 4 +- pkg/github/context_tools.go | 6 +-- pkg/github/dependabot.go | 4 +- pkg/github/dependencies.go | 26 +++------ pkg/github/discussions.go | 8 +-- pkg/github/gists.go | 8 +-- pkg/github/git.go | 2 +- pkg/github/issues.go | 16 +++--- pkg/github/labels.go | 6 +-- pkg/github/notifications.go | 12 ++--- pkg/github/projects.go | 18 +++---- pkg/github/pullrequests.go | 20 +++---- pkg/github/repositories.go | 36 ++++++------- pkg/github/search.go | 8 +-- pkg/github/secret_scanning.go | 4 +- pkg/github/security_advisories.go | 8 +-- 19 files changed, 131 insertions(+), 197 deletions(-) diff --git a/README.md b/README.md index 0111ba6d6..a4f803845 100644 --- a/README.md +++ b/README.md @@ -496,7 +496,6 @@ The following sets of tools are available: ======= - **actions_get** - Get details of GitHub Actions resources (workflows, workflow runs, jobs, and artifacts) - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `method`: The method to execute (string, required) - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) @@ -509,7 +508,6 @@ The following sets of tools are available: - **actions_list** - List GitHub Actions workflows in a repository - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `method`: The action to perform (string, required) - `owner`: Repository owner (string, required) - `page`: Page number for pagination (default: 1) (number, optional) @@ -525,7 +523,6 @@ The following sets of tools are available: - **actions_run_trigger** - Trigger GitHub Actions workflow actions - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `inputs`: Inputs the workflow accepts. Only used for 'run_workflow' method. (object, optional) - `method`: The method to execute (string, required) - `owner`: Repository owner (string, required) @@ -537,28 +534,24 @@ The following sets of tools are available: >>>>>>> 4ed0310 (Complete OAuth scope implementation for all tools) - **cancel_workflow_run** - Cancel workflow run - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `run_id`: The unique identifier of the workflow run (number, required) - **delete_workflow_run_logs** - Delete workflow logs - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `run_id`: The unique identifier of the workflow run (number, required) - **download_workflow_run_artifact** - Download workflow artifact - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `artifact_id`: The unique identifier of the artifact (number, required) - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - **get_job_logs** - Get job logs - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `failed_only`: When true, gets logs for all failed jobs in run_id (boolean, optional) - `job_id`: The unique identifier of the workflow job (required for single job logs) (number, optional) - `owner`: Repository owner (string, required) @@ -571,7 +564,6 @@ The following sets of tools are available: ======= - **get_job_logs** - Get GitHub Actions workflow job logs - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `failed_only`: When true, gets logs for all failed jobs in the workflow run specified by run_id. Requires run_id to be provided. (boolean, optional) - `job_id`: The unique identifier of the workflow job. Required when getting logs for a single job. (number, optional) - `owner`: Repository owner (string, required) @@ -583,28 +575,24 @@ The following sets of tools are available: >>>>>>> 4ed0310 (Complete OAuth scope implementation for all tools) - **get_workflow_run** - Get workflow run - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `run_id`: The unique identifier of the workflow run (number, required) - **get_workflow_run_logs** - Get workflow run logs - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `run_id`: The unique identifier of the workflow run (number, required) - **get_workflow_run_usage** - Get workflow usage - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `run_id`: The unique identifier of the workflow run (number, required) - **list_workflow_jobs** - List workflow jobs - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `filter`: Filters jobs by their completed_at timestamp (string, optional) - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) @@ -614,7 +602,6 @@ The following sets of tools are available: - **list_workflow_run_artifacts** - List workflow artifacts - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) @@ -623,7 +610,6 @@ The following sets of tools are available: - **list_workflow_runs** - List workflow runs - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `actor`: Returns someone's workflow runs. Use the login for the user who created the workflow run. (string, optional) - `branch`: Returns workflow runs associated with a branch. Use the name of the branch. (string, optional) - `event`: Returns workflow runs for a specific event type (string, optional) @@ -636,7 +622,6 @@ The following sets of tools are available: - **list_workflows** - List workflows - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) @@ -644,21 +629,18 @@ The following sets of tools are available: - **rerun_failed_jobs** - Rerun failed jobs - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `run_id`: The unique identifier of the workflow run (number, required) - **rerun_workflow_run** - Rerun workflow run - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `run_id`: The unique identifier of the workflow run (number, required) - **run_workflow** - Run workflow - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `inputs`: Inputs the workflow accepts (object, optional) - `owner`: Repository owner (string, required) - `ref`: The git reference for the workflow. The reference can be a branch or tag name. (string, required) @@ -737,14 +719,12 @@ The following sets of tools are available: - **get_discussion** - Get discussion - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `discussionNumber`: Discussion Number (number, required) - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - **get_discussion_comments** - Get discussion comments - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `after`: Cursor for pagination. Use the endCursor from the previous page's PageInfo for GraphQL APIs. (string, optional) - `discussionNumber`: Discussion Number (number, required) - `owner`: Repository owner (string, required) @@ -753,13 +733,11 @@ The following sets of tools are available: - **list_discussion_categories** - List discussion categories - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name. If not provided, discussion categories will be queried at the organisation level. (string, optional) - **list_discussions** - List discussions - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `after`: Cursor for pagination. Use the endCursor from the previous page's PageInfo for GraphQL APIs. (string, optional) - `category`: Optional filter by discussion category ID. If provided, only discussions with this category are listed. (string, optional) - `direction`: Order direction. (string, optional) @@ -776,7 +754,6 @@ The following sets of tools are available: - **create_gist** - Create Gist - **Required OAuth Scopes**: `gist` - - **Accepted OAuth Scopes**: `gist` - `content`: Content for simple single-file gist creation (string, required) - `description`: Description of the gist (string, optional) - `filename`: Filename for simple single-file gist creation (string, required) @@ -793,7 +770,6 @@ The following sets of tools are available: - **update_gist** - Update Gist - **Required OAuth Scopes**: `gist` - - **Accepted OAuth Scopes**: `gist` - `content`: Content for the file (string, required) - `description`: Updated description of the gist (string, optional) - `filename`: Filename to update or create (string, required) @@ -807,7 +783,6 @@ The following sets of tools are available: - **get_repository_tree** - Get repository tree - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (username or organization) (string, required) - `path_filter`: Optional path prefix to filter the tree results (e.g., 'src/' to only show files in the src directory) (string, optional) - `recursive`: Setting this parameter to true returns the objects or subtrees referenced by the tree. Default is false (boolean, optional) @@ -822,7 +797,6 @@ The following sets of tools are available: - **add_issue_comment** - Add comment to issue - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `body`: Comment content (string, required) - `issue_number`: Issue number to comment on (number, required) - `owner`: Repository owner (string, required) @@ -833,7 +807,6 @@ The following sets of tools are available: - `issue_number`: Issue number (number, required) ======= - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `issueNumber`: Issue number (number, required) >>>>>>> 4ed0310 (Complete OAuth scope implementation for all tools) - `owner`: Repository owner (string, required) @@ -841,14 +814,12 @@ The following sets of tools are available: - **get_label** - Get a specific label from a repository. - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `name`: Label name. (string, required) - `owner`: Repository owner (username or organization name) (string, required) - `repo`: Repository name (string, required) - **issue_read** - Get issue details - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `issue_number`: The number of the issue (number, required) - `method`: The read operation to perform on a single issue. Options are: @@ -864,7 +835,6 @@ The following sets of tools are available: - **issue_write** - Create or update issue. - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `assignees`: Usernames to assign to this issue (string[], optional) - `body`: Issue body content (string, optional) - `duplicate_of`: Issue number that this issue is a duplicate of. Only used when state_reason is 'duplicate'. (number, optional) @@ -890,7 +860,6 @@ The following sets of tools are available: - **list_issues** - List issues - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `after`: Cursor for pagination. Use the endCursor from the previous page's PageInfo for GraphQL APIs. (string, optional) - `direction`: Order direction. If provided, the 'orderBy' also needs to be provided. (string, optional) - `labels`: Filter by labels (string[], optional) @@ -903,7 +872,6 @@ The following sets of tools are available: - **search_issues** - Search issues - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `order`: Sort order (string, optional) - `owner`: Optional repository owner. If provided with repo, only issues for this repository are listed. (string, optional) - `page`: Page number for pagination (min 1) (number, optional) @@ -914,7 +882,6 @@ The following sets of tools are available: - **sub_issue_write** - Change sub-issue - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `after_id`: The ID of the sub-issue to be prioritized after (either after_id OR before_id should be specified) (number, optional) - `before_id`: The ID of the sub-issue to be prioritized before (either after_id OR before_id should be specified) (number, optional) - `issue_number`: The number of the parent issue (number, required) @@ -937,14 +904,12 @@ The following sets of tools are available: - **get_label** - Get a specific label from a repository. - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `name`: Label name. (string, required) - `owner`: Repository owner (username or organization name) (string, required) - `repo`: Repository name (string, required) - **label_write** - Write operations on repository labels. - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `color`: Label color as 6-character hex code without '#' prefix (e.g., 'f29513'). Required for 'create', optional for 'update'. (string, optional) - `description`: Label description text. Optional for 'create' and 'update'. (string, optional) - `method`: Operation to perform: 'create', 'update', or 'delete' (string, required) @@ -955,7 +920,6 @@ The following sets of tools are available: - **list_label** - List labels from a repository - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (username or organization name) - required for all operations (string, required) - `repo`: Repository name - required for all operations (string, required) @@ -967,18 +931,15 @@ The following sets of tools are available: - **dismiss_notification** - Dismiss notification - **Required OAuth Scopes**: `notifications` - - **Accepted OAuth Scopes**: `notifications` - `state`: The new state of the notification (read/done) (string, required) - `threadID`: The ID of the notification thread (string, required) - **get_notification_details** - Get notification details - **Required OAuth Scopes**: `notifications` - - **Accepted OAuth Scopes**: `notifications` - `notificationID`: The ID of the notification (string, required) - **list_notifications** - List notifications - **Required OAuth Scopes**: `notifications` - - **Accepted OAuth Scopes**: `notifications` - `before`: Only show notifications updated before the given time (ISO 8601 format) (string, optional) - `filter`: Filter notifications to, use default unless specified. Read notifications are ones that have already been acknowledged by the user. Participating notifications are those that the user is directly involved in, such as issues or pull requests they have commented on or created. (string, optional) - `owner`: Optional repository owner. If provided with repo, only notifications for this repository are listed. (string, optional) @@ -989,20 +950,17 @@ The following sets of tools are available: - **manage_notification_subscription** - Manage notification subscription - **Required OAuth Scopes**: `notifications` - - **Accepted OAuth Scopes**: `notifications` - `action`: Action to perform: ignore, watch, or delete the notification subscription. (string, required) - `notificationID`: The ID of the notification thread. (string, required) - **manage_repository_notification_subscription** - Manage repository notification subscription - **Required OAuth Scopes**: `notifications` - - **Accepted OAuth Scopes**: `notifications` - `action`: Action to perform: ignore, watch, or delete the repository notification subscription. (string, required) - `owner`: The account owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) - **mark_all_notifications_read** - Mark all notifications as read - **Required OAuth Scopes**: `notifications` - - **Accepted OAuth Scopes**: `notifications` - `lastReadAt`: Describes the last point that notifications were checked (optional). Default: Now (string, optional) - `owner`: Optional repository owner. If provided with repo, only notifications for this repository are marked as read. (string, optional) - `repo`: Optional repository name. If provided with owner, only notifications for this repository are marked as read. (string, optional) @@ -1030,7 +988,6 @@ The following sets of tools are available: - **add_project_item** - Add project item - **Required OAuth Scopes**: `project` - - **Accepted OAuth Scopes**: `project` - `item_id`: The numeric ID of the issue or pull request to add to the project. (number, required) - `item_type`: The item's type, either issue or pull_request. (string, required) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) @@ -1039,7 +996,6 @@ The following sets of tools are available: - **delete_project_item** - Delete project item - **Required OAuth Scopes**: `project` - - **Accepted OAuth Scopes**: `project` - `item_id`: The internal project item ID to delete from the project (not the issue or pull request ID). (number, required) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) - `owner_type`: Owner type (string, required) @@ -1103,7 +1059,6 @@ The following sets of tools are available: - **update_project_item** - Update project item - **Required OAuth Scopes**: `project` - - **Accepted OAuth Scopes**: `project` - `item_id`: The unique identifier of the project item. This is not the issue or pull request ID. (number, required) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) - `owner_type`: Owner type (string, required) @@ -1118,7 +1073,6 @@ The following sets of tools are available: - **add_comment_to_pending_review** - Add review comment to the requester's latest pending pull request review - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `body`: The text of the review comment (string, required) - `line`: The line of the blob in the pull request diff that the comment applies to. For multi-line comments, the last line of the range (number, optional) - `owner`: Repository owner (string, required) @@ -1132,7 +1086,6 @@ The following sets of tools are available: - **create_pull_request** - Open new pull request - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `base`: Branch to merge into (string, required) - `body`: PR description (string, optional) - `draft`: Create as draft PR (boolean, optional) @@ -1144,7 +1097,6 @@ The following sets of tools are available: - **list_pull_requests** - List pull requests - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `base`: Filter by base branch (string, optional) - `direction`: Sort direction (string, optional) - `head`: Filter by head user/org and branch (string, optional) @@ -1157,7 +1109,6 @@ The following sets of tools are available: - **merge_pull_request** - Merge pull request - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `commit_message`: Extra detail for merge commit (string, optional) - `commit_title`: Title for merge commit (string, optional) - `merge_method`: Merge method (string, optional) @@ -1167,7 +1118,6 @@ The following sets of tools are available: - **pull_request_read** - Get details for a single pull request - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `method`: Action to specify what pull request data needs to be retrieved from GitHub. Possible options: 1. get - Get details of a specific pull request. @@ -1186,7 +1136,6 @@ The following sets of tools are available: - **pull_request_review_write** - Write operations (create, submit, delete) on pull request reviews. - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `body`: Review comment text (string, optional) - `commitID`: SHA of commit to review (string, optional) - `event`: Review action to perform. (string, optional) @@ -1197,14 +1146,12 @@ The following sets of tools are available: - **request_copilot_review** - Request Copilot review - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `pullNumber`: Pull request number (number, required) - `repo`: Repository name (string, required) - **search_pull_requests** - Search pull requests - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `order`: Sort order (string, optional) - `owner`: Optional repository owner. If provided with repo, only pull requests for this repository are listed. (string, optional) - `page`: Page number for pagination (min 1) (number, optional) @@ -1215,7 +1162,6 @@ The following sets of tools are available: - **update_pull_request** - Edit pull request - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `base`: New base branch name (string, optional) - `body`: New description (string, optional) - `draft`: Mark pull request as draft (true) or ready for review (false) (boolean, optional) @@ -1229,7 +1175,6 @@ The following sets of tools are available: - **update_pull_request_branch** - Update pull request branch - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `expectedHeadSha`: The expected SHA of the pull request's HEAD ref (string, optional) - `owner`: Repository owner (string, required) - `pullNumber`: Pull request number (number, required) @@ -1243,7 +1188,6 @@ The following sets of tools are available: - **create_branch** - Create branch - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `branch`: Name for new branch (string, required) - `from_branch`: Source branch (defaults to repo default) (string, optional) - `owner`: Repository owner (string, required) @@ -1251,7 +1195,6 @@ The following sets of tools are available: - **create_or_update_file** - Create or update file - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `branch`: Branch to create/update the file in (string, required) - `content`: Content of the file (string, required) - `message`: Commit message (string, required) @@ -1262,7 +1205,6 @@ The following sets of tools are available: - **create_repository** - Create repository - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `autoInit`: Initialize with README (boolean, optional) - `description`: Repository description (string, optional) - `name`: Repository name (string, required) @@ -1271,7 +1213,6 @@ The following sets of tools are available: - **delete_file** - Delete file - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `branch`: Branch to delete the file from (string, required) - `message`: Commit message (string, required) - `owner`: Repository owner (username or organization) (string, required) @@ -1280,14 +1221,12 @@ The following sets of tools are available: - **fork_repository** - Fork repository - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `organization`: Organization to fork to (string, optional) - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - **get_commit** - Get commit details - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `include_diff`: Whether to include file diffs and stats in the response. Default is true. (boolean, optional) - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) @@ -1297,7 +1236,6 @@ The following sets of tools are available: - **get_file_contents** - Get file or directory contents - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (username or organization) (string, required) - `path`: Path to file/directory (string, optional) - `ref`: Accepts optional git refs such as `refs/tags/{tag}`, `refs/heads/{branch}` or `refs/pull/{pr_number}/head` (string, optional) @@ -1306,27 +1244,23 @@ The following sets of tools are available: - **get_latest_release** - Get latest release - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - **get_release_by_tag** - Get a release by tag name - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `tag`: Tag name (e.g., 'v1.0.0') (string, required) - **get_tag** - Get tag details - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - `tag`: Tag name (string, required) - **list_branches** - List branches - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) @@ -1334,7 +1268,6 @@ The following sets of tools are available: - **list_commits** - List commits - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `author`: Author username or email address to filter commits by (string, optional) - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) @@ -1344,7 +1277,6 @@ The following sets of tools are available: - **list_releases** - List releases - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) @@ -1352,7 +1284,6 @@ The following sets of tools are available: - **list_tags** - List tags - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) @@ -1360,7 +1291,6 @@ The following sets of tools are available: - **push_files** - Push files to repository - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `branch`: Branch to push to (string, required) - `files`: Array of file objects to push, each object with path (string) and content (string) (object[], required) - `message`: Commit message (string, required) @@ -1369,7 +1299,6 @@ The following sets of tools are available: - **search_code** - Search code - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `order`: Sort order for results (string, optional) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) @@ -1378,7 +1307,6 @@ The following sets of tools are available: - **search_repositories** - Search repositories - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `minimal_output`: Return minimal repository information (default: true). When false, returns full GitHub API repository objects. (boolean, optional) - `order`: Sort order (string, optional) - `page`: Page number for pagination (min 1) (number, optional) @@ -1459,7 +1387,6 @@ The following sets of tools are available: - **list_starred_repositories** - List starred repositories - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `direction`: The direction to sort the results by. (string, optional) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) @@ -1486,7 +1413,6 @@ The following sets of tools are available: - **search_users** - Search users - **Required OAuth Scopes**: `repo` - - **Accepted OAuth Scopes**: `repo` - `order`: Sort order (string, optional) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) diff --git a/cmd/github-mcp-server/generate_docs.go b/cmd/github-mcp-server/generate_docs.go index f28aa1bc2..85d7ecdbf 100644 --- a/cmd/github-mcp-server/generate_docs.go +++ b/cmd/github-mcp-server/generate_docs.go @@ -227,11 +227,11 @@ func writeToolDoc(buf *strings.Builder, tool inventory.ServerTool) { fmt.Fprintf(buf, "- **%s** - %s\n", tool.Tool.Name, tool.Tool.Annotations.Title) // OAuth scopes if present - if len(tool.RequiredScopes) > 0 || len(tool.AcceptedScopes) > 0 { - if len(tool.RequiredScopes) > 0 { - fmt.Fprintf(buf, " - **Required OAuth Scopes**: `%s`\n", strings.Join(tool.RequiredScopes, "`, `")) - } - if len(tool.AcceptedScopes) > 0 { + if len(tool.RequiredScopes) > 0 { + fmt.Fprintf(buf, " - **Required OAuth Scopes**: `%s`\n", strings.Join(tool.RequiredScopes, "`, `")) + + // Only show accepted scopes if they differ from required scopes + if len(tool.AcceptedScopes) > 0 && !scopesEqual(tool.RequiredScopes, tool.AcceptedScopes) { fmt.Fprintf(buf, " - **Accepted OAuth Scopes**: `%s`\n", strings.Join(tool.AcceptedScopes, "`, `")) } } @@ -290,6 +290,28 @@ func writeToolDoc(buf *strings.Builder, tool inventory.ServerTool) { } } +// scopesEqual checks if two scope slices contain the same elements (order-independent) +func scopesEqual(a, b []string) bool { + if len(a) != len(b) { + return false + } + + // Create a map for quick lookup + aMap := make(map[string]bool, len(a)) + for _, scope := range a { + aMap[scope] = true + } + + // Check if all elements in b are in a + for _, scope := range b { + if !aMap[scope] { + return false + } + } + + return true +} + func contains(slice []string, item string) bool { for _, s := range slice { if s == item { diff --git a/pkg/github/actions.go b/pkg/github/actions.go index c4ba97983..c6cbd2ee7 100644 --- a/pkg/github/actions.go +++ b/pkg/github/actions.go @@ -51,7 +51,7 @@ const ( // ListWorkflows creates a tool to list workflows in a repository func ListWorkflows(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewToolWithScopes( + tool := NewTool( ToolsetMetadataActions, mcp.Tool{ Name: "list_workflows", @@ -124,7 +124,7 @@ func ListWorkflows(t translations.TranslationHelperFunc) inventory.ServerTool { // ListWorkflowRuns creates a tool to list workflow runs for a specific workflow func ListWorkflowRuns(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewToolWithScopes( + tool := NewTool( ToolsetMetadataActions, mcp.Tool{ Name: "list_workflow_runs", @@ -280,7 +280,7 @@ func ListWorkflowRuns(t translations.TranslationHelperFunc) inventory.ServerTool // RunWorkflow creates a tool to run an Actions workflow func RunWorkflow(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewToolWithScopes( + tool := NewTool( ToolsetMetadataActions, mcp.Tool{ Name: "run_workflow", @@ -394,7 +394,7 @@ func RunWorkflow(t translations.TranslationHelperFunc) inventory.ServerTool { // GetWorkflowRun creates a tool to get details of a specific workflow run func GetWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewToolWithScopes( + tool := NewTool( ToolsetMetadataActions, mcp.Tool{ Name: "get_workflow_run", @@ -464,7 +464,7 @@ func GetWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerTool { // GetWorkflowRunLogs creates a tool to download logs for a specific workflow run func GetWorkflowRunLogs(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewToolWithScopes( + tool := NewTool( ToolsetMetadataActions, mcp.Tool{ Name: "get_workflow_run_logs", @@ -544,7 +544,7 @@ func GetWorkflowRunLogs(t translations.TranslationHelperFunc) inventory.ServerTo // ListWorkflowJobs creates a tool to list jobs for a specific workflow run func ListWorkflowJobs(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewToolWithScopes( + tool := NewTool( ToolsetMetadataActions, mcp.Tool{ Name: "list_workflow_jobs", @@ -646,7 +646,7 @@ func ListWorkflowJobs(t translations.TranslationHelperFunc) inventory.ServerTool // GetJobLogs creates a tool to download logs for a specific workflow job or efficiently get all failed job logs for a workflow run func GetJobLogs(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewToolWithScopes( + tool := NewTool( ToolsetMetadataActions, mcp.Tool{ Name: "get_job_logs", @@ -913,7 +913,7 @@ func downloadLogContent(ctx context.Context, logURL string, tailLines int, maxLi // RerunWorkflowRun creates a tool to re-run an entire workflow run func RerunWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewToolWithScopes( + tool := NewTool( ToolsetMetadataActions, mcp.Tool{ Name: "rerun_workflow_run", @@ -990,7 +990,7 @@ func RerunWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerTool // RerunFailedJobs creates a tool to re-run only the failed jobs in a workflow run func RerunFailedJobs(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewToolWithScopes( + tool := NewTool( ToolsetMetadataActions, mcp.Tool{ Name: "rerun_failed_jobs", @@ -1067,7 +1067,7 @@ func RerunFailedJobs(t translations.TranslationHelperFunc) inventory.ServerTool // CancelWorkflowRun creates a tool to cancel a workflow run func CancelWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewToolWithScopes( + tool := NewTool( ToolsetMetadataActions, mcp.Tool{ Name: "cancel_workflow_run", @@ -1146,7 +1146,7 @@ func CancelWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerToo // ListWorkflowRunArtifacts creates a tool to list artifacts for a workflow run func ListWorkflowRunArtifacts(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewToolWithScopes( + tool := NewTool( ToolsetMetadataActions, mcp.Tool{ Name: "list_workflow_run_artifacts", @@ -1228,7 +1228,7 @@ func ListWorkflowRunArtifacts(t translations.TranslationHelperFunc) inventory.Se // DownloadWorkflowRunArtifact creates a tool to download a workflow run artifact func DownloadWorkflowRunArtifact(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewToolWithScopes( + tool := NewTool( ToolsetMetadataActions, mcp.Tool{ Name: "download_workflow_run_artifact", @@ -1307,7 +1307,7 @@ func DownloadWorkflowRunArtifact(t translations.TranslationHelperFunc) inventory // DeleteWorkflowRunLogs creates a tool to delete logs for a workflow run func DeleteWorkflowRunLogs(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewToolWithScopes( + tool := NewTool( ToolsetMetadataActions, mcp.Tool{ Name: "delete_workflow_run_logs", @@ -1385,7 +1385,7 @@ func DeleteWorkflowRunLogs(t translations.TranslationHelperFunc) inventory.Serve // GetWorkflowRunUsage creates a tool to get usage metrics for a workflow run func GetWorkflowRunUsage(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewToolWithScopes( + tool := NewTool( ToolsetMetadataActions, mcp.Tool{ Name: "get_workflow_run_usage", @@ -1455,7 +1455,7 @@ func GetWorkflowRunUsage(t translations.TranslationHelperFunc) inventory.ServerT // ActionsList returns the tool and handler for listing GitHub Actions resources. func ActionsList(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewToolWithScopes( + tool := NewTool( ToolsetMetadataActions, mcp.Tool{ Name: "actions_list", @@ -1652,7 +1652,7 @@ Use this tool to list workflows in a repository, or list workflow runs, jobs, an // ActionsGet returns the tool and handler for getting GitHub Actions resources. func ActionsGet(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewToolWithScopes( + tool := NewTool( ToolsetMetadataActions, mcp.Tool{ Name: "actions_get", @@ -1762,7 +1762,7 @@ Use this tool to get details about individual workflows, workflow runs, jobs, an // ActionsRunTrigger returns the tool and handler for triggering GitHub Actions workflows. func ActionsRunTrigger(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewToolWithScopes( + tool := NewTool( ToolsetMetadataActions, mcp.Tool{ Name: "actions_run_trigger", @@ -1882,7 +1882,7 @@ func ActionsRunTrigger(t translations.TranslationHelperFunc) inventory.ServerToo // ActionsGetJobLogs returns the tool and handler for getting workflow job logs. func ActionsGetJobLogs(t translations.TranslationHelperFunc) inventory.ServerTool { - tool := NewToolWithScopes( + tool := NewTool( ToolsetMetadataActions, mcp.Tool{ Name: "get_job_logs", diff --git a/pkg/github/code_scanning.go b/pkg/github/code_scanning.go index af232937c..99ab8ad4f 100644 --- a/pkg/github/code_scanning.go +++ b/pkg/github/code_scanning.go @@ -17,7 +17,7 @@ import ( ) func GetCodeScanningAlert(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataCodeSecurity, mcp.Tool{ Name: "get_code_scanning_alert", @@ -95,7 +95,7 @@ func GetCodeScanningAlert(t translations.TranslationHelperFunc) inventory.Server } func ListCodeScanningAlerts(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataCodeSecurity, mcp.Tool{ Name: "list_code_scanning_alerts", diff --git a/pkg/github/context_tools.go b/pkg/github/context_tools.go index 77c9ce10a..ad3c8daa4 100644 --- a/pkg/github/context_tools.go +++ b/pkg/github/context_tools.go @@ -39,7 +39,7 @@ type UserDetails struct { // GetMe creates a tool to get details of the authenticated user. func GetMe(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataContext, mcp.Tool{ Name: "get_me", @@ -113,7 +113,7 @@ type OrganizationTeams struct { } func GetTeams(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataContext, mcp.Tool{ Name: "get_teams", @@ -212,7 +212,7 @@ func GetTeams(t translations.TranslationHelperFunc) inventory.ServerTool { } func GetTeamMembers(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataContext, mcp.Tool{ Name: "get_team_members", diff --git a/pkg/github/dependabot.go b/pkg/github/dependabot.go index 9a0c09a8f..5c86d9709 100644 --- a/pkg/github/dependabot.go +++ b/pkg/github/dependabot.go @@ -18,7 +18,7 @@ import ( ) func GetDependabotAlert(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataDependabot, mcp.Tool{ Name: "get_dependabot_alert", @@ -96,7 +96,7 @@ func GetDependabotAlert(t translations.TranslationHelperFunc) inventory.ServerTo } func ListDependabotAlerts(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataDependabot, mcp.Tool{ Name: "list_dependabot_alerts", diff --git a/pkg/github/dependencies.go b/pkg/github/dependencies.go index e15efb1d5..d5a5e30aa 100644 --- a/pkg/github/dependencies.go +++ b/pkg/github/dependencies.go @@ -148,16 +148,9 @@ func (d BaseDeps) GetContentWindowSize() int { return d.ContentWindowSize } // // The handler function receives deps extracted from context via MustDepsFromContext. // Ensure ContextWithDeps is called to inject deps before any tool handlers are invoked. -func NewTool[In, Out any](toolset inventory.ToolsetMetadata, tool mcp.Tool, handler func(ctx context.Context, deps ToolDependencies, req *mcp.CallToolRequest, args In) (*mcp.CallToolResult, Out, error)) inventory.ServerTool { - return inventory.NewServerToolWithContextHandler(tool, toolset, func(ctx context.Context, req *mcp.CallToolRequest, args In) (*mcp.CallToolResult, Out, error) { - deps := MustDepsFromContext(ctx) - return handler(ctx, deps, req, args) - }) -} - -// NewToolWithScopes creates a ServerTool with OAuth scope requirements. -// This is like NewTool but also accepts required and accepted scopes. -func NewToolWithScopes[In, Out any]( +// +// All tools must explicitly specify their OAuth scope requirements, even if empty (nil). +func NewTool[In, Out any]( toolset inventory.ToolsetMetadata, tool mcp.Tool, requiredScopes []string, @@ -178,16 +171,9 @@ func NewToolWithScopes[In, Out any]( // // The handler function receives deps extracted from context via MustDepsFromContext. // Ensure ContextWithDeps is called to inject deps before any tool handlers are invoked. -func NewToolFromHandler(toolset inventory.ToolsetMetadata, tool mcp.Tool, handler func(ctx context.Context, deps ToolDependencies, req *mcp.CallToolRequest) (*mcp.CallToolResult, error)) inventory.ServerTool { - return inventory.NewServerToolWithRawContextHandler(tool, toolset, func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { - deps := MustDepsFromContext(ctx) - return handler(ctx, deps, req) - }) -} - -// NewToolFromHandlerWithScopes creates a ServerTool with OAuth scope requirements. -// This is like NewToolFromHandler but also accepts required and accepted scopes. -func NewToolFromHandlerWithScopes( +// +// All tools must explicitly specify their OAuth scope requirements, even if empty (nil). +func NewToolFromHandler( toolset inventory.ToolsetMetadata, tool mcp.Tool, requiredScopes []string, diff --git a/pkg/github/discussions.go b/pkg/github/discussions.go index d91722e6e..270228c73 100644 --- a/pkg/github/discussions.go +++ b/pkg/github/discussions.go @@ -124,7 +124,7 @@ func getQueryType(useOrdering bool, categoryID *githubv4.ID) any { } func ListDiscussions(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataDiscussions, mcp.Tool{ Name: "list_discussions", @@ -278,7 +278,7 @@ func ListDiscussions(t translations.TranslationHelperFunc) inventory.ServerTool } func GetDiscussion(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataDiscussions, mcp.Tool{ Name: "get_discussion", @@ -383,7 +383,7 @@ func GetDiscussion(t translations.TranslationHelperFunc) inventory.ServerTool { } func GetDiscussionComments(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataDiscussions, mcp.Tool{ Name: "get_discussion_comments", @@ -511,7 +511,7 @@ func GetDiscussionComments(t translations.TranslationHelperFunc) inventory.Serve } func ListDiscussionCategories(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataDiscussions, mcp.Tool{ Name: "list_discussion_categories", diff --git a/pkg/github/gists.go b/pkg/github/gists.go index cd268c6a1..0509220e3 100644 --- a/pkg/github/gists.go +++ b/pkg/github/gists.go @@ -19,7 +19,7 @@ import ( // ListGists creates a tool to list gists for a user func ListGists(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataGists, mcp.Tool{ Name: "list_gists", @@ -107,7 +107,7 @@ func ListGists(t translations.TranslationHelperFunc) inventory.ServerTool { // GetGist creates a tool to get the content of a gist func GetGist(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataGists, mcp.Tool{ Name: "get_gist", @@ -166,7 +166,7 @@ func GetGist(t translations.TranslationHelperFunc) inventory.ServerTool { // CreateGist creates a tool to create a new gist func CreateGist(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataGists, mcp.Tool{ Name: "create_gist", @@ -270,7 +270,7 @@ func CreateGist(t translations.TranslationHelperFunc) inventory.ServerTool { // UpdateGist creates a tool to edit an existing gist func UpdateGist(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataGists, mcp.Tool{ Name: "update_gist", diff --git a/pkg/github/git.go b/pkg/github/git.go index 06c766163..8e632c1af 100644 --- a/pkg/github/git.go +++ b/pkg/github/git.go @@ -40,7 +40,7 @@ type TreeResponse struct { // GetRepositoryTree creates a tool to get the tree structure of a GitHub repository. func GetRepositoryTree(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataGit, mcp.Tool{ Name: "get_repository_tree", diff --git a/pkg/github/issues.go b/pkg/github/issues.go index 9ecf4d264..9f2223295 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -264,7 +264,7 @@ Options are: } WithPagination(schema) - return NewToolWithScopes( + return NewTool( ToolsetMetadataIssues, mcp.Tool{ Name: "issue_read", @@ -548,7 +548,7 @@ func GetIssueLabels(ctx context.Context, client *githubv4.Client, owner string, // ListIssueTypes creates a tool to list defined issue types for an organization. This can be used to understand supported issue type values for creating or updating issues. func ListIssueTypes(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataIssues, mcp.Tool{ Name: "list_issue_types", @@ -605,7 +605,7 @@ func ListIssueTypes(t translations.TranslationHelperFunc) inventory.ServerTool { // AddIssueComment creates a tool to add a comment to an issue. func AddIssueComment(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataIssues, mcp.Tool{ Name: "add_issue_comment", @@ -690,7 +690,7 @@ func AddIssueComment(t translations.TranslationHelperFunc) inventory.ServerTool // SubIssueWrite creates a tool to add a sub-issue to a parent issue. func SubIssueWrite(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataIssues, mcp.Tool{ Name: "sub_issue_write", @@ -961,7 +961,7 @@ func SearchIssues(t translations.TranslationHelperFunc) inventory.ServerTool { } WithPagination(schema) - return NewToolWithScopes( + return NewTool( ToolsetMetadataIssues, mcp.Tool{ Name: "search_issues", @@ -982,7 +982,7 @@ func SearchIssues(t translations.TranslationHelperFunc) inventory.ServerTool { // IssueWrite creates a tool to create a new or update an existing issue in a GitHub repository. func IssueWrite(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataIssues, mcp.Tool{ Name: "issue_write", @@ -1387,7 +1387,7 @@ func ListIssues(t translations.TranslationHelperFunc) inventory.ServerTool { } WithCursorPagination(schema) - return NewToolWithScopes( + return NewTool( ToolsetMetadataIssues, mcp.Tool{ Name: "list_issues", @@ -1627,7 +1627,7 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server }, } - return NewToolWithScopes( + return NewTool( ToolsetMetadataIssues, mcp.Tool{ Name: "assign_copilot_to_issue", diff --git a/pkg/github/labels.go b/pkg/github/labels.go index d55b605fe..e85dee450 100644 --- a/pkg/github/labels.go +++ b/pkg/github/labels.go @@ -18,7 +18,7 @@ import ( // GetLabel retrieves a specific label by name from a GitHub repository func GetLabel(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataIssues, mcp.Tool{ Name: "get_label", @@ -121,7 +121,7 @@ func GetLabelForLabelsToolset(t translations.TranslationHelperFunc) inventory.Se // ListLabels lists labels from a repository func ListLabels(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetLabels, mcp.Tool{ Name: "list_label", @@ -213,7 +213,7 @@ func ListLabels(t translations.TranslationHelperFunc) inventory.ServerTool { // LabelWrite handles create, update, and delete operations for GitHub labels func LabelWrite(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetLabels, mcp.Tool{ Name: "label_write", diff --git a/pkg/github/notifications.go b/pkg/github/notifications.go index b3008362b..87026e2cb 100644 --- a/pkg/github/notifications.go +++ b/pkg/github/notifications.go @@ -27,7 +27,7 @@ const ( // ListNotifications creates a tool to list notifications for the current user. func ListNotifications(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataNotifications, mcp.Tool{ Name: "list_notifications", @@ -165,7 +165,7 @@ func ListNotifications(t translations.TranslationHelperFunc) inventory.ServerToo // DismissNotification creates a tool to mark a notification as read/done. func DismissNotification(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataNotifications, mcp.Tool{ Name: "dismiss_notification", @@ -248,7 +248,7 @@ func DismissNotification(t translations.TranslationHelperFunc) inventory.ServerT // MarkAllNotificationsRead creates a tool to mark all notifications as read. func MarkAllNotificationsRead(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataNotifications, mcp.Tool{ Name: "mark_all_notifications_read", @@ -341,7 +341,7 @@ func MarkAllNotificationsRead(t translations.TranslationHelperFunc) inventory.Se // GetNotificationDetails creates a tool to get details for a specific notification. func GetNotificationDetails(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataNotifications, mcp.Tool{ Name: "get_notification_details", @@ -411,7 +411,7 @@ const ( // ManageNotificationSubscription creates a tool to manage a notification subscription (ignore, watch, delete) func ManageNotificationSubscription(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataNotifications, mcp.Tool{ Name: "manage_notification_subscription", @@ -508,7 +508,7 @@ const ( // ManageRepositoryNotificationSubscription creates a tool to manage a repository notification subscription (ignore, watch, delete) func ManageRepositoryNotificationSubscription(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataNotifications, mcp.Tool{ Name: "manage_repository_notification_subscription", diff --git a/pkg/github/projects.go b/pkg/github/projects.go index 4edef178e..3204243f7 100644 --- a/pkg/github/projects.go +++ b/pkg/github/projects.go @@ -27,7 +27,7 @@ const ( ) func ListProjects(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataProjects, mcp.Tool{ Name: "list_projects", @@ -146,7 +146,7 @@ func ListProjects(t translations.TranslationHelperFunc) inventory.ServerTool { } func GetProject(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataProjects, mcp.Tool{ Name: "get_project", @@ -236,7 +236,7 @@ func GetProject(t translations.TranslationHelperFunc) inventory.ServerTool { } func ListProjectFields(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataProjects, mcp.Tool{ Name: "list_project_fields", @@ -344,7 +344,7 @@ func ListProjectFields(t translations.TranslationHelperFunc) inventory.ServerToo } func GetProjectField(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataProjects, mcp.Tool{ Name: "get_project_field", @@ -438,7 +438,7 @@ func GetProjectField(t translations.TranslationHelperFunc) inventory.ServerTool } func ListProjectItems(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataProjects, mcp.Tool{ Name: "list_project_items", @@ -576,7 +576,7 @@ func ListProjectItems(t translations.TranslationHelperFunc) inventory.ServerTool } func GetProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataProjects, mcp.Tool{ Name: "get_project_item", @@ -684,7 +684,7 @@ func GetProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { } func AddProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataProjects, mcp.Tool{ Name: "add_project_item", @@ -797,7 +797,7 @@ func AddProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { } func UpdateProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataProjects, mcp.Tool{ Name: "update_project_item", @@ -911,7 +911,7 @@ func UpdateProjectItem(t translations.TranslationHelperFunc) inventory.ServerToo } func DeleteProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataProjects, mcp.Tool{ Name: "delete_project_item", diff --git a/pkg/github/pullrequests.go b/pkg/github/pullrequests.go index e81f87ca0..91145f61d 100644 --- a/pkg/github/pullrequests.go +++ b/pkg/github/pullrequests.go @@ -59,7 +59,7 @@ Possible options: } WithPagination(schema) - return NewToolWithScopes( + return NewTool( ToolsetMetadataPullRequests, mcp.Tool{ Name: "pull_request_read", @@ -510,7 +510,7 @@ func CreatePullRequest(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"owner", "repo", "title", "head", "base"}, } - return NewToolWithScopes( + return NewTool( ToolsetMetadataPullRequests, mcp.Tool{ Name: "create_pull_request", @@ -663,7 +663,7 @@ func UpdatePullRequest(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"owner", "repo", "pullNumber"}, } - return NewToolWithScopes( + return NewTool( ToolsetMetadataPullRequests, mcp.Tool{ Name: "update_pull_request", @@ -946,7 +946,7 @@ func ListPullRequests(t translations.TranslationHelperFunc) inventory.ServerTool } WithPagination(schema) - return NewToolWithScopes( + return NewTool( ToolsetMetadataPullRequests, mcp.Tool{ Name: "list_pull_requests", @@ -1083,7 +1083,7 @@ func MergePullRequest(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo", "pullNumber"}, } - return NewToolWithScopes( + return NewTool( ToolsetMetadataPullRequests, mcp.Tool{ Name: "merge_pull_request", @@ -1203,7 +1203,7 @@ func SearchPullRequests(t translations.TranslationHelperFunc) inventory.ServerTo } WithPagination(schema) - return NewToolWithScopes( + return NewTool( ToolsetMetadataPullRequests, mcp.Tool{ Name: "search_pull_requests", @@ -1247,7 +1247,7 @@ func UpdatePullRequestBranch(t translations.TranslationHelperFunc) inventory.Ser Required: []string{"owner", "repo", "pullNumber"}, } - return NewToolWithScopes( + return NewTool( ToolsetMetadataPullRequests, mcp.Tool{ Name: "update_pull_request_branch", @@ -1369,7 +1369,7 @@ func PullRequestReviewWrite(t translations.TranslationHelperFunc) inventory.Serv Required: []string{"method", "owner", "repo", "pullNumber"}, } - return NewToolWithScopes( + return NewTool( ToolsetMetadataPullRequests, mcp.Tool{ Name: "pull_request_review_write", @@ -1700,7 +1700,7 @@ func AddCommentToPendingReview(t translations.TranslationHelperFunc) inventory.S Required: []string{"owner", "repo", "pullNumber", "path", "body", "subjectType"}, } - return NewToolWithScopes( + return NewTool( ToolsetMetadataPullRequests, mcp.Tool{ Name: "add_comment_to_pending_review", @@ -1853,7 +1853,7 @@ func RequestCopilotReview(t translations.TranslationHelperFunc) inventory.Server Required: []string{"owner", "repo", "pullNumber"}, } - return NewToolWithScopes( + return NewTool( ToolsetMetadataPullRequests, mcp.Tool{ Name: "request_copilot_review", diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index b0c59e417..c2f976e81 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -22,7 +22,7 @@ import ( ) func GetCommit(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataRepos, mcp.Tool{ Name: "get_commit", @@ -121,7 +121,7 @@ func GetCommit(t translations.TranslationHelperFunc) inventory.ServerTool { // ListCommits creates a tool to get commits of a branch in a repository. func ListCommits(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataRepos, mcp.Tool{ Name: "list_commits", @@ -230,7 +230,7 @@ func ListCommits(t translations.TranslationHelperFunc) inventory.ServerTool { // ListBranches creates a tool to list branches in a GitHub repository. func ListBranches(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataRepos, mcp.Tool{ Name: "list_branches", @@ -318,7 +318,7 @@ func ListBranches(t translations.TranslationHelperFunc) inventory.ServerTool { // CreateOrUpdateFile creates a tool to create or update a file in a GitHub repository. func CreateOrUpdateFile(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataRepos, mcp.Tool{ Name: "create_or_update_file", @@ -518,7 +518,7 @@ If the SHA is not provided, the tool will attempt to acquire it by fetching the // CreateRepository creates a tool to create a new GitHub repository. func CreateRepository(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataRepos, mcp.Tool{ Name: "create_repository", @@ -625,7 +625,7 @@ func CreateRepository(t translations.TranslationHelperFunc) inventory.ServerTool // GetFileContents creates a tool to get the contents of a file or directory from a GitHub repository. func GetFileContents(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataRepos, mcp.Tool{ Name: "get_file_contents", @@ -818,7 +818,7 @@ func GetFileContents(t translations.TranslationHelperFunc) inventory.ServerTool // ForkRepository creates a tool to fork a repository. func ForkRepository(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataRepos, mcp.Tool{ Name: "fork_repository", @@ -918,7 +918,7 @@ func ForkRepository(t translations.TranslationHelperFunc) inventory.ServerTool { // The approach implemented here gets automatic commit signing when used with either the github-actions user or as an app, // both of which suit an LLM well. func DeleteFile(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataRepos, mcp.Tool{ Name: "delete_file", @@ -1104,7 +1104,7 @@ func DeleteFile(t translations.TranslationHelperFunc) inventory.ServerTool { // CreateBranch creates a tool to create a new branch. func CreateBranch(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataRepos, mcp.Tool{ Name: "create_branch", @@ -1218,7 +1218,7 @@ func CreateBranch(t translations.TranslationHelperFunc) inventory.ServerTool { // PushFiles creates a tool to push multiple files in a single commit to a GitHub repository. func PushFiles(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataRepos, mcp.Tool{ Name: "push_files", @@ -1453,7 +1453,7 @@ func PushFiles(t translations.TranslationHelperFunc) inventory.ServerTool { // ListTags creates a tool to list tags in a GitHub repository. func ListTags(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataRepos, mcp.Tool{ Name: "list_tags", @@ -1533,7 +1533,7 @@ func ListTags(t translations.TranslationHelperFunc) inventory.ServerTool { // GetTag creates a tool to get details about a specific tag in a GitHub repository. func GetTag(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataRepos, mcp.Tool{ Name: "get_tag", @@ -1632,7 +1632,7 @@ func GetTag(t translations.TranslationHelperFunc) inventory.ServerTool { // ListReleases creates a tool to list releases in a GitHub repository. func ListReleases(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataRepos, mcp.Tool{ Name: "list_releases", @@ -1708,7 +1708,7 @@ func ListReleases(t translations.TranslationHelperFunc) inventory.ServerTool { // GetLatestRelease creates a tool to get the latest release in a GitHub repository. func GetLatestRelease(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataRepos, mcp.Tool{ Name: "get_latest_release", @@ -1774,7 +1774,7 @@ func GetLatestRelease(t translations.TranslationHelperFunc) inventory.ServerTool } func GetReleaseByTag(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataRepos, mcp.Tool{ Name: "get_release_by_tag", @@ -1853,7 +1853,7 @@ func GetReleaseByTag(t translations.TranslationHelperFunc) inventory.ServerTool // ListStarredRepositories creates a tool to list starred repositories for the authenticated user or a specified user. func ListStarredRepositories(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataStargazers, mcp.Tool{ Name: "list_starred_repositories", @@ -1986,7 +1986,7 @@ func ListStarredRepositories(t translations.TranslationHelperFunc) inventory.Ser // StarRepository creates a tool to star a repository. func StarRepository(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataStargazers, mcp.Tool{ Name: "star_repository", @@ -2053,7 +2053,7 @@ func StarRepository(t translations.TranslationHelperFunc) inventory.ServerTool { // UnstarRepository creates a tool to unstar a repository. func UnstarRepository(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataStargazers, mcp.Tool{ Name: "unstar_repository", diff --git a/pkg/github/search.go b/pkg/github/search.go index d95214e55..b662cecdd 100644 --- a/pkg/github/search.go +++ b/pkg/github/search.go @@ -46,7 +46,7 @@ func SearchRepositories(t translations.TranslationHelperFunc) inventory.ServerTo } WithPagination(schema) - return NewToolWithScopes( + return NewTool( ToolsetMetadataRepos, mcp.Tool{ Name: "search_repositories", @@ -190,7 +190,7 @@ func SearchCode(t translations.TranslationHelperFunc) inventory.ServerTool { } WithPagination(schema) - return NewToolWithScopes( + return NewTool( ToolsetMetadataRepos, mcp.Tool{ Name: "search_code", @@ -373,7 +373,7 @@ func SearchUsers(t translations.TranslationHelperFunc) inventory.ServerTool { } WithPagination(schema) - return NewToolWithScopes( + return NewTool( ToolsetMetadataUsers, mcp.Tool{ Name: "search_users", @@ -416,7 +416,7 @@ func SearchOrgs(t translations.TranslationHelperFunc) inventory.ServerTool { } WithPagination(schema) - return NewToolWithScopes( + return NewTool( ToolsetMetadataOrgs, mcp.Tool{ Name: "search_orgs", diff --git a/pkg/github/secret_scanning.go b/pkg/github/secret_scanning.go index fb259758e..2f3c00877 100644 --- a/pkg/github/secret_scanning.go +++ b/pkg/github/secret_scanning.go @@ -18,7 +18,7 @@ import ( ) func GetSecretScanningAlert(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataSecretProtection, mcp.Tool{ Name: "get_secret_scanning_alert", @@ -96,7 +96,7 @@ func GetSecretScanningAlert(t translations.TranslationHelperFunc) inventory.Serv } func ListSecretScanningAlerts(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataSecretProtection, mcp.Tool{ Name: "list_secret_scanning_alerts", diff --git a/pkg/github/security_advisories.go b/pkg/github/security_advisories.go index 44858dbd9..3bbb0d1f7 100644 --- a/pkg/github/security_advisories.go +++ b/pkg/github/security_advisories.go @@ -18,7 +18,7 @@ import ( ) func ListGlobalSecurityAdvisories(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataSecurityAdvisories, mcp.Tool{ Name: "list_global_security_advisories", @@ -210,7 +210,7 @@ func ListGlobalSecurityAdvisories(t translations.TranslationHelperFunc) inventor } func ListRepositorySecurityAdvisories(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataSecurityAdvisories, mcp.Tool{ Name: "list_repository_security_advisories", @@ -315,7 +315,7 @@ func ListRepositorySecurityAdvisories(t translations.TranslationHelperFunc) inve } func GetGlobalSecurityAdvisory(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataSecurityAdvisories, mcp.Tool{ Name: "get_global_security_advisory", @@ -373,7 +373,7 @@ func GetGlobalSecurityAdvisory(t translations.TranslationHelperFunc) inventory.S } func ListOrgRepositorySecurityAdvisories(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewToolWithScopes( + return NewTool( ToolsetMetadataSecurityAdvisories, mcp.Tool{ Name: "list_org_repository_security_advisories", From c765101aebcc2b81d4a54d600e0fa91f692099e8 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 5 Jan 2026 11:20:38 +0100 Subject: [PATCH 021/138] refresh readme after rebase --- README.md | 59 +------------------------------------------------------ 1 file changed, 1 insertion(+), 58 deletions(-) diff --git a/README.md b/README.md index a4f803845..80345056a 100644 --- a/README.md +++ b/README.md @@ -492,46 +492,6 @@ The following sets of tools are available: workflow Actions -<<<<<<< HEAD -======= -- **actions_get** - Get details of GitHub Actions resources (workflows, workflow runs, jobs, and artifacts) - - **Required OAuth Scopes**: `repo` - - `method`: The method to execute (string, required) - - `owner`: Repository owner (string, required) - - `repo`: Repository name (string, required) - - `resource_id`: The unique identifier of the resource. This will vary based on the "method" provided, so ensure you provide the correct ID: - - Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'get_workflow' method. - - Provide a workflow run ID for 'get_workflow_run', 'get_workflow_run_usage', and 'get_workflow_run_logs_url' methods. - - Provide an artifact ID for 'download_workflow_run_artifact' method. - - Provide a job ID for 'get_workflow_job' method. - (string, required) - -- **actions_list** - List GitHub Actions workflows in a repository - - **Required OAuth Scopes**: `repo` - - `method`: The action to perform (string, required) - - `owner`: Repository owner (string, required) - - `page`: Page number for pagination (default: 1) (number, optional) - - `per_page`: Results per page for pagination (default: 30, max: 100) (number, optional) - - `repo`: Repository name (string, required) - - `resource_id`: The unique identifier of the resource. This will vary based on the "method" provided, so ensure you provide the correct ID: - - Do not provide any resource ID for 'list_workflows' method. - - Provide a workflow ID or workflow file name (e.g. ci.yaml) for 'list_workflow_runs' method. - - Provide a workflow run ID for 'list_workflow_jobs' and 'list_workflow_run_artifacts' methods. - (string, optional) - - `workflow_jobs_filter`: Filters for workflow jobs. **ONLY** used when method is 'list_workflow_jobs' (object, optional) - - `workflow_runs_filter`: Filters for workflow runs. **ONLY** used when method is 'list_workflow_runs' (object, optional) - -- **actions_run_trigger** - Trigger GitHub Actions workflow actions - - **Required OAuth Scopes**: `repo` - - `inputs`: Inputs the workflow accepts. Only used for 'run_workflow' method. (object, optional) - - `method`: The method to execute (string, required) - - `owner`: Repository owner (string, required) - - `ref`: The git reference for the workflow. The reference can be a branch or tag name. Required for 'run_workflow' method. (string, optional) - - `repo`: Repository name (string, required) - - `run_id`: The ID of the workflow run. Required for all methods except 'run_workflow'. (number, optional) - - `workflow_id`: The workflow ID (numeric) or workflow file name (e.g., main.yml, ci.yaml). Required for 'run_workflow' method. (string, optional) - ->>>>>>> 4ed0310 (Complete OAuth scope implementation for all tools) - **cancel_workflow_run** - Cancel workflow run - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) @@ -560,19 +520,6 @@ The following sets of tools are available: - `run_id`: Workflow run ID (required when using failed_only) (number, optional) - `tail_lines`: Number of lines to return from the end of the log (number, optional) -<<<<<<< HEAD -======= -- **get_job_logs** - Get GitHub Actions workflow job logs - - **Required OAuth Scopes**: `repo` - - `failed_only`: When true, gets logs for all failed jobs in the workflow run specified by run_id. Requires run_id to be provided. (boolean, optional) - - `job_id`: The unique identifier of the workflow job. Required when getting logs for a single job. (number, optional) - - `owner`: Repository owner (string, required) - - `repo`: Repository name (string, required) - - `return_content`: Returns actual log content instead of URLs (boolean, optional) - - `run_id`: The unique identifier of the workflow run. Required when failed_only is true to get logs for all failed jobs in the run. (number, optional) - - `tail_lines`: Number of lines to return from the end of the log (number, optional) - ->>>>>>> 4ed0310 (Complete OAuth scope implementation for all tools) - **get_workflow_run** - Get workflow run - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) @@ -803,12 +750,8 @@ The following sets of tools are available: - `repo`: Repository name (string, required) - **assign_copilot_to_issue** - Assign Copilot to issue -<<<<<<< HEAD - - `issue_number`: Issue number (number, required) -======= - **Required OAuth Scopes**: `repo` - - `issueNumber`: Issue number (number, required) ->>>>>>> 4ed0310 (Complete OAuth scope implementation for all tools) + - `issue_number`: Issue number (number, required) - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) From 7796c08681eab7cf3fd66aa599e68164ec614d7b Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 5 Jan 2026 12:02:26 +0100 Subject: [PATCH 022/138] Add scope hierarchy and auto-derive accepted scopes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add ScopeHierarchy map defining parent-child scope relationships - Add ExpandScopes() function to derive accepted scopes from required scopes - Update NewTool/NewToolFromHandler to take []scopes.Scope and auto-derive AcceptedScopes - Add new scope constants: NoScope, User, ReadUser, UserEmail, ReadPackages, WritePackages - Update all tool files to use new signature with typed scopes - Add comprehensive tests for ExpandScopes The scope hierarchy allows automatic derivation of accepted scopes: - repo → public_repo, security_events - admin:org → write:org → read:org - project → read:project - write:packages → read:packages - user → read:user, user:email This enables the remote server to consume scope info directly from OSS tools. --- pkg/github/actions.go | 54 ++++------- pkg/github/code_scanning.go | 6 +- pkg/github/context_tools.go | 9 +- pkg/github/dependabot.go | 6 +- pkg/github/dependencies.go | 22 +++-- pkg/github/discussions.go | 12 +-- pkg/github/gists.go | 12 +-- pkg/github/git.go | 3 +- pkg/github/issues.go | 24 ++--- pkg/github/labels.go | 9 +- pkg/github/notifications.go | 18 ++-- pkg/github/projects.go | 27 ++---- pkg/github/pullrequests.go | 30 ++---- pkg/github/repositories.go | 54 ++++------- pkg/github/search.go | 12 +-- pkg/github/secret_scanning.go | 6 +- pkg/github/security_advisories.go | 12 +-- pkg/scopes/scopes.go | 63 +++++++++++++ pkg/scopes/scopes_test.go | 152 ++++++++++++++++++++++++++++++ 19 files changed, 325 insertions(+), 206 deletions(-) create mode 100644 pkg/scopes/scopes_test.go diff --git a/pkg/github/actions.go b/pkg/github/actions.go index c6cbd2ee7..14cb8028c 100644 --- a/pkg/github/actions.go +++ b/pkg/github/actions.go @@ -75,8 +75,7 @@ func ListWorkflows(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }), }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -203,8 +202,7 @@ func ListWorkflowRuns(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo", "workflow_id"}, }), }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -316,8 +314,7 @@ func RunWorkflow(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "workflow_id", "ref"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -422,8 +419,7 @@ func GetWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "run_id"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -492,8 +488,7 @@ func GetWorkflowRunLogs(t translations.TranslationHelperFunc) inventory.ServerTo Required: []string{"owner", "repo", "run_id"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -577,8 +572,7 @@ func ListWorkflowJobs(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo", "run_id"}, }), }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -691,8 +685,7 @@ func GetJobLogs(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -941,8 +934,7 @@ func RerunWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo", "run_id"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -1018,8 +1010,7 @@ func RerunFailedJobs(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo", "run_id"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -1095,8 +1086,7 @@ func CancelWorkflowRun(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"owner", "repo", "run_id"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -1174,8 +1164,7 @@ func ListWorkflowRunArtifacts(t translations.TranslationHelperFunc) inventory.Se Required: []string{"owner", "repo", "run_id"}, }), }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -1256,8 +1245,7 @@ func DownloadWorkflowRunArtifact(t translations.TranslationHelperFunc) inventory Required: []string{"owner", "repo", "artifact_id"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -1336,8 +1324,7 @@ func DeleteWorkflowRunLogs(t translations.TranslationHelperFunc) inventory.Serve Required: []string{"owner", "repo", "run_id"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -1413,8 +1400,7 @@ func GetWorkflowRunUsage(t translations.TranslationHelperFunc) inventory.ServerT Required: []string{"owner", "repo", "run_id"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -1579,8 +1565,7 @@ Use this tool to list workflows in a repository, or list workflow runs, jobs, an Required: []string{"method", "owner", "repo"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1699,8 +1684,7 @@ Use this tool to get details about individual workflows, workflow runs, jobs, an Required: []string{"method", "owner", "repo", "resource_id"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1814,8 +1798,7 @@ func ActionsRunTrigger(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"method", "owner", "repo"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1930,8 +1913,7 @@ For single job logs, provide job_id. For all failed jobs in a run, provide run_i Required: []string{"owner", "repo"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/code_scanning.go b/pkg/github/code_scanning.go index 99ab8ad4f..ccc00661a 100644 --- a/pkg/github/code_scanning.go +++ b/pkg/github/code_scanning.go @@ -45,8 +45,7 @@ func GetCodeScanningAlert(t translations.TranslationHelperFunc) inventory.Server Required: []string{"owner", "repo", "alertNumber"}, }, }, - scopes.ToStringSlice(scopes.SecurityEvents), - scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), + []scopes.Scope{scopes.SecurityEvents}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -138,8 +137,7 @@ func ListCodeScanningAlerts(t translations.TranslationHelperFunc) inventory.Serv Required: []string{"owner", "repo"}, }, }, - scopes.ToStringSlice(scopes.SecurityEvents), - scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), + []scopes.Scope{scopes.SecurityEvents}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/context_tools.go b/pkg/github/context_tools.go index ad3c8daa4..29fa2925d 100644 --- a/pkg/github/context_tools.go +++ b/pkg/github/context_tools.go @@ -52,8 +52,7 @@ func GetMe(t translations.TranslationHelperFunc) inventory.ServerTool { // OpenAI strict mode requires the properties field to be present. InputSchema: json.RawMessage(`{"type":"object","properties":{}}`), }, - nil, // no required scopes - nil, // no accepted scopes + nil, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, _ map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -132,8 +131,7 @@ func GetTeams(t translations.TranslationHelperFunc) inventory.ServerTool { }, }, }, - scopes.ToStringSlice(scopes.ReadOrg), - scopes.ToStringSlice(scopes.ReadOrg, scopes.WriteOrg, scopes.AdminOrg), + []scopes.Scope{scopes.ReadOrg}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { user, err := OptionalParam[string](args, "user") if err != nil { @@ -236,8 +234,7 @@ func GetTeamMembers(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"org", "team_slug"}, }, }, - scopes.ToStringSlice(scopes.ReadOrg), - scopes.ToStringSlice(scopes.ReadOrg, scopes.WriteOrg, scopes.AdminOrg), + []scopes.Scope{scopes.ReadOrg}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { org, err := RequiredParam[string](args, "org") if err != nil { diff --git a/pkg/github/dependabot.go b/pkg/github/dependabot.go index 5c86d9709..b6b2eeaba 100644 --- a/pkg/github/dependabot.go +++ b/pkg/github/dependabot.go @@ -46,8 +46,7 @@ func GetDependabotAlert(t translations.TranslationHelperFunc) inventory.ServerTo Required: []string{"owner", "repo", "alertNumber"}, }, }, - scopes.ToStringSlice(scopes.SecurityEvents), - scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), + []scopes.Scope{scopes.SecurityEvents}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -131,8 +130,7 @@ func ListDependabotAlerts(t translations.TranslationHelperFunc) inventory.Server Required: []string{"owner", "repo"}, }, }, - scopes.ToStringSlice(scopes.SecurityEvents), - scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), + []scopes.Scope{scopes.SecurityEvents}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/dependencies.go b/pkg/github/dependencies.go index d5a5e30aa..b41bf0b87 100644 --- a/pkg/github/dependencies.go +++ b/pkg/github/dependencies.go @@ -7,6 +7,7 @@ import ( "github.com/github/github-mcp-server/pkg/inventory" "github.com/github/github-mcp-server/pkg/lockdown" "github.com/github/github-mcp-server/pkg/raw" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" gogithub "github.com/google/go-github/v79/github" "github.com/modelcontextprotocol/go-sdk/mcp" @@ -149,20 +150,21 @@ func (d BaseDeps) GetContentWindowSize() int { return d.ContentWindowSize } // The handler function receives deps extracted from context via MustDepsFromContext. // Ensure ContextWithDeps is called to inject deps before any tool handlers are invoked. // -// All tools must explicitly specify their OAuth scope requirements, even if empty (nil). +// requiredScopes specifies the minimum OAuth scopes needed for this tool. +// AcceptedScopes are automatically derived using the scope hierarchy (e.g., if +// public_repo is required, repo is also accepted since repo grants public_repo). func NewTool[In, Out any]( toolset inventory.ToolsetMetadata, tool mcp.Tool, - requiredScopes []string, - acceptedScopes []string, + requiredScopes []scopes.Scope, handler func(ctx context.Context, deps ToolDependencies, req *mcp.CallToolRequest, args In) (*mcp.CallToolResult, Out, error), ) inventory.ServerTool { st := inventory.NewServerToolWithContextHandler(tool, toolset, func(ctx context.Context, req *mcp.CallToolRequest, args In) (*mcp.CallToolResult, Out, error) { deps := MustDepsFromContext(ctx) return handler(ctx, deps, req, args) }) - st.RequiredScopes = requiredScopes - st.AcceptedScopes = acceptedScopes + st.RequiredScopes = scopes.ToStringSlice(requiredScopes...) + st.AcceptedScopes = scopes.ExpandScopes(requiredScopes...) return st } @@ -172,19 +174,19 @@ func NewTool[In, Out any]( // The handler function receives deps extracted from context via MustDepsFromContext. // Ensure ContextWithDeps is called to inject deps before any tool handlers are invoked. // -// All tools must explicitly specify their OAuth scope requirements, even if empty (nil). +// requiredScopes specifies the minimum OAuth scopes needed for this tool. +// AcceptedScopes are automatically derived using the scope hierarchy. func NewToolFromHandler( toolset inventory.ToolsetMetadata, tool mcp.Tool, - requiredScopes []string, - acceptedScopes []string, + requiredScopes []scopes.Scope, handler func(ctx context.Context, deps ToolDependencies, req *mcp.CallToolRequest) (*mcp.CallToolResult, error), ) inventory.ServerTool { st := inventory.NewServerToolWithRawContextHandler(tool, toolset, func(ctx context.Context, req *mcp.CallToolRequest) (*mcp.CallToolResult, error) { deps := MustDepsFromContext(ctx) return handler(ctx, deps, req) }) - st.RequiredScopes = requiredScopes - st.AcceptedScopes = acceptedScopes + st.RequiredScopes = scopes.ToStringSlice(requiredScopes...) + st.AcceptedScopes = scopes.ExpandScopes(requiredScopes...) return st } diff --git a/pkg/github/discussions.go b/pkg/github/discussions.go index 270228c73..c03670818 100644 --- a/pkg/github/discussions.go +++ b/pkg/github/discussions.go @@ -162,8 +162,7 @@ func ListDiscussions(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner"}, }), }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -306,8 +305,7 @@ func GetDiscussion(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "discussionNumber"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { // Decode params var params struct { @@ -411,8 +409,7 @@ func GetDiscussionComments(t translations.TranslationHelperFunc) inventory.Serve Required: []string{"owner", "repo", "discussionNumber"}, }), }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { // Decode params var params struct { @@ -535,8 +532,7 @@ func ListDiscussionCategories(t translations.TranslationHelperFunc) inventory.Se Required: []string{"owner"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/gists.go b/pkg/github/gists.go index 0509220e3..0f43ebdf9 100644 --- a/pkg/github/gists.go +++ b/pkg/github/gists.go @@ -42,8 +42,7 @@ func ListGists(t translations.TranslationHelperFunc) inventory.ServerTool { }, }), }, - nil, // no required scopes - nil, // no accepted scopes + nil, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { username, err := OptionalParam[string](args, "username") if err != nil { @@ -127,8 +126,7 @@ func GetGist(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"gist_id"}, }, }, - nil, // no required scopes - nil, // no accepted scopes + nil, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { gistID, err := RequiredParam[string](args, "gist_id") if err != nil { @@ -199,8 +197,7 @@ func CreateGist(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"filename", "content"}, }, }, - scopes.ToStringSlice(scopes.Gist), - scopes.ToStringSlice(scopes.Gist), + []scopes.Scope{scopes.Gist}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { description, err := OptionalParam[string](args, "description") if err != nil { @@ -302,8 +299,7 @@ func UpdateGist(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"gist_id", "filename", "content"}, }, }, - scopes.ToStringSlice(scopes.Gist), - scopes.ToStringSlice(scopes.Gist), + []scopes.Scope{scopes.Gist}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { gistID, err := RequiredParam[string](args, "gist_id") if err != nil { diff --git a/pkg/github/git.go b/pkg/github/git.go index 8e632c1af..ec7159b9b 100644 --- a/pkg/github/git.go +++ b/pkg/github/git.go @@ -77,8 +77,7 @@ func GetRepositoryTree(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"owner", "repo"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/issues.go b/pkg/github/issues.go index 9f2223295..1e29a0eef 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -275,8 +275,7 @@ Options are: }, InputSchema: schema, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { method, err := RequiredParam[string](args, "method") if err != nil { @@ -568,8 +567,7 @@ func ListIssueTypes(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner"}, }, }, - scopes.ToStringSlice(scopes.ReadOrg), - scopes.ToStringSlice(scopes.ReadOrg, scopes.WriteOrg, scopes.AdminOrg), + []scopes.Scope{scopes.ReadOrg}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -637,8 +635,7 @@ func AddIssueComment(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo", "issue_number", "body"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -743,8 +740,7 @@ Options are: Required: []string{"method", "owner", "repo", "issue_number", "sub_issue_id"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { method, err := RequiredParam[string](args, "method") if err != nil { @@ -972,8 +968,7 @@ func SearchIssues(t translations.TranslationHelperFunc) inventory.ServerTool { }, InputSchema: schema, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { result, err := searchHandler(ctx, deps.GetClient, args, "issue", "failed to search issues") return result, nil, err @@ -1063,8 +1058,7 @@ Options are: Required: []string{"method", "owner", "repo"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { method, err := RequiredParam[string](args, "method") if err != nil { @@ -1398,8 +1392,7 @@ func ListIssues(t translations.TranslationHelperFunc) inventory.ServerTool { }, InputSchema: schema, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1657,8 +1650,7 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server Required: []string{"owner", "repo", "issue_number"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { var params struct { Owner string `mapstructure:"owner"` diff --git a/pkg/github/labels.go b/pkg/github/labels.go index e85dee450..0dbb622d9 100644 --- a/pkg/github/labels.go +++ b/pkg/github/labels.go @@ -46,8 +46,7 @@ func GetLabel(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "name"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -145,8 +144,7 @@ func ListLabels(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -258,8 +256,7 @@ func LabelWrite(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"method", "owner", "repo", "name"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { // Get and validate required parameters method, err := RequiredParam[string](args, "method") diff --git a/pkg/github/notifications.go b/pkg/github/notifications.go index 87026e2cb..1de24fb0d 100644 --- a/pkg/github/notifications.go +++ b/pkg/github/notifications.go @@ -63,8 +63,7 @@ func ListNotifications(t translations.TranslationHelperFunc) inventory.ServerToo }, }), }, - scopes.ToStringSlice(scopes.Notifications), - scopes.ToStringSlice(scopes.Notifications), + []scopes.Scope{scopes.Notifications}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -190,8 +189,7 @@ func DismissNotification(t translations.TranslationHelperFunc) inventory.ServerT Required: []string{"threadID", "state"}, }, }, - scopes.ToStringSlice(scopes.Notifications), - scopes.ToStringSlice(scopes.Notifications), + []scopes.Scope{scopes.Notifications}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -275,8 +273,7 @@ func MarkAllNotificationsRead(t translations.TranslationHelperFunc) inventory.Se }, }, }, - scopes.ToStringSlice(scopes.Notifications), - scopes.ToStringSlice(scopes.Notifications), + []scopes.Scope{scopes.Notifications}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -361,8 +358,7 @@ func GetNotificationDetails(t translations.TranslationHelperFunc) inventory.Serv Required: []string{"notificationID"}, }, }, - scopes.ToStringSlice(scopes.Notifications), - scopes.ToStringSlice(scopes.Notifications), + []scopes.Scope{scopes.Notifications}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -436,8 +432,7 @@ func ManageNotificationSubscription(t translations.TranslationHelperFunc) invent Required: []string{"notificationID", "action"}, }, }, - scopes.ToStringSlice(scopes.Notifications), - scopes.ToStringSlice(scopes.Notifications), + []scopes.Scope{scopes.Notifications}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -537,8 +532,7 @@ func ManageRepositoryNotificationSubscription(t translations.TranslationHelperFu Required: []string{"owner", "repo", "action"}, }, }, - scopes.ToStringSlice(scopes.Notifications), - scopes.ToStringSlice(scopes.Notifications), + []scopes.Scope{scopes.Notifications}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { diff --git a/pkg/github/projects.go b/pkg/github/projects.go index 3204243f7..79cbbe680 100644 --- a/pkg/github/projects.go +++ b/pkg/github/projects.go @@ -68,8 +68,7 @@ func ListProjects(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner_type", "owner"}, }, }, - scopes.ToStringSlice(scopes.ReadProject), - scopes.ToStringSlice(scopes.ReadProject, scopes.Project), + []scopes.Scope{scopes.ReadProject}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") @@ -175,8 +174,7 @@ func GetProject(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"project_number", "owner_type", "owner"}, }, }, - scopes.ToStringSlice(scopes.ReadProject), - scopes.ToStringSlice(scopes.ReadProject, scopes.Project), + []scopes.Scope{scopes.ReadProject}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { projectNumber, err := RequiredInt(args, "project_number") @@ -277,8 +275,7 @@ func ListProjectFields(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"owner_type", "owner", "project_number"}, }, }, - scopes.ToStringSlice(scopes.ReadProject), - scopes.ToStringSlice(scopes.ReadProject, scopes.Project), + []scopes.Scope{scopes.ReadProject}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") @@ -377,8 +374,7 @@ func GetProjectField(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner_type", "owner", "project_number", "field_id"}, }, }, - scopes.ToStringSlice(scopes.ReadProject), - scopes.ToStringSlice(scopes.ReadProject, scopes.Project), + []scopes.Scope{scopes.ReadProject}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") @@ -490,8 +486,7 @@ func ListProjectItems(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner_type", "owner", "project_number"}, }, }, - scopes.ToStringSlice(scopes.ReadProject), - scopes.ToStringSlice(scopes.ReadProject, scopes.Project), + []scopes.Scope{scopes.ReadProject}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") @@ -616,8 +611,7 @@ func GetProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner_type", "owner", "project_number", "item_id"}, }, }, - scopes.ToStringSlice(scopes.ReadProject), - scopes.ToStringSlice(scopes.ReadProject, scopes.Project), + []scopes.Scope{scopes.ReadProject}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") @@ -722,8 +716,7 @@ func AddProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner_type", "owner", "project_number", "item_type", "item_id"}, }, }, - scopes.ToStringSlice(scopes.Project), - scopes.ToStringSlice(scopes.Project), + []scopes.Scope{scopes.Project}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") @@ -834,8 +827,7 @@ func UpdateProjectItem(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"owner_type", "owner", "project_number", "item_id", "updated_field"}, }, }, - scopes.ToStringSlice(scopes.Project), - scopes.ToStringSlice(scopes.Project), + []scopes.Scope{scopes.Project}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") @@ -945,8 +937,7 @@ func DeleteProjectItem(t translations.TranslationHelperFunc) inventory.ServerToo Required: []string{"owner_type", "owner", "project_number", "item_id"}, }, }, - scopes.ToStringSlice(scopes.Project), - scopes.ToStringSlice(scopes.Project), + []scopes.Scope{scopes.Project}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") diff --git a/pkg/github/pullrequests.go b/pkg/github/pullrequests.go index 91145f61d..62952783e 100644 --- a/pkg/github/pullrequests.go +++ b/pkg/github/pullrequests.go @@ -70,8 +70,7 @@ Possible options: }, InputSchema: schema, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { method, err := RequiredParam[string](args, "method") if err != nil { @@ -521,8 +520,7 @@ func CreatePullRequest(t translations.TranslationHelperFunc) inventory.ServerToo }, InputSchema: schema, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -674,8 +672,7 @@ func UpdatePullRequest(t translations.TranslationHelperFunc) inventory.ServerToo }, InputSchema: schema, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -957,8 +954,7 @@ func ListPullRequests(t translations.TranslationHelperFunc) inventory.ServerTool }, InputSchema: schema, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1095,8 +1091,7 @@ func MergePullRequest(t translations.TranslationHelperFunc) inventory.ServerTool }, InputSchema: schema, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1214,8 +1209,7 @@ func SearchPullRequests(t translations.TranslationHelperFunc) inventory.ServerTo }, InputSchema: schema, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { result, err := searchHandler(ctx, deps.GetClient, args, "pr", "failed to search pull requests") return result, nil, err @@ -1258,8 +1252,7 @@ func UpdatePullRequestBranch(t translations.TranslationHelperFunc) inventory.Ser }, InputSchema: schema, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1386,8 +1379,7 @@ Available methods: }, InputSchema: schema, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { var params PullRequestReviewWriteParams if err := mapstructure.Decode(args, ¶ms); err != nil { @@ -1711,8 +1703,7 @@ func AddCommentToPendingReview(t translations.TranslationHelperFunc) inventory.S }, InputSchema: schema, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { var params struct { Owner string @@ -1865,8 +1856,7 @@ func RequestCopilotReview(t translations.TranslationHelperFunc) inventory.Server }, InputSchema: schema, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index c2f976e81..633648891 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -55,8 +55,7 @@ func GetCommit(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "sha"}, }), }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -153,8 +152,7 @@ func ListCommits(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }), }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -254,8 +252,7 @@ func ListBranches(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }), }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -369,8 +366,7 @@ If the SHA is not provided, the tool will attempt to acquire it by fetching the Required: []string{"owner", "repo", "path", "content", "message", "branch"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -554,8 +550,7 @@ func CreateRepository(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"name"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { name, err := RequiredParam[string](args, "name") if err != nil { @@ -662,8 +657,7 @@ func GetFileContents(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -847,8 +841,7 @@ func ForkRepository(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -955,8 +948,7 @@ func DeleteFile(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "path", "message", "branch"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1136,8 +1128,7 @@ func CreateBranch(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "branch"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1268,8 +1259,7 @@ func PushFiles(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "branch", "files", "message"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1477,8 +1467,7 @@ func ListTags(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }), }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1561,8 +1550,7 @@ func GetTag(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo", "tag"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1656,8 +1644,7 @@ func ListReleases(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }), }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1732,8 +1719,7 @@ func GetLatestRelease(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1802,8 +1788,7 @@ func GetReleaseByTag(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo", "tag"}, }, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -1882,8 +1867,7 @@ func ListStarredRepositories(t translations.TranslationHelperFunc) inventory.Ser }, }), }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { username, err := OptionalParam[string](args, "username") if err != nil { @@ -2011,8 +1995,7 @@ func StarRepository(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }, }, - scopes.ToStringSlice(scopes.PublicRepo), - scopes.ToStringSlice(scopes.PublicRepo, scopes.Repo), + []scopes.Scope{scopes.PublicRepo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -2077,8 +2060,7 @@ func UnstarRepository(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo"}, }, }, - scopes.ToStringSlice(scopes.PublicRepo), - scopes.ToStringSlice(scopes.PublicRepo, scopes.Repo), + []scopes.Scope{scopes.PublicRepo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/search.go b/pkg/github/search.go index b662cecdd..552fbfe78 100644 --- a/pkg/github/search.go +++ b/pkg/github/search.go @@ -57,8 +57,7 @@ func SearchRepositories(t translations.TranslationHelperFunc) inventory.ServerTo }, InputSchema: schema, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { query, err := RequiredParam[string](args, "query") if err != nil { @@ -201,8 +200,7 @@ func SearchCode(t translations.TranslationHelperFunc) inventory.ServerTool { }, InputSchema: schema, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { query, err := RequiredParam[string](args, "query") if err != nil { @@ -384,8 +382,7 @@ func SearchUsers(t translations.TranslationHelperFunc) inventory.ServerTool { }, InputSchema: schema, }, - scopes.ToStringSlice(scopes.Repo), - scopes.ToStringSlice(scopes.Repo), + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { return userOrOrgHandler(ctx, "user", deps, args) }, @@ -427,8 +424,7 @@ func SearchOrgs(t translations.TranslationHelperFunc) inventory.ServerTool { }, InputSchema: schema, }, - scopes.ToStringSlice(scopes.ReadOrg), - scopes.ToStringSlice(scopes.ReadOrg, scopes.WriteOrg, scopes.AdminOrg), + []scopes.Scope{scopes.ReadOrg}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { return userOrOrgHandler(ctx, "org", deps, args) }, diff --git a/pkg/github/secret_scanning.go b/pkg/github/secret_scanning.go index 2f3c00877..fa60021e5 100644 --- a/pkg/github/secret_scanning.go +++ b/pkg/github/secret_scanning.go @@ -46,8 +46,7 @@ func GetSecretScanningAlert(t translations.TranslationHelperFunc) inventory.Serv Required: []string{"owner", "repo", "alertNumber"}, }, }, - scopes.ToStringSlice(scopes.SecurityEvents), - scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), + []scopes.Scope{scopes.SecurityEvents}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -134,8 +133,7 @@ func ListSecretScanningAlerts(t translations.TranslationHelperFunc) inventory.Se Required: []string{"owner", "repo"}, }, }, - scopes.ToStringSlice(scopes.SecurityEvents), - scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), + []scopes.Scope{scopes.SecurityEvents}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { diff --git a/pkg/github/security_advisories.go b/pkg/github/security_advisories.go index 3bbb0d1f7..7bdb978cd 100644 --- a/pkg/github/security_advisories.go +++ b/pkg/github/security_advisories.go @@ -84,8 +84,7 @@ func ListGlobalSecurityAdvisories(t translations.TranslationHelperFunc) inventor }, }, }, - scopes.ToStringSlice(scopes.SecurityEvents), - scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), + []scopes.Scope{scopes.SecurityEvents}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -249,8 +248,7 @@ func ListRepositorySecurityAdvisories(t translations.TranslationHelperFunc) inve Required: []string{"owner", "repo"}, }, }, - scopes.ToStringSlice(scopes.SecurityEvents), - scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), + []scopes.Scope{scopes.SecurityEvents}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -335,8 +333,7 @@ func GetGlobalSecurityAdvisory(t translations.TranslationHelperFunc) inventory.S Required: []string{"ghsaId"}, }, }, - scopes.ToStringSlice(scopes.SecurityEvents), - scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), + []scopes.Scope{scopes.SecurityEvents}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { client, err := deps.GetClient(ctx) if err != nil { @@ -408,8 +405,7 @@ func ListOrgRepositorySecurityAdvisories(t translations.TranslationHelperFunc) i Required: []string{"org"}, }, }, - scopes.ToStringSlice(scopes.SecurityEvents), - scopes.ToStringSlice(scopes.SecurityEvents, scopes.Repo), + []scopes.Scope{scopes.SecurityEvents}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { org, err := RequiredParam[string](args, "org") if err != nil { diff --git a/pkg/scopes/scopes.go b/pkg/scopes/scopes.go index 268a24d99..961cf0e1c 100644 --- a/pkg/scopes/scopes.go +++ b/pkg/scopes/scopes.go @@ -6,6 +6,9 @@ package scopes type Scope string const ( + // NoScope indicates no scope is required (public access). + NoScope Scope = "" + // Repo grants full control of private repositories Repo Scope = "repo" @@ -35,8 +38,35 @@ const ( // SecurityEvents grants read and write access to security events SecurityEvents Scope = "security_events" + + // User grants read/write access to profile info + User Scope = "user" + + // ReadUser grants read-only access to profile info + ReadUser Scope = "read:user" + + // UserEmail grants read access to user email addresses + UserEmail Scope = "user:email" + + // ReadPackages grants read access to packages + ReadPackages Scope = "read:packages" + + // WritePackages grants write access to packages + WritePackages Scope = "write:packages" ) +// ScopeHierarchy defines parent-child relationships between scopes. +// A parent scope implicitly grants access to all child scopes. +// For example, "repo" grants access to "public_repo" and "security_events". +var ScopeHierarchy = map[Scope][]Scope{ + Repo: {PublicRepo, SecurityEvents}, + AdminOrg: {WriteOrg, ReadOrg}, + WriteOrg: {ReadOrg}, + Project: {ReadProject}, + WritePackages: {ReadPackages}, + User: {ReadUser, UserEmail}, +} + // ScopeSet represents a set of OAuth scopes. type ScopeSet map[Scope]bool @@ -75,3 +105,36 @@ func ToStringSlice(scopes ...Scope) []string { } return result } + +// ExpandScopes takes a list of required scopes and returns all accepted scopes +// including parent scopes from the hierarchy. +// For example, if "public_repo" is required, "repo" is also accepted since +// having the "repo" scope grants access to "public_repo". +func ExpandScopes(required ...Scope) []string { + if len(required) == 0 { + return nil + } + + accepted := make(map[string]bool) + + // Add required scopes + for _, scope := range required { + accepted[string(scope)] = true + } + + // Add parent scopes that grant access to required scopes + for parent, children := range ScopeHierarchy { + for _, child := range children { + if accepted[string(child)] { + accepted[string(parent)] = true + } + } + } + + // Convert to slice + result := make([]string, 0, len(accepted)) + for scope := range accepted { + result = append(result, scope) + } + return result +} diff --git a/pkg/scopes/scopes_test.go b/pkg/scopes/scopes_test.go new file mode 100644 index 000000000..8ef29a115 --- /dev/null +++ b/pkg/scopes/scopes_test.go @@ -0,0 +1,152 @@ +package scopes + +import ( + "sort" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestExpandScopes(t *testing.T) { + tests := []struct { + name string + required []Scope + expected []string + }{ + { + name: "nil returns nil", + required: nil, + expected: nil, + }, + { + name: "empty returns nil", + required: []Scope{}, + expected: nil, + }, + { + name: "repo scope returns just repo", + required: []Scope{Repo}, + expected: []string{"repo"}, + }, + { + name: "public_repo also accepts repo (parent)", + required: []Scope{PublicRepo}, + expected: []string{"public_repo", "repo"}, + }, + { + name: "security_events also accepts repo (parent)", + required: []Scope{SecurityEvents}, + expected: []string{"repo", "security_events"}, + }, + { + name: "read:org also accepts write:org and admin:org (parents)", + required: []Scope{ReadOrg}, + expected: []string{"admin:org", "read:org", "write:org"}, + }, + { + name: "write:org also accepts admin:org (parent)", + required: []Scope{WriteOrg}, + expected: []string{"admin:org", "write:org"}, + }, + { + name: "admin:org returns just admin:org (no parent)", + required: []Scope{AdminOrg}, + expected: []string{"admin:org"}, + }, + { + name: "read:project also accepts project (parent)", + required: []Scope{ReadProject}, + expected: []string{"project", "read:project"}, + }, + { + name: "project returns just project (no parent)", + required: []Scope{Project}, + expected: []string{"project"}, + }, + { + name: "gist returns just gist (no parent)", + required: []Scope{Gist}, + expected: []string{"gist"}, + }, + { + name: "notifications returns just notifications (no parent)", + required: []Scope{Notifications}, + expected: []string{"notifications"}, + }, + { + name: "read:packages also accepts write:packages (parent)", + required: []Scope{ReadPackages}, + expected: []string{"read:packages", "write:packages"}, + }, + { + name: "read:user also accepts user (parent)", + required: []Scope{ReadUser}, + expected: []string{"read:user", "user"}, + }, + { + name: "multiple scopes combine correctly", + required: []Scope{PublicRepo, ReadOrg}, + expected: []string{"admin:org", "public_repo", "read:org", "repo", "write:org"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ExpandScopes(tt.required...) + + // Sort both for consistent comparison + if result != nil { + sort.Strings(result) + } + if tt.expected != nil { + sort.Strings(tt.expected) + } + + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestToStringSlice(t *testing.T) { + tests := []struct { + name string + scopes []Scope + expected []string + }{ + { + name: "empty returns empty", + scopes: []Scope{}, + expected: []string{}, + }, + { + name: "single scope", + scopes: []Scope{Repo}, + expected: []string{"repo"}, + }, + { + name: "multiple scopes", + scopes: []Scope{Repo, Gist, ReadOrg}, + expected: []string{"repo", "gist", "read:org"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ToStringSlice(tt.scopes...) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestScopeHierarchy(t *testing.T) { + // Verify the hierarchy is correctly defined + assert.Contains(t, ScopeHierarchy[Repo], PublicRepo) + assert.Contains(t, ScopeHierarchy[Repo], SecurityEvents) + assert.Contains(t, ScopeHierarchy[AdminOrg], WriteOrg) + assert.Contains(t, ScopeHierarchy[AdminOrg], ReadOrg) + assert.Contains(t, ScopeHierarchy[WriteOrg], ReadOrg) + assert.Contains(t, ScopeHierarchy[Project], ReadProject) + assert.Contains(t, ScopeHierarchy[WritePackages], ReadPackages) + assert.Contains(t, ScopeHierarchy[User], ReadUser) + assert.Contains(t, ScopeHierarchy[User], UserEmail) +} From 3d1ae306f8021c6f47e349cd161db2c78d9202d8 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 5 Jan 2026 12:51:50 +0100 Subject: [PATCH 023/138] refres readme after update --- README.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 80345056a..4aec47e6b 100644 --- a/README.md +++ b/README.md @@ -628,13 +628,13 @@ The following sets of tools are available: - **get_team_members** - Get team members - **Required OAuth Scopes**: `read:org` - - **Accepted OAuth Scopes**: `read:org`, `write:org`, `admin:org` + - **Accepted OAuth Scopes**: `read:org`, `admin:org`, `write:org` - `org`: Organization login (owner) that contains the team. (string, required) - `team_slug`: Team slug (string, required) - **get_teams** - Get teams - **Required OAuth Scopes**: `read:org` - - **Accepted OAuth Scopes**: `read:org`, `write:org`, `admin:org` + - **Accepted OAuth Scopes**: `read:org`, `admin:org`, `write:org` - `user`: Username to get teams for. If not provided, uses the authenticated user. (string, optional) @@ -798,7 +798,7 @@ The following sets of tools are available: - **list_issue_types** - List available issue types - **Required OAuth Scopes**: `read:org` - - **Accepted OAuth Scopes**: `read:org`, `write:org`, `admin:org` + - **Accepted OAuth Scopes**: `read:org`, `admin:org`, `write:org` - `owner`: The organization owner of the repository (string, required) - **list_issues** - List issues @@ -916,7 +916,7 @@ The following sets of tools are available: - **search_orgs** - Search organizations - **Required OAuth Scopes**: `read:org` - - **Accepted OAuth Scopes**: `read:org`, `write:org`, `admin:org` + - **Accepted OAuth Scopes**: `read:org`, `admin:org`, `write:org` - `order`: Sort order (string, optional) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) @@ -946,7 +946,7 @@ The following sets of tools are available: - **get_project** - Get project - **Required OAuth Scopes**: `read:project` - - **Accepted OAuth Scopes**: `read:project`, `project` + - **Accepted OAuth Scopes**: `project`, `read:project` - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) - `owner_type`: Owner type (string, required) - `project_number`: The project's number (number, required) @@ -961,7 +961,7 @@ The following sets of tools are available: - **get_project_item** - Get project item - **Required OAuth Scopes**: `read:project` - - **Accepted OAuth Scopes**: `read:project`, `project` + - **Accepted OAuth Scopes**: `project`, `read:project` - `fields`: Specific list of field IDs to include in the response (e.g. ["102589", "985201", "169875"]). If not provided, only the title field is included. (string[], optional) - `item_id`: The item's ID. (number, required) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) From df9fc6a3b3726f6bb8f6d1153c9009bb876a865e Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 5 Jan 2026 13:05:19 +0100 Subject: [PATCH 024/138] Use repo scope for star/unstar tools instead of public_repo public_repo is implicit - the GitHub API handles the distinction between public and private repos. Using repo as the required scope is more consistent with our enforcement model: - PATs: tools visible if token has repo scope - OAuth: scope challenge requests repo scope --- README.md | 12 +++++------- pkg/github/repositories.go | 4 ++-- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 4aec47e6b..efb9a2294 100644 --- a/README.md +++ b/README.md @@ -628,7 +628,7 @@ The following sets of tools are available: - **get_team_members** - Get team members - **Required OAuth Scopes**: `read:org` - - **Accepted OAuth Scopes**: `read:org`, `admin:org`, `write:org` + - **Accepted OAuth Scopes**: `write:org`, `read:org`, `admin:org` - `org`: Organization login (owner) that contains the team. (string, required) - `team_slug`: Team slug (string, required) @@ -946,7 +946,7 @@ The following sets of tools are available: - **get_project** - Get project - **Required OAuth Scopes**: `read:project` - - **Accepted OAuth Scopes**: `project`, `read:project` + - **Accepted OAuth Scopes**: `read:project`, `project` - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) - `owner_type`: Owner type (string, required) - `project_number`: The project's number (number, required) @@ -1265,7 +1265,7 @@ The following sets of tools are available: - **get_secret_scanning_alert** - Get secret scanning alert - **Required OAuth Scopes**: `security_events` - - **Accepted OAuth Scopes**: `security_events`, `repo` + - **Accepted OAuth Scopes**: `repo`, `security_events` - `alertNumber`: The number of the alert. (number, required) - `owner`: The owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) @@ -1337,14 +1337,12 @@ The following sets of tools are available: - `username`: Username to list starred repositories for. Defaults to the authenticated user. (string, optional) - **star_repository** - Star repository - - **Required OAuth Scopes**: `public_repo` - - **Accepted OAuth Scopes**: `public_repo`, `repo` + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) - **unstar_repository** - Unstar repository - - **Required OAuth Scopes**: `public_repo` - - **Accepted OAuth Scopes**: `public_repo`, `repo` + - **Required OAuth Scopes**: `repo` - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index 633648891..388f784b5 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -1995,7 +1995,7 @@ func StarRepository(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"owner", "repo"}, }, }, - []scopes.Scope{scopes.PublicRepo}, + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { @@ -2060,7 +2060,7 @@ func UnstarRepository(t translations.TranslationHelperFunc) inventory.ServerTool Required: []string{"owner", "repo"}, }, }, - []scopes.Scope{scopes.PublicRepo}, + []scopes.Scope{scopes.Repo}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { owner, err := RequiredParam[string](args, "owner") if err != nil { From cec5a1ae0f1d5f33607d33dbaaccdb15872d2962 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 5 Jan 2026 14:31:21 +0100 Subject: [PATCH 025/138] Fix conflict and regenerate docs after rebase --- README.md | 14 +++++++------- pkg/github/repositories.go | 1 - 2 files changed, 7 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index efb9a2294..380729c47 100644 --- a/README.md +++ b/README.md @@ -628,7 +628,7 @@ The following sets of tools are available: - **get_team_members** - Get team members - **Required OAuth Scopes**: `read:org` - - **Accepted OAuth Scopes**: `write:org`, `read:org`, `admin:org` + - **Accepted OAuth Scopes**: `read:org`, `admin:org`, `write:org` - `org`: Organization login (owner) that contains the team. (string, required) - `team_slug`: Team slug (string, required) @@ -652,7 +652,7 @@ The following sets of tools are available: - **list_dependabot_alerts** - List dependabot alerts - **Required OAuth Scopes**: `security_events` - - **Accepted OAuth Scopes**: `security_events`, `repo` + - **Accepted OAuth Scopes**: `repo`, `security_events` - `owner`: The owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) - `severity`: Filter dependabot alerts by severity (string, optional) @@ -961,7 +961,7 @@ The following sets of tools are available: - **get_project_item** - Get project item - **Required OAuth Scopes**: `read:project` - - **Accepted OAuth Scopes**: `project`, `read:project` + - **Accepted OAuth Scopes**: `read:project`, `project` - `fields`: Specific list of field IDs to include in the response (e.g. ["102589", "985201", "169875"]). If not provided, only the title field is included. (string[], optional) - `item_id`: The item's ID. (number, required) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) @@ -970,7 +970,7 @@ The following sets of tools are available: - **list_project_fields** - List project fields - **Required OAuth Scopes**: `read:project` - - **Accepted OAuth Scopes**: `read:project`, `project` + - **Accepted OAuth Scopes**: `project`, `read:project` - `after`: Forward pagination cursor from previous pageInfo.nextCursor. (string, optional) - `before`: Backward pagination cursor from previous pageInfo.prevCursor (rare). (string, optional) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) @@ -992,7 +992,7 @@ The following sets of tools are available: - **list_projects** - List projects - **Required OAuth Scopes**: `read:project` - - **Accepted OAuth Scopes**: `read:project`, `project` + - **Accepted OAuth Scopes**: `project`, `read:project` - `after`: Forward pagination cursor from previous pageInfo.nextCursor. (string, optional) - `before`: Backward pagination cursor from previous pageInfo.prevCursor (rare). (string, optional) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) @@ -1265,7 +1265,7 @@ The following sets of tools are available: - **get_secret_scanning_alert** - Get secret scanning alert - **Required OAuth Scopes**: `security_events` - - **Accepted OAuth Scopes**: `repo`, `security_events` + - **Accepted OAuth Scopes**: `security_events`, `repo` - `alertNumber`: The number of the alert. (number, required) - `owner`: The owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) @@ -1307,7 +1307,7 @@ The following sets of tools are available: - **list_org_repository_security_advisories** - List org repository security advisories - **Required OAuth Scopes**: `security_events` - - **Accepted OAuth Scopes**: `security_events`, `repo` + - **Accepted OAuth Scopes**: `repo`, `security_events` - `direction`: Sort direction. (string, optional) - `org`: The organization login. (string, required) - `sort`: Sort field. (string, optional) diff --git a/pkg/github/repositories.go b/pkg/github/repositories.go index 388f784b5..f6203f39f 100644 --- a/pkg/github/repositories.go +++ b/pkg/github/repositories.go @@ -12,7 +12,6 @@ import ( ghErrors "github.com/github/github-mcp-server/pkg/errors" "github.com/github/github-mcp-server/pkg/inventory" "github.com/github/github-mcp-server/pkg/octicons" - "github.com/github/github-mcp-server/pkg/raw" "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" "github.com/github/github-mcp-server/pkg/utils" From 48744ca5565fc3f59c648fcb23a73ca6ed59dabc Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 5 Jan 2026 14:37:42 +0100 Subject: [PATCH 026/138] Sort scope slices for deterministic output Map iteration in Go is non-deterministic, which causes doc generation to produce different output on each run. Sort the scope slices in: - ScopeSet.ToSlice() - ScopeSet.ToStringSlice() - ExpandScopes() --- README.md | 32 ++++++++++++++++---------------- pkg/scopes/scopes.go | 12 +++++++++++- 2 files changed, 27 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 380729c47..1a0f6b1c4 100644 --- a/README.md +++ b/README.md @@ -602,14 +602,14 @@ The following sets of tools are available: - **get_code_scanning_alert** - Get code scanning alert - **Required OAuth Scopes**: `security_events` - - **Accepted OAuth Scopes**: `security_events`, `repo` + - **Accepted OAuth Scopes**: `repo`, `security_events` - `alertNumber`: The number of the alert. (number, required) - `owner`: The owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) - **list_code_scanning_alerts** - List code scanning alerts - **Required OAuth Scopes**: `security_events` - - **Accepted OAuth Scopes**: `security_events`, `repo` + - **Accepted OAuth Scopes**: `repo`, `security_events` - `owner`: The owner of the repository. (string, required) - `ref`: The Git reference for the results you want to list. (string, optional) - `repo`: The name of the repository. (string, required) @@ -628,13 +628,13 @@ The following sets of tools are available: - **get_team_members** - Get team members - **Required OAuth Scopes**: `read:org` - - **Accepted OAuth Scopes**: `read:org`, `admin:org`, `write:org` + - **Accepted OAuth Scopes**: `admin:org`, `read:org`, `write:org` - `org`: Organization login (owner) that contains the team. (string, required) - `team_slug`: Team slug (string, required) - **get_teams** - Get teams - **Required OAuth Scopes**: `read:org` - - **Accepted OAuth Scopes**: `read:org`, `admin:org`, `write:org` + - **Accepted OAuth Scopes**: `admin:org`, `read:org`, `write:org` - `user`: Username to get teams for. If not provided, uses the authenticated user. (string, optional) @@ -645,7 +645,7 @@ The following sets of tools are available: - **get_dependabot_alert** - Get dependabot alert - **Required OAuth Scopes**: `security_events` - - **Accepted OAuth Scopes**: `security_events`, `repo` + - **Accepted OAuth Scopes**: `repo`, `security_events` - `alertNumber`: The number of the alert. (number, required) - `owner`: The owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) @@ -798,7 +798,7 @@ The following sets of tools are available: - **list_issue_types** - List available issue types - **Required OAuth Scopes**: `read:org` - - **Accepted OAuth Scopes**: `read:org`, `admin:org`, `write:org` + - **Accepted OAuth Scopes**: `admin:org`, `read:org`, `write:org` - `owner`: The organization owner of the repository (string, required) - **list_issues** - List issues @@ -916,7 +916,7 @@ The following sets of tools are available: - **search_orgs** - Search organizations - **Required OAuth Scopes**: `read:org` - - **Accepted OAuth Scopes**: `read:org`, `admin:org`, `write:org` + - **Accepted OAuth Scopes**: `admin:org`, `read:org`, `write:org` - `order`: Sort order (string, optional) - `page`: Page number for pagination (min 1) (number, optional) - `perPage`: Results per page for pagination (min 1, max 100) (number, optional) @@ -946,14 +946,14 @@ The following sets of tools are available: - **get_project** - Get project - **Required OAuth Scopes**: `read:project` - - **Accepted OAuth Scopes**: `read:project`, `project` + - **Accepted OAuth Scopes**: `project`, `read:project` - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) - `owner_type`: Owner type (string, required) - `project_number`: The project's number (number, required) - **get_project_field** - Get project field - **Required OAuth Scopes**: `read:project` - - **Accepted OAuth Scopes**: `read:project`, `project` + - **Accepted OAuth Scopes**: `project`, `read:project` - `field_id`: The field's id. (number, required) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) - `owner_type`: Owner type (string, required) @@ -961,7 +961,7 @@ The following sets of tools are available: - **get_project_item** - Get project item - **Required OAuth Scopes**: `read:project` - - **Accepted OAuth Scopes**: `read:project`, `project` + - **Accepted OAuth Scopes**: `project`, `read:project` - `fields`: Specific list of field IDs to include in the response (e.g. ["102589", "985201", "169875"]). If not provided, only the title field is included. (string[], optional) - `item_id`: The item's ID. (number, required) - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) @@ -980,7 +980,7 @@ The following sets of tools are available: - **list_project_items** - List project items - **Required OAuth Scopes**: `read:project` - - **Accepted OAuth Scopes**: `read:project`, `project` + - **Accepted OAuth Scopes**: `project`, `read:project` - `after`: Forward pagination cursor from previous pageInfo.nextCursor. (string, optional) - `before`: Backward pagination cursor from previous pageInfo.prevCursor (rare). (string, optional) - `fields`: Field IDs to include (e.g. ["102589", "985201"]). CRITICAL: Always provide to get field values. Without this, only titles returned. (string[], optional) @@ -1265,14 +1265,14 @@ The following sets of tools are available: - **get_secret_scanning_alert** - Get secret scanning alert - **Required OAuth Scopes**: `security_events` - - **Accepted OAuth Scopes**: `security_events`, `repo` + - **Accepted OAuth Scopes**: `repo`, `security_events` - `alertNumber`: The number of the alert. (number, required) - `owner`: The owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) - **list_secret_scanning_alerts** - List secret scanning alerts - **Required OAuth Scopes**: `security_events` - - **Accepted OAuth Scopes**: `security_events`, `repo` + - **Accepted OAuth Scopes**: `repo`, `security_events` - `owner`: The owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) - `resolution`: Filter by resolution (string, optional) @@ -1287,12 +1287,12 @@ The following sets of tools are available: - **get_global_security_advisory** - Get a global security advisory - **Required OAuth Scopes**: `security_events` - - **Accepted OAuth Scopes**: `security_events`, `repo` + - **Accepted OAuth Scopes**: `repo`, `security_events` - `ghsaId`: GitHub Security Advisory ID (format: GHSA-xxxx-xxxx-xxxx). (string, required) - **list_global_security_advisories** - List global security advisories - **Required OAuth Scopes**: `security_events` - - **Accepted OAuth Scopes**: `security_events`, `repo` + - **Accepted OAuth Scopes**: `repo`, `security_events` - `affects`: Filter advisories by affected package or version (e.g. "package1,package2@1.0.0"). (string, optional) - `cveId`: Filter by CVE ID. (string, optional) - `cwes`: Filter by Common Weakness Enumeration IDs (e.g. ["79", "284", "22"]). (string[], optional) @@ -1315,7 +1315,7 @@ The following sets of tools are available: - **list_repository_security_advisories** - List repository security advisories - **Required OAuth Scopes**: `security_events` - - **Accepted OAuth Scopes**: `security_events`, `repo` + - **Accepted OAuth Scopes**: `repo`, `security_events` - `direction`: Sort direction. (string, optional) - `owner`: The owner of the repository. (string, required) - `repo`: The name of the repository. (string, required) diff --git a/pkg/scopes/scopes.go b/pkg/scopes/scopes.go index 961cf0e1c..0be6ca32b 100644 --- a/pkg/scopes/scopes.go +++ b/pkg/scopes/scopes.go @@ -1,5 +1,7 @@ package scopes +import "sort" + // Scope represents a GitHub OAuth scope. // These constants define all OAuth scopes used by the GitHub MCP server tools. // See https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps @@ -85,15 +87,21 @@ func (s ScopeSet) ToSlice() []Scope { for scope := range s { scopes = append(scopes, scope) } + // Sort for deterministic output + sort.Slice(scopes, func(i, j int) bool { + return scopes[i] < scopes[j] + }) return scopes } // ToStringSlice converts a ScopeSet to a slice of string values. +// The returned slice is sorted for deterministic output. func (s ScopeSet) ToStringSlice() []string { scopes := make([]string, 0, len(s)) for scope := range s { scopes = append(scopes, string(scope)) } + sort.Strings(scopes) return scopes } @@ -110,6 +118,7 @@ func ToStringSlice(scopes ...Scope) []string { // including parent scopes from the hierarchy. // For example, if "public_repo" is required, "repo" is also accepted since // having the "repo" scope grants access to "public_repo". +// The returned slice is sorted for deterministic output. func ExpandScopes(required ...Scope) []string { if len(required) == 0 { return nil @@ -131,10 +140,11 @@ func ExpandScopes(required ...Scope) []string { } } - // Convert to slice + // Convert to slice and sort for deterministic output result := make([]string, 0, len(accepted)) for scope := range accepted { result = append(result, scope) } + sort.Strings(result) return result } From 46b8cb63accb763c1cff7e264a1e6d1cadf34b70 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 5 Jan 2026 13:55:28 +0100 Subject: [PATCH 027/138] Add PAT scope filtering for stdio server Add the ability to filter tools based on token scopes for PAT users. This uses an HTTP HEAD request to GitHub's API to discover token scopes. New components: - pkg/scopes/filter.go: HasRequiredScopes checks if scopes satisfy tool requirements - pkg/scopes/fetcher.go: FetchTokenScopes gets scopes via HTTP HEAD to GitHub API - pkg/github/scope_filter.go: CreateScopeFilter creates inventory.ToolFilter Integration: - Add --filter-by-scope flag to stdio command (disabled by default) - When enabled, fetches token scopes on startup - Tools requiring unavailable scopes are hidden from tool list - Gracefully continues without filtering if scope fetch fails (logs warning) This allows the OSS server to have similar scope-based tool visibility as the remote server, and the filter logic can be reused by remote server. --- cmd/github-mcp-server/main.go | 3 + internal/ghmcp/server.go | 53 +++++++- pkg/github/scope_filter.go | 36 ++++++ pkg/github/scope_filter_test.go | 162 ++++++++++++++++++++++++ pkg/scopes/fetcher.go | 125 +++++++++++++++++++ pkg/scopes/fetcher_test.go | 214 ++++++++++++++++++++++++++++++++ pkg/scopes/filter.go | 9 ++ pkg/scopes/scopes.go | 44 +++++++ pkg/scopes/scopes_test.go | 180 +++++++++++++++++++++++++++ 9 files changed, 822 insertions(+), 4 deletions(-) create mode 100644 pkg/github/scope_filter.go create mode 100644 pkg/github/scope_filter_test.go create mode 100644 pkg/scopes/fetcher.go create mode 100644 pkg/scopes/fetcher_test.go create mode 100644 pkg/scopes/filter.go diff --git a/cmd/github-mcp-server/main.go b/cmd/github-mcp-server/main.go index cfb68be4e..f5bf16a1a 100644 --- a/cmd/github-mcp-server/main.go +++ b/cmd/github-mcp-server/main.go @@ -84,6 +84,7 @@ var ( ContentWindowSize: viper.GetInt("content-window-size"), LockdownMode: viper.GetBool("lockdown-mode"), RepoAccessCacheTTL: &ttl, + EnableScopeFiltering: viper.GetBool("enable-scope-filtering"), } return ghmcp.RunStdioServer(stdioServerConfig) }, @@ -109,6 +110,7 @@ func init() { rootCmd.PersistentFlags().Int("content-window-size", 5000, "Specify the content window size") rootCmd.PersistentFlags().Bool("lockdown-mode", false, "Enable lockdown mode") rootCmd.PersistentFlags().Duration("repo-access-cache-ttl", 5*time.Minute, "Override the repo access cache TTL (e.g. 1m, 0s to disable)") + rootCmd.PersistentFlags().Bool("enable-scope-filtering", false, "Filter tools based on the token's OAuth scopes") // Bind flag to viper _ = viper.BindPFlag("toolsets", rootCmd.PersistentFlags().Lookup("toolsets")) @@ -123,6 +125,7 @@ func init() { _ = viper.BindPFlag("content-window-size", rootCmd.PersistentFlags().Lookup("content-window-size")) _ = viper.BindPFlag("lockdown-mode", rootCmd.PersistentFlags().Lookup("lockdown-mode")) _ = viper.BindPFlag("repo-access-cache-ttl", rootCmd.PersistentFlags().Lookup("repo-access-cache-ttl")) + _ = viper.BindPFlag("enable-scope-filtering", rootCmd.PersistentFlags().Lookup("enable-scope-filtering")) // Add subcommands rootCmd.AddCommand(stdioCmd) diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index 9859e2e9b..7da2f825f 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -19,6 +19,7 @@ import ( "github.com/github/github-mcp-server/pkg/lockdown" mcplog "github.com/github/github-mcp-server/pkg/log" "github.com/github/github-mcp-server/pkg/raw" + "github.com/github/github-mcp-server/pkg/scopes" "github.com/github/github-mcp-server/pkg/translations" gogithub "github.com/google/go-github/v79/github" "github.com/modelcontextprotocol/go-sdk/mcp" @@ -67,6 +68,11 @@ type MCPServerConfig struct { Logger *slog.Logger // RepoAccessTTL overrides the default TTL for repository access cache entries. RepoAccessTTL *time.Duration + + // TokenScopes contains the OAuth scopes available to the token. + // When non-nil, tools requiring scopes not in this list will be hidden. + // This is used for PAT scope filtering where we can't issue scope challenges. + TokenScopes []string } // githubClients holds all the GitHub API clients created for a server instance. @@ -211,13 +217,19 @@ func NewMCPServer(cfg MCPServerConfig) (*mcp.Server, error) { }) // Build and register the tool/resource/prompt inventory - inventory := github.NewInventory(cfg.Translator). + inventoryBuilder := github.NewInventory(cfg.Translator). WithDeprecatedAliases(github.DeprecatedToolAliases). WithReadOnly(cfg.ReadOnly). WithToolsets(enabledToolsets). WithTools(github.CleanTools(cfg.EnabledTools)). - WithFeatureChecker(createFeatureChecker(cfg.EnabledFeatures)). - Build() + WithFeatureChecker(createFeatureChecker(cfg.EnabledFeatures)) + + // Apply token scope filtering if scopes are known (for PAT filtering) + if cfg.TokenScopes != nil { + inventoryBuilder = inventoryBuilder.WithFilter(github.CreateToolScopeFilter(cfg.TokenScopes)) + } + + inventory := inventoryBuilder.Build() if unrecognized := inventory.UnrecognizedToolsets(); len(unrecognized) > 0 { fmt.Fprintf(os.Stderr, "Warning: unrecognized toolsets ignored: %s\n", strings.Join(unrecognized, ", ")) @@ -312,6 +324,11 @@ type StdioServerConfig struct { // RepoAccessCacheTTL overrides the default TTL for repository access cache entries. RepoAccessCacheTTL *time.Duration + + // EnableScopeFiltering enables PAT scope-based tool filtering. + // When true, the server will fetch the token's OAuth scopes at startup + // and hide tools that require scopes the token doesn't have. + EnableScopeFiltering bool } // RunStdioServer is not concurrent safe. @@ -336,7 +353,19 @@ func RunStdioServer(cfg StdioServerConfig) error { slogHandler = slog.NewTextHandler(logOutput, &slog.HandlerOptions{Level: slog.LevelInfo}) } logger := slog.New(slogHandler) - logger.Info("starting server", "version", cfg.Version, "host", cfg.Host, "dynamicToolsets", cfg.DynamicToolsets, "readOnly", cfg.ReadOnly, "lockdownEnabled", cfg.LockdownMode) + logger.Info("starting server", "version", cfg.Version, "host", cfg.Host, "dynamicToolsets", cfg.DynamicToolsets, "readOnly", cfg.ReadOnly, "lockdownEnabled", cfg.LockdownMode, "scopeFiltering", cfg.EnableScopeFiltering) + + // Fetch token scopes if scope filtering is enabled + var tokenScopes []string + if cfg.EnableScopeFiltering { + fetchedScopes, err := fetchTokenScopesForHost(ctx, cfg.Token, cfg.Host) + if err != nil { + logger.Warn("failed to fetch token scopes, continuing without scope filtering", "error", err) + } else { + tokenScopes = fetchedScopes + logger.Info("token scopes fetched for filtering", "scopes", tokenScopes) + } + } ghServer, err := NewMCPServer(MCPServerConfig{ Version: cfg.Version, @@ -352,6 +381,7 @@ func RunStdioServer(cfg StdioServerConfig) error { LockdownMode: cfg.LockdownMode, Logger: logger, RepoAccessTTL: cfg.RepoAccessCacheTTL, + TokenScopes: tokenScopes, }) if err != nil { return fmt.Errorf("failed to create MCP server: %w", err) @@ -636,3 +666,18 @@ func addUserAgentsMiddleware(cfg MCPServerConfig, restClient *gogithub.Client, g } } } + +// fetchTokenScopesForHost fetches the OAuth scopes for a token from the GitHub API. +// It constructs the appropriate API host URL based on the configured host. +func fetchTokenScopesForHost(ctx context.Context, token, host string) ([]string, error) { + apiHost, err := parseAPIHost(host) + if err != nil { + return nil, fmt.Errorf("failed to parse API host: %w", err) + } + + fetcher := scopes.NewFetcher(scopes.FetcherOptions{ + APIHost: apiHost.baseRESTURL.String(), + }) + + return fetcher.FetchTokenScopes(ctx, token) +} diff --git a/pkg/github/scope_filter.go b/pkg/github/scope_filter.go new file mode 100644 index 000000000..b1aa77c85 --- /dev/null +++ b/pkg/github/scope_filter.go @@ -0,0 +1,36 @@ +package github + +import ( + "context" + + "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/scopes" +) + +// CreateToolScopeFilter creates an inventory.ToolFilter that filters tools +// based on the token's OAuth scopes. +// +// For PATs (Personal Access Tokens), we cannot issue OAuth scope challenges +// like we can with OAuth apps. Instead, we hide tools that require scopes +// the token doesn't have. +// +// This is the recommended way to filter tools for stdio servers where the +// token is known at startup and won't change during the session. +// +// The filter returns true (include tool) if: +// - The tool has no scope requirements (AcceptedScopes is empty) +// - The token has at least one of the tool's accepted scopes +// +// Example usage: +// +// tokenScopes, err := scopes.FetchTokenScopes(ctx, token) +// if err != nil { +// // Handle error - maybe skip filtering +// } +// filter := github.CreateToolScopeFilter(tokenScopes) +// inventory := github.NewInventory(t).WithFilter(filter).Build() +func CreateToolScopeFilter(tokenScopes []string) inventory.ToolFilter { + return func(_ context.Context, tool *inventory.ServerTool) (bool, error) { + return scopes.HasRequiredScopes(tokenScopes, tool.AcceptedScopes), nil + } +} diff --git a/pkg/github/scope_filter_test.go b/pkg/github/scope_filter_test.go new file mode 100644 index 000000000..48eb52aa0 --- /dev/null +++ b/pkg/github/scope_filter_test.go @@ -0,0 +1,162 @@ +package github + +import ( + "context" + "testing" + + "github.com/github/github-mcp-server/pkg/inventory" + "github.com/modelcontextprotocol/go-sdk/mcp" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestCreateToolScopeFilter(t *testing.T) { + // Create test tools with various scope requirements + toolNoScopes := &inventory.ServerTool{ + Tool: mcp.Tool{Name: "no_scopes_tool"}, + AcceptedScopes: nil, + } + + toolEmptyScopes := &inventory.ServerTool{ + Tool: mcp.Tool{Name: "empty_scopes_tool"}, + AcceptedScopes: []string{}, + } + + toolRepoScope := &inventory.ServerTool{ + Tool: mcp.Tool{Name: "repo_tool"}, + AcceptedScopes: []string{"repo"}, + } + + toolPublicRepoScope := &inventory.ServerTool{ + Tool: mcp.Tool{Name: "public_repo_tool"}, + AcceptedScopes: []string{"public_repo", "repo"}, // repo is parent, also accepted + } + + toolGistScope := &inventory.ServerTool{ + Tool: mcp.Tool{Name: "gist_tool"}, + AcceptedScopes: []string{"gist"}, + } + + toolMultiScope := &inventory.ServerTool{ + Tool: mcp.Tool{Name: "multi_scope_tool"}, + AcceptedScopes: []string{"repo", "admin:org"}, + } + + tests := []struct { + name string + tokenScopes []string + tool *inventory.ServerTool + expected bool + }{ + { + name: "tool with no scopes is always visible", + tokenScopes: []string{}, + tool: toolNoScopes, + expected: true, + }, + { + name: "tool with empty scopes is always visible", + tokenScopes: []string{"repo"}, + tool: toolEmptyScopes, + expected: true, + }, + { + name: "token with exact scope can see tool", + tokenScopes: []string{"repo"}, + tool: toolRepoScope, + expected: true, + }, + { + name: "token with parent scope can see child-scoped tool", + tokenScopes: []string{"repo"}, + tool: toolPublicRepoScope, + expected: true, + }, + { + name: "token missing required scope cannot see tool", + tokenScopes: []string{"gist"}, + tool: toolRepoScope, + expected: false, + }, + { + name: "token with unrelated scope cannot see tool", + tokenScopes: []string{"repo"}, + tool: toolGistScope, + expected: false, + }, + { + name: "token with one of multiple accepted scopes can see tool", + tokenScopes: []string{"admin:org"}, + tool: toolMultiScope, + expected: true, + }, + { + name: "empty token scopes cannot see scoped tools", + tokenScopes: []string{}, + tool: toolRepoScope, + expected: false, + }, + { + name: "token with multiple scopes where one matches", + tokenScopes: []string{"gist", "repo"}, + tool: toolPublicRepoScope, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + filter := CreateToolScopeFilter(tt.tokenScopes) + result, err := filter(context.Background(), tt.tool) + + require.NoError(t, err) + assert.Equal(t, tt.expected, result, "filter result should match expected") + }) + } +} + +func TestCreateToolScopeFilter_Integration(t *testing.T) { + // Test integration with inventory builder + tools := []inventory.ServerTool{ + { + Tool: mcp.Tool{Name: "public_tool"}, + Toolset: inventory.ToolsetMetadata{ID: "test"}, + AcceptedScopes: nil, // No scopes required + }, + { + Tool: mcp.Tool{Name: "repo_tool"}, + Toolset: inventory.ToolsetMetadata{ID: "test"}, + AcceptedScopes: []string{"repo"}, + }, + { + Tool: mcp.Tool{Name: "gist_tool"}, + Toolset: inventory.ToolsetMetadata{ID: "test"}, + AcceptedScopes: []string{"gist"}, + }, + } + + // Create filter for token with only "repo" scope + filter := CreateToolScopeFilter([]string{"repo"}) + + // Build inventory with the filter + inv := inventory.NewBuilder(). + SetTools(tools). + WithToolsets([]string{"test"}). + WithFilter(filter). + Build() + + // Get available tools + availableTools := inv.AvailableTools(context.Background()) + + // Should see public_tool and repo_tool, but not gist_tool + assert.Len(t, availableTools, 2) + + toolNames := make([]string, len(availableTools)) + for i, tool := range availableTools { + toolNames[i] = tool.Tool.Name + } + + assert.Contains(t, toolNames, "public_tool") + assert.Contains(t, toolNames, "repo_tool") + assert.NotContains(t, toolNames, "gist_tool") +} diff --git a/pkg/scopes/fetcher.go b/pkg/scopes/fetcher.go new file mode 100644 index 000000000..48e000179 --- /dev/null +++ b/pkg/scopes/fetcher.go @@ -0,0 +1,125 @@ +package scopes + +import ( + "context" + "fmt" + "net/http" + "net/url" + "strings" + "time" +) + +// OAuthScopesHeader is the HTTP response header containing the token's OAuth scopes. +const OAuthScopesHeader = "X-OAuth-Scopes" + +// DefaultFetchTimeout is the default timeout for scope fetching requests. +const DefaultFetchTimeout = 10 * time.Second + +// FetcherOptions configures the scope fetcher. +type FetcherOptions struct { + // HTTPClient is the HTTP client to use for requests. + // If nil, a default client with DefaultFetchTimeout is used. + HTTPClient *http.Client + + // APIHost is the GitHub API host (e.g., "https://api.github.com"). + // Defaults to "https://api.github.com" if empty. + APIHost string +} + +// Fetcher retrieves token scopes from GitHub's API. +// It uses an HTTP HEAD request to minimize bandwidth since we only need headers. +type Fetcher struct { + client *http.Client + apiHost string +} + +// NewFetcher creates a new scope fetcher with the given options. +func NewFetcher(opts FetcherOptions) *Fetcher { + client := opts.HTTPClient + if client == nil { + client = &http.Client{Timeout: DefaultFetchTimeout} + } + + apiHost := opts.APIHost + if apiHost == "" { + apiHost = "https://api.github.com" + } + + return &Fetcher{ + client: client, + apiHost: apiHost, + } +} + +// FetchTokenScopes retrieves the OAuth scopes for a token by making an HTTP HEAD +// request to the GitHub API and parsing the X-OAuth-Scopes header. +// +// Returns: +// - []string: List of scopes (empty if no scopes or fine-grained PAT) +// - error: Any HTTP or parsing error +// +// Note: Fine-grained PATs don't return the X-OAuth-Scopes header, so an empty +// slice is returned for those tokens. +func (f *Fetcher) FetchTokenScopes(ctx context.Context, token string) ([]string, error) { + // Use a lightweight endpoint that requires authentication + endpoint, err := url.JoinPath(f.apiHost, "/") + if err != nil { + return nil, fmt.Errorf("failed to construct API URL: %w", err) + } + + req, err := http.NewRequestWithContext(ctx, http.MethodHead, endpoint, nil) + if err != nil { + return nil, fmt.Errorf("failed to create request: %w", err) + } + + req.Header.Set("Authorization", "Bearer "+token) + req.Header.Set("Accept", "application/vnd.github+json") + req.Header.Set("X-GitHub-Api-Version", "2022-11-28") + + resp, err := f.client.Do(req) + if err != nil { + return nil, fmt.Errorf("failed to fetch scopes: %w", err) + } + defer resp.Body.Close() + + if resp.StatusCode == http.StatusUnauthorized { + return nil, fmt.Errorf("invalid or expired token") + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("unexpected status code: %d", resp.StatusCode) + } + + return ParseScopeHeader(resp.Header.Get(OAuthScopesHeader)), nil +} + +// ParseScopeHeader parses the X-OAuth-Scopes header value into a list of scopes. +// The header contains comma-separated scope names. +// Returns an empty slice for empty or missing header. +func ParseScopeHeader(header string) []string { + if header == "" { + return []string{} + } + + parts := strings.Split(header, ",") + scopes := make([]string, 0, len(parts)) + for _, part := range parts { + scope := strings.TrimSpace(part) + if scope != "" { + scopes = append(scopes, scope) + } + } + return scopes +} + +// FetchTokenScopes is a convenience function that creates a default fetcher +// and fetches the token scopes. +func FetchTokenScopes(ctx context.Context, token string) ([]string, error) { + return NewFetcher(FetcherOptions{}).FetchTokenScopes(ctx, token) +} + +// FetchTokenScopesWithHost is a convenience function that creates a fetcher +// for a specific API host and fetches the token scopes. +func FetchTokenScopesWithHost(ctx context.Context, token, apiHost string) ([]string, error) { + return NewFetcher(FetcherOptions{APIHost: apiHost}).FetchTokenScopes(ctx, token) +} diff --git a/pkg/scopes/fetcher_test.go b/pkg/scopes/fetcher_test.go new file mode 100644 index 000000000..13feab5b0 --- /dev/null +++ b/pkg/scopes/fetcher_test.go @@ -0,0 +1,214 @@ +package scopes + +import ( + "context" + "net/http" + "net/http/httptest" + "testing" + "time" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestParseScopeHeader(t *testing.T) { + tests := []struct { + name string + header string + expected []string + }{ + { + name: "empty header", + header: "", + expected: []string{}, + }, + { + name: "single scope", + header: "repo", + expected: []string{"repo"}, + }, + { + name: "multiple scopes", + header: "repo, user, gist", + expected: []string{"repo", "user", "gist"}, + }, + { + name: "scopes with extra whitespace", + header: " repo , user , gist ", + expected: []string{"repo", "user", "gist"}, + }, + { + name: "scopes without spaces", + header: "repo,user,gist", + expected: []string{"repo", "user", "gist"}, + }, + { + name: "scopes with colons", + header: "read:org, write:org, admin:org", + expected: []string{"read:org", "write:org", "admin:org"}, + }, + { + name: "empty parts are filtered", + header: "repo,,gist", + expected: []string{"repo", "gist"}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := ParseScopeHeader(tt.header) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestFetcher_FetchTokenScopes(t *testing.T) { + tests := []struct { + name string + handler http.HandlerFunc + expectedScopes []string + expectError bool + errorContains string + }{ + { + name: "successful fetch with multiple scopes", + handler: func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("X-OAuth-Scopes", "repo, user, gist") + w.WriteHeader(http.StatusOK) + }, + expectedScopes: []string{"repo", "user", "gist"}, + expectError: false, + }, + { + name: "successful fetch with single scope", + handler: func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("X-OAuth-Scopes", "repo") + w.WriteHeader(http.StatusOK) + }, + expectedScopes: []string{"repo"}, + expectError: false, + }, + { + name: "fine-grained PAT returns empty scopes", + handler: func(w http.ResponseWriter, _ *http.Request) { + // Fine-grained PATs don't return X-OAuth-Scopes + w.WriteHeader(http.StatusOK) + }, + expectedScopes: []string{}, + expectError: false, + }, + { + name: "unauthorized token", + handler: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + }, + expectError: true, + errorContains: "invalid or expired token", + }, + { + name: "server error", + handler: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusInternalServerError) + }, + expectError: true, + errorContains: "unexpected status code: 500", + }, + { + name: "verifies authorization header is set", + handler: func(w http.ResponseWriter, r *http.Request) { + authHeader := r.Header.Get("Authorization") + if authHeader != "Bearer test-token" { + w.WriteHeader(http.StatusUnauthorized) + return + } + w.Header().Set("X-OAuth-Scopes", "repo") + w.WriteHeader(http.StatusOK) + }, + expectedScopes: []string{"repo"}, + expectError: false, + }, + { + name: "verifies request method is HEAD", + handler: func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodHead { + w.WriteHeader(http.StatusMethodNotAllowed) + return + } + w.Header().Set("X-OAuth-Scopes", "repo") + w.WriteHeader(http.StatusOK) + }, + expectedScopes: []string{"repo"}, + expectError: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := httptest.NewServer(tt.handler) + defer server.Close() + + fetcher := NewFetcher(FetcherOptions{ + APIHost: server.URL, + }) + + scopes, err := fetcher.FetchTokenScopes(context.Background(), "test-token") + + if tt.expectError { + require.Error(t, err) + if tt.errorContains != "" { + assert.Contains(t, err.Error(), tt.errorContains) + } + } else { + require.NoError(t, err) + assert.Equal(t, tt.expectedScopes, scopes) + } + }) + } +} + +func TestFetcher_DefaultOptions(t *testing.T) { + fetcher := NewFetcher(FetcherOptions{}) + + // Verify default API host is set + assert.Equal(t, "https://api.github.com", fetcher.apiHost) + + // Verify default HTTP client is set with timeout + assert.NotNil(t, fetcher.client) + assert.Equal(t, DefaultFetchTimeout, fetcher.client.Timeout) +} + +func TestFetcher_CustomHTTPClient(t *testing.T) { + customClient := &http.Client{Timeout: 5 * time.Second} + + fetcher := NewFetcher(FetcherOptions{ + HTTPClient: customClient, + }) + + assert.Equal(t, customClient, fetcher.client) +} + +func TestFetcher_CustomAPIHost(t *testing.T) { + fetcher := NewFetcher(FetcherOptions{ + APIHost: "https://api.github.enterprise.com", + }) + + assert.Equal(t, "https://api.github.enterprise.com", fetcher.apiHost) +} + +func TestFetcher_ContextCancellation(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + time.Sleep(100 * time.Millisecond) + w.WriteHeader(http.StatusOK) + })) + defer server.Close() + + fetcher := NewFetcher(FetcherOptions{ + APIHost: server.URL, + }) + + ctx, cancel := context.WithCancel(context.Background()) + cancel() // Cancel immediately + + _, err := fetcher.FetchTokenScopes(ctx, "test-token") + require.Error(t, err) +} diff --git a/pkg/scopes/filter.go b/pkg/scopes/filter.go new file mode 100644 index 000000000..143b736e2 --- /dev/null +++ b/pkg/scopes/filter.go @@ -0,0 +1,9 @@ +// Package scopes provides OAuth scope checking utilities for GitHub MCP Server. +// +// This file contains utilities for filtering tools based on token scopes. +// For PATs, we cannot issue OAuth scope challenges, so we hide tools that +// require scopes the token doesn't have. +// +// The CreateToolScopeFilter function should be called from the github package +// or other packages that can import inventory to create the actual filter. +package scopes diff --git a/pkg/scopes/scopes.go b/pkg/scopes/scopes.go index 0be6ca32b..a9b06e988 100644 --- a/pkg/scopes/scopes.go +++ b/pkg/scopes/scopes.go @@ -148,3 +148,47 @@ func ExpandScopes(required ...Scope) []string { sort.Strings(result) return result } + +// expandScopeSet returns a set of all scopes granted by the given scopes, +// including child scopes from the hierarchy. +// For example, if "repo" is provided, the result includes "repo", "public_repo", +// and "security_events" since "repo" grants access to those child scopes. +func expandScopeSet(scopes []string) map[string]bool { + expanded := make(map[string]bool, len(scopes)) + for _, scope := range scopes { + expanded[scope] = true + // Add child scopes granted by this scope + if children, ok := ScopeHierarchy[Scope(scope)]; ok { + for _, child := range children { + expanded[string(child)] = true + } + } + } + return expanded +} + +// HasRequiredScopes checks if tokenScopes satisfy the acceptedScopes requirement. +// A tool's acceptedScopes includes both the required scopes AND parent scopes +// that implicitly grant the required permissions (via ExpandScopes). +// +// For PAT filtering: if ANY of the acceptedScopes are granted by the token +// (directly or via scope hierarchy), the tool should be visible. +// +// Returns true if the tool should be visible to the token holder. +func HasRequiredScopes(tokenScopes []string, acceptedScopes []string) bool { + // No scopes required = always allowed + if len(acceptedScopes) == 0 { + return true + } + + // Expand token scopes to include child scopes they grant + grantedScopes := expandScopeSet(tokenScopes) + + // Check if any accepted scope is granted by the token + for _, accepted := range acceptedScopes { + if grantedScopes[accepted] { + return true + } + } + return false +} diff --git a/pkg/scopes/scopes_test.go b/pkg/scopes/scopes_test.go index 8ef29a115..b8e0d8e42 100644 --- a/pkg/scopes/scopes_test.go +++ b/pkg/scopes/scopes_test.go @@ -150,3 +150,183 @@ func TestScopeHierarchy(t *testing.T) { assert.Contains(t, ScopeHierarchy[User], ReadUser) assert.Contains(t, ScopeHierarchy[User], UserEmail) } + +func TestExpandScopeSet(t *testing.T) { + tests := []struct { + name string + scopes []string + expected map[string]bool + }{ + { + name: "empty scopes", + scopes: []string{}, + expected: map[string]bool{}, + }, + { + name: "repo expands to include public_repo and security_events", + scopes: []string{"repo"}, + expected: map[string]bool{ + "repo": true, + "public_repo": true, + "security_events": true, + }, + }, + { + name: "admin:org expands to include write:org and read:org", + scopes: []string{"admin:org"}, + expected: map[string]bool{ + "admin:org": true, + "write:org": true, + "read:org": true, + }, + }, + { + name: "write:org expands to include read:org", + scopes: []string{"write:org"}, + expected: map[string]bool{ + "write:org": true, + "read:org": true, + }, + }, + { + name: "user expands to include read:user and user:email", + scopes: []string{"user"}, + expected: map[string]bool{ + "user": true, + "read:user": true, + "user:email": true, + }, + }, + { + name: "scope without children stays as-is", + scopes: []string{"gist"}, + expected: map[string]bool{ + "gist": true, + }, + }, + { + name: "multiple scopes combine correctly", + scopes: []string{"repo", "gist"}, + expected: map[string]bool{ + "repo": true, + "public_repo": true, + "security_events": true, + "gist": true, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := expandScopeSet(tt.scopes) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestHasRequiredScopes(t *testing.T) { + tests := []struct { + name string + tokenScopes []string + acceptedScopes []string + expected bool + }{ + { + name: "no accepted scopes - always allowed", + tokenScopes: []string{}, + acceptedScopes: []string{}, + expected: true, + }, + { + name: "nil accepted scopes - always allowed", + tokenScopes: []string{"repo"}, + acceptedScopes: nil, + expected: true, + }, + { + name: "token has exact required scope", + tokenScopes: []string{"repo"}, + acceptedScopes: []string{"repo"}, + expected: true, + }, + { + name: "token has parent scope that grants access", + tokenScopes: []string{"repo"}, + acceptedScopes: []string{"public_repo"}, + expected: true, + }, + { + name: "token has parent scope for security_events", + tokenScopes: []string{"repo"}, + acceptedScopes: []string{"security_events"}, + expected: true, + }, + { + name: "token has admin:org which grants read:org", + tokenScopes: []string{"admin:org"}, + acceptedScopes: []string{"read:org"}, + expected: true, + }, + { + name: "token has write:org which grants read:org", + tokenScopes: []string{"write:org"}, + acceptedScopes: []string{"read:org"}, + expected: true, + }, + { + name: "token missing required scope", + tokenScopes: []string{"gist"}, + acceptedScopes: []string{"repo"}, + expected: false, + }, + { + name: "token has child but not parent - fails", + tokenScopes: []string{"public_repo"}, + acceptedScopes: []string{"repo"}, + expected: false, + }, + { + name: "multiple token scopes - one matches", + tokenScopes: []string{"gist", "repo"}, + acceptedScopes: []string{"public_repo"}, + expected: true, + }, + { + name: "multiple accepted scopes - token has one", + tokenScopes: []string{"repo"}, + acceptedScopes: []string{"repo", "admin:org"}, + expected: true, + }, + { + name: "empty token scopes - fails when scopes required", + tokenScopes: []string{}, + acceptedScopes: []string{"repo"}, + expected: false, + }, + { + name: "user scope grants read:user", + tokenScopes: []string{"user"}, + acceptedScopes: []string{"read:user"}, + expected: true, + }, + { + name: "user scope grants user:email", + tokenScopes: []string{"user"}, + acceptedScopes: []string{"user:email"}, + expected: true, + }, + { + name: "write:packages grants read:packages", + tokenScopes: []string{"write:packages"}, + acceptedScopes: []string{"read:packages"}, + expected: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := HasRequiredScopes(tt.tokenScopes, tt.acceptedScopes) + assert.Equal(t, tt.expected, result) + }) + } +} From a19a159d0a872098b3b985fd3a9df096d2d818fe Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 5 Jan 2026 14:02:31 +0100 Subject: [PATCH 028/138] Enable scope filtering by default --- cmd/github-mcp-server/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/github-mcp-server/main.go b/cmd/github-mcp-server/main.go index f5bf16a1a..528dd976a 100644 --- a/cmd/github-mcp-server/main.go +++ b/cmd/github-mcp-server/main.go @@ -110,7 +110,7 @@ func init() { rootCmd.PersistentFlags().Int("content-window-size", 5000, "Specify the content window size") rootCmd.PersistentFlags().Bool("lockdown-mode", false, "Enable lockdown mode") rootCmd.PersistentFlags().Duration("repo-access-cache-ttl", 5*time.Minute, "Override the repo access cache TTL (e.g. 1m, 0s to disable)") - rootCmd.PersistentFlags().Bool("enable-scope-filtering", false, "Filter tools based on the token's OAuth scopes") + rootCmd.PersistentFlags().Bool("enable-scope-filtering", true, "Filter tools based on the token's OAuth scopes") // Bind flag to viper _ = viper.BindPFlag("toolsets", rootCmd.PersistentFlags().Lookup("toolsets")) From f45b94a8f5bd5dcd6b9414f3caf5cf2b54f9c36d Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 5 Jan 2026 14:10:35 +0100 Subject: [PATCH 029/138] Make scope filtering always enabled (remove flag) Scope filtering is now a built-in feature rather than a configurable option. The server automatically fetches token scopes at startup and filters tools accordingly. If scope detection fails, it logs a warning and continues with all tools available. --- cmd/github-mcp-server/main.go | 3 -- docs/scope-filtering.md | 89 +++++++++++++++++++++++++++++++++++ docs/server-configuration.md | 11 +++++ internal/ghmcp/server.go | 23 ++++----- 4 files changed, 108 insertions(+), 18 deletions(-) create mode 100644 docs/scope-filtering.md diff --git a/cmd/github-mcp-server/main.go b/cmd/github-mcp-server/main.go index 528dd976a..cfb68be4e 100644 --- a/cmd/github-mcp-server/main.go +++ b/cmd/github-mcp-server/main.go @@ -84,7 +84,6 @@ var ( ContentWindowSize: viper.GetInt("content-window-size"), LockdownMode: viper.GetBool("lockdown-mode"), RepoAccessCacheTTL: &ttl, - EnableScopeFiltering: viper.GetBool("enable-scope-filtering"), } return ghmcp.RunStdioServer(stdioServerConfig) }, @@ -110,7 +109,6 @@ func init() { rootCmd.PersistentFlags().Int("content-window-size", 5000, "Specify the content window size") rootCmd.PersistentFlags().Bool("lockdown-mode", false, "Enable lockdown mode") rootCmd.PersistentFlags().Duration("repo-access-cache-ttl", 5*time.Minute, "Override the repo access cache TTL (e.g. 1m, 0s to disable)") - rootCmd.PersistentFlags().Bool("enable-scope-filtering", true, "Filter tools based on the token's OAuth scopes") // Bind flag to viper _ = viper.BindPFlag("toolsets", rootCmd.PersistentFlags().Lookup("toolsets")) @@ -125,7 +123,6 @@ func init() { _ = viper.BindPFlag("content-window-size", rootCmd.PersistentFlags().Lookup("content-window-size")) _ = viper.BindPFlag("lockdown-mode", rootCmd.PersistentFlags().Lookup("lockdown-mode")) _ = viper.BindPFlag("repo-access-cache-ttl", rootCmd.PersistentFlags().Lookup("repo-access-cache-ttl")) - _ = viper.BindPFlag("enable-scope-filtering", rootCmd.PersistentFlags().Lookup("enable-scope-filtering")) // Add subcommands rootCmd.AddCommand(stdioCmd) diff --git a/docs/scope-filtering.md b/docs/scope-filtering.md new file mode 100644 index 000000000..888a0e1f8 --- /dev/null +++ b/docs/scope-filtering.md @@ -0,0 +1,89 @@ +# OAuth Scope Filtering + +The GitHub MCP Server automatically filters available tools based on your Personal Access Token's (PAT) OAuth scopes. This ensures you only see tools that your token has permission to use, reducing clutter and preventing errors from attempting operations your token can't perform. + +## How It Works + +When the server starts, it makes a lightweight HTTP HEAD request to the GitHub API to discover your token's scopes from the `X-OAuth-Scopes` header. Tools that require scopes your token doesn't have are automatically hidden. + +**Example:** If your token only has `repo` and `gist` scopes, you won't see tools that require `admin:org`, `project`, or `notifications` scopes. + +## Checking Your Token's Scopes + +To see what scopes your token has, you can run: + +```bash +curl -sI -H "Authorization: Bearer $GITHUB_PERSONAL_ACCESS_TOKEN" \ + https://api.github.com/user | grep -i x-oauth-scopes +``` + +Example output: +``` +x-oauth-scopes: delete_repo, gist, read:org, repo +``` + +## Scopes and Tools + +The following table shows which OAuth scopes are required for each category of tools: + +| Scope | Tools Enabled | +|-------|---------------| +| `repo` | Repository operations, issues, PRs, commits, branches, code search, workflows | +| `public_repo` | Star/unstar public repositories (implicit with `repo`) | +| `read:org` | Read organization info, list teams, team members | +| `write:org` | Organization management (includes `read:org`) | +| `admin:org` | Full organization administration (includes `write:org`, `read:org`) | +| `gist` | Create, update, and manage gists | +| `notifications` | List, manage, and dismiss notifications | +| `read:project` | Read GitHub Projects | +| `project` | Create and manage GitHub Projects (includes `read:project`) | +| `security_events` | Code scanning, Dependabot, secret scanning alerts (implicit with `repo`) | +| `user` | Update user profile | +| `read:user` | Read user profile information | + +### Scope Hierarchy + +Some scopes implicitly include others: + +- `repo` → includes `public_repo`, `security_events` +- `admin:org` → includes `write:org` → includes `read:org` +- `project` → includes `read:project` + +This means if your token has `repo`, tools requiring `security_events` will also be available. + +## Recommended Token Scopes + +For full functionality, we recommend these scopes: + +| Use Case | Recommended Scopes | +|----------|-------------------| +| Basic development | `repo`, `read:org` | +| Full development | `repo`, `admin:org`, `gist`, `notifications`, `project` | +| Read-only access | `repo` (with `--read-only` flag) | +| Security analysis | `repo` (includes `security_events`) | + +## Graceful Degradation + +If the server cannot fetch your token's scopes (e.g., network issues, rate limiting), it logs a warning and continues **without filtering**. This ensures the server remains usable even when scope detection fails. + +``` +WARN: failed to fetch token scopes, continuing without scope filtering +``` + +## Fine-Grained Personal Access Tokens + +Fine-grained PATs use a different permission model and don't return OAuth scopes in the `X-OAuth-Scopes` header. When using fine-grained PATs, scope filtering will be skipped and all tools will be available. The GitHub API will still enforce permissions at the API level. + +## Troubleshooting + +| Problem | Cause | Solution | +|---------|-------|----------| +| Missing expected tools | Token lacks required scope | Add the scope to your PAT | +| All tools visible despite limited PAT | Scope detection failed | Check logs for warnings about scope fetching | +| "Insufficient permissions" errors | Tool visible but scope insufficient | This shouldn't happen with scope filtering; report as bug | + +## Related Documentation + +- [Server Configuration Guide](./server-configuration.md) +- [GitHub PAT Documentation](https://docs.github.com/en/authentication/keeping-your-account-and-data-secure/managing-your-personal-access-tokens) +- [OAuth Scopes Reference](https://docs.github.com/en/apps/oauth-apps/building-oauth-apps/scopes-for-oauth-apps) diff --git a/docs/server-configuration.md b/docs/server-configuration.md index e8b7637bd..3338a9275 100644 --- a/docs/server-configuration.md +++ b/docs/server-configuration.md @@ -12,6 +12,7 @@ We currently support the following ways in which the GitHub MCP Server can be co | Read-Only Mode | `X-MCP-Readonly` header or `/readonly` URL | `--read-only` flag or `GITHUB_READ_ONLY` env var | | Dynamic Mode | Not available | `--dynamic-toolsets` flag or `GITHUB_DYNAMIC_TOOLSETS` env var | | Lockdown Mode | `X-MCP-Lockdown` header | `--lockdown-mode` flag or `GITHUB_LOCKDOWN_MODE` env var | +| Scope Filtering | Always enabled | Always enabled | > **Default behavior:** If you don't specify any configuration, the server uses the **default toolsets**: `context`, `issues`, `pull_requests`, `repos`, `users`. @@ -330,6 +331,16 @@ Lockdown mode ensures the server only surfaces content in public repositories fr --- +### Scope Filtering + +**Automatic feature:** The server automatically detects your PAT's OAuth scopes and only shows tools you have permission to use. + +This happens transparently at startup - no configuration needed. If scope detection fails (e.g., network issues), the server logs a warning and continues with all tools available. + +See [OAuth Scope Filtering](./scope-filtering.md) for details on which scopes enable which tools. + +--- + ## Troubleshooting | Problem | Cause | Solution | diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index 7da2f825f..73aaa8518 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -324,11 +324,6 @@ type StdioServerConfig struct { // RepoAccessCacheTTL overrides the default TTL for repository access cache entries. RepoAccessCacheTTL *time.Duration - - // EnableScopeFiltering enables PAT scope-based tool filtering. - // When true, the server will fetch the token's OAuth scopes at startup - // and hide tools that require scopes the token doesn't have. - EnableScopeFiltering bool } // RunStdioServer is not concurrent safe. @@ -353,18 +348,16 @@ func RunStdioServer(cfg StdioServerConfig) error { slogHandler = slog.NewTextHandler(logOutput, &slog.HandlerOptions{Level: slog.LevelInfo}) } logger := slog.New(slogHandler) - logger.Info("starting server", "version", cfg.Version, "host", cfg.Host, "dynamicToolsets", cfg.DynamicToolsets, "readOnly", cfg.ReadOnly, "lockdownEnabled", cfg.LockdownMode, "scopeFiltering", cfg.EnableScopeFiltering) + logger.Info("starting server", "version", cfg.Version, "host", cfg.Host, "dynamicToolsets", cfg.DynamicToolsets, "readOnly", cfg.ReadOnly, "lockdownEnabled", cfg.LockdownMode) - // Fetch token scopes if scope filtering is enabled + // Fetch token scopes for scope-based tool filtering var tokenScopes []string - if cfg.EnableScopeFiltering { - fetchedScopes, err := fetchTokenScopesForHost(ctx, cfg.Token, cfg.Host) - if err != nil { - logger.Warn("failed to fetch token scopes, continuing without scope filtering", "error", err) - } else { - tokenScopes = fetchedScopes - logger.Info("token scopes fetched for filtering", "scopes", tokenScopes) - } + fetchedScopes, err := fetchTokenScopesForHost(ctx, cfg.Token, cfg.Host) + if err != nil { + logger.Warn("failed to fetch token scopes, continuing without scope filtering", "error", err) + } else { + tokenScopes = fetchedScopes + logger.Info("token scopes fetched for filtering", "scopes", tokenScopes) } ghServer, err := NewMCPServer(MCPServerConfig{ From 8afb4fb16e447c3f76c95611f2fad7ae98d4dee0 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 5 Jan 2026 14:14:39 +0100 Subject: [PATCH 030/138] Only check scopes for classic PATs (ghp_ prefix) - Scope filtering only applies to classic PATs which return X-OAuth-Scopes - Fine-grained PATs and other token types skip filtering (all tools shown) - Updated docs to clarify PAT filtering vs OAuth scope challenges --- docs/scope-filtering.md | 24 +++++++++++++++++++----- internal/ghmcp/server.go | 18 ++++++++++++------ 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/docs/scope-filtering.md b/docs/scope-filtering.md index 888a0e1f8..8dabd0d79 100644 --- a/docs/scope-filtering.md +++ b/docs/scope-filtering.md @@ -1,13 +1,25 @@ -# OAuth Scope Filtering +# PAT Scope Filtering -The GitHub MCP Server automatically filters available tools based on your Personal Access Token's (PAT) OAuth scopes. This ensures you only see tools that your token has permission to use, reducing clutter and preventing errors from attempting operations your token can't perform. +The GitHub MCP Server automatically filters available tools based on your classic Personal Access Token's (PAT) OAuth scopes. This ensures you only see tools that your token has permission to use, reducing clutter and preventing errors from attempting operations your token can't perform. + +> **Note:** This feature applies to **classic PATs** (tokens starting with `ghp_`). Fine-grained PATs and other token types don't support scope detection. ## How It Works -When the server starts, it makes a lightweight HTTP HEAD request to the GitHub API to discover your token's scopes from the `X-OAuth-Scopes` header. Tools that require scopes your token doesn't have are automatically hidden. +When the server starts with a classic PAT, it makes a lightweight HTTP HEAD request to the GitHub API to discover your token's scopes from the `X-OAuth-Scopes` header. Tools that require scopes your token doesn't have are automatically hidden. **Example:** If your token only has `repo` and `gist` scopes, you won't see tools that require `admin:org`, `project`, or `notifications` scopes. +## PAT vs OAuth Authentication + +| Authentication | Scope Handling | +|---------------|----------------| +| **Classic PAT** (`ghp_`) | Filters tools at startup based on token scopes—tools requiring unavailable scopes are hidden | +| **OAuth** (remote server only) | Uses OAuth scope challenges—when a tool needs a scope you haven't granted, you're prompted to authorize it | +| **Fine-grained PAT** (`github_pat_`) | No filtering—all tools shown, API enforces permissions | + +With OAuth, the remote server can dynamically request additional scopes as needed. With PATs, scopes are fixed at token creation, so the server proactively hides tools you can't use. + ## Checking Your Token's Scopes To see what scopes your token has, you can run: @@ -70,9 +82,11 @@ If the server cannot fetch your token's scopes (e.g., network issues, rate limit WARN: failed to fetch token scopes, continuing without scope filtering ``` -## Fine-Grained Personal Access Tokens +## Classic vs Fine-Grained Personal Access Tokens + +**Classic PATs** (`ghp_` prefix) support OAuth scopes and return them in the `X-OAuth-Scopes` header. Scope filtering works fully with these tokens. -Fine-grained PATs use a different permission model and don't return OAuth scopes in the `X-OAuth-Scopes` header. When using fine-grained PATs, scope filtering will be skipped and all tools will be available. The GitHub API will still enforce permissions at the API level. +**Fine-grained PATs** (`github_pat_` prefix) use a different permission model based on repository access and specific permissions rather than OAuth scopes. They don't return the `X-OAuth-Scopes` header, so scope filtering is skipped. All tools will be available, but the GitHub API will still enforce permissions at the API level—you'll get errors if you try to use tools your token doesn't have permission for. ## Troubleshooting diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index 73aaa8518..165886606 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -350,14 +350,20 @@ func RunStdioServer(cfg StdioServerConfig) error { logger := slog.New(slogHandler) logger.Info("starting server", "version", cfg.Version, "host", cfg.Host, "dynamicToolsets", cfg.DynamicToolsets, "readOnly", cfg.ReadOnly, "lockdownEnabled", cfg.LockdownMode) - // Fetch token scopes for scope-based tool filtering + // Fetch token scopes for scope-based tool filtering (PAT tokens only) + // Only classic PATs (ghp_ prefix) return OAuth scopes via X-OAuth-Scopes header. + // Fine-grained PATs and other token types don't support this, so we skip filtering. var tokenScopes []string - fetchedScopes, err := fetchTokenScopesForHost(ctx, cfg.Token, cfg.Host) - if err != nil { - logger.Warn("failed to fetch token scopes, continuing without scope filtering", "error", err) + if strings.HasPrefix(cfg.Token, "ghp_") { + fetchedScopes, err := fetchTokenScopesForHost(ctx, cfg.Token, cfg.Host) + if err != nil { + logger.Warn("failed to fetch token scopes, continuing without scope filtering", "error", err) + } else { + tokenScopes = fetchedScopes + logger.Info("token scopes fetched for filtering", "scopes", tokenScopes) + } } else { - tokenScopes = fetchedScopes - logger.Info("token scopes fetched for filtering", "scopes", tokenScopes) + logger.Debug("skipping scope filtering for non-PAT token") } ghServer, err := NewMCPServer(MCPServerConfig{ From 39fed357f452ff70d142dcef7582b719c64a62ad Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 5 Jan 2026 14:19:12 +0100 Subject: [PATCH 031/138] Remove manual scope-to-tools table from docs The README already has auto-generated tool documentation with scopes. Keep only the scope hierarchy explanation which is structural. --- docs/scope-filtering.md | 32 ++------------------------------ 1 file changed, 2 insertions(+), 30 deletions(-) diff --git a/docs/scope-filtering.md b/docs/scope-filtering.md index 8dabd0d79..6e251fdb3 100644 --- a/docs/scope-filtering.md +++ b/docs/scope-filtering.md @@ -34,26 +34,7 @@ Example output: x-oauth-scopes: delete_repo, gist, read:org, repo ``` -## Scopes and Tools - -The following table shows which OAuth scopes are required for each category of tools: - -| Scope | Tools Enabled | -|-------|---------------| -| `repo` | Repository operations, issues, PRs, commits, branches, code search, workflows | -| `public_repo` | Star/unstar public repositories (implicit with `repo`) | -| `read:org` | Read organization info, list teams, team members | -| `write:org` | Organization management (includes `read:org`) | -| `admin:org` | Full organization administration (includes `write:org`, `read:org`) | -| `gist` | Create, update, and manage gists | -| `notifications` | List, manage, and dismiss notifications | -| `read:project` | Read GitHub Projects | -| `project` | Create and manage GitHub Projects (includes `read:project`) | -| `security_events` | Code scanning, Dependabot, secret scanning alerts (implicit with `repo`) | -| `user` | Update user profile | -| `read:user` | Read user profile information | - -### Scope Hierarchy +## Scope Hierarchy Some scopes implicitly include others: @@ -63,16 +44,7 @@ Some scopes implicitly include others: This means if your token has `repo`, tools requiring `security_events` will also be available. -## Recommended Token Scopes - -For full functionality, we recommend these scopes: - -| Use Case | Recommended Scopes | -|----------|-------------------| -| Basic development | `repo`, `read:org` | -| Full development | `repo`, `admin:org`, `gist`, `notifications`, `project` | -| Read-only access | `repo` (with `--read-only` flag) | -| Security analysis | `repo` (includes `security_events`) | +Each tool in the [README](../README.md#tools) lists its required and accepted OAuth scopes. ## Graceful Degradation From c2450ce96ac8871a7d905599561976ef141b40cf Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 5 Jan 2026 14:25:47 +0100 Subject: [PATCH 032/138] Update pkg/scopes/filter.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pkg/scopes/filter.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/pkg/scopes/filter.go b/pkg/scopes/filter.go index 143b736e2..3eb3457a5 100644 --- a/pkg/scopes/filter.go +++ b/pkg/scopes/filter.go @@ -4,6 +4,4 @@ // For PATs, we cannot issue OAuth scope challenges, so we hide tools that // require scopes the token doesn't have. // -// The CreateToolScopeFilter function should be called from the github package -// or other packages that can import inventory to create the actual filter. package scopes From acd792928170dd902da0ff1d6160ccbb194fba99 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 5 Jan 2026 14:46:46 +0100 Subject: [PATCH 033/138] Document that GitHub App and server-to-server tokens are not filtered --- docs/scope-filtering.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/scope-filtering.md b/docs/scope-filtering.md index 6e251fdb3..5e3e353d5 100644 --- a/docs/scope-filtering.md +++ b/docs/scope-filtering.md @@ -2,7 +2,7 @@ The GitHub MCP Server automatically filters available tools based on your classic Personal Access Token's (PAT) OAuth scopes. This ensures you only see tools that your token has permission to use, reducing clutter and preventing errors from attempting operations your token can't perform. -> **Note:** This feature applies to **classic PATs** (tokens starting with `ghp_`). Fine-grained PATs and other token types don't support scope detection. +> **Note:** This feature applies to **classic PATs** (tokens starting with `ghp_`). Fine-grained PATs, GitHub App installation tokens, and server-to-server tokens don't support scope detection and show all tools. ## How It Works @@ -17,6 +17,8 @@ When the server starts with a classic PAT, it makes a lightweight HTTP HEAD requ | **Classic PAT** (`ghp_`) | Filters tools at startup based on token scopes—tools requiring unavailable scopes are hidden | | **OAuth** (remote server only) | Uses OAuth scope challenges—when a tool needs a scope you haven't granted, you're prompted to authorize it | | **Fine-grained PAT** (`github_pat_`) | No filtering—all tools shown, API enforces permissions | +| **GitHub App** (`ghs_`) | No filtering—all tools shown, permissions based on app installation | +| **Server-to-server** | No filtering—all tools shown, permissions based on app/token configuration | With OAuth, the remote server can dynamically request additional scopes as needed. With PATs, scopes are fixed at token creation, so the server proactively hides tools you can't use. @@ -60,6 +62,10 @@ WARN: failed to fetch token scopes, continuing without scope filtering **Fine-grained PATs** (`github_pat_` prefix) use a different permission model based on repository access and specific permissions rather than OAuth scopes. They don't return the `X-OAuth-Scopes` header, so scope filtering is skipped. All tools will be available, but the GitHub API will still enforce permissions at the API level—you'll get errors if you try to use tools your token doesn't have permission for. +## GitHub App and Server-to-Server Tokens + +**GitHub App installation tokens** (`ghs_` prefix) and other server-to-server tokens use a permission model based on the app's installation permissions rather than OAuth scopes. These tokens don't return the `X-OAuth-Scopes` header, so scope filtering is skipped. The GitHub API enforces permissions based on the app's configuration. + ## Troubleshooting | Problem | Cause | Solution | From f14f507a39c565ffc7734773ee98cecfcb5da51a Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 5 Jan 2026 14:47:50 +0100 Subject: [PATCH 034/138] Add tip about editing PAT scopes in GitHub UI --- docs/scope-filtering.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/scope-filtering.md b/docs/scope-filtering.md index 5e3e353d5..979a4d59e 100644 --- a/docs/scope-filtering.md +++ b/docs/scope-filtering.md @@ -70,10 +70,12 @@ WARN: failed to fetch token scopes, continuing without scope filtering | Problem | Cause | Solution | |---------|-------|----------| -| Missing expected tools | Token lacks required scope | Add the scope to your PAT | +| Missing expected tools | Token lacks required scope | [Edit your PAT's scopes](https://github.com/settings/tokens) in GitHub settings | | All tools visible despite limited PAT | Scope detection failed | Check logs for warnings about scope fetching | | "Insufficient permissions" errors | Tool visible but scope insufficient | This shouldn't happen with scope filtering; report as bug | +> **Tip:** You can adjust the scopes of an existing classic PAT at any time via [GitHub's token settings](https://github.com/settings/tokens). After updating scopes, restart the MCP server to pick up the changes. + ## Related Documentation - [Server Configuration Guide](./server-configuration.md) From 4deaa8321ecaa2a6843e08a1fc49eb9f7092a149 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 5 Jan 2026 14:54:12 +0100 Subject: [PATCH 035/138] Remove empty filter.go and document OAuth scope challenges --- docs/scope-filtering.md | 12 ++++++++++++ pkg/scopes/filter.go | 7 ------- 2 files changed, 12 insertions(+), 7 deletions(-) delete mode 100644 pkg/scopes/filter.go diff --git a/docs/scope-filtering.md b/docs/scope-filtering.md index 979a4d59e..c40bf729f 100644 --- a/docs/scope-filtering.md +++ b/docs/scope-filtering.md @@ -22,6 +22,18 @@ When the server starts with a classic PAT, it makes a lightweight HTTP HEAD requ With OAuth, the remote server can dynamically request additional scopes as needed. With PATs, scopes are fixed at token creation, so the server proactively hides tools you can't use. +## OAuth Scope Challenges (Remote Server) + +When using the [remote MCP server](./remote-server.md) with OAuth authentication, the server uses a different approach called **scope challenges**. Instead of hiding tools upfront, all tools are available, and the server requests additional scopes on-demand when you try to use a tool that requires them. + +**How it works:** +1. You attempt to use a tool (e.g., creating an issue) +2. If your current OAuth token lacks the required scope, the server returns an OAuth scope challenge +3. Your MCP client prompts you to authorize the additional scope +4. After authorization, the operation completes successfully + +This provides a smoother user experience for OAuth users since you only grant permissions as needed, rather than requesting all scopes upfront. + ## Checking Your Token's Scopes To see what scopes your token has, you can run: diff --git a/pkg/scopes/filter.go b/pkg/scopes/filter.go deleted file mode 100644 index 3eb3457a5..000000000 --- a/pkg/scopes/filter.go +++ /dev/null @@ -1,7 +0,0 @@ -// Package scopes provides OAuth scope checking utilities for GitHub MCP Server. -// -// This file contains utilities for filtering tools based on token scopes. -// For PATs, we cannot issue OAuth scope challenges, so we hide tools that -// require scopes the token doesn't have. -// -package scopes From 9aef43596e1648f1b980f6dc8731f37938482e3e Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 5 Jan 2026 14:56:10 +0100 Subject: [PATCH 036/138] Fix server-configuration.md scope filtering description --- docs/server-configuration.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/server-configuration.md b/docs/server-configuration.md index 3338a9275..b9f8b5fa7 100644 --- a/docs/server-configuration.md +++ b/docs/server-configuration.md @@ -333,11 +333,11 @@ Lockdown mode ensures the server only surfaces content in public repositories fr ### Scope Filtering -**Automatic feature:** The server automatically detects your PAT's OAuth scopes and only shows tools you have permission to use. +**Automatic feature:** The server automatically detects your classic PAT's OAuth scopes and only shows tools you have permission to use. -This happens transparently at startup - no configuration needed. If scope detection fails (e.g., network issues), the server logs a warning and continues with all tools available. +This happens transparently at startup for classic PATs (`ghp_` prefix)—no configuration needed. If scope detection fails (e.g., network issues), the server logs a warning and continues with all tools available. -See [OAuth Scope Filtering](./scope-filtering.md) for details on which scopes enable which tools. +Each tool in the [README](../README.md#tools) lists its required and accepted OAuth scopes. See [Scope Filtering](./scope-filtering.md) for details on how filtering works with different token types. --- From c80976661e7ef60be5101dab4c1656240873117d Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 5 Jan 2026 14:58:50 +0100 Subject: [PATCH 037/138] Mention OAuth scope challenges in server-configuration.md --- docs/server-configuration.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/docs/server-configuration.md b/docs/server-configuration.md index b9f8b5fa7..46ec3bc64 100644 --- a/docs/server-configuration.md +++ b/docs/server-configuration.md @@ -333,11 +333,15 @@ Lockdown mode ensures the server only surfaces content in public repositories fr ### Scope Filtering -**Automatic feature:** The server automatically detects your classic PAT's OAuth scopes and only shows tools you have permission to use. +**Automatic feature:** The server handles OAuth scopes differently depending on authentication type: -This happens transparently at startup for classic PATs (`ghp_` prefix)—no configuration needed. If scope detection fails (e.g., network issues), the server logs a warning and continues with all tools available. +- **Classic PATs** (`ghp_` prefix): Tools are filtered at startup based on token scopes—you only see tools you have permission to use +- **OAuth** (remote server): Uses scope challenges—when a tool needs a scope you haven't granted, you're prompted to authorize it +- **Other tokens**: No filtering—all tools shown, API enforces permissions -Each tool in the [README](../README.md#tools) lists its required and accepted OAuth scopes. See [Scope Filtering](./scope-filtering.md) for details on how filtering works with different token types. +This happens transparently—no configuration needed. If scope detection fails for a classic PAT (e.g., network issues), the server logs a warning and continues with all tools available. + +See [Scope Filtering](./scope-filtering.md) for details on how filtering works with different token types. --- From c428f72863fbfc12ad114c223cfea462520c13dc Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 5 Jan 2026 16:52:00 +0100 Subject: [PATCH 038/138] Don't filter read-only repo tools (work on public repos without scope) --- pkg/github/scope_filter.go | 28 ++++++++++++++++++++++++++++ pkg/github/scope_filter_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) diff --git a/pkg/github/scope_filter.go b/pkg/github/scope_filter.go index b1aa77c85..42f8e98b0 100644 --- a/pkg/github/scope_filter.go +++ b/pkg/github/scope_filter.go @@ -7,6 +7,29 @@ import ( "github.com/github/github-mcp-server/pkg/scopes" ) +// repoScopesSet contains scopes that grant access to repository content. +// Tools requiring only these scopes work on public repos without any token scope, +// so we don't filter them out even if the token lacks repo/public_repo. +var repoScopesSet = map[string]bool{ + string(scopes.Repo): true, + string(scopes.PublicRepo): true, +} + +// onlyRequiresRepoScopes returns true if all of the tool's accepted scopes +// are repo-related scopes (repo, public_repo). Such tools work on public +// repositories without needing any scope. +func onlyRequiresRepoScopes(acceptedScopes []string) bool { + if len(acceptedScopes) == 0 { + return false + } + for _, scope := range acceptedScopes { + if !repoScopesSet[scope] { + return false + } + } + return true +} + // CreateToolScopeFilter creates an inventory.ToolFilter that filters tools // based on the token's OAuth scopes. // @@ -19,6 +42,7 @@ import ( // // The filter returns true (include tool) if: // - The tool has no scope requirements (AcceptedScopes is empty) +// - The tool is read-only and only requires repo/public_repo scopes (works on public repos) // - The token has at least one of the tool's accepted scopes // // Example usage: @@ -31,6 +55,10 @@ import ( // inventory := github.NewInventory(t).WithFilter(filter).Build() func CreateToolScopeFilter(tokenScopes []string) inventory.ToolFilter { return func(_ context.Context, tool *inventory.ServerTool) (bool, error) { + // Read-only tools requiring only repo/public_repo work on public repos without any scope + if tool.Tool.Annotations != nil && tool.Tool.Annotations.ReadOnlyHint && onlyRequiresRepoScopes(tool.AcceptedScopes) { + return true, nil + } return scopes.HasRequiredScopes(tokenScopes, tool.AcceptedScopes), nil } } diff --git a/pkg/github/scope_filter_test.go b/pkg/github/scope_filter_test.go index 48eb52aa0..451d1a64e 100644 --- a/pkg/github/scope_filter_test.go +++ b/pkg/github/scope_filter_test.go @@ -27,11 +27,27 @@ func TestCreateToolScopeFilter(t *testing.T) { AcceptedScopes: []string{"repo"}, } + toolRepoScopeReadOnly := &inventory.ServerTool{ + Tool: mcp.Tool{ + Name: "repo_tool_readonly", + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, + }, + AcceptedScopes: []string{"repo"}, + } + toolPublicRepoScope := &inventory.ServerTool{ Tool: mcp.Tool{Name: "public_repo_tool"}, AcceptedScopes: []string{"public_repo", "repo"}, // repo is parent, also accepted } + toolPublicRepoScopeReadOnly := &inventory.ServerTool{ + Tool: mcp.Tool{ + Name: "public_repo_tool_readonly", + Annotations: &mcp.ToolAnnotations{ReadOnlyHint: true}, + }, + AcceptedScopes: []string{"public_repo", "repo"}, + } + toolGistScope := &inventory.ServerTool{ Tool: mcp.Tool{Name: "gist_tool"}, AcceptedScopes: []string{"gist"}, @@ -96,6 +112,18 @@ func TestCreateToolScopeFilter(t *testing.T) { tool: toolRepoScope, expected: false, }, + { + name: "empty token scopes CAN see read-only repo tools (public repos)", + tokenScopes: []string{}, + tool: toolRepoScopeReadOnly, + expected: true, + }, + { + name: "empty token scopes CAN see read-only public_repo tools", + tokenScopes: []string{}, + tool: toolPublicRepoScopeReadOnly, + expected: true, + }, { name: "token with multiple scopes where one matches", tokenScopes: []string{"gist", "repo"}, From 7d4a4a68c320797bee5b6bd14354d5a14e91b798 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 5 Jan 2026 16:55:27 +0100 Subject: [PATCH 039/138] Document public repo access quirk for read-only tools --- docs/scope-filtering.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/scope-filtering.md b/docs/scope-filtering.md index c40bf729f..f29d631ca 100644 --- a/docs/scope-filtering.md +++ b/docs/scope-filtering.md @@ -60,6 +60,14 @@ This means if your token has `repo`, tools requiring `security_events` will also Each tool in the [README](../README.md#tools) lists its required and accepted OAuth scopes. +## Public Repository Access + +Read-only tools that only require `repo` or `public_repo` scopes are **always visible**, even if your token doesn't have these scopes. This is because these tools work on public repositories without authentication. + +For example, `get_file_contents` is always available—you can read files from any public repository regardless of your token's scopes. However, write operations like `create_or_update_file` will be hidden if your token lacks `repo` scope. + +> **Note:** The GitHub API doesn't return `public_repo` in the `X-OAuth-Scopes` header—it's implicit. The server handles this by not filtering read-only repository tools. + ## Graceful Degradation If the server cannot fetch your token's scopes (e.g., network issues, rate limiting), it logs a warning and continues **without filtering**. This ensures the server remains usable even when scope detection fails. From 80b030655769b8816b726f3ce41e2285f154cf0e Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 10:45:29 +0100 Subject: [PATCH 040/138] Replace go-github-mock with stretchr/testify for actions/issues/projects tests (#1737) * Initial plan * migrate tests from go-github-mock to internal testify-based mock Co-authored-by: JoannaaKL <67866556+JoannaaKL@users.noreply.github.com> * address feedback in testmock helper Co-authored-by: JoannaaKL <67866556+JoannaaKL@users.noreply.github.com> * tweak testmock path matching edge case Co-authored-by: JoannaaKL <67866556+JoannaaKL@users.noreply.github.com> * refine testmock options and path matching Co-authored-by: JoannaaKL <67866556+JoannaaKL@users.noreply.github.com> * simplify matchPath and document delete endpoint Co-authored-by: JoannaaKL <67866556+JoannaaKL@users.noreply.github.com> * Replace go-github-mock usage in tests with shared HTTP mock helper Co-authored-by: JoannaaKL <67866556+JoannaaKL@users.noreply.github.com> * Replace go-github-mock usage in tests with shared HTTP mock helper Co-authored-by: JoannaaKL <67866556+JoannaaKL@users.noreply.github.com> * fix tests and lint after mock cleanup Co-authored-by: JoannaaKL <67866556+JoannaaKL@users.noreply.github.com> * Remove import completely * Partial removal in repositories_test.go * Final removal --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: JoannaaKL <67866556+JoannaaKL@users.noreply.github.com> Co-authored-by: JoannaaKL --- docs/testing.md | 2 +- go.mod | 4 - go.sum | 8 - pkg/github/actions_test.go | 539 +++---- pkg/github/helper_test.go | 96 +- pkg/github/issues_test.go | 851 ++++------- pkg/github/projects_test.go | 492 ++---- pkg/github/pullrequests_test.go | 716 ++++----- pkg/github/repositories_test.go | 1341 ++++++++--------- pkg/github/search_test.go | 301 ++-- pkg/raw/raw_mock.go | 20 - third-party-licenses.darwin.md | 4 - third-party-licenses.linux.md | 4 - third-party-licenses.windows.md | 4 - .../google/go-github/v71/github/LICENSE | 27 - third-party/github.com/gorilla/mux/LICENSE | 27 - .../go-github-mock/src/mock/LICENSE | 21 - third-party/golang.org/x/time/rate/LICENSE | 27 - 18 files changed, 1852 insertions(+), 2632 deletions(-) delete mode 100644 pkg/raw/raw_mock.go delete mode 100644 third-party/github.com/google/go-github/v71/github/LICENSE delete mode 100644 third-party/github.com/gorilla/mux/LICENSE delete mode 100644 third-party/github.com/migueleliasweb/go-github-mock/src/mock/LICENSE delete mode 100644 third-party/golang.org/x/time/rate/LICENSE diff --git a/docs/testing.md b/docs/testing.md index 226660e9d..2186b564b 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -7,7 +7,7 @@ This project uses a combination of unit tests and end-to-end (e2e) tests to ensu - Unit tests are located alongside implementation, with filenames ending in `_test.go`. - Currently the preference is to use internal tests i.e. test files do not have `_test` package suffix. - Tests use [testify](https://github.com/stretchr/testify) for assertions and require statements. Use `require` when continuing the test is not meaningful, for example it is almost never correct to continue after an error expectation. -- Mocking is performed using [go-github-mock](https://github.com/migueleliasweb/go-github-mock) or `githubv4mock` for simulating GitHub rest and GQL API responses. +- REST mocking is performed with the in-repo `MockHTTPClientWithHandlers` helpers; GraphQL mocking uses `githubv4mock`. - Each tool's schema is snapshotted and checked for changes using the `toolsnaps` utility (see below). - Tests are designed to be explicit and verbose to aid maintainability and clarity. - Handler unit tests should take the form of: diff --git a/go.mod b/go.mod index 691a949bd..5322b47ec 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,6 @@ require ( github.com/google/jsonschema-go v0.4.2 github.com/josephburnett/jd v1.9.2 github.com/microcosm-cc/bluemonday v1.0.27 - github.com/migueleliasweb/go-github-mock v1.3.0 github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021 github.com/spf13/cobra v1.10.2 github.com/spf13/viper v1.21.0 @@ -18,9 +17,7 @@ require ( github.com/aymerick/douceur v0.2.0 // indirect github.com/go-openapi/jsonpointer v0.19.5 // indirect github.com/go-openapi/swag v0.21.1 // indirect - github.com/google/go-github/v71 v71.0.0 // indirect github.com/gorilla/css v1.0.1 // indirect - github.com/gorilla/mux v1.8.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/stretchr/objx v0.5.2 // indirect @@ -53,7 +50,6 @@ require ( golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sys v0.31.0 // indirect golang.org/x/text v0.28.0 // indirect - golang.org/x/time v0.5.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 6f38bea2f..25cbf7fa9 100644 --- a/go.sum +++ b/go.sum @@ -22,8 +22,6 @@ github.com/golang-jwt/jwt/v5 v5.2.2/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVI github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-github/v71 v71.0.0 h1:Zi16OymGKZZMm8ZliffVVJ/Q9YZreDKONCr+WUd0Z30= -github.com/google/go-github/v71 v71.0.0/go.mod h1:URZXObp2BLlMjwu0O8g4y6VBneUj2bCHgnI8FfgZ51M= github.com/google/go-github/v79 v79.0.0 h1:MdodQojuFPBhmtwHiBcIGLw/e/wei2PvFX9ndxK0X4Y= github.com/google/go-github/v79 v79.0.0/go.mod h1:OAFbNhq7fQwohojb06iIIQAB9CBGYLq999myfUFnrS4= github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= @@ -32,8 +30,6 @@ github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbc github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/josephburnett/jd v1.9.2 h1:ECJRRFXCCqbtidkAHckHGSZm/JIaAxS1gygHLF8MI5Y= @@ -55,8 +51,6 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwXFM08ygZfk= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= -github.com/migueleliasweb/go-github-mock v1.3.0 h1:2sVP9JEMB2ubQw1IKto3/fzF51oFC6eVWOOFDgQoq88= -github.com/migueleliasweb/go-github-mock v1.3.0/go.mod h1:ipQhV8fTcj/G6m7BKzin08GaJ/3B5/SonRAkgrk0zCY= github.com/modelcontextprotocol/go-sdk v1.2.0 h1:Y23co09300CEk8iZ/tMxIX1dVmKZkzoSBZOpJwUnc/s= github.com/modelcontextprotocol/go-sdk v1.2.0/go.mod h1:6fM3LCm3yV7pAs8isnKLn07oKtB0MP9LHd3DfAcKw10= github.com/muesli/cache2go v0.0.0-20221011235721-518229cd8021 h1:31Y+Yu373ymebRdJN1cWLLooHH8xAr0MhKTEJGV/87g= @@ -114,8 +108,6 @@ golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= -golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= -golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/tools v0.35.0 h1:mBffYraMEf7aa0sB+NuKnuCy8qI/9Bughn8dC2Gu5r0= golang.org/x/tools v0.35.0/go.mod h1:NKdj5HkL/73byiZSJjqJgKn3ep7KjFkBOkR/Hps3VPw= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/github/actions_test.go b/pkg/github/actions_test.go index 7319feddf..0d47236f6 100644 --- a/pkg/github/actions_test.go +++ b/pkg/github/actions_test.go @@ -18,7 +18,6 @@ import ( "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v79/github" "github.com/google/jsonschema-go/jsonschema" - "github.com/migueleliasweb/go-github-mock/src/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -1394,17 +1393,11 @@ func Test_RerunFailedJobs(t *testing.T) { }{ { name: "successful rerun of failed jobs", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/repos/owner/repo/actions/runs/12345/rerun-failed-jobs", - Method: "POST", - }, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusCreated) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposActionsRunsRerunFailedJobsByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusCreated) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1414,7 +1407,7 @@ func Test_RerunFailedJobs(t *testing.T) { }, { name: "missing required parameter run_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1466,17 +1459,11 @@ func Test_RerunWorkflowRun_Behavioral(t *testing.T) { }{ { name: "successful rerun of workflow run", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/repos/owner/repo/actions/runs/12345/rerun", - Method: "POST", - }, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusCreated) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposActionsRunsRerunByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusCreated) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1486,7 +1473,7 @@ func Test_RerunWorkflowRun_Behavioral(t *testing.T) { }, { name: "missing required parameter run_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1538,32 +1525,29 @@ func Test_ListWorkflowRuns_Behavioral(t *testing.T) { }{ { name: "successful workflow runs listing", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsWorkflowsRunsByOwnerByRepoByWorkflowId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - runs := &github.WorkflowRuns{ - TotalCount: github.Ptr(2), - WorkflowRuns: []*github.WorkflowRun{ - { - ID: github.Ptr(int64(123)), - Name: github.Ptr("CI"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("success"), - }, - { - ID: github.Ptr(int64(456)), - Name: github.Ptr("CI"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("failure"), - }, + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsWorkflowsRunsByOwnerByRepoByWorkflowID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + runs := &github.WorkflowRuns{ + TotalCount: github.Ptr(2), + WorkflowRuns: []*github.WorkflowRun{ + { + ID: github.Ptr(int64(123)), + Name: github.Ptr("CI"), + Status: github.Ptr("completed"), + Conclusion: github.Ptr("success"), + }, + { + ID: github.Ptr(int64(456)), + Name: github.Ptr("CI"), + Status: github.Ptr("completed"), + Conclusion: github.Ptr("failure"), }, - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(runs) - }), - ), - ), + }, + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(runs) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1573,7 +1557,7 @@ func Test_ListWorkflowRuns_Behavioral(t *testing.T) { }, { name: "missing required parameter workflow_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1625,21 +1609,18 @@ func Test_GetWorkflowRun_Behavioral(t *testing.T) { }{ { name: "successful get workflow run", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsRunsByOwnerByRepoByRunId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - run := &github.WorkflowRun{ - ID: github.Ptr(int64(12345)), - Name: github.Ptr("CI"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("success"), - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(run) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsRunsByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + run := &github.WorkflowRun{ + ID: github.Ptr(int64(12345)), + Name: github.Ptr("CI"), + Status: github.Ptr("completed"), + Conclusion: github.Ptr("success"), + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(run) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1649,7 +1630,7 @@ func Test_GetWorkflowRun_Behavioral(t *testing.T) { }, { name: "missing required parameter run_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1701,15 +1682,12 @@ func Test_GetWorkflowRunLogs_Behavioral(t *testing.T) { }{ { name: "successful get workflow run logs", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsRunsLogsByOwnerByRepoByRunId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Location", "https://github.com/logs/run/12345") - w.WriteHeader(http.StatusFound) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsRunsLogsByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Location", "https://github.com/logs/run/12345") + w.WriteHeader(http.StatusFound) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1719,7 +1697,7 @@ func Test_GetWorkflowRunLogs_Behavioral(t *testing.T) { }, { name: "missing required parameter run_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1771,32 +1749,29 @@ func Test_ListWorkflowJobs_Behavioral(t *testing.T) { }{ { name: "successful list workflow jobs", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsRunsJobsByOwnerByRepoByRunId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - jobs := &github.Jobs{ - TotalCount: github.Ptr(2), - Jobs: []*github.WorkflowJob{ - { - ID: github.Ptr(int64(1)), - Name: github.Ptr("build"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("success"), - }, - { - ID: github.Ptr(int64(2)), - Name: github.Ptr("test"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("failure"), - }, + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsRunsJobsByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + jobs := &github.Jobs{ + TotalCount: github.Ptr(2), + Jobs: []*github.WorkflowJob{ + { + ID: github.Ptr(int64(1)), + Name: github.Ptr("build"), + Status: github.Ptr("completed"), + Conclusion: github.Ptr("success"), }, - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(jobs) - }), - ), - ), + { + ID: github.Ptr(int64(2)), + Name: github.Ptr("test"), + Status: github.Ptr("completed"), + Conclusion: github.Ptr("failure"), + }, + }, + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(jobs) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1806,7 +1781,7 @@ func Test_ListWorkflowJobs_Behavioral(t *testing.T) { }, { name: "missing required parameter run_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1873,32 +1848,29 @@ func Test_ActionsList_ListWorkflows(t *testing.T) { }{ { name: "successful workflow list", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsWorkflowsByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - workflows := &github.Workflows{ - TotalCount: github.Ptr(2), - Workflows: []*github.Workflow{ - { - ID: github.Ptr(int64(1)), - Name: github.Ptr("CI"), - Path: github.Ptr(".github/workflows/ci.yml"), - State: github.Ptr("active"), - }, - { - ID: github.Ptr(int64(2)), - Name: github.Ptr("Deploy"), - Path: github.Ptr(".github/workflows/deploy.yml"), - State: github.Ptr("active"), - }, + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsWorkflowsByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + workflows := &github.Workflows{ + TotalCount: github.Ptr(2), + Workflows: []*github.Workflow{ + { + ID: github.Ptr(int64(1)), + Name: github.Ptr("CI"), + Path: github.Ptr(".github/workflows/ci.yml"), + State: github.Ptr("active"), }, - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(workflows) - }), - ), - ), + { + ID: github.Ptr(int64(2)), + Name: github.Ptr("Deploy"), + Path: github.Ptr(".github/workflows/deploy.yml"), + State: github.Ptr("active"), + }, + }, + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(workflows) + }), + }), requestArgs: map[string]any{ "method": "list_workflows", "owner": "owner", @@ -1908,7 +1880,7 @@ func Test_ActionsList_ListWorkflows(t *testing.T) { }, { name: "missing required parameter method", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -1952,26 +1924,23 @@ func Test_ActionsList_ListWorkflowRuns(t *testing.T) { toolDef := ActionsList(translations.NullTranslationHelper) t.Run("successful workflow runs list", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsWorkflowsRunsByOwnerByRepoByWorkflowId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - runs := &github.WorkflowRuns{ - TotalCount: github.Ptr(1), - WorkflowRuns: []*github.WorkflowRun{ - { - ID: github.Ptr(int64(123)), - Name: github.Ptr("CI"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("success"), - }, + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsWorkflowsRunsByOwnerByRepoByWorkflowID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + runs := &github.WorkflowRuns{ + TotalCount: github.Ptr(1), + WorkflowRuns: []*github.WorkflowRun{ + { + ID: github.Ptr(int64(123)), + Name: github.Ptr("CI"), + Status: github.Ptr("completed"), + Conclusion: github.Ptr("success"), }, - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(runs) - }), - ), - ) + }, + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(runs) + }), + }) client := github.NewClient(mockedClient) deps := BaseDeps{ @@ -1998,32 +1967,29 @@ func Test_ActionsList_ListWorkflowRuns(t *testing.T) { }) t.Run("list all workflow runs without resource_id", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsRunsByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - runs := &github.WorkflowRuns{ - TotalCount: github.Ptr(2), - WorkflowRuns: []*github.WorkflowRun{ - { - ID: github.Ptr(int64(123)), - Name: github.Ptr("CI"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("success"), - }, - { - ID: github.Ptr(int64(456)), - Name: github.Ptr("Deploy"), - Status: github.Ptr("in_progress"), - Conclusion: nil, - }, + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsRunsByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + runs := &github.WorkflowRuns{ + TotalCount: github.Ptr(2), + WorkflowRuns: []*github.WorkflowRun{ + { + ID: github.Ptr(int64(123)), + Name: github.Ptr("CI"), + Status: github.Ptr("completed"), + Conclusion: github.Ptr("success"), }, - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(runs) - }), - ), - ) + { + ID: github.Ptr(int64(456)), + Name: github.Ptr("Deploy"), + Status: github.Ptr("in_progress"), + Conclusion: nil, + }, + }, + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(runs) + }), + }) client := github.NewClient(mockedClient) deps := BaseDeps{ @@ -2068,21 +2034,18 @@ func Test_ActionsGet_GetWorkflow(t *testing.T) { toolDef := ActionsGet(translations.NullTranslationHelper) t.Run("successful workflow get", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsWorkflowsByOwnerByRepoByWorkflowId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - workflow := &github.Workflow{ - ID: github.Ptr(int64(1)), - Name: github.Ptr("CI"), - Path: github.Ptr(".github/workflows/ci.yml"), - State: github.Ptr("active"), - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(workflow) - }), - ), - ) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsWorkflowsByOwnerByRepoByWorkflowID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + workflow := &github.Workflow{ + ID: github.Ptr(int64(1)), + Name: github.Ptr("CI"), + Path: github.Ptr(".github/workflows/ci.yml"), + State: github.Ptr("active"), + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(workflow) + }), + }) client := github.NewClient(mockedClient) deps := BaseDeps{ @@ -2114,21 +2077,18 @@ func Test_ActionsGet_GetWorkflowRun(t *testing.T) { toolDef := ActionsGet(translations.NullTranslationHelper) t.Run("successful workflow run get", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsRunsByOwnerByRepoByRunId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - run := &github.WorkflowRun{ - ID: github.Ptr(int64(12345)), - Name: github.Ptr("CI"), - Status: github.Ptr("completed"), - Conclusion: github.Ptr("success"), - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(run) - }), - ), - ) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsRunsByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + run := &github.WorkflowRun{ + ID: github.Ptr(int64(12345)), + Name: github.Ptr("CI"), + Status: github.Ptr("completed"), + Conclusion: github.Ptr("success"), + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(run) + }), + }) client := github.NewClient(mockedClient) deps := BaseDeps{ @@ -2185,14 +2145,11 @@ func Test_ActionsRunTrigger_RunWorkflow(t *testing.T) { }{ { name: "successful workflow run", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposActionsWorkflowsDispatchesByOwnerByRepoByWorkflowId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNoContent) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposActionsWorkflowsDispatchesByOwnerByRepoByWorkflowID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNoContent) + }), + }), requestArgs: map[string]any{ "method": "run_workflow", "owner": "owner", @@ -2204,7 +2161,7 @@ func Test_ActionsRunTrigger_RunWorkflow(t *testing.T) { }, { name: "missing required parameter workflow_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "method": "run_workflow", "owner": "owner", @@ -2216,7 +2173,7 @@ func Test_ActionsRunTrigger_RunWorkflow(t *testing.T) { }, { name: "missing required parameter ref", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "method": "run_workflow", "owner": "owner", @@ -2261,17 +2218,11 @@ func Test_ActionsRunTrigger_CancelWorkflowRun(t *testing.T) { toolDef := ActionsRunTrigger(translations.NullTranslationHelper) t.Run("successful workflow run cancellation", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/repos/owner/repo/actions/runs/12345/cancel", - Method: "POST", - }, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusAccepted) - }), - ), - ) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposActionsRunsCancelByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusAccepted) + }), + }) client := github.NewClient(mockedClient) deps := BaseDeps{ @@ -2298,17 +2249,11 @@ func Test_ActionsRunTrigger_CancelWorkflowRun(t *testing.T) { }) t.Run("conflict when cancelling a workflow run", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/repos/owner/repo/actions/runs/12345/cancel", - Method: "POST", - }, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusConflict) - }), - ), - ) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposActionsRunsCancelByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusConflict) + }), + }) client := github.NewClient(mockedClient) deps := BaseDeps{ @@ -2332,7 +2277,7 @@ func Test_ActionsRunTrigger_CancelWorkflowRun(t *testing.T) { }) t.Run("missing run_id for non-run_workflow methods", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient() + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}) client := github.NewClient(mockedClient) deps := BaseDeps{ @@ -2379,15 +2324,12 @@ func Test_ActionsGetJobLogs_SingleJob(t *testing.T) { toolDef := ActionsGetJobLogs(translations.NullTranslationHelper) t.Run("successful single job logs with URL", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsJobsLogsByOwnerByRepoByJobId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Location", "https://github.com/logs/job/123") - w.WriteHeader(http.StatusFound) - }), - ), - ) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsJobsLogsByOwnerByRepoByJobID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Location", "https://github.com/logs/job/123") + w.WriteHeader(http.StatusFound) + }), + }) client := github.NewClient(mockedClient) deps := BaseDeps{ @@ -2420,42 +2362,36 @@ func Test_ActionsGetJobLogs_FailedJobs(t *testing.T) { toolDef := ActionsGetJobLogs(translations.NullTranslationHelper) t.Run("successful failed jobs logs", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsRunsJobsByOwnerByRepoByRunId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - jobs := &github.Jobs{ - TotalCount: github.Ptr(3), - Jobs: []*github.WorkflowJob{ - { - ID: github.Ptr(int64(1)), - Name: github.Ptr("test-job-1"), - Conclusion: github.Ptr("success"), - }, - { - ID: github.Ptr(int64(2)), - Name: github.Ptr("test-job-2"), - Conclusion: github.Ptr("failure"), - }, - { - ID: github.Ptr(int64(3)), - Name: github.Ptr("test-job-3"), - Conclusion: github.Ptr("failure"), - }, + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsRunsJobsByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + jobs := &github.Jobs{ + TotalCount: github.Ptr(3), + Jobs: []*github.WorkflowJob{ + { + ID: github.Ptr(int64(1)), + Name: github.Ptr("test-job-1"), + Conclusion: github.Ptr("success"), }, - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(jobs) - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposActionsJobsLogsByOwnerByRepoByJobId, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - w.Header().Set("Location", "https://github.com/logs/job/"+r.URL.Path[len(r.URL.Path)-1:]) - w.WriteHeader(http.StatusFound) - }), - ), - ) + { + ID: github.Ptr(int64(2)), + Name: github.Ptr("test-job-2"), + Conclusion: github.Ptr("failure"), + }, + { + ID: github.Ptr(int64(3)), + Name: github.Ptr("test-job-3"), + Conclusion: github.Ptr("failure"), + }, + }, + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(jobs) + }), + GetReposActionsJobsLogsByOwnerByRepoByJobID: http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.Header().Set("Location", "https://github.com/logs/job/"+r.URL.Path[len(r.URL.Path)-1:]) + w.WriteHeader(http.StatusFound) + }), + }) client := github.NewClient(mockedClient) deps := BaseDeps{ @@ -2485,30 +2421,27 @@ func Test_ActionsGetJobLogs_FailedJobs(t *testing.T) { }) t.Run("no failed jobs found", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposActionsRunsJobsByOwnerByRepoByRunId, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - jobs := &github.Jobs{ - TotalCount: github.Ptr(2), - Jobs: []*github.WorkflowJob{ - { - ID: github.Ptr(int64(1)), - Name: github.Ptr("test-job-1"), - Conclusion: github.Ptr("success"), - }, - { - ID: github.Ptr(int64(2)), - Name: github.Ptr("test-job-2"), - Conclusion: github.Ptr("success"), - }, + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposActionsRunsJobsByOwnerByRepoByRunID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + jobs := &github.Jobs{ + TotalCount: github.Ptr(2), + Jobs: []*github.WorkflowJob{ + { + ID: github.Ptr(int64(1)), + Name: github.Ptr("test-job-1"), + Conclusion: github.Ptr("success"), }, - } - w.WriteHeader(http.StatusOK) - _ = json.NewEncoder(w).Encode(jobs) - }), - ), - ) + { + ID: github.Ptr(int64(2)), + Name: github.Ptr("test-job-2"), + Conclusion: github.Ptr("success"), + }, + }, + } + w.WriteHeader(http.StatusOK) + _ = json.NewEncoder(w).Encode(jobs) + }), + }) client := github.NewClient(mockedClient) deps := BaseDeps{ diff --git a/pkg/github/helper_test.go b/pkg/github/helper_test.go index 56a236660..0bb73008e 100644 --- a/pkg/github/helper_test.go +++ b/pkg/github/helper_test.go @@ -11,7 +11,7 @@ import ( "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/mock" + testifymock "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" ) @@ -41,9 +41,9 @@ const ( // Git endpoints GetReposGitTreesByOwnerByRepoByTree = "GET /repos/{owner}/{repo}/git/trees/{tree}" - GetReposGitRefByOwnerByRepoByRef = "GET /repos/{owner}/{repo}/git/ref/{ref}" + GetReposGitRefByOwnerByRepoByRef = "GET /repos/{owner}/{repo}/git/ref/{ref:.*}" PostReposGitRefsByOwnerByRepo = "POST /repos/{owner}/{repo}/git/refs" - PatchReposGitRefsByOwnerByRepoByRef = "PATCH /repos/{owner}/{repo}/git/refs/{ref}" + PatchReposGitRefsByOwnerByRepoByRef = "PATCH /repos/{owner}/{repo}/git/refs/{ref:.*}" GetReposGitCommitsByOwnerByRepoByCommitSHA = "GET /repos/{owner}/{repo}/git/commits/{commit_sha}" PostReposGitCommitsByOwnerByRepo = "POST /repos/{owner}/{repo}/git/commits" GetReposGitTagsByOwnerByRepoByTagSHA = "GET /repos/{owner}/{repo}/git/tags/{tag_sha}" @@ -59,7 +59,7 @@ const ( PatchReposIssuesByOwnerByRepoByIssueNumber = "PATCH /repos/{owner}/{repo}/issues/{issue_number}" GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber = "GET /repos/{owner}/{repo}/issues/{issue_number}/sub_issues" PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber = "POST /repos/{owner}/{repo}/issues/{issue_number}/sub_issues" - DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber = "DELETE /repos/{owner}/{repo}/issues/{issue_number}/sub_issues" + DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber = "DELETE /repos/{owner}/{repo}/issues/{issue_number}/sub_issue" PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber = "PATCH /repos/{owner}/{repo}/issues/{issue_number}/sub_issues/priority" // Pull request endpoints @@ -118,6 +118,7 @@ const ( GetReposActionsWorkflowsByOwnerByRepoByWorkflowID = "GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}" PostReposActionsWorkflowsDispatchesByOwnerByRepoByWorkflowID = "POST /repos/{owner}/{repo}/actions/workflows/{workflow_id}/dispatches" GetReposActionsWorkflowsRunsByOwnerByRepoByWorkflowID = "GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs" + GetReposActionsRunsByOwnerByRepo = "GET /repos/{owner}/{repo}/actions/runs" GetReposActionsRunsByOwnerByRepoByRunID = "GET /repos/{owner}/{repo}/actions/runs/{run_id}" GetReposActionsRunsLogsByOwnerByRepoByRunID = "GET /repos/{owner}/{repo}/actions/runs/{run_id}/logs" GetReposActionsRunsJobsByOwnerByRepoByRunID = "GET /repos/{owner}/{repo}/actions/runs/{run_id}/jobs" @@ -132,8 +133,8 @@ const ( // Search endpoints GetSearchCode = "GET /search/code" GetSearchIssues = "GET /search/issues" - GetSearchRepositories = "GET /search/repositories" GetSearchUsers = "GET /search/users" + GetSearchRepositories = "GET /search/repositories" // Raw content endpoints (used for GitHub raw content API, not standard API) // These are used with the raw content client that interacts with raw.githubusercontent.com @@ -141,6 +142,31 @@ const ( GetRawReposContentsByOwnerByRepoByBranchByPath = "GET /{owner}/{repo}/refs/heads/{branch}/{path:.*}" GetRawReposContentsByOwnerByRepoByTagByPath = "GET /{owner}/{repo}/refs/tags/{tag}/{path:.*}" GetRawReposContentsByOwnerByRepoBySHAByPath = "GET /{owner}/{repo}/{sha}/{path:.*}" + + // Projects (ProjectsV2) endpoints + // Organization-scoped + GetOrgsProjectsV2 = "GET /orgs/{org}/projectsV2" + GetOrgsProjectsV2ByProject = "GET /orgs/{org}/projectsV2/{project}" + GetOrgsProjectsV2FieldsByProject = "GET /orgs/{org}/projectsV2/{project}/fields" + GetOrgsProjectsV2FieldsByProjectByFieldID = "GET /orgs/{org}/projectsV2/{project}/fields/{field_id}" + GetOrgsProjectsV2ItemsByProject = "GET /orgs/{org}/projectsV2/{project}/items" + GetOrgsProjectsV2ItemsByProjectByItemID = "GET /orgs/{org}/projectsV2/{project}/items/{item_id}" + PostOrgsProjectsV2ItemsByProject = "POST /orgs/{org}/projectsV2/{project}/items" + PatchOrgsProjectsV2ItemsByProjectByItemID = "PATCH /orgs/{org}/projectsV2/{project}/items/{item_id}" + DeleteOrgsProjectsV2ItemsByProjectByItemID = "DELETE /orgs/{org}/projectsV2/{project}/items/{item_id}" + // User-scoped + GetUsersProjectsV2ByUsername = "GET /users/{username}/projectsV2" + GetUsersProjectsV2ByUsernameByProject = "GET /users/{username}/projectsV2/{project}" + GetUsersProjectsV2FieldsByUsernameByProject = "GET /users/{username}/projectsV2/{project}/fields" + GetUsersProjectsV2FieldsByUsernameByProjectByFieldID = "GET /users/{username}/projectsV2/{project}/fields/{field_id}" + GetUsersProjectsV2ItemsByUsernameByProject = "GET /users/{username}/projectsV2/{project}/items" + GetUsersProjectsV2ItemsByUsernameByProjectByItemID = "GET /users/{username}/projectsV2/{project}/items/{item_id}" + PostUsersProjectsV2ItemsByUsernameByProject = "POST /users/{username}/projectsV2/{project}/items" + PatchUsersProjectsV2ItemsByUsernameByProjectByItemID = "PATCH /users/{username}/projectsV2/{project}/items/{item_id}" + DeleteUsersProjectsV2ItemsByUsernameByProjectByItemID = "DELETE /users/{username}/projectsV2/{project}/items/{item_id}" + + // Organization issue types endpoints + GetOrgsIssueTypesByOrg = "GET /orgs/{org}/issue-types" ) type expectations struct { @@ -408,7 +434,7 @@ func getResourceResult(t *testing.T, result *mcp.CallToolResult) *mcp.ResourceCo // MockRoundTripper is a mock HTTP transport using testify/mock type MockRoundTripper struct { - mock.Mock + testifymock.Mock handlers map[string]http.HandlerFunc } @@ -564,6 +590,64 @@ func MockHTTPClientWithHandlers(handlers map[string]http.HandlerFunc) *http.Clie return &http.Client{Transport: transport} } +// Compatibility helpers to replace github.com/migueleliasweb/go-github-mock in tests +type EndpointPattern string + +type MockBackendOption func(map[string]http.HandlerFunc) + +func parseEndpointPattern(p EndpointPattern) (string, string) { + parts := strings.SplitN(string(p), " ", 2) + if len(parts) != 2 { + return http.MethodGet, string(p) + } + return parts[0], parts[1] +} + +func WithRequestMatch(pattern EndpointPattern, response any) MockBackendOption { + return func(handlers map[string]http.HandlerFunc) { + method, path := parseEndpointPattern(pattern) + handlers[method+" "+path] = func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + switch v := response.(type) { + case string: + _, _ = w.Write([]byte(v)) + case []byte: + _, _ = w.Write(v) + default: + data, err := json.Marshal(v) + if err == nil { + _, _ = w.Write(data) + } + } + } + } +} + +func WithRequestMatchHandler(pattern EndpointPattern, handler http.HandlerFunc) MockBackendOption { + return func(handlers map[string]http.HandlerFunc) { + method, path := parseEndpointPattern(pattern) + handlers[method+" "+path] = handler + } +} + +func NewMockedHTTPClient(options ...MockBackendOption) *http.Client { + handlers := map[string]http.HandlerFunc{} + for _, opt := range options { + if opt != nil { + opt(handlers) + } + } + return MockHTTPClientWithHandlers(handlers) +} + +func MustMarshal(v any) []byte { + data, err := json.Marshal(v) + if err != nil { + panic(err) + } + return data +} + type multiHandlerTransport struct { handlers map[string]http.HandlerFunc } diff --git a/pkg/github/issues_test.go b/pkg/github/issues_test.go index 694b991dc..2ccd4918f 100644 --- a/pkg/github/issues_test.go +++ b/pkg/github/issues_test.go @@ -17,7 +17,6 @@ import ( "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v79/github" "github.com/google/jsonschema-go/jsonschema" - "github.com/migueleliasweb/go-github-mock/src/mock" "github.com/shurcooL/githubv4" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -181,12 +180,9 @@ func Test_GetIssue(t *testing.T) { }{ { name: "successful issue retrieval", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposIssuesByOwnerByRepoByIssueNumber, - mockIssue, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockIssue), + }), requestArgs: map[string]interface{}{ "method": "get", "owner": "owner2", @@ -197,12 +193,9 @@ func Test_GetIssue(t *testing.T) { }, { name: "issue not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposIssuesByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusNotFound, `{"message": "Issue not found"}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Issue not found"}`), + }), requestArgs: map[string]interface{}{ "method": "get", "owner": "owner", @@ -214,12 +207,9 @@ func Test_GetIssue(t *testing.T) { }, { name: "lockdown enabled - private repository", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposIssuesByOwnerByRepoByIssueNumber, - mockIssue2, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockIssue2), + }), gqlHTTPClient: githubv4mock.NewMockedHTTPClient( githubv4mock.NewQueryMatcher( struct { @@ -261,12 +251,9 @@ func Test_GetIssue(t *testing.T) { }, { name: "lockdown enabled - user lacks push access", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposIssuesByOwnerByRepoByIssueNumber, - mockIssue, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockIssue), + }), gqlHTTPClient: githubv4mock.NewMockedHTTPClient( githubv4mock.NewQueryMatcher( struct { @@ -406,12 +393,9 @@ func Test_AddIssueComment(t *testing.T) { }{ { name: "successful comment creation", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposIssuesCommentsByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusCreated, mockComment), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposIssuesCommentsByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusCreated, mockComment), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -423,15 +407,12 @@ func Test_AddIssueComment(t *testing.T) { }, { name: "comment creation fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposIssuesCommentsByOwnerByRepoByIssueNumber, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusUnprocessableEntity) - _, _ = w.Write([]byte(`{"message": "Invalid request"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposIssuesCommentsByOwnerByRepoByIssueNumber: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnprocessableEntity) + _, _ = w.Write([]byte(`{"message": "Invalid request"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -546,23 +527,20 @@ func Test_SearchIssues(t *testing.T) { }{ { name: "successful issues search with all parameters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchIssues, - expectQueryParams( - t, - map[string]string{ - "q": "is:issue repo:owner/repo is:open", - "sort": "created", - "order": "desc", - "page": "1", - "per_page": "30", - }, - ).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchIssues: expectQueryParams( + t, + map[string]string{ + "q": "is:issue repo:owner/repo is:open", + "sort": "created", + "order": "desc", + "page": "1", + "per_page": "30", + }, + ).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "repo:owner/repo is:open", "sort": "created", @@ -575,23 +553,20 @@ func Test_SearchIssues(t *testing.T) { }, { name: "issues search with owner and repo parameters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchIssues, - expectQueryParams( - t, - map[string]string{ - "q": "repo:test-owner/test-repo is:issue is:open", - "sort": "created", - "order": "asc", - "page": "1", - "per_page": "30", - }, - ).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchIssues: expectQueryParams( + t, + map[string]string{ + "q": "repo:test-owner/test-repo is:issue is:open", + "sort": "created", + "order": "asc", + "page": "1", + "per_page": "30", + }, + ).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "is:open", "owner": "test-owner", @@ -604,21 +579,18 @@ func Test_SearchIssues(t *testing.T) { }, { name: "issues search with only owner parameter (should ignore it)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchIssues, - expectQueryParams( - t, - map[string]string{ - "q": "is:issue bug", - "page": "1", - "per_page": "30", - }, - ).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchIssues: expectQueryParams( + t, + map[string]string{ + "q": "is:issue bug", + "page": "1", + "per_page": "30", + }, + ).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "bug", "owner": "test-owner", @@ -628,21 +600,18 @@ func Test_SearchIssues(t *testing.T) { }, { name: "issues search with only repo parameter (should ignore it)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchIssues, - expectQueryParams( - t, - map[string]string{ - "q": "is:issue feature", - "page": "1", - "per_page": "30", - }, - ).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchIssues: expectQueryParams( + t, + map[string]string{ + "q": "is:issue feature", + "page": "1", + "per_page": "30", + }, + ).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "feature", "repo": "test-repo", @@ -652,12 +621,9 @@ func Test_SearchIssues(t *testing.T) { }, { name: "issues search with minimal parameters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetSearchIssues, - mockSearchResult, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchIssues: mockResponse(t, http.StatusOK, mockSearchResult), + }), requestArgs: map[string]interface{}{ "query": "is:issue repo:owner/repo is:open", }, @@ -666,21 +632,18 @@ func Test_SearchIssues(t *testing.T) { }, { name: "query with existing is:issue filter - no duplication", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchIssues, - expectQueryParams( - t, - map[string]string{ - "q": "repo:github/github-mcp-server is:issue is:open (label:critical OR label:urgent)", - "page": "1", - "per_page": "30", - }, - ).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchIssues: expectQueryParams( + t, + map[string]string{ + "q": "repo:github/github-mcp-server is:issue is:open (label:critical OR label:urgent)", + "page": "1", + "per_page": "30", + }, + ).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "repo:github/github-mcp-server is:issue is:open (label:critical OR label:urgent)", }, @@ -689,21 +652,18 @@ func Test_SearchIssues(t *testing.T) { }, { name: "query with existing repo: filter and conflicting owner/repo params - uses query filter", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchIssues, - expectQueryParams( - t, - map[string]string{ - "q": "is:issue repo:github/github-mcp-server critical", - "page": "1", - "per_page": "30", - }, - ).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchIssues: expectQueryParams( + t, + map[string]string{ + "q": "is:issue repo:github/github-mcp-server critical", + "page": "1", + "per_page": "30", + }, + ).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "repo:github/github-mcp-server critical", "owner": "different-owner", @@ -714,21 +674,18 @@ func Test_SearchIssues(t *testing.T) { }, { name: "query with both is: and repo: filters already present", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchIssues, - expectQueryParams( - t, - map[string]string{ - "q": "is:issue repo:octocat/Hello-World bug", - "page": "1", - "per_page": "30", - }, - ).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchIssues: expectQueryParams( + t, + map[string]string{ + "q": "is:issue repo:octocat/Hello-World bug", + "page": "1", + "per_page": "30", + }, + ).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "is:issue repo:octocat/Hello-World bug", }, @@ -737,21 +694,18 @@ func Test_SearchIssues(t *testing.T) { }, { name: "complex query with multiple OR operators and existing filters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchIssues, - expectQueryParams( - t, - map[string]string{ - "q": "repo:github/github-mcp-server is:issue (label:critical OR label:urgent OR label:high-priority OR label:blocker)", - "page": "1", - "per_page": "30", - }, - ).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchIssues: expectQueryParams( + t, + map[string]string{ + "q": "repo:github/github-mcp-server is:issue (label:critical OR label:urgent OR label:high-priority OR label:blocker)", + "page": "1", + "per_page": "30", + }, + ).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "repo:github/github-mcp-server is:issue (label:critical OR label:urgent OR label:high-priority OR label:blocker)", }, @@ -760,15 +714,12 @@ func Test_SearchIssues(t *testing.T) { }, { name: "search issues fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchIssues, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message": "Validation Failed"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchIssues: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"message": "Validation Failed"}`)) + }), + }), requestArgs: map[string]interface{}{ "query": "invalid:query", }, @@ -868,21 +819,18 @@ func Test_CreateIssue(t *testing.T) { }{ { name: "successful issue creation with all fields", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposIssuesByOwnerByRepo, - expectRequestBody(t, map[string]any{ - "title": "Test Issue", - "body": "This is a test issue", - "labels": []any{"bug", "help wanted"}, - "assignees": []any{"user1", "user2"}, - "milestone": float64(5), - "type": "Bug", - }).andThen( - mockResponse(t, http.StatusCreated, mockIssue), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposIssuesByOwnerByRepo: expectRequestBody(t, map[string]any{ + "title": "Test Issue", + "body": "This is a test issue", + "labels": []any{"bug", "help wanted"}, + "assignees": []any{"user1", "user2"}, + "milestone": float64(5), + "type": "Bug", + }).andThen( + mockResponse(t, http.StatusCreated, mockIssue), ), - ), + }), requestArgs: map[string]interface{}{ "method": "create", "owner": "owner", @@ -899,17 +847,14 @@ func Test_CreateIssue(t *testing.T) { }, { name: "successful issue creation with minimal fields", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposIssuesByOwnerByRepo, - mockResponse(t, http.StatusCreated, &github.Issue{ - Number: github.Ptr(124), - Title: github.Ptr("Minimal Issue"), - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/124"), - State: github.Ptr("open"), - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposIssuesByOwnerByRepo: mockResponse(t, http.StatusCreated, &github.Issue{ + Number: github.Ptr(124), + Title: github.Ptr("Minimal Issue"), + HTMLURL: github.Ptr("https://github.com/owner/repo/issues/124"), + State: github.Ptr("open"), + }), + }), requestArgs: map[string]interface{}{ "method": "create", "owner": "owner", @@ -927,15 +872,12 @@ func Test_CreateIssue(t *testing.T) { }, { name: "issue creation fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposIssuesByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusUnprocessableEntity) - _, _ = w.Write([]byte(`{"message": "Validation failed"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposIssuesByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnprocessableEntity) + _, _ = w.Write([]byte(`{"message": "Validation failed"}`)) + }), + }), requestArgs: map[string]interface{}{ "method": "create", "owner": "owner", @@ -1427,17 +1369,14 @@ func Test_UpdateIssue(t *testing.T) { }{ { name: "partial update of non-state fields only", - mockedRESTClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PatchReposIssuesByOwnerByRepoByIssueNumber, - expectRequestBody(t, map[string]interface{}{ - "title": "Updated Title", - "body": "Updated Description", - }).andThen( - mockResponse(t, http.StatusOK, mockUpdatedIssue), - ), - ), - ), + mockedRESTClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchReposIssuesByOwnerByRepoByIssueNumber: expectRequestBody(t, map[string]interface{}{ + "title": "Updated Title", + "body": "Updated Description", + }).andThen( + mockResponse(t, http.StatusOK, mockUpdatedIssue), + ), + }), mockedGQLClient: githubv4mock.NewMockedHTTPClient(), requestArgs: map[string]interface{}{ "method": "update", @@ -1452,15 +1391,12 @@ func Test_UpdateIssue(t *testing.T) { }, { name: "issue not found when updating non-state fields only", - mockedRESTClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PatchReposIssuesByOwnerByRepoByIssueNumber, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedRESTClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchReposIssuesByOwnerByRepoByIssueNumber: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }), + }), mockedGQLClient: githubv4mock.NewMockedHTTPClient(), requestArgs: map[string]interface{}{ "method": "update", @@ -1474,12 +1410,9 @@ func Test_UpdateIssue(t *testing.T) { }, { name: "close issue as duplicate", - mockedRESTClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.PatchReposIssuesByOwnerByRepoByIssueNumber, - mockBaseIssue, - ), - ), + mockedRESTClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockBaseIssue), + }), mockedGQLClient: githubv4mock.NewMockedHTTPClient( githubv4mock.NewQueryMatcher( struct { @@ -1534,12 +1467,9 @@ func Test_UpdateIssue(t *testing.T) { }, { name: "reopen issue", - mockedRESTClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.PatchReposIssuesByOwnerByRepoByIssueNumber, - mockBaseIssue, - ), - ), + mockedRESTClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockBaseIssue), + }), mockedGQLClient: githubv4mock.NewMockedHTTPClient( githubv4mock.NewQueryMatcher( struct { @@ -1586,12 +1516,9 @@ func Test_UpdateIssue(t *testing.T) { }, { name: "main issue not found when trying to close it", - mockedRESTClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.PatchReposIssuesByOwnerByRepoByIssueNumber, - mockBaseIssue, - ), - ), + mockedRESTClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockBaseIssue), + }), mockedGQLClient: githubv4mock.NewMockedHTTPClient( githubv4mock.NewQueryMatcher( struct { @@ -1622,12 +1549,9 @@ func Test_UpdateIssue(t *testing.T) { }, { name: "duplicate issue not found when closing as duplicate", - mockedRESTClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.PatchReposIssuesByOwnerByRepoByIssueNumber, - mockBaseIssue, - ), - ), + mockedRESTClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchReposIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockBaseIssue), + }), mockedGQLClient: githubv4mock.NewMockedHTTPClient( githubv4mock.NewQueryMatcher( struct { @@ -1663,31 +1587,28 @@ func Test_UpdateIssue(t *testing.T) { }, { name: "close as duplicate with combined non-state updates", - mockedRESTClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PatchReposIssuesByOwnerByRepoByIssueNumber, - expectRequestBody(t, map[string]interface{}{ - "title": "Updated Title", - "body": "Updated Description", - "labels": []any{"bug", "priority"}, - "assignees": []any{"assignee1", "assignee2"}, - "milestone": float64(5), - "type": "Bug", - }).andThen( - mockResponse(t, http.StatusOK, &github.Issue{ - Number: github.Ptr(123), - Title: github.Ptr("Updated Title"), - Body: github.Ptr("Updated Description"), - Labels: []*github.Label{{Name: github.Ptr("bug")}, {Name: github.Ptr("priority")}}, - Assignees: []*github.User{{Login: github.Ptr("assignee1")}, {Login: github.Ptr("assignee2")}}, - Milestone: &github.Milestone{Number: github.Ptr(5)}, - Type: &github.IssueType{Name: github.Ptr("Bug")}, - State: github.Ptr("open"), // Still open after REST update - HTMLURL: github.Ptr("https://github.com/owner/repo/issues/123"), - }), - ), + mockedRESTClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchReposIssuesByOwnerByRepoByIssueNumber: expectRequestBody(t, map[string]interface{}{ + "title": "Updated Title", + "body": "Updated Description", + "labels": []any{"bug", "priority"}, + "assignees": []any{"assignee1", "assignee2"}, + "milestone": float64(5), + "type": "Bug", + }).andThen( + mockResponse(t, http.StatusOK, &github.Issue{ + Number: github.Ptr(123), + Title: github.Ptr("Updated Title"), + Body: github.Ptr("Updated Description"), + Labels: []*github.Label{{Name: github.Ptr("bug")}, {Name: github.Ptr("priority")}}, + Assignees: []*github.User{{Login: github.Ptr("assignee1")}, {Login: github.Ptr("assignee2")}}, + Milestone: &github.Milestone{Number: github.Ptr(5)}, + Type: &github.IssueType{Name: github.Ptr("Bug")}, + State: github.Ptr("open"), // Still open after REST update + HTMLURL: github.Ptr("https://github.com/owner/repo/issues/123"), + }), ), - ), + }), mockedGQLClient: githubv4mock.NewMockedHTTPClient( githubv4mock.NewQueryMatcher( struct { @@ -1748,7 +1669,7 @@ func Test_UpdateIssue(t *testing.T) { }, { name: "duplicate_of without duplicate state_reason should fail", - mockedRESTClient: mock.NewMockedHTTPClient(), + mockedRESTClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), mockedGQLClient: githubv4mock.NewMockedHTTPClient(), requestArgs: map[string]interface{}{ "method": "update", @@ -1910,12 +1831,9 @@ func Test_GetIssueComments(t *testing.T) { }{ { name: "successful comments retrieval", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposIssuesCommentsByOwnerByRepoByIssueNumber, - mockComments, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposIssuesCommentsByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockComments), + }), requestArgs: map[string]interface{}{ "method": "get_comments", "owner": "owner", @@ -1927,17 +1845,14 @@ func Test_GetIssueComments(t *testing.T) { }, { name: "successful comments retrieval with pagination", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposIssuesCommentsByOwnerByRepoByIssueNumber, - expectQueryParams(t, map[string]string{ - "page": "2", - "per_page": "10", - }).andThen( - mockResponse(t, http.StatusOK, mockComments), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposIssuesCommentsByOwnerByRepoByIssueNumber: expectQueryParams(t, map[string]string{ + "page": "2", + "per_page": "10", + }).andThen( + mockResponse(t, http.StatusOK, mockComments), ), - ), + }), requestArgs: map[string]interface{}{ "method": "get_comments", "owner": "owner", @@ -1951,12 +1866,9 @@ func Test_GetIssueComments(t *testing.T) { }, { name: "issue not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposIssuesCommentsByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusNotFound, `{"message": "Issue not found"}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposIssuesCommentsByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Issue not found"}`), + }), requestArgs: map[string]interface{}{ "method": "get_comments", "owner": "owner", @@ -1968,23 +1880,20 @@ func Test_GetIssueComments(t *testing.T) { }, { name: "lockdown enabled filters comments without push access", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposIssuesCommentsByOwnerByRepoByIssueNumber, - []*github.IssueComment{ - { - ID: github.Ptr(int64(789)), - Body: github.Ptr("Maintainer comment"), - User: &github.User{Login: github.Ptr("maintainer")}, - }, - { - ID: github.Ptr(int64(790)), - Body: github.Ptr("External user comment"), - User: &github.User{Login: github.Ptr("testuser")}, - }, + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposIssuesCommentsByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, []*github.IssueComment{ + { + ID: github.Ptr(int64(789)), + Body: github.Ptr("Maintainer comment"), + User: &github.User{Login: github.Ptr("maintainer")}, }, - ), - ), + { + ID: github.Ptr(int64(790)), + Body: github.Ptr("External user comment"), + User: &github.User{Login: github.Ptr("testuser")}, + }, + }), + }), gqlHTTPClient: newRepoAccessHTTPClient(), requestArgs: map[string]interface{}{ "method": "get_comments", @@ -2631,12 +2540,9 @@ func Test_AddSubIssue(t *testing.T) { }{ { name: "successful sub-issue addition with all parameters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusCreated, mockIssue), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusCreated, mockIssue), + }), requestArgs: map[string]interface{}{ "method": "add", "owner": "owner", @@ -2650,12 +2556,9 @@ func Test_AddSubIssue(t *testing.T) { }, { name: "successful sub-issue addition with minimal parameters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusCreated, mockIssue), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusCreated, mockIssue), + }), requestArgs: map[string]interface{}{ "method": "add", "owner": "owner", @@ -2668,12 +2571,9 @@ func Test_AddSubIssue(t *testing.T) { }, { name: "successful sub-issue addition with replace_parent false", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusCreated, mockIssue), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusCreated, mockIssue), + }), requestArgs: map[string]interface{}{ "method": "add", "owner": "owner", @@ -2687,12 +2587,9 @@ func Test_AddSubIssue(t *testing.T) { }, { name: "parent issue not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusNotFound, `{"message": "Parent issue not found"}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Parent issue not found"}`), + }), requestArgs: map[string]interface{}{ "method": "add", "owner": "owner", @@ -2705,12 +2602,9 @@ func Test_AddSubIssue(t *testing.T) { }, { name: "sub-issue not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusNotFound, `{"message": "Sub-issue not found"}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Sub-issue not found"}`), + }), requestArgs: map[string]interface{}{ "method": "add", "owner": "owner", @@ -2723,12 +2617,9 @@ func Test_AddSubIssue(t *testing.T) { }, { name: "validation failed - sub-issue cannot be parent of itself", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusUnprocessableEntity, `{"message": "Validation failed", "errors": [{"message": "Sub-issue cannot be a parent of itself"}]}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusUnprocessableEntity, `{"message": "Validation failed", "errors": [{"message": "Sub-issue cannot be a parent of itself"}]}`), + }), requestArgs: map[string]interface{}{ "method": "add", "owner": "owner", @@ -2741,12 +2632,9 @@ func Test_AddSubIssue(t *testing.T) { }, { name: "insufficient permissions", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusForbidden, `{"message": "Must have write access to repository"}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusForbidden, `{"message": "Must have write access to repository"}`), + }), requestArgs: map[string]interface{}{ "method": "add", "owner": "owner", @@ -2759,9 +2647,7 @@ func Test_AddSubIssue(t *testing.T) { }, { name: "missing required parameter owner", - mockedClient: mock.NewMockedHTTPClient( - // No mocked requests needed since validation fails before HTTP call - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "method": "add", "repo": "repo", @@ -2773,9 +2659,7 @@ func Test_AddSubIssue(t *testing.T) { }, { name: "missing required parameter sub_issue_id", - mockedClient: mock.NewMockedHTTPClient( - // No mocked requests needed since validation fails before HTTP call - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "method": "add", "owner": "owner", @@ -2895,12 +2779,9 @@ func Test_GetSubIssues(t *testing.T) { }{ { name: "successful sub-issues listing with minimal parameters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber, - mockSubIssues, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockSubIssues), + }), requestArgs: map[string]interface{}{ "method": "get_sub_issues", "owner": "owner", @@ -2912,17 +2793,14 @@ func Test_GetSubIssues(t *testing.T) { }, { name: "successful sub-issues listing with pagination", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber, - expectQueryParams(t, map[string]string{ - "page": "2", - "per_page": "10", - }).andThen( - mockResponse(t, http.StatusOK, mockSubIssues), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber: expectQueryParams(t, map[string]string{ + "page": "2", + "per_page": "10", + }).andThen( + mockResponse(t, http.StatusOK, mockSubIssues), ), - ), + }), requestArgs: map[string]interface{}{ "method": "get_sub_issues", "owner": "owner", @@ -2936,12 +2814,9 @@ func Test_GetSubIssues(t *testing.T) { }, { name: "successful sub-issues listing with empty result", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber, - []*github.Issue{}, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, []*github.Issue{}), + }), requestArgs: map[string]interface{}{ "method": "get_sub_issues", "owner": "owner", @@ -2953,12 +2828,9 @@ func Test_GetSubIssues(t *testing.T) { }, { name: "parent issue not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusNotFound, `{"message": "Not Found"}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Not Found"}`), + }), requestArgs: map[string]interface{}{ "method": "get_sub_issues", "owner": "owner", @@ -2970,12 +2842,9 @@ func Test_GetSubIssues(t *testing.T) { }, { name: "repository not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusNotFound, `{"message": "Not Found"}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Not Found"}`), + }), requestArgs: map[string]interface{}{ "method": "get_sub_issues", "owner": "nonexistent", @@ -2987,12 +2856,9 @@ func Test_GetSubIssues(t *testing.T) { }, { name: "sub-issues feature gone/deprecated", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusGone, `{"message": "This feature has been deprecated"}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposIssuesSubIssuesByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusGone, `{"message": "This feature has been deprecated"}`), + }), requestArgs: map[string]interface{}{ "method": "get_sub_issues", "owner": "owner", @@ -3004,9 +2870,7 @@ func Test_GetSubIssues(t *testing.T) { }, { name: "missing required parameter owner", - mockedClient: mock.NewMockedHTTPClient( - // No mocked requests needed since validation fails before HTTP call - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "method": "get_sub_issues", "repo": "repo", @@ -3017,9 +2881,7 @@ func Test_GetSubIssues(t *testing.T) { }, { name: "missing required parameter issue_number", - mockedClient: mock.NewMockedHTTPClient( - // No mocked requests needed since validation fails before HTTP call - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "method": "get_sub_issues", "owner": "owner", @@ -3135,12 +2997,9 @@ func Test_RemoveSubIssue(t *testing.T) { }{ { name: "successful sub-issue removal", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusOK, mockIssue), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockIssue), + }), requestArgs: map[string]interface{}{ "method": "remove", "owner": "owner", @@ -3153,12 +3012,9 @@ func Test_RemoveSubIssue(t *testing.T) { }, { name: "parent issue not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusNotFound, `{"message": "Not Found"}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Not Found"}`), + }), requestArgs: map[string]interface{}{ "method": "remove", "owner": "owner", @@ -3171,12 +3027,9 @@ func Test_RemoveSubIssue(t *testing.T) { }, { name: "sub-issue not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusNotFound, `{"message": "Sub-issue not found"}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Sub-issue not found"}`), + }), requestArgs: map[string]interface{}{ "method": "remove", "owner": "owner", @@ -3189,12 +3042,9 @@ func Test_RemoveSubIssue(t *testing.T) { }, { name: "bad request - invalid sub_issue_id", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusBadRequest, `{"message": "Invalid sub_issue_id"}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusBadRequest, `{"message": "Invalid sub_issue_id"}`), + }), requestArgs: map[string]interface{}{ "method": "remove", "owner": "owner", @@ -3207,12 +3057,9 @@ func Test_RemoveSubIssue(t *testing.T) { }, { name: "repository not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusNotFound, `{"message": "Not Found"}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Not Found"}`), + }), requestArgs: map[string]interface{}{ "method": "remove", "owner": "nonexistent", @@ -3225,12 +3072,9 @@ func Test_RemoveSubIssue(t *testing.T) { }, { name: "insufficient permissions", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusForbidden, `{"message": "Must have write access to repository"}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteReposIssuesSubIssueByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusForbidden, `{"message": "Must have write access to repository"}`), + }), requestArgs: map[string]interface{}{ "method": "remove", "owner": "owner", @@ -3243,9 +3087,7 @@ func Test_RemoveSubIssue(t *testing.T) { }, { name: "missing required parameter owner", - mockedClient: mock.NewMockedHTTPClient( - // No mocked requests needed since validation fails before HTTP call - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "method": "remove", "repo": "repo", @@ -3257,9 +3099,7 @@ func Test_RemoveSubIssue(t *testing.T) { }, { name: "missing required parameter sub_issue_id", - mockedClient: mock.NewMockedHTTPClient( - // No mocked requests needed since validation fails before HTTP call - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "method": "remove", "owner": "owner", @@ -3365,12 +3205,9 @@ func Test_ReprioritizeSubIssue(t *testing.T) { }{ { name: "successful reprioritization with after_id", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusOK, mockIssue), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockIssue), + }), requestArgs: map[string]interface{}{ "method": "reprioritize", "owner": "owner", @@ -3384,12 +3221,9 @@ func Test_ReprioritizeSubIssue(t *testing.T) { }, { name: "successful reprioritization with before_id", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusOK, mockIssue), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusOK, mockIssue), + }), requestArgs: map[string]interface{}{ "method": "reprioritize", "owner": "owner", @@ -3403,9 +3237,7 @@ func Test_ReprioritizeSubIssue(t *testing.T) { }, { name: "validation error - neither after_id nor before_id specified", - mockedClient: mock.NewMockedHTTPClient( - // No mocked requests needed since validation fails before HTTP call - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "method": "reprioritize", "owner": "owner", @@ -3418,9 +3250,7 @@ func Test_ReprioritizeSubIssue(t *testing.T) { }, { name: "validation error - both after_id and before_id specified", - mockedClient: mock.NewMockedHTTPClient( - // No mocked requests needed since validation fails before HTTP call - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "method": "reprioritize", "owner": "owner", @@ -3435,12 +3265,9 @@ func Test_ReprioritizeSubIssue(t *testing.T) { }, { name: "parent issue not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusNotFound, `{"message": "Not Found"}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Not Found"}`), + }), requestArgs: map[string]interface{}{ "method": "reprioritize", "owner": "owner", @@ -3454,12 +3281,9 @@ func Test_ReprioritizeSubIssue(t *testing.T) { }, { name: "sub-issue not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusNotFound, `{"message": "Sub-issue not found"}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusNotFound, `{"message": "Sub-issue not found"}`), + }), requestArgs: map[string]interface{}{ "method": "reprioritize", "owner": "owner", @@ -3473,12 +3297,9 @@ func Test_ReprioritizeSubIssue(t *testing.T) { }, { name: "validation failed - positioning sub-issue not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusUnprocessableEntity, `{"message": "Validation failed", "errors": [{"message": "Positioning sub-issue not found"}]}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusUnprocessableEntity, `{"message": "Validation failed", "errors": [{"message": "Positioning sub-issue not found"}]}`), + }), requestArgs: map[string]interface{}{ "method": "reprioritize", "owner": "owner", @@ -3492,12 +3313,9 @@ func Test_ReprioritizeSubIssue(t *testing.T) { }, { name: "insufficient permissions", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusForbidden, `{"message": "Must have write access to repository"}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusForbidden, `{"message": "Must have write access to repository"}`), + }), requestArgs: map[string]interface{}{ "method": "reprioritize", "owner": "owner", @@ -3511,12 +3329,9 @@ func Test_ReprioritizeSubIssue(t *testing.T) { }, { name: "service unavailable", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber, - mockResponse(t, http.StatusServiceUnavailable, `{"message": "Service Unavailable"}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchReposIssuesSubIssuesPriorityByOwnerByRepoByIssueNumber: mockResponse(t, http.StatusServiceUnavailable, `{"message": "Service Unavailable"}`), + }), requestArgs: map[string]interface{}{ "method": "reprioritize", "owner": "owner", @@ -3530,9 +3345,7 @@ func Test_ReprioritizeSubIssue(t *testing.T) { }, { name: "missing required parameter owner", - mockedClient: mock.NewMockedHTTPClient( - // No mocked requests needed since validation fails before HTTP call - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "method": "reprioritize", "repo": "repo", @@ -3545,9 +3358,7 @@ func Test_ReprioritizeSubIssue(t *testing.T) { }, { name: "missing required parameter sub_issue_id", - mockedClient: mock.NewMockedHTTPClient( - // No mocked requests needed since validation fails before HTTP call - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "method": "reprioritize", "owner": "owner", @@ -3645,15 +3456,9 @@ func Test_ListIssueTypes(t *testing.T) { }{ { name: "successful issue types retrieval", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/orgs/testorg/issue-types", - Method: "GET", - }, - mockResponse(t, http.StatusOK, mockIssueTypes), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + "GET /orgs/testorg/issue-types": mockResponse(t, http.StatusOK, mockIssueTypes), + }), requestArgs: map[string]interface{}{ "owner": "testorg", }, @@ -3662,15 +3467,9 @@ func Test_ListIssueTypes(t *testing.T) { }, { name: "organization not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/orgs/nonexistent/issue-types", - Method: "GET", - }, - mockResponse(t, http.StatusNotFound, `{"message": "Organization not found"}`), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + "GET /orgs/nonexistent/issue-types": mockResponse(t, http.StatusNotFound, `{"message": "Organization not found"}`), + }), requestArgs: map[string]interface{}{ "owner": "nonexistent", }, @@ -3679,15 +3478,9 @@ func Test_ListIssueTypes(t *testing.T) { }, { name: "missing owner parameter", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/orgs/testorg/issue-types", - Method: "GET", - }, - mockResponse(t, http.StatusOK, mockIssueTypes), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + "GET /orgs/testorg/issue-types": mockResponse(t, http.StatusOK, mockIssueTypes), + }), requestArgs: map[string]interface{}{}, expectError: false, // This should be handled by parameter validation, error returned in result expectedErrMsg: "missing required parameter: owner", diff --git a/pkg/github/projects_test.go b/pkg/github/projects_test.go index e443b9ecd..e3a50af29 100644 --- a/pkg/github/projects_test.go +++ b/pkg/github/projects_test.go @@ -3,7 +3,6 @@ package github import ( "context" "encoding/json" - "io" "net/http" "testing" @@ -11,7 +10,6 @@ import ( "github.com/github/github-mcp-server/pkg/translations" gh "github.com/google/go-github/v79/github" "github.com/google/jsonschema-go/jsonschema" - "github.com/migueleliasweb/go-github-mock/src/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -45,15 +43,9 @@ func Test_ListProjects(t *testing.T) { }{ { name: "success organization", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(orgProjects)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2: mockResponse(t, http.StatusOK, orgProjects), + }), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -63,15 +55,9 @@ func Test_ListProjects(t *testing.T) { }, { name: "success user", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/users/{username}/projectsV2", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(userProjects)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUsersProjectsV2ByUsername: mockResponse(t, http.StatusOK, userProjects), + }), requestArgs: map[string]interface{}{ "owner": "octocat", "owner_type": "user", @@ -81,21 +67,12 @@ func Test_ListProjects(t *testing.T) { }, { name: "success organization with pagination & query", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - q := r.URL.Query() - if q.Get("per_page") == "50" && q.Get("q") == "roadmap" { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(orgProjects)) - return - } - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message":"unexpected query params"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2: expectQueryParams(t, map[string]string{ + "per_page": "50", + "q": "roadmap", + }).andThen(mockResponse(t, http.StatusOK, orgProjects)), + }), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -107,12 +84,9 @@ func Test_ListProjects(t *testing.T) { }, { name: "api error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2", Method: http.MethodGet}, - mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2: mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), + }), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -122,7 +96,7 @@ func Test_ListProjects(t *testing.T) { }, { name: "missing owner", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "owner_type": "org", }, @@ -130,7 +104,7 @@ func Test_ListProjects(t *testing.T) { }, { name: "missing owner_type", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "owner": "octo-org", }, @@ -204,12 +178,9 @@ func Test_GetProject(t *testing.T) { }{ { name: "success organization project fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/123", Method: http.MethodGet}, - mockResponse(t, http.StatusOK, project), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2ByProject: mockResponse(t, http.StatusOK, project), + }), requestArgs: map[string]interface{}{ "project_number": float64(123), "owner": "octo-org", @@ -219,12 +190,9 @@ func Test_GetProject(t *testing.T) { }, { name: "success user project fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/users/{username}/projectsV2/456", Method: http.MethodGet}, - mockResponse(t, http.StatusOK, project), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUsersProjectsV2ByUsernameByProject: mockResponse(t, http.StatusOK, project), + }), requestArgs: map[string]interface{}{ "project_number": float64(456), "owner": "octocat", @@ -234,12 +202,9 @@ func Test_GetProject(t *testing.T) { }, { name: "api error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/999", Method: http.MethodGet}, - mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2ByProject: mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), + }), requestArgs: map[string]interface{}{ "project_number": float64(999), "owner": "octo-org", @@ -250,7 +215,7 @@ func Test_GetProject(t *testing.T) { }, { name: "missing project_number", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -259,7 +224,7 @@ func Test_GetProject(t *testing.T) { }, { name: "missing owner", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "project_number": float64(123), "owner_type": "org", @@ -268,7 +233,7 @@ func Test_GetProject(t *testing.T) { }, { name: "missing owner_type", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "project_number": float64(123), "owner": "octo-org", @@ -343,15 +308,9 @@ func Test_ListProjectFields(t *testing.T) { }{ { name: "success organization fields", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/fields", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(orgFields)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2FieldsByProject: mockResponse(t, http.StatusOK, orgFields), + }), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -361,21 +320,11 @@ func Test_ListProjectFields(t *testing.T) { }, { name: "success user fields with per_page override", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/users/{user}/projectsV2/{project}/fields", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - q := r.URL.Query() - if q.Get("per_page") == "50" { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(userFields)) - return - } - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message":"unexpected query params"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUsersProjectsV2FieldsByUsernameByProject: expectQueryParams(t, map[string]string{ + "per_page": "50", + }).andThen(mockResponse(t, http.StatusOK, userFields)), + }), requestArgs: map[string]interface{}{ "owner": "octocat", "owner_type": "user", @@ -386,12 +335,9 @@ func Test_ListProjectFields(t *testing.T) { }, { name: "api error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/fields", Method: http.MethodGet}, - mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2FieldsByProject: mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), + }), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -402,7 +348,7 @@ func Test_ListProjectFields(t *testing.T) { }, { name: "missing owner", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "owner_type": "org", "project_number": 10, @@ -411,7 +357,7 @@ func Test_ListProjectFields(t *testing.T) { }, { name: "missing owner_type", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "owner": "octo-org", "project_number": 10, @@ -420,7 +366,7 @@ func Test_ListProjectFields(t *testing.T) { }, { name: "missing project_number", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -500,12 +446,9 @@ func Test_GetProjectField(t *testing.T) { }{ { name: "success organization field", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/fields/{field_id}", Method: http.MethodGet}, - mockResponse(t, http.StatusOK, orgField), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2FieldsByProjectByFieldID: mockResponse(t, http.StatusOK, orgField), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -516,12 +459,9 @@ func Test_GetProjectField(t *testing.T) { }, { name: "success user field", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/users/{user}/projectsV2/{project}/fields/{field_id}", Method: http.MethodGet}, - mockResponse(t, http.StatusOK, userField), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUsersProjectsV2FieldsByUsernameByProjectByFieldID: mockResponse(t, http.StatusOK, userField), + }), requestArgs: map[string]any{ "owner": "octocat", "owner_type": "user", @@ -532,12 +472,9 @@ func Test_GetProjectField(t *testing.T) { }, { name: "api error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/fields/{field_id}", Method: http.MethodGet}, - mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2FieldsByProjectByFieldID: mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -549,7 +486,7 @@ func Test_GetProjectField(t *testing.T) { }, { name: "missing owner", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner_type": "org", "project_number": float64(10), @@ -559,7 +496,7 @@ func Test_GetProjectField(t *testing.T) { }, { name: "missing owner_type", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "octo-org", "project_number": float64(10), @@ -569,7 +506,7 @@ func Test_GetProjectField(t *testing.T) { }, { name: "missing project_number", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -579,7 +516,7 @@ func Test_GetProjectField(t *testing.T) { }, { name: "missing field_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -671,12 +608,9 @@ func Test_ListProjectItems(t *testing.T) { }{ { name: "success organization items", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items", Method: http.MethodGet}, - mockResponse(t, http.StatusOK, orgItems), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2ItemsByProject: mockResponse(t, http.StatusOK, orgItems), + }), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -686,21 +620,12 @@ func Test_ListProjectItems(t *testing.T) { }, { name: "success organization items with fields", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - q := r.URL.Query() - if q.Get("fields") == "123,456,789" { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(orgItems)) - return - } - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message":"unexpected query params"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2ItemsByProject: expectQueryParams(t, map[string]string{ + "fields": "123,456,789", + "per_page": "50", + }).andThen(mockResponse(t, http.StatusOK, orgItems)), + }), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -711,12 +636,9 @@ func Test_ListProjectItems(t *testing.T) { }, { name: "success user items", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/users/{user}/projectsV2/{project}/items", Method: http.MethodGet}, - mockResponse(t, http.StatusOK, userItems), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUsersProjectsV2ItemsByUsernameByProject: mockResponse(t, http.StatusOK, userItems), + }), requestArgs: map[string]interface{}{ "owner": "octocat", "owner_type": "user", @@ -726,21 +648,12 @@ func Test_ListProjectItems(t *testing.T) { }, { name: "success with pagination and query", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - q := r.URL.Query() - if q.Get("per_page") == "50" && q.Get("q") == "bug" { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(orgItems)) - return - } - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message":"unexpected query params"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2ItemsByProject: expectQueryParams(t, map[string]string{ + "per_page": "50", + "q": "bug", + }).andThen(mockResponse(t, http.StatusOK, orgItems)), + }), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -752,12 +665,9 @@ func Test_ListProjectItems(t *testing.T) { }, { name: "api error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items", Method: http.MethodGet}, - mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2ItemsByProject: mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), + }), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -768,7 +678,7 @@ func Test_ListProjectItems(t *testing.T) { }, { name: "missing owner", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "owner_type": "org", "project_number": float64(10), @@ -777,7 +687,7 @@ func Test_ListProjectItems(t *testing.T) { }, { name: "missing owner_type", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "owner": "octo-org", "project_number": float64(10), @@ -786,7 +696,7 @@ func Test_ListProjectItems(t *testing.T) { }, { name: "missing project_number", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "owner": "octo-org", "owner_type": "org", @@ -877,12 +787,9 @@ func Test_GetProjectItem(t *testing.T) { }{ { name: "success organization item", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items/{item_id}", Method: http.MethodGet}, - mockResponse(t, http.StatusOK, orgItem), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2ItemsByProjectByItemID: mockResponse(t, http.StatusOK, orgItem), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -893,21 +800,11 @@ func Test_GetProjectItem(t *testing.T) { }, { name: "success organization item with fields", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items/{item_id}", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - q := r.URL.Query() - if q.Get("fields") == "123,456" { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(orgItem)) - return - } - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message":"unexpected query params"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2ItemsByProjectByItemID: expectQueryParams(t, map[string]string{ + "fields": "123,456", + }).andThen(mockResponse(t, http.StatusOK, orgItem)), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -919,12 +816,9 @@ func Test_GetProjectItem(t *testing.T) { }, { name: "success user item", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/users/{user}/projectsV2/{project}/items/{item_id}", Method: http.MethodGet}, - mockResponse(t, http.StatusOK, userItem), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUsersProjectsV2ItemsByUsernameByProjectByItemID: mockResponse(t, http.StatusOK, userItem), + }), requestArgs: map[string]any{ "owner": "octocat", "owner_type": "user", @@ -935,12 +829,9 @@ func Test_GetProjectItem(t *testing.T) { }, { name: "api error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items/{item_id}", Method: http.MethodGet}, - mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2ItemsByProjectByItemID: mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -952,7 +843,7 @@ func Test_GetProjectItem(t *testing.T) { }, { name: "missing owner", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner_type": "org", "project_number": float64(10), @@ -962,7 +853,7 @@ func Test_GetProjectItem(t *testing.T) { }, { name: "missing owner_type", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "octo-org", "project_number": float64(10), @@ -972,7 +863,7 @@ func Test_GetProjectItem(t *testing.T) { }, { name: "missing project_number", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -982,7 +873,7 @@ func Test_GetProjectItem(t *testing.T) { }, { name: "missing item_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1086,24 +977,12 @@ func Test_AddProjectItem(t *testing.T) { }{ { name: "success organization issue", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items", Method: http.MethodPost}, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - body, err := io.ReadAll(r.Body) - assert.NoError(t, err) - var payload struct { - Type string `json:"type"` - ID int `json:"id"` - } - assert.NoError(t, json.Unmarshal(body, &payload)) - assert.Equal(t, "Issue", payload.Type) - assert.Equal(t, 9876, payload.ID) - w.WriteHeader(http.StatusCreated) - _, _ = w.Write(mock.MustMarshal(orgItem)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostOrgsProjectsV2ItemsByProject: expectRequestBody(t, map[string]any{ + "type": "Issue", + "id": float64(9876), + }).andThen(mockResponse(t, http.StatusCreated, orgItem)), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1117,24 +996,12 @@ func Test_AddProjectItem(t *testing.T) { }, { name: "success user pull request", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/users/{user}/projectsV2/{project}/items", Method: http.MethodPost}, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - body, err := io.ReadAll(r.Body) - assert.NoError(t, err) - var payload struct { - Type string `json:"type"` - ID int `json:"id"` - } - assert.NoError(t, json.Unmarshal(body, &payload)) - assert.Equal(t, "PullRequest", payload.Type) - assert.Equal(t, 7654, payload.ID) - w.WriteHeader(http.StatusCreated) - _, _ = w.Write(mock.MustMarshal(userItem)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostUsersProjectsV2ItemsByUsernameByProject: expectRequestBody(t, map[string]any{ + "type": "PullRequest", + "id": float64(7654), + }).andThen(mockResponse(t, http.StatusCreated, userItem)), + }), requestArgs: map[string]any{ "owner": "octocat", "owner_type": "user", @@ -1148,12 +1015,9 @@ func Test_AddProjectItem(t *testing.T) { }, { name: "api error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items", Method: http.MethodPost}, - mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostOrgsProjectsV2ItemsByProject: mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1166,7 +1030,7 @@ func Test_AddProjectItem(t *testing.T) { }, { name: "missing owner", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner_type": "org", "project_number": float64(1), @@ -1177,7 +1041,7 @@ func Test_AddProjectItem(t *testing.T) { }, { name: "missing owner_type", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "octo-org", "project_number": float64(1), @@ -1188,7 +1052,7 @@ func Test_AddProjectItem(t *testing.T) { }, { name: "missing project_number", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1199,7 +1063,7 @@ func Test_AddProjectItem(t *testing.T) { }, { name: "missing item_type", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1210,7 +1074,7 @@ func Test_AddProjectItem(t *testing.T) { }, { name: "missing item_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1310,27 +1174,11 @@ func Test_UpdateProjectItem(t *testing.T) { }{ { name: "success organization update", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items/{item_id}", Method: http.MethodPatch}, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - body, err := io.ReadAll(r.Body) - assert.NoError(t, err) - var payload struct { - Fields []struct { - ID int `json:"id"` - Value interface{} `json:"value"` - } `json:"fields"` - } - assert.NoError(t, json.Unmarshal(body, &payload)) - require.Len(t, payload.Fields, 1) - assert.Equal(t, 101, payload.Fields[0].ID) - assert.Equal(t, "Done", payload.Fields[0].Value) - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(orgUpdatedItem)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchOrgsProjectsV2ItemsByProjectByItemID: expectRequestBody(t, map[string]any{ + "fields": []any{map[string]any{"id": float64(101), "value": "Done"}}, + }).andThen(mockResponse(t, http.StatusOK, orgUpdatedItem)), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1345,27 +1193,11 @@ func Test_UpdateProjectItem(t *testing.T) { }, { name: "success user update", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/users/{user}/projectsV2/{project}/items/{item_id}", Method: http.MethodPatch}, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - body, err := io.ReadAll(r.Body) - assert.NoError(t, err) - var payload struct { - Fields []struct { - ID int `json:"id"` - Value interface{} `json:"value"` - } `json:"fields"` - } - assert.NoError(t, json.Unmarshal(body, &payload)) - require.Len(t, payload.Fields, 1) - assert.Equal(t, 202, payload.Fields[0].ID) - assert.Equal(t, 42.0, payload.Fields[0].Value) - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(userUpdatedItem)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchUsersProjectsV2ItemsByUsernameByProjectByItemID: expectRequestBody(t, map[string]any{ + "fields": []any{map[string]any{"id": float64(202), "value": float64(42)}}, + }).andThen(mockResponse(t, http.StatusOK, userUpdatedItem)), + }), requestArgs: map[string]any{ "owner": "octocat", "owner_type": "user", @@ -1380,12 +1212,9 @@ func Test_UpdateProjectItem(t *testing.T) { }, { name: "api error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items/{item_id}", Method: http.MethodPatch}, - mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchOrgsProjectsV2ItemsByProjectByItemID: mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1401,7 +1230,7 @@ func Test_UpdateProjectItem(t *testing.T) { }, { name: "missing owner", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner_type": "org", "project_number": float64(1), @@ -1415,7 +1244,7 @@ func Test_UpdateProjectItem(t *testing.T) { }, { name: "missing owner_type", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "octo-org", "project_number": float64(1), @@ -1429,7 +1258,7 @@ func Test_UpdateProjectItem(t *testing.T) { }, { name: "missing project_number", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1443,7 +1272,7 @@ func Test_UpdateProjectItem(t *testing.T) { }, { name: "missing item_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1457,7 +1286,7 @@ func Test_UpdateProjectItem(t *testing.T) { }, { name: "missing updated_field", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1468,7 +1297,7 @@ func Test_UpdateProjectItem(t *testing.T) { }, { name: "updated_field not object", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1480,7 +1309,7 @@ func Test_UpdateProjectItem(t *testing.T) { }, { name: "updated_field missing id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1492,7 +1321,7 @@ func Test_UpdateProjectItem(t *testing.T) { }, { name: "updated_field missing value", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1580,14 +1409,11 @@ func Test_DeleteProjectItem(t *testing.T) { }{ { name: "success organization delete", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items/{item_id}", Method: http.MethodDelete}, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNoContent) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteOrgsProjectsV2ItemsByProjectByItemID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNoContent) + }), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1598,14 +1424,11 @@ func Test_DeleteProjectItem(t *testing.T) { }, { name: "success user delete", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/users/{user}/projectsV2/{project}/items/{item_id}", Method: http.MethodDelete}, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNoContent) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteUsersProjectsV2ItemsByUsernameByProjectByItemID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNoContent) + }), + }), requestArgs: map[string]any{ "owner": "octocat", "owner_type": "user", @@ -1616,12 +1439,9 @@ func Test_DeleteProjectItem(t *testing.T) { }, { name: "api error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/{project}/items/{item_id}", Method: http.MethodDelete}, - mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteOrgsProjectsV2ItemsByProjectByItemID: mockResponse(t, http.StatusInternalServerError, map[string]string{"message": "boom"}), + }), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1633,7 +1453,7 @@ func Test_DeleteProjectItem(t *testing.T) { }, { name: "missing owner", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner_type": "org", "project_number": float64(1), @@ -1643,7 +1463,7 @@ func Test_DeleteProjectItem(t *testing.T) { }, { name: "missing owner_type", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "octo-org", "project_number": float64(1), @@ -1653,7 +1473,7 @@ func Test_DeleteProjectItem(t *testing.T) { }, { name: "missing project_number", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1663,7 +1483,7 @@ func Test_DeleteProjectItem(t *testing.T) { }, { name: "missing item_id", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", diff --git a/pkg/github/pullrequests_test.go b/pkg/github/pullrequests_test.go index 3cb41515d..d2664479d 100644 --- a/pkg/github/pullrequests_test.go +++ b/pkg/github/pullrequests_test.go @@ -14,8 +14,6 @@ import ( "github.com/google/go-github/v79/github" "github.com/google/jsonschema-go/jsonschema" "github.com/shurcooL/githubv4" - - "github.com/migueleliasweb/go-github-mock/src/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -64,12 +62,9 @@ func Test_GetPullRequest(t *testing.T) { }{ { name: "successful PR fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposPullsByOwnerByRepoByPullNumber, - mockPR, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockPR), + }), requestArgs: map[string]interface{}{ "method": "get", "owner": "owner", @@ -81,15 +76,12 @@ func Test_GetPullRequest(t *testing.T) { }, { name: "PR fetch fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposPullsByOwnerByRepoByPullNumber, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposPullsByOwnerByRepoByPullNumber: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }, + }), requestArgs: map[string]interface{}{ "method": "get", "owner": "owner", @@ -209,24 +201,17 @@ func Test_UpdatePullRequest(t *testing.T) { }{ { name: "successful PR update (title, body, base, maintainer_can_modify)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PatchReposPullsByOwnerByRepoByPullNumber, - // Expect the flat string based on previous test failure output and API docs - expectRequestBody(t, map[string]interface{}{ - "title": "Updated Test PR Title", - "body": "Updated test PR body.", - "base": "develop", - "maintainer_can_modify": false, - }).andThen( - mockResponse(t, http.StatusOK, mockUpdatedPR), - ), - ), - mock.WithRequestMatch( - mock.GetReposPullsByOwnerByRepoByPullNumber, - mockUpdatedPR, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchReposPullsByOwnerByRepoByPullNumber: expectRequestBody(t, map[string]interface{}{ + "title": "Updated Test PR Title", + "body": "Updated test PR body.", + "base": "develop", + "maintainer_can_modify": false, + }).andThen( + mockResponse(t, http.StatusOK, mockUpdatedPR), + ), + GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockUpdatedPR), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -241,20 +226,14 @@ func Test_UpdatePullRequest(t *testing.T) { }, { name: "successful PR update (state)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PatchReposPullsByOwnerByRepoByPullNumber, - expectRequestBody(t, map[string]interface{}{ - "state": "closed", - }).andThen( - mockResponse(t, http.StatusOK, mockClosedPR), - ), - ), - mock.WithRequestMatch( - mock.GetReposPullsByOwnerByRepoByPullNumber, - mockClosedPR, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchReposPullsByOwnerByRepoByPullNumber: expectRequestBody(t, map[string]interface{}{ + "state": "closed", + }).andThen( + mockResponse(t, http.StatusOK, mockClosedPR), + ), + GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockClosedPR), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -266,17 +245,10 @@ func Test_UpdatePullRequest(t *testing.T) { }, { name: "successful PR update with reviewers", - mockedClient: mock.NewMockedHTTPClient( - // Mock for RequestReviewers call, returning the PR with reviewers - mock.WithRequestMatch( - mock.PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber, - mockPRWithReviewers, - ), - mock.WithRequestMatch( - mock.GetReposPullsByOwnerByRepoByPullNumber, - mockPRWithReviewers, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockPRWithReviewers), + GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockPRWithReviewers), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -288,20 +260,14 @@ func Test_UpdatePullRequest(t *testing.T) { }, { name: "successful PR update (title only)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PatchReposPullsByOwnerByRepoByPullNumber, - expectRequestBody(t, map[string]interface{}{ - "title": "Updated Test PR Title", - }).andThen( - mockResponse(t, http.StatusOK, mockUpdatedPR), - ), - ), - mock.WithRequestMatch( - mock.GetReposPullsByOwnerByRepoByPullNumber, - mockUpdatedPR, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchReposPullsByOwnerByRepoByPullNumber: expectRequestBody(t, map[string]interface{}{ + "title": "Updated Test PR Title", + }).andThen( + mockResponse(t, http.StatusOK, mockUpdatedPR), + ), + GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockUpdatedPR), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -313,7 +279,7 @@ func Test_UpdatePullRequest(t *testing.T) { }, { name: "no update parameters provided", - mockedClient: mock.NewMockedHTTPClient(), // No API call expected + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), // No API call expected requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -325,15 +291,12 @@ func Test_UpdatePullRequest(t *testing.T) { }, { name: "PR update fails (API error)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PatchReposPullsByOwnerByRepoByPullNumber, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusUnprocessableEntity) - _, _ = w.Write([]byte(`{"message": "Validation Failed"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchReposPullsByOwnerByRepoByPullNumber: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnprocessableEntity) + _, _ = w.Write([]byte(`{"message": "Validation Failed"}`)) + }, + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -345,16 +308,12 @@ func Test_UpdatePullRequest(t *testing.T) { }, { name: "request reviewers fails", - mockedClient: mock.NewMockedHTTPClient( - // Then reviewer request fails - mock.WithRequestMatchHandler( - mock.PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusUnprocessableEntity) - _, _ = w.Write([]byte(`{"message": "Invalid reviewers"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnprocessableEntity) + _, _ = w.Write([]byte(`{"message": "Invalid reviewers"}`)) + }, + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -553,12 +512,9 @@ func Test_UpdatePullRequest_Draft(t *testing.T) { for _, tc := range tests { t.Run(tc.name, func(t *testing.T) { // For draft-only tests, we need to mock both GraphQL and the final REST GET call - restClient := github.NewClient(mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposPullsByOwnerByRepoByPullNumber, - mockUpdatedPR, - ), - )) + restClient := github.NewClient(MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockUpdatedPR), + })) gqlClient := githubv4.NewClient(tc.mockedClient) serverTool := UpdatePullRequest(translations.NullTranslationHelper) @@ -642,20 +598,17 @@ func Test_ListPullRequests(t *testing.T) { }{ { name: "successful PRs listing", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposPullsByOwnerByRepo, - expectQueryParams(t, map[string]string{ - "state": "all", - "sort": "created", - "direction": "desc", - "per_page": "30", - "page": "1", - }).andThen( - mockResponse(t, http.StatusOK, mockPRs), - ), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposPullsByOwnerByRepo: expectQueryParams(t, map[string]string{ + "state": "all", + "sort": "created", + "direction": "desc", + "per_page": "30", + "page": "1", + }).andThen( + mockResponse(t, http.StatusOK, mockPRs), + ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -670,15 +623,12 @@ func Test_ListPullRequests(t *testing.T) { }, { name: "PRs listing fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposPullsByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message": "Invalid request"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposPullsByOwnerByRepo: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"message": "Invalid request"}`)) + }, + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -769,18 +719,15 @@ func Test_MergePullRequest(t *testing.T) { }{ { name: "successful merge", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PutReposPullsMergeByOwnerByRepoByPullNumber, - expectRequestBody(t, map[string]interface{}{ - "commit_title": "Merge PR #42", - "commit_message": "Merging awesome feature", - "merge_method": "squash", - }).andThen( - mockResponse(t, http.StatusOK, mockMergeResult), - ), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PutReposPullsMergeByOwnerByRepoByPullNumber: expectRequestBody(t, map[string]interface{}{ + "commit_title": "Merge PR #42", + "commit_message": "Merging awesome feature", + "merge_method": "squash", + }).andThen( + mockResponse(t, http.StatusOK, mockMergeResult), + ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -794,15 +741,12 @@ func Test_MergePullRequest(t *testing.T) { }, { name: "merge fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PutReposPullsMergeByOwnerByRepoByPullNumber, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusMethodNotAllowed) - _, _ = w.Write([]byte(`{"message": "Pull request cannot be merged"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PutReposPullsMergeByOwnerByRepoByPullNumber: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusMethodNotAllowed) + _, _ = w.Write([]byte(`{"message": "Pull request cannot be merged"}`)) + }, + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -911,23 +855,20 @@ func Test_SearchPullRequests(t *testing.T) { }{ { name: "successful pull request search with all parameters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchIssues, - expectQueryParams( - t, - map[string]string{ - "q": "is:pr repo:owner/repo is:open", - "sort": "created", - "order": "desc", - "page": "1", - "per_page": "30", - }, - ).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchIssues: expectQueryParams( + t, + map[string]string{ + "q": "is:pr repo:owner/repo is:open", + "sort": "created", + "order": "desc", + "page": "1", + "per_page": "30", + }, + ).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "repo:owner/repo is:open", "sort": "created", @@ -940,23 +881,20 @@ func Test_SearchPullRequests(t *testing.T) { }, { name: "pull request search with owner and repo parameters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchIssues, - expectQueryParams( - t, - map[string]string{ - "q": "repo:test-owner/test-repo is:pr draft:false", - "sort": "updated", - "order": "asc", - "page": "1", - "per_page": "30", - }, - ).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchIssues: expectQueryParams( + t, + map[string]string{ + "q": "repo:test-owner/test-repo is:pr draft:false", + "sort": "updated", + "order": "asc", + "page": "1", + "per_page": "30", + }, + ).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "draft:false", "owner": "test-owner", @@ -969,21 +907,18 @@ func Test_SearchPullRequests(t *testing.T) { }, { name: "pull request search with only owner parameter (should ignore it)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchIssues, - expectQueryParams( - t, - map[string]string{ - "q": "is:pr feature", - "page": "1", - "per_page": "30", - }, - ).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchIssues: expectQueryParams( + t, + map[string]string{ + "q": "is:pr feature", + "page": "1", + "per_page": "30", + }, + ).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "feature", "owner": "test-owner", @@ -993,21 +928,18 @@ func Test_SearchPullRequests(t *testing.T) { }, { name: "pull request search with only repo parameter (should ignore it)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchIssues, - expectQueryParams( - t, - map[string]string{ - "q": "is:pr review-required", - "page": "1", - "per_page": "30", - }, - ).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchIssues: expectQueryParams( + t, + map[string]string{ + "q": "is:pr review-required", + "page": "1", + "per_page": "30", + }, + ).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "review-required", "repo": "test-repo", @@ -1017,12 +949,9 @@ func Test_SearchPullRequests(t *testing.T) { }, { name: "pull request search with minimal parameters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetSearchIssues, - mockSearchResult, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchIssues: mockResponse(t, http.StatusOK, mockSearchResult), + }), requestArgs: map[string]interface{}{ "query": "is:pr repo:owner/repo is:open", }, @@ -1031,21 +960,18 @@ func Test_SearchPullRequests(t *testing.T) { }, { name: "query with existing is:pr filter - no duplication", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchIssues, - expectQueryParams( - t, - map[string]string{ - "q": "is:pr repo:github/github-mcp-server is:open draft:false", - "page": "1", - "per_page": "30", - }, - ).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchIssues: expectQueryParams( + t, + map[string]string{ + "q": "is:pr repo:github/github-mcp-server is:open draft:false", + "page": "1", + "per_page": "30", + }, + ).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "is:pr repo:github/github-mcp-server is:open draft:false", }, @@ -1054,21 +980,18 @@ func Test_SearchPullRequests(t *testing.T) { }, { name: "query with existing repo: filter and conflicting owner/repo params - uses query filter", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchIssues, - expectQueryParams( - t, - map[string]string{ - "q": "is:pr repo:github/github-mcp-server author:octocat", - "page": "1", - "per_page": "30", - }, - ).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchIssues: expectQueryParams( + t, + map[string]string{ + "q": "is:pr repo:github/github-mcp-server author:octocat", + "page": "1", + "per_page": "30", + }, + ).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "repo:github/github-mcp-server author:octocat", "owner": "different-owner", @@ -1079,21 +1002,18 @@ func Test_SearchPullRequests(t *testing.T) { }, { name: "complex query with existing is:pr filter and OR operators", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchIssues, - expectQueryParams( - t, - map[string]string{ - "q": "is:pr repo:github/github-mcp-server (label:bug OR label:enhancement OR label:feature)", - "page": "1", - "per_page": "30", - }, - ).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchIssues: expectQueryParams( + t, + map[string]string{ + "q": "is:pr repo:github/github-mcp-server (label:bug OR label:enhancement OR label:feature)", + "page": "1", + "per_page": "30", + }, + ).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "is:pr repo:github/github-mcp-server (label:bug OR label:enhancement OR label:feature)", }, @@ -1102,15 +1022,12 @@ func Test_SearchPullRequests(t *testing.T) { }, { name: "search pull requests fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchIssues, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message": "Validation Failed"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchIssues: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"message": "Validation Failed"}`)) + }, + }), requestArgs: map[string]interface{}{ "query": "invalid:query", }, @@ -1216,12 +1133,14 @@ func Test_GetPullRequestFiles(t *testing.T) { }{ { name: "successful files fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposPullsFilesByOwnerByRepoByPullNumber, - mockFiles, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposPullsFilesByOwnerByRepoByPullNumber: expectQueryParams(t, map[string]string{ + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockFiles), + ), + }), requestArgs: map[string]interface{}{ "method": "get_files", "owner": "owner", @@ -1233,12 +1152,14 @@ func Test_GetPullRequestFiles(t *testing.T) { }, { name: "successful files fetch with pagination", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposPullsFilesByOwnerByRepoByPullNumber, - mockFiles, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposPullsFilesByOwnerByRepoByPullNumber: expectQueryParams(t, map[string]string{ + "page": "2", + "per_page": "10", + }).andThen( + mockResponse(t, http.StatusOK, mockFiles), + ), + }), requestArgs: map[string]interface{}{ "method": "get_files", "owner": "owner", @@ -1252,15 +1173,17 @@ func Test_GetPullRequestFiles(t *testing.T) { }, { name: "files fetch fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposPullsFilesByOwnerByRepoByPullNumber, + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposPullsFilesByOwnerByRepoByPullNumber: expectQueryParams(t, map[string]string{ + "page": "1", + "per_page": "30", + }).andThen( http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNotFound) _, _ = w.Write([]byte(`{"message": "Not Found"}`)) }), ), - ), + }), requestArgs: map[string]interface{}{ "method": "get_files", "owner": "owner", @@ -1382,16 +1305,10 @@ func Test_GetPullRequestStatus(t *testing.T) { }{ { name: "successful status fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposPullsByOwnerByRepoByPullNumber, - mockPR, - ), - mock.WithRequestMatch( - mock.GetReposCommitsStatusByOwnerByRepoByRef, - mockStatus, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockPR), + GetReposCommitsStatusByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockStatus), + }), requestArgs: map[string]interface{}{ "method": "get_status", "owner": "owner", @@ -1403,15 +1320,12 @@ func Test_GetPullRequestStatus(t *testing.T) { }, { name: "PR fetch fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposPullsByOwnerByRepoByPullNumber, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposPullsByOwnerByRepoByPullNumber: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }), + }), requestArgs: map[string]interface{}{ "method": "get_status", "owner": "owner", @@ -1423,19 +1337,13 @@ func Test_GetPullRequestStatus(t *testing.T) { }, { name: "status fetch fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposPullsByOwnerByRepoByPullNumber, - mockPR, - ), - mock.WithRequestMatchHandler( - mock.GetReposCommitsStatusesByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposPullsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockPR), + GetReposCommitsStatusesByOwnerByRepoByRef: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }), + }), requestArgs: map[string]interface{}{ "method": "get_status", "owner": "owner", @@ -1527,16 +1435,13 @@ func Test_UpdatePullRequestBranch(t *testing.T) { }{ { name: "successful branch update", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PutReposPullsUpdateBranchByOwnerByRepoByPullNumber, - expectRequestBody(t, map[string]interface{}{ - "expected_head_sha": "abcd1234", - }).andThen( - mockResponse(t, http.StatusAccepted, mockUpdateResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PutReposPullsUpdateBranchByOwnerByRepoByPullNumber: expectRequestBody(t, map[string]interface{}{ + "expected_head_sha": "abcd1234", + }).andThen( + mockResponse(t, http.StatusAccepted, mockUpdateResult), ), - ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1548,14 +1453,11 @@ func Test_UpdatePullRequestBranch(t *testing.T) { }, { name: "branch update without expected SHA", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PutReposPullsUpdateBranchByOwnerByRepoByPullNumber, - expectRequestBody(t, map[string]interface{}{}).andThen( - mockResponse(t, http.StatusAccepted, mockUpdateResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PutReposPullsUpdateBranchByOwnerByRepoByPullNumber: expectRequestBody(t, map[string]interface{}{}).andThen( + mockResponse(t, http.StatusAccepted, mockUpdateResult), ), - ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1566,15 +1468,12 @@ func Test_UpdatePullRequestBranch(t *testing.T) { }, { name: "branch update fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PutReposPullsUpdateBranchByOwnerByRepoByPullNumber, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusConflict) - _, _ = w.Write([]byte(`{"message": "Merge conflict"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PutReposPullsUpdateBranchByOwnerByRepoByPullNumber: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusConflict) + _, _ = w.Write([]byte(`{"message": "Merge conflict"}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1997,12 +1896,9 @@ func Test_GetPullRequestReviews(t *testing.T) { }{ { name: "successful reviews fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposPullsReviewsByOwnerByRepoByPullNumber, - mockReviews, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposPullsReviewsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, mockReviews), + }), requestArgs: map[string]interface{}{ "method": "get_reviews", "owner": "owner", @@ -2014,15 +1910,12 @@ func Test_GetPullRequestReviews(t *testing.T) { }, { name: "reviews fetch fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposPullsReviewsByOwnerByRepoByPullNumber, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposPullsReviewsByOwnerByRepoByPullNumber: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }), + }), requestArgs: map[string]interface{}{ "method": "get_reviews", "owner": "owner", @@ -2034,25 +1927,22 @@ func Test_GetPullRequestReviews(t *testing.T) { }, { name: "lockdown enabled filters reviews without push access", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposPullsReviewsByOwnerByRepoByPullNumber, - []*github.PullRequestReview{ - { - ID: github.Ptr(int64(2030)), - State: github.Ptr("APPROVED"), - Body: github.Ptr("Maintainer review"), - User: &github.User{Login: github.Ptr("maintainer")}, - }, - { - ID: github.Ptr(int64(2031)), - State: github.Ptr("COMMENTED"), - Body: github.Ptr("External reviewer"), - User: &github.User{Login: github.Ptr("testuser")}, - }, + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposPullsReviewsByOwnerByRepoByPullNumber: mockResponse(t, http.StatusOK, []*github.PullRequestReview{ + { + ID: github.Ptr(int64(2030)), + State: github.Ptr("APPROVED"), + Body: github.Ptr("Maintainer review"), + User: &github.User{Login: github.Ptr("maintainer")}, }, - ), - ), + { + ID: github.Ptr(int64(2031)), + State: github.Ptr("COMMENTED"), + Body: github.Ptr("External reviewer"), + User: &github.User{Login: github.Ptr("testuser")}, + }, + }), + }), gqlHTTPClient: newRepoAccessHTTPClient(), requestArgs: map[string]interface{}{ "method": "get_reviews", @@ -2183,21 +2073,18 @@ func Test_CreatePullRequest(t *testing.T) { }{ { name: "successful PR creation", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposPullsByOwnerByRepo, - expectRequestBody(t, map[string]interface{}{ - "title": "Test PR", - "body": "This is a test PR", - "head": "feature-branch", - "base": "main", - "draft": false, - "maintainer_can_modify": true, - }).andThen( - mockResponse(t, http.StatusCreated, mockPR), - ), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposPullsByOwnerByRepo: expectRequestBody(t, map[string]interface{}{ + "title": "Test PR", + "body": "This is a test PR", + "head": "feature-branch", + "base": "main", + "draft": false, + "maintainer_can_modify": true, + }).andThen( + mockResponse(t, http.StatusCreated, mockPR), + ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -2213,7 +2100,7 @@ func Test_CreatePullRequest(t *testing.T) { }, { name: "missing required parameter", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -2224,15 +2111,12 @@ func Test_CreatePullRequest(t *testing.T) { }, { name: "PR creation fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposPullsByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusUnprocessableEntity) - _, _ = w.Write([]byte(`{"message":"Validation failed","errors":[{"resource":"PullRequest","code":"invalid"}]}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposPullsByOwnerByRepo: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnprocessableEntity) + _, _ = w.Write([]byte(`{"message":"Validation failed","errors":[{"resource":"PullRequest","code":"invalid"}]}`)) + }), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -2535,19 +2419,16 @@ func Test_RequestCopilotReview(t *testing.T) { }{ { name: "successful request", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber, - expect(t, expectations{ - path: "/repos/owner/repo/pulls/1/requested_reviewers", - requestBody: map[string]any{ - "reviewers": []any{"copilot-pull-request-reviewer[bot]"}, - }, - }).andThen( - mockResponse(t, http.StatusCreated, mockPR), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber: expect(t, expectations{ + path: "/repos/owner/repo/pulls/1/requested_reviewers", + requestBody: map[string]any{ + "reviewers": []any{"copilot-pull-request-reviewer[bot]"}, + }, + }).andThen( + mockResponse(t, http.StatusCreated, mockPR), ), - ), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -2557,15 +2438,12 @@ func Test_RequestCopilotReview(t *testing.T) { }, { name: "request fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposPullsRequestedReviewersByOwnerByRepoByPullNumber: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }), + }), requestArgs: map[string]any{ "owner": "owner", "repo": "repo", @@ -3234,15 +3112,11 @@ index 5d6e7b2..8a4f5c3 100644 "repo": "repo", "pullNumber": float64(42), }, - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposPullsByOwnerByRepoByPullNumber, - // Should also expect Accept header to be application/vnd.github.v3.diff - expectPath(t, "/repos/owner/repo/pulls/42").andThen( - mockResponse(t, http.StatusOK, stubbedDiff), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposPullsByOwnerByRepoByPullNumber: expectPath(t, "/repos/owner/repo/pulls/42").andThen( + mockResponse(t, http.StatusOK, stubbedDiff), ), - ), + }), expectToolError: false, }, } diff --git a/pkg/github/repositories_test.go b/pkg/github/repositories_test.go index 1e81d8c53..d91af8851 100644 --- a/pkg/github/repositories_test.go +++ b/pkg/github/repositories_test.go @@ -16,7 +16,6 @@ import ( "github.com/github/github-mcp-server/pkg/utils" "github.com/google/go-github/v79/github" "github.com/google/jsonschema-go/jsonschema" - "github.com/migueleliasweb/go-github-mock/src/mock" "github.com/modelcontextprotocol/go-sdk/mcp" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -74,36 +73,25 @@ func Test_GetFileContents(t *testing.T) { }{ { name: "successful text content fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposContentsByOwnerByRepoByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - fileContent := &github.RepositoryContent{ - Name: github.Ptr("README.md"), - Path: github.Ptr("README.md"), - SHA: github.Ptr("abc123"), - Type: github.Ptr("file"), - } - contentBytes, _ := json.Marshal(fileContent) - _, _ = w.Write(contentBytes) - }), - ), - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoByBranchByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "text/markdown") - _, _ = w.Write(mockRawContent) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, "{\"ref\": \"refs/heads/main\", \"object\": {\"sha\": \"\"}}"), + GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, "{\"name\": \"repo\", \"default_branch\": \"main\"}"), + GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + fileContent := &github.RepositoryContent{ + Name: github.Ptr("README.md"), + Path: github.Ptr("README.md"), + SHA: github.Ptr("abc123"), + Type: github.Ptr("file"), + } + contentBytes, _ := json.Marshal(fileContent) + _, _ = w.Write(contentBytes) + }, + GetRawReposContentsByOwnerByRepoByBranchByPath: func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/markdown") + _, _ = w.Write(mockRawContent) + }, + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -119,36 +107,25 @@ func Test_GetFileContents(t *testing.T) { }, { name: "successful file blob content fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposContentsByOwnerByRepoByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - fileContent := &github.RepositoryContent{ - Name: github.Ptr("test.png"), - Path: github.Ptr("test.png"), - SHA: github.Ptr("def456"), - Type: github.Ptr("file"), - } - contentBytes, _ := json.Marshal(fileContent) - _, _ = w.Write(contentBytes) - }), - ), - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoByBranchByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "image/png") - _, _ = w.Write(mockRawContent) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, "{\"ref\": \"refs/heads/main\", \"object\": {\"sha\": \"\"}}"), + GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, "{\"name\": \"repo\", \"default_branch\": \"main\"}"), + GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + fileContent := &github.RepositoryContent{ + Name: github.Ptr("test.png"), + Path: github.Ptr("test.png"), + SHA: github.Ptr("def456"), + Type: github.Ptr("file"), + } + contentBytes, _ := json.Marshal(fileContent) + _, _ = w.Write(contentBytes) + }, + GetRawReposContentsByOwnerByRepoByBranchByPath: func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "image/png") + _, _ = w.Write(mockRawContent) + }, + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -164,36 +141,25 @@ func Test_GetFileContents(t *testing.T) { }, { name: "successful PDF file content fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposContentsByOwnerByRepoByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - fileContent := &github.RepositoryContent{ - Name: github.Ptr("document.pdf"), - Path: github.Ptr("document.pdf"), - SHA: github.Ptr("pdf123"), - Type: github.Ptr("file"), - } - contentBytes, _ := json.Marshal(fileContent) - _, _ = w.Write(contentBytes) - }), - ), - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoByBranchByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "application/pdf") - _, _ = w.Write(mockRawContent) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, "{\"ref\": \"refs/heads/main\", \"object\": {\"sha\": \"\"}}"), + GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, "{\"name\": \"repo\", \"default_branch\": \"main\"}"), + GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + fileContent := &github.RepositoryContent{ + Name: github.Ptr("document.pdf"), + Path: github.Ptr("document.pdf"), + SHA: github.Ptr("pdf123"), + Type: github.Ptr("file"), + } + contentBytes, _ := json.Marshal(fileContent) + _, _ = w.Write(contentBytes) + }, + GetRawReposContentsByOwnerByRepoByBranchByPath: func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "application/pdf") + _, _ = w.Write(mockRawContent) + }, + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -209,36 +175,16 @@ func Test_GetFileContents(t *testing.T) { }, { name: "successful directory content fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"name": "repo", "default_branch": "main"}`)) - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposContentsByOwnerByRepoByPath, - expectQueryParams(t, map[string]string{}).andThen( - mockResponse(t, http.StatusOK, mockDirContent), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, "{\"name\": \"repo\", \"default_branch\": \"main\"}"), + GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, "{\"ref\": \"refs/heads/main\", \"object\": {\"sha\": \"\"}}"), + GetReposContentsByOwnerByRepoByPath: expectQueryParams(t, map[string]string{}).andThen( + mockResponse(t, http.StatusOK, mockDirContent), ), - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoByPath, - expectQueryParams(t, map[string]string{ - "branch": "main", - }).andThen( - mockResponse(t, http.StatusNotFound, nil), - ), + GetRawReposContentsByOwnerByRepoByPath: expectQueryParams(t, map[string]string{"branch": "main"}).andThen( + mockResponse(t, http.StatusNotFound, nil), ), - ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -249,36 +195,25 @@ func Test_GetFileContents(t *testing.T) { }, { name: "successful text content fetch with leading slash in path", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposContentsByOwnerByRepoByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - fileContent := &github.RepositoryContent{ - Name: github.Ptr("README.md"), - Path: github.Ptr("README.md"), - SHA: github.Ptr("abc123"), - Type: github.Ptr("file"), - } - contentBytes, _ := json.Marshal(fileContent) - _, _ = w.Write(contentBytes) - }), - ), - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoByBranchByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "text/markdown") - _, _ = w.Write(mockRawContent) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, "{\"ref\": \"refs/heads/main\", \"object\": {\"sha\": \"\"}}"), + GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, "{\"name\": \"repo\", \"default_branch\": \"main\"}"), + GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + fileContent := &github.RepositoryContent{ + Name: github.Ptr("README.md"), + Path: github.Ptr("README.md"), + SHA: github.Ptr("abc123"), + Type: github.Ptr("file"), + } + contentBytes, _ := json.Marshal(fileContent) + _, _ = w.Write(contentBytes) + }, + GetRawReposContentsByOwnerByRepoByBranchByPath: func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/markdown") + _, _ = w.Write(mockRawContent) + }, + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -294,54 +229,82 @@ func Test_GetFileContents(t *testing.T) { }, { name: "successful text content fetch with note when ref falls back to default branch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, "{\"name\": \"repo\", \"default_branch\": \"develop\"}"), + GetReposGitRefByOwnerByRepoByRef: func(w http.ResponseWriter, r *http.Request) { + path := strings.ReplaceAll(r.URL.Path, "%2F", "/") + switch { + case strings.Contains(path, "heads/main"): + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + case strings.Contains(path, "heads/develop"): w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"name": "repo", "default_branch": "develop"}`)) - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // Request for "refs/heads/main" -> 404 (doesn't exist) - // Request for "refs/heads/develop" (default branch) -> 200 - switch { - case strings.Contains(r.URL.Path, "heads/main"): - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - case strings.Contains(r.URL.Path, "heads/develop"): - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"ref": "refs/heads/develop", "object": {"sha": "abc123def456"}}`)) - default: - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - } - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposContentsByOwnerByRepoByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + _, _ = w.Write([]byte(`{"ref": "refs/heads/develop", "object": {"sha": "abc123def456", "type": "commit", "url": "https://api.github.com/repos/owner/repo/git/commits/abc123def456"}}`)) + default: + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + } + }, + "GET /repos/{owner}/{repo}/git/refs/{ref}": func(w http.ResponseWriter, r *http.Request) { + path := strings.ReplaceAll(r.URL.Path, "%2F", "/") + switch { + case strings.Contains(path, "heads/main"): + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + case strings.Contains(path, "heads/develop"): w.WriteHeader(http.StatusOK) - fileContent := &github.RepositoryContent{ - Name: github.Ptr("README.md"), - Path: github.Ptr("README.md"), - SHA: github.Ptr("abc123"), - Type: github.Ptr("file"), - } - contentBytes, _ := json.Marshal(fileContent) - _, _ = w.Write(contentBytes) - }), - ), - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoBySHAByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("Content-Type", "text/markdown") - _, _ = w.Write(mockRawContent) - }), - ), - ), + _, _ = w.Write([]byte(`{"ref": "refs/heads/develop", "object": {"sha": "abc123def456", "type": "commit", "url": "https://api.github.com/repos/owner/repo/git/commits/abc123def456"}}`)) + default: + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + } + }, + "GET /repos/{owner}/{repo}/git/refs/{ref:.*}": func(w http.ResponseWriter, r *http.Request) { + path := strings.ReplaceAll(r.URL.Path, "%2F", "/") + switch { + case strings.Contains(path, "heads/main"): + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + case strings.Contains(path, "heads/develop"): + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"ref": "refs/heads/develop", "object": {"sha": "abc123def456", "type": "commit", "url": "https://api.github.com/repos/owner/repo/git/commits/abc123def456"}}`)) + default: + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + } + }, + "GET /repos/owner/repo/git/ref/heads/main": func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }, + "GET /repos/owner/repo/git/ref/heads/develop": func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{"ref": "refs/heads/develop", "object": {"sha": "abc123def456", "type": "commit", "url": "https://api.github.com/repos/owner/repo/git/commits/abc123def456"}}`)) + }, + GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + fileContent := &github.RepositoryContent{ + Name: github.Ptr("README.md"), + Path: github.Ptr("README.md"), + SHA: github.Ptr("abc123"), + Type: github.Ptr("file"), + } + contentBytes, _ := json.Marshal(fileContent) + _, _ = w.Write(contentBytes) + }, + "GET /owner/repo/refs/heads/develop/README.md": func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/markdown") + _, _ = w.Write(mockRawContent) + }, + "GET /owner/repo/refs%2Fheads%2Fdevelop/README.md": func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/markdown") + _, _ = w.Write(mockRawContent) + }, + "GET /owner/repo/abc123def456/README.md": func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("Content-Type", "text/markdown") + _, _ = w.Write(mockRawContent) + }, + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -358,29 +321,17 @@ func Test_GetFileContents(t *testing.T) { }, { name: "content fetch fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write([]byte(`{"ref": "refs/heads/main", "object": {"sha": ""}}`)) - }), - ), - mock.WithRequestMatchHandler( - mock.GetReposContentsByOwnerByRepoByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - mock.WithRequestMatchHandler( - raw.GetRawReposContentsByOwnerByRepoByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, "{\"ref\": \"refs/heads/main\", \"object\": {\"sha\": \"\"}}"), + GetReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }, + GetRawReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }, + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -492,12 +443,9 @@ func Test_ForkRepository(t *testing.T) { }{ { name: "successful repository fork", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposForksByOwnerByRepo, - mockResponse(t, http.StatusAccepted, mockForkedRepo), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposForksByOwnerByRepo: mockResponse(t, http.StatusAccepted, mockForkedRepo), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -507,15 +455,12 @@ func Test_ForkRepository(t *testing.T) { }, { name: "repository fork fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PostReposForksByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusForbidden) - _, _ = w.Write([]byte(`{"message": "Forbidden"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostReposForksByOwnerByRepo: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusForbidden) + _, _ = w.Write([]byte(`{"message": "Forbidden"}`)) + }, + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -608,16 +553,11 @@ func Test_CreateBranch(t *testing.T) { }{ { name: "successful branch creation with from_branch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposGitRefByOwnerByRepoByRef, - mockSourceRef, - ), - mock.WithRequestMatch( - mock.PostReposGitRefsByOwnerByRepo, - mockCreatedRef, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockSourceRef), + "GET /repos/owner/repo/git/ref/heads/main": mockResponse(t, http.StatusOK, mockSourceRef), + PostReposGitRefsByOwnerByRepo: mockResponse(t, http.StatusCreated, mockCreatedRef), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -629,25 +569,17 @@ func Test_CreateBranch(t *testing.T) { }, { name: "successful branch creation with default branch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposByOwnerByRepo, - mockRepo, - ), - mock.WithRequestMatch( - mock.GetReposGitRefByOwnerByRepoByRef, - mockSourceRef, - ), - mock.WithRequestMatchHandler( - mock.PostReposGitRefsByOwnerByRepo, - expectRequestBody(t, map[string]interface{}{ - "ref": "refs/heads/new-feature", - "sha": "abc123def456", - }).andThen( - mockResponse(t, http.StatusCreated, mockCreatedRef), - ), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposByOwnerByRepo: mockResponse(t, http.StatusOK, mockRepo), + GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockSourceRef), + "GET /repos/owner/repo/git/ref/heads/main": mockResponse(t, http.StatusOK, mockSourceRef), + PostReposGitRefsByOwnerByRepo: expectRequestBody(t, map[string]interface{}{ + "ref": "refs/heads/new-feature", + "sha": "abc123def456", + }).andThen( + mockResponse(t, http.StatusCreated, mockCreatedRef), + ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -658,15 +590,12 @@ func Test_CreateBranch(t *testing.T) { }, { name: "fail to get repository", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Repository not found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposByOwnerByRepo: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Repository not found"}`)) + }, + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "nonexistent-repo", @@ -677,15 +606,12 @@ func Test_CreateBranch(t *testing.T) { }, { name: "fail to get reference", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Reference not found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Reference not found"}`)) + }, + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -697,19 +623,14 @@ func Test_CreateBranch(t *testing.T) { }, { name: "fail to create branch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposGitRefByOwnerByRepoByRef, - mockSourceRef, - ), - mock.WithRequestMatchHandler( - mock.PostReposGitRefsByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusUnprocessableEntity) - _, _ = w.Write([]byte(`{"message": "Reference already exists"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposGitRefByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockSourceRef), + "GET /repos/owner/repo/git/ref/heads/main": mockResponse(t, http.StatusOK, mockSourceRef), + PostReposGitRefsByOwnerByRepo: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnprocessableEntity) + _, _ = w.Write([]byte(`{"message": "Reference already exists"}`)) + }, + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -818,12 +739,9 @@ func Test_GetCommit(t *testing.T) { }{ { name: "successful commit fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposCommitsByOwnerByRepoByRef, - mockResponse(t, http.StatusOK, mockCommit), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposCommitsByOwnerByRepoByRef: mockResponse(t, http.StatusOK, mockCommit), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -834,15 +752,12 @@ func Test_GetCommit(t *testing.T) { }, { name: "commit fetch fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposCommitsByOwnerByRepoByRef, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposCommitsByOwnerByRepoByRef: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }, + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1000,12 +915,9 @@ func Test_ListCommits(t *testing.T) { }{ { name: "successful commits fetch with default params", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposCommitsByOwnerByRepo, - mockCommits, - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposCommitsByOwnerByRepo: mockResponse(t, http.StatusOK, mockCommits), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1015,19 +927,16 @@ func Test_ListCommits(t *testing.T) { }, { name: "successful commits fetch with branch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposCommitsByOwnerByRepo, - expectQueryParams(t, map[string]string{ - "author": "username", - "sha": "main", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockCommits), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposCommitsByOwnerByRepo: expectQueryParams(t, map[string]string{ + "author": "username", + "sha": "main", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockCommits), ), - ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1039,17 +948,14 @@ func Test_ListCommits(t *testing.T) { }, { name: "successful commits fetch with pagination", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposCommitsByOwnerByRepo, - expectQueryParams(t, map[string]string{ - "page": "2", - "per_page": "10", - }).andThen( - mockResponse(t, http.StatusOK, mockCommits), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposCommitsByOwnerByRepo: expectQueryParams(t, map[string]string{ + "page": "2", + "per_page": "10", + }).andThen( + mockResponse(t, http.StatusOK, mockCommits), ), - ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1061,15 +967,12 @@ func Test_ListCommits(t *testing.T) { }, { name: "commits fetch fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposCommitsByOwnerByRepo, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - _, _ = w.Write([]byte(`{"message": "Not Found"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetReposCommitsByOwnerByRepo: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + _, _ = w.Write([]byte(`{"message": "Not Found"}`)) + }, + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "nonexistent-repo", @@ -1184,18 +1087,22 @@ func Test_CreateOrUpdateFile(t *testing.T) { }{ { name: "successful file creation", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PutReposContentsByOwnerByRepoByPath, - expectRequestBody(t, map[string]interface{}{ - "message": "Add example file", - "content": "IyBFeGFtcGxlCgpUaGlzIGlzIGFuIGV4YW1wbGUgZmlsZS4=", // Base64 encoded content - "branch": "main", - }).andThen( - mockResponse(t, http.StatusOK, mockFileResponse), - ), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PutReposContentsByOwnerByRepoByPath: expectRequestBody(t, map[string]interface{}{ + "message": "Add example file", + "content": "IyBFeGFtcGxlCgpUaGlzIGlzIGFuIGV4YW1wbGUgZmlsZS4=", // Base64 encoded content + "branch": "main", + }).andThen( + mockResponse(t, http.StatusOK, mockFileResponse), + ), + "PUT /repos/{owner}/{repo}/contents/{path:.*}": expectRequestBody(t, map[string]interface{}{ + "message": "Add example file", + "content": "IyBFeGFtcGxlCgpUaGlzIGlzIGFuIGV4YW1wbGUgZmlsZS4=", // Base64 encoded content + "branch": "main", + }).andThen( + mockResponse(t, http.StatusOK, mockFileResponse), + ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1209,19 +1116,24 @@ func Test_CreateOrUpdateFile(t *testing.T) { }, { name: "successful file update with SHA", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PutReposContentsByOwnerByRepoByPath, - expectRequestBody(t, map[string]interface{}{ - "message": "Update example file", - "content": "IyBVcGRhdGVkIEV4YW1wbGUKClRoaXMgZmlsZSBoYXMgYmVlbiB1cGRhdGVkLg==", // Base64 encoded content - "branch": "main", - "sha": "abc123def456", - }).andThen( - mockResponse(t, http.StatusOK, mockFileResponse), - ), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PutReposContentsByOwnerByRepoByPath: expectRequestBody(t, map[string]interface{}{ + "message": "Update example file", + "content": "IyBVcGRhdGVkIEV4YW1wbGUKClRoaXMgZmlsZSBoYXMgYmVlbiB1cGRhdGVkLg==", // Base64 encoded content + "branch": "main", + "sha": "abc123def456", + }).andThen( + mockResponse(t, http.StatusOK, mockFileResponse), + ), + "PUT /repos/{owner}/{repo}/contents/{path:.*}": expectRequestBody(t, map[string]interface{}{ + "message": "Update example file", + "content": "IyBVcGRhdGVkIEV4YW1wbGUKClRoaXMgZmlsZSBoYXMgYmVlbiB1cGRhdGVkLg==", // Base64 encoded content + "branch": "main", + "sha": "abc123def456", + }).andThen( + mockResponse(t, http.StatusOK, mockFileResponse), + ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1236,15 +1148,16 @@ func Test_CreateOrUpdateFile(t *testing.T) { }, { name: "file creation fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PutReposContentsByOwnerByRepoByPath, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusUnprocessableEntity) - _, _ = w.Write([]byte(`{"message": "Invalid request"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PutReposContentsByOwnerByRepoByPath: func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnprocessableEntity) + _, _ = w.Write([]byte(`{"message": "Invalid request"}`)) + }, + "PUT /repos/{owner}/{repo}/contents/{path:.*}": func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusUnprocessableEntity) + _, _ = w.Write([]byte(`{"message": "Invalid request"}`)) + }, + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1258,35 +1171,42 @@ func Test_CreateOrUpdateFile(t *testing.T) { }, { name: "sha validation - current sha matches (304 Not Modified)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/repos/owner/repo/contents/docs/example.md", - Method: "HEAD", - }, - http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { - // Verify If-None-Match header is set correctly - ifNoneMatch := req.Header.Get("If-None-Match") - if ifNoneMatch == `"abc123def456"` { - w.WriteHeader(http.StatusNotModified) - } else { - w.WriteHeader(http.StatusOK) - w.Header().Set("ETag", `"abc123def456"`) - } - }), - ), - mock.WithRequestMatchHandler( - mock.PutReposContentsByOwnerByRepoByPath, - expectRequestBody(t, map[string]interface{}{ - "message": "Update example file", - "content": "IyBVcGRhdGVkIEV4YW1wbGUKClRoaXMgZmlsZSBoYXMgYmVlbiB1cGRhdGVkLg==", - "branch": "main", - "sha": "abc123def456", - }).andThen( - mockResponse(t, http.StatusOK, mockFileResponse), - ), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + "HEAD /repos/owner/repo/contents/docs/example.md": func(w http.ResponseWriter, req *http.Request) { + ifNoneMatch := req.Header.Get("If-None-Match") + if ifNoneMatch == `"abc123def456"` { + w.WriteHeader(http.StatusNotModified) + } else { + w.WriteHeader(http.StatusOK) + w.Header().Set("ETag", `"abc123def456"`) + } + }, + "HEAD /repos/{owner}/{repo}/contents/{path:.*}": func(w http.ResponseWriter, req *http.Request) { + ifNoneMatch := req.Header.Get("If-None-Match") + if ifNoneMatch == `"abc123def456"` { + w.WriteHeader(http.StatusNotModified) + } else { + w.WriteHeader(http.StatusOK) + w.Header().Set("ETag", `"abc123def456"`) + } + }, + PutReposContentsByOwnerByRepoByPath: expectRequestBody(t, map[string]interface{}{ + "message": "Update example file", + "content": "IyBVcGRhdGVkIEV4YW1wbGUKClRoaXMgZmlsZSBoYXMgYmVlbiB1cGRhdGVkLg==", + "branch": "main", + "sha": "abc123def456", + }).andThen( + mockResponse(t, http.StatusOK, mockFileResponse), + ), + "PUT /repos/{owner}/{repo}/contents/{path:.*}": expectRequestBody(t, map[string]interface{}{ + "message": "Update example file", + "content": "IyBVcGRhdGVkIEV4YW1wbGUKClRoaXMgZmlsZSBoYXMgYmVlbiB1cGRhdGVkLg==", + "branch": "main", + "sha": "abc123def456", + }).andThen( + mockResponse(t, http.StatusOK, mockFileResponse), + ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1301,19 +1221,16 @@ func Test_CreateOrUpdateFile(t *testing.T) { }, { name: "sha validation - stale sha detected (200 OK with different ETag)", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/repos/owner/repo/contents/docs/example.md", - Method: "HEAD", - }, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - // SHA doesn't match - return 200 with current ETag - w.Header().Set("ETag", `"newsha999888"`) - w.WriteHeader(http.StatusOK) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + "HEAD /repos/owner/repo/contents/docs/example.md": func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("ETag", `"newsha999888"`) + w.WriteHeader(http.StatusOK) + }, + "HEAD /repos/{owner}/{repo}/contents/{path:.*}": func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("ETag", `"newsha999888"`) + w.WriteHeader(http.StatusOK) + }, + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1328,28 +1245,30 @@ func Test_CreateOrUpdateFile(t *testing.T) { }, { name: "sha validation - file doesn't exist (404), proceed with create", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/repos/owner/repo/contents/docs/example.md", - Method: "HEAD", - }, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - }), - ), - mock.WithRequestMatchHandler( - mock.PutReposContentsByOwnerByRepoByPath, - expectRequestBody(t, map[string]interface{}{ - "message": "Create new file", - "content": "IyBOZXcgRmlsZQoKVGhpcyBpcyBhIG5ldyBmaWxlLg==", - "branch": "main", - "sha": "ignoredsha", // SHA is sent but GitHub API ignores it for new files - }).andThen( - mockResponse(t, http.StatusCreated, mockFileResponse), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + "HEAD /repos/owner/repo/contents/docs/example.md": func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + }, + PutReposContentsByOwnerByRepoByPath: expectRequestBody(t, map[string]interface{}{ + "message": "Create new file", + "content": "IyBOZXcgRmlsZQoKVGhpcyBpcyBhIG5ldyBmaWxlLg==", + "branch": "main", + "sha": "ignoredsha", // SHA is sent but GitHub API ignores it for new files + }).andThen( + mockResponse(t, http.StatusCreated, mockFileResponse), + ), + "HEAD /repos/{owner}/{repo}/contents/{path:.*}": func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + }, + "PUT /repos/{owner}/{repo}/contents/{path:.*}": expectRequestBody(t, map[string]interface{}{ + "message": "Create new file", + "content": "IyBOZXcgRmlsZQoKVGhpcyBpcyBhIG5ldyBmaWxlLg==", + "branch": "main", + "sha": "ignoredsha", // SHA is sent but GitHub API ignores it for new files + }).andThen( + mockResponse(t, http.StatusCreated, mockFileResponse), ), - ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1364,29 +1283,32 @@ func Test_CreateOrUpdateFile(t *testing.T) { }, { name: "no sha provided - file exists, returns warning", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/repos/owner/repo/contents/docs/example.md", - Method: "HEAD", - }, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.Header().Set("ETag", `"existing123"`) - w.WriteHeader(http.StatusOK) - }), - ), - mock.WithRequestMatchHandler( - mock.PutReposContentsByOwnerByRepoByPath, - expectRequestBody(t, map[string]interface{}{ - "message": "Update without SHA", - "content": "IyBVcGRhdGVkCgpVcGRhdGVkIHdpdGhvdXQgU0hBLg==", - "branch": "main", - "sha": "existing123", // SHA is automatically added from ETag - }).andThen( - mockResponse(t, http.StatusOK, mockFileResponse), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + "HEAD /repos/owner/repo/contents/docs/example.md": func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("ETag", `"existing123"`) + w.WriteHeader(http.StatusOK) + }, + PutReposContentsByOwnerByRepoByPath: expectRequestBody(t, map[string]interface{}{ + "message": "Update without SHA", + "content": "IyBVcGRhdGVkCgpVcGRhdGVkIHdpdGhvdXQgU0hBLg==", + "branch": "main", + "sha": "existing123", // SHA is automatically added from ETag + }).andThen( + mockResponse(t, http.StatusOK, mockFileResponse), + ), + "HEAD /repos/{owner}/{repo}/contents/{path:.*}": func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set("ETag", `"existing123"`) + w.WriteHeader(http.StatusOK) + }, + "PUT /repos/{owner}/{repo}/contents/{path:.*}": expectRequestBody(t, map[string]interface{}{ + "message": "Update without SHA", + "content": "IyBVcGRhdGVkCgpVcGRhdGVkIHdpdGhvdXQgU0hBLg==", + "branch": "main", + "sha": "existing123", // SHA is automatically added from ETag + }).andThen( + mockResponse(t, http.StatusOK, mockFileResponse), ), - ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1400,27 +1322,28 @@ func Test_CreateOrUpdateFile(t *testing.T) { }, { name: "no sha provided - file doesn't exist, no warning", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/repos/owner/repo/contents/docs/example.md", - Method: "HEAD", - }, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNotFound) - }), - ), - mock.WithRequestMatchHandler( - mock.PutReposContentsByOwnerByRepoByPath, - expectRequestBody(t, map[string]interface{}{ - "message": "Create new file", - "content": "IyBOZXcgRmlsZQoKQ3JlYXRlZCB3aXRob3V0IFNIQQ==", - "branch": "main", - }).andThen( - mockResponse(t, http.StatusCreated, mockFileResponse), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + "HEAD /repos/owner/repo/contents/docs/example.md": func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + }, + PutReposContentsByOwnerByRepoByPath: expectRequestBody(t, map[string]interface{}{ + "message": "Create new file", + "content": "IyBOZXcgRmlsZQoKQ3JlYXRlZCB3aXRob3V0IFNIQQ==", + "branch": "main", + }).andThen( + mockResponse(t, http.StatusCreated, mockFileResponse), + ), + "HEAD /repos/{owner}/{repo}/contents/{path:.*}": func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNotFound) + }, + "PUT /repos/{owner}/{repo}/contents/{path:.*}": expectRequestBody(t, map[string]interface{}{ + "message": "Create new file", + "content": "IyBOZXcgRmlsZQoKQ3JlYXRlZCB3aXRob3V0IFNIQQ==", + "branch": "main", + }).andThen( + mockResponse(t, http.StatusCreated, mockFileResponse), ), - ), + }), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -1527,12 +1450,9 @@ func Test_CreateRepository(t *testing.T) { }{ { name: "successful repository creation with all parameters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/user/repos", - Method: "POST", - }, + mockedClient: NewMockedHTTPClient( + WithRequestMatchHandler( + EndpointPattern("POST /user/repos"), expectRequestBody(t, map[string]interface{}{ "name": "test-repo", "description": "Test repository", @@ -1554,12 +1474,9 @@ func Test_CreateRepository(t *testing.T) { }, { name: "successful repository creation in organization", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/orgs/testorg/repos", - Method: "POST", - }, + mockedClient: NewMockedHTTPClient( + WithRequestMatchHandler( + EndpointPattern("POST /orgs/testorg/repos"), expectRequestBody(t, map[string]interface{}{ "name": "test-repo", "description": "Test repository", @@ -1582,12 +1499,9 @@ func Test_CreateRepository(t *testing.T) { }, { name: "successful repository creation with minimal parameters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/user/repos", - Method: "POST", - }, + mockedClient: NewMockedHTTPClient( + WithRequestMatchHandler( + EndpointPattern("POST /user/repos"), expectRequestBody(t, map[string]interface{}{ "name": "test-repo", "auto_init": false, @@ -1606,12 +1520,9 @@ func Test_CreateRepository(t *testing.T) { }, { name: "repository creation fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{ - Pattern: "/user/repos", - Method: "POST", - }, + mockedClient: NewMockedHTTPClient( + WithRequestMatchHandler( + EndpointPattern("POST /user/repos"), http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusUnprocessableEntity) _, _ = w.Write([]byte(`{"message": "Repository creation failed"}`)) @@ -1730,20 +1641,20 @@ func Test_PushFiles(t *testing.T) { }{ { name: "successful push of multiple files", - mockedClient: mock.NewMockedHTTPClient( + mockedClient: NewMockedHTTPClient( // Get branch reference - mock.WithRequestMatch( - mock.GetReposGitRefByOwnerByRepoByRef, + WithRequestMatch( + GetReposGitRefByOwnerByRepoByRef, mockRef, ), // Get commit - mock.WithRequestMatch( - mock.GetReposGitCommitsByOwnerByRepoByCommitSha, + WithRequestMatch( + GetReposGitCommitsByOwnerByRepoByCommitSHA, mockCommit, ), // Create tree - mock.WithRequestMatchHandler( - mock.PostReposGitTreesByOwnerByRepo, + WithRequestMatchHandler( + PostReposGitTreesByOwnerByRepo, expectRequestBody(t, map[string]interface{}{ "base_tree": "def456", "tree": []interface{}{ @@ -1765,8 +1676,8 @@ func Test_PushFiles(t *testing.T) { ), ), // Create commit - mock.WithRequestMatchHandler( - mock.PostReposGitCommitsByOwnerByRepo, + WithRequestMatchHandler( + PostReposGitCommitsByOwnerByRepo, expectRequestBody(t, map[string]interface{}{ "message": "Update multiple files", "tree": "ghi789", @@ -1776,8 +1687,8 @@ func Test_PushFiles(t *testing.T) { ), ), // Update reference - mock.WithRequestMatchHandler( - mock.PatchReposGitRefsByOwnerByRepoByRef, + WithRequestMatchHandler( + PatchReposGitRefsByOwnerByRepoByRef, expectRequestBody(t, map[string]interface{}{ "sha": "jkl012", "force": false, @@ -1807,7 +1718,7 @@ func Test_PushFiles(t *testing.T) { }, { name: "fails when files parameter is invalid", - mockedClient: mock.NewMockedHTTPClient( + mockedClient: NewMockedHTTPClient( // No requests expected ), requestArgs: map[string]interface{}{ @@ -1822,15 +1733,15 @@ func Test_PushFiles(t *testing.T) { }, { name: "fails when files contains object without path", - mockedClient: mock.NewMockedHTTPClient( + mockedClient: NewMockedHTTPClient( // Get branch reference - mock.WithRequestMatch( - mock.GetReposGitRefByOwnerByRepoByRef, + WithRequestMatch( + GetReposGitRefByOwnerByRepoByRef, mockRef, ), // Get commit - mock.WithRequestMatch( - mock.GetReposGitCommitsByOwnerByRepoByCommitSha, + WithRequestMatch( + GetReposGitCommitsByOwnerByRepoByCommitSHA, mockCommit, ), ), @@ -1850,15 +1761,15 @@ func Test_PushFiles(t *testing.T) { }, { name: "fails when files contains object without content", - mockedClient: mock.NewMockedHTTPClient( + mockedClient: NewMockedHTTPClient( // Get branch reference - mock.WithRequestMatch( - mock.GetReposGitRefByOwnerByRepoByRef, + WithRequestMatch( + GetReposGitRefByOwnerByRepoByRef, mockRef, ), // Get commit - mock.WithRequestMatch( - mock.GetReposGitCommitsByOwnerByRepoByCommitSha, + WithRequestMatch( + GetReposGitCommitsByOwnerByRepoByCommitSHA, mockCommit, ), ), @@ -1879,14 +1790,14 @@ func Test_PushFiles(t *testing.T) { }, { name: "fails to get branch reference", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, + mockedClient: NewMockedHTTPClient( + WithRequestMatchHandler( + GetReposGitRefByOwnerByRepoByRef, mockResponse(t, http.StatusNotFound, nil), ), // Mock Repositories.Get to fail when trying to create branch from default - mock.WithRequestMatchHandler( - mock.GetReposByOwnerByRepo, + WithRequestMatchHandler( + GetReposByOwnerByRepo, mockResponse(t, http.StatusNotFound, nil), ), ), @@ -1907,15 +1818,15 @@ func Test_PushFiles(t *testing.T) { }, { name: "fails to get base commit", - mockedClient: mock.NewMockedHTTPClient( + mockedClient: NewMockedHTTPClient( // Get branch reference - mock.WithRequestMatch( - mock.GetReposGitRefByOwnerByRepoByRef, + WithRequestMatch( + GetReposGitRefByOwnerByRepoByRef, mockRef, ), // Fail to get commit - mock.WithRequestMatchHandler( - mock.GetReposGitCommitsByOwnerByRepoByCommitSha, + WithRequestMatchHandler( + GetReposGitCommitsByOwnerByRepoByCommitSHA, mockResponse(t, http.StatusNotFound, nil), ), ), @@ -1936,20 +1847,20 @@ func Test_PushFiles(t *testing.T) { }, { name: "fails to create tree", - mockedClient: mock.NewMockedHTTPClient( + mockedClient: NewMockedHTTPClient( // Get branch reference - mock.WithRequestMatch( - mock.GetReposGitRefByOwnerByRepoByRef, + WithRequestMatch( + GetReposGitRefByOwnerByRepoByRef, mockRef, ), // Get commit - mock.WithRequestMatch( - mock.GetReposGitCommitsByOwnerByRepoByCommitSha, + WithRequestMatch( + GetReposGitCommitsByOwnerByRepoByCommitSHA, mockCommit, ), // Fail to create tree - mock.WithRequestMatchHandler( - mock.PostReposGitTreesByOwnerByRepo, + WithRequestMatchHandler( + PostReposGitTreesByOwnerByRepo, mockResponse(t, http.StatusInternalServerError, nil), ), ), @@ -1970,10 +1881,10 @@ func Test_PushFiles(t *testing.T) { }, { name: "successful push to empty repository", - mockedClient: mock.NewMockedHTTPClient( + mockedClient: NewMockedHTTPClient( // Get branch reference - first returns 409 for empty repo, second returns success after init - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, + WithRequestMatchHandler( + GetReposGitRefByOwnerByRepoByRef, func() http.HandlerFunc { callCount := 0 return func(w http.ResponseWriter, _ *http.Request) { @@ -1995,15 +1906,15 @@ func Test_PushFiles(t *testing.T) { }(), ), // Mock Repositories.Get to return default branch for initialization - mock.WithRequestMatch( - mock.GetReposByOwnerByRepo, + WithRequestMatch( + GetReposByOwnerByRepo, &github.Repository{ DefaultBranch: github.Ptr("main"), }, ), // Create initial file using Contents API - mock.WithRequestMatchHandler( - mock.PutReposContentsByOwnerByRepoByPath, + WithRequestMatchHandler( + PutReposContentsByOwnerByRepoByPath, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var body map[string]interface{} err := json.NewDecoder(r.Body).Decode(&body) @@ -2019,23 +1930,23 @@ func Test_PushFiles(t *testing.T) { }), ), // Get the commit after initialization - mock.WithRequestMatch( - mock.GetReposGitCommitsByOwnerByRepoByCommitSha, + WithRequestMatch( + GetReposGitCommitsByOwnerByRepoByCommitSHA, mockCommit, ), // Create tree - mock.WithRequestMatch( - mock.PostReposGitTreesByOwnerByRepo, + WithRequestMatch( + PostReposGitTreesByOwnerByRepo, mockTree, ), // Create commit - mock.WithRequestMatch( - mock.PostReposGitCommitsByOwnerByRepo, + WithRequestMatch( + PostReposGitCommitsByOwnerByRepo, mockNewCommit, ), // Update reference - mock.WithRequestMatch( - mock.PatchReposGitRefsByOwnerByRepoByRef, + WithRequestMatch( + PatchReposGitRefsByOwnerByRepoByRef, mockUpdatedRef, ), ), @@ -2056,10 +1967,10 @@ func Test_PushFiles(t *testing.T) { }, { name: "successful push multiple files to empty repository", - mockedClient: mock.NewMockedHTTPClient( + mockedClient: NewMockedHTTPClient( // Get branch reference - called twice: first for empty check, second after file creation - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, + WithRequestMatchHandler( + GetReposGitRefByOwnerByRepoByRef, func() http.HandlerFunc { callCount := 0 return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { @@ -2085,15 +1996,15 @@ func Test_PushFiles(t *testing.T) { }(), ), // Mock Repositories.Get to return default branch for initialization - mock.WithRequestMatch( - mock.GetReposByOwnerByRepo, + WithRequestMatch( + GetReposByOwnerByRepo, &github.Repository{ DefaultBranch: github.Ptr("main"), }, ), // Create initial empty README.md file using Contents API to initialize repo - mock.WithRequestMatchHandler( - mock.PutReposContentsByOwnerByRepoByPath, + WithRequestMatchHandler( + PutReposContentsByOwnerByRepoByPath, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { var body map[string]interface{} err := json.NewDecoder(r.Body).Decode(&body) @@ -2120,8 +2031,8 @@ func Test_PushFiles(t *testing.T) { }), ), // Get the commit to retrieve parent SHA - mock.WithRequestMatchHandler( - mock.GetReposGitCommitsByOwnerByRepoByCommitSha, + WithRequestMatchHandler( + GetReposGitCommitsByOwnerByRepoByCommitSHA, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) response := &github.Commit{ @@ -2135,8 +2046,8 @@ func Test_PushFiles(t *testing.T) { }), ), // Create tree with all user files - mock.WithRequestMatchHandler( - mock.PostReposGitTreesByOwnerByRepo, + WithRequestMatchHandler( + PostReposGitTreesByOwnerByRepo, expectRequestBody(t, map[string]interface{}{ "base_tree": "tree456", "tree": []interface{}{ @@ -2164,8 +2075,8 @@ func Test_PushFiles(t *testing.T) { ), ), // Create commit with all user files - mock.WithRequestMatchHandler( - mock.PostReposGitCommitsByOwnerByRepo, + WithRequestMatchHandler( + PostReposGitCommitsByOwnerByRepo, expectRequestBody(t, map[string]interface{}{ "message": "Initial project setup", "tree": "ghi789", @@ -2175,8 +2086,8 @@ func Test_PushFiles(t *testing.T) { ), ), // Update reference - mock.WithRequestMatchHandler( - mock.PatchReposGitRefsByOwnerByRepoByRef, + WithRequestMatchHandler( + PatchReposGitRefsByOwnerByRepoByRef, expectRequestBody(t, map[string]interface{}{ "sha": "jkl012", "force": false, @@ -2210,10 +2121,10 @@ func Test_PushFiles(t *testing.T) { }, { name: "fails to create initial file in empty repository", - mockedClient: mock.NewMockedHTTPClient( + mockedClient: NewMockedHTTPClient( // Get branch reference returns 409 Conflict for empty repo - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, + WithRequestMatchHandler( + GetReposGitRefByOwnerByRepoByRef, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusConflict) @@ -2224,15 +2135,15 @@ func Test_PushFiles(t *testing.T) { }), ), // Mock Repositories.Get to return default branch - mock.WithRequestMatch( - mock.GetReposByOwnerByRepo, + WithRequestMatch( + GetReposByOwnerByRepo, &github.Repository{ DefaultBranch: github.Ptr("main"), }, ), // Fail to create initial file using Contents API - mock.WithRequestMatchHandler( - mock.PutReposContentsByOwnerByRepoByPath, + WithRequestMatchHandler( + PutReposContentsByOwnerByRepoByPath, mockResponse(t, http.StatusInternalServerError, nil), ), ), @@ -2253,10 +2164,10 @@ func Test_PushFiles(t *testing.T) { }, { name: "fails to get reference after creating initial file in empty repository", - mockedClient: mock.NewMockedHTTPClient( + mockedClient: NewMockedHTTPClient( // Get branch reference - called twice - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, + WithRequestMatchHandler( + GetReposGitRefByOwnerByRepoByRef, func() http.HandlerFunc { callCount := 0 return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { @@ -2277,15 +2188,15 @@ func Test_PushFiles(t *testing.T) { }(), ), // Mock Repositories.Get to return default branch - mock.WithRequestMatch( - mock.GetReposByOwnerByRepo, + WithRequestMatch( + GetReposByOwnerByRepo, &github.Repository{ DefaultBranch: github.Ptr("main"), }, ), // Create initial file using Contents API - mock.WithRequestMatch( - mock.PutReposContentsByOwnerByRepoByPath, + WithRequestMatch( + PutReposContentsByOwnerByRepoByPath, &github.RepositoryContentResponse{ Content: &github.RepositoryContent{SHA: github.Ptr("readme123")}, Commit: github.Commit{SHA: github.Ptr("init456")}, @@ -2309,10 +2220,10 @@ func Test_PushFiles(t *testing.T) { }, { name: "fails to get commit in empty repository with multiple files", - mockedClient: mock.NewMockedHTTPClient( + mockedClient: NewMockedHTTPClient( // Get branch reference returns 409 Conflict for empty repo - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, + WithRequestMatchHandler( + GetReposGitRefByOwnerByRepoByRef, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusConflict) @@ -2323,23 +2234,23 @@ func Test_PushFiles(t *testing.T) { }), ), // Mock Repositories.Get to return default branch - mock.WithRequestMatch( - mock.GetReposByOwnerByRepo, + WithRequestMatch( + GetReposByOwnerByRepo, &github.Repository{ DefaultBranch: github.Ptr("main"), }, ), // Create initial file using Contents API - mock.WithRequestMatch( - mock.PutReposContentsByOwnerByRepoByPath, + WithRequestMatch( + PutReposContentsByOwnerByRepoByPath, &github.RepositoryContentResponse{ Content: &github.RepositoryContent{SHA: github.Ptr("readme123")}, Commit: github.Commit{SHA: github.Ptr("init456")}, }, ), // Fail to get commit - mock.WithRequestMatchHandler( - mock.GetReposGitCommitsByOwnerByRepoByCommitSha, + WithRequestMatchHandler( + GetReposGitCommitsByOwnerByRepoByCommitSHA, mockResponse(t, http.StatusInternalServerError, nil), ), ), @@ -2446,7 +2357,7 @@ func Test_ListBranches(t *testing.T) { tests := []struct { name string args map[string]interface{} - mockResponses []mock.MockBackendOption + mockResponses []MockBackendOption wantErr bool errContains string }{ @@ -2457,9 +2368,9 @@ func Test_ListBranches(t *testing.T) { "repo": "repo", "page": float64(2), }, - mockResponses: []mock.MockBackendOption{ - mock.WithRequestMatch( - mock.GetReposBranchesByOwnerByRepo, + mockResponses: []MockBackendOption{ + WithRequestMatch( + GetReposBranchesByOwnerByRepo, mockBranches, ), }, @@ -2470,7 +2381,7 @@ func Test_ListBranches(t *testing.T) { args: map[string]interface{}{ "repo": "repo", }, - mockResponses: []mock.MockBackendOption{}, + mockResponses: []MockBackendOption{}, wantErr: false, errContains: "missing required parameter: owner", }, @@ -2479,7 +2390,7 @@ func Test_ListBranches(t *testing.T) { args: map[string]interface{}{ "owner": "owner", }, - mockResponses: []mock.MockBackendOption{}, + mockResponses: []MockBackendOption{}, wantErr: false, errContains: "missing required parameter: repo", }, @@ -2488,7 +2399,7 @@ func Test_ListBranches(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { // Create mock client - mockClient := github.NewClient(mock.NewMockedHTTPClient(tt.mockResponses...)) + mockClient := github.NewClient(NewMockedHTTPClient(tt.mockResponses...)) deps := BaseDeps{ Client: mockClient, } @@ -2585,20 +2496,20 @@ func Test_DeleteFile(t *testing.T) { }{ { name: "successful file deletion using Git Data API", - mockedClient: mock.NewMockedHTTPClient( + mockedClient: NewMockedHTTPClient( // Get branch reference - mock.WithRequestMatch( - mock.GetReposGitRefByOwnerByRepoByRef, + WithRequestMatch( + GetReposGitRefByOwnerByRepoByRef, mockRef, ), // Get commit - mock.WithRequestMatch( - mock.GetReposGitCommitsByOwnerByRepoByCommitSha, + WithRequestMatch( + GetReposGitCommitsByOwnerByRepoByCommitSHA, mockCommit, ), // Create tree - mock.WithRequestMatchHandler( - mock.PostReposGitTreesByOwnerByRepo, + WithRequestMatchHandler( + PostReposGitTreesByOwnerByRepo, expectRequestBody(t, map[string]interface{}{ "base_tree": "def456", "tree": []interface{}{ @@ -2614,8 +2525,8 @@ func Test_DeleteFile(t *testing.T) { ), ), // Create commit - mock.WithRequestMatchHandler( - mock.PostReposGitCommitsByOwnerByRepo, + WithRequestMatchHandler( + PostReposGitCommitsByOwnerByRepo, expectRequestBody(t, map[string]interface{}{ "message": "Delete example file", "tree": "ghi789", @@ -2625,8 +2536,8 @@ func Test_DeleteFile(t *testing.T) { ), ), // Update reference - mock.WithRequestMatchHandler( - mock.PatchReposGitRefsByOwnerByRepoByRef, + WithRequestMatchHandler( + PatchReposGitRefsByOwnerByRepoByRef, expectRequestBody(t, map[string]interface{}{ "sha": "jkl012", "force": false, @@ -2652,9 +2563,9 @@ func Test_DeleteFile(t *testing.T) { }, { name: "file deletion fails - branch not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, + mockedClient: NewMockedHTTPClient( + WithRequestMatchHandler( + GetReposGitRefByOwnerByRepoByRef, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNotFound) _, _ = w.Write([]byte(`{"message": "Reference not found"}`)) @@ -2762,9 +2673,9 @@ func Test_ListTags(t *testing.T) { }{ { name: "successful tags list", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposTagsByOwnerByRepo, + mockedClient: NewMockedHTTPClient( + WithRequestMatchHandler( + GetReposTagsByOwnerByRepo, expectPath( t, "/repos/owner/repo/tags", @@ -2782,9 +2693,9 @@ func Test_ListTags(t *testing.T) { }, { name: "list tags fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposTagsByOwnerByRepo, + mockedClient: NewMockedHTTPClient( + WithRequestMatchHandler( + GetReposTagsByOwnerByRepo, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusInternalServerError) _, _ = w.Write([]byte(`{"message": "Internal Server Error"}`)) @@ -2888,9 +2799,9 @@ func Test_GetTag(t *testing.T) { }{ { name: "successful tag retrieval", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, + mockedClient: NewMockedHTTPClient( + WithRequestMatchHandler( + GetReposGitRefByOwnerByRepoByRef, expectPath( t, "/repos/owner/repo/git/ref/tags/v1.0.0", @@ -2898,8 +2809,8 @@ func Test_GetTag(t *testing.T) { mockResponse(t, http.StatusOK, mockTagRef), ), ), - mock.WithRequestMatchHandler( - mock.GetReposGitTagsByOwnerByRepoByTagSha, + WithRequestMatchHandler( + GetReposGitTagsByOwnerByRepoByTagSHA, expectPath( t, "/repos/owner/repo/git/tags/v1.0.0-tag-sha", @@ -2918,9 +2829,9 @@ func Test_GetTag(t *testing.T) { }, { name: "tag reference not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, + mockedClient: NewMockedHTTPClient( + WithRequestMatchHandler( + GetReposGitRefByOwnerByRepoByRef, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNotFound) _, _ = w.Write([]byte(`{"message": "Reference does not exist"}`)) @@ -2937,13 +2848,13 @@ func Test_GetTag(t *testing.T) { }, { name: "tag object not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposGitRefByOwnerByRepoByRef, + mockedClient: NewMockedHTTPClient( + WithRequestMatch( + GetReposGitRefByOwnerByRepoByRef, mockTagRef, ), - mock.WithRequestMatchHandler( - mock.GetReposGitTagsByOwnerByRepoByTagSha, + WithRequestMatchHandler( + GetReposGitTagsByOwnerByRepoByTagSHA, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNotFound) _, _ = w.Write([]byte(`{"message": "Tag object does not exist"}`)) @@ -3041,9 +2952,9 @@ func Test_ListReleases(t *testing.T) { }{ { name: "successful releases list", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposReleasesByOwnerByRepo, + mockedClient: NewMockedHTTPClient( + WithRequestMatch( + GetReposReleasesByOwnerByRepo, mockReleases, ), ), @@ -3056,9 +2967,9 @@ func Test_ListReleases(t *testing.T) { }, { name: "releases list fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposReleasesByOwnerByRepo, + mockedClient: NewMockedHTTPClient( + WithRequestMatchHandler( + GetReposReleasesByOwnerByRepo, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNotFound) _, _ = w.Write([]byte(`{"message": "Not Found"}`)) @@ -3132,9 +3043,9 @@ func Test_GetLatestRelease(t *testing.T) { }{ { name: "successful latest release fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposReleasesLatestByOwnerByRepo, + mockedClient: NewMockedHTTPClient( + WithRequestMatch( + GetReposReleasesLatestByOwnerByRepo, mockRelease, ), ), @@ -3147,9 +3058,9 @@ func Test_GetLatestRelease(t *testing.T) { }, { name: "latest release fetch fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposReleasesLatestByOwnerByRepo, + mockedClient: NewMockedHTTPClient( + WithRequestMatchHandler( + GetReposReleasesLatestByOwnerByRepo, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNotFound) _, _ = w.Write([]byte(`{"message": "Not Found"}`)) @@ -3229,9 +3140,9 @@ func Test_GetReleaseByTag(t *testing.T) { }{ { name: "successful release by tag fetch", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatch( - mock.GetReposReleasesTagsByOwnerByRepoByTag, + mockedClient: NewMockedHTTPClient( + WithRequestMatch( + GetReposReleasesTagsByOwnerByRepoByTag, mockRelease, ), ), @@ -3245,7 +3156,7 @@ func Test_GetReleaseByTag(t *testing.T) { }, { name: "missing owner parameter", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: NewMockedHTTPClient(), requestArgs: map[string]interface{}{ "repo": "repo", "tag": "v1.0.0", @@ -3255,7 +3166,7 @@ func Test_GetReleaseByTag(t *testing.T) { }, { name: "missing repo parameter", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: NewMockedHTTPClient(), requestArgs: map[string]interface{}{ "owner": "owner", "tag": "v1.0.0", @@ -3265,7 +3176,7 @@ func Test_GetReleaseByTag(t *testing.T) { }, { name: "missing tag parameter", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: NewMockedHTTPClient(), requestArgs: map[string]interface{}{ "owner": "owner", "repo": "repo", @@ -3275,9 +3186,9 @@ func Test_GetReleaseByTag(t *testing.T) { }, { name: "release by tag not found", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposReleasesTagsByOwnerByRepoByTag, + mockedClient: NewMockedHTTPClient( + WithRequestMatchHandler( + GetReposReleasesTagsByOwnerByRepoByTag, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNotFound) _, _ = w.Write([]byte(`{"message": "Not Found"}`)) @@ -3294,9 +3205,9 @@ func Test_GetReleaseByTag(t *testing.T) { }, { name: "server error", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposReleasesTagsByOwnerByRepoByTag, + mockedClient: NewMockedHTTPClient( + WithRequestMatchHandler( + GetReposReleasesTagsByOwnerByRepoByTag, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusInternalServerError) _, _ = w.Write([]byte(`{"message": "Internal Server Error"}`)) @@ -3543,7 +3454,7 @@ func Test_resolveGitReference(t *testing.T) { sha: "123sha456", mockSetup: func() *http.Client { // No API calls should be made when SHA is provided - return mock.NewMockedHTTPClient() + return NewMockedHTTPClient() }, expectedOutput: &raw.ContentOpts{ SHA: "123sha456", @@ -3555,16 +3466,16 @@ func Test_resolveGitReference(t *testing.T) { ref: "", sha: "", mockSetup: func() *http.Client { - return mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposByOwnerByRepo, + return NewMockedHTTPClient( + WithRequestMatchHandler( + GetReposByOwnerByRepo, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) _, _ = w.Write([]byte(`{"name": "repo", "default_branch": "main"}`)) }), ), - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, + WithRequestMatchHandler( + GetReposGitRefByOwnerByRepoByRef, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Contains(t, r.URL.Path, "/git/ref/heads/main") w.WriteHeader(http.StatusOK) @@ -3584,9 +3495,9 @@ func Test_resolveGitReference(t *testing.T) { ref: "refs/heads/feature-branch", sha: "", mockSetup: func() *http.Client { - return mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, + return NewMockedHTTPClient( + WithRequestMatchHandler( + GetReposGitRefByOwnerByRepoByRef, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Contains(t, r.URL.Path, "/git/ref/heads/feature-branch") w.WriteHeader(http.StatusOK) @@ -3606,9 +3517,9 @@ func Test_resolveGitReference(t *testing.T) { ref: "main", sha: "", mockSetup: func() *http.Client { - return mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, + return NewMockedHTTPClient( + WithRequestMatchHandler( + GetReposGitRefByOwnerByRepoByRef, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if strings.Contains(r.URL.Path, "/git/ref/heads/main") { w.WriteHeader(http.StatusOK) @@ -3632,9 +3543,9 @@ func Test_resolveGitReference(t *testing.T) { ref: "v1.0.0", sha: "", mockSetup: func() *http.Client { - return mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, + return NewMockedHTTPClient( + WithRequestMatchHandler( + GetReposGitRefByOwnerByRepoByRef, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch { case strings.Contains(r.URL.Path, "/git/ref/heads/v1.0.0"): @@ -3662,9 +3573,9 @@ func Test_resolveGitReference(t *testing.T) { ref: "heads/feature-branch", sha: "", mockSetup: func() *http.Client { - return mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, + return NewMockedHTTPClient( + WithRequestMatchHandler( + GetReposGitRefByOwnerByRepoByRef, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Contains(t, r.URL.Path, "/git/ref/heads/feature-branch") w.WriteHeader(http.StatusOK) @@ -3684,9 +3595,9 @@ func Test_resolveGitReference(t *testing.T) { ref: "tags/v1.0.0", sha: "", mockSetup: func() *http.Client { - return mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, + return NewMockedHTTPClient( + WithRequestMatchHandler( + GetReposGitRefByOwnerByRepoByRef, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Contains(t, r.URL.Path, "/git/ref/tags/v1.0.0") w.WriteHeader(http.StatusOK) @@ -3706,9 +3617,9 @@ func Test_resolveGitReference(t *testing.T) { ref: "nonexistent", sha: "", mockSetup: func() *http.Client { - return mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, + return NewMockedHTTPClient( + WithRequestMatchHandler( + GetReposGitRefByOwnerByRepoByRef, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { // Both branch and tag attempts should return 404 w.WriteHeader(http.StatusNotFound) @@ -3725,9 +3636,9 @@ func Test_resolveGitReference(t *testing.T) { ref: "refs/pull/123/head", sha: "", mockSetup: func() *http.Client { - return mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetReposGitRefByOwnerByRepoByRef, + return NewMockedHTTPClient( + WithRequestMatchHandler( + GetReposGitRefByOwnerByRepoByRef, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Contains(t, r.URL.Path, "/git/ref/pull/123/head") w.WriteHeader(http.StatusOK) @@ -3748,7 +3659,7 @@ func Test_resolveGitReference(t *testing.T) { sha: "", mockSetup: func() *http.Client { // No API calls should be made when ref looks like SHA - return mock.NewMockedHTTPClient() + return NewMockedHTTPClient() }, expectedOutput: &raw.ContentOpts{ SHA: "abc123def456abc123def456abc123def456abc1", @@ -3856,12 +3767,12 @@ func Test_ListStarredRepositories(t *testing.T) { }{ { name: "successful list for authenticated user", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetUserStarred, + mockedClient: NewMockedHTTPClient( + WithRequestMatchHandler( + GetUserStarred, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(mockStarredRepos)) + _, _ = w.Write(MustMarshal(mockStarredRepos)) }), ), ), @@ -3871,12 +3782,12 @@ func Test_ListStarredRepositories(t *testing.T) { }, { name: "successful list for specific user", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetUsersStarredByUsername, + mockedClient: NewMockedHTTPClient( + WithRequestMatchHandler( + GetUsersStarredByUsername, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(mockStarredRepos)) + _, _ = w.Write(MustMarshal(mockStarredRepos)) }), ), ), @@ -3888,9 +3799,9 @@ func Test_ListStarredRepositories(t *testing.T) { }, { name: "list fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetUserStarred, + mockedClient: NewMockedHTTPClient( + WithRequestMatchHandler( + GetUserStarred, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNotFound) _, _ = w.Write([]byte(`{"message": "Not Found"}`)) @@ -3970,9 +3881,9 @@ func Test_StarRepository(t *testing.T) { }{ { name: "successful star", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PutUserStarredByOwnerByRepo, + mockedClient: NewMockedHTTPClient( + WithRequestMatchHandler( + PutUserStarredByOwnerByRepo, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNoContent) }), @@ -3986,9 +3897,9 @@ func Test_StarRepository(t *testing.T) { }, { name: "star fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.PutUserStarredByOwnerByRepo, + mockedClient: NewMockedHTTPClient( + WithRequestMatchHandler( + PutUserStarredByOwnerByRepo, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNotFound) _, _ = w.Write([]byte(`{"message": "Not Found"}`)) @@ -4061,9 +3972,9 @@ func Test_UnstarRepository(t *testing.T) { }{ { name: "successful unstar", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.DeleteUserStarredByOwnerByRepo, + mockedClient: NewMockedHTTPClient( + WithRequestMatchHandler( + DeleteUserStarredByOwnerByRepo, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNoContent) }), @@ -4077,9 +3988,9 @@ func Test_UnstarRepository(t *testing.T) { }, { name: "unstar fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.DeleteUserStarredByOwnerByRepo, + mockedClient: NewMockedHTTPClient( + WithRequestMatchHandler( + DeleteUserStarredByOwnerByRepo, http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusNotFound) _, _ = w.Write([]byte(`{"message": "Not Found"}`)) diff --git a/pkg/github/search_test.go b/pkg/github/search_test.go index be1b26714..e15758c3e 100644 --- a/pkg/github/search_test.go +++ b/pkg/github/search_test.go @@ -10,7 +10,6 @@ import ( "github.com/github/github-mcp-server/pkg/translations" "github.com/google/go-github/v79/github" "github.com/google/jsonschema-go/jsonschema" - "github.com/migueleliasweb/go-github-mock/src/mock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -67,20 +66,17 @@ func Test_SearchRepositories(t *testing.T) { }{ { name: "successful repository search", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchRepositories, - expectQueryParams(t, map[string]string{ - "q": "golang test", - "sort": "stars", - "order": "desc", - "page": "2", - "per_page": "10", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchRepositories: expectQueryParams(t, map[string]string{ + "q": "golang test", + "sort": "stars", + "order": "desc", + "page": "2", + "per_page": "10", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "golang test", "sort": "stars", @@ -93,18 +89,15 @@ func Test_SearchRepositories(t *testing.T) { }, { name: "repository search with default pagination", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchRepositories, - expectQueryParams(t, map[string]string{ - "q": "golang test", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchRepositories: expectQueryParams(t, map[string]string{ + "q": "golang test", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "golang test", }, @@ -113,15 +106,12 @@ func Test_SearchRepositories(t *testing.T) { }, { name: "search fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchRepositories, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message": "Invalid query"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchRepositories: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"message": "Invalid query"}`)) + }), + }), requestArgs: map[string]interface{}{ "query": "invalid:query", }, @@ -194,18 +184,15 @@ func Test_SearchRepositories_FullOutput(t *testing.T) { }, } - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchRepositories, - expectQueryParams(t, map[string]string{ - "q": "golang test", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchRepositories: expectQueryParams(t, map[string]string{ + "q": "golang test", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ) + }) client := github.NewClient(mockedClient) serverTool := SearchRepositories(translations.NullTranslationHelper) @@ -291,20 +278,17 @@ func Test_SearchCode(t *testing.T) { }{ { name: "successful code search with all parameters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchCode, - expectQueryParams(t, map[string]string{ - "q": "fmt.Println language:go", - "sort": "indexed", - "order": "desc", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchCode: expectQueryParams(t, map[string]string{ + "q": "fmt.Println language:go", + "sort": "indexed", + "order": "desc", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "fmt.Println language:go", "sort": "indexed", @@ -317,18 +301,15 @@ func Test_SearchCode(t *testing.T) { }, { name: "code search with minimal parameters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchCode, - expectQueryParams(t, map[string]string{ - "q": "fmt.Println language:go", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchCode: expectQueryParams(t, map[string]string{ + "q": "fmt.Println language:go", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "fmt.Println language:go", }, @@ -337,15 +318,12 @@ func Test_SearchCode(t *testing.T) { }, { name: "search code fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchCode, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message": "Validation Failed"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchCode: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"message": "Validation Failed"}`)) + }), + }), requestArgs: map[string]interface{}{ "query": "invalid:query", }, @@ -451,20 +429,17 @@ func Test_SearchUsers(t *testing.T) { }{ { name: "successful users search with all parameters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchUsers, - expectQueryParams(t, map[string]string{ - "q": "type:user location:finland language:go", - "sort": "followers", - "order": "desc", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchUsers: expectQueryParams(t, map[string]string{ + "q": "type:user location:finland language:go", + "sort": "followers", + "order": "desc", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "location:finland language:go", "sort": "followers", @@ -477,18 +452,15 @@ func Test_SearchUsers(t *testing.T) { }, { name: "users search with minimal parameters", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchUsers, - expectQueryParams(t, map[string]string{ - "q": "type:user location:finland language:go", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchUsers: expectQueryParams(t, map[string]string{ + "q": "type:user location:finland language:go", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "location:finland language:go", }, @@ -497,18 +469,15 @@ func Test_SearchUsers(t *testing.T) { }, { name: "query with existing type:user filter - no duplication", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchUsers, - expectQueryParams(t, map[string]string{ - "q": "type:user location:seattle followers:>100", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchUsers: expectQueryParams(t, map[string]string{ + "q": "type:user location:seattle followers:>100", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "type:user location:seattle followers:>100", }, @@ -517,18 +486,15 @@ func Test_SearchUsers(t *testing.T) { }, { name: "complex query with existing type:user filter and OR operators", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchUsers, - expectQueryParams(t, map[string]string{ - "q": "type:user (location:seattle OR location:california) followers:>50", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchUsers: expectQueryParams(t, map[string]string{ + "q": "type:user (location:seattle OR location:california) followers:>50", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "type:user (location:seattle OR location:california) followers:>50", }, @@ -537,15 +503,12 @@ func Test_SearchUsers(t *testing.T) { }, { name: "search users fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchUsers, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message": "Validation Failed"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchUsers: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"message": "Validation Failed"}`)) + }), + }), requestArgs: map[string]interface{}{ "query": "invalid:query", }, @@ -652,18 +615,15 @@ func Test_SearchOrgs(t *testing.T) { }{ { name: "successful org search", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchUsers, - expectQueryParams(t, map[string]string{ - "q": "type:org github", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchUsers: expectQueryParams(t, map[string]string{ + "q": "type:org github", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "github", }, @@ -672,18 +632,15 @@ func Test_SearchOrgs(t *testing.T) { }, { name: "query with existing type:org filter - no duplication", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchUsers, - expectQueryParams(t, map[string]string{ - "q": "type:org location:california followers:>1000", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchUsers: expectQueryParams(t, map[string]string{ + "q": "type:org location:california followers:>1000", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "type:org location:california followers:>1000", }, @@ -692,18 +649,15 @@ func Test_SearchOrgs(t *testing.T) { }, { name: "complex query with existing type:org filter and OR operators", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchUsers, - expectQueryParams(t, map[string]string{ - "q": "type:org (location:seattle OR location:california OR location:newyork) repos:>10", - "page": "1", - "per_page": "30", - }).andThen( - mockResponse(t, http.StatusOK, mockSearchResult), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchUsers: expectQueryParams(t, map[string]string{ + "q": "type:org (location:seattle OR location:california OR location:newyork) repos:>10", + "page": "1", + "per_page": "30", + }).andThen( + mockResponse(t, http.StatusOK, mockSearchResult), ), - ), + }), requestArgs: map[string]interface{}{ "query": "type:org (location:seattle OR location:california OR location:newyork) repos:>10", }, @@ -712,15 +666,12 @@ func Test_SearchOrgs(t *testing.T) { }, { name: "org search fails", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.GetSearchUsers, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message": "Validation Failed"}`)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetSearchUsers: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"message": "Validation Failed"}`)) + }), + }), requestArgs: map[string]interface{}{ "query": "invalid:query", }, diff --git a/pkg/raw/raw_mock.go b/pkg/raw/raw_mock.go deleted file mode 100644 index 30c7759d3..000000000 --- a/pkg/raw/raw_mock.go +++ /dev/null @@ -1,20 +0,0 @@ -package raw - -import "github.com/migueleliasweb/go-github-mock/src/mock" - -var GetRawReposContentsByOwnerByRepoByPath mock.EndpointPattern = mock.EndpointPattern{ - Pattern: "/{owner}/{repo}/HEAD/{path:.*}", - Method: "GET", -} -var GetRawReposContentsByOwnerByRepoByBranchByPath mock.EndpointPattern = mock.EndpointPattern{ - Pattern: "/{owner}/{repo}/refs/heads/{branch}/{path:.*}", - Method: "GET", -} -var GetRawReposContentsByOwnerByRepoByTagByPath mock.EndpointPattern = mock.EndpointPattern{ - Pattern: "/{owner}/{repo}/refs/tags/{tag}/{path:.*}", - Method: "GET", -} -var GetRawReposContentsByOwnerByRepoBySHAByPath mock.EndpointPattern = mock.EndpointPattern{ - Pattern: "/{owner}/{repo}/{sha}/{path:.*}", - Method: "GET", -} diff --git a/third-party-licenses.darwin.md b/third-party-licenses.darwin.md index 5cb31cac4..fb4392fb9 100644 --- a/third-party-licenses.darwin.md +++ b/third-party-licenses.darwin.md @@ -18,17 +18,14 @@ The following packages are included for the amd64, arm64 architectures. - [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE)) - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE)) - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.4.0/LICENSE)) - - [github.com/google/go-github/v71/github](https://pkg.go.dev/github.com/google/go-github/v71/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v71.0.0/LICENSE)) - [github.com/google/go-github/v79/github](https://pkg.go.dev/github.com/google/go-github/v79/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v79.0.0/LICENSE)) - [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE)) - [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.4.2/LICENSE)) - [github.com/gorilla/css/scanner](https://pkg.go.dev/github.com/gorilla/css/scanner) ([BSD-3-Clause](https://github.com/gorilla/css/blob/v1.0.1/LICENSE)) - - [github.com/gorilla/mux](https://pkg.go.dev/github.com/gorilla/mux) ([BSD-3-Clause](https://github.com/gorilla/mux/blob/v1.8.0/LICENSE)) - [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v1.9.2/LICENSE)) - [github.com/josharian/intern](https://pkg.go.dev/github.com/josharian/intern) ([MIT](https://github.com/josharian/intern/blob/v1.0.0/license.md)) - [github.com/mailru/easyjson](https://pkg.go.dev/github.com/mailru/easyjson) ([MIT](https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE)) - [github.com/microcosm-cc/bluemonday](https://pkg.go.dev/github.com/microcosm-cc/bluemonday) ([BSD-3-Clause](https://github.com/microcosm-cc/bluemonday/blob/v1.0.27/LICENSE.md)) - - [github.com/migueleliasweb/go-github-mock/src/mock](https://pkg.go.dev/github.com/migueleliasweb/go-github-mock/src/mock) ([MIT](https://github.com/migueleliasweb/go-github-mock/blob/v1.3.0/LICENSE)) - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.2.0/LICENSE)) - [github.com/muesli/cache2go](https://pkg.go.dev/github.com/muesli/cache2go) ([BSD-3-Clause](https://github.com/muesli/cache2go/blob/518229cd8021/LICENSE.txt)) - [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.4/LICENSE)) @@ -49,7 +46,6 @@ The following packages are included for the amd64, arm64 architectures. - [golang.org/x/net/html](https://pkg.go.dev/golang.org/x/net/html) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.38.0:LICENSE)) - [golang.org/x/sys/unix](https://pkg.go.dev/golang.org/x/sys/unix) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.31.0:LICENSE)) - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.28.0:LICENSE)) - - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.5.0:LICENSE)) - [gopkg.in/yaml.v2](https://pkg.go.dev/gopkg.in/yaml.v2) ([Apache-2.0](https://github.com/go-yaml/yaml/blob/v2.4.0/LICENSE)) [github/github-mcp-server]: https://github.com/github/github-mcp-server diff --git a/third-party-licenses.linux.md b/third-party-licenses.linux.md index 8d0829a63..564f20dcb 100644 --- a/third-party-licenses.linux.md +++ b/third-party-licenses.linux.md @@ -18,17 +18,14 @@ The following packages are included for the 386, amd64, arm64 architectures. - [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE)) - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE)) - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.4.0/LICENSE)) - - [github.com/google/go-github/v71/github](https://pkg.go.dev/github.com/google/go-github/v71/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v71.0.0/LICENSE)) - [github.com/google/go-github/v79/github](https://pkg.go.dev/github.com/google/go-github/v79/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v79.0.0/LICENSE)) - [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE)) - [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.4.2/LICENSE)) - [github.com/gorilla/css/scanner](https://pkg.go.dev/github.com/gorilla/css/scanner) ([BSD-3-Clause](https://github.com/gorilla/css/blob/v1.0.1/LICENSE)) - - [github.com/gorilla/mux](https://pkg.go.dev/github.com/gorilla/mux) ([BSD-3-Clause](https://github.com/gorilla/mux/blob/v1.8.0/LICENSE)) - [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v1.9.2/LICENSE)) - [github.com/josharian/intern](https://pkg.go.dev/github.com/josharian/intern) ([MIT](https://github.com/josharian/intern/blob/v1.0.0/license.md)) - [github.com/mailru/easyjson](https://pkg.go.dev/github.com/mailru/easyjson) ([MIT](https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE)) - [github.com/microcosm-cc/bluemonday](https://pkg.go.dev/github.com/microcosm-cc/bluemonday) ([BSD-3-Clause](https://github.com/microcosm-cc/bluemonday/blob/v1.0.27/LICENSE.md)) - - [github.com/migueleliasweb/go-github-mock/src/mock](https://pkg.go.dev/github.com/migueleliasweb/go-github-mock/src/mock) ([MIT](https://github.com/migueleliasweb/go-github-mock/blob/v1.3.0/LICENSE)) - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.2.0/LICENSE)) - [github.com/muesli/cache2go](https://pkg.go.dev/github.com/muesli/cache2go) ([BSD-3-Clause](https://github.com/muesli/cache2go/blob/518229cd8021/LICENSE.txt)) - [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.4/LICENSE)) @@ -49,7 +46,6 @@ The following packages are included for the 386, amd64, arm64 architectures. - [golang.org/x/net/html](https://pkg.go.dev/golang.org/x/net/html) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.38.0:LICENSE)) - [golang.org/x/sys/unix](https://pkg.go.dev/golang.org/x/sys/unix) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.31.0:LICENSE)) - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.28.0:LICENSE)) - - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.5.0:LICENSE)) - [gopkg.in/yaml.v2](https://pkg.go.dev/gopkg.in/yaml.v2) ([Apache-2.0](https://github.com/go-yaml/yaml/blob/v2.4.0/LICENSE)) [github/github-mcp-server]: https://github.com/github/github-mcp-server diff --git a/third-party-licenses.windows.md b/third-party-licenses.windows.md index 79b925138..6b4dcfb97 100644 --- a/third-party-licenses.windows.md +++ b/third-party-licenses.windows.md @@ -18,18 +18,15 @@ The following packages are included for the 386, amd64, arm64 architectures. - [github.com/go-openapi/jsonpointer](https://pkg.go.dev/github.com/go-openapi/jsonpointer) ([Apache-2.0](https://github.com/go-openapi/jsonpointer/blob/v0.19.5/LICENSE)) - [github.com/go-openapi/swag](https://pkg.go.dev/github.com/go-openapi/swag) ([Apache-2.0](https://github.com/go-openapi/swag/blob/v0.21.1/LICENSE)) - [github.com/go-viper/mapstructure/v2](https://pkg.go.dev/github.com/go-viper/mapstructure/v2) ([MIT](https://github.com/go-viper/mapstructure/blob/v2.4.0/LICENSE)) - - [github.com/google/go-github/v71/github](https://pkg.go.dev/github.com/google/go-github/v71/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v71.0.0/LICENSE)) - [github.com/google/go-github/v79/github](https://pkg.go.dev/github.com/google/go-github/v79/github) ([BSD-3-Clause](https://github.com/google/go-github/blob/v79.0.0/LICENSE)) - [github.com/google/go-querystring/query](https://pkg.go.dev/github.com/google/go-querystring/query) ([BSD-3-Clause](https://github.com/google/go-querystring/blob/v1.1.0/LICENSE)) - [github.com/google/jsonschema-go/jsonschema](https://pkg.go.dev/github.com/google/jsonschema-go/jsonschema) ([MIT](https://github.com/google/jsonschema-go/blob/v0.4.2/LICENSE)) - [github.com/gorilla/css/scanner](https://pkg.go.dev/github.com/gorilla/css/scanner) ([BSD-3-Clause](https://github.com/gorilla/css/blob/v1.0.1/LICENSE)) - - [github.com/gorilla/mux](https://pkg.go.dev/github.com/gorilla/mux) ([BSD-3-Clause](https://github.com/gorilla/mux/blob/v1.8.0/LICENSE)) - [github.com/inconshreveable/mousetrap](https://pkg.go.dev/github.com/inconshreveable/mousetrap) ([Apache-2.0](https://github.com/inconshreveable/mousetrap/blob/v1.1.0/LICENSE)) - [github.com/josephburnett/jd/v2](https://pkg.go.dev/github.com/josephburnett/jd/v2) ([MIT](https://github.com/josephburnett/jd/blob/v1.9.2/LICENSE)) - [github.com/josharian/intern](https://pkg.go.dev/github.com/josharian/intern) ([MIT](https://github.com/josharian/intern/blob/v1.0.0/license.md)) - [github.com/mailru/easyjson](https://pkg.go.dev/github.com/mailru/easyjson) ([MIT](https://github.com/mailru/easyjson/blob/v0.7.7/LICENSE)) - [github.com/microcosm-cc/bluemonday](https://pkg.go.dev/github.com/microcosm-cc/bluemonday) ([BSD-3-Clause](https://github.com/microcosm-cc/bluemonday/blob/v1.0.27/LICENSE.md)) - - [github.com/migueleliasweb/go-github-mock/src/mock](https://pkg.go.dev/github.com/migueleliasweb/go-github-mock/src/mock) ([MIT](https://github.com/migueleliasweb/go-github-mock/blob/v1.3.0/LICENSE)) - [github.com/modelcontextprotocol/go-sdk](https://pkg.go.dev/github.com/modelcontextprotocol/go-sdk) ([MIT](https://github.com/modelcontextprotocol/go-sdk/blob/v1.2.0/LICENSE)) - [github.com/muesli/cache2go](https://pkg.go.dev/github.com/muesli/cache2go) ([BSD-3-Clause](https://github.com/muesli/cache2go/blob/518229cd8021/LICENSE.txt)) - [github.com/pelletier/go-toml/v2](https://pkg.go.dev/github.com/pelletier/go-toml/v2) ([MIT](https://github.com/pelletier/go-toml/blob/v2.2.4/LICENSE)) @@ -50,7 +47,6 @@ The following packages are included for the 386, amd64, arm64 architectures. - [golang.org/x/net/html](https://pkg.go.dev/golang.org/x/net/html) ([BSD-3-Clause](https://cs.opensource.google/go/x/net/+/v0.38.0:LICENSE)) - [golang.org/x/sys/windows](https://pkg.go.dev/golang.org/x/sys/windows) ([BSD-3-Clause](https://cs.opensource.google/go/x/sys/+/v0.31.0:LICENSE)) - [golang.org/x/text](https://pkg.go.dev/golang.org/x/text) ([BSD-3-Clause](https://cs.opensource.google/go/x/text/+/v0.28.0:LICENSE)) - - [golang.org/x/time/rate](https://pkg.go.dev/golang.org/x/time/rate) ([BSD-3-Clause](https://cs.opensource.google/go/x/time/+/v0.5.0:LICENSE)) - [gopkg.in/yaml.v2](https://pkg.go.dev/gopkg.in/yaml.v2) ([Apache-2.0](https://github.com/go-yaml/yaml/blob/v2.4.0/LICENSE)) [github/github-mcp-server]: https://github.com/github/github-mcp-server diff --git a/third-party/github.com/google/go-github/v71/github/LICENSE b/third-party/github.com/google/go-github/v71/github/LICENSE deleted file mode 100644 index 28b6486f0..000000000 --- a/third-party/github.com/google/go-github/v71/github/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2013 The go-github AUTHORS. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third-party/github.com/gorilla/mux/LICENSE b/third-party/github.com/gorilla/mux/LICENSE deleted file mode 100644 index 6903df638..000000000 --- a/third-party/github.com/gorilla/mux/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2012-2018 The Gorilla Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/third-party/github.com/migueleliasweb/go-github-mock/src/mock/LICENSE b/third-party/github.com/migueleliasweb/go-github-mock/src/mock/LICENSE deleted file mode 100644 index 86d42717d..000000000 --- a/third-party/github.com/migueleliasweb/go-github-mock/src/mock/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2021 Miguel Elias dos Santos - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/third-party/golang.org/x/time/rate/LICENSE b/third-party/golang.org/x/time/rate/LICENSE deleted file mode 100644 index 6a66aea5e..000000000 --- a/third-party/golang.org/x/time/rate/LICENSE +++ /dev/null @@ -1,27 +0,0 @@ -Copyright (c) 2009 The Go Authors. All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are -met: - - * Redistributions of source code must retain the above copyright -notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above -copyright notice, this list of conditions and the following disclaimer -in the documentation and/or other materials provided with the -distribution. - * Neither the name of Google Inc. nor the names of its -contributors may be used to endorse or promote products derived from -this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 66a01645f82e00c56a141e08903402f9d4e1ead7 Mon Sep 17 00:00:00 2001 From: Matt Holloway Date: Wed, 31 Dec 2025 10:26:30 +0000 Subject: [PATCH 041/138] initial projects consolidation --- README.md | 29 + pkg/github/__toolsnaps__/projects_get.snap | 59 ++ pkg/github/__toolsnaps__/projects_list.snap | 66 ++ pkg/github/__toolsnaps__/projects_write.snap | 60 ++ pkg/github/projects.go | 775 ++++++++++++++++++- pkg/github/projects_test.go | 694 +++++++++++++++++ pkg/github/tools.go | 5 + 7 files changed, 1679 insertions(+), 9 deletions(-) create mode 100644 pkg/github/__toolsnaps__/projects_get.snap create mode 100644 pkg/github/__toolsnaps__/projects_list.snap create mode 100644 pkg/github/__toolsnaps__/projects_write.snap diff --git a/README.md b/README.md index 1a0f6b1c4..dcf4756d2 100644 --- a/README.md +++ b/README.md @@ -1000,6 +1000,35 @@ The following sets of tools are available: - `per_page`: Results per page (max 50) (number, optional) - `query`: Filter projects by title text and open/closed state; permitted qualifiers: is:open, is:closed; examples: "roadmap is:open", "is:open feature planning". (string, optional) +- **projects_get** - Get details of GitHub Projects resources + - `field_id`: The field's ID. Required for 'get_project_field' method. (number, optional) + - `fields`: Specific list of field IDs to include in the response when getting a project item (e.g. ["102589", "985201", "169875"]). If not provided, only the title field is included. Only used for 'get_project_item' method. (string[], optional) + - `item_id`: The item's ID. Required for 'get_project_item' method. (number, optional) + - `method`: The method to execute (string, required) + - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) + - `owner_type`: Owner type (string, required) + - `project_number`: The project's number. (number, required) + +- **projects_list** - List GitHub Projects resources + - `after`: Forward pagination cursor from previous pageInfo.nextCursor. (string, optional) + - `before`: Backward pagination cursor from previous pageInfo.prevCursor (rare). (string, optional) + - `fields`: Field IDs to include when listing project items (e.g. ["102589", "985201"]). CRITICAL: Always provide to get field values. Without this, only titles returned. Only used for 'list_project_items' method. (string[], optional) + - `method`: The action to perform (string, required) + - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) + - `owner_type`: Owner type (string, required) + - `per_page`: Results per page (max 50) (number, optional) + - `project_number`: The project's number. Required for 'list_project_fields' and 'list_project_items' methods. (number, optional) + - `query`: Filter/query string. For list_projects: filter by title text and state (e.g. "roadmap is:open"). For list_project_items: advanced filtering using GitHub's project filtering syntax. (string, optional) + +- **projects_write** - Modify GitHub Project items + - `item_id`: The project item ID. Required for 'update_project_item' and 'delete_project_item' methods. For add_project_item, this is the numeric ID of the issue or pull request to add. (number, optional) + - `item_type`: The item's type, either issue or pull_request. Required for 'add_project_item' method. (string, optional) + - `method`: The method to execute (string, required) + - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) + - `owner_type`: Owner type (string, required) + - `project_number`: The project's number. (number, required) + - `updated_field`: Object consisting of the ID of the project field to update and the new value for the field. To clear the field, set value to null. Example: {"id": 123456, "value": "New Value"}. Required for 'update_project_item' method. (object, optional) + - **update_project_item** - Update project item - **Required OAuth Scopes**: `project` - `item_id`: The unique identifier of the project item. This is not the issue or pull request ID. (number, required) diff --git a/pkg/github/__toolsnaps__/projects_get.snap b/pkg/github/__toolsnaps__/projects_get.snap new file mode 100644 index 000000000..9758de0f2 --- /dev/null +++ b/pkg/github/__toolsnaps__/projects_get.snap @@ -0,0 +1,59 @@ +{ + "annotations": { + "readOnlyHint": true, + "title": "Get details of GitHub Projects resources" + }, + "description": "Get details about specific GitHub Projects resources.\nUse this tool to get details about individual projects, project fields, and project items by their unique IDs.\n", + "inputSchema": { + "type": "object", + "required": [ + "method", + "owner_type", + "owner", + "project_number" + ], + "properties": { + "field_id": { + "type": "number", + "description": "The field's ID. Required for 'get_project_field' method." + }, + "fields": { + "type": "array", + "description": "Specific list of field IDs to include in the response when getting a project item (e.g. [\"102589\", \"985201\", \"169875\"]). If not provided, only the title field is included. Only used for 'get_project_item' method.", + "items": { + "type": "string" + } + }, + "item_id": { + "type": "number", + "description": "The item's ID. Required for 'get_project_item' method." + }, + "method": { + "type": "string", + "description": "The method to execute", + "enum": [ + "get_project", + "get_project_field", + "get_project_item" + ] + }, + "owner": { + "type": "string", + "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive." + }, + "owner_type": { + "type": "string", + "description": "Owner type", + "enum": [ + "user", + "org" + ] + }, + "project_number": { + "type": "number", + "description": "The project's number." + } + } + }, + "name": "projects_get" +} \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/projects_list.snap b/pkg/github/__toolsnaps__/projects_list.snap new file mode 100644 index 000000000..7cc2e2df7 --- /dev/null +++ b/pkg/github/__toolsnaps__/projects_list.snap @@ -0,0 +1,66 @@ +{ + "annotations": { + "readOnlyHint": true, + "title": "List GitHub Projects resources" + }, + "description": "Tools for listing GitHub Projects resources.\nUse this tool to list projects for a user or organization, or list project fields and items for a specific project.\n", + "inputSchema": { + "type": "object", + "required": [ + "method", + "owner_type", + "owner" + ], + "properties": { + "after": { + "type": "string", + "description": "Forward pagination cursor from previous pageInfo.nextCursor." + }, + "before": { + "type": "string", + "description": "Backward pagination cursor from previous pageInfo.prevCursor (rare)." + }, + "fields": { + "type": "array", + "description": "Field IDs to include when listing project items (e.g. [\"102589\", \"985201\"]). CRITICAL: Always provide to get field values. Without this, only titles returned. Only used for 'list_project_items' method.", + "items": { + "type": "string" + } + }, + "method": { + "type": "string", + "description": "The action to perform", + "enum": [ + "list_projects", + "list_project_fields", + "list_project_items" + ] + }, + "owner": { + "type": "string", + "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive." + }, + "owner_type": { + "type": "string", + "description": "Owner type", + "enum": [ + "user", + "org" + ] + }, + "per_page": { + "type": "number", + "description": "Results per page (max 50)" + }, + "project_number": { + "type": "number", + "description": "The project's number. Required for 'list_project_fields' and 'list_project_items' methods." + }, + "query": { + "type": "string", + "description": "Filter/query string. For list_projects: filter by title text and state (e.g. \"roadmap is:open\"). For list_project_items: advanced filtering using GitHub's project filtering syntax." + } + } + }, + "name": "projects_list" +} \ No newline at end of file diff --git a/pkg/github/__toolsnaps__/projects_write.snap b/pkg/github/__toolsnaps__/projects_write.snap new file mode 100644 index 000000000..2224590c5 --- /dev/null +++ b/pkg/github/__toolsnaps__/projects_write.snap @@ -0,0 +1,60 @@ +{ + "annotations": { + "destructiveHint": true, + "title": "Modify GitHub Project items" + }, + "description": "Add, update, or delete project items in a GitHub Project.", + "inputSchema": { + "type": "object", + "required": [ + "method", + "owner_type", + "owner", + "project_number" + ], + "properties": { + "item_id": { + "type": "number", + "description": "The project item ID. Required for 'update_project_item' and 'delete_project_item' methods. For add_project_item, this is the numeric ID of the issue or pull request to add." + }, + "item_type": { + "type": "string", + "description": "The item's type, either issue or pull_request. Required for 'add_project_item' method.", + "enum": [ + "issue", + "pull_request" + ] + }, + "method": { + "type": "string", + "description": "The method to execute", + "enum": [ + "add_project_item", + "update_project_item", + "delete_project_item" + ] + }, + "owner": { + "type": "string", + "description": "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive." + }, + "owner_type": { + "type": "string", + "description": "Owner type", + "enum": [ + "user", + "org" + ] + }, + "project_number": { + "type": "number", + "description": "The project's number." + }, + "updated_field": { + "type": "object", + "description": "Object consisting of the ID of the project field to update and the new value for the field. To clear the field, set value to null. Example: {\"id\": 123456, \"value\": \"New Value\"}. Required for 'update_project_item' method." + } + } + }, + "name": "projects_write" +} \ No newline at end of file diff --git a/pkg/github/projects.go b/pkg/github/projects.go index 79cbbe680..9a5d25839 100644 --- a/pkg/github/projects.go +++ b/pkg/github/projects.go @@ -26,8 +26,25 @@ const ( MaxProjectsPerPage = 50 ) +// FeatureFlagConsolidatedProjects is the feature flag that disables individual project tools +// in favor of the consolidated project tools. +const FeatureFlagConsolidatedProjects = "remote_mcp_consolidated_projects" + +// Method constants for consolidated project tools +const ( + projectsMethodListProjects = "list_projects" + projectsMethodListProjectFields = "list_project_fields" + projectsMethodListProjectItems = "list_project_items" + projectsMethodGetProject = "get_project" + projectsMethodGetProjectField = "get_project_field" + projectsMethodGetProjectItem = "get_project_item" + projectsMethodAddProjectItem = "add_project_item" + projectsMethodUpdateProjectItem = "update_project_item" + projectsMethodDeleteProjectItem = "delete_project_item" +) + func ListProjects(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + tool := NewTool( ToolsetMetadataProjects, mcp.Tool{ Name: "list_projects", @@ -142,10 +159,12 @@ func ListProjects(t translations.TranslationHelperFunc) inventory.ServerTool { return utils.NewToolResultText(string(r)), nil, nil }, ) + tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects + return tool } func GetProject(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + tool := NewTool( ToolsetMetadataProjects, mcp.Tool{ Name: "get_project", @@ -231,10 +250,12 @@ func GetProject(t translations.TranslationHelperFunc) inventory.ServerTool { return utils.NewToolResultText(string(r)), nil, nil }, ) + tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects + return tool } func ListProjectFields(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + tool := NewTool( ToolsetMetadataProjects, mcp.Tool{ Name: "list_project_fields", @@ -338,10 +359,12 @@ func ListProjectFields(t translations.TranslationHelperFunc) inventory.ServerToo return utils.NewToolResultText(string(r)), nil, nil }, ) + tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects + return tool } func GetProjectField(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + tool := NewTool( ToolsetMetadataProjects, mcp.Tool{ Name: "get_project_field", @@ -431,10 +454,12 @@ func GetProjectField(t translations.TranslationHelperFunc) inventory.ServerTool return utils.NewToolResultText(string(r)), nil, nil }, ) + tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects + return tool } func ListProjectItems(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + tool := NewTool( ToolsetMetadataProjects, mcp.Tool{ Name: "list_project_items", @@ -568,10 +593,12 @@ func ListProjectItems(t translations.TranslationHelperFunc) inventory.ServerTool return utils.NewToolResultText(string(r)), nil, nil }, ) + tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects + return tool } func GetProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + tool := NewTool( ToolsetMetadataProjects, mcp.Tool{ Name: "get_project_item", @@ -675,10 +702,12 @@ func GetProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { return utils.NewToolResultText(string(r)), nil, nil }, ) + tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects + return tool } func AddProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + tool := NewTool( ToolsetMetadataProjects, mcp.Tool{ Name: "add_project_item", @@ -787,10 +816,12 @@ func AddProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { return utils.NewToolResultText(string(r)), nil, nil }, ) + tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects + return tool } func UpdateProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + tool := NewTool( ToolsetMetadataProjects, mcp.Tool{ Name: "update_project_item", @@ -900,10 +931,12 @@ func UpdateProjectItem(t translations.TranslationHelperFunc) inventory.ServerToo return utils.NewToolResultText(string(r)), nil, nil }, ) + tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects + return tool } func DeleteProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { - return NewTool( + tool := NewTool( ToolsetMetadataProjects, mcp.Tool{ Name: "delete_project_item", @@ -987,6 +1020,730 @@ func DeleteProjectItem(t translations.TranslationHelperFunc) inventory.ServerToo return utils.NewToolResultText("project item successfully deleted"), nil, nil }, ) + tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects + return tool +} + +// ProjectsList returns the tool and handler for listing GitHub Projects resources. +func ProjectsList(t translations.TranslationHelperFunc) inventory.ServerTool { + tool := NewTool( + ToolsetMetadataProjects, + mcp.Tool{ + Name: "projects_list", + Description: t("TOOL_PROJECTS_LIST_DESCRIPTION", + `Tools for listing GitHub Projects resources. +Use this tool to list projects for a user or organization, or list project fields and items for a specific project. +`), + Annotations: &mcp.ToolAnnotations{ + Title: t("TOOL_PROJECTS_LIST_USER_TITLE", "List GitHub Projects resources"), + ReadOnlyHint: true, + }, + InputSchema: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "method": { + Type: "string", + Description: "The action to perform", + Enum: []any{ + projectsMethodListProjects, + projectsMethodListProjectFields, + projectsMethodListProjectItems, + }, + }, + "owner_type": { + Type: "string", + Description: "Owner type", + Enum: []any{"user", "org"}, + }, + "owner": { + Type: "string", + Description: "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive.", + }, + "project_number": { + Type: "number", + Description: "The project's number. Required for 'list_project_fields' and 'list_project_items' methods.", + }, + "query": { + Type: "string", + Description: `Filter/query string. For list_projects: filter by title text and state (e.g. "roadmap is:open"). For list_project_items: advanced filtering using GitHub's project filtering syntax.`, + }, + "fields": { + Type: "array", + Description: "Field IDs to include when listing project items (e.g. [\"102589\", \"985201\"]). CRITICAL: Always provide to get field values. Without this, only titles returned. Only used for 'list_project_items' method.", + Items: &jsonschema.Schema{ + Type: "string", + }, + }, + "per_page": { + Type: "number", + Description: fmt.Sprintf("Results per page (max %d)", MaxProjectsPerPage), + }, + "after": { + Type: "string", + Description: "Forward pagination cursor from previous pageInfo.nextCursor.", + }, + "before": { + Type: "string", + Description: "Backward pagination cursor from previous pageInfo.prevCursor (rare).", + }, + }, + Required: []string{"method", "owner_type", "owner"}, + }, + }, + func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { + method, err := RequiredParam[string](args, "method") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + owner, err := RequiredParam[string](args, "owner") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + ownerType, err := RequiredParam[string](args, "owner_type") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + client, err := deps.GetClient(ctx) + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + switch method { + case projectsMethodListProjects: + return listProjects(ctx, client, args, owner, ownerType) + case projectsMethodListProjectFields: + return listProjectFields(ctx, client, args, owner, ownerType) + case projectsMethodListProjectItems: + return listProjectItems(ctx, client, args, owner, ownerType) + default: + return utils.NewToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil + } + }, + ) + tool.FeatureFlagEnable = FeatureFlagConsolidatedProjects + return tool +} + +// ProjectsGet returns the tool and handler for getting GitHub Projects resources. +func ProjectsGet(t translations.TranslationHelperFunc) inventory.ServerTool { + tool := NewTool( + ToolsetMetadataProjects, + mcp.Tool{ + Name: "projects_get", + Description: t("TOOL_PROJECTS_GET_DESCRIPTION", `Get details about specific GitHub Projects resources. +Use this tool to get details about individual projects, project fields, and project items by their unique IDs. +`), + Annotations: &mcp.ToolAnnotations{ + Title: t("TOOL_PROJECTS_GET_USER_TITLE", "Get details of GitHub Projects resources"), + ReadOnlyHint: true, + }, + InputSchema: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "method": { + Type: "string", + Description: "The method to execute", + Enum: []any{ + projectsMethodGetProject, + projectsMethodGetProjectField, + projectsMethodGetProjectItem, + }, + }, + "owner_type": { + Type: "string", + Description: "Owner type", + Enum: []any{"user", "org"}, + }, + "owner": { + Type: "string", + Description: "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive.", + }, + "project_number": { + Type: "number", + Description: "The project's number.", + }, + "field_id": { + Type: "number", + Description: "The field's ID. Required for 'get_project_field' method.", + }, + "item_id": { + Type: "number", + Description: "The item's ID. Required for 'get_project_item' method.", + }, + "fields": { + Type: "array", + Description: "Specific list of field IDs to include in the response when getting a project item (e.g. [\"102589\", \"985201\", \"169875\"]). If not provided, only the title field is included. Only used for 'get_project_item' method.", + Items: &jsonschema.Schema{ + Type: "string", + }, + }, + }, + Required: []string{"method", "owner_type", "owner", "project_number"}, + }, + }, + func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { + method, err := RequiredParam[string](args, "method") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + owner, err := RequiredParam[string](args, "owner") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + ownerType, err := RequiredParam[string](args, "owner_type") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + projectNumber, err := RequiredInt(args, "project_number") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + client, err := deps.GetClient(ctx) + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + switch method { + case projectsMethodGetProject: + return getProject(ctx, client, owner, ownerType, projectNumber) + case projectsMethodGetProjectField: + fieldID, err := RequiredBigInt(args, "field_id") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + return getProjectField(ctx, client, owner, ownerType, projectNumber, fieldID) + case projectsMethodGetProjectItem: + itemID, err := RequiredBigInt(args, "item_id") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + fields, err := OptionalBigIntArrayParam(args, "fields") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + return getProjectItem(ctx, client, owner, ownerType, projectNumber, itemID, fields) + default: + return utils.NewToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil + } + }, + ) + tool.FeatureFlagEnable = FeatureFlagConsolidatedProjects + return tool +} + +// ProjectsWrite returns the tool and handler for modifying GitHub Projects resources. +func ProjectsWrite(t translations.TranslationHelperFunc) inventory.ServerTool { + tool := NewTool( + ToolsetMetadataProjects, + mcp.Tool{ + Name: "projects_write", + Description: t("TOOL_PROJECTS_WRITE_DESCRIPTION", "Add, update, or delete project items in a GitHub Project."), + Annotations: &mcp.ToolAnnotations{ + Title: t("TOOL_PROJECTS_WRITE_USER_TITLE", "Modify GitHub Project items"), + ReadOnlyHint: false, + DestructiveHint: jsonschema.Ptr(true), + }, + InputSchema: &jsonschema.Schema{ + Type: "object", + Properties: map[string]*jsonschema.Schema{ + "method": { + Type: "string", + Description: "The method to execute", + Enum: []any{ + projectsMethodAddProjectItem, + projectsMethodUpdateProjectItem, + projectsMethodDeleteProjectItem, + }, + }, + "owner_type": { + Type: "string", + Description: "Owner type", + Enum: []any{"user", "org"}, + }, + "owner": { + Type: "string", + Description: "If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive.", + }, + "project_number": { + Type: "number", + Description: "The project's number.", + }, + "item_id": { + Type: "number", + Description: "The project item ID. Required for 'update_project_item' and 'delete_project_item' methods. For add_project_item, this is the numeric ID of the issue or pull request to add.", + }, + "item_type": { + Type: "string", + Description: "The item's type, either issue or pull_request. Required for 'add_project_item' method.", + Enum: []any{"issue", "pull_request"}, + }, + "updated_field": { + Type: "object", + Description: "Object consisting of the ID of the project field to update and the new value for the field. To clear the field, set value to null. Example: {\"id\": 123456, \"value\": \"New Value\"}. Required for 'update_project_item' method.", + }, + }, + Required: []string{"method", "owner_type", "owner", "project_number"}, + }, + }, + func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { + method, err := RequiredParam[string](args, "method") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + owner, err := RequiredParam[string](args, "owner") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + ownerType, err := RequiredParam[string](args, "owner_type") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + projectNumber, err := RequiredInt(args, "project_number") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + client, err := deps.GetClient(ctx) + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + switch method { + case projectsMethodAddProjectItem: + itemID, err := RequiredBigInt(args, "item_id") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + itemType, err := RequiredParam[string](args, "item_type") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + return addProjectItem(ctx, client, owner, ownerType, projectNumber, itemID, itemType) + case projectsMethodUpdateProjectItem: + itemID, err := RequiredBigInt(args, "item_id") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + rawUpdatedField, exists := args["updated_field"] + if !exists { + return utils.NewToolResultError("missing required parameter: updated_field"), nil, nil + } + fieldValue, ok := rawUpdatedField.(map[string]any) + if !ok || fieldValue == nil { + return utils.NewToolResultError("updated_field must be an object"), nil, nil + } + return updateProjectItem(ctx, client, owner, ownerType, projectNumber, itemID, fieldValue) + case projectsMethodDeleteProjectItem: + itemID, err := RequiredBigInt(args, "item_id") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + return deleteProjectItem(ctx, client, owner, ownerType, projectNumber, itemID) + default: + return utils.NewToolResultError(fmt.Sprintf("unknown method: %s", method)), nil, nil + } + }, + ) + tool.FeatureFlagEnable = FeatureFlagConsolidatedProjects + return tool +} + +// Helper functions for consolidated projects tools + +func listProjects(ctx context.Context, client *github.Client, args map[string]any, owner, ownerType string) (*mcp.CallToolResult, any, error) { + queryStr, err := OptionalParam[string](args, "query") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + pagination, err := extractPaginationOptionsFromArgs(args) + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + var resp *github.Response + var projects []*github.ProjectV2 + var queryPtr *string + + if queryStr != "" { + queryPtr = &queryStr + } + + minimalProjects := []MinimalProject{} + opts := &github.ListProjectsOptions{ + ListProjectsPaginationOptions: pagination, + Query: queryPtr, + } + + if ownerType == "org" { + projects, resp, err = client.Projects.ListOrganizationProjects(ctx, owner, opts) + } else { + projects, resp, err = client.Projects.ListUserProjects(ctx, owner, opts) + } + + if err != nil { + return ghErrors.NewGitHubAPIErrorResponse(ctx, + "failed to list projects", + resp, + err, + ), nil, nil + } + defer func() { _ = resp.Body.Close() }() + + for _, project := range projects { + minimalProjects = append(minimalProjects, *convertToMinimalProject(project)) + } + + response := map[string]any{ + "projects": minimalProjects, + "pageInfo": buildPageInfo(resp), + } + + r, err := json.Marshal(response) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal response: %w", err) + } + + return utils.NewToolResultText(string(r)), nil, nil +} + +func listProjectFields(ctx context.Context, client *github.Client, args map[string]any, owner, ownerType string) (*mcp.CallToolResult, any, error) { + projectNumber, err := RequiredInt(args, "project_number") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + pagination, err := extractPaginationOptionsFromArgs(args) + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + var resp *github.Response + var projectFields []*github.ProjectV2Field + + opts := &github.ListProjectsOptions{ + ListProjectsPaginationOptions: pagination, + } + + if ownerType == "org" { + projectFields, resp, err = client.Projects.ListOrganizationProjectFields(ctx, owner, projectNumber, opts) + } else { + projectFields, resp, err = client.Projects.ListUserProjectFields(ctx, owner, projectNumber, opts) + } + + if err != nil { + return ghErrors.NewGitHubAPIErrorResponse(ctx, + "failed to list project fields", + resp, + err, + ), nil, nil + } + defer func() { _ = resp.Body.Close() }() + + response := map[string]any{ + "fields": projectFields, + "pageInfo": buildPageInfo(resp), + } + + r, err := json.Marshal(response) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal response: %w", err) + } + + return utils.NewToolResultText(string(r)), nil, nil +} + +func listProjectItems(ctx context.Context, client *github.Client, args map[string]any, owner, ownerType string) (*mcp.CallToolResult, any, error) { + projectNumber, err := RequiredInt(args, "project_number") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + queryStr, err := OptionalParam[string](args, "query") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + fields, err := OptionalBigIntArrayParam(args, "fields") + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + pagination, err := extractPaginationOptionsFromArgs(args) + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + var resp *github.Response + var projectItems []*github.ProjectV2Item + var queryPtr *string + + if queryStr != "" { + queryPtr = &queryStr + } + + opts := &github.ListProjectItemsOptions{ + Fields: fields, + ListProjectsOptions: github.ListProjectsOptions{ + ListProjectsPaginationOptions: pagination, + Query: queryPtr, + }, + } + + if ownerType == "org" { + projectItems, resp, err = client.Projects.ListOrganizationProjectItems(ctx, owner, projectNumber, opts) + } else { + projectItems, resp, err = client.Projects.ListUserProjectItems(ctx, owner, projectNumber, opts) + } + + if err != nil { + return ghErrors.NewGitHubAPIErrorResponse(ctx, + ProjectListFailedError, + resp, + err, + ), nil, nil + } + defer func() { _ = resp.Body.Close() }() + + response := map[string]any{ + "items": projectItems, + "pageInfo": buildPageInfo(resp), + } + + r, err := json.Marshal(response) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal response: %w", err) + } + + return utils.NewToolResultText(string(r)), nil, nil +} + +func getProject(ctx context.Context, client *github.Client, owner, ownerType string, projectNumber int) (*mcp.CallToolResult, any, error) { + var resp *github.Response + var project *github.ProjectV2 + var err error + + if ownerType == "org" { + project, resp, err = client.Projects.GetOrganizationProject(ctx, owner, projectNumber) + } else { + project, resp, err = client.Projects.GetUserProject(ctx, owner, projectNumber) + } + if err != nil { + return ghErrors.NewGitHubAPIErrorResponse(ctx, + "failed to get project", + resp, + err, + ), nil, nil + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, nil, fmt.Errorf("failed to read response body: %w", err) + } + return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to get project", resp, body), nil, nil + } + + minimalProject := convertToMinimalProject(project) + r, err := json.Marshal(minimalProject) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal response: %w", err) + } + + return utils.NewToolResultText(string(r)), nil, nil +} + +func getProjectField(ctx context.Context, client *github.Client, owner, ownerType string, projectNumber int, fieldID int64) (*mcp.CallToolResult, any, error) { + var resp *github.Response + var projectField *github.ProjectV2Field + var err error + + if ownerType == "org" { + projectField, resp, err = client.Projects.GetOrganizationProjectField(ctx, owner, projectNumber, fieldID) + } else { + projectField, resp, err = client.Projects.GetUserProjectField(ctx, owner, projectNumber, fieldID) + } + + if err != nil { + return ghErrors.NewGitHubAPIErrorResponse(ctx, + "failed to get project field", + resp, + err, + ), nil, nil + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, nil, fmt.Errorf("failed to read response body: %w", err) + } + return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to get project field", resp, body), nil, nil + } + r, err := json.Marshal(projectField) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal response: %w", err) + } + + return utils.NewToolResultText(string(r)), nil, nil +} + +func getProjectItem(ctx context.Context, client *github.Client, owner, ownerType string, projectNumber int, itemID int64, fields []int64) (*mcp.CallToolResult, any, error) { + var resp *github.Response + var projectItem *github.ProjectV2Item + var opts *github.GetProjectItemOptions + var err error + + if len(fields) > 0 { + opts = &github.GetProjectItemOptions{ + Fields: fields, + } + } + + if ownerType == "org" { + projectItem, resp, err = client.Projects.GetOrganizationProjectItem(ctx, owner, projectNumber, itemID, opts) + } else { + projectItem, resp, err = client.Projects.GetUserProjectItem(ctx, owner, projectNumber, itemID, opts) + } + + if err != nil { + return ghErrors.NewGitHubAPIErrorResponse(ctx, + "failed to get project item", + resp, + err, + ), nil, nil + } + defer func() { _ = resp.Body.Close() }() + + r, err := json.Marshal(projectItem) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal response: %w", err) + } + + return utils.NewToolResultText(string(r)), nil, nil +} + +func addProjectItem(ctx context.Context, client *github.Client, owner, ownerType string, projectNumber int, itemID int64, itemType string) (*mcp.CallToolResult, any, error) { + if itemType != "issue" && itemType != "pull_request" { + return utils.NewToolResultError("item_type must be either 'issue' or 'pull_request'"), nil, nil + } + + newItem := &github.AddProjectItemOptions{ + ID: itemID, + Type: toNewProjectType(itemType), + } + + var resp *github.Response + var addedItem *github.ProjectV2Item + var err error + + if ownerType == "org" { + addedItem, resp, err = client.Projects.AddOrganizationProjectItem(ctx, owner, projectNumber, newItem) + } else { + addedItem, resp, err = client.Projects.AddUserProjectItem(ctx, owner, projectNumber, newItem) + } + + if err != nil { + return ghErrors.NewGitHubAPIErrorResponse(ctx, + ProjectAddFailedError, + resp, + err, + ), nil, nil + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusCreated { + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, nil, fmt.Errorf("failed to read response body: %w", err) + } + return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, ProjectAddFailedError, resp, body), nil, nil + } + r, err := json.Marshal(addedItem) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal response: %w", err) + } + + return utils.NewToolResultText(string(r)), nil, nil +} + +func updateProjectItem(ctx context.Context, client *github.Client, owner, ownerType string, projectNumber int, itemID int64, fieldValue map[string]any) (*mcp.CallToolResult, any, error) { + updatePayload, err := buildUpdateProjectItem(fieldValue) + if err != nil { + return utils.NewToolResultError(err.Error()), nil, nil + } + + var resp *github.Response + var updatedItem *github.ProjectV2Item + + if ownerType == "org" { + updatedItem, resp, err = client.Projects.UpdateOrganizationProjectItem(ctx, owner, projectNumber, itemID, updatePayload) + } else { + updatedItem, resp, err = client.Projects.UpdateUserProjectItem(ctx, owner, projectNumber, itemID, updatePayload) + } + + if err != nil { + return ghErrors.NewGitHubAPIErrorResponse(ctx, + ProjectUpdateFailedError, + resp, + err, + ), nil, nil + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusOK { + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, nil, fmt.Errorf("failed to read response body: %w", err) + } + return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, ProjectUpdateFailedError, resp, body), nil, nil + } + r, err := json.Marshal(updatedItem) + if err != nil { + return nil, nil, fmt.Errorf("failed to marshal response: %w", err) + } + + return utils.NewToolResultText(string(r)), nil, nil +} + +func deleteProjectItem(ctx context.Context, client *github.Client, owner, ownerType string, projectNumber int, itemID int64) (*mcp.CallToolResult, any, error) { + var resp *github.Response + var err error + + if ownerType == "org" { + resp, err = client.Projects.DeleteOrganizationProjectItem(ctx, owner, projectNumber, itemID) + } else { + resp, err = client.Projects.DeleteUserProjectItem(ctx, owner, projectNumber, itemID) + } + + if err != nil { + return ghErrors.NewGitHubAPIErrorResponse(ctx, + ProjectDeleteFailedError, + resp, + err, + ), nil, nil + } + defer func() { _ = resp.Body.Close() }() + + if resp.StatusCode != http.StatusNoContent { + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, nil, fmt.Errorf("failed to read response body: %w", err) + } + return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, ProjectDeleteFailedError, resp, body), nil, nil + } + return utils.NewToolResultText("project item successfully deleted"), nil, nil } type pageInfo struct { diff --git a/pkg/github/projects_test.go b/pkg/github/projects_test.go index e3a50af29..96bd4d128 100644 --- a/pkg/github/projects_test.go +++ b/pkg/github/projects_test.go @@ -1529,3 +1529,697 @@ func Test_DeleteProjectItem(t *testing.T) { }) } } + +// Tests for consolidated project tools + +func Test_ProjectsList(t *testing.T) { + // Verify tool definition once + toolDef := ProjectsList(translations.NullTranslationHelper) + require.NoError(t, toolsnaps.Test(toolDef.Tool.Name, toolDef.Tool)) + + assert.Equal(t, "projects_list", toolDef.Tool.Name) + assert.NotEmpty(t, toolDef.Tool.Description) + inputSchema := toolDef.Tool.InputSchema.(*jsonschema.Schema) + assert.Contains(t, inputSchema.Properties, "method") + assert.Contains(t, inputSchema.Properties, "owner") + assert.Contains(t, inputSchema.Properties, "owner_type") + assert.Contains(t, inputSchema.Properties, "project_number") + assert.Contains(t, inputSchema.Properties, "query") + assert.Contains(t, inputSchema.Properties, "fields") + assert.ElementsMatch(t, inputSchema.Required, []string{"method", "owner_type", "owner"}) +} + +func Test_ProjectsList_ListProjects(t *testing.T) { + toolDef := ProjectsList(translations.NullTranslationHelper) + + orgProjects := []map[string]any{{"id": 1, "node_id": "NODE1", "title": "Org Project"}} + userProjects := []map[string]any{{"id": 2, "node_id": "NODE2", "title": "User Project"}} + + tests := []struct { + name string + mockedClient *http.Client + requestArgs map[string]any + expectError bool + expectedErrMsg string + expectedLength int + }{ + { + name: "success organization", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2", Method: http.MethodGet}, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write(mock.MustMarshal(orgProjects)) + }), + ), + ), + requestArgs: map[string]any{ + "method": "list_projects", + "owner": "octo-org", + "owner_type": "org", + }, + expectError: false, + expectedLength: 1, + }, + { + name: "success user", + mockedClient: mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.EndpointPattern{Pattern: "/users/{username}/projectsV2", Method: http.MethodGet}, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write(mock.MustMarshal(userProjects)) + }), + ), + ), + requestArgs: map[string]any{ + "method": "list_projects", + "owner": "octocat", + "owner_type": "user", + }, + expectError: false, + expectedLength: 1, + }, + { + name: "missing required parameter method", + mockedClient: mock.NewMockedHTTPClient(), + requestArgs: map[string]any{ + "owner": "octo-org", + "owner_type": "org", + }, + expectError: true, + expectedErrMsg: "missing required parameter: method", + }, + { + name: "unknown method", + mockedClient: mock.NewMockedHTTPClient(), + requestArgs: map[string]any{ + "method": "unknown_method", + "owner": "octo-org", + "owner_type": "org", + }, + expectError: true, + expectedErrMsg: "unknown method: unknown_method", + }, + } + + for _, tc := range tests { + t.Run(tc.name, func(t *testing.T) { + client := gh.NewClient(tc.mockedClient) + deps := BaseDeps{ + Client: client, + } + handler := toolDef.Handler(deps) + request := createMCPRequest(tc.requestArgs) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + + require.NoError(t, err) + require.Equal(t, tc.expectError, result.IsError) + + textContent := getTextResult(t, result) + + if tc.expectError { + if tc.expectedErrMsg != "" { + assert.Contains(t, textContent.Text, tc.expectedErrMsg) + } + return + } + + var response map[string]any + err = json.Unmarshal([]byte(textContent.Text), &response) + require.NoError(t, err) + projects, ok := response["projects"].([]interface{}) + require.True(t, ok) + assert.Equal(t, tc.expectedLength, len(projects)) + }) + } +} + +func Test_ProjectsList_ListProjectFields(t *testing.T) { + toolDef := ProjectsList(translations.NullTranslationHelper) + + fields := []map[string]any{{"id": 101, "name": "Status", "data_type": "single_select"}} + + t.Run("success organization", func(t *testing.T) { + mockedClient := mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/1/fields", Method: http.MethodGet}, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write(mock.MustMarshal(fields)) + }), + ), + ) + + client := gh.NewClient(mockedClient) + deps := BaseDeps{ + Client: client, + } + handler := toolDef.Handler(deps) + request := createMCPRequest(map[string]any{ + "method": "list_project_fields", + "owner": "octo-org", + "owner_type": "org", + "project_number": float64(1), + }) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + + require.NoError(t, err) + require.False(t, result.IsError) + + textContent := getTextResult(t, result) + var response map[string]any + err = json.Unmarshal([]byte(textContent.Text), &response) + require.NoError(t, err) + fieldsList, ok := response["fields"].([]interface{}) + require.True(t, ok) + assert.Equal(t, 1, len(fieldsList)) + }) + + t.Run("missing project_number", func(t *testing.T) { + mockedClient := mock.NewMockedHTTPClient() + client := gh.NewClient(mockedClient) + deps := BaseDeps{ + Client: client, + } + handler := toolDef.Handler(deps) + request := createMCPRequest(map[string]any{ + "method": "list_project_fields", + "owner": "octo-org", + "owner_type": "org", + }) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + + require.NoError(t, err) + require.True(t, result.IsError) + textContent := getTextResult(t, result) + assert.Contains(t, textContent.Text, "missing required parameter: project_number") + }) +} + +func Test_ProjectsList_ListProjectItems(t *testing.T) { + toolDef := ProjectsList(translations.NullTranslationHelper) + + items := []map[string]any{{"id": 1001, "archived_at": nil, "content": map[string]any{"title": "Issue 1"}}} + + t.Run("success organization", func(t *testing.T) { + mockedClient := mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/1/items", Method: http.MethodGet}, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write(mock.MustMarshal(items)) + }), + ), + ) + + client := gh.NewClient(mockedClient) + deps := BaseDeps{ + Client: client, + } + handler := toolDef.Handler(deps) + request := createMCPRequest(map[string]any{ + "method": "list_project_items", + "owner": "octo-org", + "owner_type": "org", + "project_number": float64(1), + }) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + + require.NoError(t, err) + require.False(t, result.IsError) + + textContent := getTextResult(t, result) + var response map[string]any + err = json.Unmarshal([]byte(textContent.Text), &response) + require.NoError(t, err) + itemsList, ok := response["items"].([]interface{}) + require.True(t, ok) + assert.Equal(t, 1, len(itemsList)) + }) +} + +func Test_ProjectsGet(t *testing.T) { + // Verify tool definition once + toolDef := ProjectsGet(translations.NullTranslationHelper) + require.NoError(t, toolsnaps.Test(toolDef.Tool.Name, toolDef.Tool)) + + assert.Equal(t, "projects_get", toolDef.Tool.Name) + assert.NotEmpty(t, toolDef.Tool.Description) + inputSchema := toolDef.Tool.InputSchema.(*jsonschema.Schema) + assert.Contains(t, inputSchema.Properties, "method") + assert.Contains(t, inputSchema.Properties, "owner") + assert.Contains(t, inputSchema.Properties, "owner_type") + assert.Contains(t, inputSchema.Properties, "project_number") + assert.Contains(t, inputSchema.Properties, "field_id") + assert.Contains(t, inputSchema.Properties, "item_id") + assert.ElementsMatch(t, inputSchema.Required, []string{"method", "owner_type", "owner", "project_number"}) +} + +func Test_ProjectsGet_GetProject(t *testing.T) { + toolDef := ProjectsGet(translations.NullTranslationHelper) + + project := map[string]any{"id": 123, "title": "Project Title"} + + t.Run("success organization", func(t *testing.T) { + mockedClient := mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/1", Method: http.MethodGet}, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write(mock.MustMarshal(project)) + }), + ), + ) + + client := gh.NewClient(mockedClient) + deps := BaseDeps{ + Client: client, + } + handler := toolDef.Handler(deps) + request := createMCPRequest(map[string]any{ + "method": "get_project", + "owner": "octo-org", + "owner_type": "org", + "project_number": float64(1), + }) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + + require.NoError(t, err) + require.False(t, result.IsError) + + textContent := getTextResult(t, result) + var response map[string]any + err = json.Unmarshal([]byte(textContent.Text), &response) + require.NoError(t, err) + assert.NotNil(t, response["id"]) + }) + + t.Run("unknown method", func(t *testing.T) { + mockedClient := mock.NewMockedHTTPClient() + client := gh.NewClient(mockedClient) + deps := BaseDeps{ + Client: client, + } + handler := toolDef.Handler(deps) + request := createMCPRequest(map[string]any{ + "method": "unknown_method", + "owner": "octo-org", + "owner_type": "org", + "project_number": float64(1), + }) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + + require.NoError(t, err) + require.True(t, result.IsError) + textContent := getTextResult(t, result) + assert.Contains(t, textContent.Text, "unknown method: unknown_method") + }) +} + +func Test_ProjectsGet_GetProjectField(t *testing.T) { + toolDef := ProjectsGet(translations.NullTranslationHelper) + + field := map[string]any{"id": 101, "name": "Status", "data_type": "single_select"} + + t.Run("success organization", func(t *testing.T) { + mockedClient := mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/1/fields/101", Method: http.MethodGet}, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write(mock.MustMarshal(field)) + }), + ), + ) + + client := gh.NewClient(mockedClient) + deps := BaseDeps{ + Client: client, + } + handler := toolDef.Handler(deps) + request := createMCPRequest(map[string]any{ + "method": "get_project_field", + "owner": "octo-org", + "owner_type": "org", + "project_number": float64(1), + "field_id": float64(101), + }) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + + require.NoError(t, err) + require.False(t, result.IsError) + + textContent := getTextResult(t, result) + var response map[string]any + err = json.Unmarshal([]byte(textContent.Text), &response) + require.NoError(t, err) + assert.NotNil(t, response["id"]) + }) + + t.Run("missing field_id", func(t *testing.T) { + mockedClient := mock.NewMockedHTTPClient() + client := gh.NewClient(mockedClient) + deps := BaseDeps{ + Client: client, + } + handler := toolDef.Handler(deps) + request := createMCPRequest(map[string]any{ + "method": "get_project_field", + "owner": "octo-org", + "owner_type": "org", + "project_number": float64(1), + }) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + + require.NoError(t, err) + require.True(t, result.IsError) + textContent := getTextResult(t, result) + assert.Contains(t, textContent.Text, "missing required parameter: field_id") + }) +} + +func Test_ProjectsGet_GetProjectItem(t *testing.T) { + toolDef := ProjectsGet(translations.NullTranslationHelper) + + item := map[string]any{"id": 1001, "archived_at": nil, "content": map[string]any{"title": "Issue 1"}} + + t.Run("success organization", func(t *testing.T) { + mockedClient := mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/1/items/1001", Method: http.MethodGet}, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write(mock.MustMarshal(item)) + }), + ), + ) + + client := gh.NewClient(mockedClient) + deps := BaseDeps{ + Client: client, + } + handler := toolDef.Handler(deps) + request := createMCPRequest(map[string]any{ + "method": "get_project_item", + "owner": "octo-org", + "owner_type": "org", + "project_number": float64(1), + "item_id": float64(1001), + }) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + + require.NoError(t, err) + require.False(t, result.IsError) + + textContent := getTextResult(t, result) + var response map[string]any + err = json.Unmarshal([]byte(textContent.Text), &response) + require.NoError(t, err) + assert.NotNil(t, response["id"]) + }) + + t.Run("missing item_id", func(t *testing.T) { + mockedClient := mock.NewMockedHTTPClient() + client := gh.NewClient(mockedClient) + deps := BaseDeps{ + Client: client, + } + handler := toolDef.Handler(deps) + request := createMCPRequest(map[string]any{ + "method": "get_project_item", + "owner": "octo-org", + "owner_type": "org", + "project_number": float64(1), + }) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + + require.NoError(t, err) + require.True(t, result.IsError) + textContent := getTextResult(t, result) + assert.Contains(t, textContent.Text, "missing required parameter: item_id") + }) +} + +func Test_ProjectsWrite(t *testing.T) { + // Verify tool definition once + toolDef := ProjectsWrite(translations.NullTranslationHelper) + require.NoError(t, toolsnaps.Test(toolDef.Tool.Name, toolDef.Tool)) + + assert.Equal(t, "projects_write", toolDef.Tool.Name) + assert.NotEmpty(t, toolDef.Tool.Description) + inputSchema := toolDef.Tool.InputSchema.(*jsonschema.Schema) + assert.Contains(t, inputSchema.Properties, "method") + assert.Contains(t, inputSchema.Properties, "owner") + assert.Contains(t, inputSchema.Properties, "owner_type") + assert.Contains(t, inputSchema.Properties, "project_number") + assert.Contains(t, inputSchema.Properties, "item_id") + assert.Contains(t, inputSchema.Properties, "item_type") + assert.Contains(t, inputSchema.Properties, "updated_field") + assert.ElementsMatch(t, inputSchema.Required, []string{"method", "owner_type", "owner", "project_number"}) + + // Verify DestructiveHint is set + assert.NotNil(t, toolDef.Tool.Annotations) + assert.NotNil(t, toolDef.Tool.Annotations.DestructiveHint) + assert.True(t, *toolDef.Tool.Annotations.DestructiveHint) +} + +func Test_ProjectsWrite_AddProjectItem(t *testing.T) { + toolDef := ProjectsWrite(translations.NullTranslationHelper) + + addedItem := map[string]any{"id": 2001, "archived_at": nil} + + t.Run("success organization", func(t *testing.T) { + mockedClient := mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/1/items", Method: http.MethodPost}, + http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + body, _ := io.ReadAll(r.Body) + var payload map[string]any + _ = json.Unmarshal(body, &payload) + if payload["id"] == nil || payload["type"] == nil { + w.WriteHeader(http.StatusBadRequest) + _, _ = w.Write([]byte(`{"message":"bad request"}`)) + return + } + w.WriteHeader(http.StatusCreated) + _, _ = w.Write(mock.MustMarshal(addedItem)) + }), + ), + ) + + client := gh.NewClient(mockedClient) + deps := BaseDeps{ + Client: client, + } + handler := toolDef.Handler(deps) + request := createMCPRequest(map[string]any{ + "method": "add_project_item", + "owner": "octo-org", + "owner_type": "org", + "project_number": float64(1), + "item_id": float64(123), + "item_type": "issue", + }) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + + require.NoError(t, err) + require.False(t, result.IsError) + + textContent := getTextResult(t, result) + var response map[string]any + err = json.Unmarshal([]byte(textContent.Text), &response) + require.NoError(t, err) + assert.NotNil(t, response["id"]) + }) + + t.Run("missing item_type", func(t *testing.T) { + mockedClient := mock.NewMockedHTTPClient() + client := gh.NewClient(mockedClient) + deps := BaseDeps{ + Client: client, + } + handler := toolDef.Handler(deps) + request := createMCPRequest(map[string]any{ + "method": "add_project_item", + "owner": "octo-org", + "owner_type": "org", + "project_number": float64(1), + "item_id": float64(123), + }) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + + require.NoError(t, err) + require.True(t, result.IsError) + textContent := getTextResult(t, result) + assert.Contains(t, textContent.Text, "missing required parameter: item_type") + }) + + t.Run("invalid item_type", func(t *testing.T) { + mockedClient := mock.NewMockedHTTPClient() + client := gh.NewClient(mockedClient) + deps := BaseDeps{ + Client: client, + } + handler := toolDef.Handler(deps) + request := createMCPRequest(map[string]any{ + "method": "add_project_item", + "owner": "octo-org", + "owner_type": "org", + "project_number": float64(1), + "item_id": float64(123), + "item_type": "invalid_type", + }) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + + require.NoError(t, err) + require.True(t, result.IsError) + textContent := getTextResult(t, result) + assert.Contains(t, textContent.Text, "item_type must be either 'issue' or 'pull_request'") + }) + + t.Run("unknown method", func(t *testing.T) { + mockedClient := mock.NewMockedHTTPClient() + client := gh.NewClient(mockedClient) + deps := BaseDeps{ + Client: client, + } + handler := toolDef.Handler(deps) + request := createMCPRequest(map[string]any{ + "method": "unknown_method", + "owner": "octo-org", + "owner_type": "org", + "project_number": float64(1), + }) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + + require.NoError(t, err) + require.True(t, result.IsError) + textContent := getTextResult(t, result) + assert.Contains(t, textContent.Text, "unknown method: unknown_method") + }) +} + +func Test_ProjectsWrite_UpdateProjectItem(t *testing.T) { + toolDef := ProjectsWrite(translations.NullTranslationHelper) + + updatedItem := map[string]any{"id": 1001, "archived_at": nil} + + t.Run("success organization", func(t *testing.T) { + mockedClient := mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/1/items/1001", Method: http.MethodPatch}, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusOK) + _, _ = w.Write(mock.MustMarshal(updatedItem)) + }), + ), + ) + + client := gh.NewClient(mockedClient) + deps := BaseDeps{ + Client: client, + } + handler := toolDef.Handler(deps) + request := createMCPRequest(map[string]any{ + "method": "update_project_item", + "owner": "octo-org", + "owner_type": "org", + "project_number": float64(1), + "item_id": float64(1001), + "updated_field": map[string]any{ + "id": float64(101), + "value": "In Progress", + }, + }) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + + require.NoError(t, err) + require.False(t, result.IsError) + + textContent := getTextResult(t, result) + var response map[string]any + err = json.Unmarshal([]byte(textContent.Text), &response) + require.NoError(t, err) + assert.NotNil(t, response["id"]) + }) + + t.Run("missing updated_field", func(t *testing.T) { + mockedClient := mock.NewMockedHTTPClient() + client := gh.NewClient(mockedClient) + deps := BaseDeps{ + Client: client, + } + handler := toolDef.Handler(deps) + request := createMCPRequest(map[string]any{ + "method": "update_project_item", + "owner": "octo-org", + "owner_type": "org", + "project_number": float64(1), + "item_id": float64(1001), + }) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + + require.NoError(t, err) + require.True(t, result.IsError) + textContent := getTextResult(t, result) + assert.Contains(t, textContent.Text, "missing required parameter: updated_field") + }) +} + +func Test_ProjectsWrite_DeleteProjectItem(t *testing.T) { + toolDef := ProjectsWrite(translations.NullTranslationHelper) + + t.Run("success organization", func(t *testing.T) { + mockedClient := mock.NewMockedHTTPClient( + mock.WithRequestMatchHandler( + mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/1/items/1001", Method: http.MethodDelete}, + http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNoContent) + }), + ), + ) + + client := gh.NewClient(mockedClient) + deps := BaseDeps{ + Client: client, + } + handler := toolDef.Handler(deps) + request := createMCPRequest(map[string]any{ + "method": "delete_project_item", + "owner": "octo-org", + "owner_type": "org", + "project_number": float64(1), + "item_id": float64(1001), + }) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + + require.NoError(t, err) + require.False(t, result.IsError) + + textContent := getTextResult(t, result) + assert.Contains(t, textContent.Text, "project item successfully deleted") + }) + + t.Run("missing item_id", func(t *testing.T) { + mockedClient := mock.NewMockedHTTPClient() + client := gh.NewClient(mockedClient) + deps := BaseDeps{ + Client: client, + } + handler := toolDef.Handler(deps) + request := createMCPRequest(map[string]any{ + "method": "delete_project_item", + "owner": "octo-org", + "owner_type": "org", + "project_number": float64(1), + }) + result, err := handler(ContextWithDeps(context.Background(), deps), &request) + + require.NoError(t, err) + require.True(t, result.IsError) + textContent := getTextResult(t, result) + assert.Contains(t, textContent.Text, "missing required parameter: item_id") + }) +} diff --git a/pkg/github/tools.go b/pkg/github/tools.go index f6d4afa80..b15c4fc9a 100644 --- a/pkg/github/tools.go +++ b/pkg/github/tools.go @@ -279,6 +279,11 @@ func AllTools(t translations.TranslationHelperFunc) []inventory.ServerTool { DeleteProjectItem(t), UpdateProjectItem(t), + // Consolidated project tools (enabled via feature flag) + ProjectsList(t), + ProjectsGet(t), + ProjectsWrite(t), + // Label tools GetLabel(t), GetLabelForLabelsToolset(t), From 3cd0be2af2a9d71ed95ff4dfd4d85081321b5d29 Mon Sep 17 00:00:00 2001 From: Matt Holloway Date: Wed, 31 Dec 2025 11:59:16 +0000 Subject: [PATCH 042/138] update tool aliases --- pkg/github/deprecated_tool_aliases.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/pkg/github/deprecated_tool_aliases.go b/pkg/github/deprecated_tool_aliases.go index 63394770e..4415731fb 100644 --- a/pkg/github/deprecated_tool_aliases.go +++ b/pkg/github/deprecated_tool_aliases.go @@ -28,4 +28,15 @@ var DeprecatedToolAliases = map[string]string{ "rerun_failed_jobs": "actions_run_trigger", "cancel_workflow_run": "actions_run_trigger", "delete_workflow_run_logs": "actions_run_trigger", + + // Projects tools consolidated + "list_projects": "projects_list", + "list_project_fields": "projects_list", + "list_project_items": "projects_list", + "get_project": "projects_get", + "get_project_field": "projects_get", + "get_project_item": "projects_get", + "add_project_item": "projects_write", + "update_project_item": "projects_write", + "delete_project_item": "projects_write", } From 304f07401e6b88591c5e659f6d86d0065f55480d Mon Sep 17 00:00:00 2001 From: Matt Holloway Date: Mon, 5 Jan 2026 15:27:57 +0000 Subject: [PATCH 043/138] hold-bac feature flag --- pkg/github/projects.go | 17 ++++++++++++ pkg/inventory/filters.go | 13 ++++++--- pkg/inventory/prompts.go | 3 ++ pkg/inventory/registry_test.go | 50 ++++++++++++++++++++++++++++++++++ pkg/inventory/resources.go | 3 ++ pkg/inventory/server_tool.go | 6 ++++ 6 files changed, 88 insertions(+), 4 deletions(-) diff --git a/pkg/github/projects.go b/pkg/github/projects.go index 9a5d25839..bb53fac45 100644 --- a/pkg/github/projects.go +++ b/pkg/github/projects.go @@ -30,6 +30,14 @@ const ( // in favor of the consolidated project tools. const FeatureFlagConsolidatedProjects = "remote_mcp_consolidated_projects" +// FeatureFlagHoldBackLegacyProjects allows users to keep the old individual project tools +// even after FeatureFlagConsolidatedProjects is enabled. This provides a transition period +// for users who need more time to migrate to the consolidated tools. +// +// Deprecated: This flag will be removed in a future release. Users should migrate to +// the consolidated project tools (projects_list, projects_get, projects_write). +const FeatureFlagHoldBackLegacyProjects = "remote_mcp_holdback_legacy_projects" + // Method constants for consolidated project tools const ( projectsMethodListProjects = "list_projects" @@ -160,6 +168,7 @@ func ListProjects(t translations.TranslationHelperFunc) inventory.ServerTool { }, ) tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects + tool.FeatureFlagHoldBack = FeatureFlagHoldBackLegacyProjects return tool } @@ -251,6 +260,7 @@ func GetProject(t translations.TranslationHelperFunc) inventory.ServerTool { }, ) tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects + tool.FeatureFlagHoldBack = FeatureFlagHoldBackLegacyProjects return tool } @@ -360,6 +370,7 @@ func ListProjectFields(t translations.TranslationHelperFunc) inventory.ServerToo }, ) tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects + tool.FeatureFlagHoldBack = FeatureFlagHoldBackLegacyProjects return tool } @@ -455,6 +466,7 @@ func GetProjectField(t translations.TranslationHelperFunc) inventory.ServerTool }, ) tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects + tool.FeatureFlagHoldBack = FeatureFlagHoldBackLegacyProjects return tool } @@ -594,6 +606,7 @@ func ListProjectItems(t translations.TranslationHelperFunc) inventory.ServerTool }, ) tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects + tool.FeatureFlagHoldBack = FeatureFlagHoldBackLegacyProjects return tool } @@ -703,6 +716,7 @@ func GetProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { }, ) tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects + tool.FeatureFlagHoldBack = FeatureFlagHoldBackLegacyProjects return tool } @@ -817,6 +831,7 @@ func AddProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { }, ) tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects + tool.FeatureFlagHoldBack = FeatureFlagHoldBackLegacyProjects return tool } @@ -932,6 +947,7 @@ func UpdateProjectItem(t translations.TranslationHelperFunc) inventory.ServerToo }, ) tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects + tool.FeatureFlagHoldBack = FeatureFlagHoldBackLegacyProjects return tool } @@ -1021,6 +1037,7 @@ func DeleteProjectItem(t translations.TranslationHelperFunc) inventory.ServerToo }, ) tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects + tool.FeatureFlagHoldBack = FeatureFlagHoldBackLegacyProjects return tool } diff --git a/pkg/inventory/filters.go b/pkg/inventory/filters.go index c5156e61a..a852760ce 100644 --- a/pkg/inventory/filters.go +++ b/pkg/inventory/filters.go @@ -38,13 +38,18 @@ func (r *Inventory) checkFeatureFlag(ctx context.Context, flagName string) bool // isFeatureFlagAllowed checks if an item passes feature flag filtering. // - If FeatureFlagEnable is set, the item is only allowed if the flag is enabled // - If FeatureFlagDisable is set, the item is excluded if the flag is enabled -func (r *Inventory) isFeatureFlagAllowed(ctx context.Context, enableFlag, disableFlag string) bool { +// - If FeatureFlagHoldBack is set and enabled, it overrides FeatureFlagDisable (keeps tool available) +func (r *Inventory) isFeatureFlagAllowed(ctx context.Context, enableFlag, disableFlag, holdBackFlag string) bool { // Check enable flag - item requires this flag to be on if enableFlag != "" && !r.checkFeatureFlag(ctx, enableFlag) { return false } // Check disable flag - item is excluded if this flag is on if disableFlag != "" && r.checkFeatureFlag(ctx, disableFlag) { + // Check if hold-back flag overrides the disable + if holdBackFlag != "" && r.checkFeatureFlag(ctx, holdBackFlag) { + return true // Hold-back keeps tool enabled during transition + } return false } return true @@ -70,7 +75,7 @@ func (r *Inventory) isToolEnabled(ctx context.Context, tool *ServerTool) bool { } } // 2. Check feature flags - if !r.isFeatureFlagAllowed(ctx, tool.FeatureFlagEnable, tool.FeatureFlagDisable) { + if !r.isFeatureFlagAllowed(ctx, tool.FeatureFlagEnable, tool.FeatureFlagDisable, tool.FeatureFlagHoldBack) { return false } // 3. Check read-only filter (applies to all tools) @@ -130,7 +135,7 @@ func (r *Inventory) AvailableResourceTemplates(ctx context.Context) []ServerReso for i := range r.resourceTemplates { res := &r.resourceTemplates[i] // Check feature flags - if !r.isFeatureFlagAllowed(ctx, res.FeatureFlagEnable, res.FeatureFlagDisable) { + if !r.isFeatureFlagAllowed(ctx, res.FeatureFlagEnable, res.FeatureFlagDisable, res.FeatureFlagHoldBack) { continue } if r.isToolsetEnabled(res.Toolset.ID) { @@ -157,7 +162,7 @@ func (r *Inventory) AvailablePrompts(ctx context.Context) []ServerPrompt { for i := range r.prompts { prompt := &r.prompts[i] // Check feature flags - if !r.isFeatureFlagAllowed(ctx, prompt.FeatureFlagEnable, prompt.FeatureFlagDisable) { + if !r.isFeatureFlagAllowed(ctx, prompt.FeatureFlagEnable, prompt.FeatureFlagDisable, prompt.FeatureFlagHoldBack) { continue } if r.isToolsetEnabled(prompt.Toolset.ID) { diff --git a/pkg/inventory/prompts.go b/pkg/inventory/prompts.go index 648f20f9c..2ef57deb8 100644 --- a/pkg/inventory/prompts.go +++ b/pkg/inventory/prompts.go @@ -14,6 +14,9 @@ type ServerPrompt struct { // FeatureFlagDisable specifies a feature flag that, when enabled, causes this prompt // to be omitted. Used to disable prompts when a feature flag is on. FeatureFlagDisable string + // FeatureFlagHoldBack specifies a feature flag that, when enabled, overrides + // FeatureFlagDisable and keeps the prompt available during a transition period. + FeatureFlagHoldBack string } // NewServerPrompt creates a new ServerPrompt with toolset metadata. diff --git a/pkg/inventory/registry_test.go b/pkg/inventory/registry_test.go index 742ad3646..305f0fe86 100644 --- a/pkg/inventory/registry_test.go +++ b/pkg/inventory/registry_test.go @@ -1077,6 +1077,56 @@ func TestFeatureFlagBoth(t *testing.T) { } } +func TestFeatureFlagHoldBack(t *testing.T) { + // Tool with disable flag and hold-back flag (simulates legacy tool during consolidation) + legacyTool := mockToolWithFlags("legacy_tool", "toolset1", true, "", "consolidation_flag") + legacyTool.FeatureFlagHoldBack = "holdback_flag" + + tools := []ServerTool{ + mockTool("always_available", "toolset1", true), + legacyTool, + } + + // Consolidation OFF, hold-back OFF -> legacy tool available (normal operation) + checkerAllOff := func(_ context.Context, _ string) (bool, error) { return false, nil } + regAllOff := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checkerAllOff).Build() + availableAllOff := regAllOff.AvailableTools(context.Background()) + if len(availableAllOff) != 2 { + t.Fatalf("Expected 2 tools when both flags off, got %d", len(availableAllOff)) + } + + // Consolidation ON, hold-back OFF -> legacy tool excluded (migrated to new tools) + checkerConsolidationOnly := func(_ context.Context, flag string) (bool, error) { + return flag == "consolidation_flag", nil + } + regConsolidationOnly := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checkerConsolidationOnly).Build() + availableConsolidationOnly := regConsolidationOnly.AvailableTools(context.Background()) + if len(availableConsolidationOnly) != 1 { + t.Fatalf("Expected 1 tool when consolidation on but holdback off, got %d", len(availableConsolidationOnly)) + } + if availableConsolidationOnly[0].Tool.Name != "always_available" { + t.Errorf("Expected always_available, got %s", availableConsolidationOnly[0].Tool.Name) + } + + // Consolidation ON, hold-back ON -> legacy tool available (user opted to hold back) + checkerBothOn := func(_ context.Context, _ string) (bool, error) { return true, nil } + regBothOn := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checkerBothOn).Build() + availableBothOn := regBothOn.AvailableTools(context.Background()) + if len(availableBothOn) != 2 { + t.Fatalf("Expected 2 tools when both consolidation and holdback on, got %d", len(availableBothOn)) + } + + // Consolidation OFF, hold-back ON -> legacy tool available (hold-back has no effect when consolidation off) + checkerHoldbackOnly := func(_ context.Context, flag string) (bool, error) { + return flag == "holdback_flag", nil + } + regHoldbackOnly := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checkerHoldbackOnly).Build() + availableHoldbackOnly := regHoldbackOnly.AvailableTools(context.Background()) + if len(availableHoldbackOnly) != 2 { + t.Fatalf("Expected 2 tools when only holdback on, got %d", len(availableHoldbackOnly)) + } +} + func TestFeatureFlagError(t *testing.T) { tools := []ServerTool{ mockToolWithFlags("needs_flag", "toolset1", true, "my_feature", ""), diff --git a/pkg/inventory/resources.go b/pkg/inventory/resources.go index 6de037d58..83904355c 100644 --- a/pkg/inventory/resources.go +++ b/pkg/inventory/resources.go @@ -22,6 +22,9 @@ type ServerResourceTemplate struct { // FeatureFlagDisable specifies a feature flag that, when enabled, causes this resource // to be omitted. Used to disable resources when a feature flag is on. FeatureFlagDisable string + // FeatureFlagHoldBack specifies a feature flag that, when enabled, overrides + // FeatureFlagDisable and keeps the resource available during a transition period. + FeatureFlagHoldBack string } // HasHandler returns true if this resource has a handler function. diff --git a/pkg/inventory/server_tool.go b/pkg/inventory/server_tool.go index 095bedf2b..728b44697 100644 --- a/pkg/inventory/server_tool.go +++ b/pkg/inventory/server_tool.go @@ -64,6 +64,12 @@ type ServerTool struct { // to be omitted. Used to disable tools when a feature flag is on. FeatureFlagDisable string + // FeatureFlagHoldBack specifies a feature flag that, when enabled, overrides + // FeatureFlagDisable and keeps the tool available. This allows users to "hold back" + // on a deprecation by opting to keep the old tools during a transition period. + // Used during tool consolidation to give users time to migrate. + FeatureFlagHoldBack string + // Enabled is an optional function called at build/filter time to determine // if this tool should be available. If nil, the tool is considered enabled // (subject to FeatureFlagEnable/FeatureFlagDisable checks). From 7b30c930f8505e29fdf4f44efdcccdac9b4cd052 Mon Sep 17 00:00:00 2001 From: Matt Holloway Date: Mon, 5 Jan 2026 15:28:42 +0000 Subject: [PATCH 044/138] update docs --- README.md | 29 ----------------------------- docs/tool-renaming.md | 9 +++++++++ 2 files changed, 9 insertions(+), 29 deletions(-) diff --git a/README.md b/README.md index dcf4756d2..1a0f6b1c4 100644 --- a/README.md +++ b/README.md @@ -1000,35 +1000,6 @@ The following sets of tools are available: - `per_page`: Results per page (max 50) (number, optional) - `query`: Filter projects by title text and open/closed state; permitted qualifiers: is:open, is:closed; examples: "roadmap is:open", "is:open feature planning". (string, optional) -- **projects_get** - Get details of GitHub Projects resources - - `field_id`: The field's ID. Required for 'get_project_field' method. (number, optional) - - `fields`: Specific list of field IDs to include in the response when getting a project item (e.g. ["102589", "985201", "169875"]). If not provided, only the title field is included. Only used for 'get_project_item' method. (string[], optional) - - `item_id`: The item's ID. Required for 'get_project_item' method. (number, optional) - - `method`: The method to execute (string, required) - - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) - - `owner_type`: Owner type (string, required) - - `project_number`: The project's number. (number, required) - -- **projects_list** - List GitHub Projects resources - - `after`: Forward pagination cursor from previous pageInfo.nextCursor. (string, optional) - - `before`: Backward pagination cursor from previous pageInfo.prevCursor (rare). (string, optional) - - `fields`: Field IDs to include when listing project items (e.g. ["102589", "985201"]). CRITICAL: Always provide to get field values. Without this, only titles returned. Only used for 'list_project_items' method. (string[], optional) - - `method`: The action to perform (string, required) - - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) - - `owner_type`: Owner type (string, required) - - `per_page`: Results per page (max 50) (number, optional) - - `project_number`: The project's number. Required for 'list_project_fields' and 'list_project_items' methods. (number, optional) - - `query`: Filter/query string. For list_projects: filter by title text and state (e.g. "roadmap is:open"). For list_project_items: advanced filtering using GitHub's project filtering syntax. (string, optional) - -- **projects_write** - Modify GitHub Project items - - `item_id`: The project item ID. Required for 'update_project_item' and 'delete_project_item' methods. For add_project_item, this is the numeric ID of the issue or pull request to add. (number, optional) - - `item_type`: The item's type, either issue or pull_request. Required for 'add_project_item' method. (string, optional) - - `method`: The method to execute (string, required) - - `owner`: If owner_type == user it is the handle for the GitHub user account. If owner_type == org it is the name of the organization. The name is not case sensitive. (string, required) - - `owner_type`: Owner type (string, required) - - `project_number`: The project's number. (number, required) - - `updated_field`: Object consisting of the ID of the project field to update and the new value for the field. To clear the field, set value to null. Example: {"id": 123456, "value": "New Value"}. Required for 'update_project_item' method. (object, optional) - - **update_project_item** - Update project item - **Required OAuth Scopes**: `project` - `item_id`: The unique identifier of the project item. This is not the issue or pull request ID. (number, required) diff --git a/docs/tool-renaming.md b/docs/tool-renaming.md index cf342f6dc..050ac9b77 100644 --- a/docs/tool-renaming.md +++ b/docs/tool-renaming.md @@ -46,15 +46,23 @@ Will get `issue_read` and `get_file_contents` tools registered, with no errors. | Old Name | New Name | |----------|----------| +| `add_project_item` | `projects_write` | | `cancel_workflow_run` | `actions_run_trigger` | +| `delete_project_item` | `projects_write` | | `delete_workflow_run_logs` | `actions_run_trigger` | | `download_workflow_run_artifact` | `actions_get` | +| `get_project` | `projects_get` | +| `get_project_field` | `projects_get` | +| `get_project_item` | `projects_get` | | `get_workflow` | `actions_get` | | `get_workflow_job` | `actions_get` | | `get_workflow_job_logs` | `actions_get` | | `get_workflow_run` | `actions_get` | | `get_workflow_run_logs` | `actions_get` | | `get_workflow_run_usage` | `actions_get` | +| `list_project_fields` | `projects_list` | +| `list_project_items` | `projects_list` | +| `list_projects` | `projects_list` | | `list_workflow_jobs` | `actions_list` | | `list_workflow_run_artifacts` | `actions_list` | | `list_workflow_runs` | `actions_list` | @@ -62,4 +70,5 @@ Will get `issue_read` and `get_file_contents` tools registered, with no errors. | `rerun_failed_jobs` | `actions_run_trigger` | | `rerun_workflow_run` | `actions_run_trigger` | | `run_workflow` | `actions_run_trigger` | +| `update_project_item` | `projects_write` | From 099f995615c94c31f4ccd3a42efdb13889d4cde4 Mon Sep 17 00:00:00 2001 From: Matt Holloway Date: Mon, 5 Jan 2026 16:18:40 +0000 Subject: [PATCH 045/138] revert "hold-bac feature flag" --- pkg/github/projects.go | 17 ------------ pkg/inventory/filters.go | 13 +++------ pkg/inventory/prompts.go | 3 -- pkg/inventory/registry_test.go | 50 ---------------------------------- pkg/inventory/resources.go | 3 -- pkg/inventory/server_tool.go | 6 ---- 6 files changed, 4 insertions(+), 88 deletions(-) diff --git a/pkg/github/projects.go b/pkg/github/projects.go index bb53fac45..9a5d25839 100644 --- a/pkg/github/projects.go +++ b/pkg/github/projects.go @@ -30,14 +30,6 @@ const ( // in favor of the consolidated project tools. const FeatureFlagConsolidatedProjects = "remote_mcp_consolidated_projects" -// FeatureFlagHoldBackLegacyProjects allows users to keep the old individual project tools -// even after FeatureFlagConsolidatedProjects is enabled. This provides a transition period -// for users who need more time to migrate to the consolidated tools. -// -// Deprecated: This flag will be removed in a future release. Users should migrate to -// the consolidated project tools (projects_list, projects_get, projects_write). -const FeatureFlagHoldBackLegacyProjects = "remote_mcp_holdback_legacy_projects" - // Method constants for consolidated project tools const ( projectsMethodListProjects = "list_projects" @@ -168,7 +160,6 @@ func ListProjects(t translations.TranslationHelperFunc) inventory.ServerTool { }, ) tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects - tool.FeatureFlagHoldBack = FeatureFlagHoldBackLegacyProjects return tool } @@ -260,7 +251,6 @@ func GetProject(t translations.TranslationHelperFunc) inventory.ServerTool { }, ) tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects - tool.FeatureFlagHoldBack = FeatureFlagHoldBackLegacyProjects return tool } @@ -370,7 +360,6 @@ func ListProjectFields(t translations.TranslationHelperFunc) inventory.ServerToo }, ) tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects - tool.FeatureFlagHoldBack = FeatureFlagHoldBackLegacyProjects return tool } @@ -466,7 +455,6 @@ func GetProjectField(t translations.TranslationHelperFunc) inventory.ServerTool }, ) tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects - tool.FeatureFlagHoldBack = FeatureFlagHoldBackLegacyProjects return tool } @@ -606,7 +594,6 @@ func ListProjectItems(t translations.TranslationHelperFunc) inventory.ServerTool }, ) tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects - tool.FeatureFlagHoldBack = FeatureFlagHoldBackLegacyProjects return tool } @@ -716,7 +703,6 @@ func GetProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { }, ) tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects - tool.FeatureFlagHoldBack = FeatureFlagHoldBackLegacyProjects return tool } @@ -831,7 +817,6 @@ func AddProjectItem(t translations.TranslationHelperFunc) inventory.ServerTool { }, ) tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects - tool.FeatureFlagHoldBack = FeatureFlagHoldBackLegacyProjects return tool } @@ -947,7 +932,6 @@ func UpdateProjectItem(t translations.TranslationHelperFunc) inventory.ServerToo }, ) tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects - tool.FeatureFlagHoldBack = FeatureFlagHoldBackLegacyProjects return tool } @@ -1037,7 +1021,6 @@ func DeleteProjectItem(t translations.TranslationHelperFunc) inventory.ServerToo }, ) tool.FeatureFlagDisable = FeatureFlagConsolidatedProjects - tool.FeatureFlagHoldBack = FeatureFlagHoldBackLegacyProjects return tool } diff --git a/pkg/inventory/filters.go b/pkg/inventory/filters.go index a852760ce..c5156e61a 100644 --- a/pkg/inventory/filters.go +++ b/pkg/inventory/filters.go @@ -38,18 +38,13 @@ func (r *Inventory) checkFeatureFlag(ctx context.Context, flagName string) bool // isFeatureFlagAllowed checks if an item passes feature flag filtering. // - If FeatureFlagEnable is set, the item is only allowed if the flag is enabled // - If FeatureFlagDisable is set, the item is excluded if the flag is enabled -// - If FeatureFlagHoldBack is set and enabled, it overrides FeatureFlagDisable (keeps tool available) -func (r *Inventory) isFeatureFlagAllowed(ctx context.Context, enableFlag, disableFlag, holdBackFlag string) bool { +func (r *Inventory) isFeatureFlagAllowed(ctx context.Context, enableFlag, disableFlag string) bool { // Check enable flag - item requires this flag to be on if enableFlag != "" && !r.checkFeatureFlag(ctx, enableFlag) { return false } // Check disable flag - item is excluded if this flag is on if disableFlag != "" && r.checkFeatureFlag(ctx, disableFlag) { - // Check if hold-back flag overrides the disable - if holdBackFlag != "" && r.checkFeatureFlag(ctx, holdBackFlag) { - return true // Hold-back keeps tool enabled during transition - } return false } return true @@ -75,7 +70,7 @@ func (r *Inventory) isToolEnabled(ctx context.Context, tool *ServerTool) bool { } } // 2. Check feature flags - if !r.isFeatureFlagAllowed(ctx, tool.FeatureFlagEnable, tool.FeatureFlagDisable, tool.FeatureFlagHoldBack) { + if !r.isFeatureFlagAllowed(ctx, tool.FeatureFlagEnable, tool.FeatureFlagDisable) { return false } // 3. Check read-only filter (applies to all tools) @@ -135,7 +130,7 @@ func (r *Inventory) AvailableResourceTemplates(ctx context.Context) []ServerReso for i := range r.resourceTemplates { res := &r.resourceTemplates[i] // Check feature flags - if !r.isFeatureFlagAllowed(ctx, res.FeatureFlagEnable, res.FeatureFlagDisable, res.FeatureFlagHoldBack) { + if !r.isFeatureFlagAllowed(ctx, res.FeatureFlagEnable, res.FeatureFlagDisable) { continue } if r.isToolsetEnabled(res.Toolset.ID) { @@ -162,7 +157,7 @@ func (r *Inventory) AvailablePrompts(ctx context.Context) []ServerPrompt { for i := range r.prompts { prompt := &r.prompts[i] // Check feature flags - if !r.isFeatureFlagAllowed(ctx, prompt.FeatureFlagEnable, prompt.FeatureFlagDisable, prompt.FeatureFlagHoldBack) { + if !r.isFeatureFlagAllowed(ctx, prompt.FeatureFlagEnable, prompt.FeatureFlagDisable) { continue } if r.isToolsetEnabled(prompt.Toolset.ID) { diff --git a/pkg/inventory/prompts.go b/pkg/inventory/prompts.go index 2ef57deb8..648f20f9c 100644 --- a/pkg/inventory/prompts.go +++ b/pkg/inventory/prompts.go @@ -14,9 +14,6 @@ type ServerPrompt struct { // FeatureFlagDisable specifies a feature flag that, when enabled, causes this prompt // to be omitted. Used to disable prompts when a feature flag is on. FeatureFlagDisable string - // FeatureFlagHoldBack specifies a feature flag that, when enabled, overrides - // FeatureFlagDisable and keeps the prompt available during a transition period. - FeatureFlagHoldBack string } // NewServerPrompt creates a new ServerPrompt with toolset metadata. diff --git a/pkg/inventory/registry_test.go b/pkg/inventory/registry_test.go index 305f0fe86..742ad3646 100644 --- a/pkg/inventory/registry_test.go +++ b/pkg/inventory/registry_test.go @@ -1077,56 +1077,6 @@ func TestFeatureFlagBoth(t *testing.T) { } } -func TestFeatureFlagHoldBack(t *testing.T) { - // Tool with disable flag and hold-back flag (simulates legacy tool during consolidation) - legacyTool := mockToolWithFlags("legacy_tool", "toolset1", true, "", "consolidation_flag") - legacyTool.FeatureFlagHoldBack = "holdback_flag" - - tools := []ServerTool{ - mockTool("always_available", "toolset1", true), - legacyTool, - } - - // Consolidation OFF, hold-back OFF -> legacy tool available (normal operation) - checkerAllOff := func(_ context.Context, _ string) (bool, error) { return false, nil } - regAllOff := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checkerAllOff).Build() - availableAllOff := regAllOff.AvailableTools(context.Background()) - if len(availableAllOff) != 2 { - t.Fatalf("Expected 2 tools when both flags off, got %d", len(availableAllOff)) - } - - // Consolidation ON, hold-back OFF -> legacy tool excluded (migrated to new tools) - checkerConsolidationOnly := func(_ context.Context, flag string) (bool, error) { - return flag == "consolidation_flag", nil - } - regConsolidationOnly := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checkerConsolidationOnly).Build() - availableConsolidationOnly := regConsolidationOnly.AvailableTools(context.Background()) - if len(availableConsolidationOnly) != 1 { - t.Fatalf("Expected 1 tool when consolidation on but holdback off, got %d", len(availableConsolidationOnly)) - } - if availableConsolidationOnly[0].Tool.Name != "always_available" { - t.Errorf("Expected always_available, got %s", availableConsolidationOnly[0].Tool.Name) - } - - // Consolidation ON, hold-back ON -> legacy tool available (user opted to hold back) - checkerBothOn := func(_ context.Context, _ string) (bool, error) { return true, nil } - regBothOn := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checkerBothOn).Build() - availableBothOn := regBothOn.AvailableTools(context.Background()) - if len(availableBothOn) != 2 { - t.Fatalf("Expected 2 tools when both consolidation and holdback on, got %d", len(availableBothOn)) - } - - // Consolidation OFF, hold-back ON -> legacy tool available (hold-back has no effect when consolidation off) - checkerHoldbackOnly := func(_ context.Context, flag string) (bool, error) { - return flag == "holdback_flag", nil - } - regHoldbackOnly := NewBuilder().SetTools(tools).WithToolsets([]string{"all"}).WithFeatureChecker(checkerHoldbackOnly).Build() - availableHoldbackOnly := regHoldbackOnly.AvailableTools(context.Background()) - if len(availableHoldbackOnly) != 2 { - t.Fatalf("Expected 2 tools when only holdback on, got %d", len(availableHoldbackOnly)) - } -} - func TestFeatureFlagError(t *testing.T) { tools := []ServerTool{ mockToolWithFlags("needs_flag", "toolset1", true, "my_feature", ""), diff --git a/pkg/inventory/resources.go b/pkg/inventory/resources.go index 83904355c..6de037d58 100644 --- a/pkg/inventory/resources.go +++ b/pkg/inventory/resources.go @@ -22,9 +22,6 @@ type ServerResourceTemplate struct { // FeatureFlagDisable specifies a feature flag that, when enabled, causes this resource // to be omitted. Used to disable resources when a feature flag is on. FeatureFlagDisable string - // FeatureFlagHoldBack specifies a feature flag that, when enabled, overrides - // FeatureFlagDisable and keeps the resource available during a transition period. - FeatureFlagHoldBack string } // HasHandler returns true if this resource has a handler function. diff --git a/pkg/inventory/server_tool.go b/pkg/inventory/server_tool.go index 728b44697..095bedf2b 100644 --- a/pkg/inventory/server_tool.go +++ b/pkg/inventory/server_tool.go @@ -64,12 +64,6 @@ type ServerTool struct { // to be omitted. Used to disable tools when a feature flag is on. FeatureFlagDisable string - // FeatureFlagHoldBack specifies a feature flag that, when enabled, overrides - // FeatureFlagDisable and keeps the tool available. This allows users to "hold back" - // on a deprecation by opting to keep the old tools during a transition period. - // Used during tool consolidation to give users time to migrate. - FeatureFlagHoldBack string - // Enabled is an optional function called at build/filter time to determine // if this tool should be available. If nil, the tool is considered enabled // (subject to FeatureFlagEnable/FeatureFlagDisable checks). From be5a449e4898069f47a5eca3b16e4b3fc4813a32 Mon Sep 17 00:00:00 2001 From: Matt Holloway Date: Mon, 5 Jan 2026 16:23:00 +0000 Subject: [PATCH 046/138] fix project tools to add scope to newtool init --- pkg/github/projects.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/pkg/github/projects.go b/pkg/github/projects.go index 9a5d25839..6e43a3e9b 100644 --- a/pkg/github/projects.go +++ b/pkg/github/projects.go @@ -1090,6 +1090,7 @@ Use this tool to list projects for a user or organization, or list project field Required: []string{"method", "owner_type", "owner"}, }, }, + []scopes.Scope{scopes.ReadProject}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { method, err := RequiredParam[string](args, "method") if err != nil { @@ -1184,6 +1185,7 @@ Use this tool to get details about individual projects, project fields, and proj Required: []string{"method", "owner_type", "owner", "project_number"}, }, }, + []scopes.Scope{scopes.ReadProject}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { method, err := RequiredParam[string](args, "method") if err != nil { @@ -1292,6 +1294,7 @@ func ProjectsWrite(t translations.TranslationHelperFunc) inventory.ServerTool { Required: []string{"method", "owner_type", "owner", "project_number"}, }, }, + []scopes.Scope{scopes.Project}, func(ctx context.Context, deps ToolDependencies, _ *mcp.CallToolRequest, args map[string]any) (*mcp.CallToolResult, any, error) { method, err := RequiredParam[string](args, "method") if err != nil { From a57b4726e5994a20cc5395dfa050f2dd8594fb93 Mon Sep 17 00:00:00 2001 From: Matt Holloway Date: Tue, 6 Jan 2026 13:24:24 +0000 Subject: [PATCH 047/138] add http resp code checking for getProjectItem --- pkg/github/projects.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pkg/github/projects.go b/pkg/github/projects.go index 6e43a3e9b..8af181a72 100644 --- a/pkg/github/projects.go +++ b/pkg/github/projects.go @@ -1629,6 +1629,14 @@ func getProjectItem(ctx context.Context, client *github.Client, owner, ownerType } defer func() { _ = resp.Body.Close() }() + if resp.StatusCode != http.StatusOK { + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, nil, fmt.Errorf("failed to read response body: %w", err) + } + return ghErrors.NewGitHubAPIStatusErrorResponse(ctx, "failed to get project item", resp, body), nil, nil + } + r, err := json.Marshal(projectItem) if err != nil { return nil, nil, fmt.Errorf("failed to marshal response: %w", err) From 71862a93e45ddcc178e672ea4dad61226e80c72d Mon Sep 17 00:00:00 2001 From: Matt Holloway Date: Tue, 6 Jan 2026 13:27:43 +0000 Subject: [PATCH 048/138] update tests to use new mock pattern --- pkg/github/projects_test.go | 154 +++++++++++------------------------- 1 file changed, 46 insertions(+), 108 deletions(-) diff --git a/pkg/github/projects_test.go b/pkg/github/projects_test.go index 96bd4d128..9819e7d7e 100644 --- a/pkg/github/projects_test.go +++ b/pkg/github/projects_test.go @@ -1565,15 +1565,9 @@ func Test_ProjectsList_ListProjects(t *testing.T) { }{ { name: "success organization", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(orgProjects)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2: mockResponse(t, http.StatusOK, orgProjects), + }), requestArgs: map[string]any{ "method": "list_projects", "owner": "octo-org", @@ -1584,15 +1578,9 @@ func Test_ProjectsList_ListProjects(t *testing.T) { }, { name: "success user", - mockedClient: mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/users/{username}/projectsV2", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(userProjects)) - }), - ), - ), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetUsersProjectsV2ByUsername: mockResponse(t, http.StatusOK, userProjects), + }), requestArgs: map[string]any{ "method": "list_projects", "owner": "octocat", @@ -1603,7 +1591,7 @@ func Test_ProjectsList_ListProjects(t *testing.T) { }, { name: "missing required parameter method", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "owner": "octo-org", "owner_type": "org", @@ -1613,7 +1601,7 @@ func Test_ProjectsList_ListProjects(t *testing.T) { }, { name: "unknown method", - mockedClient: mock.NewMockedHTTPClient(), + mockedClient: MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}), requestArgs: map[string]any{ "method": "unknown_method", "owner": "octo-org", @@ -1662,15 +1650,9 @@ func Test_ProjectsList_ListProjectFields(t *testing.T) { fields := []map[string]any{{"id": 101, "name": "Status", "data_type": "single_select"}} t.Run("success organization", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/1/fields", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(fields)) - }), - ), - ) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2FieldsByProject: mockResponse(t, http.StatusOK, fields), + }) client := gh.NewClient(mockedClient) deps := BaseDeps{ @@ -1698,7 +1680,7 @@ func Test_ProjectsList_ListProjectFields(t *testing.T) { }) t.Run("missing project_number", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient() + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}) client := gh.NewClient(mockedClient) deps := BaseDeps{ Client: client, @@ -1724,15 +1706,9 @@ func Test_ProjectsList_ListProjectItems(t *testing.T) { items := []map[string]any{{"id": 1001, "archived_at": nil, "content": map[string]any{"title": "Issue 1"}}} t.Run("success organization", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/1/items", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(items)) - }), - ), - ) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2ItemsByProject: mockResponse(t, http.StatusOK, items), + }) client := gh.NewClient(mockedClient) deps := BaseDeps{ @@ -1783,15 +1759,9 @@ func Test_ProjectsGet_GetProject(t *testing.T) { project := map[string]any{"id": 123, "title": "Project Title"} t.Run("success organization", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/1", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(project)) - }), - ), - ) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2ByProject: mockResponse(t, http.StatusOK, project), + }) client := gh.NewClient(mockedClient) deps := BaseDeps{ @@ -1817,7 +1787,7 @@ func Test_ProjectsGet_GetProject(t *testing.T) { }) t.Run("unknown method", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient() + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}) client := gh.NewClient(mockedClient) deps := BaseDeps{ Client: client, @@ -1844,15 +1814,9 @@ func Test_ProjectsGet_GetProjectField(t *testing.T) { field := map[string]any{"id": 101, "name": "Status", "data_type": "single_select"} t.Run("success organization", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/1/fields/101", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(field)) - }), - ), - ) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2FieldsByProjectByFieldID: mockResponse(t, http.StatusOK, field), + }) client := gh.NewClient(mockedClient) deps := BaseDeps{ @@ -1879,7 +1843,7 @@ func Test_ProjectsGet_GetProjectField(t *testing.T) { }) t.Run("missing field_id", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient() + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}) client := gh.NewClient(mockedClient) deps := BaseDeps{ Client: client, @@ -1906,15 +1870,9 @@ func Test_ProjectsGet_GetProjectItem(t *testing.T) { item := map[string]any{"id": 1001, "archived_at": nil, "content": map[string]any{"title": "Issue 1"}} t.Run("success organization", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/1/items/1001", Method: http.MethodGet}, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(item)) - }), - ), - ) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + GetOrgsProjectsV2ItemsByProjectByItemID: mockResponse(t, http.StatusOK, item), + }) client := gh.NewClient(mockedClient) deps := BaseDeps{ @@ -1941,7 +1899,7 @@ func Test_ProjectsGet_GetProjectItem(t *testing.T) { }) t.Run("missing item_id", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient() + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}) client := gh.NewClient(mockedClient) deps := BaseDeps{ Client: client, @@ -1991,23 +1949,12 @@ func Test_ProjectsWrite_AddProjectItem(t *testing.T) { addedItem := map[string]any{"id": 2001, "archived_at": nil} t.Run("success organization", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/1/items", Method: http.MethodPost}, - http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - body, _ := io.ReadAll(r.Body) - var payload map[string]any - _ = json.Unmarshal(body, &payload) - if payload["id"] == nil || payload["type"] == nil { - w.WriteHeader(http.StatusBadRequest) - _, _ = w.Write([]byte(`{"message":"bad request"}`)) - return - } - w.WriteHeader(http.StatusCreated) - _, _ = w.Write(mock.MustMarshal(addedItem)) - }), - ), - ) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PostOrgsProjectsV2ItemsByProject: expectRequestBody(t, map[string]any{ + "type": "Issue", + "id": float64(123), + }).andThen(mockResponse(t, http.StatusCreated, addedItem)), + }) client := gh.NewClient(mockedClient) deps := BaseDeps{ @@ -2035,7 +1982,7 @@ func Test_ProjectsWrite_AddProjectItem(t *testing.T) { }) t.Run("missing item_type", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient() + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}) client := gh.NewClient(mockedClient) deps := BaseDeps{ Client: client, @@ -2057,7 +2004,7 @@ func Test_ProjectsWrite_AddProjectItem(t *testing.T) { }) t.Run("invalid item_type", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient() + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}) client := gh.NewClient(mockedClient) deps := BaseDeps{ Client: client, @@ -2080,7 +2027,7 @@ func Test_ProjectsWrite_AddProjectItem(t *testing.T) { }) t.Run("unknown method", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient() + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}) client := gh.NewClient(mockedClient) deps := BaseDeps{ Client: client, @@ -2107,15 +2054,9 @@ func Test_ProjectsWrite_UpdateProjectItem(t *testing.T) { updatedItem := map[string]any{"id": 1001, "archived_at": nil} t.Run("success organization", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/1/items/1001", Method: http.MethodPatch}, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusOK) - _, _ = w.Write(mock.MustMarshal(updatedItem)) - }), - ), - ) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + PatchOrgsProjectsV2ItemsByProjectByItemID: mockResponse(t, http.StatusOK, updatedItem), + }) client := gh.NewClient(mockedClient) deps := BaseDeps{ @@ -2146,7 +2087,7 @@ func Test_ProjectsWrite_UpdateProjectItem(t *testing.T) { }) t.Run("missing updated_field", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient() + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}) client := gh.NewClient(mockedClient) deps := BaseDeps{ Client: client, @@ -2172,14 +2113,11 @@ func Test_ProjectsWrite_DeleteProjectItem(t *testing.T) { toolDef := ProjectsWrite(translations.NullTranslationHelper) t.Run("success organization", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient( - mock.WithRequestMatchHandler( - mock.EndpointPattern{Pattern: "/orgs/{org}/projectsV2/1/items/1001", Method: http.MethodDelete}, - http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { - w.WriteHeader(http.StatusNoContent) - }), - ), - ) + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{ + DeleteOrgsProjectsV2ItemsByProjectByItemID: http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.WriteHeader(http.StatusNoContent) + }), + }) client := gh.NewClient(mockedClient) deps := BaseDeps{ @@ -2203,7 +2141,7 @@ func Test_ProjectsWrite_DeleteProjectItem(t *testing.T) { }) t.Run("missing item_id", func(t *testing.T) { - mockedClient := mock.NewMockedHTTPClient() + mockedClient := MockHTTPClientWithHandlers(map[string]http.HandlerFunc{}) client := gh.NewClient(mockedClient) deps := BaseDeps{ Client: client, From ab23070b43a70fd0d4d7d516132e33bc8b79841b Mon Sep 17 00:00:00 2001 From: Florian Grousset Date: Sat, 25 Oct 2025 11:50:20 -0500 Subject: [PATCH 049/138] Update command instructions for terminal usage Clarified instructions to run commands in the terminal instead of Claude Code CLI. --- docs/installation-guides/install-claude.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/installation-guides/install-claude.md b/docs/installation-guides/install-claude.md index 1a5b789f4..ca8491aff 100644 --- a/docs/installation-guides/install-claude.md +++ b/docs/installation-guides/install-claude.md @@ -28,7 +28,7 @@ echo -e ".env\n.mcp.json" >> .gitignore ### Remote Server Setup (Streamable HTTP) -1. Run the following command in the Claude Code CLI +1. Run the following command in the terminal (not in Claude Code CLI): ```bash claude mcp add --transport http github https://api.githubcopilot.com/mcp -H "Authorization: Bearer YOUR_GITHUB_PAT" ``` @@ -43,7 +43,7 @@ claude mcp add --transport http github https://api.githubcopilot.com/mcp -H "Aut ### Local Server Setup (Docker required) ### With Docker -1. Run the following command in the Claude Code CLI: +1. Run the following command in the terminal (not in Claude Code CLI): ```bash claude mcp add github -e GITHUB_PERSONAL_ACCESS_TOKEN=YOUR_GITHUB_PAT -- docker run -i --rm -e GITHUB_PERSONAL_ACCESS_TOKEN ghcr.io/github/github-mcp-server ``` From cc9e8645c1427a2988e96e81dd34e1dc6ec0d31e Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Wed, 7 Jan 2026 16:11:03 +0100 Subject: [PATCH 050/138] Fix nil pointer dereference in completion handler The CompleteParams.Context field is optional (marked omitempty) and can be nil when clients don't send it. The code was accessing Context.Arguments directly without checking if Context was nil first, causing a panic. This fix adds a nil check for Context before accessing Arguments. --- pkg/github/repository_resource_completions.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/github/repository_resource_completions.go b/pkg/github/repository_resource_completions.go index aeb2d88a6..c70cfe948 100644 --- a/pkg/github/repository_resource_completions.go +++ b/pkg/github/repository_resource_completions.go @@ -33,8 +33,10 @@ func RepositoryResourceCompletionHandler(getClient GetClientFn) func(ctx context argName := req.Params.Argument.Name argValue := req.Params.Argument.Value - resolved := req.Params.Context.Arguments - if resolved == nil { + var resolved map[string]string + if req.Params.Context != nil && req.Params.Context.Arguments != nil { + resolved = req.Params.Context.Arguments + } else { resolved = map[string]string{} } From f2ff9d22fe392468fdd5454881eaeace683bc4f9 Mon Sep 17 00:00:00 2001 From: Tommaso Moro <37270480+tommaso-moro@users.noreply.github.com> Date: Thu, 8 Jan 2026 09:53:48 +0000 Subject: [PATCH 051/138] updated (#1756) --- docs/remote-server.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/remote-server.md b/docs/remote-server.md index d7d0f72b1..039d094fe 100644 --- a/docs/remote-server.md +++ b/docs/remote-server.md @@ -19,24 +19,24 @@ Below is a table of available toolsets for the remote GitHub MCP Server. Each to | Name | Description | API URL | 1-Click Install (VS Code) | Read-only Link | 1-Click Read-only Install (VS Code) | | ---- | ----------- | ------- | ------------------------- | -------------- | ----------------------------------- | -| apps
all | All available GitHub MCP tools | https://api.githubcopilot.com/mcp/ | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2F%22%7D) | [read-only](https://api.githubcopilot.com/mcp/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Freadonly%22%7D) | -| workflow
Actions | GitHub Actions workflows and CI/CD operations | https://api.githubcopilot.com/mcp/x/actions | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-actions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Factions%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/actions/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-actions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Factions%2Freadonly%22%7D) | -| codescan
Code Security | Code security related tools, such as GitHub Code Scanning | https://api.githubcopilot.com/mcp/x/code_security | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_security&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_security%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/code_security/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_security&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_security%2Freadonly%22%7D) | -| dependabot
Dependabot | Dependabot tools | https://api.githubcopilot.com/mcp/x/dependabot | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-dependabot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdependabot%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/dependabot/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-dependabot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdependabot%2Freadonly%22%7D) | -| comment-discussion
Discussions | GitHub Discussions related tools | https://api.githubcopilot.com/mcp/x/discussions | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/discussions/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%2Freadonly%22%7D) | -| logo-gist
Gists | GitHub Gist related tools | https://api.githubcopilot.com/mcp/x/gists | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-gists&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgists%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/gists/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-gists&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgists%2Freadonly%22%7D) | -| git-branch
Git | GitHub Git API related tools for low-level Git operations | https://api.githubcopilot.com/mcp/x/git | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-git&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgit%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/git/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-git&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgit%2Freadonly%22%7D) | -| issue-opened
Issues | GitHub Issues related tools | https://api.githubcopilot.com/mcp/x/issues | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-issues&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fissues%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/issues/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-issues&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fissues%2Freadonly%22%7D) | -| tag
Labels | GitHub Labels related tools | https://api.githubcopilot.com/mcp/x/labels | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-labels&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Flabels%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/labels/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-labels&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Flabels%2Freadonly%22%7D) | -| bell
Notifications | GitHub Notifications related tools | https://api.githubcopilot.com/mcp/x/notifications | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-notifications&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fnotifications%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/notifications/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-notifications&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fnotifications%2Freadonly%22%7D) | -| organization
Organizations | GitHub Organization related tools | https://api.githubcopilot.com/mcp/x/orgs | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-orgs&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Forgs%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/orgs/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-orgs&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Forgs%2Freadonly%22%7D) | -| project
Projects | GitHub Projects related tools | https://api.githubcopilot.com/mcp/x/projects | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-projects&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fprojects%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/projects/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-projects&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fprojects%2Freadonly%22%7D) | -| git-pull-request
Pull Requests | GitHub Pull Request related tools | https://api.githubcopilot.com/mcp/x/pull_requests | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-pull_requests&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fpull_requests%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/pull_requests/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-pull_requests&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fpull_requests%2Freadonly%22%7D) | -| repo
Repositories | GitHub Repository related tools | https://api.githubcopilot.com/mcp/x/repos | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-repos&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Frepos%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/repos/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-repos&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Frepos%2Freadonly%22%7D) | -| shield-lock
Secret Protection | Secret protection related tools, such as GitHub Secret Scanning | https://api.githubcopilot.com/mcp/x/secret_protection | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-secret_protection&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecret_protection%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/secret_protection/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-secret_protection&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecret_protection%2Freadonly%22%7D) | -| shield
Security Advisories | Security advisories related tools | https://api.githubcopilot.com/mcp/x/security_advisories | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-security_advisories&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecurity_advisories%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/security_advisories/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-security_advisories&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecurity_advisories%2Freadonly%22%7D) | -| star
Stargazers | GitHub Stargazers related tools | https://api.githubcopilot.com/mcp/x/stargazers | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-stargazers&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fstargazers%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/stargazers/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-stargazers&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fstargazers%2Freadonly%22%7D) | -| people
Users | GitHub User related tools | https://api.githubcopilot.com/mcp/x/users | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-users&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fusers%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/users/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-users&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fusers%2Freadonly%22%7D) | +| apps
`all` | All available GitHub MCP tools | https://api.githubcopilot.com/mcp/ | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2F%22%7D) | [read-only](https://api.githubcopilot.com/mcp/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Freadonly%22%7D) | +| workflow
`actions` | GitHub Actions workflows and CI/CD operations | https://api.githubcopilot.com/mcp/x/actions | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-actions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Factions%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/actions/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-actions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Factions%2Freadonly%22%7D) | +| codescan
`code_security` | Code security related tools, such as GitHub Code Scanning | https://api.githubcopilot.com/mcp/x/code_security | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_security&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_security%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/code_security/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_security&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_security%2Freadonly%22%7D) | +| dependabot
`dependabot` | Dependabot tools | https://api.githubcopilot.com/mcp/x/dependabot | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-dependabot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdependabot%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/dependabot/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-dependabot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdependabot%2Freadonly%22%7D) | +| comment-discussion
`discussions` | GitHub Discussions related tools | https://api.githubcopilot.com/mcp/x/discussions | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/discussions/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%2Freadonly%22%7D) | +| logo-gist
`gists` | GitHub Gist related tools | https://api.githubcopilot.com/mcp/x/gists | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-gists&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgists%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/gists/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-gists&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgists%2Freadonly%22%7D) | +| git-branch
`git` | GitHub Git API related tools for low-level Git operations | https://api.githubcopilot.com/mcp/x/git | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-git&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgit%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/git/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-git&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgit%2Freadonly%22%7D) | +| issue-opened
`issues` | GitHub Issues related tools | https://api.githubcopilot.com/mcp/x/issues | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-issues&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fissues%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/issues/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-issues&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fissues%2Freadonly%22%7D) | +| tag
`labels` | GitHub Labels related tools | https://api.githubcopilot.com/mcp/x/labels | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-labels&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Flabels%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/labels/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-labels&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Flabels%2Freadonly%22%7D) | +| bell
`notifications` | GitHub Notifications related tools | https://api.githubcopilot.com/mcp/x/notifications | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-notifications&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fnotifications%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/notifications/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-notifications&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fnotifications%2Freadonly%22%7D) | +| organization
`orgs` | GitHub Organization related tools | https://api.githubcopilot.com/mcp/x/orgs | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-orgs&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Forgs%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/orgs/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-orgs&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Forgs%2Freadonly%22%7D) | +| project
`projects` | GitHub Projects related tools | https://api.githubcopilot.com/mcp/x/projects | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-projects&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fprojects%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/projects/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-projects&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fprojects%2Freadonly%22%7D) | +| git-pull-request
`pull_requests` | GitHub Pull Request related tools | https://api.githubcopilot.com/mcp/x/pull_requests | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-pull_requests&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fpull_requests%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/pull_requests/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-pull_requests&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fpull_requests%2Freadonly%22%7D) | +| repo
`repos` | GitHub Repository related tools | https://api.githubcopilot.com/mcp/x/repos | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-repos&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Frepos%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/repos/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-repos&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Frepos%2Freadonly%22%7D) | +| shield-lock
`secret_protection` | Secret protection related tools, such as GitHub Secret Scanning | https://api.githubcopilot.com/mcp/x/secret_protection | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-secret_protection&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecret_protection%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/secret_protection/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-secret_protection&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecret_protection%2Freadonly%22%7D) | +| shield
`security_advisories` | Security advisories related tools | https://api.githubcopilot.com/mcp/x/security_advisories | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-security_advisories&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecurity_advisories%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/security_advisories/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-security_advisories&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecurity_advisories%2Freadonly%22%7D) | +| star
`stargazers` | GitHub Stargazers related tools | https://api.githubcopilot.com/mcp/x/stargazers | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-stargazers&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fstargazers%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/stargazers/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-stargazers&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fstargazers%2Freadonly%22%7D) | +| people
`users` | GitHub User related tools | https://api.githubcopilot.com/mcp/x/users | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-users&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fusers%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/users/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-users&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fusers%2Freadonly%22%7D) | ### Additional _Remote_ Server Toolsets @@ -46,9 +46,9 @@ These toolsets are only available in the remote GitHub MCP Server and are not in | Name | Description | API URL | 1-Click Install (VS Code) | Read-only Link | 1-Click Read-only Install (VS Code) | | ---- | ----------- | ------- | ------------------------- | -------------- | ----------------------------------- | -| copilot
Copilot | Copilot related tools | https://api.githubcopilot.com/mcp/x/copilot | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/copilot/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot%2Freadonly%22%7D) | -| copilot
Copilot Spaces | Copilot Spaces tools | https://api.githubcopilot.com/mcp/x/copilot_spaces | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot_spaces&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot_spaces%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/copilot_spaces/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot_spaces&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot_spaces%2Freadonly%22%7D) | -| book
Github Support Docs Search | Retrieve documentation to answer GitHub product and support questions. Topics include: GitHub Actions Workflows, Authentication, ... | https://api.githubcopilot.com/mcp/x/github_support_docs_search | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-github_support_docs_search&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgithub_support_docs_search%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/github_support_docs_search/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-github_support_docs_search&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgithub_support_docs_search%2Freadonly%22%7D) | +| copilot
`copilot` | Copilot related tools | https://api.githubcopilot.com/mcp/x/copilot | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/copilot/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot%2Freadonly%22%7D) | +| copilot
`copilot_spaces` | Copilot Spaces tools | https://api.githubcopilot.com/mcp/x/copilot_spaces | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot_spaces&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot_spaces%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/copilot_spaces/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot_spaces&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot_spaces%2Freadonly%22%7D) | +| book
`github_support_docs_search` | Retrieve documentation to answer GitHub product and support questions. Topics include: GitHub Actions Workflows, Authentication, ... | https://api.githubcopilot.com/mcp/x/github_support_docs_search | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-github_support_docs_search&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgithub_support_docs_search%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/github_support_docs_search/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-github_support_docs_search&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgithub_support_docs_search%2Freadonly%22%7D) | ### Optional Headers From ee8f4e6bc594bcdaee496c566cb43995025135a1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:53:24 +0000 Subject: [PATCH 052/138] Add list-scopes command using inventory architecture Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com> --- script/list-scopes | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100755 script/list-scopes diff --git a/script/list-scopes b/script/list-scopes new file mode 100755 index 000000000..2f7502823 --- /dev/null +++ b/script/list-scopes @@ -0,0 +1,24 @@ +#!/bin/bash +# +# List required OAuth scopes for enabled tools. +# +# Usage: +# script/list-scopes [--toolsets=...] [--output=text|json|summary] +# +# Examples: +# script/list-scopes +# script/list-scopes --toolsets=all --output=json +# script/list-scopes --toolsets=repos,issues --output=summary +# + +set -e + +cd "$(dirname "$0")/.." + +# Build the server if it doesn't exist or is outdated +if [ ! -f github-mcp-server ] || [ cmd/github-mcp-server/list_scopes.go -nt github-mcp-server ]; then + echo "Building github-mcp-server..." >&2 + go build -o github-mcp-server ./cmd/github-mcp-server +fi + +exec ./github-mcp-server list-scopes "$@" From d2df189e980423a0b7afaf8e33c71a4ce345cb4e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 17:55:59 +0000 Subject: [PATCH 053/138] Add list_scopes.go implementation file Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com> --- cmd/github-mcp-server/list_scopes.go | 315 +++++++++++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 cmd/github-mcp-server/list_scopes.go diff --git a/cmd/github-mcp-server/list_scopes.go b/cmd/github-mcp-server/list_scopes.go new file mode 100644 index 000000000..a63bd44f5 --- /dev/null +++ b/cmd/github-mcp-server/list_scopes.go @@ -0,0 +1,315 @@ +package main + +import ( + "context" + "encoding/json" + "fmt" + "os" + "sort" + "strings" + + "github.com/github/github-mcp-server/pkg/github" + "github.com/github/github-mcp-server/pkg/inventory" + "github.com/github/github-mcp-server/pkg/translations" + "github.com/spf13/cobra" + "github.com/spf13/viper" +) + +// ToolScopeInfo contains scope information for a single tool. +type ToolScopeInfo struct { + Name string `json:"name"` + Toolset string `json:"toolset"` + ReadOnly bool `json:"read_only"` + RequiredScopes []string `json:"required_scopes"` + AcceptedScopes []string `json:"accepted_scopes,omitempty"` +} + +// ScopesOutput is the full output structure for the list-scopes command. +type ScopesOutput struct { + Tools []ToolScopeInfo `json:"tools"` + UniqueScopes []string `json:"unique_scopes"` + ScopesByTool map[string][]string `json:"scopes_by_tool"` + ToolsByScope map[string][]string `json:"tools_by_scope"` + EnabledToolsets []string `json:"enabled_toolsets"` + ReadOnly bool `json:"read_only"` +} + +var listScopesCmd = &cobra.Command{ + Use: "list-scopes", + Short: "List required OAuth scopes for enabled tools", + Long: `List the required OAuth scopes for all enabled tools. + +This command creates an inventory based on the same flags as the stdio command +and outputs the required OAuth scopes for each enabled tool. This is useful for +determining what scopes a token needs to use specific tools. + +The output format can be controlled with the --output flag: + - text (default): Human-readable text output + - json: JSON output for programmatic use + - summary: Just the unique scopes needed + +Examples: + # List scopes for default toolsets + github-mcp-server list-scopes + + # List scopes for specific toolsets + github-mcp-server list-scopes --toolsets=repos,issues,pull_requests + + # List scopes for all toolsets + github-mcp-server list-scopes --toolsets=all + + # Output as JSON + github-mcp-server list-scopes --output=json + + # Just show unique scopes needed + github-mcp-server list-scopes --output=summary`, + RunE: func(_ *cobra.Command, _ []string) error { + return runListScopes() + }, +} + +func init() { + listScopesCmd.Flags().StringP("output", "o", "text", "Output format: text, json, or summary") + _ = viper.BindPFlag("list-scopes-output", listScopesCmd.Flags().Lookup("output")) + + rootCmd.AddCommand(listScopesCmd) +} + +func runListScopes() error { + // Get toolsets configuration (same logic as stdio command) + var enabledToolsets []string + if viper.IsSet("toolsets") { + if err := viper.UnmarshalKey("toolsets", &enabledToolsets); err != nil { + return fmt.Errorf("failed to unmarshal toolsets: %w", err) + } + } + // else: enabledToolsets stays nil, meaning "use defaults" + + // Get specific tools (similar to toolsets) + var enabledTools []string + if viper.IsSet("tools") { + if err := viper.UnmarshalKey("tools", &enabledTools); err != nil { + return fmt.Errorf("failed to unmarshal tools: %w", err) + } + } + + readOnly := viper.GetBool("read-only") + outputFormat := viper.GetString("list-scopes-output") + + // Create translation helper + t, _ := translations.TranslationHelper() + + // Build inventory using the same logic as the stdio server + inventoryBuilder := github.NewInventory(t). + WithReadOnly(readOnly) + + // Configure toolsets (same as stdio) + if enabledToolsets != nil { + inventoryBuilder = inventoryBuilder.WithToolsets(enabledToolsets) + } + + // Configure specific tools + if len(enabledTools) > 0 { + inventoryBuilder = inventoryBuilder.WithTools(enabledTools) + } + + inv := inventoryBuilder.Build() + + // Collect all tools and their scopes + output := collectToolScopes(inv, readOnly) + + // Output based on format + switch outputFormat { + case "json": + return outputJSON(output) + case "summary": + return outputSummary(output) + default: + return outputText(output) + } +} + +func collectToolScopes(inv *inventory.Inventory, readOnly bool) ScopesOutput { + var tools []ToolScopeInfo + scopeSet := make(map[string]bool) + scopesByTool := make(map[string][]string) + toolsByScope := make(map[string][]string) + + // Get all available tools from the inventory + // Use context.Background() for feature flag evaluation + availableTools := inv.AvailableTools(context.Background()) + + for _, serverTool := range availableTools { + tool := serverTool.Tool + + // Get scope information directly from ServerTool + requiredScopes := serverTool.RequiredScopes + acceptedScopes := serverTool.AcceptedScopes + + // Determine if tool is read-only + isReadOnly := serverTool.IsReadOnly() + + toolInfo := ToolScopeInfo{ + Name: tool.Name, + Toolset: string(serverTool.Toolset.ID), + ReadOnly: isReadOnly, + RequiredScopes: requiredScopes, + AcceptedScopes: acceptedScopes, + } + tools = append(tools, toolInfo) + + // Track unique scopes + for _, s := range requiredScopes { + scopeSet[s] = true + toolsByScope[s] = append(toolsByScope[s], tool.Name) + } + + // Track scopes by tool + scopesByTool[tool.Name] = requiredScopes + } + + // Sort tools by name + sort.Slice(tools, func(i, j int) bool { + return tools[i].Name < tools[j].Name + }) + + // Get unique scopes as sorted slice + var uniqueScopes []string + for s := range scopeSet { + uniqueScopes = append(uniqueScopes, s) + } + sort.Strings(uniqueScopes) + + // Sort tools within each scope + for scope := range toolsByScope { + sort.Strings(toolsByScope[scope]) + } + + // Get enabled toolsets as string slice + toolsetIDs := inv.ToolsetIDs() + toolsetIDStrs := make([]string, len(toolsetIDs)) + for i, id := range toolsetIDs { + toolsetIDStrs[i] = string(id) + } + + return ScopesOutput{ + Tools: tools, + UniqueScopes: uniqueScopes, + ScopesByTool: scopesByTool, + ToolsByScope: toolsByScope, + EnabledToolsets: toolsetIDStrs, + ReadOnly: readOnly, + } +} + +func outputJSON(output ScopesOutput) error { + encoder := json.NewEncoder(os.Stdout) + encoder.SetIndent("", " ") + return encoder.Encode(output) +} + +func outputSummary(output ScopesOutput) error { + if len(output.UniqueScopes) == 0 { + fmt.Println("No OAuth scopes required for enabled tools.") + return nil + } + + fmt.Println("Required OAuth scopes for enabled tools:") + fmt.Println() + for _, scope := range output.UniqueScopes { + if scope == "" { + fmt.Println(" (no scope required for public read access)") + } else { + fmt.Printf(" %s\n", scope) + } + } + fmt.Printf("\nTotal: %d unique scope(s)\n", len(output.UniqueScopes)) + return nil +} + +func outputText(output ScopesOutput) error { + fmt.Printf("OAuth Scopes for Enabled Tools\n") + fmt.Printf("==============================\n\n") + + fmt.Printf("Enabled Toolsets: %s\n", strings.Join(output.EnabledToolsets, ", ")) + fmt.Printf("Read-Only Mode: %v\n\n", output.ReadOnly) + + // Group tools by toolset + toolsByToolset := make(map[string][]ToolScopeInfo) + for _, tool := range output.Tools { + toolsByToolset[tool.Toolset] = append(toolsByToolset[tool.Toolset], tool) + } + + // Get sorted toolset names + var toolsetNames []string + for name := range toolsByToolset { + toolsetNames = append(toolsetNames, name) + } + sort.Strings(toolsetNames) + + for _, toolsetName := range toolsetNames { + tools := toolsByToolset[toolsetName] + fmt.Printf("## %s\n\n", formatToolsetNameForOutput(toolsetName)) + + for _, tool := range tools { + rwIndicator := "📝" + if tool.ReadOnly { + rwIndicator = "👁" + } + + scopeStr := "(no scope required)" + if len(tool.RequiredScopes) > 0 { + scopeStr = strings.Join(tool.RequiredScopes, ", ") + } + + fmt.Printf(" %s %s: %s\n", rwIndicator, tool.Name, scopeStr) + } + fmt.Println() + } + + // Summary + fmt.Println("## Summary") + fmt.Println() + if len(output.UniqueScopes) == 0 { + fmt.Println("No OAuth scopes required for enabled tools.") + } else { + fmt.Println("Unique scopes required:") + for _, scope := range output.UniqueScopes { + if scope == "" { + fmt.Println(" • (no scope - public read access)") + } else { + fmt.Printf(" • %s\n", scope) + } + } + } + fmt.Printf("\nTotal: %d tools, %d unique scopes\n", len(output.Tools), len(output.UniqueScopes)) + + // Legend + fmt.Println("\nLegend: 👁 = read-only, 📝 = read-write") + + return nil +} + +func formatToolsetNameForOutput(name string) string { + switch name { + case "pull_requests": + return "Pull Requests" + case "repos": + return "Repositories" + case "code_security": + return "Code Security" + case "secret_protection": + return "Secret Protection" + case "orgs": + return "Organizations" + default: + // Capitalize first letter and replace underscores with spaces + parts := strings.Split(name, "_") + for i, part := range parts { + if len(part) > 0 { + parts[i] = strings.ToUpper(string(part[0])) + part[1:] + } + } + return strings.Join(parts, " ") + } +} From cd75b9b96b928641cc3a6bf26433569994d52fa9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 18:00:00 +0000 Subject: [PATCH 054/138] Refactor formatToolsetName to shared helper function Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com> --- cmd/github-mcp-server/generate_docs.go | 24 ------------------------ cmd/github-mcp-server/list_scopes.go | 26 +------------------------- 2 files changed, 1 insertion(+), 49 deletions(-) diff --git a/cmd/github-mcp-server/generate_docs.go b/cmd/github-mcp-server/generate_docs.go index 85d7ecdbf..14d771330 100644 --- a/cmd/github-mcp-server/generate_docs.go +++ b/cmd/github-mcp-server/generate_docs.go @@ -198,30 +198,6 @@ func generateToolsDoc(r *inventory.Inventory) string { return buf.String() } -func formatToolsetName(name string) string { - switch name { - case "pull_requests": - return "Pull Requests" - case "repos": - return "Repositories" - case "code_security": - return "Code Security" - case "secret_protection": - return "Secret Protection" - case "orgs": - return "Organizations" - default: - // Fallback: capitalize first letter and replace underscores with spaces - parts := strings.Split(name, "_") - for i, part := range parts { - if len(part) > 0 { - parts[i] = strings.ToUpper(string(part[0])) + part[1:] - } - } - return strings.Join(parts, " ") - } -} - func writeToolDoc(buf *strings.Builder, tool inventory.ServerTool) { // Tool name (no icon - section header already has the toolset icon) fmt.Fprintf(buf, "- **%s** - %s\n", tool.Tool.Name, tool.Tool.Annotations.Title) diff --git a/cmd/github-mcp-server/list_scopes.go b/cmd/github-mcp-server/list_scopes.go index a63bd44f5..9f7dcabce 100644 --- a/cmd/github-mcp-server/list_scopes.go +++ b/cmd/github-mcp-server/list_scopes.go @@ -249,7 +249,7 @@ func outputText(output ScopesOutput) error { for _, toolsetName := range toolsetNames { tools := toolsByToolset[toolsetName] - fmt.Printf("## %s\n\n", formatToolsetNameForOutput(toolsetName)) + fmt.Printf("## %s\n\n", formatToolsetName(toolsetName)) for _, tool := range tools { rwIndicator := "📝" @@ -289,27 +289,3 @@ func outputText(output ScopesOutput) error { return nil } - -func formatToolsetNameForOutput(name string) string { - switch name { - case "pull_requests": - return "Pull Requests" - case "repos": - return "Repositories" - case "code_security": - return "Code Security" - case "secret_protection": - return "Secret Protection" - case "orgs": - return "Organizations" - default: - // Capitalize first letter and replace underscores with spaces - parts := strings.Split(name, "_") - for i, part := range parts { - if len(part) > 0 { - parts[i] = strings.ToUpper(string(part[0])) + part[1:] - } - } - return strings.Join(parts, " ") - } -} From 33014a66ef908cf1cad3b132bd61bc17ca149046 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 18:00:47 +0000 Subject: [PATCH 055/138] Add helpers.go with shared formatToolsetName function Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com> --- cmd/github-mcp-server/helpers.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 cmd/github-mcp-server/helpers.go diff --git a/cmd/github-mcp-server/helpers.go b/cmd/github-mcp-server/helpers.go new file mode 100644 index 000000000..c5f498813 --- /dev/null +++ b/cmd/github-mcp-server/helpers.go @@ -0,0 +1,29 @@ +package main + +import "strings" + +// formatToolsetName converts a toolset ID to a human-readable name. +// Used by both generate_docs.go and list_scopes.go for consistent formatting. +func formatToolsetName(name string) string { + switch name { + case "pull_requests": + return "Pull Requests" + case "repos": + return "Repositories" + case "code_security": + return "Code Security" + case "secret_protection": + return "Secret Protection" + case "orgs": + return "Organizations" + default: + // Fallback: capitalize first letter and replace underscores with spaces + parts := strings.Split(name, "_") + for i, part := range parts { + if len(part) > 0 { + parts[i] = strings.ToUpper(string(part[0])) + part[1:] + } + } + return strings.Join(parts, " ") + } +} From ff0e67e00899df9a185fbbaf423c9d15f2093ffa Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 6 Jan 2026 18:03:29 +0000 Subject: [PATCH 056/138] Add formatScopeDisplay helper and improve empty scope handling Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com> --- cmd/github-mcp-server/list_scopes.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/cmd/github-mcp-server/list_scopes.go b/cmd/github-mcp-server/list_scopes.go index 9f7dcabce..2d1817500 100644 --- a/cmd/github-mcp-server/list_scopes.go +++ b/cmd/github-mcp-server/list_scopes.go @@ -75,6 +75,14 @@ func init() { rootCmd.AddCommand(listScopesCmd) } +// formatScopeDisplay formats a scope string for display, handling empty scopes. +func formatScopeDisplay(scope string) string { + if scope == "" { + return "(no scope required for public read access)" + } + return scope +} + func runListScopes() error { // Get toolsets configuration (same logic as stdio command) var enabledToolsets []string @@ -217,11 +225,7 @@ func outputSummary(output ScopesOutput) error { fmt.Println("Required OAuth scopes for enabled tools:") fmt.Println() for _, scope := range output.UniqueScopes { - if scope == "" { - fmt.Println(" (no scope required for public read access)") - } else { - fmt.Printf(" %s\n", scope) - } + fmt.Printf(" %s\n", formatScopeDisplay(scope)) } fmt.Printf("\nTotal: %d unique scope(s)\n", len(output.UniqueScopes)) return nil @@ -275,11 +279,7 @@ func outputText(output ScopesOutput) error { } else { fmt.Println("Unique scopes required:") for _, scope := range output.UniqueScopes { - if scope == "" { - fmt.Println(" • (no scope - public read access)") - } else { - fmt.Printf(" • %s\n", scope) - } + fmt.Printf(" • %s\n", formatScopeDisplay(scope)) } } fmt.Printf("\nTotal: %d tools, %d unique scopes\n", len(output.Tools), len(output.UniqueScopes)) From e33550311a5ddd66f1a72b14111554457ddd59a4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 8 Jan 2026 14:29:49 +0000 Subject: [PATCH 057/138] Update Claude Code installation command to use add-json format for v2.1.1+ Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com> --- docs/installation-guides/install-claude.md | 30 +++++++++++++++++----- 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/docs/installation-guides/install-claude.md b/docs/installation-guides/install-claude.md index ca8491aff..8f01a59e4 100644 --- a/docs/installation-guides/install-claude.md +++ b/docs/installation-guides/install-claude.md @@ -28,15 +28,23 @@ echo -e ".env\n.mcp.json" >> .gitignore ### Remote Server Setup (Streamable HTTP) +> **Note**: For Claude Code versions **2.1.1 and newer**, use the `add-json` command format below. For older versions, see the [legacy command format](#for-older-versions-of-claude-code). + 1. Run the following command in the terminal (not in Claude Code CLI): ```bash -claude mcp add --transport http github https://api.githubcopilot.com/mcp -H "Authorization: Bearer YOUR_GITHUB_PAT" +claude mcp add-json github '{"type":"http","url":"https://api.githubcopilot.com/mcp","headers":{"Authorization":"Bearer YOUR_GITHUB_PAT"}}' --scope user ``` With an environment variable: ```bash -claude mcp add --transport http github https://api.githubcopilot.com/mcp -H "Authorization: Bearer $(grep GITHUB_PAT .env | cut -d '=' -f2)" +claude mcp add-json github '{"type":"http","url":"https://api.githubcopilot.com/mcp","headers":{"Authorization":"Bearer '"$(grep GITHUB_PAT .env | cut -d '=' -f2)"'"}}' --scope user ``` + +> **About the `--scope` flag**: This specifies where the configuration is stored. Options: +> - `local` (default): Available only to you in the current project (was called `project` in older versions) +> - `project`: Shared with everyone in the project via `.mcp.json` file +> - `user`: Available to you across all projects (was called `global` in older versions) + 2. Restart Claude Code 3. Run `claude mcp list` to see if the GitHub server is configured @@ -72,6 +80,19 @@ claude mcp list claude mcp get github ``` +### For Older Versions of Claude Code + +If you're using Claude Code version **2.1.0 or earlier**, use this legacy command format: + +```bash +claude mcp add --transport http github https://api.githubcopilot.com/mcp -H "Authorization: Bearer YOUR_GITHUB_PAT" +``` + +With an environment variable: +```bash +claude mcp add --transport http github https://api.githubcopilot.com/mcp -H "Authorization: Bearer $(grep GITHUB_PAT .env | cut -d '=' -f2)" +``` + --- ## Claude Desktop @@ -161,7 +182,4 @@ Add this codeblock to your `claude_desktop_config.json`: - The npm package `@modelcontextprotocol/server-github` is deprecated as of April 2025 - Remote server requires Streamable HTTP support (check your Claude version) -- Configuration scopes for Claude Code: - - `-s user`: Available across all projects - - `-s project`: Shared via `.mcp.json` file - - Default: `local` (current project only) +- For Claude Code configuration scopes, see the `--scope` flag documentation in the [Remote Server Setup](#remote-server-setup-streamable-http) section From c061804405bd4ac96ea3c81d110195a2194e415a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 8 Jan 2026 21:28:57 +0000 Subject: [PATCH 058/138] Use default scope in examples and clarify --scope flag is optional Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com> --- docs/installation-guides/install-claude.md | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/installation-guides/install-claude.md b/docs/installation-guides/install-claude.md index 8f01a59e4..ff1b26d70 100644 --- a/docs/installation-guides/install-claude.md +++ b/docs/installation-guides/install-claude.md @@ -32,18 +32,20 @@ echo -e ".env\n.mcp.json" >> .gitignore 1. Run the following command in the terminal (not in Claude Code CLI): ```bash -claude mcp add-json github '{"type":"http","url":"https://api.githubcopilot.com/mcp","headers":{"Authorization":"Bearer YOUR_GITHUB_PAT"}}' --scope user +claude mcp add-json github '{"type":"http","url":"https://api.githubcopilot.com/mcp","headers":{"Authorization":"Bearer YOUR_GITHUB_PAT"}}' ``` With an environment variable: ```bash -claude mcp add-json github '{"type":"http","url":"https://api.githubcopilot.com/mcp","headers":{"Authorization":"Bearer '"$(grep GITHUB_PAT .env | cut -d '=' -f2)"'"}}' --scope user +claude mcp add-json github '{"type":"http","url":"https://api.githubcopilot.com/mcp","headers":{"Authorization":"Bearer '"$(grep GITHUB_PAT .env | cut -d '=' -f2)"'"}}' ``` -> **About the `--scope` flag**: This specifies where the configuration is stored. Options: +> **About the `--scope` flag** (optional): Use this to specify where the configuration is stored: > - `local` (default): Available only to you in the current project (was called `project` in older versions) > - `project`: Shared with everyone in the project via `.mcp.json` file > - `user`: Available to you across all projects (was called `global` in older versions) +> +> Example: Add `--scope user` to the end of the command to make it available across all projects. 2. Restart Claude Code 3. Run `claude mcp list` to see if the GitHub server is configured From b1ab893af3dbbd20fa0ee18d029eef644a3e671e Mon Sep 17 00:00:00 2001 From: Tommaso Moro <37270480+tommaso-moro@users.noreply.github.com> Date: Fri, 9 Jan 2026 12:10:42 +0000 Subject: [PATCH 059/138] bug fix (#1775) --- pkg/inventory/builder.go | 9 ++++-- pkg/inventory/registry_test.go | 54 ++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/pkg/inventory/builder.go b/pkg/inventory/builder.go index a0ed2baee..0400c2a24 100644 --- a/pkg/inventory/builder.go +++ b/pkg/inventory/builder.go @@ -149,11 +149,14 @@ func (b *Builder) Build() *Inventory { if len(b.additionalTools) > 0 { r.additionalTools = make(map[string]bool, len(b.additionalTools)) for _, name := range b.additionalTools { - // Resolve deprecated aliases to canonical names + // Always include the original name - this handles the case where + // the tool exists but is controlled by a feature flag that's OFF. + r.additionalTools[name] = true + // Also include the canonical name if this is a deprecated alias. + // This handles the case where the feature flag is ON and only + // the new consolidated tool is available. if canonical, isAlias := b.deprecatedAliases[name]; isAlias { r.additionalTools[canonical] = true - } else { - r.additionalTools[name] = true } } } diff --git a/pkg/inventory/registry_test.go b/pkg/inventory/registry_test.go index 742ad3646..2c3262873 100644 --- a/pkg/inventory/registry_test.go +++ b/pkg/inventory/registry_test.go @@ -1688,3 +1688,57 @@ func TestForMCPRequest_ToolsCall_FeatureFlaggedVariants(t *testing.T) { availableOn[0].FeatureFlagEnable, availableOn[0].FeatureFlagDisable) } } + +// TestWithTools_DeprecatedAliasAndFeatureFlag tests that deprecated aliases work correctly +// when the old tool is controlled by a feature flag. This covers the scenario where: +// - Old tool "old_tool" has FeatureFlagDisable="my_flag" (available when flag is OFF) +// - New tool "new_tool" has FeatureFlagEnable="my_flag" (available when flag is ON) +// - Deprecated alias maps "old_tool" -> "new_tool" +// - User specifies --tools=old_tool +// Expected behavior: +// - Flag OFF: old_tool should be available (not the new_tool via alias) +// - Flag ON: new_tool should be available (via alias resolution) +func TestWithTools_DeprecatedAliasAndFeatureFlag(t *testing.T) { + oldTool := mockToolWithFlags("old_tool", "actions", true, "", "my_flag") + newTool := mockToolWithFlags("new_tool", "actions", true, "my_flag", "") + tools := []ServerTool{oldTool, newTool} + + deprecatedAliases := map[string]string{ + "old_tool": "new_tool", + } + + // Test 1: Flag OFF - old_tool should be available via direct name match + // (not via alias resolution to new_tool, since old_tool still exists) + regFlagOff := NewBuilder(). + SetTools(tools). + WithDeprecatedAliases(deprecatedAliases). + WithToolsets([]string{}). // No toolsets enabled + WithTools([]string{"old_tool"}). // Explicitly request old tool + Build() + availableOff := regFlagOff.AvailableTools(context.Background()) + if len(availableOff) != 1 { + t.Fatalf("Flag OFF: Expected 1 tool, got %d", len(availableOff)) + } + if availableOff[0].Tool.Name != "old_tool" { + t.Errorf("Flag OFF: Expected old_tool, got %s", availableOff[0].Tool.Name) + } + + // Test 2: Flag ON - new_tool should be available via alias resolution + checker := func(_ context.Context, flag string) (bool, error) { + return flag == "my_flag", nil + } + regFlagOn := NewBuilder(). + SetTools(tools). + WithDeprecatedAliases(deprecatedAliases). + WithToolsets([]string{}). // No toolsets enabled + WithTools([]string{"old_tool"}). // Request old tool name + WithFeatureChecker(checker). + Build() + availableOn := regFlagOn.AvailableTools(context.Background()) + if len(availableOn) != 1 { + t.Fatalf("Flag ON: Expected 1 tool, got %d", len(availableOn)) + } + if availableOn[0].Tool.Name != "new_tool" { + t.Errorf("Flag ON: Expected new_tool (via alias), got %s", availableOn[0].Tool.Name) + } +} From 44d9e1329e416082619a78a3ff8c4ef29dc3d09b Mon Sep 17 00:00:00 2001 From: Ksenia Bobrova Date: Fri, 9 Jan 2026 13:41:11 +0100 Subject: [PATCH 060/138] Bringing back local mcp server registry config (#1767) * Bringing back local mcp server registry config * Making auth header optional --- server.json | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/server.json b/server.json index 83b4e06be..15fdf47bd 100644 --- a/server.json +++ b/server.json @@ -8,6 +8,31 @@ "source": "github" }, "version": "${VERSION}", + "packages": [ + { + "registryType": "oci", + "identifier": "ghcr.io/github/github-mcp-server:${VERSION}", + "transport": { + "type": "stdio" + }, + "runtimeArguments": [ + { + "type": "named", + "name": "-e", + "description": "Set an environment variable in the runtime", + "value": "GITHUB_PERSONAL_ACCESS_TOKEN={token}", + "isRequired": true, + "variables": { + "token": { + "isRequired": true, + "isSecret": true, + "format": "string" + } + } + } + ] + } + ], "remotes": [ { "type": "streamable-http", @@ -15,8 +40,7 @@ "headers": [ { "name": "Authorization", - "description": "Authentication token (PAT or App token)", - "isRequired": true, + "description": "Authorization header with authentication token (PAT or App token)", "isSecret": true } ] From 8058d3070974a490e7e6bdf707da4d6b418dfff1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 10 Jan 2026 13:54:48 +0000 Subject: [PATCH 061/138] Update automation to use toolset IDs instead of display names The generate-docs command now outputs toolset IDs (e.g., `actions`, `code_security`) wrapped in backticks instead of display names (e.g., "Actions", "Code Security"). This ensures the manual changes from PR #1756 persist when the docs are regenerated, fixing the issue where users need to configure the actual toolset ID, not the display name. Changes: - Modified generateRemoteToolsetsDoc() to use `idStr` instead of `formattedName()` - Modified generateRemoteOnlyToolsetsDoc() to use `idStr` instead of `formattedName()` - Both functions now wrap the toolset ID in backticks for clarity Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com> --- cmd/github-mcp-server/generate_docs.go | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/cmd/github-mcp-server/generate_docs.go b/cmd/github-mcp-server/generate_docs.go index 14d771330..a458c04b6 100644 --- a/cmd/github-mcp-server/generate_docs.go +++ b/cmd/github-mcp-server/generate_docs.go @@ -349,14 +349,13 @@ func generateRemoteToolsetsDoc() string { // Add "all" toolset first (special case) allIcon := octiconImg("apps", "../") - fmt.Fprintf(&buf, "| %s
all | All available GitHub MCP tools | https://api.githubcopilot.com/mcp/ | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%%7B%%22type%%22%%3A%%20%%22http%%22%%2C%%22url%%22%%3A%%20%%22https%%3A%%2F%%2Fapi.githubcopilot.com%%2Fmcp%%2F%%22%%7D) | [read-only](https://api.githubcopilot.com/mcp/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%%7B%%22type%%22%%3A%%20%%22http%%22%%2C%%22url%%22%%3A%%20%%22https%%3A%%2F%%2Fapi.githubcopilot.com%%2Fmcp%%2Freadonly%%22%%7D) |\n", allIcon) + fmt.Fprintf(&buf, "| %s
`all` | All available GitHub MCP tools | https://api.githubcopilot.com/mcp/ | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%%7B%%22type%%22%%3A%%20%%22http%%22%%2C%%22url%%22%%3A%%20%%22https%%3A%%2F%%2Fapi.githubcopilot.com%%2Fmcp%%2F%%22%%7D) | [read-only](https://api.githubcopilot.com/mcp/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%%7B%%22type%%22%%3A%%20%%22http%%22%%2C%%22url%%22%%3A%%20%%22https%%3A%%2F%%2Fapi.githubcopilot.com%%2Fmcp%%2Freadonly%%22%%7D) |\n", allIcon) // AvailableToolsets() returns toolsets that have tools, sorted by ID // Exclude context (handled separately) and dynamic (internal only) for _, ts := range r.AvailableToolsets("context", "dynamic") { idStr := string(ts.ID) - formattedName := formatToolsetName(idStr) apiURL := fmt.Sprintf("https://api.githubcopilot.com/mcp/x/%s", idStr) readonlyURL := fmt.Sprintf("https://api.githubcopilot.com/mcp/x/%s/readonly", idStr) @@ -372,9 +371,9 @@ func generateRemoteToolsetsDoc() string { readonlyInstallLink := fmt.Sprintf("[Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-%s&config=%s)", idStr, readonlyConfig) icon := octiconImg(ts.Icon, "../") - fmt.Fprintf(&buf, "| %s
%s | %s | %s | %s | [read-only](%s) | %s |\n", + fmt.Fprintf(&buf, "| %s
`%s` | %s | %s | %s | [read-only](%s) | %s |\n", icon, - formattedName, + idStr, ts.Description, apiURL, installLink, @@ -397,7 +396,6 @@ func generateRemoteOnlyToolsetsDoc() string { for _, ts := range github.RemoteOnlyToolsets() { idStr := string(ts.ID) - formattedName := formatToolsetName(idStr) apiURL := fmt.Sprintf("https://api.githubcopilot.com/mcp/x/%s", idStr) readonlyURL := fmt.Sprintf("https://api.githubcopilot.com/mcp/x/%s/readonly", idStr) @@ -413,9 +411,9 @@ func generateRemoteOnlyToolsetsDoc() string { readonlyInstallLink := fmt.Sprintf("[Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-%s&config=%s)", idStr, readonlyConfig) icon := octiconImg(ts.Icon, "../") - fmt.Fprintf(&buf, "| %s
%s | %s | %s | %s | [read-only](%s) | %s |\n", + fmt.Fprintf(&buf, "| %s
`%s` | %s | %s | %s | [read-only](%s) | %s |\n", icon, - formattedName, + idStr, ts.Description, apiURL, installLink, From 676956faf30ffd8f71275be6d2a34aed632624ba Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Fri, 9 Jan 2026 18:26:59 +0100 Subject: [PATCH 062/138] Fix resource handler to use deps from context The RepositoryResourceContentsHandler was using closure-captured deps instead of retrieving them from context at call time. This causes issues on the remote server which injects per-request deps via context. Changed to use MustDepsFromContext(ctx) pattern consistent with tool handlers in NewTool and NewToolFromHandler. --- pkg/github/repository_resource.go | 9 ++--- pkg/github/repository_resource_test.go | 47 +++++++++++++------------- 2 files changed, 29 insertions(+), 27 deletions(-) diff --git a/pkg/github/repository_resource.go b/pkg/github/repository_resource.go index ee43e9d04..28ce63b46 100644 --- a/pkg/github/repository_resource.go +++ b/pkg/github/repository_resource.go @@ -102,15 +102,16 @@ func GetRepositoryResourcePrContent(t translations.TranslationHelperFunc) invent // repositoryResourceContentsHandlerFunc returns a ResourceHandlerFunc that creates handlers on-demand. func repositoryResourceContentsHandlerFunc(resourceURITemplate *uritemplate.Template) inventory.ResourceHandlerFunc { - return func(deps any) mcp.ResourceHandler { - d := deps.(ToolDependencies) - return RepositoryResourceContentsHandler(d, resourceURITemplate) + return func(_ any) mcp.ResourceHandler { + return RepositoryResourceContentsHandler(resourceURITemplate) } } // RepositoryResourceContentsHandler returns a handler function for repository content requests. -func RepositoryResourceContentsHandler(deps ToolDependencies, resourceURITemplate *uritemplate.Template) mcp.ResourceHandler { +// It retrieves ToolDependencies from the context at call time via MustDepsFromContext. +func RepositoryResourceContentsHandler(resourceURITemplate *uritemplate.Template) mcp.ResourceHandler { return func(ctx context.Context, request *mcp.ReadResourceRequest) (*mcp.ReadResourceResult, error) { + deps := MustDepsFromContext(ctx) // Match the URI to extract parameters uriValues := resourceURITemplate.Match(request.Params.URI) if uriValues == nil { diff --git a/pkg/github/repository_resource_test.go b/pkg/github/repository_resource_test.go index b55b821af..a3b3ca754 100644 --- a/pkg/github/repository_resource_test.go +++ b/pkg/github/repository_resource_test.go @@ -26,7 +26,7 @@ func Test_repositoryResourceContents(t *testing.T) { name string mockedClient *http.Client uri string - handlerFn func(deps ToolDependencies) mcp.ResourceHandler + handlerFn func() mcp.ResourceHandler expectedResponseType resourceResponseType expectError string expectedResult *mcp.ReadResourceResult @@ -41,8 +41,8 @@ func Test_repositoryResourceContents(t *testing.T) { }), }), uri: "repo:///repo/contents/README.md", - handlerFn: func(deps ToolDependencies) mcp.ResourceHandler { - return RepositoryResourceContentsHandler(deps, repositoryResourceContentURITemplate) + handlerFn: func() mcp.ResourceHandler { + return RepositoryResourceContentsHandler(repositoryResourceContentURITemplate) }, expectedResponseType: resourceResponseTypeText, // Ignored as error is expected expectError: "owner is required", @@ -57,8 +57,8 @@ func Test_repositoryResourceContents(t *testing.T) { }), }), uri: "repo://owner//refs/heads/main/contents/README.md", - handlerFn: func(deps ToolDependencies) mcp.ResourceHandler { - return RepositoryResourceContentsHandler(deps, repositoryResourceBranchContentURITemplate) + handlerFn: func() mcp.ResourceHandler { + return RepositoryResourceContentsHandler(repositoryResourceBranchContentURITemplate) }, expectedResponseType: resourceResponseTypeText, // Ignored as error is expected expectError: "repo is required", @@ -73,8 +73,8 @@ func Test_repositoryResourceContents(t *testing.T) { }), }), uri: "repo://owner/repo/contents/data.png", - handlerFn: func(deps ToolDependencies) mcp.ResourceHandler { - return RepositoryResourceContentsHandler(deps, repositoryResourceContentURITemplate) + handlerFn: func() mcp.ResourceHandler { + return RepositoryResourceContentsHandler(repositoryResourceContentURITemplate) }, expectedResponseType: resourceResponseTypeBlob, expectedResult: &mcp.ReadResourceResult{ @@ -94,8 +94,8 @@ func Test_repositoryResourceContents(t *testing.T) { }), }), uri: "repo://owner/repo/contents/README.md", - handlerFn: func(deps ToolDependencies) mcp.ResourceHandler { - return RepositoryResourceContentsHandler(deps, repositoryResourceContentURITemplate) + handlerFn: func() mcp.ResourceHandler { + return RepositoryResourceContentsHandler(repositoryResourceContentURITemplate) }, expectedResponseType: resourceResponseTypeText, expectedResult: &mcp.ReadResourceResult{ @@ -117,8 +117,8 @@ func Test_repositoryResourceContents(t *testing.T) { }), }), uri: "repo://owner/repo/contents/pkg/github/actions.go", - handlerFn: func(deps ToolDependencies) mcp.ResourceHandler { - return RepositoryResourceContentsHandler(deps, repositoryResourceContentURITemplate) + handlerFn: func() mcp.ResourceHandler { + return RepositoryResourceContentsHandler(repositoryResourceContentURITemplate) }, expectedResponseType: resourceResponseTypeText, expectedResult: &mcp.ReadResourceResult{ @@ -138,8 +138,8 @@ func Test_repositoryResourceContents(t *testing.T) { }), }), uri: "repo://owner/repo/refs/heads/main/contents/README.md", - handlerFn: func(deps ToolDependencies) mcp.ResourceHandler { - return RepositoryResourceContentsHandler(deps, repositoryResourceBranchContentURITemplate) + handlerFn: func() mcp.ResourceHandler { + return RepositoryResourceContentsHandler(repositoryResourceBranchContentURITemplate) }, expectedResponseType: resourceResponseTypeText, expectedResult: &mcp.ReadResourceResult{ @@ -159,8 +159,8 @@ func Test_repositoryResourceContents(t *testing.T) { }), }), uri: "repo://owner/repo/refs/tags/v1.0.0/contents/README.md", - handlerFn: func(deps ToolDependencies) mcp.ResourceHandler { - return RepositoryResourceContentsHandler(deps, repositoryResourceTagContentURITemplate) + handlerFn: func() mcp.ResourceHandler { + return RepositoryResourceContentsHandler(repositoryResourceTagContentURITemplate) }, expectedResponseType: resourceResponseTypeText, expectedResult: &mcp.ReadResourceResult{ @@ -180,8 +180,8 @@ func Test_repositoryResourceContents(t *testing.T) { }), }), uri: "repo://owner/repo/sha/abc123/contents/README.md", - handlerFn: func(deps ToolDependencies) mcp.ResourceHandler { - return RepositoryResourceContentsHandler(deps, repositoryResourceCommitContentURITemplate) + handlerFn: func() mcp.ResourceHandler { + return RepositoryResourceContentsHandler(repositoryResourceCommitContentURITemplate) }, expectedResponseType: resourceResponseTypeText, expectedResult: &mcp.ReadResourceResult{ @@ -206,8 +206,8 @@ func Test_repositoryResourceContents(t *testing.T) { }), }), uri: "repo://owner/repo/refs/pull/42/head/contents/README.md", - handlerFn: func(deps ToolDependencies) mcp.ResourceHandler { - return RepositoryResourceContentsHandler(deps, repositoryResourcePrContentURITemplate) + handlerFn: func() mcp.ResourceHandler { + return RepositoryResourceContentsHandler(repositoryResourcePrContentURITemplate) }, expectedResponseType: resourceResponseTypeText, expectedResult: &mcp.ReadResourceResult{ @@ -226,8 +226,8 @@ func Test_repositoryResourceContents(t *testing.T) { }), }), uri: "repo://owner/repo/contents/nonexistent.md", - handlerFn: func(deps ToolDependencies) mcp.ResourceHandler { - return RepositoryResourceContentsHandler(deps, repositoryResourceContentURITemplate) + handlerFn: func() mcp.ResourceHandler { + return RepositoryResourceContentsHandler(repositoryResourceContentURITemplate) }, expectedResponseType: resourceResponseTypeText, // Ignored as error is expected expectError: "404 Not Found", @@ -242,7 +242,8 @@ func Test_repositoryResourceContents(t *testing.T) { Client: client, RawClient: mockRawClient, } - handler := tc.handlerFn(deps) + ctx := ContextWithDeps(context.Background(), deps) + handler := tc.handlerFn() request := &mcp.ReadResourceRequest{ Params: &mcp.ReadResourceParams{ @@ -250,7 +251,7 @@ func Test_repositoryResourceContents(t *testing.T) { }, } - resp, err := handler(context.TODO(), request) + resp, err := handler(ctx, request) if tc.expectError != "" { require.ErrorContains(t, err, tc.expectError) From 53a672040f05205a52e2a3e3c3536d1ec8b2fa13 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 12 Jan 2026 11:46:59 +0100 Subject: [PATCH 063/138] fix: keep all resources registered for resources/read requests The ForMCPRequest optimization was incorrectly filtering resources by doing an exact string match between the URI template pattern and the concrete URI. This would never match because templates like 'repo://{owner}/{repo}/contents{/path*}' don't match concrete URIs like 'repo://owner/repo/contents/file.py'. Instead of implementing template matching in the inventory, we simply keep all resources registered for resources/read requests and let the SDK handle URI template matching internally (which it already does correctly via uritemplate.Regexp().MatchString()). This fixes resources/read returning 'Resource not found' for valid URIs. --- pkg/inventory/registry.go | 6 ++---- pkg/inventory/registry_test.go | 12 +++++------- 2 files changed, 7 insertions(+), 11 deletions(-) diff --git a/pkg/inventory/registry.go b/pkg/inventory/registry.go index f3691e38a..885617b43 100644 --- a/pkg/inventory/registry.go +++ b/pkg/inventory/registry.go @@ -91,7 +91,7 @@ const ( // - MCPMethodToolsList: All available tools (no resources/prompts) // - MCPMethodToolsCall: Only the named tool // - MCPMethodResourcesList, MCPMethodResourcesTemplatesList: All available resources (no tools/prompts) -// - MCPMethodResourcesRead: Only the named resource template +// - MCPMethodResourcesRead: All resources (SDK handles URI template matching) // - MCPMethodPromptsList: All available prompts (no tools/resources) // - MCPMethodPromptsGet: Only the named prompt // - Unknown methods: Empty (no items registered) @@ -134,10 +134,8 @@ func (r *Inventory) ForMCPRequest(method string, itemName string) *Inventory { case MCPMethodResourcesList, MCPMethodResourcesTemplatesList: result.tools, result.prompts = nil, nil case MCPMethodResourcesRead: + // Keep all resources registered - SDK handles URI template matching internally result.tools, result.prompts = nil, nil - if itemName != "" { - result.resourceTemplates = r.filterResourcesByURI(itemName) - } case MCPMethodPromptsList: result.tools, result.resourceTemplates = nil, nil case MCPMethodPromptsGet: diff --git a/pkg/inventory/registry_test.go b/pkg/inventory/registry_test.go index 2c3262873..136f8e523 100644 --- a/pkg/inventory/registry_test.go +++ b/pkg/inventory/registry_test.go @@ -775,17 +775,15 @@ func TestForMCPRequest_ResourcesRead(t *testing.T) { } reg := NewBuilder().SetResources(resources).WithToolsets([]string{"all"}).Build() - filtered := reg.ForMCPRequest(MCPMethodResourcesRead, "repo://{owner}/{repo}") + // Pass a concrete URI - all resources remain registered, SDK handles matching + filtered := reg.ForMCPRequest(MCPMethodResourcesRead, "repo://owner/repo") + // All resources should be available - SDK handles URI template matching internally available := filtered.AvailableResourceTemplates(context.Background()) - if len(available) != 1 { - t.Fatalf("Expected 1 resource for resources/read, got %d", len(available)) - } - if available[0].Template.URITemplate != "repo://{owner}/{repo}" { - t.Errorf("Expected URI template 'repo://{owner}/{repo}', got %q", available[0].Template.URITemplate) + if len(available) != 2 { + t.Fatalf("Expected 2 resources for resources/read (SDK handles matching), got %d", len(available)) } } - func TestForMCPRequest_PromptsList(t *testing.T) { tools := []ServerTool{ mockTool("tool1", "repos", true), From c44ce2e2b881a7cadc37ca38941cc608e6c5d085 Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 12 Jan 2026 12:49:57 +0100 Subject: [PATCH 064/138] chore: remove unused filterResourcesByURI function --- pkg/inventory/filters.go | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/pkg/inventory/filters.go b/pkg/inventory/filters.go index c5156e61a..533bba552 100644 --- a/pkg/inventory/filters.go +++ b/pkg/inventory/filters.go @@ -203,17 +203,6 @@ func (r *Inventory) filterToolsByName(name string) []ServerTool { return result } -// filterResourcesByURI returns resource templates matching the given URI pattern. -// Uses linear scan - optimized for single-lookup per-request scenarios (ForMCPRequest). -func (r *Inventory) filterResourcesByURI(uri string) []ServerResourceTemplate { - for i := range r.resourceTemplates { - if r.resourceTemplates[i].Template.URITemplate == uri { - return []ServerResourceTemplate{r.resourceTemplates[i]} - } - } - return []ServerResourceTemplate{} -} - // filterPromptsByName returns prompts matching the given name. // Uses linear scan - optimized for single-lookup per-request scenarios (ForMCPRequest). func (r *Inventory) filterPromptsByName(name string) []ServerPrompt { From 88d117bd82f7da044fc5f1619037477d5fc691fb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 10 Jan 2026 13:44:40 +0000 Subject: [PATCH 065/138] Add base_ref support to assign_copilot_to_issue tool - Add optional base_ref parameter to tool schema - Change from replaceActorsForAssignable to updateIssue mutation with agentAssignment - Add AgentAssignmentInput and UpdateIssueInput structs for new GraphQL mutation - Update all tests to use new mutation structure - Add test case for base_ref functionality - Update toolsnaps and documentation Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com> --- README.md | 1 + docs/remote-server.md | 42 ++-- .../assign_copilot_to_issue.snap | 4 + pkg/github/issues.go | 69 ++++-- pkg/github/issues_test.go | 219 ++++++++++++++++-- 5 files changed, 278 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 1a0f6b1c4..6da46c00e 100644 --- a/README.md +++ b/README.md @@ -751,6 +751,7 @@ The following sets of tools are available: - **assign_copilot_to_issue** - Assign Copilot to issue - **Required OAuth Scopes**: `repo` + - `base_ref`: Git reference (e.g., branch) that the agent will start its work from. If not specified, defaults to the repository's default branch (string, optional) - `issue_number`: Issue number (number, required) - `owner`: Repository owner (string, required) - `repo`: Repository name (string, required) diff --git a/docs/remote-server.md b/docs/remote-server.md index 039d094fe..d7d0f72b1 100644 --- a/docs/remote-server.md +++ b/docs/remote-server.md @@ -19,24 +19,24 @@ Below is a table of available toolsets for the remote GitHub MCP Server. Each to | Name | Description | API URL | 1-Click Install (VS Code) | Read-only Link | 1-Click Read-only Install (VS Code) | | ---- | ----------- | ------- | ------------------------- | -------------- | ----------------------------------- | -| apps
`all` | All available GitHub MCP tools | https://api.githubcopilot.com/mcp/ | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2F%22%7D) | [read-only](https://api.githubcopilot.com/mcp/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Freadonly%22%7D) | -| workflow
`actions` | GitHub Actions workflows and CI/CD operations | https://api.githubcopilot.com/mcp/x/actions | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-actions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Factions%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/actions/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-actions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Factions%2Freadonly%22%7D) | -| codescan
`code_security` | Code security related tools, such as GitHub Code Scanning | https://api.githubcopilot.com/mcp/x/code_security | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_security&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_security%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/code_security/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_security&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_security%2Freadonly%22%7D) | -| dependabot
`dependabot` | Dependabot tools | https://api.githubcopilot.com/mcp/x/dependabot | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-dependabot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdependabot%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/dependabot/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-dependabot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdependabot%2Freadonly%22%7D) | -| comment-discussion
`discussions` | GitHub Discussions related tools | https://api.githubcopilot.com/mcp/x/discussions | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/discussions/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%2Freadonly%22%7D) | -| logo-gist
`gists` | GitHub Gist related tools | https://api.githubcopilot.com/mcp/x/gists | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-gists&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgists%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/gists/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-gists&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgists%2Freadonly%22%7D) | -| git-branch
`git` | GitHub Git API related tools for low-level Git operations | https://api.githubcopilot.com/mcp/x/git | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-git&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgit%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/git/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-git&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgit%2Freadonly%22%7D) | -| issue-opened
`issues` | GitHub Issues related tools | https://api.githubcopilot.com/mcp/x/issues | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-issues&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fissues%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/issues/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-issues&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fissues%2Freadonly%22%7D) | -| tag
`labels` | GitHub Labels related tools | https://api.githubcopilot.com/mcp/x/labels | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-labels&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Flabels%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/labels/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-labels&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Flabels%2Freadonly%22%7D) | -| bell
`notifications` | GitHub Notifications related tools | https://api.githubcopilot.com/mcp/x/notifications | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-notifications&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fnotifications%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/notifications/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-notifications&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fnotifications%2Freadonly%22%7D) | -| organization
`orgs` | GitHub Organization related tools | https://api.githubcopilot.com/mcp/x/orgs | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-orgs&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Forgs%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/orgs/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-orgs&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Forgs%2Freadonly%22%7D) | -| project
`projects` | GitHub Projects related tools | https://api.githubcopilot.com/mcp/x/projects | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-projects&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fprojects%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/projects/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-projects&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fprojects%2Freadonly%22%7D) | -| git-pull-request
`pull_requests` | GitHub Pull Request related tools | https://api.githubcopilot.com/mcp/x/pull_requests | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-pull_requests&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fpull_requests%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/pull_requests/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-pull_requests&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fpull_requests%2Freadonly%22%7D) | -| repo
`repos` | GitHub Repository related tools | https://api.githubcopilot.com/mcp/x/repos | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-repos&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Frepos%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/repos/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-repos&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Frepos%2Freadonly%22%7D) | -| shield-lock
`secret_protection` | Secret protection related tools, such as GitHub Secret Scanning | https://api.githubcopilot.com/mcp/x/secret_protection | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-secret_protection&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecret_protection%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/secret_protection/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-secret_protection&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecret_protection%2Freadonly%22%7D) | -| shield
`security_advisories` | Security advisories related tools | https://api.githubcopilot.com/mcp/x/security_advisories | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-security_advisories&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecurity_advisories%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/security_advisories/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-security_advisories&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecurity_advisories%2Freadonly%22%7D) | -| star
`stargazers` | GitHub Stargazers related tools | https://api.githubcopilot.com/mcp/x/stargazers | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-stargazers&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fstargazers%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/stargazers/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-stargazers&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fstargazers%2Freadonly%22%7D) | -| people
`users` | GitHub User related tools | https://api.githubcopilot.com/mcp/x/users | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-users&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fusers%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/users/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-users&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fusers%2Freadonly%22%7D) | +| apps
all | All available GitHub MCP tools | https://api.githubcopilot.com/mcp/ | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2F%22%7D) | [read-only](https://api.githubcopilot.com/mcp/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Freadonly%22%7D) | +| workflow
Actions | GitHub Actions workflows and CI/CD operations | https://api.githubcopilot.com/mcp/x/actions | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-actions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Factions%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/actions/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-actions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Factions%2Freadonly%22%7D) | +| codescan
Code Security | Code security related tools, such as GitHub Code Scanning | https://api.githubcopilot.com/mcp/x/code_security | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_security&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_security%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/code_security/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_security&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_security%2Freadonly%22%7D) | +| dependabot
Dependabot | Dependabot tools | https://api.githubcopilot.com/mcp/x/dependabot | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-dependabot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdependabot%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/dependabot/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-dependabot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdependabot%2Freadonly%22%7D) | +| comment-discussion
Discussions | GitHub Discussions related tools | https://api.githubcopilot.com/mcp/x/discussions | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/discussions/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%2Freadonly%22%7D) | +| logo-gist
Gists | GitHub Gist related tools | https://api.githubcopilot.com/mcp/x/gists | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-gists&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgists%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/gists/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-gists&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgists%2Freadonly%22%7D) | +| git-branch
Git | GitHub Git API related tools for low-level Git operations | https://api.githubcopilot.com/mcp/x/git | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-git&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgit%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/git/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-git&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgit%2Freadonly%22%7D) | +| issue-opened
Issues | GitHub Issues related tools | https://api.githubcopilot.com/mcp/x/issues | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-issues&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fissues%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/issues/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-issues&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fissues%2Freadonly%22%7D) | +| tag
Labels | GitHub Labels related tools | https://api.githubcopilot.com/mcp/x/labels | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-labels&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Flabels%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/labels/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-labels&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Flabels%2Freadonly%22%7D) | +| bell
Notifications | GitHub Notifications related tools | https://api.githubcopilot.com/mcp/x/notifications | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-notifications&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fnotifications%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/notifications/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-notifications&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fnotifications%2Freadonly%22%7D) | +| organization
Organizations | GitHub Organization related tools | https://api.githubcopilot.com/mcp/x/orgs | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-orgs&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Forgs%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/orgs/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-orgs&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Forgs%2Freadonly%22%7D) | +| project
Projects | GitHub Projects related tools | https://api.githubcopilot.com/mcp/x/projects | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-projects&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fprojects%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/projects/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-projects&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fprojects%2Freadonly%22%7D) | +| git-pull-request
Pull Requests | GitHub Pull Request related tools | https://api.githubcopilot.com/mcp/x/pull_requests | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-pull_requests&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fpull_requests%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/pull_requests/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-pull_requests&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fpull_requests%2Freadonly%22%7D) | +| repo
Repositories | GitHub Repository related tools | https://api.githubcopilot.com/mcp/x/repos | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-repos&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Frepos%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/repos/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-repos&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Frepos%2Freadonly%22%7D) | +| shield-lock
Secret Protection | Secret protection related tools, such as GitHub Secret Scanning | https://api.githubcopilot.com/mcp/x/secret_protection | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-secret_protection&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecret_protection%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/secret_protection/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-secret_protection&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecret_protection%2Freadonly%22%7D) | +| shield
Security Advisories | Security advisories related tools | https://api.githubcopilot.com/mcp/x/security_advisories | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-security_advisories&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecurity_advisories%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/security_advisories/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-security_advisories&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecurity_advisories%2Freadonly%22%7D) | +| star
Stargazers | GitHub Stargazers related tools | https://api.githubcopilot.com/mcp/x/stargazers | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-stargazers&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fstargazers%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/stargazers/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-stargazers&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fstargazers%2Freadonly%22%7D) | +| people
Users | GitHub User related tools | https://api.githubcopilot.com/mcp/x/users | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-users&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fusers%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/users/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-users&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fusers%2Freadonly%22%7D) | ### Additional _Remote_ Server Toolsets @@ -46,9 +46,9 @@ These toolsets are only available in the remote GitHub MCP Server and are not in | Name | Description | API URL | 1-Click Install (VS Code) | Read-only Link | 1-Click Read-only Install (VS Code) | | ---- | ----------- | ------- | ------------------------- | -------------- | ----------------------------------- | -| copilot
`copilot` | Copilot related tools | https://api.githubcopilot.com/mcp/x/copilot | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/copilot/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot%2Freadonly%22%7D) | -| copilot
`copilot_spaces` | Copilot Spaces tools | https://api.githubcopilot.com/mcp/x/copilot_spaces | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot_spaces&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot_spaces%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/copilot_spaces/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot_spaces&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot_spaces%2Freadonly%22%7D) | -| book
`github_support_docs_search` | Retrieve documentation to answer GitHub product and support questions. Topics include: GitHub Actions Workflows, Authentication, ... | https://api.githubcopilot.com/mcp/x/github_support_docs_search | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-github_support_docs_search&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgithub_support_docs_search%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/github_support_docs_search/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-github_support_docs_search&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgithub_support_docs_search%2Freadonly%22%7D) | +| copilot
Copilot | Copilot related tools | https://api.githubcopilot.com/mcp/x/copilot | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/copilot/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot%2Freadonly%22%7D) | +| copilot
Copilot Spaces | Copilot Spaces tools | https://api.githubcopilot.com/mcp/x/copilot_spaces | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot_spaces&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot_spaces%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/copilot_spaces/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot_spaces&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot_spaces%2Freadonly%22%7D) | +| book
Github Support Docs Search | Retrieve documentation to answer GitHub product and support questions. Topics include: GitHub Actions Workflows, Authentication, ... | https://api.githubcopilot.com/mcp/x/github_support_docs_search | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-github_support_docs_search&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgithub_support_docs_search%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/github_support_docs_search/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-github_support_docs_search&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgithub_support_docs_search%2Freadonly%22%7D) | ### Optional Headers diff --git a/pkg/github/__toolsnaps__/assign_copilot_to_issue.snap b/pkg/github/__toolsnaps__/assign_copilot_to_issue.snap index 354600147..7ad1922a0 100644 --- a/pkg/github/__toolsnaps__/assign_copilot_to_issue.snap +++ b/pkg/github/__toolsnaps__/assign_copilot_to_issue.snap @@ -7,6 +7,10 @@ "inputSchema": { "type": "object", "properties": { + "base_ref": { + "type": "string", + "description": "Git reference (e.g., branch) that the agent will start its work from. If not specified, defaults to the repository's default branch" + }, "issue_number": { "type": "number", "description": "Issue number" diff --git a/pkg/github/issues.go b/pkg/github/issues.go index 1e29a0eef..b4580a89d 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -1646,6 +1646,10 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server Type: "number", Description: "Issue number", }, + "base_ref": { + Type: "string", + Description: "Git reference (e.g., branch) that the agent will start its work from. If not specified, defaults to the repository's default branch", + }, }, Required: []string{"owner", "repo", "issue_number"}, }, @@ -1656,6 +1660,7 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server Owner string `mapstructure:"owner"` Repo string `mapstructure:"repo"` IssueNumber int32 `mapstructure:"issue_number"` + BaseRef string `mapstructure:"base_ref"` } if err := mapstructure.Decode(args, ¶ms); err != nil { return utils.NewToolResultError(err.Error()), nil, nil @@ -1724,10 +1729,10 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server return utils.NewToolResultError("copilot isn't available as an assignee for this issue. Please inform the user to visit https://docs.github.com/en/copilot/using-github-copilot/using-copilot-coding-agent-to-work-on-tasks/about-assigning-tasks-to-copilot for more information."), nil, nil } - // Next let's get the GQL Node ID and current assignees for this issue because the only way to - // assign copilot is to use replaceActorsForAssignable which requires the full list. + // Next, get the issue ID and repository ID var getIssueQuery struct { Repository struct { + ID githubv4.ID Issue struct { ID githubv4.ID Assignees struct { @@ -1749,30 +1754,49 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server return ghErrors.NewGitHubGraphQLErrorResponse(ctx, "failed to get issue ID", err), nil, nil } - // Finally, do the assignment. Just for reference, assigning copilot to an issue that it is already - // assigned to seems to have no impact (which is a good thing). - var assignCopilotMutation struct { - ReplaceActorsForAssignable struct { - Typename string `graphql:"__typename"` // Not required but we need a selector or GQL errors - } `graphql:"replaceActorsForAssignable(input: $input)"` - } - + // Build the assignee IDs list including copilot actorIDs := make([]githubv4.ID, len(getIssueQuery.Repository.Issue.Assignees.Nodes)+1) for i, node := range getIssueQuery.Repository.Issue.Assignees.Nodes { actorIDs[i] = node.ID } actorIDs[len(getIssueQuery.Repository.Issue.Assignees.Nodes)] = copilotAssignee.ID + // Prepare agent assignment input + emptyString := githubv4.String("") + agentAssignment := &AgentAssignmentInput{ + CustomAgent: &emptyString, + CustomInstructions: &emptyString, + TargetRepositoryID: getIssueQuery.Repository.ID, + } + + // Add base ref if provided + if params.BaseRef != "" { + baseRef := githubv4.String(params.BaseRef) + agentAssignment.BaseRef = &baseRef + } + + // Execute the updateIssue mutation + var updateIssueMutation struct { + UpdateIssue struct { + Issue struct { + ID githubv4.ID + Number githubv4.Int + URL githubv4.String + } + } `graphql:"updateIssue(input: $input)"` + } + if err := client.Mutate( ctx, - &assignCopilotMutation, - ReplaceActorsForAssignableInput{ - AssignableID: getIssueQuery.Repository.Issue.ID, - ActorIDs: actorIDs, + &updateIssueMutation, + UpdateIssueInput{ + ID: getIssueQuery.Repository.Issue.ID, + AssigneeIDs: actorIDs, + AgentAssignment: agentAssignment, }, nil, ); err != nil { - return nil, nil, fmt.Errorf("failed to replace actors for assignable: %w", err) + return nil, nil, fmt.Errorf("failed to update issue with agent assignment: %w", err) } return utils.NewToolResultText("successfully assigned copilot to issue"), nil, nil @@ -1784,6 +1808,21 @@ type ReplaceActorsForAssignableInput struct { ActorIDs []githubv4.ID `json:"actorIds"` } +// AgentAssignmentInput represents the input for assigning an agent to an issue. +type AgentAssignmentInput struct { + BaseRef *githubv4.String `json:"baseRef,omitempty"` + CustomAgent *githubv4.String `json:"customAgent,omitempty"` + CustomInstructions *githubv4.String `json:"customInstructions,omitempty"` + TargetRepositoryID githubv4.ID `json:"targetRepositoryId"` +} + +// UpdateIssueInput represents the input for updating an issue with agent assignment. +type UpdateIssueInput struct { + ID githubv4.ID `json:"id"` + AssigneeIDs []githubv4.ID `json:"assigneeIds"` + AgentAssignment *AgentAssignmentInput `json:"agentAssignment,omitempty"` +} + // parseISOTimestamp parses an ISO 8601 timestamp string into a time.Time object. // Returns the parsed time or an error if parsing fails. // Example formats supported: "2023-01-15T14:30:00Z", "2023-01-15" diff --git a/pkg/github/issues_test.go b/pkg/github/issues_test.go index 2ccd4918f..21e78874a 100644 --- a/pkg/github/issues_test.go +++ b/pkg/github/issues_test.go @@ -2085,8 +2085,15 @@ func TestAssignCopilotToIssue(t *testing.T) { assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "owner") assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "repo") assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "issue_number") + assert.Contains(t, tool.InputSchema.(*jsonschema.Schema).Properties, "base_ref") assert.ElementsMatch(t, tool.InputSchema.(*jsonschema.Schema).Required, []string{"owner", "repo", "issue_number"}) + // Helper function to create pointer to githubv4.String + ptrGitHubv4String := func(s string) *githubv4.String { + v := githubv4.String(s) + return &v + } + var pageOfFakeBots = func(n int) []struct{} { // We don't _really_ need real bots here, just objects that count as entries for the page bots := make([]struct{}, n) @@ -2151,6 +2158,7 @@ func TestAssignCopilotToIssue(t *testing.T) { githubv4mock.NewQueryMatcher( struct { Repository struct { + ID githubv4.ID Issue struct { ID githubv4.ID Assignees struct { @@ -2168,6 +2176,7 @@ func TestAssignCopilotToIssue(t *testing.T) { }, githubv4mock.DataResponse(map[string]any{ "repository": map[string]any{ + "id": githubv4.ID("test-repo-id"), "issue": map[string]any{ "id": githubv4.ID("test-issue-id"), "assignees": map[string]any{ @@ -2179,16 +2188,34 @@ func TestAssignCopilotToIssue(t *testing.T) { ), githubv4mock.NewMutationMatcher( struct { - ReplaceActorsForAssignable struct { - Typename string `graphql:"__typename"` - } `graphql:"replaceActorsForAssignable(input: $input)"` + UpdateIssue struct { + Issue struct { + ID githubv4.ID + Number githubv4.Int + URL githubv4.String + } + } `graphql:"updateIssue(input: $input)"` }{}, - ReplaceActorsForAssignableInput{ - AssignableID: githubv4.ID("test-issue-id"), - ActorIDs: []githubv4.ID{githubv4.ID("copilot-swe-agent-id")}, + UpdateIssueInput{ + ID: githubv4.ID("test-issue-id"), + AssigneeIDs: []githubv4.ID{githubv4.ID("copilot-swe-agent-id")}, + AgentAssignment: &AgentAssignmentInput{ + BaseRef: nil, + CustomAgent: ptrGitHubv4String(""), + CustomInstructions: ptrGitHubv4String(""), + TargetRepositoryID: githubv4.ID("test-repo-id"), + }, }, nil, - githubv4mock.DataResponse(map[string]any{}), + githubv4mock.DataResponse(map[string]any{ + "updateIssue": map[string]any{ + "issue": map[string]any{ + "id": githubv4.ID("test-issue-id"), + "number": githubv4.Int(123), + "url": githubv4.String("https://github.com/owner/repo/issues/123"), + }, + }, + }), ), ), }, @@ -2240,6 +2267,7 @@ func TestAssignCopilotToIssue(t *testing.T) { githubv4mock.NewQueryMatcher( struct { Repository struct { + ID githubv4.ID Issue struct { ID githubv4.ID Assignees struct { @@ -2257,6 +2285,7 @@ func TestAssignCopilotToIssue(t *testing.T) { }, githubv4mock.DataResponse(map[string]any{ "repository": map[string]any{ + "id": githubv4.ID("test-repo-id"), "issue": map[string]any{ "id": githubv4.ID("test-issue-id"), "assignees": map[string]any{ @@ -2275,20 +2304,38 @@ func TestAssignCopilotToIssue(t *testing.T) { ), githubv4mock.NewMutationMatcher( struct { - ReplaceActorsForAssignable struct { - Typename string `graphql:"__typename"` - } `graphql:"replaceActorsForAssignable(input: $input)"` + UpdateIssue struct { + Issue struct { + ID githubv4.ID + Number githubv4.Int + URL githubv4.String + } + } `graphql:"updateIssue(input: $input)"` }{}, - ReplaceActorsForAssignableInput{ - AssignableID: githubv4.ID("test-issue-id"), - ActorIDs: []githubv4.ID{ + UpdateIssueInput{ + ID: githubv4.ID("test-issue-id"), + AssigneeIDs: []githubv4.ID{ githubv4.ID("existing-assignee-id"), githubv4.ID("existing-assignee-id-2"), githubv4.ID("copilot-swe-agent-id"), }, + AgentAssignment: &AgentAssignmentInput{ + BaseRef: nil, + CustomAgent: ptrGitHubv4String(""), + CustomInstructions: ptrGitHubv4String(""), + TargetRepositoryID: githubv4.ID("test-repo-id"), + }, }, nil, - githubv4mock.DataResponse(map[string]any{}), + githubv4mock.DataResponse(map[string]any{ + "updateIssue": map[string]any{ + "issue": map[string]any{ + "id": githubv4.ID("test-issue-id"), + "number": githubv4.Int(123), + "url": githubv4.String("https://github.com/owner/repo/issues/123"), + }, + }, + }), ), ), }, @@ -2377,6 +2424,7 @@ func TestAssignCopilotToIssue(t *testing.T) { githubv4mock.NewQueryMatcher( struct { Repository struct { + ID githubv4.ID Issue struct { ID githubv4.ID Assignees struct { @@ -2394,6 +2442,7 @@ func TestAssignCopilotToIssue(t *testing.T) { }, githubv4mock.DataResponse(map[string]any{ "repository": map[string]any{ + "id": githubv4.ID("test-repo-id"), "issue": map[string]any{ "id": githubv4.ID("test-issue-id"), "assignees": map[string]any{ @@ -2405,16 +2454,34 @@ func TestAssignCopilotToIssue(t *testing.T) { ), githubv4mock.NewMutationMatcher( struct { - ReplaceActorsForAssignable struct { - Typename string `graphql:"__typename"` - } `graphql:"replaceActorsForAssignable(input: $input)"` + UpdateIssue struct { + Issue struct { + ID githubv4.ID + Number githubv4.Int + URL githubv4.String + } + } `graphql:"updateIssue(input: $input)"` }{}, - ReplaceActorsForAssignableInput{ - AssignableID: githubv4.ID("test-issue-id"), - ActorIDs: []githubv4.ID{githubv4.ID("copilot-swe-agent-id")}, + UpdateIssueInput{ + ID: githubv4.ID("test-issue-id"), + AssigneeIDs: []githubv4.ID{githubv4.ID("copilot-swe-agent-id")}, + AgentAssignment: &AgentAssignmentInput{ + BaseRef: nil, + CustomAgent: ptrGitHubv4String(""), + CustomInstructions: ptrGitHubv4String(""), + TargetRepositoryID: githubv4.ID("test-repo-id"), + }, }, nil, - githubv4mock.DataResponse(map[string]any{}), + githubv4mock.DataResponse(map[string]any{ + "updateIssue": map[string]any{ + "issue": map[string]any{ + "id": githubv4.ID("test-issue-id"), + "number": githubv4.Int(123), + "url": githubv4.String("https://github.com/owner/repo/issues/123"), + }, + }, + }), ), ), }, @@ -2461,6 +2528,116 @@ func TestAssignCopilotToIssue(t *testing.T) { expectToolError: true, expectedToolErrMsg: "copilot isn't available as an assignee for this issue. Please inform the user to visit https://docs.github.com/en/copilot/using-github-copilot/using-copilot-coding-agent-to-work-on-tasks/about-assigning-tasks-to-copilot for more information.", }, + { + name: "successful assignment with base_ref specified", + requestArgs: map[string]any{ + "owner": "owner", + "repo": "repo", + "issue_number": float64(123), + "base_ref": "feature-branch", + }, + mockedClient: githubv4mock.NewMockedHTTPClient( + githubv4mock.NewQueryMatcher( + struct { + Repository struct { + SuggestedActors struct { + Nodes []struct { + Bot struct { + ID githubv4.ID + Login githubv4.String + TypeName string `graphql:"__typename"` + } `graphql:"... on Bot"` + } + PageInfo struct { + HasNextPage bool + EndCursor string + } + } `graphql:"suggestedActors(first: 100, after: $endCursor, capabilities: CAN_BE_ASSIGNED)"` + } `graphql:"repository(owner: $owner, name: $name)"` + }{}, + map[string]any{ + "owner": githubv4.String("owner"), + "name": githubv4.String("repo"), + "endCursor": (*githubv4.String)(nil), + }, + githubv4mock.DataResponse(map[string]any{ + "repository": map[string]any{ + "suggestedActors": map[string]any{ + "nodes": []any{ + map[string]any{ + "id": githubv4.ID("copilot-swe-agent-id"), + "login": githubv4.String("copilot-swe-agent"), + "__typename": "Bot", + }, + }, + }, + }, + }), + ), + githubv4mock.NewQueryMatcher( + struct { + Repository struct { + ID githubv4.ID + Issue struct { + ID githubv4.ID + Assignees struct { + Nodes []struct { + ID githubv4.ID + } + } `graphql:"assignees(first: 100)"` + } `graphql:"issue(number: $number)"` + } `graphql:"repository(owner: $owner, name: $name)"` + }{}, + map[string]any{ + "owner": githubv4.String("owner"), + "name": githubv4.String("repo"), + "number": githubv4.Int(123), + }, + githubv4mock.DataResponse(map[string]any{ + "repository": map[string]any{ + "id": githubv4.ID("test-repo-id"), + "issue": map[string]any{ + "id": githubv4.ID("test-issue-id"), + "assignees": map[string]any{ + "nodes": []any{}, + }, + }, + }, + }), + ), + githubv4mock.NewMutationMatcher( + struct { + UpdateIssue struct { + Issue struct { + ID githubv4.ID + Number githubv4.Int + URL githubv4.String + } + } `graphql:"updateIssue(input: $input)"` + }{}, + UpdateIssueInput{ + ID: githubv4.ID("test-issue-id"), + AssigneeIDs: []githubv4.ID{githubv4.ID("copilot-swe-agent-id")}, + AgentAssignment: &AgentAssignmentInput{ + BaseRef: ptrGitHubv4String("feature-branch"), + CustomAgent: ptrGitHubv4String(""), + CustomInstructions: ptrGitHubv4String(""), + TargetRepositoryID: githubv4.ID("test-repo-id"), + }, + }, + nil, + githubv4mock.DataResponse(map[string]any{ + "updateIssue": map[string]any{ + "issue": map[string]any{ + "id": githubv4.ID("test-issue-id"), + "number": githubv4.Int(123), + "url": githubv4.String("https://github.com/owner/repo/issues/123"), + }, + }, + }), + ), + ), + }, } for _, tc := range tests { From d18f26e5595d51ed21fcc10c0aa6726f27b2fdf1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 12 Jan 2026 13:31:06 +0000 Subject: [PATCH 066/138] Add GraphQL-Features header support for agent assignment API - Add context-based GraphQL feature flag support - Modify bearerAuthTransport to read features from context and add GraphQL-Features header - Use issues_copilot_assignment_api_support feature flag for updateIssue mutation - Export GetGraphQLFeatures function for use in HTTP transport layer This allows the assign_copilot_to_issue tool to work with the non-GA agent assignment API by sending the required GraphQL-Features header. Co-authored-by: SamMorrowDrums <4811358+SamMorrowDrums@users.noreply.github.com> --- internal/ghmcp/server.go | 6 ++++++ pkg/github/issues.go | 25 +++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/internal/ghmcp/server.go b/internal/ghmcp/server.go index 165886606..250f6b4cc 100644 --- a/internal/ghmcp/server.go +++ b/internal/ghmcp/server.go @@ -622,6 +622,12 @@ type bearerAuthTransport struct { func (t *bearerAuthTransport) RoundTrip(req *http.Request) (*http.Response, error) { req = req.Clone(req.Context()) req.Header.Set("Authorization", "Bearer "+t.token) + + // Check for GraphQL-Features in context and add header if present + if features := github.GetGraphQLFeatures(req.Context()); len(features) > 0 { + req.Header.Set("GraphQL-Features", strings.Join(features, ", ")) + } + return t.transport.RoundTrip(req) } diff --git a/pkg/github/issues.go b/pkg/github/issues.go index b4580a89d..63174c9e9 100644 --- a/pkg/github/issues.go +++ b/pkg/github/issues.go @@ -1775,7 +1775,8 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server agentAssignment.BaseRef = &baseRef } - // Execute the updateIssue mutation + // Execute the updateIssue mutation with the GraphQL-Features header + // This header is required for the agent assignment API which is not GA yet var updateIssueMutation struct { UpdateIssue struct { Issue struct { @@ -1786,8 +1787,12 @@ func AssignCopilotToIssue(t translations.TranslationHelperFunc) inventory.Server } `graphql:"updateIssue(input: $input)"` } + // Add the GraphQL-Features header for the agent assignment API + // The header will be read by the HTTP transport if it's configured to do so + ctxWithFeatures := withGraphQLFeatures(ctx, "issues_copilot_assignment_api_support") + if err := client.Mutate( - ctx, + ctxWithFeatures, &updateIssueMutation, UpdateIssueInput{ ID: getIssueQuery.Repository.Issue.ID, @@ -1908,3 +1913,19 @@ func AssignCodingAgentPrompt(t translations.TranslationHelperFunc) inventory.Ser }, ) } + +// graphQLFeaturesKey is a context key for GraphQL feature flags +type graphQLFeaturesKey struct{} + +// withGraphQLFeatures adds GraphQL feature flags to the context +func withGraphQLFeatures(ctx context.Context, features ...string) context.Context { + return context.WithValue(ctx, graphQLFeaturesKey{}, features) +} + +// GetGraphQLFeatures retrieves GraphQL feature flags from the context +func GetGraphQLFeatures(ctx context.Context) []string { + if features, ok := ctx.Value(graphQLFeaturesKey{}).([]string); ok { + return features + } + return nil +} From f62ff634c53b94e9cf38a80ee1fc93fef8d532fb Mon Sep 17 00:00:00 2001 From: Sam Morrow Date: Mon, 12 Jan 2026 14:54:38 +0100 Subject: [PATCH 067/138] Regenerate documentation TOCs --- docs/remote-server.md | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/remote-server.md b/docs/remote-server.md index d7d0f72b1..039d094fe 100644 --- a/docs/remote-server.md +++ b/docs/remote-server.md @@ -19,24 +19,24 @@ Below is a table of available toolsets for the remote GitHub MCP Server. Each to | Name | Description | API URL | 1-Click Install (VS Code) | Read-only Link | 1-Click Read-only Install (VS Code) | | ---- | ----------- | ------- | ------------------------- | -------------- | ----------------------------------- | -| apps
all | All available GitHub MCP tools | https://api.githubcopilot.com/mcp/ | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2F%22%7D) | [read-only](https://api.githubcopilot.com/mcp/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Freadonly%22%7D) | -| workflow
Actions | GitHub Actions workflows and CI/CD operations | https://api.githubcopilot.com/mcp/x/actions | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-actions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Factions%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/actions/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-actions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Factions%2Freadonly%22%7D) | -| codescan
Code Security | Code security related tools, such as GitHub Code Scanning | https://api.githubcopilot.com/mcp/x/code_security | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_security&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_security%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/code_security/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_security&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_security%2Freadonly%22%7D) | -| dependabot
Dependabot | Dependabot tools | https://api.githubcopilot.com/mcp/x/dependabot | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-dependabot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdependabot%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/dependabot/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-dependabot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdependabot%2Freadonly%22%7D) | -| comment-discussion
Discussions | GitHub Discussions related tools | https://api.githubcopilot.com/mcp/x/discussions | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/discussions/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%2Freadonly%22%7D) | -| logo-gist
Gists | GitHub Gist related tools | https://api.githubcopilot.com/mcp/x/gists | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-gists&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgists%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/gists/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-gists&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgists%2Freadonly%22%7D) | -| git-branch
Git | GitHub Git API related tools for low-level Git operations | https://api.githubcopilot.com/mcp/x/git | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-git&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgit%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/git/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-git&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgit%2Freadonly%22%7D) | -| issue-opened
Issues | GitHub Issues related tools | https://api.githubcopilot.com/mcp/x/issues | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-issues&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fissues%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/issues/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-issues&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fissues%2Freadonly%22%7D) | -| tag
Labels | GitHub Labels related tools | https://api.githubcopilot.com/mcp/x/labels | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-labels&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Flabels%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/labels/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-labels&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Flabels%2Freadonly%22%7D) | -| bell
Notifications | GitHub Notifications related tools | https://api.githubcopilot.com/mcp/x/notifications | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-notifications&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fnotifications%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/notifications/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-notifications&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fnotifications%2Freadonly%22%7D) | -| organization
Organizations | GitHub Organization related tools | https://api.githubcopilot.com/mcp/x/orgs | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-orgs&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Forgs%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/orgs/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-orgs&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Forgs%2Freadonly%22%7D) | -| project
Projects | GitHub Projects related tools | https://api.githubcopilot.com/mcp/x/projects | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-projects&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fprojects%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/projects/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-projects&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fprojects%2Freadonly%22%7D) | -| git-pull-request
Pull Requests | GitHub Pull Request related tools | https://api.githubcopilot.com/mcp/x/pull_requests | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-pull_requests&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fpull_requests%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/pull_requests/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-pull_requests&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fpull_requests%2Freadonly%22%7D) | -| repo
Repositories | GitHub Repository related tools | https://api.githubcopilot.com/mcp/x/repos | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-repos&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Frepos%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/repos/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-repos&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Frepos%2Freadonly%22%7D) | -| shield-lock
Secret Protection | Secret protection related tools, such as GitHub Secret Scanning | https://api.githubcopilot.com/mcp/x/secret_protection | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-secret_protection&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecret_protection%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/secret_protection/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-secret_protection&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecret_protection%2Freadonly%22%7D) | -| shield
Security Advisories | Security advisories related tools | https://api.githubcopilot.com/mcp/x/security_advisories | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-security_advisories&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecurity_advisories%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/security_advisories/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-security_advisories&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecurity_advisories%2Freadonly%22%7D) | -| star
Stargazers | GitHub Stargazers related tools | https://api.githubcopilot.com/mcp/x/stargazers | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-stargazers&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fstargazers%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/stargazers/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-stargazers&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fstargazers%2Freadonly%22%7D) | -| people
Users | GitHub User related tools | https://api.githubcopilot.com/mcp/x/users | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-users&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fusers%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/users/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-users&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fusers%2Freadonly%22%7D) | +| apps
`all` | All available GitHub MCP tools | https://api.githubcopilot.com/mcp/ | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2F%22%7D) | [read-only](https://api.githubcopilot.com/mcp/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=github&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Freadonly%22%7D) | +| workflow
`actions` | GitHub Actions workflows and CI/CD operations | https://api.githubcopilot.com/mcp/x/actions | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-actions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Factions%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/actions/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-actions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Factions%2Freadonly%22%7D) | +| codescan
`code_security` | Code security related tools, such as GitHub Code Scanning | https://api.githubcopilot.com/mcp/x/code_security | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_security&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_security%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/code_security/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_security&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_security%2Freadonly%22%7D) | +| dependabot
`dependabot` | Dependabot tools | https://api.githubcopilot.com/mcp/x/dependabot | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-dependabot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdependabot%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/dependabot/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-dependabot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdependabot%2Freadonly%22%7D) | +| comment-discussion
`discussions` | GitHub Discussions related tools | https://api.githubcopilot.com/mcp/x/discussions | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/discussions/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-discussions&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fdiscussions%2Freadonly%22%7D) | +| logo-gist
`gists` | GitHub Gist related tools | https://api.githubcopilot.com/mcp/x/gists | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-gists&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgists%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/gists/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-gists&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgists%2Freadonly%22%7D) | +| git-branch
`git` | GitHub Git API related tools for low-level Git operations | https://api.githubcopilot.com/mcp/x/git | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-git&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgit%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/git/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-git&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgit%2Freadonly%22%7D) | +| issue-opened
`issues` | GitHub Issues related tools | https://api.githubcopilot.com/mcp/x/issues | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-issues&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fissues%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/issues/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-issues&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fissues%2Freadonly%22%7D) | +| tag
`labels` | GitHub Labels related tools | https://api.githubcopilot.com/mcp/x/labels | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-labels&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Flabels%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/labels/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-labels&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Flabels%2Freadonly%22%7D) | +| bell
`notifications` | GitHub Notifications related tools | https://api.githubcopilot.com/mcp/x/notifications | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-notifications&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fnotifications%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/notifications/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-notifications&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fnotifications%2Freadonly%22%7D) | +| organization
`orgs` | GitHub Organization related tools | https://api.githubcopilot.com/mcp/x/orgs | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-orgs&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Forgs%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/orgs/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-orgs&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Forgs%2Freadonly%22%7D) | +| project
`projects` | GitHub Projects related tools | https://api.githubcopilot.com/mcp/x/projects | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-projects&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fprojects%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/projects/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-projects&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fprojects%2Freadonly%22%7D) | +| git-pull-request
`pull_requests` | GitHub Pull Request related tools | https://api.githubcopilot.com/mcp/x/pull_requests | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-pull_requests&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fpull_requests%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/pull_requests/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-pull_requests&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fpull_requests%2Freadonly%22%7D) | +| repo
`repos` | GitHub Repository related tools | https://api.githubcopilot.com/mcp/x/repos | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-repos&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Frepos%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/repos/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-repos&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Frepos%2Freadonly%22%7D) | +| shield-lock
`secret_protection` | Secret protection related tools, such as GitHub Secret Scanning | https://api.githubcopilot.com/mcp/x/secret_protection | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-secret_protection&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecret_protection%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/secret_protection/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-secret_protection&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecret_protection%2Freadonly%22%7D) | +| shield
`security_advisories` | Security advisories related tools | https://api.githubcopilot.com/mcp/x/security_advisories | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-security_advisories&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecurity_advisories%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/security_advisories/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-security_advisories&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fsecurity_advisories%2Freadonly%22%7D) | +| star
`stargazers` | GitHub Stargazers related tools | https://api.githubcopilot.com/mcp/x/stargazers | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-stargazers&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fstargazers%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/stargazers/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-stargazers&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fstargazers%2Freadonly%22%7D) | +| people
`users` | GitHub User related tools | https://api.githubcopilot.com/mcp/x/users | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-users&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fusers%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/users/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-users&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fusers%2Freadonly%22%7D) | ### Additional _Remote_ Server Toolsets @@ -46,9 +46,9 @@ These toolsets are only available in the remote GitHub MCP Server and are not in | Name | Description | API URL | 1-Click Install (VS Code) | Read-only Link | 1-Click Read-only Install (VS Code) | | ---- | ----------- | ------- | ------------------------- | -------------- | ----------------------------------- | -| copilot
Copilot | Copilot related tools | https://api.githubcopilot.com/mcp/x/copilot | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/copilot/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot%2Freadonly%22%7D) | -| copilot
Copilot Spaces | Copilot Spaces tools | https://api.githubcopilot.com/mcp/x/copilot_spaces | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot_spaces&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot_spaces%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/copilot_spaces/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot_spaces&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot_spaces%2Freadonly%22%7D) | -| book
Github Support Docs Search | Retrieve documentation to answer GitHub product and support questions. Topics include: GitHub Actions Workflows, Authentication, ... | https://api.githubcopilot.com/mcp/x/github_support_docs_search | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-github_support_docs_search&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgithub_support_docs_search%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/github_support_docs_search/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-github_support_docs_search&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgithub_support_docs_search%2Freadonly%22%7D) | +| copilot
`copilot` | Copilot related tools | https://api.githubcopilot.com/mcp/x/copilot | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/copilot/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot%2Freadonly%22%7D) | +| copilot
`copilot_spaces` | Copilot Spaces tools | https://api.githubcopilot.com/mcp/x/copilot_spaces | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot_spaces&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot_spaces%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/copilot_spaces/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot_spaces&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot_spaces%2Freadonly%22%7D) | +| book
`github_support_docs_search` | Retrieve documentation to answer GitHub product and support questions. Topics include: GitHub Actions Workflows, Authentication, ... | https://api.githubcopilot.com/mcp/x/github_support_docs_search | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-github_support_docs_search&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgithub_support_docs_search%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/github_support_docs_search/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-github_support_docs_search&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fgithub_support_docs_search%2Freadonly%22%7D) | ### Optional Headers From 31b541ea0806479b619125d10c4282b9c2ae4913 Mon Sep 17 00:00:00 2001 From: Oleksandr Redko Date: Mon, 12 Jan 2026 20:13:19 +0200 Subject: [PATCH 068/138] chore: remove binary files --- .gitignore | 2 ++ cmd/mcpcurl/mcpcurl | Bin 6722165 -> 0 bytes e2e.test | Bin 15935450 -> 0 bytes 3 files changed, 2 insertions(+) delete mode 100755 cmd/mcpcurl/mcpcurl delete mode 100755 e2e.test diff --git a/.gitignore b/.gitignore index 5684108b0..eedf65165 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,8 @@ bin/ # binary github-mcp-server +mcpcurl +e2e.test .history conformance-report/ diff --git a/cmd/mcpcurl/mcpcurl b/cmd/mcpcurl/mcpcurl deleted file mode 100755 index 6ea4eeda6230889649969ca0c75467aeebe88ca5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6722165 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

lL&O#xkZvmaTmiky8x<( zj4eu4gz&xTtKpfiUNWM4O8UFlMMbuF_pwFMi;Ak31&aXVi_9z<(MW8wnEZInqaNd7 ztIwU*Dz1>%w_}Of61P!zW3_W`WOYR8KKKaoEAMCxHafjf-}yPo4Ve@yaZfW))Jtq8 z1ji%+skiY0LCNtHZ3VS`Pp9iFm=^GdI5|=QwCZE8O35CQ=#FKaWs%&64)-oU)+PP- zE$PJd*oTWEo%K$CYbt7>r4h*Yl@EtUH))}{K3C#Qs)b!n&OWCy|FxYpzNR1$BW;eA zs9BhSH$j!NZkL7yhUel=a#nmeKpa&HDuyA_JA(ezF@lN9>PsHaKxxt z@rQeW2h@uZ%YF9sv~>Lr_pLQ%t$xy80MC9cR#|hZS{rzDQjhn5(G1xwZOvYINI4>Q=4Ou9itcIuVo>|X$K`y{{zbRg z!mWUq{kh$>RvkEh=a4TdcLzpqU_G%<0!?=BySzufpleBV^PVj{GFUUwhb+(+P(&t^ z@)^MkMea}IR3oBgEcFn1V4{C{j3WH9pwz(!9c^s;CUfmWMk8ANxutQP6Eo$U=yQ7B z5tUj@y_kktDX2^ibKTcoWg2PWi=c+S6E>uY`rM%gfkbTXR-nZSU+)14*0=8;)V|}m zoXzd~yUku_mRJJkudiG??Pyk%dYT#3lGt1|9k=fDiTBlg|J?%m4cpo|*l*@#X~1-!o(bp54)Jv6`R9 z1`offtq92>7Y`6gs7arWHqhr*E}!}) z6ilxZAYc9em8PGzNPnq?wtb0C{wz-oR>W4kaQ7EAHco8lio1BAWq$v+c;@D{sK5O0 z{I^E_x0$#u%(?2u+V{+TLkcGrVnoDI9(Qndxi^kEXCMzMYx z$qe{s^W#0Z_0wYV6Ul%ao_dJE+L~`4e1=YOuUOCDV{Cz}i|BfH z!D@p-Esqxt28H1F_KPl4Ka!Zq0BvIohJkOyg+THFZ7lUDNEjbD7i@2)2kV+HoL-@ zS_lJmT(VG((U*_s&r1N%;oC#o+<$bVp|WC>9>nOs$btN(k@}|C9UC;xv-b)ZWCnWg z-9-+ruLpR;fz#L=KDT$TFD1<5V_Xb}HJ$0^TM*B%F1KhnrI9~2{}ot$uj6Q$Ijb4*AZ1qodXr>R6Eo&t4greFv|@ zvsVRXH**myytw5YCP~ldn)ziOBb5Jz9@Mq7UzLaASwjF{WPgvw36cG|f!W(kkscg* zjSkf4lWW@dNS}k66TyTtsd&@uko-Lb&5?a@_{KZV@o}bPC$B`aAgUJbm_q|JaZr}O zQkThqiWlAktQrpF%K)=oF3(%K%US>MnI3QIFMFKudkIxMf7PVHzL$-=yZ-~0z7}+( z>V;mA<@U;d#rChbH7$dYs$*2eJGA9VBRyc?eyZ~2)@%N< zg>&O!&OVJJzM}fioDV4s^TTYrh^!4KAAx1mG7uNA)n9VGW zp3Vm2u19}oNZ?WOAOFa%3i%BT{_z?7ia0E6`wvywud0_q;=6 ziu>>S3^E+6GLPTfl?>y$;+}0p)ZlOM@S~g3d!G&c7zQ@U%kqWU>Awr5wZJTR{Jx$( zgW1M9(Y|CnS8z7!+|a>v)Jcb^UC^?EM~A<^noWmfb9R@cRYzz2CVKTNlTHSyR1o^q zK?t(-r~6ZeURnCYGQoV_tQ9TYMP<1w?z0kwPE41vwdz#+?g?G$*QxQ%Hl^$eTK-pT zoL9$k(U9sHC4Z|ZgW~@(>$}_8sRsXx^#iS2=`Vdp1Es5>Xi#!osrbb$O~cdz^sPn^ zhMju17cGcXP3ex9S^rzmlyINX2x=nc(h5~h^U_jhQ+=_`KQU3*(t19l)o=a>vZ;j;!~#Z#=0PSGQ~ChFzdry zn$}Wv^WIv_r>nsIm!ppJEvlh13rDF&t@nXung8BxH9kG$@+VyCRix@#p9M2qF0MH$Y7A~caaxox|v11Iax z5rowdK}X!*Z39s>{RAjpc;I;k3C`ab>0o;=Nq(2}#CYk~%h?AhMK|ts*G+F7CqaaDauTS9Xu}Cq? zF)XXB=wa8cX1Co=Ca-%+=OS%@dmM0zm@vrV`EOK{$Mz%uyK{vC&w?ozEr?V-kcH;3 z44Ry8K+|H;oZS^o?O4#9EHot^8dFYaPR~N~#QP$ZTC@A{uW!u^i)Q^*U0PF70h$wp z<^}e)t2L&a&@A64q7mmjHW&FEoxrlWuYPd`w;yz*&@p6is-NmB6qQEktrucpG!(THn={6=&3A3he9*SqIvkrE`)p6 zPkUq?yR$qrrkv3HGz(3S44N_DfTrG}`F2+{Ej6Gi6PkV=8dFYa$_$!x|2*<;nsCX- zzMc`rT0|?a=+d6vmx9RHInT9QvYB#1^zxogvYnAZb9WY+L-&tk5na(0(UR|i$jFc5 zAu{EJ=#nf%tG1`xbJH)szCA&UX8)uv?RoSt(3rrkZEdzarkv1x@_A=_uFjx2@*B`p zSTsNDie`Uz&=@wj#Y1Dt3C&!CCfz>=SktzG`rQ8<)Shk&0F5!|shx6*=;W@5cJxlS z=LipxDJMjOvf6Wh2F-<8XuSS82vM0u)c%7mglqmBP1;`^_vFi#aHgCP&HJpAaHnJt z?Rxa<+f!`OjPHu(p`D=VCp6U_8dFYaF33Xj3M;^tZ1)|ECbuiuiY%H>FYnTt4>p3v zm;#$$vaK=YgvR~1T9fXd2^mB~zX4H!MRa#pL}On8k(t%ph`)99sJnp}!E1K7RY56(ZLu1MbjbqTH`)ASH zX~HdhxRY?EJqID$X%X$1*rh$sy+(TqMYgps+V+@oLiG0T&h}iKK{GxJjn_X1A#yFE zSzQqoy$K?$E1x^rLuAScQF9ie6bs0fvG+an_3hbe(e&$z=6Cx*GeBr^JT#`9&;+v3 zv}Vve{0(R}S~P!|(1mQi6=^njc#$QWDJL{f?#h;*{uxA--+*Y1MKq=>qKp3pA|t3n zJw&FQ5S?QXS^4?vn`yFbez22lht_Y)EuwAXyR_(qHV_#>UH*b?ktruc8+LXw!g(1) zH+zU+-UqsZ+QU9bBZ}BZGX*SdZ^p9#b;|&%=mb>P70@C=IqjkFXJmt^%>uKCm1d7$ ze$mE@J!Bk{{`BMhWU=K`Y5_6D}deTzwjZ3>UIhjk7wKv6GGT(B8DJSZ! z{iu^0YBOkF`T5r~-D4I_Q&%)k^#{%IykdUu;V}h;=Z6LlVLfA6K*K+`DNUFwz5&&P z7FF-AsLmY(s=>T+cg?dcGUbG+U^Tft9*+w3W<{)dY#iCjNy)LcE z8%b--H1mb$E!j*tp;?~l!v7gW$)6q6o-T-#8Z7SrQCBPv5?=iBDOA9{*kI9iX`#5r zpsl+YE2Eo%8ccIf^ zn)Mqx-RB{gY!LirM<;I;We~h|pCu6)23{@lF%or>%8xWkIaA+C#{GPVTW=E88PK&l|2m%Pl&Zbmz3NOd)#;m6ohU2PcEq^U zt4>Bo9Mog=AXs!!m-c?p0|Z7v{Q57Jpm-P|=wFhZ?Jdh7_~PELXNj>O7~d7a$#U43 zX-2h&0Ea6GF3du(j5T4~g5Q1v0tW>9F6`2RJIiT-=9$)ixaH5b8IHm7q1)MvD>7I{ zdss}jJ?M4YV_)BGrdXG5Tgrl)x-CeZuIXB*vB`9!!(MfQraIrxs?NH9rW@Usuv7)k zz^-rxzXcrQeD5JRX|v<#EHJSQm|2}*HjL{+qK7{LjK)yxmqmmvWB~o+gHG~{%m8va z0mZuly8m?mnK8dH8_b+6Fb7yAwtbn`38uCyn13Elmz9XCPs;{#P8OKIX27icX@)u- zW4ko#4GjfGINpCYt5JL3?`+hMGGJnzU>0-*^MHn7c@T_$^O0;&f64-Nd?BZ-N4Ow8etjo}+6U>QS!5p8ik(-|lW{|*eNFdHT`6x-n7Aripo$xogJC^{r zE_zh@WgXsSJj_}Ff9fZefZ=H27j3v$V})n=^-*C3Kgut;I2h9EPzxj=zHMFLCvS)t z1>FKiw3cda7bxHeXs(yr>tbDRwAbajcDZhOv2-UtzNTjtbeG^#J8t&sH};}|;;?_P zje9L+4!nH-VBbQ~oQy5kT{zfRZFVBcKA5mst0{#StEP(EBpbHB*9G*u%|GZK_&-@) zo&R2ESKqldP1uT<$1yQm1JOh^QvW9NSsGgEe{O3nTf@^Xc6J7sh)dx&@hRTDdpIh? zO(GZx_o_c@hm z(S~Io6E<naNL2w(d`;&rVEYIG zKP>w0{1!p7s}gd<`~M9N$m-WuQ9=$1x9dT)P|sVc-ZPwqgFKNOq*}xZUoqv!(oIo*RWZ^YDbU#Cycys=wbS{N6m%wUs2asN>nvKtUgMs+#tP+Vs7yQHP zeP>V7$yrGpCaD)lJw+_akTn$bPo1}r0iN|!1JgfQ3B-&J9Xs(W7OiZQ$gXFoLuQ$| zbLGlLiTvfzap-3KwaVkI+*tkq+D5F|36i?}05>m)J;~8>ggu&0!CPaI7q|OrR^uW{ zH}1{=acW*CPiJ2l_@|}e!0;!oORG}PWCQ2g zz`r0Z8;0wLP;0k_(?j9mV zAqKrrREuJTkMYWY__09jKwf2#X{E{PMM7mI&Exd;2Cb6C^6R)}&2S_?`X|9Y+t?Nf zUY7mf9Mfo-s{{1WFN_K>+uJ-{YeF-Suwe36`aJG$or4aH5B%tjTHiwSK~|8rd-Hh9 zR|H$x8YOiytJL*_v|Sy=vhevdgcWhqj3)u&F8KkOWR!g?Cw~F=RO@l`qow7jvMd(f z%e^T-L*+lcN)0o0G=guSdMG%f;wbTWu!hskYhVv0UQg91l8yZT>**dWv*AR!t*j6= zly3NpQPK9AyT%l7iM}xV21)Q|Vuso9PrbeIYZUue=O{5%b;okHNT8Vhi>?)pGcG_U zX^-I~r5Tt)ko1~N)OY3re!ih}rxWLo`zQm76Z?_x{Mf~-%R-UG1v(3+B3!wC8b_k7 z8M?)2JNNnumGat2gFqzpHA&l}&_yKjU#vQhiQT%I1WfcS=S#7XTU~rR`?aV^su!)L zw_jHms441#`txHGS96N}ysM6>j=Vc4Q^D$%|MX?3fGVxM&W~Nan$2Q8N}2z_{l3P+ zaP0KD=-ciuSo5xn4z7z{PfqOvVfNSFv~VzWlnb-B^E$exIX`+P0~R*V`4R{IZmA!jviYrM3i6+-ALwZk~`^7u|j>Pm~q$GfBpY> zP!`j~0yHfFrul0(0=0x#F0qgasfZ6C6W9bs@Y!%Y99YAj)@lV!Fsx}bT3HM)tgLWP zq^hClw;8CY>UjmpfhrUC{{nN`9?3cC61XVtf9YE#K6ld`>6~E#*;FiAs!vE&SOA-l z$Ib-4ekN5{J`U?7wp{#Y3vq;eYBgZ|8e+_};iK;po*#et9ACAksFu8R@G zGtF9~t2{0?>S72NTXk``!OxwmyCb>Ssf(w$*vm!pV`j)8A<>s?rtCUuf5?+IIAdY`!Hi`!SJs2DR*m%|6o z4#;TXDrd8I^j0h1a<8**ITc9fZ=KAp&(`bNsmJE6z8kcQ;Aoxl*`De>Yx~{~%EtQk zbE2QJ?`54{_HMc58qB=Oz$Yd|LrP%wlZK#5gr@@`36FC7SJ9tjl{-b9YT6Mm{NP_2 z+o%mNE7x*99m5vgyz_78_>zC*nllP@*dk}z%$jKrTI_8OT4XzL8zbfY7Ea~jMrahTkd{KL6w~c6LLP|F_HHNv77s-!0b27^p~A2 zf%#L1W%N_So~6E)!0Zco*$}yx^k)DHyxx6Y(MVE*`Fv$l8Kf#uP_w>`CADK#=M?-owOTy_w- z0`sBjfx!GNvNbE;4%}6)3fL2dIOxXg1UhhzQ*r>#WzSPb>P9%FL0yV`w5fLSw4Ah- zF8A)u^JtG!?C%4=IY|F8OOkjIZkN7bBlBkMx-J|O9}!?2?J!5|y%y@Oy*`}0bSno6 zjppo$t$U?b$%%IDXGYx>8;-Htb+T)b@M>YDm82WGhd}kJ@?+i&RBJ7L(z`)i@Uxj4 zrlR`get~{z++1_tqtq?`%BCoZ_a=8Ns?&|F>0!QZF<&WL0GKDcjXYj>yq+8Olyel)bZ$Ap*Un4{@rqjHuM6O=3hcK?*jOhqXcdQ>O#3& z*+7qKqxpG1)u#nKG7W$BMC$kCNQ;arFfr*pgj`hmMOx?}v9tB#%iA{SeAksd$hy1M%$j@(u<^6T-NL2X1UNO)TSmrimAl1*UqTGNza&9$rEd|GuKT z9pBwYER;>(0<7_L-^~Ne#`GLVW^<8=^>@pEO%32CRB)Ft|KwRD?#Le0vaU7%3C#H! z$h|p`!%?_p(vRgP!3KD++m&TQ88^nT%N_@0C{~>tigC7e?80(I&X&1Nxf8I zx6KSR+LA6qopcGhz=9sn{}`xB*1^VU*)(NCKNo*ZD9Y@)CY^Prl~2^sO1)q7nDT>Q z5oJ|6>(b2M!d~5r)A`p69BOF#*F8|kF7&D!u+V&L7X`0*N2aRdO#)225#LNcybOhI z2#l%UdpIn8UhJd~XRIFZ1<|2*-N!>I{TFg|5g5sy5ISP<0=*4ku#?*VQ?d>(!Twz* z>;pph%^+KW`}>z=p5bng&uk{1h6zQXsKfP)f>E-$s+R@DA-?Kp@YK+d;JMX8r6R zKEUiKi{1xcTKen;nb!wpkN#*tt%QQ{FOz;j2(g%M)m} z*L~htmAXe&X2O~)Vl&E|$}gG+$Htzjl(DD?I_`(J%it9=MMoCLrk)xVzpgRa*CDVh zAD~Pug&CM51Pg4=IXKTe5`Ny~4soccsf@!c&|q|*%{KwUwY8o|Tm5-70l}Z!H-B6N zVND2z|GYF+-4}l85iq&)5Tv5o<<2Sa7eCrp#z~6N`l3#g)9~+&Y5q-UOld){9{XYq z+c?yJk6?=+gah0o;Wyeq+Ua)mPAgs|Q@@$FN^js+jptAPN3CyZj~up|w&4fLz4b%K zn7EABC>#ID|EPgo%|QmesB)?^em*d#jUrwhuuJ@R{b!&Jtwui&e;5BJ6~IpMfAS&~ z`ok?6T=V62bfUSDs(r7)+)Zq*MU+zc?)5K<99+ZTv^v-A_oAWdU-a#b_SoD}(se4jZg=2vTo>P3%Z^cxQcG!x z@#1*-QC`lfUrW{KQ%0li@?(XkuT(B(@&={+#tq0>V0n?M|E!QUw0j(=n+E8s;Mbg{ z%2@uNn1#+>aVuMB^VgM)#y-&0kMng_Ih6x%4&xksv|Zc!&8~jFWxzCD$LHOej*1no zURmQCvKBM&cFj{5SbBG1FHM(!`W&PelAKLai~E^ z^TULlA3oJHGe2~pM?+0u!;8i~Czs^6$pCW+$zV)23tfhzjDE)c7#3)KmEy4%d`J|h z;RJpP$M~Gc3$ikpDLupAn)v9BL`~Futpjt8YI#XVazUTQTxVHr!CTC%L$L?sERp`Z zR*uC4?;}+cUKOzeq1ZgVbpNryI0m#fqW8|SIeM&rWYgYB12lVmOt(OI()^Oo(!Bm_ z=J{6c)u#WCwR3@wvbg&H2C@*4xB&@91q~WBh?k(Ei5T5zz$X|qs8tkeQ55k~i;%=9 z7Xuq0%euPyTCaUuTWz&k>$S+mVgi~3s2b2J%3DROcGlR6SmoB5|MxfZ>?VNL_x*hS z;ln=9GuJaSXU?2C=gb+7f+Aq!gC65*9eY7DvWEe?_qwCCX5mw;7CkR!`=KL;eFT>jz`e_q^Z)ta_Dz%^d>8i#H3%Hdi9z3_L92ErH zn9h~f0;^Y*J0dgPmZ+l51-8^sLl*6g zyj{%AWXiwWJ7bUXe{&wJH{rE=h2KHAmZs@w+IxiV3BKN1j<(~PH>ikRmYmD?HGLy+ z&PwCPpDiWcMj8t0so4B}q2iEc@zLd;eLrrlkIs|pdA!YAjMf3uc>w}q*1J%=3il}1 z@4TkbfYedmZthVmOtIk4pJ5G%W&eT)d)g8;;Q&BX*O?R8L+g^f6WG~T53o3^&fW|t z_AGDLaM?}QoWs~iZ^ZLZaqQ)1G9@$(R$V$aP|mdw?BGP#wnzQ% z-HU9+f`_-@qCGem5Te0@`Hf`{B&4KyPOoToFTOcA(i6^&)$162wFVF3p+BL_WP-15 zDaU`^8+#S(#dPS=Zs>yRNI_$YIi%8mHY_O{l`308JoqqBs_WZTAW}IP3V(MO_X7p? zr9a)LbR9Cw2(F=I9&7dBrjDQZ%{+SV^kyS`d)k-n2$FLe8?l~*$y2fqdzJRZgD*%3 zhJp!ca%LA;y0|VHw#E>Jbk1i<_ zb-G>Ca59vH3!qYOcE6%9+-*?$kQ;{u%*MOL?8Sp-wLevQQH=Jb!Kc&%M<&&`7HtMb)5M>W5KIlhHGwmGE0*vG3 zDq7n`$Nv#c#*@r*#F}G+{CKYPSO1%I)}Z_1Fbyy_=@5CE<^l&?Df>jb+x=e~ z2Nztvg|jM$H!)W(rw6U$;NWSzd9j0O&cUz)CihsdAo5BvY}{IUyQ4A1VA=YM{rYoK z`@1Ylp>6wDl)Boo8hdzAX^T+?M{m_$j?EDK>ns(wELGe-Ph0+pwtQH^24EV8vCuFk zr9DBHu&d^a&!+clUbIm`zgBm{mGQ)yLjzeO(iwU2aD9K&OGv$B2zt&_+6;k;dnnDZO z=n%DLnqRBG3Oo8W%j+3v3HGC*8Lh!yfT6#H$Z(zBTHMM^gwfGz1=8W3~m)4~G|#RW$kD7lx_D167_@{u}}sq$^ZW_s=2 z0L?wEDK$XBqm~Ed9Bj$#y&8o}*5d=!*R%v9D;aZ3saxxJ z0KWbrrQ=rFj=JA!jjTw^Ed9Mj?_>jE8plkKKv?PazvCdQDHlPDgU3-QeAw5?r`YUj ze|E@kds?|w_R+-Pc_1FFVDg|_fQS@n{uR=3Uso{F!QPaMe!8E$WyFI&U+GiRWgxx9 zDRbvm=JJ&6ev}kiQhFB&NcUj7Rz`Wz$YDv+rP6jUA65mPmdq-H&)Y5-&2Tz5+2DI1 zvh%yfryuNr5J=I3g(kCguWs|E1n*acTC^Zuh297ViCeffD?B6nLmr_e*i@o@j@=3^ znjva8>vYi{-8es@40%FumJ-`D%ntu|V{Q38=fgt_?_p@VV3c-S1;%p!@f!+bO5}5v z_EY-~b!HBt@)WIh_gRq8rjX39WaFGMY9J71;s_IuG3LRze1|~MxfL;XmvzO@>b4R+ zOb+wAhI25{DsB6)>j_jprh*Gw>@^m6!(jYI3;yqszkLgR25H^;bV%`krq7#h|9<+M zx?hoQkTd#f-Ag-;CFddrbiQREXlV|D0?;d_OEPIho+&GLZ#nkLg=IGOwPOi$LXaz| z82bG^_bbYNBM)41TfDlQGzp(}3(n48^>VBJ7r z0bOyE5W%wYlP4U_g1gEQ$no5t3=r+I2~w9z#OhryHWcKO#&+bgUKF75A4!kJb5H6| zUI_6HtwwAMo>AlY4%jmN@F=u-#Nxd`hmIYo5>BA+l%vY#xUk?zeN6PT+dyUiEV@^A zRVKh&<|D6F-9C&21>BhK9MOYKR*zNX=}lJ9{JJKS))OL?-DI(rS!6K#-gcJWgR{9+xon+NQwT2ShMQk-O~!-3s>{7?6f58k1)MH%&_ zrAtAH>Rz3}Yn4l@&}D>}i*-p)Z~|fKcZH0(jl1a-f11ATJHzS|w~m8Tjs1-NiwCc= zv(|O1x%hBKI>9Yv$GymYwt0P$E1yrs&RXtQfPu@+a{tN`gM&vIRi@Fy zJan1Mk>Q4~tNY>Y^S~5;hAeEsFW6!zB1`(;d3>-HeyO01nQUJ+re2KZHtTmkVMv2b zpdqxF!*@H;T7J)HWS6X@#5rt2!RvoJD%en9iz41p$A*HfwrIc1D0{igg?nMJnQ;mC zz8w381!3%Dhx^cpIv}sdb9-gAXV8Uq4cf4Fw*-Ufe}{0(8>e_0y_HDGHMbMn$6dA3uuV0!tFMI~GAm5w-?5cvjdN+;+g(d}GG!F%zv&;wbxjyeaz1;{HKPEbgDV zMAQI#&n4I1@O1Exr!4fge+Vueuv7{NxoO`j{tQ7=-3mI?% zbkYF+FLC#5=7l0R^-&vV7I8yRzoWePP54~dnOpPwmhyf_9%toe9luK3S5PuJMv;Ry zIGCA!$842+(5@MgRYi>#W*!yG9YDK8(!(M!anDE+jgL@oXuB7`Eb}PtPFKIytCw1z zhNq;ni`3azixrc_obXRt9}l+mVAl$^V88k=bq6D-*td4t*pD-awJUkVkF{mc{kDu7 z6&MdTr&57Gll*i7*4((u#$S`FL+cyyW{~QR<^JLYys2nzB;3%v9+vatejdF>cJ!RX1$xXeeZ^jS#R7>KSFy;?n?eoOL*6trnvG`d zHrofYPEB1kQKHfZ+R|MTnII^$@GE&ddCk2p&+%}_ z&OY(EYBL5udQKU$12$v!7y8obLWV{XFceqpF$m^!Y}C3&AZ+(Hqo4YgQTUC$y?3us z=nwLk&-@sp&hBhV(EY(sJfSbDwV@1wgZm|CDuQmJS~Ij~%?W0_vg7*Yo5P(TkNrD{ zZSJ+Dy7Wnp9`Ble8o!e@1_NBy)y1iCn@3LYyE{M(+(tl2;%*Q{S?~k4uiP3jP@sj? zWwGG@C@7ZwmuKm#<|Hw<7Zll;>en}|whhJ06 zAey^)<18~QVaD8J;XV8le(_Novqf3ccEa(;+zKZn??keaPLtdswB)W35XqH(;{$rD zN3*fNoMvUJr*=c zx5vDyY{e;HNB?tU{Z^MXWK{ILYexYUcQ#d&Y@Pk7hJ1&TflgV}En?HgxB5@F)^ZE{ zFKK%oL1wKw3<{_8#BXwB|L>O(Z>?mW@Z*)=^6DyYcpH!`BSerio8P!h^|;MQNkw9UugD^2@|cYzg9F|ph~+0T0l@>qHxMy0Rt9Vs34EDN^eJVSn`tdF5d z#-%;_h6ni6pDlK};$ovz?CrJyHACfgAeMn;unoVxppc@yScaL0h(syvcceK-sco80 zHRd)*WX#SVlfINn!nH#PyY*tn`^XrIpNst4sTIHZcD2yFS0)228n~XfyK|z>zt`-~)niR+w|Phx^TmVz98Xu<`O#Ms zf~F{%@f+=yhRypE52u5Chp{oQ+FWT29<<*c7`*P$6b2`BT}czfu{;z%*@~nbAYJsY zB>Mrh$ki;Hw>ug}Pj99ky0GRj>fep?bm05Nr78TmPW6wW{*Li%3NE`ArSXfIWf~w6 zI2~P z>=(&=(dOi=jTB>m40 z|NH=?)(zq-^&&@N8s*5l$_|(l9ccyR{MQrya^xgOdG|TVQ9Sq0Xa&8f4W&;51M-o- z|3GMNU29I@-q`tCR`nMtot!G=xvhMg4XEEa6C$DTWYQ^2e+i7V(A@2DQ<1`;A`V0* z%7O5l=6oR#ekiMkF&@}0FB?p5dzZ(^ugy;jD11)~7w%c1riy44CE;on#>tdx3 z@Q$H-!-2q|-YUk*ln-raPL_j%l{NfcU;!MuTgy5SKqg&S%Pj^$oUIQqeuUM;al{i{VCE%l%&v1%F-0!s7;_CVX3NJJ;&1%&+wLNjHT~>6G#FqRF2s8E z7~*HS`d^L^&oIFMzynZzp&9DRe=uEsyNfx-^j7)vUHK2W{I5Jle!KcVk#E_78NVk$ zQ_MU-fF?+6o<$kk>Y&S2TTUX&l~r5LLmjeMg%6`}*Z$Ei<%3C59;xESQ5>BM$6)E} z?wKOGd?hFB{uWDLxBkrRT7SYR9ZX?LnO5ZV1(s9EKFIM5g$)#%u<$a%)u+oIs+wam zyLyHe45To3C-jQmwtc69Krmir0Agd=+GWRyk_Z5}<8QBwSho1{P7bJ6O?Ah_}ApI%x%n-(d3=;5(} z9-#%FLRe8JY1S{BQFjHr^_U-KJUbR{A9E`${d%nh0a*o|m= z^f7Vx|9jM5kFsCm+caWwAnV@N;Br3oNBL(hcI8ioNStBcFSNH&AL>Tc%zWN8KWt@J z{*gwH^G)q@eyHIJd$GZYZZL|9lwN6}zMv_9BdklC*#Q4C4%i-xsta--=fNJuZJ%qZ2Ajt|)d zE>vgX|KK^cXSeidE4ThnCb&2Lf301I-LYa_6x&`4)vg{F_T% zY_>BSiF)yDqAU$+Axbp;K(}T>~FnqLHM#TmHn)A_Dhs~Alc2+Br>0F<|pWC8`6(W3Z`bn z4-R7g;0h_ywp&z3___NNlD|zvN0a%9i_h#Hze({+h)7<}VjK&e_;cY&c|lPuc#QuF zB?+G9zk9qF`|qGc@Ff2|pZ6mFJrJ|ht&yOxNH@WEJ&PcZoxbhzzL%guSbQv`iU*gaJ4^cV*f`nvZz!o5Wty2w6#ByZ3G`xsKFM>P8q`^deN zN2GN1FGN!C%$|0Eqhv59La z+Z0g>F4=hvR3F?DoURwp@(P}*)A_GqM`bgV6KLU^METZC9diodJ=}0Vs7_h2xuRM% zD`XElj*|A-@0elic78xE9(#VBYC2~Kh3AO z*79Mjf5@%{bxJe%rGr8@3#rLxhSnIG8%{l+N#wD%i& zd-GZYnJ{FjeQ)a7O)feCyB}%()SV@5bLi;7s}N(Je;gJ|z5Gi|XFSU(+1B8%9!RZQ z!AE{4h=RvF$oNOxg&!LcG=f?4*kQrnVf&XH1nKJD>aKUU{{EzkKZ2FdJ+O|#FtQ0c z!|7P|k@LH->u~#So7a(roz$X3Ex_$_inSgk>PsKj^sD|@$O7@MF33mK$Ig4(rxUbj*WFg~h=^uHqOC0~V zqS4O0j`jHevr$Mvvkz5azqB!0=J(@Mf3~7ss9DSc2!Z|jm%a{k)$ZV(gGjAq`AST+ ztPiT0+X5!!M@ritQoA-&bG{klYGr)eBc(6e2-Ukpr43Kl`v{-p_Y`WEoHM-?@hgMSkCJbai7c)D=o*`QXwIG-c^bI>bk zojp!ar(5~0fO;jnYkwp_%NzS~QvU4Dsfo&xrkdvk>KQHNr*s}i;5QwQC|!XdZ5(*I zH+*Lopx)4306Iv=&n=)jEeLEcVl<@Z)AnhD9_2Lw255fP&h)W>>&l|5MmDWvctsFT z2#4-zkzqtx@uk7)5!!g?G};@!+I4dnj2TRt-JC08T(ITLUK0Ls)(jdq2cKyxj-?>8 za{#r+gZVvEypkb+n%GlOT8_wFp1Ef#i6Hh^)1<_-wtxRO^25-=2I@(*p}wwHp!zC& zDimvd@k-vi?P%3iDBihj-eY z?*Kh`Pe7#$?;_v+`L(|Tkq={4LS36)XXxM1o&NW&v;93C%&E%P?VRr@ivCs{r-4&(MVj61^>w`iHDfhwlLh27YtuMhLbx&~!{ri| zZL@XJEH|GsbuwF-%6@Q$NHWbW-U#cWvJ2a zWxP{4yu};-4W2=~=uR_&rn-!F1Fg&A!H&rq#c5hka3f>uls6&5!j(7xiO;tkz*La& zf4+1Ng%z>PH4SXpC5ke&@Q_C*X@(gW+7mx^UAYG$ckIdQFo-O;)3dm7)Qi614S$yf zZSE+%qRco}6D>n>QZGjbT{Y4ip7k>QJFOS?k7a-J`Pe{b^CBvVTw`{D`tqo@Ng7qqOg%`7G`yw7w(3-_SNwpoRLf6?|0C+ zP{RT{hD;`K3Q=Rb$)Kf-HEf3h$CZ0i)_B*hu~vgRL=l?-g8+Sbz4kEca4S9zU@Ykj zFB}=mKEgF^T(~29=6;@y)P0j9En(Sfx~?luk(Jl7?QA`NGXaGF1piG)x9Mt$}A(VT`~0Ciayw|zy?IO>ay^@sRbi$SHg96o!@PL*L}wj zx$)_yzscJh-r_fL>SD)6)r{-}`=32kmy+A{!QM=sy01yCS-V$jcImEkFtOzAt{^czjz5Qd< zt1{M@%J>s63Y25Mc$3}j4ek6aa;3~YkXlYBnYour9FlacsAvAjN0P1;HF?KA%%JQ4 zCw=-Zk=BMdjf@3%-=GZ|{Am;NJv;$$MyVrHCKb5CK|+MDsrI>-d$Gamxx9BHFnA-L zZ`Wg9OJ^sF_U3;KKs*;_evFz&fpa7ptg|IQZ(Qoldc=S3Qg;kM(lz8qO8E#I+>3bW zcn~OZ4`=s*{e>RU;Cb#*#UoPs%LMFl!d~NbJOSo}IaccCuJOtqtfbAGGs)H8RvY}Z zM(xVpqzC&M1xAC%^5Fg(6=h4x=Gz!l#=iRrw&lBSw<+J`!F^)<>W_k+PqXEghMrHf_P=zCxW8EpE6HZ*vRLi^>!GjnWc zwF|vOp_y4W=xW{(v`zgXo31hqrW?QCP>a7l7sax-B6DC(7MYon#@qcaVFShLIsSIVIxDUpXlAUpxS9DEa5Ixq%utZkBIZOF z6P*fDBZB09IAY8eGJ@(Yw-9O-17Bo;q~^)X0DUav!;vTle}<2LUgR_QFr6RipZ2en zdkGDhJE(r?5{lL+#m~&}qGB}Bi2J{+y2aQRHzr6C#j~T@7i+wqFTHabqF$M;7_wun z1#8i}G(&@9E@fJ2f)$(g4L0iRzZ2DqC@Y^B?MLUCo0xhu=&QaoNNbbppF{k3?#FZS zryoPSlm3A#W;caeQKtFgPxQ*~^wa8KI8buHkYISbcWVBcj!XS%oBUAYEB)w3ryo6+ zUbllh@3V)=Bo*m1JTU`Y%A@i20tdcjSFcCOki(I*++18T02AXxAeIeHy*@F|YP7+Y6~HT}4fH)> z#TyFh?YBK&HfiGC`b(~fXTy#hB_90xq+t|>mGr2+Q_qkvy|KQ>V{FzC?P+|Y)$4zwj zKgq>UO~>~o-hAvT`%G3x2PoG+GeK)G%qx$biL*y0?y7vR`6JX8+3OdHxVMh{T1;2J zBQ=Di12f^V9EzuUMO@!D)~-sW5Syy-Hwmm9}q%=5tWd&A5yVJ@Z@UDLwC`5}Pzg%|6f^^+(a{O4ZLq9H{8LF0G__To#y- zSEQ08CcnilE?STjg3GmWAU(&yn4V$ie}-1Q+#j&&wTqzd^{4f(_L9`v?M1hF&7a_l z^UIr^{}+j$Gq%EZ_p6LA3fT@o8&&?M)0#{&cvSC~=09 zUDJM4aT2L$A=J(^U)@`1jsPMhWUW=KHWAh?PH0@1#A&kw)veD5b&c;t%lQ8Nzr`JD zeH-1IT~fBbp}g@V^h?OF(c|!J9J28Xu-Oa@1^ec}bj4AGHH2WalZ0TEnM(L0ArF09h*?0ki zD=*1=jjcXi|DDF-;*W71l`T3@1Pak2jv<>nPV+L*OYF|%`q$9Hpq8Q7s2?gtZ1DTM zJ~ybFd_EL;NA!jmG``JS`pnzXc^Kc?+5MxZH}4w|0$ufQlqSA1z`4u{B!JsLsA%4S z5VM6-E+=~NB>_y3)B1r(XE-!}nlivo*hP=NsnQ@$7NShU-X|A5%Itz7yOl&764@D^ zGZ@Lk{$myg?EJ*MnGw*0J$?*{vgwvOgrxC`jLPWxipb8anQax(wUyB|WmW`S_U1=p z8a7scaBh6?hKh`J75IzG2kHnL9eg_v-jo=#c*>L%e0Uw^T>ec&sgjBHr3>rFvVI>t zCN_lyC>$s&`B!yzdHg3m$CP}A+gOmpihnBNXVnQErYFpUnL-03HTZ9;CtgBn@M*gF zFiR1BqZ#vwA|aEN6!cTeX>++)x8p0KYvexKOBOOMyjUM0&2qtmf_p+vJ*FFz>v#36 zE~?+vyJoW&FUzz1Axqojj58Rlmu$Z>Igi&^Q4@F!JRN`No^;fnNi+9My4Z`h$yQne zWMui@RZLP6yYlE&xVQ*4*>(K(EolZ8|LfuptpAHwS(pFbY`==kqkLKVbO`H+IoW0W zlf;HOs$$H)GXv#~R}~y*R=jM>JcC2A&XoLU3RJHg$A*>pIZ4I7O|j++m$LmYq?C<@ zqYu0PmC2BxGRmjeb1|k_7ADS%?lQlWWepx#g|p_L_QTgK9pye8{hX0vjV&27*H?9? zTZ!aDAo;hye{-lZMPnQ2y9=g?0lun|O|2JgVHrmpKXOCG_Ae?pA0zL*m9ew( zDl=%G-1y>B4w|U(W?rl_Yq%#v-EXmk-P2fqxT9~t!o;(0=scbN8$udOfz7S+(DMlw zyU}6u>+`y|LZ#&;WtW)(JN@itABj=8--uW0{HZt_nW>(@shzt&8YgZ3(cfZU!u>o3R z2U%%%BD{FisD`mRh8bF*!;ui$*r*rTmrwOVH@4WXXZiACOCJ~b+dl|hY|pvAfq%BP zZIz*8nr%XfcOS^H;@vihcfY{OGP{V374ljECWk7&S*e%GP-TnoYdqr039O}a3RGZ4 z=;$@JK(g-idb+wgTfL}#>*(uc*q8Cq=E~?Bc-ciN$tlbBJCPt^N*Efi!$>~^KI|tK zG|@n9hw`9VY;>H1s|_88nc*C*v@LglFgaP5(Zsqwk+b4HE%g#sWpv!;uk7vSpW#dz z?{*?B^K7hRET1buTP^`YrR7B zSDkG>MZTEsjEL>;Kqx*4uPwGGw*y(ZSvv|$;aqHL7`iurqn8qm zpgy|ljJ@&6HX#51Zv)B;20i{=@S{Sa`!yPm+R?aTUiZ<+q!qRUx{b!ZV^OTJc;NJJ zZA$U~-=;vRG#35$7>lWi)M%I!8?-5~G)}bMtgi4We6r+Q-1?{KyXF56@z#7W*x!^z^V5qTtr)cvUSt1raD`lIF zfhza7*_y1M()oIMEckJ4IjrK=P=juI@@FwDnA=qfHZ$|@8P-DIi_N#S`qsAM)TWt> zv~gruVcY<&Ybyx<^N?ZOVGfIZ4dW^nuH0V8ytE#zWij#9j4Y#*y`J~l)eGr))V|rZ z4_Ge(Je=Mg*tsfJdMfWF?}X-e0u}yeCLldNP%>-Ia~%{54peM#0KX}l@;+>xg4SFK z?cx!U(&#Pz*e}#zm_tykvy#gbvKL*VpoBTGU5CpL~=Iv~x2j>olaOXbJ==3eP^bejs z8^Qg~&B|gojPsOQe-2Newu9j_3e3rWuuSN!a{7imEMR`k4xL&Cqd%b!De1x?M9FH< zEOt=^rkNP@`^X2(MK1plQ%GcG(N}NF48XkPTcdgN&I`7TAL)$cAeAxJm66Z*&T{4d zb8xEsqf_NyVawmNgz~?31>|@nvf{2F<#%KHto@$4g-tEH?c{ILOsk#0m-%zcZC=+@ zv%UQ5b{&q)ewFuF?i>6{HS;3R*5Duc)t_j27G0F%#YJ$QLlR}Tr%~cf=D)}vk=cSN z83O^iZ!fx$`c(a)g$FPNd>9IC8Dl8J@$ZuWB$oZ-nesauYFJObVjJ^8@{y#e#789$ zj!KN+k*H{0_^?3Z53PI-LEK+g-EVyK%kj|%A11V8zwyy+32wgoY^E|_Sdg!ezwyx- zOxmE_`3uJ?*WA&S(O?Tf++@_sqdc13rZ39dkJAryuHCF#XzwG!48RjNQdO%K5QDJl zuwfv^kXghnlR4_kS^TVI`lFxtA~S8^hZ4b-ae>Nseb+agDa$`1#9!<_i_QJp-@du0 z6J7sZ+pYh+Na_BR6{t>8geE9L_CFUmijZb6t(L+K_{JGj{pD90aP|QmCj))$S~ep;iW^Av@7 zTeO+63HDPd(Y3th_TjZNxF5fqDumr0;ZsV|#j@Xm=@+F`zgSAyCK@r9P_@3V9h#*# z(LX0*#NYGlKvD;wNwU&cR`PjWwk*0HyG`FK9>vn(2z87@3cl#$^2G_A z;zmWUud4deb1;>}7yMoTW$JhqSKc6>+)zC%v?TEinJ_gwJ{0+BCa}b_{~9_YfbYqH z{r#eX%Yc{$|EwV2g6{61k$h9TjpvEW=on)Dagl^=u2bX`$)?4@lu_O|?mq0Z+eKZB zZ~EMQnKj|UzLdsS#<&ULc9$My%EQVRsjCWb<4vTg3j7|s^>m>5z>B|N6Ip?Yaekis z>f^InZuj(E^WN1Ob82W^t+iiv3fP=PTO>k;1{Ryw|AaHc4OAA{)vIPdLb*|r<}$^* zbc471w-43Z#()7^NpIfzyMe|eNYKX!uabVl2!N?0#AJ1T< zWmPZygAqYirMgqYqVdnJy+$mmjDeN$&iVq^tH@a@+ma=x+H_fR@!42SN>F zG=y;nSgM%gzoi(n1M-~$AsJGx*roYw1~3C6yM__>%m-ZL;B)ND?bgpn0<++UE+L$5 z*{oLk$xW||`G6e$NX4pvgg3lh?@E=tj~{nM)a&$e7Q6cDR}pNoQjN<>u=fWOf){dZ zUi+8iqa@b3J?Rc@-L2sqf&%7O5k)k(K$y4pyvR#dVH>Dn61-HZ%u7F{HAjKFm1Y+>y55BP55A+IKna*%TX47OUvz^=vHGVE1%gGk!{0 zR>P+1e70JZ=k`)TY!-o#n^cnfiOCA+fV`1Vu9y{GQ~; z?+_$+XqxtUik|{kOXLnx<}2z%fK0(=U9qR>hs}A<6Sm|N&L_QPA$xX*s`fb>dte%> zj}^dZ+q`)Ws*hz9Wa&pMu`F#TMVsJXp9PxQ-|=&otKwsMVo?>2KYQqJdvcl`^Za$u za+-gWcR8xU+%8aBe7YuDuWT`qYHXcgQYV0K78kg@ofbU)U8?V<{^raIDG^(yN_Ou zM&r4kIGbrL;hc;PjIZ1)G~4c--EaKt3=4lnNz>fVP({nusXW%NmlyBXt1@b%z|IJ~k^7jv@XnJ4I4m8w|>?v9FMdE;jJs31MvVd~f?|P2B3D(DILZ z%Eu^rt#W z9pcq5cT&fZWDCs9Dr46NpLYJY~er$`|@Dz1z&{ZCb~Dqw49fYOz^taL&r77d4k9pT+mKa|Ejg9kRFh zF%LmgAt~YoNkeU9x+IIl5`Pj;e)!vpig``KpJc99A*D|4mwK~6+UhKf$!*pu0^e#o zENsZfIAxpuRGYPfIbM5|RX6*46t!_CMgqhQ2X)$v`6AUczdg)c6x)^2&C4`YrVwKw zI}j3Za>`c7x5q%_b&Y!p`%c^g`-X#k%jatU_P@fv5?bZ(wlY3s!xZP@;DGYju(iC8 zDcQoNhZN-y+4#GD7pb>{I^TyGQmT_BMlZLFMQYg8ioE2<>P>DM(86S;E4@G5b4_~j zr0x_X+1AaZ1p2|g>@VQ1Pe1bOOIxQ}`T@@k*4eQ}Td#uz)~tjhIlg5H(>o7sGxkvQ z9trqNU80i7MT^`+9bR1e!L`yVq2#`&l?Pm>N)-{xct`UCE9cN1BW+ERq(n-exI$C& zAhWtw=~O%8lhaj$7sFnwvwC=C>})I~g`d?!#>c(R%IJB_#hq?0{)1s27prW|Nu(y@ zT=QHt1u_wTXxdJ26S0W=<{>|LY%I6h93GnNS4Qa@yHVVJ-GHgGE_4e`g=1x%+u_dhX6lPeu`< z94|ibl;qv1@{^7B&CB=VnYyMuU&!6&ko%k;FxK(lVu|YVgzdZD-xC?e;blf`@I_&} zQ!7aqPtyI(tuMX1@WT^IpncNKkPvfL05J)mLY)kgE0IK$yjeTM+NyurOB-MixlcvB zDojwvX4xl%fY{^Cr_Sc}pH@VgxLSE<)|?id{%Q^_-x8Sl$@uud!1&kzyS|F({#j(3 zyQ4Y8%t~{Tj>E%A54t&z@vAJkD62Mv8k?)78I*VhYXiiI{y;_84u83+MO?czV*0>R zn+j?2b+-wWFbw~RxIx`vzo&s~9O$&SUeY4VDTo$?wFVR?D>V54Bdcq5R`c`K{6aHf zWcQA>>$D2USFMG~=NYo@9qYe$(=WfDW&&TS59KjDxu^Vd41xwpcZA8|kB@GJS`+D|a)oF|CriJ=AbHNkU8NuGA%}p#^6s1AsGl z{>InNb=~LNBTzdeZT~wxFSKBSCTRR##Uvm0Co9AMCH;HIfo>Y`2{WV4!v3@)vm!pj zp5N3$7}Tius|>xgrZTi*&AIV@>m;aw^OV(H{G=RnU>;>G_&U_^5be}_cjZ(>zZf4~ zJ3iKD{P0!c2A>=sT{nJsb0s#_59{u6%Y9gU3~LzbgpZ)fMz%>9zL*bR5#lrS`4V(+ zqtyMB?vMU-4bL>q|4Esuk|B00qbe5ycpADo+Z>5WrzJ;N!fRvw?#j3zPq1<=!Ug$} z(#x-rUqmi$Lqi!_a{iU=pL(H{C?jwk$N7`a^3_F7JGBh*hWvbjaK)R(t$=x61}0@+ z2>8k7wOsY49h7a}@b^^zWUD&w!4;h9{_&x+L2QL)Y{l$|{fYWcP$zZL)SI_TsUWb^ zyq*bAiQC z-Y#m?m-BpGm&Y^y72BK60NBui8_58fD(-j*2DGBt^el^P?rqJcnm2qZE0q1gsmkcu zj-O$xV#7hU3eFJ7t`&-Ox(7un@kEaBVa3?IX(fJUH{-2_Rtdi*`;eUO>Lm6;;F-?04PR3p3txtb4CY*IfS8}q`P5Z6k70&~nfJ}f zBOthaA$^e#dpmxk?9DA@H2H|{f#6lUo>Wbv?Dk58&P)IcZ?WaX?FO*j*j4&Jw`7hj z=RY>~QYDG_rkamR+!qqET)1(MRZ2sf*NwJ*HKIGZs5|JBG;RIQ=l^qR&+&PiVt+ei zype}a0(=2m!3ZCz~pTXhpv24YB z7>c~#N`e_$pgYKf%OJGK4GpHtFZLRGSo}-&93Nd(7TMA}Q4w7S=9Rq!eg?|k{Oh^# zfqm;s1DA+JgMV^ovm&K$TuH#({PNI?KPehtVrC3tu7}GS0%wPoWM;voz7lF!O$l70 zJ+NRmLOrB$DaKf{%GdN-Jn##@t)X6ZcaQZt&NnBuoeW(y?=4 zbLTH?)P=i<(hgVjeK+fsu-`K`x!A)u7dqc$mcKBY{P-#}!Nm{$4Y^qx$<5T|eqTgC z_o_|`2@5}%#DE-Ha`BavFUoMTU9AVP#>xR2H?Pd=Op#ayco!u0%pC1wXzNxp;u^o{ zi1Nnfc|FgDc=1$c=roOfqdP2#b@k7}zDGpYQqep3Fu%Rew(`hpQ zf!u?t16ZGTt5vT3F7ogX_H4f&Py1c`;9u|Ae&Wr&uFAT{iDJE^K&gxAhALy1?0E1= zqXArUHoHHb^p;&Vexb7uM-XVn5zuX=*ozX_ z&8PX~sQ*9?v}hJ3BW^`;^RETS{Q1X%kve4J>_RmMr)v(@`NtURX#_N|`O|};ub%j! za9{??o{pn1iF@=@rU_D%fp2TL4*=u)&il}DTK)Q>aFf$MEvEUV$GxuOq4bn(X1d;e z>s${?GCcZT0tS{dmvC?|#tc z@3)Baixs5NjIQV=(s^)N=r&wx1V& zww~;~&**J;nH?Of)>^cwdZ>Brzih*1JxL`3bjZc0OdwJbF_K^Na#CNrWGVz$wv{&pt|SO>MK*kF9d!JDSck_o@o2 zZWQ3+MQ~tKgZW{Y{I2$zbpFNUH&+wta~DH%M7sCo>`-${q(Q%sWSXJ2@bKa(6dj$6mapK7Fc?Q(yYj1TCox zw$sE&=?*?n@PrYg{#k7v0h=DHVsVanm*Kf$ogDHtcW{C#c|_zR&epz;xcp^?J@Dq= zyo_xunu)F9tW&`>Ev?f(vD>&6MElz~W!BOv`m$T}W#-k8l=rlEQ&i5pkF>6amb`Fq zw`Da|etaO;652(T_Jtd^*JP?L>!dZ7w%EFBvMhyqvh$0&S8L1$hwm1WF=Oblc<|&F zsO||sW!`$-lHm(qAmA7UtR?^lC60dnoDjdKn3lQQPb~-cwDpON`qI8vjKue{Jgia(OjCIT%KP9iFHKiW7=C3pOp+ zYpC))Zwlu?whZ#tbD*NdTe{ynk4!J^`_3cIp-s?>UX+tqbSM@Pv5VvcVRjIUw{yWo z=UB(w&5{Nzm<+0wS!onCB|s*)v!G`=mUT@|r9ua%>)&;dDZhWiDeDZGQZL+6s0*;U zOeiP!Zu>rQz09gNq^`aW<-Es5L+gnj_S}a`d)ZqFtl$?t^Drak-X2fCJ(GSDm*--e z(r;qUTv&VhZ6j}9^#09y#HNEw(mjn%84k8$GJD2%Y^a23+c0Y>HwQM-@HeiOvMihnRx>RLj{W+X(&nb@1cZ^ zzG)>i(Jnhj`&oR|^*h@g+p3--54SmO3L; zx!r9+OLooqNoA~TVg(|{;Z6SZ%_8R|4(q96!?%i?)!~j6`k}Bl{Ycn0hMmr* zh>=7{K*s5#iO5e{LklgUEcklvP>S8=m-9MkBl6cp$1Y#r(UUL2A12(3J>&3e8*iel zLLJe_SlvWy8xYt)!jsT`I7a17}%0CBnhJi)!az< zyENua!n}NlMOD5pJfB9FcQ*U>g^`_^bMi=XS(^yU1#V^Rt zFEh)FS8-%&Vv!eZch@~^8XKfGs7+YQY8|Q03`a^E&KENz{^t(KiOr}e6TFBYDAhlg z`J~9s|A+4K$w3jNWXJ!Y3tU)*6K?U9Od@U8IUtIT>AfBgm^)lSOATl!3XYS#8FgS~^Q&S*2ujasljNj?~p&2_HWW5p!*QimPn zN9CFGT+|IoqO@1j`XTzP>hc5^nAv9fxOSYMN@X^Z#G*j4Id-wF_ukk2?!o^Jk>*~& zF(dVJgAid6Z`#v`mNbSS8@k85F`>75ytNImHlzJK9X|+D|LfLnR5&X#(Z#xl8F$gUoCb`No?v8#O+C~2v)r8S#W1?IdDZOp1q(aAG3#$D%kyw24f>zg>eX_sX zP~A5+fOU8v4C+A;ER#iD7`*3AOYsIong)cn3es0gdRgA>>A3En9Wl>Hqe&~&CNB7J;x;_K`=_4YhE9eqQS5DG}!fD4mjk~UsSAzjnU+8NHVc4wrNZzYa=`~VQxQn zpQ8!=IErBPW4_7{OwTQ?gib?-aqKj2r+ilGy2Ie-=P2|`^Vld46iYc$-bW$XRp|!Lq@Nus+i{U9I7_WMcZYmZ=Lm@ zwM@Y(CxK49`W^8RplxU9&P9X+zvT}Fe4?huEWA7|+e~-`S_QkX%sq|6=dl&KI3cx` z_^Z_0tQTvQul6AJRxr`X|6aCc`3!_y5}d&u2_DlDx zXSI1*G!Y9P$1lJ#RZaKbxjd#$;abfIPt@~FZAt4(H&&>3alLE(3!~c>RjB?qoh*u= zK139VWX)fn4siXK-@X4*CsA$xE!6<3|GJ+=_2@s_lizg~wTrW}B-_lC!%aFh%QEdr z4)t3ePwlV0#&0|J8+dg)*t*hbY@^on(AnJ;0#xgINU$`v6WuWg{V0Bca$OFwTjqK; zIUuUG(Fphw3|XOqPg=R}klFQR6Yey-;Idtmye7T9_T?Aaq4_Wh2}2E50byq(y$eQP z=<|wdAmh_-TyFhUy!&#evh? zYB#St&CmgEWb(R3X1_FMe-~a~l0GB+-FOWc039j4_zdWH%|Ix3HG<4|upXvO5aChE zydv{1x7g9lZp*HDB4^(Y`_JcVYNw{hS9k~JdY-Tn1wZ>wx>oQY`fQttBy1Hucr-~6 zfn#x4#ezH$AB{)^Ugvy7Cudcpp@tsdp^}XWTl{uMFH>?8(>DcQM}(MwLW9V@;(?-k-sS=u2-nE7IG9;_Xs~UN4fCep3Q3taw+eh!@Jh4CxI47#k!XR^zSB2$~Snhevc326BWhIc~oi4|s zoYd8T#_qSfhuAZAOsp^fer~-L33Ao(-Xg(_ZP@3r^tG;n;xL|EhEW0ocxJn|XaYRm zJXhvjDw9<`c*YxccT|>-dmTg#8sE_epGhes)N1`?W8e)3mABAI}g^$4L8Cbl1P z6l;+DNv~Ew{MPH6Sb@R__okOQv%GS=RB$D|w zvXE_3s)Z14TBrr^S$o}^YvHS@ae{2vHoHCIWV(+EeBVdFkZT5LNvl)f9{38L8$7ag!&eo3zIu(mw2W zKk8~&WteRNw_PP@$XAH&Rw1#%1mLACd93c&w`5k&-gjlk6WE#C;2ZDvk4AouIYwGVFc$T5$Txgv@m`wPffmeYV2 z_R=pc-m?fG{)|m5Hjmmgw)BeVBo@ZpU;U*3!Y=7(?omI^*DI6VEB2N?_<;WYd+VuSm7Ex6HFzOdQF08K`9H=hWWfERqOL#EbY*q>i z5Ae*<({2`8=RK@<{#Z)b%XEs8$4Lo&+UY1eei_X1%dISvr9c(2{Ry-GNdc9-sXJXt z0qEABK{V6vqshuT-3sID1nI0l_&p_ijS?>_wBQ|PhDg{YuZo=I;M2xjy-vT4)|Wbsrg@H5TD6 zwiv=bib6i46F>36{qZ;6Ir3|cdtr=ij=xGQibpvtDo`1(@L+^V(!?4fkW3m%5m+-;ztPV&63PXc~?*Qmt;6 z6->$MIeod(XBzeE0>t4A4GFESNVm1YcGyJNW^^D`Y`N?Vi1m>FAf2vz`a^!;Qmq=4lD93W#KUrNEFn#lcH1sWnl@%hXE%26iP5cRkO#E%ZqZvbH=7ec5 z6K0~`o%IT9@qdHf_8jgNPo<`t1uMto;Hp$rYNSCxi~q?L-+w-%c7%^xmQdzd{O7oV ziUQ|{WPlJgFY+DHoz50>&94Kgsr3E+hwx8=Z>1Fe=BofZ$D5x^V=i-Cn0pS%5~dGz z(W!6}z34YOfaYZ1zRGP@LJP%K*#@&LYMKFMQF{BuQ72h^TJa2hOvHpb@E1Cp5$NQa zs7*Cjux@p7Wmf8Zf)?l3ZD+4S?t&P0`xpK`$}Y!kT78@CJ}T+;NoiuGcjJFf?#IoR zWsu$tR)f%W9YeYnaxg++@Vcv&cqxfB@~DKle5ip+8CTiwWa!E!?uj}cl8%cb=zx|n z5|Rs%T}LlK2<5Q9TT|P|E6EN6{>{jaQL*C9( z6AAVCT=R|iF-w2~$w~hU`aIpoj~w-QKE-}gfJcuj#CcH+0Yhq@2eWg|21(+`M6tMq^ zKK_}W^kMXrm!;BIHcXS6Xu7DjrPdt-1ToQQ^+_R35mXcYI22h(84Y-iTFP)lcbl_V zV~|e)clA#I^aYI-l#LruG=RM2YlJAtI-3;^QYCb+990Mqghl>A9ejuH+?~1i4aqut zVvj>MbvvIoP3QA|D1M1T5QR$EZac#6SZ(E2=Vc22?T??`k_rqw;ajNOhBID$489k^Sep&LlL;jT+=vbCoj`n%F^$CN) zRR0$WN z*2mq~Swwz*erhvx-AuQj7VD4_`;X~1{=>f!uOT+${P?hopXK6j{DycL4V$C=_X z$;F>7;gpp>_4VWPyz85;va`?<1#^N+xLfj%UMlFr8M5D80erF?^`PV@)R&r}AZ>Mp zDR*G^O;`Iosm&dPwgg{MnvP+PKRLuDJnRLEwf41(mGEkndYJHk_qBYUd;>od2>VX0 z>Hb!0`u5Rm)Yd#}X|+FoD~^%An2v?GgXfG%p84CZ?g*q)ta-^3_r)CzHsj0w@QyD1 z-*h?SsHgoh!LZ99CKw6({gOQ)#T;t>oKu~%!GidC85+)MjC^c3V~u^kz9Gx?c^SYp zmHzvkRvN!ZiPtc;xL?I>Uu9h*quR3E#56B7W|z=Y`dMp^<%P=M)afD%M9I2PI+=^lhN{FfPmxXtU0drb zib~aK51T=StbJ$n8#8>dV(;+_m~T7UyzsQT*#*`wA4%ss{0c4LR36DeU`b>kihzq} z>n+(hVqmazOltckJ*%XaVCxqjmCwFbL>uqyZDz$$me7BuJsr6xn#KR5Jp6n30afM} z7qHrR9JU3iIfeK?Rg%40FzWZ}{&K*GyfPiKZdVz{|I2VGZLu`ZjaDy`TM3`irkpRY zKPU)>ZZvCncAL>Yy@dXor%OpeEp#kXUEo0JwJ@Cgok~bYzpK0GHw(}1%$sNOn^AoY z_Qle;!IS>L|MR#?ab@}g0e7EOULRB+Zzp%CYsl+gfWGo(|IZuR;w?{qpor+x;&NE} zmTb1!tiCR?JLl%hDI%+8xVQ2)|IcPme{{>9l&)Lw<%AaeN`n6Jv2+GzoJZtJ})5cHB@%ns2OA<8Ri=TrK zYOD9=KjdBAJ7|NvS~Ba0(?48|*UqRT^ajTtTnU-CdW%PXnc~MZxpK?}Uv;O+$*X*F z?8Ya%i~d4VWa)!o)%KsaV0~!8QzAEFWFOTF5QG+8%A4>DtAH9zkJrM+#s}hW>3raP z-P`_t!a@jV%E3|J@RbF%qgMx(FAzbmbDa>WN;qW6?_@!lFA3I}5O~HY6!{dS@nZcp zdizyw1gbVsrl-W}aiwqdGG!-y)856X^w8~(FklUqzp_PPx4%zOSE?vT61``Yfl(A& zTY7n+Kkle-Vx@{uId&E2HRH#*GJbJJ1+*#J#!)x#&CQ6a4n$N1S2>X9`57D$505`+ zP32JONHFdR{c$U~w|bEl%Evv#_M}_EEX~jogU{&B*E^1qF@`i-VFnMH!AUI4`iS=- zkUJSVs8Ggw=Zng~x6F1EkDkl?!rQO>i?I50vbl#DNVRP#QDLZjt-YF1ax0W=@pHq1 z>_J<4x>)WJ;2JEQM=Iu?)QafNiqM%n+Ofv$+CO{~$jARvITv*ml6dCHtaaI3o_fM~ zjrI2TD~95)5oPH?bD=gP_qSSEq{N-D)DH`g@rtdZIDhsBh_?L%V$~Sb}PGTGL#LqLa z&vT()zMaZ{PdfkFspKD%&OgbosgjM%Q02dl{NGOga&X!y81^B5FMcQa^W}>geV<2W z`ztpDO$kJOMbJO=7Q+$tzVs`+tNDvHxSH{eZi7WbQxST0dXZCJtfec{DTwCeqg)}O zlw(fi&QbvxyOe>OO?5G7^#=rBMQ}y5DqPk$v65QNpPr<2t1YlRTchZ>7-DlGJ}+(K zx~_xM{7#OB8tucC|3B8w1U|~@>i-kSKv?1gB^W?-qOk_oKv9`uWCB5+feGN+RBfBO zP;6~qGzp+k4Niaz&=bn4+x$PgsKc)@$`WEbXLQP)EQ_Jx*OQG`o+1vhJMvJ4d72Tshk6?2hcE4KK5k_xzKLA$6VW9j)e9H z=Cv}E(L^1_wI-e@|6U+A5cU#H1Z$FI|4P*)%F1fu??hVPACRK{s?S#DHLM^c@yMF^ z`;pcUf@4DI#GM14NzA;<@9mVZs6zmHLyo>CobFKz08h4|K`ZS>Z|Nqj364aI+A7U*I$ z_n1;Rj+twLcVss3HN$2jE$?)%!xQF^9X>06meFkc8WKA?N91Gw%SRE2UF<3BSdu%t zbevtRdUp}gH^H06w)MlH?lEcoXh7_}k*Ue@;y-3GE4*W2nL({-%Qze5uwdqHb4P|BMb_1kJw7$j8+o$Mrt>A5*&cd?xaUYf+ zh$0efbnoL4)MwW~c0%p?;1DuSA92WJvi&~?lpfOm{=##6|CyzZ*#D$2!-Ra`z=RDC zFo5s?1{Ki`s8IZ}GC9XsR24yNcHI9lVzHiDQD5Nfh0f-U8t!4fJdnja>$`!x9E^LRDd>Lh{X+)8 zYO9J)lVu=nVMmjJXyWX$?%kFJn9g^t4U0Pi2bY7sCLp#01fJ=M!obg%EOBn7&7IF< z3bDq}BGCx?LueJRO-rtw2g#hF-*K);miviBvi&6GQyM7F`a1>SXFVb}L8JvT%zn>M zeCvCthA(sY{@h6Gt^tuA`-Sl+gqqCXDkN8cC&OH~f}aD023hW>{2ns<4zs^r5?I7U z8#W+oza)MO6f*sckdxogeR6zKm$;K3SX7X=ZE_qQ=$p(wZkNVhoc}F|?ffFu0v0^! z!p@56ko&;5j4Yof4;&-Q3$8$TFCzB8=x@Y#P3o%;kZP2H-4e)9ns>aTATQr4Tnc5fGvRNzn9+&vwLwlMZbleyOu*CwHXZX zQ_89IgU0?h9vvup5x71uuRU)p;CC(cl(4-UE(aLjs&GXf?S@N+EL%Zci{YET6&+HC z!Z{m{$+w}M=3 z#Lgg>u>t4hawpC!aw=7QeGyHsV)?`e&9l^sXZ6|rr+hJz{=h(wcHBqq>O?liEH{Zz zpzUO1%yOypfey^D9WA*hiyyV&;U_GSr2XPqW#!EVDZ)d!#fn5KZ@LPs8_N&z@7TUB zfn>~dwc&+wCvmPK{k-MGZZ7gQMmXXWtYKXOE@exQxHc;sX{xty%vaD(^6U&oTq^?> z2e3edw#3a8G4jjqSSgys!e#h(@wWaW!n^sa}$^t(BvS^Nx!YhKC z4y_KCs4cs!(}uma=^1YR7|P%jhKeVyFM@_{x`t?SQSR+AxB4B3#6BMxh&@4ZLhW5|%D`!+M9V`E^fswCSgaU*3U#el%7T8(FLM%ZR9 z7l|DhSx*YK%eVZxtTGNDiHgYJkC9LVf{E+$;|&AKQViFCa;FWul*jD1K06Ws+#hpK zEoAuM6v&X?%716z9lGCZnN6QSI)ytcO3%LCkt_vIN&3+&f)AOWtYa}m{7T)0X6hui zTX8Gl@Z`pQ`ZC;I3oC5ynw4L%ec)`4$zClIGgzj~POKJMS|^8b`}#DqFl zy$hUV{PQk(zKy$MyYkxB$%5PWPE?VNRz4qi4Gvi9<$LUK`P{8!VI2}^z7(8tGMbJ z_^${JYI$Uguc07)9v`V(M}gPP-bvSPZd>bly~RpMOOC_-w1PE$;Hh2~ppCQl^SvoB z=TpFlRsL`P^T7OVukey$iz{>jCU7a+uDQt)!tXmmw;@jt_Qpn9KHN+UYyIF@nU4B-i>LWZzs(L61L|yFIjO z#D>`TPl#mCzeoG_wrX$!b8aVBqHx{AV|?IG$MMWL78y3f#x$55bcAr69L62v(+W1G z@tJE|%bFqCI}W{LWa~=;#|e_Xv3oKCrvD?h(VEzSJH&g!%C`)j!tJ^2zd4m6xmowp z#gF|UQZSME45rpRGa?+o?j-Df{@PA6sb9erm`M>Ky1j+(%;qX^oIaul@_af zxDUjG{?%K)YZb=sm+8ma<~r9;yHf|BP!>Z|-4cti!!&P{UA zKki}!{`QmPk)8Chq^b&jp-;=@!mTiwk(ZE8AGHBGwbi#B&X>w(PA3=;;a=+883EW+oDQ3o+x=XLNg z)Rxk221Jg9c~0v-!_alD>fmio-dZzqp^c4C85PEzs-=indgdYbDxMdc9=-N8Mfqy{ zAUznI8`h_=vm`4?MjdKikfER`x-_k#@8l%$w^;`K4^U~(P0f2 z7+-7I0C%KpwL6e8nf2SPFYQlY?%ZC`h5U@&#qsQnka#O{r@ZcgC!L}weM6R34g#$M z{hY=U&x0{6dLa|LgVn;MUeubM5FohA-a zZ6JwWo}IO6ZN!)0?&Z#Ev5dBsiwMuGMg^z%4pEf+Kc|HI4!u#x_oFexo)xS7;3y{J zO@l~%VG&a&j@QQM*ADX9NZEsgxOe)xRQu-P6W1AQOhhg0QBP6k`+?pv20Q4E7-u-? zxE@(*_$>@q4g=g(y$t@n-E!LU-)H&neA20_%x$$Ptq*27_f(q_bbn2Xdweuc`kPn( z`46)5%z7w#|AO! z{z%&-&`b#ySsD7Qv|wD^y^Pu!#^Z=`nXn~C|jlh=K6n>TOMt8%J_?kB2))VQAr zKQ}-26NAUu@0Wm5g^P?W(VX&u$|Wwp29@0PIy5k66|x*#AwBKxNC480Ge20s%tX^A z^7W0}p&a&wo7ytZqD8azY2(3f&ebMw!h_%RJ@&ZV~>Jl0-6H5lwuLNbc7-Rn24Aqv2ThZZ6bVczLm} zD)wIYXnbETL~0~&WvZ(AwTACkHQi9;Z|IlG0uxC9#oZR3FfZsu_D#MZTTO@hRjsP( z&qdqpT)ssFOLzUH(1BS-dtKGgMWJxBXbGovG=&(RCFzxDbAjKjPgS)5kr9O%#vZo0 z%)~Z^u^PACaJqv1W~m9`^cNJ-^8N-VX3#19<3~=SGVoIZjNu1Asw9&0vWVmn#Wqcl zF@mD&tY9EETBXfKMeB)jjTx6X=VeL}>Xy@l`2Z6;S~-&Eba#@^>yC1T@_bgrU$uE> z)!vPJs)K>1$`OU4(w^L*a0EOz)eyxG&7m5GP4i=wz0WDnS#2*@;S<2$7;f$k?*42p zK_Uy4qxz6VizEHx0uWsj5?C~@VsdEQ&}sBawN}N~(uuA_Ej=d?OAwWvruVgd^L2#?chFkg1e43mn=I|*NK=b)Cre3;M+B86m0sV?G&|IZwE`lI zN#K+-3EMK=qj@jm{R>$cFT*wU*F+QJpr*Wq|96pQAg(o@0(m5dO0wU8+&|{ zfs0+28|x;o9#z5X>QTcGp_RYw#dtK7)}T<-B;sqpB{5)kMiW1nQWJRMeF2;In*zmx z19T@oE*w}iYN$K;GJVkLQ=L?Z-N40Q>|d>MUn0h@_npXWM!(*^Mmj0<8=gO2dP87N z6||DLuok?QO-_YA3CyX`2S`47Qyx2b%|*N>Z|Z8-zxp7fdi;}l^|ehiul5C+#VZMI zXZRoS4={YbNCqwQr%vV`qJdlJSGM%{UNVM0!!hjlI72fcn`eCk~4lJ6*FwJS3j_9*}Bq@ClwT4?7$_B0) zeU)0BGfE!J)hL(SUpAf;C^rKM7`_&JUUeY$XcyC4meld7JO&oAZ5CLxN!%wzO=8t( z-2Rc&;@$|?H{-)~0#n52mBSobO%|SBY(2gD5TV!LyLqhv8LGsjto8$QhJmLn@&nCt zDLgq;KeX{Y&ws|Op8~NS7NRWvuLh5Y$N%pj!SoK*(qa7-h&``X+Q;uiTON<%NI~cE z`=QD|M0u@VzEeOc21oB2oX7N=^pCx(+I)V}yk?Ubs|gzp8Y{zBv&6t~#{~KsMd;Wu06F4R;^|Kzb(3Py}A;81i6>P{t%1uHkB_GYItGs(h=- zXXey!tSs}Z+;qkV-HuVn1Z#4x|)+j{?fw)6CBWU2(V` zFJxT@C(9vA@0S*WlU-sEGKHxzFqL znJb09g%WnMl5Ut~KKynidawx$KZOs0g(x#NpeU-G9-EZ+#dD3e{VT;xI*JeI$798k zY)<#b)h7AJI{(VNmHs@?OE<+Q*z`xx{){s*=?5?qfsCL#lr;D8Fn`wAMw%i(c#>Lu zb!NWLY!MzCSd;@RuxND_Eb&b7_8IPJw&;b~2C&~yznJz6cjIUST;okN4&8e5d!|YE zy7vd0W$YVdDYo!mMtXqFx?|ltY+_~S@*}+RW*B}<7B@YTKlv7rUzXB`p!#qWebAP` zq8kxqs}buTVWJJQzq>CGJ4S0iBTx~f+^IDVL7GR@?%q8R>($LKH(-Z0zVI8|sE7#S zt35<=9W1mmcQ|cUfMahl&4xY?#J*uve5(r})^0VoI|dexq!q55V1AL4(;|O1lK*;^ zu2v^7S)~pHL%ud&-(oTReM$!a)4$b z$tf!x0FG6@mJd_lM$)#-b%BL{HYiQ8uxM&LQbXVZSyt`J^vr7ux7aL7lT)XP2(`9I zu|8`_lU2jgXR;WwrIaiEHC|Wehdz4l@i!o({AlRS>-S_@o|T5^9`TXvT5C`;!#+2U z7hTRFZG7xMSwe-*$Ez^Jlho)gjc*Bm>HfU4WmhsTc?@xuhoS{OWAtf_yw#xbiryg2 z`HqPia`={zpyc}j-cX&nfy2|Gt^SR0X(p8dI(x9`)3!FKx}XL>xx3OUd6OL6L9#py z0?kjirz2Z=0lQZeMH5p>#wRZ?^i?(et4qT%{OyYBFBxm#k{^t%B=gjN27K#;{|)CQAQ{_D(&61OY&_qBg>hmlRM!DixH6S%@WD|-pI{+vw+WAnkSX(B16 zdm7y1*TU3O>C3?T;S6)$x32{MqCa}@zr@2q@Xxe;I|TmG7j%NZK|S~2cV-?0|8xxR z7XIYTIry(w^Ht%WcB6;;6652lj=O8mv;f-qu@v~+ejUIGEW(*e3cS^?IkfLZ#N%ijfM}W`*m^W?3*6I+CG$9Z zDoWi()tU6?*MbEkJn=y)Q9Izz?ov`R$GR8U4C!xXGpsNf*q`^Hfo-vT4lMLFuW1w-zR+2~XJC*e#IwUpgtG4~s22O_f^?j5d}) z4Ct9+_l76LblBxKz6)VZSw~(LvqUaIvs1I){fFU?(9{1RM#uG7_@0s~5ZnJH7CwQb z<}&hzsgM(W_6s{*L*|XqDyBHE+j&fdGf(DK!4o+%!G(CCz2?ltScMk;lAuOJ8aAPh zIB;FNOmjiUWeYQNwWVrf9&c2fFFIKwPj^my@??9Dh5%rdzb3CU_+=;b>Q7E}@8o=# z*{ISk+&xMme-V?dVMW^9nz-7Z;H&;MRW`7>Vj~zT-pSE_0K>#Pxm<&Q|MWufYOFWy zxa^f2)fS)a1w1i}O0B?V8*{1I#z{^BnI`m!0_q)=4M>>k5RuSgkQR`N;<}|PJW4T4 zIK4^aa>zez(Eol%Zfmd5S?&Mp9NUXbJ=iadp&RNYG}R^jqxCwk=->z+X8&!N z;qCOS$h)7Ev2@~GkPCm=UO>Yf9`?IW0iw`KQdNQHo6h=exrc?!Glz7Z4_1Esok7dV zIE|4oSb_0f`dz~{&g>hD{!NxfRxMM(E)oPE=fow%CxaW9l;NTyTHfRChNYO%T~$?{ zNBbUBRLN$nptF}qYEY0qW{>POkxi| zj!;u4{xDLtf)PBOHZ`7ZjhM``DJFwET0Bew!;y`9jUJI!Dk=*lLHr2Ts1wZ5ym3X* z!7t-ZwAJG(|&KOb3Bx+a>pB088u`RM;iy00k=nz0nBt%~$^zNG^5v^=Rv zwqT~?%wRP&07Uavb&(@Wti4L25wiy zL*8f46RzF`yIYA@T~LX|vbtT^=2?!j?u>N5K=x%#UH9U@cs(}vM0OZPJAU)1&KtjM z|E^O(hMsXUqjLgg_@exq%$eMivVDUm31l-D4a?l%p3<=|lZlT;)s~7N)mY`oj0j$l z8oGQjM)t9#j~J3yX#woP`{G$$;G&LNr z9FJeG_0cSXK{Wn=Oy8k#;SoEkXql(5p5Q_lgR2M%iKleiRnf#SE<+U>R4~x|Ti`TR zzMQ7Y{XY^=w$Uy65XrXqq~1o*qS=x?0{j`qVQfcelqsJ?8`@?Q{`(i%;Y7%kduqKz ziK21ltx-}1&Z1hGbN|+4wFluE{sXDAeas}R$UJI(+}&i-Q!sNkbe_TW$=|5O4f|T0 zzy7|)&Hs`M2BFN8jKA7GqE+uCTD3Sp^3116gN)^bw|sI6|A?36ilHgRX$Lc%#L?2@ ztPVR%R@uI)deda)uX`&q5t2N@@@!OYHWT7n+v_fowyNwovu!79f z9VVXlXy8Ane)%VBDH~S(+c~M5t3UB5^&11q%Byqr-|%1S&)qM?5cqe$`{iTv5^347 zVPrNEzs(yPww5(E4VH&nx1=0c|9B(|)@cIEeQz;1onv}v`AZWckQ*aWn_Jy37BC0k zrq#L|C*Iebjobsm)jHqe+a&0dUyr9712<={mNB-+;B$9b30?$jhHiAT{=*e6F$f@C zv8~Rm|JB!W$ch4*8I1H5%(%noZ`KZRF^I7AG4lt)f8`(Ffu#iIn3d3K|8BkU5B+wu zLFd}WMM77SB~PVud$!WaR$%s68KY27$^Nd6j~T! zwil_0$`~)h``~yJ2_V^t<55e(|MA=E5Ay@aU+3AKVmO{Yz_su3C7sXq@oB%Tdgd{u^kE{v8jzN&nr`tHC}A zmRpP&WSJaJ?#qnMAqS7+SiXgSGLVVUtLfRNs2}{IJSPU7p&k(X=^;j4roFNBvlD&Y z(=MOF&9w7mExy^7zlNTiq~}}vd7@&Hz^i4uc$z)t$gKE8vjU@zMpR>s@+!6-^!XO+ zj}w@PBR>pTD{wO>HsGH3yP>SRl7$2BI^G69bB`n@p|WwB9G9GrsY<)5lbpnD0zg8c z!YF!D1JNa)(^u9fxO1H7B!2EFPv~WqXe?*9HXJFP*V;#SYC@TcfXkdEH(IWfm}|-; zey+YcF;gGL`PLhH_T7N)asSXDg^WTzUnE^Op{% zA}{?Tr7v$!pP_V7znA{9(jRV5Uv^OWCzO6mdwKzpjh0Gf_R8O`^efuaCn-JHp8h{d zAJv|I|3T>!m41AC`i_Ir&ro`o_VjXMRCTPsyV7^Tjy?QLJt+Mn0#W{(^Q_c5T+#d27_pg{}JgmB-@eBeYFt-p)L`y(a$mXkaWq z@6-(bJ7FE7aaY&4Jfu8TfxB0Z2`qd&5`R59czey9x1)hEnamTZs--@Z+C9-g>-`!D zs)!J4DjM469aThxRA6CfERV*oD&v5&`={@Tsb>3RtP_Cf?OT#18s8j^zgx9f)w&Kq zYCtNq{t0cEUg!+ww#-}2Sa615@P_P*TqkD>P9m5w1V{}kt>NzN9HLAMk@(+?MZyen z`a{Sn6`j{2z$eSUs-ZUkHb{*Y5j0iKFd9boevzJT{|7w!&|0ic?5EB>(Hr%;_EG;o znm~`tnm{{PK-GT2P%AF^|MmV66FW)rLQ6&WQ=(;d5FabOkRZ|IRb|Vp^KJ+RXqf2i z_pNgBpmBL^Ht|3&fr&mf1iJ!3d2BzvE%cP}I= zgH!Q&-jwN)sO-m+a$={EyK`A%naFWQl@w}H8nVFylP*rzp#`CCP$>^^Z!+!w}0^IVc5 ziD4f%cO#q(=hYFlHl9XTZXNf5fxx^Y$P(o$D=?%$8GE7Ke~*loepD0xD{(5iMw929 zTZ4lqOaoufbLH!q4~d6XPUp(`9ntu;WmC=kmsjqTC_bBy0iCXE4a2WY&#O=CdGgE_;%c`JVrq66apWgon-aJ*R5# zrs`56oHi^qjAIN`(kH4DHFRLBsSoT&V*3l~e@Y;vs=rVU=i7Ixn*Z9cbz<_=w<38j zMrQxp7i5@Y0TP9vTDd8t$cP4i+_=OsA8I`ibl-Xb@yDnUv!(GQ)i0>DPxg@cWmCuO zy~&YTh11REGlx8~9)!9s(_YS)9f1llIV$JQW_RTV9l$^dv}`Mz*NDzaT#qh`H4r!6N}uJpYv!^|HtMuT+_oJ#L8Klu%_kjc|$) zgQ>1*0Eef;ZsTt{av#B1VwHIm>2iTsk-q-Gm)UQp{m9ephHI5k0MvGsQDoK{CFU>o zq~fWjKW3Xk%R1&?Q0N9MRuybKR_LwBiL=tX`qu}o^ek&M%cJ(LP^QFP@>oaEHA71d zx_@}uRXO+)7{E_F6rQ`Di!* z-`HtwHY^MoXU#!cwcP>CncF*Q-)4PTl8axg^Z$iI+T5`!9Me7tyrlBmtSC^TxPS7Z zP6MUhvA+E9knUxE+un%Rv!Z5bKP6_`+@(DXPc)f-EG%(=toUND^*8I7Hj;i^qVIBI zUT6ALHT%ZL&)EW4=#*Rwr=XAFo8 z-X4v=A)Q2SRW;frT?50Yj_+cD6qwrtw$$M#O^3U+N428E$C(c2{HHwl0a1g*(U>YPM1|Baj(8bL$6JLk#I26)-dv| z;~Dv{Q>}Z}1s!k`$1&#M`Rcy%Xh$+I6V$B#(@UjgfB8S~OMKMlXvD)W4n{_mPPE@2 ziMeaZGMQf+j}!4j(wI3}4$N@;TB5o4+XJ@R3`yLgjM^{Bp3j+mpox<8iZQy}q{V+~ zesUHxCA;^m27DM#kyqsyRSB12HX4&^^SU*vSB@kCfN*%THaM$IV`0i?=9)?G{y5`d zAzkL#qNE?~KtH0BulaNOk&&TDD$7A1G_s-PR^tON??I`T9gvU}FvgeeP0~!z#@KrE zbfe>Iya-}T5}76|FWg^UVr6v;S=<}0q(oM##7BkMNpd)epnZXa1;O2Nsq%ZS>#PQh ztV)gwPiBAiyOhS%UDFXDHhoWzCgux~x>x@A8p0_?X76vJxj^h~w4e!zTh}Qy8oyIN znXY(;Dre6jYtuuSOuVwU%MET>5Hd$9%KchR`hx6}SU=~I2*59XSl5g->&1%&D8fjF z2!i8lIO@dshNopC%hqO3jab(`t!~sG_v7)lC;5;LWjyR>Q!iv&8c|L3{%n$=Uw7%Z zjQMZ&N{yMO6AmiMA=KuozSmYAXnxhOT?O4hdJI?QI*}D6P*k*ISd^{Z?PB6HV%Z|4 zy-{~O?Wu>8By(r9_YX77;jJn-z}&RzXyU}{DG^PaHj|&4y!Rt{FVuu~)gN0k z_?2jAXCyH8g-B?9G%#vg)FTTRZRW&|FC6W%1f!H*9bVeIZpJRqjCyO%iOm(OYPqQF z45_xY>|RwfDhsw9BuT-4zIR(EicHF&`Hl+hZnW;M6t<>f(mBC|AZ3oV9Y+XKFX)}m zR~PBAhg(DE%{KYHA!c&KI79@IV0JA0`VD26SZ9@;;pY94g&Jc2jw`cgQrsgB2F6MB z=Gp|?=J6WIu*S~-moGh+?#nx&Lk=2>OjK~NHLvA1!=%5~E9}U=cwhnLiC7T8B=dx+OAF!n zI_=aFGt8Wun1pkx?H4t0O$v@&N+u>!Ds#N!04mp@6J? zzrIUO{*{YqfMEP8sK`-8_>W=_5;v^n04nz^O0PDj3E_TbT;VTh?e0WWK^8Y)8#~3S zu~+4Rf4_N=dEbF$e9c)swj7u_zBX1)yUFQSrm7axy03aJVX~<%eKO=HC&oz2cdv-Q zu_2?cwb*T>VZk8#5rSxnO6U1jkG)(~rJMM}*(m}^9^mZM10vsKVb}TRa@L^tx19LI zDdEpn<#C#l?V(}mMkOn#8k@FXiy{MB-EPsr_(fs+x`;GdVugUak8YBf93MM0b2Lgz zReVG4L$&6NaFHzh;=yx$iQW&yCteOj6{c5mrCmLCxL8AA(b%ExffM)ucEUPp7g$uK zgN9dXCI>D5*>2LomlGcwb`le(1QvaBC=Q|PoURi~0Dh{$3h`!I6mMqU#Mg6IoCi9D&Bn2=O5cr+Lmd2pHdDor-qzFSrewwM(R7=$Xocl7{M`(TJ_&2FmLx0=x z1#v%n5iCUQ~0d@{7gH2 zPtDFM%!gRt89 ziX@r@T4Zo5S_`J!W$C$mHT+f$F&UvK_&GE<^mSlQR&E_|ybqj$=Ljb@VQI(rH21sB zB+{gz1`QJ?>ZHF132rq^MWUoh)yi~@(Ub-q)OHc8+kBEOHbra$T_DTvSd2&F8v)jU zm+af$gBv=#W-XFD(V_&|MVy+vjT4f+d)C-1$D*a{(s!w`gumtQK%6MNK|j_zqK>au z=+zATngXHFKc+w2PRznRyAK~2aL4@-D^qVnF5d+AQpdaN*G-KOrN)!&mIOnRiMI5TIsi6^U8>#Ig^}UlnqUCkp1)ijlf1lqXWUe!%da*UpDgW*I=Tla+Y3@XS(P3SSx>HU17K6R46XOqyq)8 zNqI!rH)=YfEq}ZWN0oM6*hIOA{mK@PwqADZr7Mf1%mZ zoAXPdP)C^O^df&?(S>y;2<~K#7|rPqhe_1ehMp9c4$SGvD>2SEd2AFE!yXm$3_s~B zE`=R;Sd;6DUn1+mjb*_V+q8!R62rbAn+aQx4aJ-6+s{efX&LfIg#Z2`?73$Gjf(DJFw;cWII(Qr9=t+JIVm#WqIV@c*HXX0)?WzAG( z=x-?%Y9wo|lB_wLI6sJI3af4_D51=aZbOxZTnjD)+uWWCa)J}W8b(Abu~VGHY`d!d z{N|gFbt;?qUSFfUbu3<6?b_Mo$l$iC#u6v}gs7AhNHCmc{yN%k^~PDf1eW z-aY_uF`9WL zL+xt_1}ZS&TaTeiH_}@muyQl_Inc`_&lm_d^DFseFFEL~UwWGKXLMLXv7+3bqz81M z)TWQXR*$w2 z8)Zl1INWH=Mz1pw2l&KfA5C#gw(e&d_N0GmIY>GU5Z$XaY9OSYiS@M;}YQ^j!Jf`p`c93wU+!WoX@}ERgr< z{IPmx>tA|qr{Q#ecxT6k;cv`DEbDcu-Qq0g%O4A<0vcrtZ)b5X(RSajdwJG7EQsRT;}^_x;pXwija#B z(#g@p*aFPEjXlD{rx!I8@i_g+#y3-Xo3=I6-AI{ifEsq1j%Jyek;(-{ZcR6ZA~Z9_ce_5 zoMZQPHtT(!c{!BI7sRhV0nC$ZaC^c$%@+r9Qw-d;$4$B>jI<^zA|P!JR*% z6KNP-M2! zRhB$I2Av)sJ&bW$8&5UXiOktn->ffp5;H5@zQ4y{#3ZS$@h(PIZf-~}ip1;Tp_Jkw z7>Unh!xPPfc?@JTgHHUyawks2Vv0^;TkkYBId#q9AHhegaud*CDqEnTGa0yOY@2&c zX};M#@Enr@3opDPl9*PZJ_5_nrL#3Nr&l;fqCb7MJWt0v@TYMXaI z{>`5Z%-8cHn18BQ?tC{tX7gAuU#nEXyjesvjad(!YeUuk72H<7FPQHE`k(ISJoT{j zYwE98{R_{b{-pmT8g=LI=2-*_P%n|FO)JSPKz*K9Q|Me3+8I`NEE#u?Ri6K6kuhf> zkSUq|B3`5aZa3T4e2c`tZg2D!y+Q$yW>9-V2l>BQQhgSHQDDOO-*NIIR#^`Id=2RS zvX3X0m~{iAW2lVaWwTd{7!8R*b0>$+^>J}r-Zw2HtvH-CISid?xXZzBC%SM)#5)o~ zn%fdPZAV)E|0&Ss^do~iXeunUIijgaqA3i2pg12|bjOY`==MFs^T4!4chDriqY-Ek z`;e4hHM$7;Gg`)YkY;Kd4+&~RC_cVCR{6Rnu*G>;H>h7&;r@_adW`{Qo-DpmK@LGP z)qy^PX1Bz602r%)5xcp_f!OE1EO=l}aX$R?@waru+QHLjbvqI*{{GsIKk5@x2Y)EQ zjQ_w_>|_F9#}H15Gk6`CY-yql_UT^6x859#B0_tA`M}|F%ylH8EsZ^zW)>AR1bCd@ zr}0g_OxwmC(?V?~z)m5D)){Y8_G29gd zYv@&!s!d9n_JSlRyXAe5pr29GXINPXG@^-V7>e6vUPlz9kIJc(htaPq;Tsjc-mbZhX!3{uM2$*i=ZRL(EhUl>i>hsSwwpLYy7>51Jz?1nbkB z{;-qbwEE*q-p!w${`euX<_jN|(Ln4;<>6|z|Exts#LSo@ht(nvJMZmkreT7af>%7v z6nyk-)lqoclZI7)lOM6_oAsI~tTR6s(ODy2&R44DcDYKf*W9+~^pJ|X641MMYGQh7 zr#r{IWR>@`v&wtFrkjfj*zF2UFKOt9z6O1FwQAcYrC6ftthRlgZe})hpu73ao#<{s zPE>WGyX=ddf7qC_^jEH`tg=%36#ZFn7X9JMf+u+XpSO9|D4XtvNX9<$4PnYMtGvUk zbu6Pn^?3RdOPnQNl^u)4WY1^*Ddu%5J-+iivhb(d;Xio2l^J8C4Z~OL`{QU|o4*Tx zHCnsAQwA{&+Ws^_x-pjfo)7TnIU~SNs_3KvsaH*vzagWK(48=K+y7ti0ske!FNxZM zxo*WD5EuOt$7w~6E|099`w)%kpX~0`BV%o#CcY;czvUsmrX6}o0CIpHO=>#CE6Ysi zsscx>$8O>HjLu^-i0c2e%RSyvV#cKhCET}%39Ldt&xzsye=&Ph?tN;sZ?*s5{MA48 zqyXjsQ-Nu@PtU-RCd8~I-OUeC-sl=`h~_f)WeMh>UyJ{hbl8IdsfVf=tGq;_X>uws zXEtf>-}s~T@f_U*U|Kow2U#%Dj|FJbe>tyiz&nCt$!{Bw@& zHCFlNAEgw)>@F;E8y;fFJk=NgDlj(~4v3)KLf8YCGbDJ%9;^Mz)gxB<3MB1om}Uf_ zwAI5%N%UQ#YaF6ttgeuXA@~rCv2>G$2V&6q!iJNvOe5bnH})dNS=Y%aiM7qsiyC+6b=qrO%Y*1dq^~t6l9(YC zAX$o``^g~_J@p@FdUk{*Scz-?dr<#``evm9rewxJ=f{>c)XHKXYFS3=biiqmb~}B& zCm%yC8}!tVr@mTzw12hhm*s(l92{<2m-TOq?Jo$-TP2c+ z*7GOjzP7S02Kq~W1N0A^Zn)CAUmLD;7eC@kOZA#4e9-*lxKh%jCjEmbXxFDpv8$x( zRDXg1S-OQFF$1Ji}H$@j5U-uZh9~ojdTp*MZAP zHTw4fSV{UJQ*jvbTkr0kBg{_@`8K*-0jq{sXZ4FqNU->Uznf{{wxguvM*9?@lSa z)ATA{xJ0js!Ug6hSJkabRmnf{;9f#qOdlLq__9!2X+>uGuzp;6VyF2drKiN5d_%`c zq#0R~-sGJ~>NNem{f`ap^Jh4Oc%MXZvUVgKuA4NszT!X0VQ;Wd*`qDszp6)$Qy9RA^d2+bpUMffc$qKpF7Mb zFK9^uiue=xuYCzfi!=w!byglo+=fB>3(V9mY(~Ddx6kPV+W(|;`+V%s{w#vDZ({t< z_1b^cYd=?N8eR^hAIoLi*?M9p8W{Ta#aY{keLC%varP)!a(eC)tT%~uyWho=7c^#J?NW-_x_IFvayJEvyxuRZ;D)&EfV)n;?d+Esr*0n@_UBt_Vq=nvU&Wax{Q^zU88Fi zV0DVYKZMZx0G7Muc<7mPF&b@m@B@0;U|#S~;mv;UlY8pU6H^{}$o?6zgCvOx(tQrM zQnd0<2=?TEfq{GuV$JgRndOH5?&gEW|0t%%RP(&4aV%WrDm^C(f25yy;g9XxH|<+3 zKX?Qri^uAHUQbi=DLlJ##R+0wd>+g?sBDA}_=gwwq1+bl!A~mlu=_wG%b|zYoHI44$}uu|A6j2{0Bks*f744$2CzLb#i}kVBSVONhOiHr_;>hW(91~r_o3S z*%o#$=DN#Byq7h=8vVt147DiH@uyg@O0=DE1=UC^(T<4Y$?r0KE37^dJHC=|wU|Nn zSmpQBLdqH+hi4%1REb*3nEmAjT%oe|$0zmO@h_&7saNba>~_7yuA3^Hyj#l2p#4X@ zRi7TB%rP68ikaii+(r9I|7)v2SdebIyLSs9IJVsv*t|ohDV+XmozgY#hv>p?{B8a` zLNO!1N7(Y8{E_m5RQ|V=XCQ+f{Vr9JOH|}KoAXybHu+B>r$^t%W#@{~zl{74mV<6- z{UJg`p1Hxa(?O-SobV4Ap#Ed8dRVRjzqD6Uk`fQ8q86K{k#M4c* z37u-c*Io@y)I|hD`~4%Fk-`C)X=3iC|6&^AYl#YJ$~X~jt=9?Ka4H(QvxTqq+{a=b z45_X0Az`SgW3M(!AWS0e<21aEj62i)A~-oB zn+|DN0N75Znv9>%Xzt>f3tQji-jjUfa9Ar69~d{|zy0fKY8I+(+iI^#Qv-D=9g}V# z_AnTA;$N^=U8ue4AVp)(n{UuD8t3OTIOf|3$7FP!#Qj1s6jUT0@U3sd1Eg%Yn|@|_ z0DWR4nwE%=nIC6pylb15ROq##H(RMiKKEA-f6Wnw)MbFF6E%|746olF@ZIqp*x$ScEyfm*2mAaz^sDDb&5hvSKo} zrkRcQtV?7mFmPKV33*#}zP-idS%I58F^OtIYXZ$-22&|bq!3@AO>TozVN2*|(L`Nc zH2z50LnNgq=PTbP<~qT#YwI|lC1MK1j^KN^<(aZ=WXfIYk&XX6E)m@@gJpN??!fF_ zO>F_@NPZvpGkMj6xZ&ADVjb*18v}C*kW2D2-f_14EjmxUjM_1aPt{45CY~{a2X2|E zjLA_1xLFanMW^heiHUWQ!5bp+*EqHmA&9F^wGf}uAwH#3x_n$Rzo)v;wvLGL%?9Ez z+*R0f$fk6EN{hd-5^ipWS#VW6K`J-9iF-9f22|T{4loa>^d;nGl{v-82O?hv=AL7| zOagVAg)L5>vb=5vXsXR(t(Wq^GRt!q$^>S=GN>-?QZ9Vpykb4RHZ#FfkFy z_i|3?ni)Ka+r5^ZeJ&gQy*bXhKC(Go6JHtG>_(HPUBl;^#H_zEVPLa-*lP0eK@2dQ z^5ku#bWNmmZKQ3Py#4D?u1Dp-(Ig6MrCvuXmr^>)APTN%;t>mH^0b)wtOh{#4DaHk z8v2-&gY2K^KW<)l>&580YXwLh>HT^f$ zl)h3^x-lAhD{zY}yk>lALN8o@z3@IYI(U7};1`T@>~;k~b-3m-TDl&O?SWkV<<<`n zy2;m$`{Pz#R2a$0_Iti0W2R zI<<^={ zAHdJ7vq_-SoA~!0%fEGY(+J7g@7qE20%H*C=ONSoTMdJ2HvF6gDEDgj=Kn}r`}uAs z@6D_>##EaUc@~@)W**IZDH8vf03-cibvn9^il{r2N20`WMO^FdLaAeQW&<_!MWA^d z4c5fP^-nN|r0b>D=Z$Pn2<{OQ&P8bcl_ZaqOWsVTdsPS3zW8e+a zY~=dXMtpR^1)sIVL4~RG*YgClj(K(H$#UT{t2kR&70K9os)Rd&4ax$u-!&74PP6W} z8ls@C`fpHM{c))1>|n^jl%!;cF{Bea|H3$HIu;zNqbiKF8z|uo@^O%XuVAk#K{tI>Ryg}Os z3fGYEKTyn4-M*;Ply>`d?u0w}s*dodag;g?=mvh>uUbp8WpTrLW1l~ew6S#LI+^tU_H?N)y@^_}j@aW>#>+Ue`7LMju8GOA z3nnD4-BA=xR_32^u5Wx|*sBu~Wjn0;u(qajJ%`-JacD3SdNFXzxjoJP9Onb)ox)=j zHZEn0({s_myKCYvS{-6PIs{>D%qljrcY8$3MzcJx$=gRW-v$^WhrG&(TU4w_-Yd4P zWi_R(GzWdvgfepdv=Mir$*MNuvdg!|ejuDdl;}V#en47-!a4okTnQx|9Vcpl^5`FF zK{P93o)7_Hd;0n=%D)97g4uXl`)Kp+8CzH4B9!!l7S7rq*YxJ*_dxSabi(i|j!_C2 zGhFBt1`_UxbXwo-7ft5p>;(`A!Bc={jSA+yfpj>+X?>4iXxt-lz)9Y1YG`9^pJkAC zK=B{J?5?EVHTe?JyMAI_MmzX&KoETTfgkX<(ounXajuo8w*W}FE z%yUq&h}JuSCA&rjV)A~p-!PE{CFsfnF`*Az3K`*cC2rphz@7BJcQFfSV^WQ6MU7^q z!~WM6leFdaQlItjoI+9-BzLAw{n>k@`jvg0NyXr2^`n`8Go_Erl|I=^hdVRB-OFtH zCx6e@-qTCB@ul4nHvPO@`Q5Kl>aQ<*`mK8zPXK=9WdM#3B=%?01)U}&_JISh@NF6f zLiY}vb5WF>MdY;YFX#;`Xx)9XbPBfnKQz3OaKo8mMUMfq!eM$jpWP>M1j+Hy6}+JQ zGaa%==TVUZ$!j6AK9sYmSWl*$_xg zo~wNKn|vResq1WhYht1|6KHK@^9~E7s&JJh_63H&&z#av&Cxhx2K+2ix+fbTsI3h$ z#DMP+->cqtrl)ia0pZNqE1gu8RlzBPM7tQ%s>82!tn0goScS!Ch{%Lr;{jZ}H?qF_ zV&%*nW#hILbJ{^@A_^UQJ*-RTRwD=$R;ahhE~zvnbG-SnlYe{FpQ+2o;-#ka@5C3Y zzrohqOUdvL>SpeIbh1~mS=(&St^#YgTt2dSS4}B#Iu2-2k! zgvT?Il`jt#PsZyEZjr{xl!#~N0NG(H@fMw5UfC_SGA-?v36-YO-CAO($LfpIf0Gbx zMt$>pPU)*)MZ{vV2dZB^v}JLzFK@Ak7Fovp7LLLNh&#T=^| z`>!c7XOA7;NMOu9kp1q+`W{t=Bcw7XM&gjqhs9VC8OBIp$y*~M^22&V>T|9Jsk~^+ zv&w4gyFX`!s3(O^wuQu+9e2}ukdX90dVZFJR5A0^MW05fZH;^WCX?Z|ZQe|`^KYQZ zg^uM8vFV?R6NvHH^aGn!3tE*sgh@;TYqoYOzuilpZLpG#~WY**K&y8 zod>Ay@fO~0Ie70dIW4^IBet@8Poy`!0LHENQZ*smUr`mHtU3TFW&`A6FSSWor-<8h zr6Hr=tC<{$HOZ9gFvWL}(XL-vs!-KBomwkV>7HIy$^rkHX#pw!fTqm(y4^w(6@G5h z52#o7zQCWSR8tn;$--Shv+aMhfc6Ke{rO(~TW$SI zE!Vu}2c#D({Rdt;?FzmHq`Upp{t#+H4W48s#JMjHN}t9LvAcBI21HWHf^AdnO|e zK<<6qhszkAo~z?LFKIaAXVZ?%rJd}hdFN9vv+0v_={-qz-$54;kfw2k7`OTm#<4)M z*#LI8VQx<17>?KyKFUWizv(--c0?Pq11MNDxeZKExzJC&pzf^-H zgD%vf7Rke9E(oXUdz$HzbnftG_I(1z;?*iLkP>J06# z*P=pB6+(sdrl8hiCGLBx%y|E#I@|k8L>qLf#C?Hj4`ROFha?lcVT6|ycCXVUd7puG zyRWv{gXX}ftn!I4#>MKgBKKBX_0U|^2SgvH>g#OPV+h?~PC{G$xZO)Ha?clQyH)l7 z+E#MgjpQuR=q&8oc@S1z(s2;d_c5AgwR7knyZ`YM1dNCO;mv?^@TOIASRZZGV4iV8 zPij9bF!%dn19Zs|@+MhZjm$IAh=BTBYUO$F|v_o>emOgwD_gx4fM z<$WRZYmhA*uB4mBF5@j$g0McQrY=)?hj!fPN(v?Y!@t4M2Q>6cp*l-HA6rhi;#iU- z(k%Tf@X{x_8%cMgkdFI(Rbn7acmj*IPBh!Hm{ByUj ze~FOPM=+f2B~`eOBO_cFbh}+Gpo6Q(WH`u?dEy-2j@{MM^@1AG>R!rYM+P!(K_}Y1 z?2s+HPDE_!_nNLb`kgBJO&KJ5(gSiMjwUlc8d|PxUbEdQR@emW*+}J*Qz`RKgdvXi zCx@|9t=($Om$ zgcgRjC1w=vY`iSb83bia%+h|&XbJ5PkkxN}KRdQ$F_)#u9E$2%?%sK|CBiz-o<@d0 zmo)*42+&F2YUY;qh)>AP9`uJCe14^8O_D&+ZjZ#UV@|Sx1Do(s(Y#eDg7y{EpKskSv{5KD zvq81R3H)*0xH!8T@%6?^>E3yj`tOZ;!W!l!cQxwXcC$NyB_VUp1mM|Vy?&SCxstV) zxqsj#N5i^bR+C*j=&J=HoB!=b#wZrV!x=i|sQ*0M5!_F00Fo)vT829h2*k?07S^z7 z?V8u$nYl)@778);M>D^(_Qz4;G-1#F7-U4)OyO{P*7TUS0fXa{eLIkbqgW+!^SJTK zwzl+JlJ{8?;+lF~s(eAK-!A1P*X2L((qXQON=w`a?jr|q6}0a#q_5688+uZu#4r^_ z)}DL12njznt-B|u>W{R{-vPh-6O9D z_nmm3UabCvFv`krtkd`};6$~2fEL5ZI_`6Qj^{uh%+EaQyPhF{OfPNpImromBD9z+ zjXwu-Ruf7@UqJq}ZXkY+!6ybruDU6^X$v`PNkZKj-KpXa=?H;yLW%%J>Re zPcYf#>y%ac;XvX%?uc(thVILuG@@)zCe zmhOwNLB4l;G%p=h0%tW|{zYl#INBKj!d(TC?mjM8u0NtdIE=kAA$Bgt81 zZIMu0V6F+W4gEsP_Zlym*7rDDFzjfE=;#7CKl&qt_cAJkv-!ggntt9UXY+2bb<5Gh zR^R0cRG&3S2?yq1gqxfU?XD8}T0*WVU5O(B_Yp)(-^KphHCoEij^5|O$fKpZBFT&X zhWxvrEn4~-5wY0+cJo3SCt4SVH!u_P7&C`0M%>5?oGk$*A{?h#m&l$7%MP)>`V|k%l9~@=vmZ};2 zqM{{#AYG}PRza)$)pWs1vT{&w!rt`+x$fK3cWO5AKNCq|O7Zgc{0Y^eeMPxZSpFUyi4|>5!WjInlNf;+belLYzZ?0j0trF{c-PVICw%SfRi_&DC7D9Ay_o}=1Zh2Kt$$EPqG+-GPh>A!7gb~b$TFMP<(hIJU$Nqww) zb~c2(R5KfXPpbRgLjH95lgT&h4R1EMeLgvKCK#7nchQ(bri0PVnV;zcSxx#7)z5s; z{FwF~Z*BxNjuojP%K*X`LCYe&nzjnU_l56V&1aWo}1eIF6RV`Jk5hW zy-0E@3$Tq2-%I!6%;{fpolcp5q@4?V zl*QHmHzC1*#9eNR1~e$vpuQRu+eA#=K+tC)(I8%;Xp5p2t$mA-Kor!#hReFHUDR5Y zzJ2Y*zSe5%t(1!w0!jdHT&$v4MceAEYiq>5f<(>#`|DB%B2EWU?vXM;AZv+D|Bk0nMI>>WF<8}@d5mG*4$ zhyV0vFc=?yVPYqVE!M*m>{?ZUq~f7YeE?-PB<@(I6&?OvSqES7I_>C-RiZ#EXh#^x zaC$;N3r0s+CWei+I$l>03H}oA0;@m$2qag>UXf#J)*1B@@^T+Y|Fh!fZewN6LO5eN zkjBcXw(6W$aNnVPB-&mOj25fzM05o@7`B5qtAmfdkL}=1YzL@O&U`SL6F6cc@D$mJ z;ZN{s`qtSoEn-TgA7K!v3pohU@5VrWJ>mPM2qS0Hg!sH|jhwyDLI>0Tmb`mUw49y z2X{^w{}>dD%@F9p9?R7|9gx`TsrLl{_foV~@KY~c;Z6;Z4FQcWvs>iCbg;*D8VP@8 z=Rmy{1oe9w#RY1K!Q1JJF6{yuvUeqO!p2#2Q-#*iz>I?LYoN>2UM*WH2g|-^?c-yp zm%#FL51jyyv0(HtzOd19FwwEqn8GdX!iD@`FS>J$Ea?$eMGUZac6{i%$P@I%ZqrRn zM zqP;f16vsG)I&;}~g$o}s=kcT-%wBY!G<3L z(BfkIKDgMu)f?{6K_sojUK1i2jt>0)*q9?On{K51S0 zk=gnk;nPQMzJ2kLyXTS&b#+;SjT_@Yb$nVRKP2^|ePzAVkKX9Sf2qUsJ0K}L5{@SC z(;#$5J^B%(h_j4{uuCb(ivn){szD1mxz zKK;XIn()6NS<@2*jgmre2`o#q9kchkR8+%it9#DsQ3eCpKBS0&axxUYnd&a0I`|(A z_8GNNtYbr(`^9O>g*9bZN-tTb9_qq1JL_hlz5?zx6Wy8PlC$J{=f!yeBrR}=B8GV7 zAI}4K)AO7dkA>c93J^opTt%iQE(9A;W6UGNqL?w6m~lL`!RI%?O8hKJ}E2T?Zu-#OzXv6 z_X?PugE6N29uzHq!LrUN&9-%3_DsmSNMZtL`n2GNGaS@{v(m~Ob_!aopDi}V533Vr z=2S0it>0`_GdP5`?@Nrag_T~VKY!4lhyVX!f_=wc#I~EBf=QN19f^HL!;wCpMH2NR zav}@=*-()FiLAA;&$zBPvis#o@QLTaQ zL9@@UB+S*87u!WeM+>t4(}GzX*BP|IgUvD<-e*bITdN~4XA4I1R_y7(rd7#4OA$wl z_k43-TD&p~r69>r;3w(4hz{h?q`NQB4W~|=?0!`XHH;$MIi1FCv$vz6KQDrX(S8+G ztv`|IVjF9MPi(GS9i}%7{kDkqjJM4`GrJs2f5)!r(u3?$?+d%s+f8S7gLqMAc;*F> z;FE2g5*0EYtZQmFzk`>VMX&+;5j)7#-YXxYmu2kns#1Y7W3{H=x~hH1yRqayxwa z`9(*=AwDXk;i7c~pI9>?F*+yB6}}t!Bo|jKts4bGlJ0GdILPfBPK<4dBu+#5xG4vh zkMtr4a#Q6BA&}$^*xUN!$uT=D)-~&dOeHnJC)fIeRvoNb%elrDJT-CNxhaS9jcDR2 zyJ$*`Mfuy9I?)oy9=s{m5zA`!fmy`y&d~C?D?)laTlzgMqdSNNmx-*xkVc{wNhIIs zyFP&e#d#%O(2jM?qFFIRt!VDa8~cvlhaS^+_h_~A)wVw~JAdG_NIvP9L``wyLr~y8 zUgEKbK^cCri5Xu&M>%u;1DXT8Hq7CP!k37^XdWHlEI5?MJ|dNuhY~mGP(UYq4+XS{ zO3ZKG>Skv$7oOY|;MCRGxg#hoe5d&EQLR_PYU9&D9+9>R2^vQm&(VbJD~bH6wKQ>v znm8ldglRv?uRZ;EXRkY-Z2c!^>yI{uh=HtTSf*@Xsf#+{*Z%9%sC|%XH*aP-ApQDZ zs6U*oKkkJfwOpMR`Lx%UG&CvGknnHjW*al5*`|Jz?SSA9E_$JJeOfDen5r?HUbQ51 zoKa_lpsE>3R&>{&Zo?f07s}qlax#(cUCQvC{MXO9O^-M!HhE)1#fm+921A6yb`i9W zF&==U%+rI_{i}!q*eTz%SUrvAsc7&KDvNfoIM>p!tvo%I%D-tow-h{8#5+S|JY5P> zqdgV8gi1v(+(^&)iS!ZG@j)NeJnoJHH? z-Qy=wIQz(OF-uWza!d$(3v_W;SR3%v5{fzh)t`ud3=-KpL%FE^Wqv~87v zw8*d5Hx+fQ`FB24$<%6%4s47uYcwxv4+#1yGnaR%$P1oLj2I0pXQu~>b68(9>ACo~ zN}(gMST(}iCcwp5dGo**S=Hse27}4pblSK8*WYSh==C|hvPqr4iU;Kc0a!3OD?fw_ zPzz8Aw<*Y&x7fvbYyJ*o_R`-RnUyhVg_Vx===UGrcpR!;8^VRb+k<>}b?w{f)N+;= z7Wv;&Wefbu#KK%|;KkrZo1Qc}g;5vArECv;_*>JeB~7HETj4;+UsO})cdW$ee8`m^iP zWHO$A<;CLz3}YzP65WVc8+<<6#sRG2vmJIC&$nu>bn$<{V(4nBT1zP8_MRaGEP9PuJarQ`piUM=(@!Vv@gAFd5 z>4Sq`OH*G)z#Si611HLyuUq&@uz9Lf)_77;(5k29|KRd2@jKZg4la;6+4~ZQ!4hXPJ~@D&#^NdA6rb$pR(-tvA`VnS}E9q$^(8++jJGZX#JsT#Sdx_nJ= z@!zQ@x;7V1!db{$1)?^6wW;LAT3J`Pi?mh%3iro<>hlTYBS&w!eNm;NdwT_=_t*mL z@G*5--pLnd3PXV-m~v1J8!VWxljgoZJFXXY)2pMZZ*jJ@oQwOGIzp-izdPP0XQJ>9 zTdm!I^j+o|@2MIa221=VVb5Ln8)2><+2g`m#%qQpJ^o_*vcxyOh6$$Yw%3|fJjw?4 zD8F!>ZVOS`Uq)C@5j9kz9Vku36d|WFixjJ06m`n~i^wvzz#RP$2(p@mSp_`l?HPLL z`RnN8UV7)(Q7W$BCF6y^sACQ@6-_y+AQKnFt;C|zH>x%ET&Sff|a%c?N>b2Sq>*?ic;uZ6mB@2 zp=OMzTLV}pjy_$%sJS?CpUVMCqZM;iN5g>Vs<0;1O?{eH$%YuHpH>}TfExqHt=80^ zBt4=G2Y`A;Y1IZhBOldN_Tsl`eroSn%k(Yg&Yih|lr|B)_&sjsKI*csve~uZ`^bzM z45yX#(Qm@8ztqm$n%GBbWtJDUt>9AdO@OI(t<=QB;hI=3g;XW$x18Vtu0X@fCsJAy zj|tdF{6f^7oBft;lN~Qc5{>8*yKKOiy{AXt!DmXIV*=Ou?sL#CJpukdQM4p1a!0v{ zvDAw{=4x8)`moGaxNswC2o<2YarAh*)PopH+4V*{1-|P5)exS1sm))$dCnz~c+Ayg z2LzWi8*5~`NVk^zt%NRgw5qth9%Vf3b5+W1Y=RI~8l`1_q3rFp6calPLy5IuzcX_q zc>zmDobUZT&u$<8Z#bA({FmbYnFfGR zbH&y#Q4-97-{?B`EI+cULF%0S+Q%G!(ZL0uey7UQyoomCDI(R}-gVi_ix(+W7cOF? z$Y;ZDYsR^tZQ+`m1}JIKHB?d^gO_xyO>Y)oFII?;BYbj4X^hx|FJd9<6vxil)HBlYw_Zl`@z65_Hrimdm(;*0Yi~0Q- za(t#eb01IAH)5Zfdw5I=gD4;wABawUk^7VPPY+gAB)&@fe#s&$hOGR`*w+dyYh96L z%Dv`WCcCPW!7}^SdAr}quw;V#ykG3a?p8LeG(*h~SPhI1UeIxQb$s;EXa>yayb<(~ zJ3yEi_SZy!IK9}1z|rS*o%A^b>a(6jUak8;TjqBJ%j^{FqVLp{PF*KU zG+iFt_-WV6=Pz7M@Y@Tme0+e9>%*nkLmNYgl-~#tnwrWe7I>{W5J!jC$|8mDa7Zfw z20vsoVH6Xz2vKP%unf$9xVFeagM4&CS|3Hjn2Btip7_8JFP1mLe4ludJt&YioP>!_ z3VG#gZ#-x=)uYe1=C#kB#eL9sm-@5zf_kNK%1XXb=zlu$LU$bB6zbrZw* z%!X_g8HL*nSwR(uW1WE)RFPPSW3K80_k(0)+qSXb!TkMjbcg~*E<*xt{7+*7`r}_$ z!)+eP%8vPxqk3K+9+gu)ZSYSnP%v7%LsMP8&O)Ch(0LX|c+&+JYsXZig~Db13+-u2)LOhO3$Wkd zrH$yXv_ac2=o&XVkdOE~oca{jC+F7SoB~OeTC!wE8Pa2gDBT#6E5(4MCcbc$m@D;$ zK$%YG>_cr_rxF8FA!72R4t%)+K}1xFW9Kx=XM3Z!!97&(XL=!?yg|}TrX!rLE7t5iq&4j5t(A$ zvj%_voXvjdF3x+L`sOk83LH{X4mIH_%DtrmYeh9+wQsYljWW%>FOhLp(whOpy7DH~ z$Sls-M67hT?nfEI^nZoF+2efNan*%{#BCcie8~Mn*)+tn?j_>pjM#G&_k-wr)DLss z@;j8VREfEyqAuVmWLsDzVMTu@KhWMy_6 zStP@2<=^KTjBQS;P(3wKCO6ouF~3sf@hTAStcjnYd8HV!?ir+#u6<%`Ko0qYd>`Dvvsh5#QF5p%#T3K!!v1xkS#gLO ztmTwA$`CcquvjN+*oUJF8UqX|vOEEm6xFOeTQ+1Ld6pkkt1R43#rk!ioz zK<9pfiMRs<;A8~;Vqp5V{yoB6ZUx?#b=q+rD_;z=&JO)*BRQ2K*}5S3PzJoDCKn zci2Fd#(Q};FaDl9bPf{X7mofK{aN|lp#gITJ9a8&Q&3kN^h8>SO~75$X-C9*jReo$ z7_GQ6;?wO(*jG>SUBbC?FviinpSqUIw-c$2V_;?s`Wf2FW?H;t-D)X>X8Nfak=ldg ztcBf56YBtM^}E#v@1F|o`pJqFYlTT6ruN5YL_fyS&wqB(kL$nFrou~}u<=9`3AmpY z+iZSoAFM{B{;);VPhIpj5O*){D_D#K%(y250j&VOh{)c-{)P-ygKkfV!~pllCVmoP1DxJD3y073-9PW%~$YY?L-Jz9rYmA1gJvzes|aty41q87}k4 zi5M>jBpo6W!%q7Ez2zr=V%mWj)HAPi{d`%(c()f=e!c&FXm+KKyU>2l1Z@u9iF@XA!8=>ZwluIU?(ep* z+klifjn>TdPj?d#=2+V86btE}?MJA0`Q*m0%l=TPi&u4GbYDC`xU0QwCia(p1Ca}- z)*vSH`C>O$VNn*KS%eMgcPhyPNuG0>UWm*g{!m@MY4!k&qq7+*c;TKj*G9JK(#W=1 zeeC+IeAk>_HmTE3;lH0Fgijy(Ci~#Br@D2otNa_#wWD3+UkQp)ri<#|>{&j3t@;2x z%1GK8F0g~u8V-ecSRn-sINUYRy+3lgK6!Ja6^B$oehnOLVoK>2+p)#wK5k&N?^Y$i z%BStx*0DoumC+t2GVbc%70}dGUBJuw2irxxoclM(4Ubip)asERCE4#SxN!=j@a@PJ zqkX1Dx6?)q;TkchofRLH8(FP315JG7%x7Xm5djx+3u?+=p4pEG>qJ!FIp-j~Js3$` zn?pdB^e-ccN_iEA2>O(mL8$UCZaC2^Uq6f6c-wHc(;3}^J0rR4ng#p^I9op(`MAcX zKlZyFA&RrKnQt*6?QYN-b3Bh-9f2Kv5o0UEALyc0;W!#*A2_YIONwnh@7K6RBKDHjRP!>c9L#mi=OdQY=5)tOQG`p)I^?Op z{)Wry07CpMFvFff`KQ4}EVHl(aJ1mPyhwar8AcQMWOaEfx6-wyiJ(M&`w3lzV%713 zgoh)7QM3%W8RsP4;UM#%{`QCfrzR^|7NyC?Y8M+X3#4H4-x(s>M^gGu+lqa;jN*bJ zTgB!53oJa>D*<3?VwaaQDrXgB0{qZ!aPb97jencK@uA4bmYUe}k=?J!XIs(I6;F_0 zrX!!ldoGCOBe-A~xoZ+N9gz<5y&+H|EuWG1WzMG`Q&avzP4H|YK&-1TinRPKkMlpK z4t=Jx%eEKxHa;P&VSE`m2I-EX{p4J~joIDig;G!QX zKU7l_yEvzNq0|wsK>e{+e^)ipi?g}$=k3}8@}bT0ohmKHxgaoc9TReI@D?#-TRym8 zkP64R^chSjZSfvTFk;o@kBnSflemmvO>eNjXVDND{xT<{IiEe;x9^tk2`-j*x~h)I zIqw;rQjc)|K+W)XCMJgVWqjBfdI{IOU~`2sw!EFEh3}ER4>5pcsKuf#HN--Et{i4` znu|ZR4!+X{@XP$>AkI#1LpiJ1G1HZ4$4sGK^v{_g%c(oZ|D)jIgH@wc7iM*EvE{^& zTCI~0+v|_g(Kz}i(LU@&J9y<pIa!{(Jf86L#zD*aaF4*oCD(v#iOc?^=|Jzt`7K_gwm1mwwUWO#1ut zQA(b5wZ3>OwT@&o%*)I772_7ZxqUUD;-}eFY^t^MD7lj#S~6y3c$`C#<+DOJoFkq$BRi9P|Ar5pj4S^~6Xh|oD1Ld1|>zG>!YZo8y;u78Y8hS~`g z#$Pq^q8F@wk%>{?FEc3$<;-m5yIG67O0rWDm`D3`?MP~z69f+a8zAtke(V_e#Uo}k zQ`MScvl3pN@H&`BLq+GQK6YLug%k+9E~I;9%%C;ugZ;v!gZ76CU!};e=bY;ad6KKG zi=xehAEj0-PNhM;`x;Klw7KF2Tr`pgO`XEIIh7g)Rn~^Qg-mucv0C-Qz9KyXX=>m{hrfPPIG`0 z1L7fxJfLj~z%ltOhr7iEZgO=Qr@jT<#_3<$pV5AOhW^wd+h}&Eijzt7+u=Gj-17cN z67Yd4e^t6JJMAB*kG6YuL;SrvepPlNg>>A$B}FCM$yUCl;kSwF30eJKab;|Kb)Qy} zS^BqOBYR|SvF?%8^^DKNHbEM$pQ?uCe4^irdODO7yI@S0&byAElcF?WnK=~*a9EN;4Y(T-L5Y0sX3|j`7f(bO<^gN@)6&~VWa`!Ztqh$8?&yDLC;rs??)VEX#sn4+-Rs~u_?)0Dwd&s{+dr1%_1|Q^ zVsSo?9|v-MxyBUxd=HSLKH_GJk~cy@#qK8r_{A)H6Zr%0hcS(!Su^HQ3@eHJ zg%9yzv_2fG56;$zInpevxBPFxeegFMO1AF}e1I~2``wu9ZTdf-^4Bcp{dq2ZNNkPx za}nw0&e{BV#MSh3MQS~YmK^_F{YZu}ni=^Sgs#sbRQQ{HQ-=1M18e}{1HwW9WE^Yh zZttPYQ#;_NqJ#T%Q|_)G4(tBm`WI|O6AHjtWVnP7ne}`Bz^tF&uHj#$KOE3`eMJBf zzNNhH{2K;L8q~|m1FL;y0NbEJijpJ7yuCzeP`e5NkRIl=un+EZ81CFDWMW z`tp~{r@AhvG7DS(H-q(_J2OeUmSM8r^WX@?%j6uwwFPDtak(}9Drwezhd_(an1bW2 zz%7Xz1Cvl z_}6_*`29l@p!z{(z#RIRVDQZSF5{U}In+_}$KQQvA3OVZRw;J*4P=+-8IrX7s`PKZ z`q#0BK%{|Lq4c0^&&)e3=$IuudOXN%n_;1|(XH(`jm-ddohIi$LB0t(z?QXpC%~+d z)$v08dO3J_5oT6?v^llKUe1h?>Kw0>`yLMRa=bF{xM60(I1Ji_e?O7T!Qa2;^&0l3 zzQpQk0igYC9^)K|$-!CC3-id=x?-FCk5#fO`-F%tYN8+dYqu ze>_{xD2=YjgO3Mp9L5%h1_E3smfSx`#g^QqpQ!v)V5aZAaTMS3IA8|;I(qf+C$PCP zWDNfx{=_W!kKa<6YJJ$&8h5phByeP^mrk{iE4bKRX2y_uQgBMJyQyjYYpR%Vf($jB zry8{*eFnZrNHvzqSXno#K2!U0CWeXvHu|bZ2@+@Ct6sipG4^%=yUT%-mZe{<4in#( zZV`Xn)}q6KU}`3Z5LGv9qZppcF(3A#U%I206sd-TyVQ=>0tDnrKZwxwnmLrUfphx3 z&Im5=*E{<50WQc_<+gW{m?*|KZ>s08T^6)#;nX$ovt(OU0aZ^#J3VFI+NuC`t#Ccp zg1BD+;WH0QCX?CC3CVw5RTT(xdSeecC&Wj;C;&JLmI^7>Aq)vP?l>sGz=GsL!DAUm z<+i_YgpA`%%at^~d0RssjUOIdKI^jZC+l-KL+yZkc&Y?OuUXT*n0vIkUKVWDw~xub zgZ5DD)WGr=@dX1Nj9&A{c9c1>f*NFt#6C_nE4!0Qb58aWNt-3#Q!BC8Y}`#e7tNIw zs#IulwAS zvk)q6lzlt550=mMvCW(yIBSSILhyEGmZ2#s&O;EV^BE}BTk8kLcDMW^q(G8EBtW42 z#rlGEvE4EW!sFh{2?uXmA1?d2zC89tbayUpzUYaS!ABa0_b> z%UAzy>B-9bpO0Cd3l}iI{H&Mu2W|ax{kaXN$=Lp+vyNl>fcB($^>_Zlr3BTG+bvs! zl8zkK0A9#k4@F1~B&Rt@_48ER%*yrWl(H`rVv%7D5`E!vI7gVjNoS2(Te+IBwmK66 zBwLRNFqFDfDnf?8vHgQ^({}aD`hxkNrg&^9XzrCv-N)0YtxUU<1`yt-l0= z8`@))JMr`aM&Mj-DcP7t?b}#?U`O?cJQ)ht%TU;snngeOrP9gRPxt0aM?vnVf=)RM|0zYJH0%z-OUNmW{(^ak+?u`F=ti|qR3akUEg-Qk_vs?MU7&)GE zF)e+&(^O|^=Rc73A1U?{6Lp;Cd!S3~lW{@G5+6t9vctq4*ZX_+6&O<5JIB!@TxjPw zrhnSCy`ImO{*LxX8OG_(7g$#Wr=Jwv1}s)hFtwjTvM+Y8LFKtXvvd6--6>I99BrSY zV_3B%QpnrDpw&m%nry2MK9UQ*Tb%uEeDlWO!WZZ-^S5#(0xX~mePvMf$9HSPK+e&z z3!YQ|2RYL|oLWV$=8X+?a%eegNTp61ajA=#P5Sg``^<(yeaf9V02q?#UV>zxzVsrK z8b}{TCVijTaFD*|@LifGtAI6D|N17_oWLN8h9O|p7% zZUlFlIYaoH!}x=$l1rRZn|q|52aZQyDpJvZ{EDK9{B^l}kW@xL-oYp40;7wl3~^=V zAhU!Kw&SO1s4clcsUK$-Jl)an`=8{|FMkY6RaaOO+M}{s?O9G@NbFHpwAiG%`2oik z!mx#~c13HOw>GeOv*cj_k|gpl>@>7W5~k!hg!5_6sSP}E+W4kZ`sV#Y~YcAcCN5y4y6Q%h6hsbVRZ&Ly;| zTH?c2eOIRH^hMfasNz_dtJ>;MSuV#)>Hr*V9j7C|LYxm1)4#;_Jeg77JS3b zOl|aV5Cv{PjUEoGaGR6FL`J%k0HbzjVqa8}u`;}pWiO@+a|3LFN&K8;3bB_=%qbIg-HTPFu>3vwU;J1iN@Q%CcRb6w;jAJ+ zW+yk2mP38`=cJoO%3slb%f$V(KdQs@>CyhmcWHmnm$rZE|8M)1J=!O(ITVR~SOjJ{ zq7I4Bf3<(d9bel2e|}y6gNur&*dK#pA+c2Q`|V%5iCSvo%{3ggLVERTYl=t|K4`<| zL0@y_-ze=6Dj91fy%iU6Y#E*;eY-#1Wkp9!j1Tt5wYj;Vjh^Tqeg}-$Y5YGEa|F`I z2A6133P*oh-e%*?o@*pW;wG-=%PXi!*q!`nMKMz-P=6?!i3Kyb^{GIG%!6z?<9kpO zn_PfP!0BpA-}NsUt@!)V6bUwS^Cd7pQLvM*IOkYk%uGr~Vq0$M`WtClN&nuv#=9?Q&$|t3!AFh%){7XW)i`_soGV>Clr&8m6S{Gv&SA+s#9& zqC{1UbNQk{^4XXN=VYtcFx7%ERu|0;R~6sNRAGLg^5!!B^m64>N0Z6QwgHq+n&&_2 z6-a%r`}oYh)~D?{i$rS7B2h@qFAg+$(>7S}QT*U}+o3%(cA|W}B+I4^JND-wH-psL zPNa&Fu;XemyKA0e8_D=m*G(Hb6^J_dPwM;7Qx_R(`OicjzS@1@qwU|_>jpN{yyu#i zCgHc-m~Gj5_={GbZSx!b*ZfN!qB0+b5=2~LaN!Z-12$$BYoe3Tg^UkeDB=?cL)dg=Bjq`S=xIKPA8kW|wDzNFEG4g|( zL2FFZ<2_szPaCyn$%vBV6Pu z{Z_JXcuk|`?+)!=>l0JAJukR$3Di&p{*EV{>5WhJs8ReJJ~*P5`t^^;I{#4zD+C$_ zupQ~#jutk`5AAl>A;RouG!q=DeHM6+^fAz|r7!f8ZxNtGe5SfJoW&n`Q z!ZIk>tl&QC!BjHpAD7hLLOrp(5njT5wUQtD3>A=Uj$y6X?Tic|dl8gnL$w+PO{FLS zWtqOmv#^y>XKFXRWB2+)i$S;aK<;(K@iT)V;AfXyQrnV}s4>qzut} zbwt4!TfV^N|85_9&o;|sEQsF;)v$lR@E$T&F&-=^A0N?3?&uR1Wd*@I&;i>x6k_5Z zQWw3_XvrH?xd3;x+0}*yd56U#cT4u{1*pA_?m1{vCs}}8C6v){$6iBO$PgK=SMyE! z>y#-Pm;7_(%;dQIs|Jz(P}Mwxn!RcHe_mD*FwM+vntgCx#wfd8G|K#Uwan~8 zEmlq(OM_0+v-&M(X&ie^^+zx2IsG&CgD^7h2Iw_!1Vmyr0+h^t)+00|wgz|s({@Y` zvVm`f#mm=vE1o~+qGk7S?o<48xo`;obcd(^w_l5*@4b^i-GP> z^X6)eJdpYK)9#pR(_i>4>Gr_a1V7#1zncR8H=kU@pZ*tG7*Hb38Gz~Q@3Fr|IFEJ& zHc=b&y-RNV3%74@zFYfpxt@J>BARpbvBHuEo|v!^2FYZzMGS? zYje7JN5jYI7gnh@^ZJ2UI?W(@WFv9y2fx8ZUHlWK3avA|I5>27-i1Y35~6`5{gPV1 zy!!Lq2oN%&HIrzEMD%C5<5gH*yKzVIpOQl{^0V77df=&6Pc!sXr#iTw^oKc$xiX@& z6~AT<8ZzPnAlpRmo=s5K2uvHI#HrwF+8R|13(kMMbX>qYF8@jWxUZ#V!}+!ko12*O zCs3{VmUIBBC^a?k1i+p3GSs3bef@y6GBe7hU3@udLWAQ^?Wh{&6>1a+(LD!Z{7dA2 zcrSVS*z!AW@5T9a*W&V@(PD*Ke3nu^eN58uo1nk!(icBHKScDYagOe^SL z9+2^nXe^iyQgt+y(##8;k4fae|5qyEL$RxWHK&EUjRtUP(iR}HqOuL364_rf>0OBa zNBgCZ8T8kOA*{Fqxon_J+vUYaf6S?lhFyBw%BCqH1wi6zItVncx%Q_>u49!rm#tIw zoA)X)xY#+h4SwZ|vA{a`Requs4-aASBufqaBh4B*|6x9xG5N53ead>;7T%>&j&(*W zaz~E^m5VeRDDwA{RUvX4QV}s?pWysc&;Ex{$I5J!ea?%cvIU+#l0Q#LIYZ?&1&6TY znCWoc?B8C;t}ty)Or2ec8#YXOagH2cj#_+9pFYzR3eXUC!k@&_ zj(P6@Se6iLZ7rq_il#28US|8NyKU0A_3;4!=+oyAXWpSSLFmyB4`-c?la0B(LUT)k zGTLH2i%L{osr;uFJ)!Q!32`Z9@JF_<*XB6;EitWpcVuwF+^gTf&d=W7(%?_o4myZW z7A2MevG!0c@5q0?Kc5nOI<#RiXD$ft-I||hA1e|<9OU2ZKSnFo4c3@p`OUwYZ|1yQ zjoHaHF}aeTasi(>xgz}|RUzHfIvbtN4D0W0*#pk3ZK@68z<*EkLt#N)GY|8av;^5{ zqcTN*@p+2Ho-i~qOKhl%x;o?jo+D}c)I7&WA~bSdc`9eqAemwo(}3J+lr8#@uiCWDLaBTeJw zxrBKBRU~xB@wX1?fn!ZA=YL7JXJ0I`hCD~a`i;;4hpkYc8J$0zyHD9^2x_%eLM0=F z>LL4NA%(F=Iwbwe>&}CxxLG=pr+tWQxsKXV$W2j4WVsNoZ~rXy`(9uQpUVjtuV;I5 zE{FrW{1&3xBQsY}VOqONen}gyz^b?6?8Wffp-DZtEQlw8iZyF?jT099Qhe!*4k?sXd$5eR)YhY9h~^H_-ATHjsOaV5WWWW9OoOR<=%CQIMT{@g?32E**&WV&hT z`&=IiM;4ZWnxi<^*KJ;FeBqg4sLKvC$k$WI9J)_VpFt@boe7zryxEmJ((z2={ObJQ z8mCmhiX^sx%>Q5o*t4-z{3Cv``2}$Jo0pErw;MKf9E{sQmiYW)J^%z9#-`!!w`0*^ zt@bnN0gzx`Cu~!7TG~(daH6{Eb+?>*%bttt5+o+7C25av zDO+@cV)srLCnorNxtA7uiJ3W_ZP*|z|FCVed_e=dysLJpfyhYRMT67%?W)$}D67Di zJ>Y(Rt3jyxmAU3hlZjXLa~_eL^fzq(`ez!wRYjyS-6&xXqDR6|W*FsDYEyRg$ufYQ zFH5xM*g-w;ZB+v%;rwSbte30<|I&qMTJ=&%=De%ug|Cv8*k5~AL$M*K_@epZ%y`A@ zNPCI-A`E|9dJ)b{eoNU0NCeW&wBw(u2|Am-l=1OXfzs2w!@cVSX}!r$l~y zDeC)ad^No%^8r3u%>18Z({9_0#&6f3@1Mg5iZ3A}{I|WZnX&~+%yWsyJA>-MYUGiL{CVEh z@?TnR+wSaGggJ!<3e3{iof?-a=we9mC;WEmlndj{w;jz$YLhesfmK7bn@Pd1?p#Ez zNU)ZO7_~g>2Gly9^k1N~2!xwJAO!@-D)3<5T2OK_dU1?}AWJL;O<>1@yIn7wBtsjZMGn7o;Dk z^sD{!H7M>)=z)frB89`{j%wO{dB=k`7tj2gIhE8 zzkjxa?_QT)4o)kVz@NR$2zJrDRnDfCaeS$L+aWC>q{A9YrpPWta__;vVc zKf{(E{Q~8k{m0gij^)n}SO54r((U}4;HQ_l^tmqmH*-iofLhJ5e!73YWr|Dx>;0tn zQ+i*Up2hzdm;Sd4GWEZY5lo;8MEY0c(l5B1bZdXwL^|=wEYxq_$z-cit#=G_r8e_| zkY$6^%hyyGwbAmk?R=J6@r--u6rfJOjFark@Q_Ft$(%V~IrlMSuS_GvYFU#mjC*

;9njf_Xu2<;;fvtiS39SDfIL7u1(3T9k8Rij?Z1PjscB1zamp9ym8D zf`*^6d}90-IN9#r%?G?DorWOMFJOk4pS;pl{yX)s%lVkuqdQ@Tr~0jP3@1{m`L|E= zW^wmM%(uGLpZ;wpdb-;WL5p;60Q1y^GNO0qY#AO{Ie@;IN73omSB)46`nBT;uNlH? z2?zGh6-z*qbE%1ZvmU%9(CD=OXrF^hhwxA2Z{f4)cSE=S6aVy2UHfkRIJYA9`QL)?yw8EpTHj}~F8_M`;r^9>|Fd22 zw|aI4e@ppno}b$TzA676e1G^X1K(Dp&VJzQfj_}l&=tO-EPNk51ANE#gl{!lk|L%8 zXPuCr?*C-& z3BC>My1=)iJ_Fw)d^R6k*8{$}TmLQm{b_FozFiy|+z)&`@Q2%Jeo0sO#$@5^FZil^ z!ncFVv;U3pJtYg@Is1pN2mJ}YC2PCjZ`rI2{;uV-d9bku{>J=!@csDH4E`Qv7yIk+ zmj`^Nr3-xR%zK~z@!9n23E#5K{}%rKcTWbs0sj(wge%YYy23Xl3*RWgcg>t0_-lXZ z--7SFEPOKw*ZKAMQ^fxJ*L1<(>T5IjTgqqi{Olg^P5JlWYg~L}VCAkSxg|8?J-$zda-|;=+Tm9nK<1g&XcZ~YTccgB`pBdG`K!yGYSm^ho55(w&_bbPC z&w-T=J6`X&6`IuzAjF(UE9u9&GFJcD+Aqqp`a>dr?K>!12dl$Bw>1~88myW(s^*Al zepK%S_Pk6LqrY%BNP6XdYIdLYsei=^>JO>@N>~4c9`%p)>(6!dFZ=rXgNq)bz6_3J z_o$BT%J@f=FO{O+Fi)yWiPVsgQCMg6Ls|X2_@t6(#q17rNbQ`U^l{Csj{U^1EzwzD z^!pEbVObuixMdMeMn__^7zOh2p*RwoOO>28idFJkM}l3&J7TB z5i@uYxYx;B_oy+yk=sYv47wTyk z{d7-m!x3bh+#};eKjShoLbKmL4^8@5!FM$rNH-jCrZrypA3aJP?U%a3Ru9(5nb+_% zIS2O0d7z(j3^|=bYc@ZZ>FPJKQr`9N$RthS>)+B0DFHe41V6Es+_g5sq4nY2?!_-GV4R@h3q$-e`iwLi zRez0E%=+9X0rp|iAl8yV@s-RkU1!OPmuu<9I3k$rh#=MoV3Ud&VOzOY=pSy%bdbwF zof?xFHJ>UypBzVB*$U*o)kmi<M>N1E7eVSVyZDRVI6Z*tdbDEcz;W1gIdiSZi(s}E56=7sKAPk_ zSSgUyrhcZT<7Z8`>-YL^d+{5mvetiTPqbpHPe)V`<;MnM-x9bT%=lz8@uYst8K=VHaPPqX0R?g>Ct`A_0TLThs z+cSv|z;Nu`?R%KGGrTSD<9|G9dN8rk!aMkn1ICF72Afy%uDE>Bie>GBw}>->_mEf} zKd0D4EW})QKd}He)KGOS|0b1d9a}8;tK*e3ppWT_lO3;|8Y$mC>r7E10kT@Ge=l*# zT5l~1m?Lk~U}I6?X#R|7#YwiI5Uox%t?WZ{jkw#58atJ^8~!5b!s*1nQpRAjt{efU z+xM8eZEh|h8ds1i=BvizZ~e#l>+>l$X;3s)SX^pTSF(yRi#M>_lFL&jzT~gqX#V(5 zhjvSvdqpdLzL$2A_5G^jVPt9;m4+a07#mYm^9b|{UQF%drTi& zqR53uyLj`S%D9br%Enl-GG@cgonEEI-Y6x+}+Hd z16@l5JK4RbI`%s9un%S{_c8}k| z8O7!zDyWfwnniTT+Tzqa5mszVs-9n&51>zWP~e$~{2v9Gvea_X_jl;#W8` z*-;-+(C&u2H~@zb+R(AptgoS!MB&qYhy)6YD`i?0L@Unz#HYHMA@kRF{As1Im^jsN zXW@AwxNH?sSH-;URrKpp#n1PxVgOZ`KB5@gMMo}ouk*8isRH@7fCGfVMb!X=(y+#Xk4=r21{#iT zYMcsx{7hV28hj<4zA#20X>TmMF?c(>W4)cg4A?5|^ZGP6(S&r;=d zxKyEso4d?4!@OM@T=IAbV<%qQhgIBWYLPz|MW>? zMv2+T-m}%1MH;mwO^wqKcMnj1w4yMRt+v$s!e%21p^rJ``;9;_?;~PUC*dB8IfZ1XS? zYwPUz6?Frn&PSjFW+pwm$mTE6V!%cE^}^3!xA< zfc<#01JwDIY9u5>AqH7>Th{nvB0&Vud`l&|k4{FuU*3s-%)5k=5mFltfj%SUXA}n) zo2ex^m!J4?L0QGX;_(B%tNBzH@Sc|_~b^SrF8@! z`y#yLBq5q6HtksrWWq!-x-1HNuYZbLRIwb>{Zd_ln1uemwf+~(no{)7@{0-@?rgd~ zC(wXic@en9Zp<1N95V1ss`kv~rCsWmgT3EUB7eZLqwo)U5?m+pPxuwHC(p_>goAr{rmWU?iXZh4|jHul9AXQh0QbJ~oyG8Vc=m#z_!nI#bgWh%)y> z%p`u>S*U*JaLu^e=0}t?{VG>-V53?D}^+X8lndY(Q9k%>6*nA6ltIKhR=Hw|8#gZ!Z zYY$TPjb$XW@Ovy%g(#_(8AulP6QdRPeCTtg38m)5H+-Qrk!diNn%HEFRy1aFgiT?$ z9APi+wia1~uy=GnO+mb^W6O9@&}a9-)IEVZetXZE>qf%y&(AWbkA6_rH21i?IXXf< zB0hO&{M_45!v~_*QdYN|uIRAmNQ28i(Y+bNv0-RAQ{O*2w;wF@7i6)~oBew^@smN% z<94CGsK!Yy|7n_eL}m2to7{!nmD}E~PE;OHmGfdGx@YK(e@fL-oCQ|!ma#l!^Kb=r zk~4k9;}2akes~MHy^*_{P7l;`Y?VN&2U+@+Lr%xqs#ptc<tC}(f9;<3Lo%o!67F=o%I zHI?j_i()X<_{#iuYQ^VDy^+X)1uAf5QmcgX>&#&CW*OvJbYQwc({{ieNk^fi# zT_`c^jo2{(^S#S@1dhRSEdK7Rt`wZ$9~>nwvf~GJB~lc zDAjUu^b;I_zvBMqY|AueN50I&T#E~Qq5yhrC6Z2KHk0@uyn{IkGb~!sgBjYfF`SCW zym3ZpaQPAi&z5OnNm5Tum=+Y@E|EVnRmJ+HXa-7`g|#PZwC`VwpB=|o{5+Y=;AcW9 z7&uye=)}*RmEh+Qa+(i7jX6N6K7Pgk|5xD0@rV05*YEHyxjrbwTYM5M>cTL#NIqJ9 z3u4wa0DYRi9S8@1yq<&QpGc&5x(MwMD8Vz&-@ysLMzM?2sZ(`@wbs#&b$_^!=sz5f zFsEAFRB{umEqzGxX#@!omhzXsTNNNAN(&5hHo-Y)4s+Ozg!y@O)midNhW8TfATQ`1C!v>q(FE5hmTMHZr7P7R%}&$dUL3079-UWWpwTTBwu zwsqzik@CiZnc+yRu`Ggq7ikUG31Sfgmbx`y`7LI)7*#MT9q869Ov*MDS|362Q2qF7x9U{ zX8oSgjO?nxx+Jn-2uD|bK$ZAI#OCXP?w_k4DrY$D%=fv0= zCLKCb*NN`t*HY4aNFZx1S2bD+!)Yl?TP8G7+Eg*|FAz)pQ2(Hg9Y1Ue-IOuYM-vMu znVg!Bq7tGGVCI${a$s@=Zi4|c;;x*4UyEt%Sp{GsQ~mLwGbx>C?+SJ2vk!Gxrs7(s zUM2?rkGcxLwjWqRd=-+VL7U$*WUZb`)j!-cGMeR!kM`(H(>xT`EYLsv2^=Dk-*(GU zR4?)k9pf;L zW#!1^N^vS&k^8*^ry_ce*{w5~8WC*}sMm?X&A&U`&TngWVU0s%Ote@V$dMd0nHf4= zTKq>b8#IaVl3a#{qj+W^y|&}?b?28||LUl~A1QQc)@NR{V&tD8rDQ{qxqCRZC-PsK z$TIr`(MAFLEaorz3he;bq|%NBMnlR|)JBozKy&K?8Or&O&ymvjQo@LF#bL1dF1{$% z%@vA7bb9N2eTVe8Uu(E+rsYpe*dhAxb3_sLMLt7_dHV7p2HtM8OoH`^d!=WB)%%8A zvd-0S`O4N}TYu>vG904P)?Y;Z-E#v_=Ve9L>~v#fCGt&gSK%La`R5(ciob{}-_$2s z@roX9?7Kpx%t73wXs)W}Isv^h7Jy*xeuNov$Ntr8>;Ds6G#w21_@5znwf#l`{{JcD<3%PKnXyAP6sQ8XKsISGN(nuf7fI+$Z(rGH};P zYb`T4J7`o-p9(q(sL-5L!Ut#z>4WE^If_^Dk9P86Q*AkZz9d250749UtLwcPmeP@PB`| z0wryBX;plY*gR*k>kBIFo?W^CcmJHq;B76z<;xGFH?lVjjJ?wGkK9UjEGumwrpn;B zHnC-{D8Kbv%{|sYw3)uh ztR3e3FTsR4SdI9ZP2?JTgUVOb-Fog<)!kIF{tf0t!I)`_TT^GRf4GF3!sPkq`)B zOyf(M7+#`d?JG^BG9)|fWscpqQ0tG4FV&RL{cy3n^#oVk->wYzY{y-CdrrgA8Bffi z85Lxp7hS1o7HGgrQ_M6_e{^Cpb=umKQ3~znB4#R0Cgb!+q753}ohHzm3AC#u7ge8ntF>-w$QD zPfotROlSO>Y6S=r;9}W-6rwy@#&SUnu?MMm@ueF;fea{!`+GL@he9fDkx;Hb%!@Oh zSLu@(!=Al05X{1sJ2<hG1Y;ivInJB<5gfrT{bNfm2zeJiAyz?r^mxqr|zaf-pJr zLs)Th|3)>*l4BN1PA>M;0=30&DBo5wV+it5ETBGE9Vb4s&F-IV1a9nR`G5O^tPzDj z3+bm~&H|?UZ8?h0D%>P+mp8wZmMRfp?V@IMp48J|vjx%t!lBg(A&hkecB?y=YdB|} zDy`DXY^?_V$=0g;Hw3xI9uTXki4HC=-q*an!S9BwX}MnPBDRQ*SF-DISQ}T>>~F6m zpVhAHrhqbQ|K6^GdJ@&|ezPx8?d@U>*_TWlef%7}WhhN`4&nU0(Z?^Ri7H(-uVHTB z7DHPCtp8w#uwKNDgOa>>8TJF)OAJUE`mmJbuQ%eBF(qmzhMLWK=_Ot|K)8WqFtR`km!?c0kB16 zE#Q{h1KA3Jy?!+Fg*0SQM-St#tA~UzlnpLU?9ifCpZ*z`ME(mCh*h@a7Pn7D)8t~i zwNZ!m8toI2YI=|>6UGN>gZ6p9&wNVSarO0w|H<^Xdtgj|C|&>G?Arf0Y>Z2Lmh&yR ze9|G&caCVGZp0%JMk$$y;|{oz4)M4 z!~nR^BGq^P+j}}RsQvodfP6A_H(hsybILD)!EsOR+t$W6WHz?E=vlzc{zB2V+`vkV zUc9st{tJec7B;R)+@)=4T(2iOzi5n?76sfF-Omfyp|7-B<`@C z=G*?Fz}VrN?L00}e813=i%0aA%0i$>o|?A>f4qkALa)v!?HOLrr+ufFJzwbGFL)zl zX2G5Rf#hE1*v*p6?SU5d>RlH0hN9r|TkH`X;&(QeQj+GnUYp0N)oT<3$5Eo2gx9xP z^Xw)uvs*^otA^k{L%ies#d>_g#meG=8`vE2F)y&OZ{;cZ$m^3d1;^uL(^S#)s-^ePQeWgEUhoAqZ>XA-1Jn828TeBg}a1GDxIN9LA_ILRqr8sv5GJ+B-6-!zv$5j zhJr%^4FeS(^3ojiWIDEc3r!UFMP1ZLm`5*icDdSrQD{24%hE(5|IJ}6kXc*=%zJ0s zNwntAb~@fYlS%Z?37JU*L5Ivjkf3I{{gBtx`Clp{#9wBgoSkJZ{s#kU87QJJ?oC%L zcr%C;(q`Sg?YhST`aGFVXKV!nNp>f)7xXO$7TdRgYR9$6Ou@_1( zr)bt}r25y%bv{qT<|6=AI=|+m_DS}>HS(`2< ze=nOq4^r|U;X&#iI1#IgO(tj@qg;Ec6v$Qz;Ue$?Th99gZf!bn~AU4Fq{Uq3YsOUVdyFS`M=#8P?^@ln9 zwg(q{PUePf`Z)Uq=MWg@)~>`{@OUGjwi8s&Ep25vi^Gu*BlNFYzos0@nf*badND#04xX%3mF%mM51sdU$9@DW$uEtCyWtfS zFnuQW3fN(>>+2~kU54f?Ub+m8=Fc+1W?D|?M(3Jtqhr^9-2X6>g@eyOkf-*Jd09y; ze?a?5#&46v#$p(3K836z-$pigx#xlC!P=omma&gh>UCHLu|~0Z<}yrP(^xXF#J|v; zCx`|bCyf}wgjS>&aklMU<(1J-xoi3pC?UK5%O~!SzYGO{r`+JeEhILrcob*?^<_;f z9jF(no1db6swYo3!WY0~8@TbtBiPL9@EYEMgLb!BQY{-_$nvd0e5JZyO7uht)6 z*FovN+Tbexg8jBuDO887RvRm6(b-27YL8gAf2!hr;XV3_v&JEK#JKgIotGNFlGH6E zCe3p%_mHe!JP&Rp-pDOEHH>}>1O)1*+M>kweDn?=%S3=aN?gpt^T7t&L%z&?Qr)m- z!Au!1F@FhrIE3!WR)czy#uF%;Ljc(k*VY^%w;4%Bns5u;=MAD#{{&Y1)FV@Ont;Ai zo5;z~_L_Ng`X+wiqiiB{8~=h}^L;K;pZdWHsNOHQ;0``m{+?QMyM+GoSk1qV=NIY+)nLZU#V=nJqUbZN5e2P zcw3KAt7g<9^VfvXSM1C%A8+lELyZ;VzmzxPhNG~v_f_vs_(P9O7X7IvY-DEU_8w_Y z{w?cSuivspSop7^tnHUi-0Ee>o4*#O&OEOTjoRTRb5t(a#Owmy3Vdu|wN+7z9m`H$ zIdka5M7-%Pp$Znk4kPh_&?R7B$Nd=l=>YX%f?XMdc?X}2s-$0BPWk~#Kh{t8_a~>Y zc}RN6Pf52Mi+%m{uq!`?bh8}nm@Tw~@`L62O93)94w~QJ;X}juCbh3ZW|V_uYncy; zk3T=X%xo#NkSxeR;?n2(>0$E->E?Jra;9tLg@x2aGV$le!-Nz0(*|ZV)bV_9jN06T zZ=C=rNX>wwbWsiMqm!TW!Foo&6J~31H|BH%D28T_U2db22DP;Npfc67gIa9*Fidpj zLhbC@5pBdH&Q@vu;RfqR-||rif0^PZ7MrF0Y`O2;L!w5~A3s06#9T|d`2f_J18BjH zWNFut><&eg%?hCS+ie*rcBySTR%)g@DEefeFt7Sa#ir1~@Kgo{#@|mXF&`aB)7Pl| z#jft(W^$Q3$7O~~s|Rr2-;Dc-KWugdp*hk&b#@Qo$_V6xUx%SU+rAiOxHF_&Ays3*6bNl3~0pB6Lk=SZ<<4Ws#6Bzd36 zpSKA-7txHlS<@h%KaK24S5O7>O$N+Nayd8LM^2qobK^T@ET}Vs?_>|Ic3<1ewS{?*qq7P!4NzsGD*61dXr zVGYY33igA)p+fKZBG~+-(vgs5P1hfn0|rUv+WT}#eCE)2{iCJ!y^;;~UVjXevogA; z1KniJ_~7z2k&X@3!`Ith{U=Gmh=szm_4vP{%Y;x}FBEj#${Ig6Cr?M#TsR68(Cx8p=7QJQ zw^tyFf!?alKolY3f+w}(4a+#d@{05*a`nGKHnL-c3JeNz6i5s^GsbyR_%i(O5Q4a% z3Y1`qVlsN%qf%_E9zcwXE3UleifQ#H5aU8x8P!~woUsHaaOqMx?cPl&eUq4>?g z`&Wydg*$@HR`*t)i5NI%o;UI}P=wBe@8O_1KXA4HYZ7*k6q^nWYcwfS7Ppsrh0R&7 z(oteZfecELtoUly*O#3C>Jzi!#A~5(7zdO+_jGCF;MhEZaCSUhxCu!bqUYj}XAZgD zpU+kY7ewg9BuM_YfA#*Bk9Drc@IEuLT1QWfCz8$ml{YDeK_Er~Qn zGrvRzvf%Qi*3FI0t|fO{H#auB4zlZxv1?g(oDyNhV$HBbL@eP;P0ofI-9x_TbS_K# zR!z?4$o!oF*jvrWwbik~y~#0=OB$iC@|GKa#zlMoq#PasB-bun>WDgA$1XHE7hJ zsEG=a;?@L%E*dnoR#DW(YAIFQA|!}{ny?A5tgDMR5UgUY_1*$i zmxBRTa6>r3_$#g zWdhHv<)z~|nd;fQ4Uwg<@8rLIy3gf>>K1F$&mcU@;hSw(h=EwK7UwTEg7ZsGajX99 zmbkVbLBEVVY1R?xhcdig<3DMzS3jNvZHKbsNKRI*SDa=?lKle5zvipHZ$Dohp8gpF z)=FQnw+#QWb8oe#ONQ*sIILy%gtX?L93V8_@a8x5Bi>wR->)r-J=2Ab1#YNjvrNo6 znwYgsKLdB_%8bu9i!v$(tj0dX5z%Tmt9RowLx->2vs}f;+A7~pB(pzG(%#HbT4I{5 zDKXF1%LR)iYbFeg#%I}AOUPD%U=}c2%%%gy;Oy3i`x{?rrs1Nq8yWoa=H1AkdCTrn zJu^IR|MXyg{9z4ejPL_7T_P>Q4e9|@n|`^qRO^!+-?rtCFZat2Xf40#rf)4j)NdaL zGQ3ZM3q!CcEr*Z~Tan7?UL>yGpF~~)W{m{31q`clwz-DNwdl-FFK&g)e{%fiK<154 zA7jJEG=>{w71a7DJZqtza@K4Qk8UJuP0eZ7;v%$>$8}d9W)n)TJ(1rHlZpZ2MISPA ziUY!JoUF(s56UD{35OaB4x)PYHdzbl0b}$*N2#`&{A<6roj7B6oq0rHd-!5*`#NU* zq@c2oE^f3-Z5qp{(CH>Ep=b66d-u;m14chG^Z6R`JxJK|797&9pny6r3# zIi_i1I^Au1_ia6&g*)x{ga zIqMLZ1PBZd;=tg9A`JRH`-RVIB;m-F+rt;C;$Q(4(asvbnhjFc`^`M3pfA1Xcg$E2 zaOlsDLdaj2O+vxZT(+X!o~1ory>EP^`>XdUXuYX1*-?GhdH+H56Ia!19{FrnH^vJx#Qw!+=3N!GG;Hh4wsq#1#}Ymel*WAeozAz| z^P%DC3NP;f_0EX)+tV4*B@4wNei>MHDW7BC?;7R6IvN@G6m!6tn*=h*=01kc-)3`> zkTSBtpRh!mZUaAwCMS9%;PjW4?h~JaaxS|X1fPGG&J}Cb&9{7w2}d;D?uWGzS{Dw@UNAaEFalSBN2|2S~~ zZ0UrqcDit6BWL0(oD;b#`uXe8lEJqxD-Yc8Tr^n*s9#(*armn8o_#0t`i*@Z$m>Bx ztZU}kOQv?7938L*Tz2Uf{oLLY+l^2*?y#;!r1|_2bU2~Z{EH_Kn?H^F*v#9*x}`0D zoV34(-)!?QhdiXOMIQXOuV2l#@c-8GkL^}|>r4-fZz+GI*S^)qhpJ_C4b&fL;5=pmSXsAf9G>IN&w&^HcGFTGL z9Dgmv{~Y95I1GVolzmtikL(=%4)gMJ3`TS1AM}Gvar6uBFGhdc;T_T|GPo9~C;1+N zZBLwnuy}@ts8%Ig`nBqB*Q53?u7CTt%C{=@YP{;5tc!hWMHPK$9WJXtRD zAp6m5f60!*ZdJClm+^4WBH806k1Y8jJiUgQrK-S}(WL9at%<9kRQzynm z^g-Qb2??sK(mX12Tr$|Lv(N4NLJ?+%mybH5hFCjP+Lm-^t(J;8mUCaUY(S_V zj0sf25Qt&g2isXLI@s6ZQ86bHIES#|$NrmFb&)BS74b%ac7*;M!m&!=o07?2^ivS5 zm2gl)27;pMUuh@g(a ztxA`EtgbB*OXeT_=rM%VyKb0T9n99_ZG*2~75s29PMV-_`%ex9-R!lO#5x#$)yY&# zZ8b!#N6w4F(q@jMUcHWtPhYRMKy`%@seI(KB^CTHsn^

ZSQXrtzwA$jp2VV}Ny(Vz{>eT=YIc)|Zn8S< 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¬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%0D&p-*3&8ILz%5Y#|1L_P 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

g@G?+wN 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

S}qDz<<@cOPjmov6T2VGr3rcP}oFC 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�>i*?TGztb>kqT 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

)Zddmg|E1h5fY7u>xy( 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

GS&MSh?xsOcygt0pfK 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

4YP-;XaB%`owrM#UPj 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