Skip to content

Commit ee69163

Browse files
committed
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
1 parent 1338dbe commit ee69163

7 files changed

Lines changed: 933 additions & 2 deletions

File tree

README.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,7 @@ The following sets of tools are available:
590590
| <picture><source media="(prefers-color-scheme: dark)" srcset="pkg/octicons/icons/code-square-dark.png"><source media="(prefers-color-scheme: light)" srcset="pkg/octicons/icons/code-square-light.png"><img src="pkg/octicons/icons/code-square-light.png" width="20" height="20" alt="code-square"></picture> | `code_quality` | GitHub Code Quality related tools |
591591
| <picture><source media="(prefers-color-scheme: dark)" srcset="pkg/octicons/icons/codescan-dark.png"><source media="(prefers-color-scheme: light)" srcset="pkg/octicons/icons/codescan-light.png"><img src="pkg/octicons/icons/codescan-light.png" width="20" height="20" alt="codescan"></picture> | `code_security` | Code security related tools, such as GitHub Code Scanning |
592592
| <picture><source media="(prefers-color-scheme: dark)" srcset="pkg/octicons/icons/copilot-dark.png"><source media="(prefers-color-scheme: light)" srcset="pkg/octicons/icons/copilot-light.png"><img src="pkg/octicons/icons/copilot-light.png" width="20" height="20" alt="copilot"></picture> | `copilot` | Copilot related tools |
593+
| <picture><source media="(prefers-color-scheme: dark)" srcset="pkg/octicons/icons/copilot-dark.png"><source media="(prefers-color-scheme: light)" srcset="pkg/octicons/icons/copilot-light.png"><img src="pkg/octicons/icons/copilot-light.png" width="20" height="20" alt="copilot"></picture> | `copilot_issue_intents` | Opt-in Copilot issue assignment tools that carry intent metadata (rationale, confidence, suggestion) |
593594
| <picture><source media="(prefers-color-scheme: dark)" srcset="pkg/octicons/icons/dependabot-dark.png"><source media="(prefers-color-scheme: light)" srcset="pkg/octicons/icons/dependabot-light.png"><img src="pkg/octicons/icons/dependabot-light.png" width="20" height="20" alt="dependabot"></picture> | `dependabot` | Dependabot tools |
594595
| <picture><source media="(prefers-color-scheme: dark)" srcset="pkg/octicons/icons/comment-discussion-dark.png"><source media="(prefers-color-scheme: light)" srcset="pkg/octicons/icons/comment-discussion-light.png"><img src="pkg/octicons/icons/comment-discussion-light.png" width="20" height="20" alt="comment-discussion"></picture> | `discussions` | GitHub Discussions related tools |
595596
| <picture><source media="(prefers-color-scheme: dark)" srcset="pkg/octicons/icons/logo-gist-dark.png"><source media="(prefers-color-scheme: light)" srcset="pkg/octicons/icons/logo-gist-light.png"><img src="pkg/octicons/icons/logo-gist-light.png" width="20" height="20" alt="logo-gist"></picture> | `gists` | GitHub Gist related tools |
@@ -750,6 +751,23 @@ The following sets of tools are available:
750751

751752
<details>
752753

