Skip to content

Commit cb60838

Browse files
committed
Implement Tool to Assign Issues to Contributors #7625
1 parent 9f99f71 commit cb60838

3 files changed

Lines changed: 122 additions & 14 deletions

File tree

ai/mcp/server/github-workflow/openapi.yaml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,67 @@ paths:
409409
schema:
410410
$ref: '#/components/schemas/ErrorResponse'
411411

412+
/issues/{issue_number}/assignees:
413+
post:
414+
summary: Assign an issue to users
415+
operationId: assign_issue
416+
x-pass-as-object: true
417+
x-annotations:
418+
readOnlyHint: false
419+
description: |
420+
Assigns a GitHub issue to one or more users.
421+
422+
**Permission Required:**
423+
This is a restricted action. Before calling this tool, you **must** first use the `get_viewer_permission` tool to verify that you have `ADMIN`, `MAINTAIN`, or `WRITE` permissions.
424+
tags: [Issues]
425+
parameters:
426+
- name: issue_number
427+
in: path
428+
required: true
429+
description: The number of the issue to assign.
430+
schema:
431+
type: integer
432+
requestBody:
433+
required: true
434+
content:
435+
application/json:
436+
schema:
437+
type: object
438+
required:
439+
- assignees
440+
properties:
441+
assignees:
442+
type: array
443+
items:
444+
type: string
445+
description: An array of GitHub user logins to assign to the issue.
446+
example: ["tobiu", "some-contributor"]
447+
responses:
448+
'200':
449+
description: Issue assigned successfully.
450+
content:
451+
application/json:
452+
schema:
453+
$ref: '#/components/schemas/SuccessResponse'
454+
'403':
455+
description: Forbidden. The user does not have permission to assign issues.
456+
content:
457+
application/json:
458+
schema:
459+
$ref: '#/components/schemas/ErrorResponse'
460+
'404':
461+
description: Issue not found.
462+
content:
463+
application/json:
464+
schema:
465+
$ref: '#/components/schemas/ErrorResponse'
466+
'500':
467+
description: Internal server error, e.g., `gh` command failed.
468+
content:
469+
application/json:
470+
schema:
471+
$ref: '#/components/schemas/ErrorResponse'
472+
412473
/issues/{issue_number}/content:
413474
get:
414475
summary: Get Local Issue Content by ID

ai/mcp/server/github-workflow/services/IssueService.mjs

Lines changed: 60 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
1-
import Base from '../../../../../src/core/Base.mjs';
2-
import GraphqlService from './GraphqlService.mjs';
3-
import aiConfig from '../config.mjs';
4-
import logger from '../logger.mjs';
5-
import { spawn } from 'child_process';
6-
import os from 'os';
7-
import path from 'path';
8-
import { promises as fsp } from 'fs';
9-
import { exec } from 'child_process';
10-
import { promisify } from 'util';
11-
import { spawn } from 'child_process';
12-
import { GET_ISSUE_AND_LABEL_IDS } from './queries/issueQueries.mjs';
13-
import { ADD_LABELS, REMOVE_LABELS } from './queries/mutations.mjs';
1+
import aiConfig from '../config.mjs';
2+
import Base from '../../../../../src/core/Base.mjs';
3+
import GraphqlService from './GraphqlService.mjs';
4+
import logger from '../logger.mjs';
5+
import {exec} from 'child_process';
6+
import {promisify} from 'util';
7+
import {spawn} from 'child_process';
8+
import {GET_ISSUE_AND_LABEL_IDS} from './queries/issueQueries.mjs';
9+
import {ADD_LABELS, REMOVE_LABELS} from './queries/mutations.mjs';
10+
import RepositoryService from './RepositoryService.mjs';
1411

1512
const execAsync = promisify(exec);
1613

@@ -31,7 +28,56 @@ class IssueService extends Base {
3128
* @member {Boolean} singleton=true
3229
* @protected
3330
*/
34-
singleton: true
31+
singleton: true,
32+
/**
33+
* @member {String[]} writePermissions=['ADMIN', 'MAINTAIN', 'WRITE']
34+
* @protected
35+
*/
36+
writePermissions: ['ADMIN', 'MAINTAIN', 'WRITE']
37+
}
38+
39+
/**
40+
* Assigns a GitHub issue to one or more users.
41+
* @param {object} options
42+
* @param {number} options.issue_number
43+
* @param {string[]} options.assignees
44+
* @returns {Promise<object>}
45+
*/
46+
async assignIssue({issue_number, assignees}) {
47+
if (!this.writePermissions.includes(RepositoryService.viewerPermission)) {
48+
const message = [
49+
`Permission denied. Viewer has '${RepositoryService.viewerPermission}' permission, `,
50+
`but one of [${this.writePermissions.join(', ')}] is required to assign issues.`
51+
].join('');
52+
53+
logger.warn(message);
54+
return {
55+
error : 'Permission Denied',
56+
message,
57+
code : 'FORBIDDEN'
58+
};
59+
}
60+
61+
logger.info(`Attempting to assign issue #${issue_number} to: ${assignees.join(', ')}`);
62+
63+
try {
64+
const assigneeFlags = assignees.map(a => `--add-assignee "${a}"`).join(' ');
65+
const command = `gh issue edit ${issue_number} ${assigneeFlags}`;
66+
67+
await execAsync(command);
68+
69+
const message = `Successfully assigned issue #${issue_number} to ${assignees.join(', ')}`;
70+
logger.info(message);
71+
return { message };
72+
73+
} catch (error) {
74+
logger.error(`Error assigning issue #${issue_number}:`, error);
75+
return {
76+
error : 'GitHub CLI command failed',
77+
message: error.message,
78+
code : 'GH_CLI_ERROR'
79+
};
80+
}
3581
}
3682

3783
/**

ai/mcp/server/github-workflow/services/toolService.mjs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const openApiFilePath = path.join(__dirname, '../openapi.yaml');
1515

1616
const serviceMapping = {
1717
add_labels : IssueService.addLabels.bind(IssueService),
18+
assign_issue : IssueService.assignIssue.bind(IssueService),
1819
checkout_pull_request: PullRequestService.checkoutPullRequest.bind(PullRequestService),
1920
create_comment : PullRequestService.createComment.bind(PullRequestService),
2021
create_issue : IssueService.createIssue.bind(IssueService),

0 commit comments

Comments
 (0)