* Add opt-in intent-aware Copilot issue assignment tool Add `assign_copilot_to_issue_with_intent` in a new non-default toolset `copilot_issue_intents`. The tool uses GraphQL's object-form `assignees: [AssigneeUpdateInput!]` so intent metadata (rationale, confidence, is_suggestion) is attached to the Copilot entry only, while existing assignees are preserved. - Reuses the existing Copilot actor lookup, target-repository resolution, base_ref, custom_instructions, GraphQL-Features header, and direct-assignment PR polling behavior. - `is_suggestion: true` records a pending Copilot assignment intent, returns a suggestion-shaped result, and does not launch Copilot or poll for a linked PR. - `rationale` is capped at 280 characters (schema + runtime); `confidence` is validated against `LOW`/`MEDIUM`/`HIGH`. - Toolset is non-default so its inputs do not add schema bloat to the default tool surface; available via `copilot_issue_intents`, `all`, or explicit tool selection. Includes unit tests for direct assignment (with existing assignees and with base_ref/custom_instructions), the suggestion path, invalid rationale length, invalid confidence, and Copilot-not-available; a generated toolsnap; regenerated docs; and an e2e test for the suggestion path. Refs: github/plan-track-agentic-toolkit#683 * Address review: tighten description, require intent fields Per review feedback on #2909: - Trim the tool description to mirror assign_copilot_to_issue and add "Prefer this tool over assign_copilot_to_issue when available", removing the verbose is_suggestion narrative from the schema. - Make rationale, confidence, and is_suggestion required inputs (schema and runtime). is_suggestion is now always sent explicitly on the Copilot AssigneeUpdateInput entry. - Update unit tests to supply the newly-required fields and cover the missing-rationale and missing-confidence rejection paths. - Regenerate toolsnap and README. * Address review: dedupe copilot, require is_suggestion, update doc link Per @RossTarrant review feedback on #2909: - Reject requests where `is_suggestion` is omitted from the raw args before decoding. `mapstructure.WeakDecode` defaults missing bools to false, which would silently launch Copilot instead of recording a suggestion. Presence-check the raw map so callers make the choice explicit. - Skip the copilot-swe-agent actor when copying existing assignees so we don't send its actorId twice (once without metadata and once with intent metadata) when Copilot is already assigned. - Update the stale about-assigning-tasks-to-copilot reference to the redirect target (about-cloud-agent). Applied to the const message, both tool descriptions, and the e2e/unit-test literals that assert on that message. New unit tests cover the missing-is_suggestion rejection and the copilot-dedup behavior. Regenerated toolsnaps.
End To End (e2e) Tests
The purpose of the E2E tests is to have a simple (currently) test that gives maintainers some confidence in the black box behavior of our artifacts. It does this by:
- Building the
github-mcp-serverdocker image - Running the image
- Interacting with the server via stdio
- Issuing requests that interact with the live GitHub API
Running the Tests
A service must be running that supports image building and container creation via the docker CLI.
Since these tests require a token to interact with real resources on the GitHub API, it is gated behind the e2e build flag.
GITHUB_MCP_SERVER_E2E_TOKEN=<YOUR TOKEN> go test -v --tags e2e ./e2e
The GITHUB_MCP_SERVER_E2E_TOKEN environment variable is mapped to GITHUB_PERSONAL_ACCESS_TOKEN internally, but separated to avoid accidental reuse of credentials.
Example
The following diff adjusts the get_me tool to return foobar as the user login.
diff --git a/pkg/github/context_tools.go b/pkg/github/context_tools.go
index 1c91d70..ac4ef2b 100644
--- a/pkg/github/context_tools.go
+++ b/pkg/github/context_tools.go
@@ -39,6 +39,8 @@ func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mc
return mcp.NewToolResultError(fmt.Sprintf("failed to get user: %s", string(body))), nil
}
+ user.Login = sPtr("foobar")
+
r, err := json.Marshal(user)
if err != nil {
return nil, fmt.Errorf("failed to marshal user: %w", err)
@@ -47,3 +49,7 @@ func GetMe(getClient GetClientFn, t translations.TranslationHelperFunc) (tool mc
return mcp.NewToolResultText(string(r)), nil
}
}
+
+func sPtr(s string) *string {
+ return &s
+}
Running the tests:
➜ GITHUB_MCP_SERVER_E2E_TOKEN=$(gh auth token) go test -v --tags e2e ./e2e
=== RUN TestE2E
e2e_test.go:92: Building Docker image for e2e tests...
e2e_test.go:36: Starting Stdio MCP client...
=== RUN TestE2E/Initialize
=== RUN TestE2E/CallTool_get_me
e2e_test.go:85:
Error Trace: /Users/williammartin/workspace/github-mcp-server/e2e/e2e_test.go:85
Error: Not equal:
expected: "foobar"
actual : "williammartin"
Diff:
--- Expected
+++ Actual
@@ -1 +1 @@
-foobar
+williammartin
Test: TestE2E/CallTool_get_me
Messages: expected login to match
--- FAIL: TestE2E (1.05s)
--- PASS: TestE2E/Initialize (0.09s)
--- FAIL: TestE2E/CallTool_get_me (0.46s)
FAIL
FAIL github.com/github/github-mcp-server/e2e 1.433s
FAIL
Debugging the Tests
It is possible to provide GITHUB_MCP_SERVER_E2E_DEBUG=true to run the e2e tests with an in-process version of the MCP server. This has slightly reduced coverage as it doesn't integrate with Docker, or make use of the cobra/viper configuration parsing. However, it allows for placing breakpoints in the MCP Server internals, supporting much better debugging flows than the fully black-box tests.
One might argue that the lack of visibility into failures for the black box tests also indicates a product need, but this solves for the immediate pain point felt as a maintainer.
Limitations
The current test suite is intentionally very limited in scope. This is because the maintenance costs on e2e tests tend to increase significantly over time. To read about some challenges with GitHub integration tests, see go-github integration tests README. We will expand this suite circumspectly!
The tests are quite repetitive and verbose. This is intentional as we want to see them develop more before committing to abstractions.
Currently, visibility into failures is not particularly good. We're hoping that we can pull apart the mcp-go client and have it hook into streams representing stdio without requiring an exec. This way we can get breakpoints in the debugger easily.
Global State Mutation Tests
Some tools (such as those that mark all notifications as read) would change the global state for the tester, and are also not idempotent, so they offer little value for end to end tests and instead should rely on unit testing and manual verifications.