754+
<summary><picture><source media="(prefers-color-scheme: dark)" srcset="pkg/octicons/icons/copilot-dark.png"><source media="(prefers-color-scheme: light)" srcset="pkg/octicons/icons/copilot-light.png"><img src="pkg/octicons/icons/copilot-light.png" width="20" height="20" alt="copilot"></picture> Copilot Issue Intents</summary>
755+
756+
- **assign_copilot_to_issue_with_intent** - Assign Copilot to issue with intent
757+
- **Required OAuth Scopes**: `repo`
758+
- `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. Ignored when is_suggestion is true (string, optional)
759+
- `confidence`: How confident you are in this choice. 'HIGH' for clear signal or explicit user request, 'MEDIUM' for reasonable inference with some ambiguity, 'LOW' for best guess with limited signal. (string, optional)
760+
- `custom_instructions`: Optional custom instructions to guide the agent beyond the issue body. Ignored when is_suggestion is true (string, optional)
761+
- `is_suggestion`: If true, records a pending Copilot assignment intent rather than launching the agent. Approval later supplies the launch context; base_ref and custom_instructions are ignored in this case. (boolean, optional)
762+
- `issue_number`: Issue number (number, required)
763+
- `owner`: Repository owner (string, required)
764+
- `rationale`: One concise sentence explaining what specifically about the issue led to choosing Copilot. State the concrete signal (e.g. 'Well-scoped task with clear acceptance criteria'). (string, optional)
765+
- `repo`: Repository name (string, required)
766+
767+
</details>
768+
769+
<details>
770+
753771
<summary><picture><source media="(prefers-color-scheme: dark)" srcset="pkg/octicons/icons/dependabot-dark.png"><source media="(prefers-color-scheme: light)" srcset="pkg/octicons/icons/dependabot-light.png"><img src="pkg/octicons/icons/dependabot-light.png" width="20" height="20" alt="dependabot"></picture> Dependabot</summary>
754772

755773
- **get_dependabot_alert** - Get dependabot alert

docs/remote-server.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Below is a table of available toolsets for the remote GitHub MCP Server. Each to
2525
| <picture><source media="(prefers-color-scheme: dark)" srcset="../pkg/octicons/icons/code-square-dark.png"><source media="(prefers-color-scheme: light)" srcset="../pkg/octicons/icons/code-square-light.png"><img src="../pkg/octicons/icons/code-square-light.png" width="20" height="20" alt="code-square"></picture><br>`code_quality` | GitHub Code Quality related tools | https://api.githubcopilot.com/mcp/x/code_quality | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_quality&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_quality%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/code_quality/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-code_quality&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcode_quality%2Freadonly%22%7D) |
2626
| <picture><source media="(prefers-color-scheme: dark)" srcset="../pkg/octicons/icons/codescan-dark.png"><source media="(prefers-color-scheme: light)" srcset="../pkg/octicons/icons/codescan-light.png"><img src="../pkg/octicons/icons/codescan-light.png" width="20" height="20" alt="codescan"></picture><br>`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) |
2727
| <picture><source media="(prefers-color-scheme: dark)" srcset="../pkg/octicons/icons/copilot-dark.png"><source media="(prefers-color-scheme: light)" srcset="../pkg/octicons/icons/copilot-light.png"><img src="../pkg/octicons/icons/copilot-light.png" width="20" height="20" alt="copilot"></picture><br>`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) |
28+
| <picture><source media="(prefers-color-scheme: dark)" srcset="../pkg/octicons/icons/copilot-dark.png"><source media="(prefers-color-scheme: light)" srcset="../pkg/octicons/icons/copilot-light.png"><img src="../pkg/octicons/icons/copilot-light.png" width="20" height="20" alt="copilot"></picture><br>`copilot_issue_intents` | Opt-in Copilot issue assignment tools that carry intent metadata (rationale, confidence, suggestion) | https://api.githubcopilot.com/mcp/x/copilot_issue_intents | [Install](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot_issue_intents&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot_issue_intents%22%7D) | [read-only](https://api.githubcopilot.com/mcp/x/copilot_issue_intents/readonly) | [Install read-only](https://insiders.vscode.dev/redirect/mcp/install?name=gh-copilot_issue_intents&config=%7B%22type%22%3A%20%22http%22%2C%22url%22%3A%20%22https%3A%2F%2Fapi.githubcopilot.com%2Fmcp%2Fx%2Fcopilot_issue_intents%2Freadonly%22%7D) |
2829
| <picture><source media="(prefers-color-scheme: dark)" srcset="../pkg/octicons/icons/dependabot-dark.png"><source media="(prefers-color-scheme: light)" srcset="../pkg/octicons/icons/dependabot-light.png"><img src="../pkg/octicons/icons/dependabot-light.png" width="20" height="20" alt="dependabot"></picture><br>`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) |
2930
| <picture><source media="(prefers-color-scheme: dark)" srcset="../pkg/octicons/icons/comment-discussion-dark.png"><source media="(prefers-color-scheme: light)" srcset="../pkg/octicons/icons/comment-discussion-light.png"><img src="../pkg/octicons/icons/comment-discussion-light.png" width="20" height="20" alt="comment-discussion"></picture><br>`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) |
3031
| <picture><source media="(prefers-color-scheme: dark)" srcset="../pkg/octicons/icons/logo-gist-dark.png"><source media="(prefers-color-scheme: light)" srcset="../pkg/octicons/icons/logo-gist-light.png"><img src="../pkg/octicons/icons/logo-gist-light.png" width="20" height="20" alt="logo-gist"></picture><br>`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) |

e2e/e2e_test.go

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1104,6 +1104,115 @@ func TestAssignCopilotToIssue(t *testing.T) {
11041104
require.Equal(t, "Copilot", *assignees.Assignees[0].Login, "expected copilot to be assigned to the issue")
11051105
}
11061106

