github-pro-mcp 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +76 -0
- package/README.md +167 -66
- package/THIRD-PARTY-NOTICES.md +635 -0
- package/dist-package/index.js +28601 -0
- package/package.json +33 -17
- package/dist/client.d.ts +0 -27
- package/dist/client.js +0 -80
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -52
- package/dist/tools/issue_fields.d.ts +0 -4
- package/dist/tools/issue_fields.js +0 -104
- package/dist/tools/issues.d.ts +0 -12
- package/dist/tools/issues.js +0 -607
- package/dist/tools/labels.d.ts +0 -3
- package/dist/tools/labels.js +0 -87
- package/dist/tools/organizations.d.ts +0 -4
- package/dist/tools/organizations.js +0 -89
- package/dist/tools/projects.d.ts +0 -4
- package/dist/tools/projects.js +0 -474
- package/dist/tools/pull_requests.d.ts +0 -4
- package/dist/tools/pull_requests.js +0 -127
- package/dist/tools/repos.d.ts +0 -8
- package/dist/tools/repos.js +0 -217
- package/dist/types/github-api.d.ts +0 -63
- package/dist/types/github-api.js +0 -1
- package/dist/utils/circuitBreaker.d.ts +0 -11
- package/dist/utils/circuitBreaker.js +0 -125
- package/dist/utils/errors.d.ts +0 -17
- package/dist/utils/errors.js +0 -107
- package/dist/utils/logger.d.ts +0 -14
- package/dist/utils/logger.js +0 -40
- package/dist/utils/metrics.d.ts +0 -11
- package/dist/utils/metrics.js +0 -70
- package/dist/utils/rateLimit.d.ts +0 -21
- package/dist/utils/rateLimit.js +0 -40
- package/dist/utils/retry.d.ts +0 -9
- package/dist/utils/retry.js +0 -76
- package/dist/utils/types.d.ts +0 -33
- package/dist/utils/types.js +0 -33
- package/dist/utils/validation.d.ts +0 -12
- package/dist/utils/validation.js +0 -75
package/dist/tools/issues.js
DELETED
|
@@ -1,607 +0,0 @@
|
|
|
1
|
-
import * as z from "zod/v4";
|
|
2
|
-
import { createClient, gql } from "../client.js";
|
|
3
|
-
import { handleGitHubError } from "../utils/errors.js";
|
|
4
|
-
import { validatePinnedOwnerRepo } from "../utils/validation.js";
|
|
5
|
-
import { OwnerRepoSchema, PaginationSchema, IssueNumberSchema, IncludeFlagsSchema } from "../utils/types.js";
|
|
6
|
-
export function registerIssueRead(server) {
|
|
7
|
-
server.registerTool("issue_read", {
|
|
8
|
-
description: "Get details of a specific issue. Optionally include comments, labels, field values, and sub-issues. Each include_* flag makes additional API calls.",
|
|
9
|
-
inputSchema: OwnerRepoSchema.extend({
|
|
10
|
-
issue_number: IssueNumberSchema,
|
|
11
|
-
include_comments: IncludeFlagsSchema.shape.include_comments,
|
|
12
|
-
include_labels: IncludeFlagsSchema.shape.include_labels,
|
|
13
|
-
include_fields: IncludeFlagsSchema.shape.include_fields,
|
|
14
|
-
include_sub_issues: IncludeFlagsSchema.shape.include_sub_issues,
|
|
15
|
-
max_comments: z.number().int().positive().optional().describe("Maximum number of comments to return (default: all)"),
|
|
16
|
-
max_labels: z.number().int().positive().optional().default(20).describe("Maximum number of labels to return via GraphQL"),
|
|
17
|
-
max_sub_issues: z.number().int().positive().optional().default(20).describe("Maximum number of sub-issues to return via GraphQL"),
|
|
18
|
-
}),
|
|
19
|
-
}, async ({ owner, repo, issue_number, include_comments, include_labels, include_fields, include_sub_issues, max_comments, max_labels, max_sub_issues }) => {
|
|
20
|
-
try {
|
|
21
|
-
const pinErr = validatePinnedOwnerRepo(owner, repo);
|
|
22
|
-
if (pinErr)
|
|
23
|
-
return pinErr;
|
|
24
|
-
const octokit = createClient();
|
|
25
|
-
const { data: issue } = await octokit.rest.issues.get({ owner, repo, issue_number });
|
|
26
|
-
const issueData = issue;
|
|
27
|
-
const result = {
|
|
28
|
-
number: issueData.number,
|
|
29
|
-
title: issueData.title,
|
|
30
|
-
body: issueData.body,
|
|
31
|
-
state: issueData.state,
|
|
32
|
-
html_url: issueData.html_url,
|
|
33
|
-
created_at: issueData.created_at,
|
|
34
|
-
updated_at: issueData.updated_at,
|
|
35
|
-
closed_at: issueData.closed_at,
|
|
36
|
-
user: { login: issueData.user?.login, avatar_url: issueData.user?.avatar_url },
|
|
37
|
-
labels: issueData.labels.map((l) => {
|
|
38
|
-
if (typeof l === "string")
|
|
39
|
-
return { name: l, color: null };
|
|
40
|
-
return { name: l.name ?? null, color: l.color ?? null };
|
|
41
|
-
}),
|
|
42
|
-
assignees: issueData.assignees?.map((a) => a.login) ?? [],
|
|
43
|
-
};
|
|
44
|
-
if (include_comments) {
|
|
45
|
-
const { data: comments } = await octokit.rest.issues.listComments({ owner, repo, issue_number, per_page: max_comments ?? 100 });
|
|
46
|
-
result.comments = comments.map((c) => ({
|
|
47
|
-
id: c.id,
|
|
48
|
-
user: c.user?.login,
|
|
49
|
-
body: c.body,
|
|
50
|
-
created_at: c.created_at,
|
|
51
|
-
}));
|
|
52
|
-
}
|
|
53
|
-
if (include_labels) {
|
|
54
|
-
const labelLimit = max_labels ?? 20;
|
|
55
|
-
const query = `
|
|
56
|
-
query($owner: String!, $repo: String!, $number: Int!) {
|
|
57
|
-
repository(owner: $owner, name: $repo) {
|
|
58
|
-
issue(number: $number) {
|
|
59
|
-
labels(first: ${labelLimit}) {
|
|
60
|
-
nodes { name color description }
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
`;
|
|
66
|
-
const labelData = await gql(query, { owner, repo, number: issue_number });
|
|
67
|
-
result.labels_graphql = labelData.repository?.issue?.labels?.nodes ?? [];
|
|
68
|
-
}
|
|
69
|
-
if (include_fields) {
|
|
70
|
-
result.field_values = issueData.issue_field_values ?? [];
|
|
71
|
-
}
|
|
72
|
-
if (include_sub_issues) {
|
|
73
|
-
const subLimit = max_sub_issues ?? 20;
|
|
74
|
-
const query = `
|
|
75
|
-
query($owner: String!, $repo: String!, $number: Int!) {
|
|
76
|
-
repository(owner: $owner, name: $repo) {
|
|
77
|
-
issue(number: $number) {
|
|
78
|
-
subIssues(first: ${subLimit}) {
|
|
79
|
-
nodes { number title state }
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
`;
|
|
85
|
-
const subData = await gql(query, { owner, repo, number: issue_number });
|
|
86
|
-
result.sub_issues = subData.repository?.issue?.subIssues?.nodes ?? [];
|
|
87
|
-
}
|
|
88
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
89
|
-
}
|
|
90
|
-
catch (err) {
|
|
91
|
-
return handleGitHubError(err);
|
|
92
|
-
}
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
export function registerIssueList(server) {
|
|
96
|
-
server.registerTool("issue_list", {
|
|
97
|
-
description: "List issues in a repository. Optionally include org-level field values (Priority, Effort) and/or project board field values (Status, Application). Supports field-based filtering with filter_fields (AND logic).",
|
|
98
|
-
inputSchema: OwnerRepoSchema.extend({
|
|
99
|
-
state: z.enum(["open", "closed"]).optional().describe("Filter by issue state"),
|
|
100
|
-
labels: z.string().optional().describe("Comma-separated label names to filter by"),
|
|
101
|
-
per_page: PaginationSchema.shape.per_page,
|
|
102
|
-
page: PaginationSchema.shape.page,
|
|
103
|
-
include_fields: z.boolean().optional().default(false).describe("Include org-level field values (Priority, Effort) for each issue"),
|
|
104
|
-
include_project_fields: z.boolean().optional().default(false).describe("Include project board field values (Status, Application) for each issue"),
|
|
105
|
-
filter_fields: z.array(z.object({
|
|
106
|
-
field_name: z.string().min(1).describe("Field name to filter by, e.g. 'Priority', 'Status', 'Application'"),
|
|
107
|
-
field_value: z.string().min(1).describe("Field value to match, e.g. 'Medium', 'In Progress', 'Done'"),
|
|
108
|
-
})).optional().describe("Filter issues by field values (AND logic). All filters must match. Fetches required field data automatically."),
|
|
109
|
-
}),
|
|
110
|
-
}, async ({ owner, repo, state, labels, per_page, page, include_fields, include_project_fields, filter_fields }) => {
|
|
111
|
-
try {
|
|
112
|
-
const pinErr = validatePinnedOwnerRepo(owner, repo);
|
|
113
|
-
if (pinErr)
|
|
114
|
-
return pinErr;
|
|
115
|
-
const octokit = createClient();
|
|
116
|
-
const hasFilterFields = !!(filter_fields && filter_fields.length > 0);
|
|
117
|
-
const needOrgFields = include_fields || hasFilterFields;
|
|
118
|
-
const needProjectFields = include_project_fields || hasFilterFields;
|
|
119
|
-
let fieldIdToName = null;
|
|
120
|
-
if (needOrgFields) {
|
|
121
|
-
try {
|
|
122
|
-
const { data: profile } = await octokit.rest.users.getByUsername({ username: owner });
|
|
123
|
-
if (profile.type === "Organization") {
|
|
124
|
-
const { data: fieldDefs } = await octokit.request('GET /orgs/{owner}/issue-fields', { owner });
|
|
125
|
-
fieldIdToName = new Map(fieldDefs.map((fd) => [fd.id, fd.name]));
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
catch {
|
|
129
|
-
fieldIdToName = null;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
const restParams = { owner, repo, per_page, page };
|
|
133
|
-
if (state)
|
|
134
|
-
restParams.state = state;
|
|
135
|
-
if (labels)
|
|
136
|
-
restParams.labels = labels;
|
|
137
|
-
const { data: restIssues } = await octokit.rest.issues.listForRepo(restParams);
|
|
138
|
-
if (!restIssues.length) {
|
|
139
|
-
return { content: [{ type: "text", text: JSON.stringify({ total: 0, issues: [] }, null, 2) }] };
|
|
140
|
-
}
|
|
141
|
-
let projectMap = null;
|
|
142
|
-
if (needProjectFields) {
|
|
143
|
-
const gqlStates = state ? [state.toUpperCase()] : null;
|
|
144
|
-
const gqlLabels = labels ? labels.split(",").map((l) => l.trim()) : null;
|
|
145
|
-
const gqlQuery = `
|
|
146
|
-
query($owner: String!, $repo: String!, $first: Int!, $states: [IssueState!], $labels: [String!]) {
|
|
147
|
-
repository(owner: $owner, name: $repo) {
|
|
148
|
-
issues(first: $first, states: $states, labels: $labels, orderBy: { field: UPDATED_AT, direction: DESC }) {
|
|
149
|
-
nodes {
|
|
150
|
-
number
|
|
151
|
-
projectItems(first: 5) {
|
|
152
|
-
nodes {
|
|
153
|
-
project { title }
|
|
154
|
-
fieldValues(first: 20) {
|
|
155
|
-
nodes {
|
|
156
|
-
__typename
|
|
157
|
-
... on ProjectV2ItemFieldTextValue { text field { ... on ProjectV2Field { name } ... on ProjectV2SingleSelectField { name } ... on ProjectV2IterationField { name } } }
|
|
158
|
-
... on ProjectV2ItemFieldSingleSelectValue { name field { ... on ProjectV2Field { name } ... on ProjectV2SingleSelectField { name } ... on ProjectV2IterationField { name } } }
|
|
159
|
-
... on ProjectV2ItemFieldNumberValue { number field { ... on ProjectV2Field { name } ... on ProjectV2SingleSelectField { name } ... on ProjectV2IterationField { name } } }
|
|
160
|
-
... on ProjectV2ItemFieldDateValue { date field { ... on ProjectV2Field { name } ... on ProjectV2SingleSelectField { name } ... on ProjectV2IterationField { name } } }
|
|
161
|
-
... on ProjectV2ItemFieldIterationValue { title field { ... on ProjectV2Field { name } ... on ProjectV2SingleSelectField { name } ... on ProjectV2IterationField { name } } }
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
`;
|
|
171
|
-
const gqlResult = await gql(gqlQuery, {
|
|
172
|
-
owner, repo, first: per_page, states: gqlStates, labels: gqlLabels,
|
|
173
|
-
});
|
|
174
|
-
projectMap = new Map();
|
|
175
|
-
for (const node of gqlResult.repository?.issues?.nodes ?? []) {
|
|
176
|
-
projectMap.set(node.number, (node.projectItems?.nodes ?? []));
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
const filterNameSet = hasFilterFields ? new Set(filter_fields.map((f) => f.field_name)) : null;
|
|
180
|
-
const labelsToNames = (issueLabels) => issueLabels.map((l) => {
|
|
181
|
-
if (typeof l === "string")
|
|
182
|
-
return { name: l, color: null };
|
|
183
|
-
return { name: l.name ?? null, color: l.color ?? null };
|
|
184
|
-
});
|
|
185
|
-
const issues = [];
|
|
186
|
-
for (const issue of restIssues) {
|
|
187
|
-
const issueData = issue;
|
|
188
|
-
const entry = {
|
|
189
|
-
number: issueData.number,
|
|
190
|
-
title: issueData.title,
|
|
191
|
-
state: issueData.state,
|
|
192
|
-
html_url: issueData.html_url,
|
|
193
|
-
created_at: issueData.created_at,
|
|
194
|
-
updated_at: issueData.updated_at,
|
|
195
|
-
labels: labelsToNames(issueData.labels),
|
|
196
|
-
};
|
|
197
|
-
let orgFieldVals = null;
|
|
198
|
-
let projectFieldVals = null;
|
|
199
|
-
if (needOrgFields) {
|
|
200
|
-
orgFieldVals = [];
|
|
201
|
-
if (fieldIdToName && issueData.issue_field_values) {
|
|
202
|
-
for (const fv of issueData.issue_field_values) {
|
|
203
|
-
const fName = fieldIdToName.get(fv.issue_field_id);
|
|
204
|
-
if (fName && (!filterNameSet || filterNameSet.has(fName))) {
|
|
205
|
-
orgFieldVals.push({
|
|
206
|
-
name: fName,
|
|
207
|
-
value: fv.single_select_option?.name ?? String(fv.value ?? ""),
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
if (include_fields) {
|
|
213
|
-
entry.field_values = orgFieldVals;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
if (needProjectFields && projectMap) {
|
|
217
|
-
projectFieldVals = [];
|
|
218
|
-
const projectItems = projectMap.get(issueData.number) ?? [];
|
|
219
|
-
for (const pi of projectItems) {
|
|
220
|
-
const projectTitle = pi.project?.title ?? "unknown";
|
|
221
|
-
for (const fvNode of pi.fieldValues?.nodes ?? []) {
|
|
222
|
-
if (!fvNode)
|
|
223
|
-
continue;
|
|
224
|
-
const fv = fvNode;
|
|
225
|
-
const fvFieldName = fv.field?.name;
|
|
226
|
-
if (!fvFieldName || (filterNameSet && !filterNameSet.has(fvFieldName)))
|
|
227
|
-
continue;
|
|
228
|
-
let fValue = null;
|
|
229
|
-
const typename = fv.__typename;
|
|
230
|
-
if (typename === "ProjectV2ItemFieldSingleSelectValue")
|
|
231
|
-
fValue = fv.name;
|
|
232
|
-
else if (typename === "ProjectV2ItemFieldTextValue")
|
|
233
|
-
fValue = fv.text;
|
|
234
|
-
else if (typename === "ProjectV2ItemFieldNumberValue")
|
|
235
|
-
fValue = String(fv.number);
|
|
236
|
-
else if (typename === "ProjectV2ItemFieldDateValue")
|
|
237
|
-
fValue = fv.date;
|
|
238
|
-
else if (typename === "ProjectV2ItemFieldIterationValue")
|
|
239
|
-
fValue = fv.title;
|
|
240
|
-
if (fValue !== null) {
|
|
241
|
-
projectFieldVals.push({ name: fvFieldName, value: fValue, project: projectTitle });
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
if (include_project_fields) {
|
|
246
|
-
entry.project_fields = projectFieldVals;
|
|
247
|
-
}
|
|
248
|
-
}
|
|
249
|
-
if (hasFilterFields) {
|
|
250
|
-
const allFields = new Map();
|
|
251
|
-
if (orgFieldVals) {
|
|
252
|
-
for (const f of orgFieldVals) {
|
|
253
|
-
if (!allFields.has(f.name))
|
|
254
|
-
allFields.set(f.name, []);
|
|
255
|
-
allFields.get(f.name).push({ value: f.value, source: "org_field" });
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
if (projectFieldVals) {
|
|
259
|
-
for (const f of projectFieldVals) {
|
|
260
|
-
if (!allFields.has(f.name))
|
|
261
|
-
allFields.set(f.name, []);
|
|
262
|
-
allFields.get(f.name).push({ value: f.value, source: `project_board:${f.project}` });
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
let allMatch = true;
|
|
266
|
-
const matchedFields = [];
|
|
267
|
-
for (const filter of filter_fields) {
|
|
268
|
-
const entries = allFields.get(filter.field_name);
|
|
269
|
-
if (!entries) {
|
|
270
|
-
allMatch = false;
|
|
271
|
-
break;
|
|
272
|
-
}
|
|
273
|
-
const match = entries.find((e) => e.value === filter.field_value);
|
|
274
|
-
if (!match) {
|
|
275
|
-
allMatch = false;
|
|
276
|
-
break;
|
|
277
|
-
}
|
|
278
|
-
matchedFields.push({ source: match.source, field: filter.field_name, value: match.value });
|
|
279
|
-
}
|
|
280
|
-
if (!allMatch)
|
|
281
|
-
continue;
|
|
282
|
-
entry.matched_fields = matchedFields;
|
|
283
|
-
}
|
|
284
|
-
issues.push(entry);
|
|
285
|
-
}
|
|
286
|
-
return {
|
|
287
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
288
|
-
total: issues.length,
|
|
289
|
-
page,
|
|
290
|
-
per_page,
|
|
291
|
-
issues,
|
|
292
|
-
}, null, 2) }],
|
|
293
|
-
};
|
|
294
|
-
}
|
|
295
|
-
catch (err) {
|
|
296
|
-
return handleGitHubError(err);
|
|
297
|
-
}
|
|
298
|
-
});
|
|
299
|
-
}
|
|
300
|
-
export function registerIssueSearch(server) {
|
|
301
|
-
server.registerTool("issue_search", {
|
|
302
|
-
description: "Search for issues in GitHub repositories using GitHub's search syntax.",
|
|
303
|
-
inputSchema: z.object({
|
|
304
|
-
query: z.string().min(1).describe("Search query using GitHub issues search syntax, e.g., 'label:bug repo:owner/repo'"),
|
|
305
|
-
page: PaginationSchema.shape.page,
|
|
306
|
-
per_page: PaginationSchema.shape.per_page,
|
|
307
|
-
}),
|
|
308
|
-
}, async ({ query, page, per_page }) => {
|
|
309
|
-
try {
|
|
310
|
-
const octokit = createClient();
|
|
311
|
-
const { data } = await octokit.rest.search.issuesAndPullRequests({ q: query, page, per_page });
|
|
312
|
-
return {
|
|
313
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
314
|
-
total_count: data.total_count,
|
|
315
|
-
items: data.items.map((i) => ({
|
|
316
|
-
number: i.number,
|
|
317
|
-
title: i.title,
|
|
318
|
-
state: i.state,
|
|
319
|
-
html_url: i.html_url,
|
|
320
|
-
repository: i.repository?.full_name ?? null,
|
|
321
|
-
created_at: i.created_at,
|
|
322
|
-
updated_at: i.updated_at,
|
|
323
|
-
user: i.user?.login,
|
|
324
|
-
labels: i.labels?.map((l) => (typeof l === "string" ? l : l.name)) ?? [],
|
|
325
|
-
})),
|
|
326
|
-
}, null, 2) }],
|
|
327
|
-
};
|
|
328
|
-
}
|
|
329
|
-
catch (err) {
|
|
330
|
-
return handleGitHubError(err);
|
|
331
|
-
}
|
|
332
|
-
});
|
|
333
|
-
}
|
|
334
|
-
export function registerIssueAddComment(server) {
|
|
335
|
-
server.registerTool("issue_add_comment", {
|
|
336
|
-
description: "Add a comment to an existing issue in a GitHub repository.",
|
|
337
|
-
inputSchema: OwnerRepoSchema.extend({
|
|
338
|
-
issue_number: IssueNumberSchema,
|
|
339
|
-
body: z.string().min(1).describe("Comment content"),
|
|
340
|
-
}),
|
|
341
|
-
}, async ({ owner, repo, issue_number, body }) => {
|
|
342
|
-
try {
|
|
343
|
-
const pinErr = validatePinnedOwnerRepo(owner, repo);
|
|
344
|
-
if (pinErr)
|
|
345
|
-
return pinErr;
|
|
346
|
-
const octokit = createClient();
|
|
347
|
-
const { data } = await octokit.rest.issues.createComment({ owner, repo, issue_number, body });
|
|
348
|
-
return {
|
|
349
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
350
|
-
id: data.id,
|
|
351
|
-
html_url: data.html_url,
|
|
352
|
-
created_at: data.created_at,
|
|
353
|
-
}, null, 2) }],
|
|
354
|
-
};
|
|
355
|
-
}
|
|
356
|
-
catch (err) {
|
|
357
|
-
return handleGitHubError(err);
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
}
|
|
361
|
-
export function registerSubIssueWrite(server) {
|
|
362
|
-
server.registerTool("sub_issue_write", {
|
|
363
|
-
description: "Add or remove a sub-issue from a parent issue in a GitHub repository. Method 'add' adds sub_issue_number as a sub-issue of issue_number. Method 'remove' removes the sub-issue relationship.",
|
|
364
|
-
inputSchema: OwnerRepoSchema.extend({
|
|
365
|
-
method: z.enum(["add", "remove"]).describe("Operation: add or remove a sub-issue"),
|
|
366
|
-
issue_number: IssueNumberSchema,
|
|
367
|
-
sub_issue_number: z.number().int().positive().describe("Sub-issue issue number"),
|
|
368
|
-
}),
|
|
369
|
-
}, async ({ owner, repo, method, issue_number, sub_issue_number }) => {
|
|
370
|
-
try {
|
|
371
|
-
const pinErr = validatePinnedOwnerRepo(owner, repo);
|
|
372
|
-
if (pinErr)
|
|
373
|
-
return pinErr;
|
|
374
|
-
const idQuery = `
|
|
375
|
-
query($owner: String!, $repo: String!, $parentNumber: Int!, $subNumber: Int!) {
|
|
376
|
-
parent: repository(owner: $owner, name: $repo) {
|
|
377
|
-
issue(number: $parentNumber) { id }
|
|
378
|
-
}
|
|
379
|
-
child: repository(owner: $owner, name: $repo) {
|
|
380
|
-
issue(number: $subNumber) { id }
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
`;
|
|
384
|
-
const idData = await gql(idQuery, { owner, repo, parentNumber: issue_number, subNumber: sub_issue_number });
|
|
385
|
-
const parentId = idData.parent?.issue?.id;
|
|
386
|
-
const childId = idData.child?.issue?.id;
|
|
387
|
-
if (!parentId || !childId) {
|
|
388
|
-
return {
|
|
389
|
-
content: [{ type: "text", text: `Could not resolve one or both issues. Parent: ${issue_number}, Sub: ${sub_issue_number}.` }],
|
|
390
|
-
isError: true,
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
if (method === "add") {
|
|
394
|
-
const mutation = `
|
|
395
|
-
mutation($parentId: ID!, $subIssueId: ID!) {
|
|
396
|
-
addSubIssue(input: { issueId: $parentId, subIssueId: $subIssueId }) {
|
|
397
|
-
subIssue { number title state }
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
`;
|
|
401
|
-
const result = await gql(mutation, { parentId, subIssueId: childId });
|
|
402
|
-
return {
|
|
403
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
404
|
-
success: true,
|
|
405
|
-
sub_issue: result.addSubIssue?.subIssue,
|
|
406
|
-
}, null, 2) }],
|
|
407
|
-
};
|
|
408
|
-
}
|
|
409
|
-
else {
|
|
410
|
-
const mutation = `
|
|
411
|
-
mutation($parentId: ID!, $subIssueId: ID!) {
|
|
412
|
-
removeSubIssue(input: { issueId: $parentId, subIssueId: $subIssueId }) {
|
|
413
|
-
issue { number title }
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
`;
|
|
417
|
-
const result = await gql(mutation, { parentId, subIssueId: childId });
|
|
418
|
-
return {
|
|
419
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
420
|
-
success: true,
|
|
421
|
-
parent_issue: result.removeSubIssue?.issue,
|
|
422
|
-
}, null, 2) }],
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
}
|
|
426
|
-
catch (err) {
|
|
427
|
-
return handleGitHubError(err);
|
|
428
|
-
}
|
|
429
|
-
});
|
|
430
|
-
}
|
|
431
|
-
export function registerIssueCreate(server) {
|
|
432
|
-
server.registerTool("issue_create", {
|
|
433
|
-
description: "Create a new issue in a GitHub repository.",
|
|
434
|
-
inputSchema: OwnerRepoSchema.extend({
|
|
435
|
-
title: z.string().min(1).describe("Issue title"),
|
|
436
|
-
body: z.string().optional().describe("Issue body content"),
|
|
437
|
-
labels: z.array(z.string()).optional().describe("Labels to apply to the issue"),
|
|
438
|
-
assignees: z.array(z.string()).optional().describe("Usernames to assign to the issue"),
|
|
439
|
-
}),
|
|
440
|
-
}, async ({ owner, repo, title, body, labels, assignees }) => {
|
|
441
|
-
try {
|
|
442
|
-
const pinErr = validatePinnedOwnerRepo(owner, repo);
|
|
443
|
-
if (pinErr)
|
|
444
|
-
return pinErr;
|
|
445
|
-
const octokit = createClient();
|
|
446
|
-
const { data } = await octokit.rest.issues.create({ owner, repo, title, body, labels, assignees });
|
|
447
|
-
return {
|
|
448
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
449
|
-
number: data.number,
|
|
450
|
-
title: data.title,
|
|
451
|
-
html_url: data.html_url,
|
|
452
|
-
state: data.state,
|
|
453
|
-
created_at: data.created_at,
|
|
454
|
-
}, null, 2) }],
|
|
455
|
-
};
|
|
456
|
-
}
|
|
457
|
-
catch (err) {
|
|
458
|
-
return handleGitHubError(err);
|
|
459
|
-
}
|
|
460
|
-
});
|
|
461
|
-
}
|
|
462
|
-
export function registerIssueClose(server) {
|
|
463
|
-
server.registerTool("issue_close", {
|
|
464
|
-
description: "Close an issue in a GitHub repository.",
|
|
465
|
-
inputSchema: OwnerRepoSchema.extend({
|
|
466
|
-
issue_number: IssueNumberSchema,
|
|
467
|
-
comment: z.string().optional().describe("Optional closing comment to leave on the issue"),
|
|
468
|
-
}),
|
|
469
|
-
}, async ({ owner, repo, issue_number, comment }) => {
|
|
470
|
-
try {
|
|
471
|
-
const pinErr = validatePinnedOwnerRepo(owner, repo);
|
|
472
|
-
if (pinErr)
|
|
473
|
-
return pinErr;
|
|
474
|
-
const octokit = createClient();
|
|
475
|
-
const { data } = await octokit.rest.issues.update({ owner, repo, issue_number, state: "closed" });
|
|
476
|
-
let commentPosted = false;
|
|
477
|
-
if (comment) {
|
|
478
|
-
await octokit.rest.issues.createComment({ owner, repo, issue_number, body: comment });
|
|
479
|
-
commentPosted = true;
|
|
480
|
-
}
|
|
481
|
-
return {
|
|
482
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
483
|
-
number: data.number,
|
|
484
|
-
state: data.state,
|
|
485
|
-
closed_at: data.closed_at,
|
|
486
|
-
comment_posted: commentPosted,
|
|
487
|
-
}, null, 2) }],
|
|
488
|
-
};
|
|
489
|
-
}
|
|
490
|
-
catch (err) {
|
|
491
|
-
return handleGitHubError(err);
|
|
492
|
-
}
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
|
-
export function registerIssueReopen(server) {
|
|
496
|
-
server.registerTool("issue_reopen", {
|
|
497
|
-
description: "Reopen a closed issue in a GitHub repository.",
|
|
498
|
-
inputSchema: OwnerRepoSchema.extend({
|
|
499
|
-
issue_number: IssueNumberSchema,
|
|
500
|
-
}),
|
|
501
|
-
}, async ({ owner, repo, issue_number }) => {
|
|
502
|
-
try {
|
|
503
|
-
const pinErr = validatePinnedOwnerRepo(owner, repo);
|
|
504
|
-
if (pinErr)
|
|
505
|
-
return pinErr;
|
|
506
|
-
const octokit = createClient();
|
|
507
|
-
const { data } = await octokit.rest.issues.update({ owner, repo, issue_number, state: "open" });
|
|
508
|
-
return {
|
|
509
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
510
|
-
number: data.number,
|
|
511
|
-
state: data.state,
|
|
512
|
-
}, null, 2) }],
|
|
513
|
-
};
|
|
514
|
-
}
|
|
515
|
-
catch (err) {
|
|
516
|
-
return handleGitHubError(err);
|
|
517
|
-
}
|
|
518
|
-
});
|
|
519
|
-
}
|
|
520
|
-
export function registerIssueUpdate(server) {
|
|
521
|
-
server.registerTool("issue_update", {
|
|
522
|
-
description: "Update an issue's title, body, or state in a GitHub repository.",
|
|
523
|
-
inputSchema: OwnerRepoSchema.extend({
|
|
524
|
-
issue_number: IssueNumberSchema,
|
|
525
|
-
title: z.string().optional().describe("New title for the issue"),
|
|
526
|
-
body: z.string().optional().describe("New body content for the issue"),
|
|
527
|
-
state: z.enum(["open", "closed"]).optional().describe("New state for the issue"),
|
|
528
|
-
}),
|
|
529
|
-
}, async ({ owner, repo, issue_number, title, body, state }) => {
|
|
530
|
-
try {
|
|
531
|
-
const pinErr = validatePinnedOwnerRepo(owner, repo);
|
|
532
|
-
if (pinErr)
|
|
533
|
-
return pinErr;
|
|
534
|
-
const octokit = createClient();
|
|
535
|
-
const { data } = await octokit.rest.issues.update({ owner, repo, issue_number, title, body, state });
|
|
536
|
-
return {
|
|
537
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
538
|
-
number: data.number,
|
|
539
|
-
title: data.title,
|
|
540
|
-
body: data.body,
|
|
541
|
-
state: data.state,
|
|
542
|
-
html_url: data.html_url,
|
|
543
|
-
updated_at: data.updated_at,
|
|
544
|
-
}, null, 2) }],
|
|
545
|
-
};
|
|
546
|
-
}
|
|
547
|
-
catch (err) {
|
|
548
|
-
return handleGitHubError(err);
|
|
549
|
-
}
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
export function registerIssueAddLabel(server) {
|
|
553
|
-
server.registerTool("issue_add_label", {
|
|
554
|
-
description: "Add labels to an issue in a GitHub repository.",
|
|
555
|
-
inputSchema: OwnerRepoSchema.extend({
|
|
556
|
-
issue_number: IssueNumberSchema,
|
|
557
|
-
labels: z.array(z.string()).min(1).describe("Label names to add to the issue"),
|
|
558
|
-
}),
|
|
559
|
-
}, async ({ owner, repo, issue_number, labels }) => {
|
|
560
|
-
try {
|
|
561
|
-
const pinErr = validatePinnedOwnerRepo(owner, repo);
|
|
562
|
-
if (pinErr)
|
|
563
|
-
return pinErr;
|
|
564
|
-
const octokit = createClient();
|
|
565
|
-
const { data } = await octokit.rest.issues.addLabels({ owner, repo, issue_number, labels });
|
|
566
|
-
return {
|
|
567
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
568
|
-
success: true,
|
|
569
|
-
labels_added: data.map((l) => l.name),
|
|
570
|
-
}, null, 2) }],
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
catch (err) {
|
|
574
|
-
return handleGitHubError(err);
|
|
575
|
-
}
|
|
576
|
-
});
|
|
577
|
-
}
|
|
578
|
-
export function registerIssueRemoveLabel(server) {
|
|
579
|
-
server.registerTool("issue_remove_label", {
|
|
580
|
-
description: "Remove labels from an issue in a GitHub repository.",
|
|
581
|
-
inputSchema: OwnerRepoSchema.extend({
|
|
582
|
-
issue_number: IssueNumberSchema,
|
|
583
|
-
labels: z.array(z.string()).min(1).describe("Label names to remove from the issue"),
|
|
584
|
-
}),
|
|
585
|
-
}, async ({ owner, repo, issue_number, labels }) => {
|
|
586
|
-
try {
|
|
587
|
-
const pinErr = validatePinnedOwnerRepo(owner, repo);
|
|
588
|
-
if (pinErr)
|
|
589
|
-
return pinErr;
|
|
590
|
-
const octokit = createClient();
|
|
591
|
-
const results = [];
|
|
592
|
-
for (const label of labels) {
|
|
593
|
-
await octokit.rest.issues.removeLabel({ owner, repo, issue_number, name: label });
|
|
594
|
-
results.push(label);
|
|
595
|
-
}
|
|
596
|
-
return {
|
|
597
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
598
|
-
success: true,
|
|
599
|
-
labels_removed: results,
|
|
600
|
-
}, null, 2) }],
|
|
601
|
-
};
|
|
602
|
-
}
|
|
603
|
-
catch (err) {
|
|
604
|
-
return handleGitHubError(err);
|
|
605
|
-
}
|
|
606
|
-
});
|
|
607
|
-
}
|
package/dist/tools/labels.d.ts
DELETED