1107+
// TestAssignCopilotToIssueWithIntent exercises the opt-in intent-aware assignment
1108+
// tool along the is_suggestion=true path. That path records a pending Copilot
1109+
// assignment intent rather than launching the agent, so the tool returns a
1110+
// suggestion-shaped result without a linked pull request and no Copilot user is
1111+
// added to the issue's assignees.
1112+
func TestAssignCopilotToIssueWithIntent(t *testing.T) {
1113+
t.Parallel()
1114+
1115+
if getE2EHost() != "" && getE2EHost() != "https://github.com" {
1116+
t.Skip("Skipping test because the host does not support copilot being assigned to issues")
1117+
}
1118+
1119+
mcpClient := setupMCPClient(t)
1120+
ctx := context.Background()
1121+
1122+
t.Log("Getting current user...")
1123+
resp, err := mcpClient.CallTool(ctx, &mcp.CallToolParams{Name: "get_me"})
1124+
require.NoError(t, err, "expected to call 'get_me' tool successfully")
1125+
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
1126+
require.Len(t, resp.Content, 1, "expected content to have one item")
1127+
1128+
textContent, ok := resp.Content[0].(*mcp.TextContent)
1129+
require.True(t, ok, "expected content to be of type TextContent")
1130+
1131+
var trimmedGetMeText struct {
1132+
Login string `json:"login"`
1133+
}
1134+
err = json.Unmarshal([]byte(textContent.Text), &trimmedGetMeText)
1135+
require.NoError(t, err, "expected to unmarshal text content successfully")
1136+
currentOwner := trimmedGetMeText.Login
1137+
1138+
repoName := fmt.Sprintf("github-mcp-server-e2e-%s-%d", t.Name(), time.Now().UnixMilli())
1139+
1140+
t.Logf("Creating repository %s/%s...", currentOwner, repoName)
1141+
_, err = mcpClient.CallTool(ctx, &mcp.CallToolParams{
1142+
Name: "create_repository",
1143+
Arguments: map[string]any{
1144+
"name": repoName,
1145+
"private": true,
1146+
"autoInit": true,
1147+
},
1148+
})
1149+
require.NoError(t, err, "expected to call 'create_repository' tool successfully")
1150+
1151+
t.Cleanup(func() {
1152+
ghClient := getRESTClient(t)
1153+
t.Logf("Deleting repository %s/%s...", currentOwner, repoName)
1154+
_, err := ghClient.Repositories.Delete(context.Background(), currentOwner, repoName)
1155+
require.NoError(t, err, "expected to delete repository successfully")
1156+
})
1157+
1158+
t.Logf("Creating issue in %s/%s...", currentOwner, repoName)
1159+
resp, err = mcpClient.CallTool(ctx, &mcp.CallToolParams{
1160+
Name: "issue_write",
1161+
Arguments: map[string]any{
1162+
"method": "create",
1163+
"owner": currentOwner,
1164+
"repo": repoName,
1165+
"title": "Test issue for intent-aware copilot suggestion",
1166+
},
1167+
})
1168+
require.NoError(t, err, "expected to call 'issue_write' tool successfully")
1169+
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
1170+
1171+
t.Logf("Recording pending copilot assignment suggestion in %s/%s...", currentOwner, repoName)
1172+
resp, err = mcpClient.CallTool(ctx, &mcp.CallToolParams{
1173+
Name: "assign_copilot_to_issue_with_intent",
1174+
Arguments: map[string]any{
1175+
"owner": currentOwner,
1176+
"repo": repoName,
1177+
"issue_number": 1,
1178+
"rationale": "E2E: well-scoped test task.",
1179+
"confidence": "HIGH",
1180+
"is_suggestion": true,
1181+
},
1182+
})
1183+
require.NoError(t, err, "expected to call 'assign_copilot_to_issue_with_intent' tool successfully")
1184+
1185+
require.Len(t, resp.Content, 1, "expected content to have one item")
1186+
textContent, ok = resp.Content[0].(*mcp.TextContent)
1187+
require.True(t, ok, "expected content to be of type TextContent")
1188+
1189+
possibleExpectedFailure := "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."
1190+
if resp.IsError && textContent.Text == possibleExpectedFailure {
1191+
t.Skip("skipping because copilot wasn't available as an assignee on this issue, it's likely that the owner doesn't have copilot enabled in their settings")
1192+
}
1193+
1194+
require.False(t, resp.IsError, fmt.Sprintf("expected result not to be an error: %+v", resp))
1195+
1196+
var response map[string]any
1197+
require.NoError(t, json.Unmarshal([]byte(textContent.Text), &response), "expected suggestion result to be JSON")
1198+
require.Equal(t, true, response["is_suggestion"], "expected is_suggestion=true in result")
1199+
require.Contains(t, response["message"], "pending copilot assignment suggestion",
1200+
"expected suggestion-shaped message, got %v", response["message"])
1201+
require.NotContains(t, response, "pull_request",
1202+
"suggestion path must not claim PR creation")
1203+
1204+
// A pure suggestion does not launch Copilot, so no Copilot user should appear
1205+
// on the issue's assignees list.
1206+
ghClient := getRESTClient(t)
1207+
issue, response2, err := ghClient.Issues.Get(context.Background(), currentOwner, repoName, 1)
1208+
require.NoError(t, err, "expected to get issue successfully")
1209+
require.Equal(t, http.StatusOK, response2.StatusCode, "expected to get issue successfully")
1210+
for _, a := range issue.Assignees {
1211+
require.NotEqual(t, "Copilot", *a.Login,
1212+
"suggestion path must not add Copilot to applied assignees")
1213+
}
1214+
}
1215+
11071216
func TestPullRequestAtomicCreateAndSubmit(t *testing.T) {
11081217
t.Parallel()
11091218

0 commit comments

Comments
 (0)