issue-scribe-mcp 1.1.0 → 1.3.0

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/src/index.ts CHANGED
@@ -2,161 +2,15 @@
2
2
 
3
3
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
4
4
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
5
- import {
6
- CallToolRequestSchema,
7
- ListToolsRequestSchema,
8
- } from "@modelcontextprotocol/sdk/types.js";
9
- import { Octokit } from "@octokit/rest";
10
- import { z } from "zod";
5
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
11
6
 
12
- const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
13
-
14
- // Reaction type mapping: user-friendly names to GitHub API format
15
- const REACTION_MAP: Record<string, string> = {
16
- "thumbs_up": "+1",
17
- "thumbs_down": "-1",
18
- "laugh": "laugh",
19
- "confused": "confused",
20
- "heart": "heart",
21
- "hooray": "hooray",
22
- "rocket": "rocket",
23
- "eyes": "eyes",
24
- };
25
-
26
- if (!GITHUB_TOKEN) {
27
- console.error("Error: GITHUB_TOKEN environment variable is required");
28
- process.exit(1);
29
- }
30
-
31
- const octokit = new Octokit({ auth: GITHUB_TOKEN });
32
-
33
- const GetIssueSchema = z.object({
34
- owner: z.string(),
35
- repo: z.string(),
36
- issue_number: z.number(),
37
- });
38
-
39
- const GetPRSchema = z.object({
40
- owner: z.string(),
41
- repo: z.string(),
42
- pull_number: z.number(),
43
- });
44
-
45
- const CreateIssueSchema = z.object({
46
- owner: z.string(),
47
- repo: z.string(),
48
- title: z.string(),
49
- body: z.string().optional(),
50
- labels: z.array(z.string()).optional(),
51
- assignees: z.array(z.string()).optional(),
52
- });
53
-
54
- const UpdateIssueSchema = z.object({
55
- owner: z.string(),
56
- repo: z.string(),
57
- issue_number: z.number(),
58
- title: z.string().optional(),
59
- body: z.string().optional(),
60
- state: z.enum(["open", "closed"]).optional(),
61
- labels: z.array(z.string()).optional(),
62
- assignees: z.array(z.string()).optional(),
63
- });
64
-
65
- const CreatePRSchema = z.object({
66
- owner: z.string(),
67
- repo: z.string(),
68
- title: z.string(),
69
- body: z.string().optional(),
70
- head: z.string(), // branch to merge FROM (e.g., "feature-branch")
71
- base: z.string(), // branch to merge INTO (e.g., "main")
72
- draft: z.boolean().optional(),
73
- maintainer_can_modify: z.boolean().optional(),
74
- });
75
-
76
- const AddCommentSchema = z.object({
77
- owner: z.string(),
78
- repo: z.string(),
79
- issue_number: z.number(), // works for both issues and PRs
80
- body: z.string(),
81
- });
82
-
83
- const UpdateCommentSchema = z.object({
84
- owner: z.string(),
85
- repo: z.string(),
86
- comment_id: z.number(),
87
- body: z.string(),
88
- });
89
-
90
- const DeleteCommentSchema = z.object({
91
- owner: z.string(),
92
- repo: z.string(),
93
- comment_id: z.number(),
94
- });
95
-
96
- const AddReactionSchema = z.object({
97
- owner: z.string(),
98
- repo: z.string(),
99
- comment_id: z.number().optional(),
100
- issue_number: z.number().optional(),
101
- reaction: z.enum(["thumbs_up", "thumbs_down", "laugh", "confused", "heart", "hooray", "rocket", "eyes"]),
102
- }).refine(data => data.comment_id || data.issue_number, {
103
- message: "Either comment_id or issue_number must be provided",
104
- });
105
-
106
- const SearchIssuesSchema = z.object({
107
- owner: z.string(),
108
- repo: z.string(),
109
- query: z.string().optional(),
110
- state: z.enum(["open", "closed", "all"]).optional(),
111
- labels: z.array(z.string()).optional(),
112
- sort: z.enum(["created", "updated", "comments"]).optional(),
113
- direction: z.enum(["asc", "desc"]).optional(),
114
- per_page: z.number().max(100).optional(),
115
- });
116
-
117
- const SearchPRsSchema = z.object({
118
- owner: z.string(),
119
- repo: z.string(),
120
- query: z.string().optional(),
121
- state: z.enum(["open", "closed", "all"]).optional(),
122
- sort: z.enum(["created", "updated", "popularity", "long-running"]).optional(),
123
- direction: z.enum(["asc", "desc"]).optional(),
124
- per_page: z.number().max(100).optional(),
125
- });
126
-
127
- const ListRecentIssuesSchema = z.object({
128
- owner: z.string(),
129
- repo: z.string(),
130
- state: z.enum(["open", "closed", "all"]).optional(),
131
- sort: z.enum(["created", "updated"]).optional(),
132
- per_page: z.number().max(100).optional(),
133
- });
134
-
135
- const MergePRSchema = z.object({
136
- owner: z.string(),
137
- repo: z.string(),
138
- pull_number: z.number(),
139
- merge_method: z.enum(["merge", "squash", "rebase"]).optional(),
140
- commit_title: z.string().optional(),
141
- commit_message: z.string().optional(),
142
- });
143
-
144
- const GetPRDiffSchema = z.object({
145
- owner: z.string(),
146
- repo: z.string(),
147
- pull_number: z.number(),
148
- });
149
-
150
- const GetPRFilesSchema = z.object({
151
- owner: z.string(),
152
- repo: z.string(),
153
- pull_number: z.number(),
154
- });
7
+ import { dispatchTool, toolDefinitions } from "./tools/index.js";
8
+ import { SERVER_VERSION } from "./lib/version.js";
155
9
 
156
10
  const server = new Server(
157
11
  {
158
12
  name: "issue-scribe-mcp",
159
- version: "1.0.0",
13
+ version: SERVER_VERSION,
160
14
  },
161
15
  {
162
16
  capabilities: {
@@ -167,1124 +21,19 @@ const server = new Server(
167
21
 
168
22
  server.setRequestHandler(ListToolsRequestSchema, async () => {
169
23
  return {
170
- tools: [
171
- {
172
- name: "github_get_issue_context",
173
- description: "Get GitHub Issue context including title, body, comments, and metadata",
174
- inputSchema: {
175
- type: "object",
176
- properties: {
177
- owner: { type: "string", description: "Repository owner" },
178
- repo: { type: "string", description: "Repository name" },
179
- issue_number: { type: "number", description: "Issue number" },
180
- },
181
- required: ["owner", "repo", "issue_number"],
182
- },
183
- },
184
- {
185
- name: "github_get_pr_context",
186
- description: "Get GitHub Pull Request context including title, body, comments, commits, and metadata",
187
- inputSchema: {
188
- type: "object",
189
- properties: {
190
- owner: { type: "string", description: "Repository owner" },
191
- repo: { type: "string", description: "Repository name" },
192
- pull_number: { type: "number", description: "Pull request number" },
193
- },
194
- required: ["owner", "repo", "pull_number"],
195
- },
196
- },
197
- {
198
- name: "github_create_issue",
199
- description: "Create a new GitHub Issue",
200
- inputSchema: {
201
- type: "object",
202
- properties: {
203
- owner: { type: "string", description: "Repository owner" },
204
- repo: { type: "string", description: "Repository name" },
205
- title: { type: "string", description: "Issue title" },
206
- body: { type: "string", description: "Issue body (optional)" },
207
- labels: { type: "array", items: { type: "string" }, description: "Labels to add (optional)" },
208
- assignees: { type: "array", items: { type: "string" }, description: "Assignees (optional)" },
209
- },
210
- required: ["owner", "repo", "title"],
211
- },
212
- },
213
- {
214
- name: "github_update_issue",
215
- description: "Update an existing GitHub Issue",
216
- inputSchema: {
217
- type: "object",
218
- properties: {
219
- owner: { type: "string", description: "Repository owner" },
220
- repo: { type: "string", description: "Repository name" },
221
- issue_number: { type: "number", description: "Issue number" },
222
- title: { type: "string", description: "New title (optional)" },
223
- body: { type: "string", description: "New body (optional)" },
224
- state: { type: "string", enum: ["open", "closed"], description: "Issue state (optional)" },
225
- labels: { type: "array", items: { type: "string" }, description: "New labels (optional)" },
226
- assignees: { type: "array", items: { type: "string" }, description: "New assignees (optional)" },
227
- },
228
- required: ["owner", "repo", "issue_number"],
229
- },
230
- },
231
- {
232
- name: "github_create_pr",
233
- description: "Create a new GitHub Pull Request",
234
- inputSchema: {
235
- type: "object",
236
- properties: {
237
- owner: { type: "string", description: "Repository owner" },
238
- repo: { type: "string", description: "Repository name" },
239
- title: { type: "string", description: "PR title" },
240
- body: { type: "string", description: "PR body/description (optional)" },
241
- head: { type: "string", description: "Branch to merge FROM (e.g., 'feature-branch')" },
242
- base: { type: "string", description: "Branch to merge INTO (e.g., 'main')" },
243
- draft: { type: "boolean", description: "Create as draft PR (optional)" },
244
- maintainer_can_modify: { type: "boolean", description: "Allow maintainer edits (optional)" },
245
- },
246
- required: ["owner", "repo", "title", "head", "base"],
247
- },
248
- },
249
- {
250
- name: "github_add_comment",
251
- description: "Add a comment to a GitHub Issue or Pull Request",
252
- inputSchema: {
253
- type: "object",
254
- properties: {
255
- owner: { type: "string", description: "Repository owner" },
256
- repo: { type: "string", description: "Repository name" },
257
- issue_number: { type: "number", description: "Issue or PR number" },
258
- body: { type: "string", description: "Comment body text" },
259
- },
260
- required: ["owner", "repo", "issue_number", "body"],
261
- },
262
- },
263
- {
264
- name: "github_update_comment",
265
- description: "Update an existing comment on a GitHub Issue or Pull Request",
266
- inputSchema: {
267
- type: "object",
268
- properties: {
269
- owner: { type: "string", description: "Repository owner" },
270
- repo: { type: "string", description: "Repository name" },
271
- comment_id: { type: "number", description: "Comment ID to update" },
272
- body: { type: "string", description: "New comment body text" },
273
- },
274
- required: ["owner", "repo", "comment_id", "body"],
275
- },
276
- },
277
- {
278
- name: "github_delete_comment",
279
- description: "Delete a comment from a GitHub Issue or Pull Request",
280
- inputSchema: {
281
- type: "object",
282
- properties: {
283
- owner: { type: "string", description: "Repository owner" },
284
- repo: { type: "string", description: "Repository name" },
285
- comment_id: { type: "number", description: "Comment ID to delete" },
286
- },
287
- required: ["owner", "repo", "comment_id"],
288
- },
289
- },
290
- {
291
- name: "github_add_reaction",
292
- description: "Add a reaction (emoji) to a comment or an issue/PR directly. Provide either comment_id OR issue_number.",
293
- inputSchema: {
294
- type: "object",
295
- properties: {
296
- owner: { type: "string", description: "Repository owner" },
297
- repo: { type: "string", description: "Repository name" },
298
- comment_id: { type: "number", description: "Comment ID to react to (optional if issue_number is provided)" },
299
- issue_number: { type: "number", description: "Issue/PR number to react to (optional if comment_id is provided)" },
300
- reaction: {
301
- type: "string",
302
- enum: ["thumbs_up", "thumbs_down", "laugh", "confused", "heart", "hooray", "rocket", "eyes"],
303
- description: "Reaction type: thumbs_up 👍, thumbs_down 👎, laugh 😄, confused 😕, heart ❤️, hooray 🎉, rocket 🚀, eyes 👀"
304
- },
305
- },
306
- required: ["owner", "repo", "reaction"],
307
- },
308
- },
309
- {
310
- name: "github_search_issues",
311
- description: "Search for issues in a repository with advanced filters",
312
- inputSchema: {
313
- type: "object",
314
- properties: {
315
- owner: { type: "string", description: "Repository owner" },
316
- repo: { type: "string", description: "Repository name" },
317
- query: { type: "string", description: "Search query (optional)" },
318
- state: { type: "string", enum: ["open", "closed", "all"], description: "Issue state (optional)" },
319
- labels: { type: "array", items: { type: "string" }, description: "Filter by labels (optional)" },
320
- sort: { type: "string", enum: ["created", "updated", "comments"], description: "Sort by (optional)" },
321
- direction: { type: "string", enum: ["asc", "desc"], description: "Sort direction (optional)" },
322
- per_page: { type: "number", description: "Results per page, max 100 (optional)" },
323
- },
324
- required: ["owner", "repo"],
325
- },
326
- },
327
- {
328
- name: "github_search_prs",
329
- description: "Search for pull requests in a repository with advanced filters",
330
- inputSchema: {
331
- type: "object",
332
- properties: {
333
- owner: { type: "string", description: "Repository owner" },
334
- repo: { type: "string", description: "Repository name" },
335
- query: { type: "string", description: "Search query (optional)" },
336
- state: { type: "string", enum: ["open", "closed", "all"], description: "PR state (optional)" },
337
- sort: { type: "string", enum: ["created", "updated", "popularity", "long-running"], description: "Sort by (optional)" },
338
- direction: { type: "string", enum: ["asc", "desc"], description: "Sort direction (optional)" },
339
- per_page: { type: "number", description: "Results per page, max 100 (optional)" },
340
- },
341
- required: ["owner", "repo"],
342
- },
343
- },
344
- {
345
- name: "github_list_recent_issues",
346
- description: "List recent issues in a repository",
347
- inputSchema: {
348
- type: "object",
349
- properties: {
350
- owner: { type: "string", description: "Repository owner" },
351
- repo: { type: "string", description: "Repository name" },
352
- state: { type: "string", enum: ["open", "closed", "all"], description: "Issue state (optional, default: open)" },
353
- sort: { type: "string", enum: ["created", "updated"], description: "Sort by (optional, default: created)" },
354
- per_page: { type: "number", description: "Results per page, max 100 (optional, default: 30)" },
355
- },
356
- required: ["owner", "repo"],
357
- },
358
- },
359
- {
360
- name: "github_merge_pr",
361
- description: "Merge a pull request",
362
- inputSchema: {
363
- type: "object",
364
- properties: {
365
- owner: { type: "string", description: "Repository owner" },
366
- repo: { type: "string", description: "Repository name" },
367
- pull_number: { type: "number", description: "PR number to merge" },
368
- merge_method: { type: "string", enum: ["merge", "squash", "rebase"], description: "Merge method (optional, default: merge)" },
369
- commit_title: { type: "string", description: "Custom commit title (optional)" },
370
- commit_message: { type: "string", description: "Custom commit message (optional)" },
371
- },
372
- required: ["owner", "repo", "pull_number"],
373
- },
374
- },
375
- {
376
- name: "github_get_pr_diff",
377
- description: "Get the full diff of a pull request",
378
- inputSchema: {
379
- type: "object",
380
- properties: {
381
- owner: { type: "string", description: "Repository owner" },
382
- repo: { type: "string", description: "Repository name" },
383
- pull_number: { type: "number", description: "PR number" },
384
- },
385
- required: ["owner", "repo", "pull_number"],
386
- },
387
- },
388
- {
389
- name: "github_get_pr_files",
390
- description: "Get list of files changed in a pull request with details",
391
- inputSchema: {
392
- type: "object",
393
- properties: {
394
- owner: { type: "string", description: "Repository owner" },
395
- repo: { type: "string", description: "Repository name" },
396
- pull_number: { type: "number", description: "PR number" },
397
- },
398
- required: ["owner", "repo", "pull_number"],
399
- },
400
- },
401
- ],
24
+ tools: toolDefinitions,
402
25
  };
403
26
  });
404
27
 
405
28
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
406
29
  const { name, arguments: args } = request.params;
407
-
408
- if (name === "github_get_issue_context") {
409
- try {
410
- const { owner, repo, issue_number } = GetIssueSchema.parse(args);
411
-
412
- const [issue, comments] = await Promise.all([
413
- octokit.rest.issues.get({ owner, repo, issue_number }),
414
- octokit.rest.issues.listComments({ owner, repo, issue_number }),
415
- ]);
416
-
417
- return {
418
- content: [
419
- {
420
- type: "text",
421
- text: JSON.stringify(
422
- {
423
- issue: {
424
- number: issue.data.number,
425
- title: issue.data.title,
426
- body: issue.data.body,
427
- state: issue.data.state,
428
- user: issue.data.user?.login,
429
- created_at: issue.data.created_at,
430
- updated_at: issue.data.updated_at,
431
- labels: issue.data.labels.map((l: any) =>
432
- typeof l === "string" ? l : l.name
433
- ),
434
- },
435
- comments: comments.data.map((c) => ({
436
- id: c.id,
437
- user: c.user?.login,
438
- body: c.body,
439
- created_at: c.created_at,
440
- html_url: c.html_url,
441
- })),
442
- },
443
- null,
444
- 2
445
- ),
446
- },
447
- ],
448
- };
449
- } catch (error: any) {
450
- const issueNum = args && typeof args === 'object' && 'issue_number' in args ? args.issue_number : 'unknown';
451
- const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
452
- const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
453
- return {
454
- content: [
455
- {
456
- type: "text",
457
- text: JSON.stringify({
458
- error: error.message,
459
- status: error.status,
460
- detail: `Failed to fetch issue #${issueNum} from ${owner}/${repo}`,
461
- }, null, 2),
462
- },
463
- ],
464
- isError: true,
465
- };
466
- }
467
- }
468
-
469
- if (name === "github_get_pr_context") {
470
- try {
471
- const { owner, repo, pull_number } = GetPRSchema.parse(args);
472
-
473
- const [pr, comments, commits] = await Promise.all([
474
- octokit.rest.pulls.get({ owner, repo, pull_number }),
475
- octokit.rest.issues.listComments({ owner, repo, issue_number: pull_number }),
476
- octokit.rest.pulls.listCommits({ owner, repo, pull_number }),
477
- ]);
478
-
479
- return {
480
- content: [
481
- {
482
- type: "text",
483
- text: JSON.stringify(
484
- {
485
- pull_request: {
486
- number: pr.data.number,
487
- title: pr.data.title,
488
- body: pr.data.body,
489
- state: pr.data.state,
490
- user: pr.data.user?.login,
491
- created_at: pr.data.created_at,
492
- updated_at: pr.data.updated_at,
493
- merged_at: pr.data.merged_at,
494
- base: pr.data.base.ref,
495
- head: pr.data.head.ref,
496
- labels: pr.data.labels.map((l: any) =>
497
- typeof l === "string" ? l : l.name
498
- ),
499
- },
500
- comments: comments.data.map((c) => ({
501
- id: c.id,
502
- user: c.user?.login,
503
- body: c.body,
504
- created_at: c.created_at,
505
- html_url: c.html_url,
506
- })),
507
- commits: commits.data.map((c) => ({
508
- sha: c.sha,
509
- message: c.commit.message,
510
- author: c.commit.author?.name,
511
- date: c.commit.author?.date,
512
- })),
513
- },
514
- null,
515
- 2
516
- ),
517
- },
518
- ],
519
- };
520
- } catch (error: any) {
521
- const pullNum = args && typeof args === 'object' && 'pull_number' in args ? args.pull_number : 'unknown';
522
- const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
523
- const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
524
- return {
525
- content: [
526
- {
527
- type: "text",
528
- text: JSON.stringify({
529
- error: error.message,
530
- status: error.status,
531
- detail: `Failed to fetch PR #${pullNum} from ${owner}/${repo}`,
532
- }, null, 2),
533
- },
534
- ],
535
- isError: true,
536
- };
537
- }
538
- }
539
-
540
- if (name === "github_create_issue") {
541
- try {
542
- const { owner, repo, title, body, labels, assignees } = CreateIssueSchema.parse(args);
543
-
544
- const issue = await octokit.rest.issues.create({
545
- owner,
546
- repo,
547
- title,
548
- body,
549
- labels,
550
- assignees,
551
- });
552
-
553
- return {
554
- content: [
555
- {
556
- type: "text",
557
- text: JSON.stringify(
558
- {
559
- success: true,
560
- issue: {
561
- number: issue.data.number,
562
- title: issue.data.title,
563
- state: issue.data.state,
564
- html_url: issue.data.html_url,
565
- created_at: issue.data.created_at,
566
- },
567
- message: `Issue #${issue.data.number} created successfully`,
568
- },
569
- null,
570
- 2
571
- ),
572
- },
573
- ],
574
- };
575
- } catch (error: any) {
576
- const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
577
- const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
578
- const title = args && typeof args === 'object' && 'title' in args ? args.title : 'unknown';
579
- return {
580
- content: [
581
- {
582
- type: "text",
583
- text: JSON.stringify({
584
- error: error.message,
585
- status: error.status,
586
- detail: `Failed to create issue "${title}" in ${owner}/${repo}`,
587
- }, null, 2),
588
- },
589
- ],
590
- isError: true,
591
- };
592
- }
593
- }
594
-
595
- if (name === "github_update_issue") {
596
- try {
597
- const { owner, repo, issue_number, title, body, state, labels, assignees } = UpdateIssueSchema.parse(args);
598
-
599
- const issue = await octokit.rest.issues.update({
600
- owner,
601
- repo,
602
- issue_number,
603
- title,
604
- body,
605
- state,
606
- labels,
607
- assignees,
608
- });
609
-
610
- return {
611
- content: [
612
- {
613
- type: "text",
614
- text: JSON.stringify(
615
- {
616
- success: true,
617
- issue: {
618
- number: issue.data.number,
619
- title: issue.data.title,
620
- state: issue.data.state,
621
- html_url: issue.data.html_url,
622
- updated_at: issue.data.updated_at,
623
- },
624
- message: `Issue #${issue.data.number} updated successfully`,
625
- },
626
- null,
627
- 2
628
- ),
629
- },
630
- ],
631
- };
632
- } catch (error: any) {
633
- const issueNum = args && typeof args === 'object' && 'issue_number' in args ? args.issue_number : 'unknown';
634
- const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
635
- const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
636
- return {
637
- content: [
638
- {
639
- type: "text",
640
- text: JSON.stringify({
641
- error: error.message,
642
- status: error.status,
643
- detail: `Failed to update issue #${issueNum} in ${owner}/${repo}`,
644
- }, null, 2),
645
- },
646
- ],
647
- isError: true,
648
- };
649
- }
650
- }
651
-
652
- if (name === "github_create_pr") {
653
- try {
654
- const { owner, repo, title, body, head, base, draft, maintainer_can_modify } = CreatePRSchema.parse(args);
655
-
656
- const pr = await octokit.rest.pulls.create({
657
- owner,
658
- repo,
659
- title,
660
- body,
661
- head,
662
- base,
663
- draft,
664
- maintainer_can_modify,
665
- });
666
-
667
- return {
668
- content: [
669
- {
670
- type: "text",
671
- text: JSON.stringify(
672
- {
673
- success: true,
674
- pull_request: {
675
- number: pr.data.number,
676
- title: pr.data.title,
677
- state: pr.data.state,
678
- html_url: pr.data.html_url,
679
- draft: pr.data.draft,
680
- head: pr.data.head.ref,
681
- base: pr.data.base.ref,
682
- created_at: pr.data.created_at,
683
- },
684
- message: `PR #${pr.data.number} created successfully`,
685
- },
686
- null,
687
- 2
688
- ),
689
- },
690
- ],
691
- };
692
- } catch (error: any) {
693
- const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
694
- const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
695
- const title = args && typeof args === 'object' && 'title' in args ? args.title : 'unknown';
696
- return {
697
- content: [
698
- {
699
- type: "text",
700
- text: JSON.stringify({
701
- error: error.message,
702
- status: error.status,
703
- detail: `Failed to create PR "${title}" in ${owner}/${repo}`,
704
- }, null, 2),
705
- },
706
- ],
707
- isError: true,
708
- };
709
- }
710
- }
711
-
712
- if (name === "github_add_comment") {
713
- try {
714
- const { owner, repo, issue_number, body } = AddCommentSchema.parse(args);
715
-
716
- const comment = await octokit.rest.issues.createComment({
717
- owner,
718
- repo,
719
- issue_number,
720
- body,
721
- });
722
-
723
- return {
724
- content: [
725
- {
726
- type: "text",
727
- text: JSON.stringify(
728
- {
729
- success: true,
730
- comment: {
731
- id: comment.data.id,
732
- body: comment.data.body,
733
- user: comment.data.user?.login,
734
- html_url: comment.data.html_url,
735
- created_at: comment.data.created_at,
736
- },
737
- message: `Comment added successfully to issue/PR #${issue_number}`,
738
- },
739
- null,
740
- 2
741
- ),
742
- },
743
- ],
744
- };
745
- } catch (error: any) {
746
- const issueNum = args && typeof args === 'object' && 'issue_number' in args ? args.issue_number : 'unknown';
747
- const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
748
- const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
749
- return {
750
- content: [
751
- {
752
- type: "text",
753
- text: JSON.stringify({
754
- error: error.message,
755
- status: error.status,
756
- detail: `Failed to add comment to issue/PR #${issueNum} in ${owner}/${repo}`,
757
- }, null, 2),
758
- },
759
- ],
760
- isError: true,
761
- };
762
- }
763
- }
764
-
765
- if (name === "github_update_comment") {
766
- try {
767
- const { owner, repo, comment_id, body } = UpdateCommentSchema.parse(args);
768
-
769
- const comment = await octokit.rest.issues.updateComment({
770
- owner,
771
- repo,
772
- comment_id,
773
- body,
774
- });
775
-
776
- return {
777
- content: [
778
- {
779
- type: "text",
780
- text: JSON.stringify(
781
- {
782
- success: true,
783
- comment: {
784
- id: comment.data.id,
785
- body: comment.data.body,
786
- user: comment.data.user?.login,
787
- html_url: comment.data.html_url,
788
- updated_at: comment.data.updated_at,
789
- },
790
- message: `Comment #${comment_id} updated successfully`,
791
- },
792
- null,
793
- 2
794
- ),
795
- },
796
- ],
797
- };
798
- } catch (error: any) {
799
- const commentId = args && typeof args === 'object' && 'comment_id' in args ? args.comment_id : 'unknown';
800
- const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
801
- const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
802
- return {
803
- content: [
804
- {
805
- type: "text",
806
- text: JSON.stringify({
807
- error: error.message,
808
- status: error.status,
809
- detail: `Failed to update comment #${commentId} in ${owner}/${repo}`,
810
- }, null, 2),
811
- },
812
- ],
813
- isError: true,
814
- };
815
- }
816
- }
817
-
818
- if (name === "github_delete_comment") {
819
- try {
820
- const { owner, repo, comment_id } = DeleteCommentSchema.parse(args);
821
-
822
- await octokit.rest.issues.deleteComment({
823
- owner,
824
- repo,
825
- comment_id,
826
- });
827
-
828
- return {
829
- content: [
830
- {
831
- type: "text",
832
- text: JSON.stringify(
833
- {
834
- success: true,
835
- message: `Comment #${comment_id} deleted successfully`,
836
- },
837
- null,
838
- 2
839
- ),
840
- },
841
- ],
842
- };
843
- } catch (error: any) {
844
- const commentId = args && typeof args === 'object' && 'comment_id' in args ? args.comment_id : 'unknown';
845
- const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
846
- const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
847
- return {
848
- content: [
849
- {
850
- type: "text",
851
- text: JSON.stringify({
852
- error: error.message,
853
- status: error.status,
854
- detail: `Failed to delete comment #${commentId} in ${owner}/${repo}`,
855
- }, null, 2),
856
- },
857
- ],
858
- isError: true,
859
- };
860
- }
861
- }
862
-
863
- if (name === "github_add_reaction") {
864
- try {
865
- const { owner, repo, comment_id, issue_number, reaction } = AddReactionSchema.parse(args);
866
-
867
- let reactionResponse;
868
- let target = "";
869
-
870
- const githubReaction = REACTION_MAP[reaction] || reaction;
871
-
872
- if (comment_id) {
873
- // Add reaction to a comment
874
- reactionResponse = await octokit.rest.reactions.createForIssueComment({
875
- owner,
876
- repo,
877
- comment_id,
878
- content: githubReaction as any,
879
- });
880
- target = `comment #${comment_id}`;
881
- } else if (issue_number) {
882
- // Add reaction to an issue/PR
883
- reactionResponse = await octokit.rest.reactions.createForIssue({
884
- owner,
885
- repo,
886
- issue_number,
887
- content: githubReaction as any,
888
- });
889
- target = `issue/PR #${issue_number}`;
890
- }
891
-
892
- return {
893
- content: [
894
- {
895
- type: "text",
896
- text: JSON.stringify(
897
- {
898
- success: true,
899
- reaction: {
900
- id: reactionResponse!.data.id,
901
- content: reactionResponse!.data.content,
902
- user: reactionResponse!.data.user?.login,
903
- created_at: reactionResponse!.data.created_at,
904
- },
905
- message: `Reaction "${reaction}" added successfully to ${target}`,
906
- },
907
- null,
908
- 2
909
- ),
910
- },
911
- ],
912
- };
913
- } catch (error: any) {
914
- const commentId = args && typeof args === 'object' && 'comment_id' in args ? args.comment_id : undefined;
915
- const issueNum = args && typeof args === 'object' && 'issue_number' in args ? args.issue_number : undefined;
916
- const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
917
- const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
918
- const reaction = args && typeof args === 'object' && 'reaction' in args ? args.reaction : 'unknown';
919
- const target = commentId ? `comment #${commentId}` : issueNum ? `issue/PR #${issueNum}` : 'unknown';
920
- return {
921
- content: [
922
- {
923
- type: "text",
924
- text: JSON.stringify({
925
- error: error.message,
926
- status: error.status,
927
- detail: `Failed to add reaction "${reaction}" to ${target} in ${owner}/${repo}`,
928
- }, null, 2),
929
- },
930
- ],
931
- isError: true,
932
- };
933
- }
934
- }
935
-
936
- if (name === "github_search_issues") {
937
- try {
938
- const { owner, repo, query, state, labels, sort, direction, per_page } = SearchIssuesSchema.parse(args);
939
-
940
- const issues = await octokit.rest.issues.listForRepo({
941
- owner,
942
- repo,
943
- state: state || "open",
944
- labels: labels?.join(","),
945
- sort: sort as any,
946
- direction: direction as any,
947
- per_page: per_page || 30,
948
- });
949
-
950
- // Filter out pull requests first
951
- const issuesOnly = issues.data.filter(issue => !issue.pull_request);
952
- const filteredIssues = query
953
- ? issues.data.filter(issue => !issue.pull_request).filter(issue =>
954
- issue.title.toLowerCase().includes(query.toLowerCase()) ||
955
- issue.body?.toLowerCase().includes(query.toLowerCase())
956
- )
957
- : issues.data.filter(issue => !issue.pull_request);
958
-
959
- return {
960
- content: [
961
- {
962
- type: "text",
963
- text: JSON.stringify(
964
- {
965
- total_count: filteredIssues.length,
966
- issues: filteredIssues.map(issue => ({
967
- number: issue.number,
968
- title: issue.title,
969
- state: issue.state,
970
- user: issue.user?.login,
971
- labels: issue.labels.map((l: any) => typeof l === "string" ? l : l.name),
972
- created_at: issue.created_at,
973
- updated_at: issue.updated_at,
974
- comments: issue.comments,
975
- html_url: issue.html_url,
976
- })),
977
- },
978
- null,
979
- 2
980
- ),
981
- },
982
- ],
983
- };
984
- } catch (error: any) {
985
- const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
986
- const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
987
- return {
988
- content: [
989
- {
990
- type: "text",
991
- text: JSON.stringify({
992
- error: error.message,
993
- status: error.status,
994
- detail: `Failed to search issues in ${owner}/${repo}`,
995
- }, null, 2),
996
- },
997
- ],
998
- isError: true,
999
- };
1000
- }
1001
- }
1002
-
1003
- if (name === "github_search_prs") {
1004
- try {
1005
- const { owner, repo, query, state, sort, direction, per_page } = SearchPRsSchema.parse(args);
1006
-
1007
- const prs = await octokit.rest.pulls.list({
1008
- owner,
1009
- repo,
1010
- state: state || "open",
1011
- sort: sort as any,
1012
- direction: direction as any,
1013
- per_page: per_page || 30,
1014
- });
1015
-
1016
- const filteredPRs = query
1017
- ? prs.data.filter(pr =>
1018
- pr.title.toLowerCase().includes(query.toLowerCase()) ||
1019
- pr.body?.toLowerCase().includes(query.toLowerCase())
1020
- )
1021
- : prs.data;
1022
-
1023
- return {
1024
- content: [
1025
- {
1026
- type: "text",
1027
- text: JSON.stringify(
1028
- {
1029
- total_count: filteredPRs.length,
1030
- pull_requests: filteredPRs.map(pr => ({
1031
- number: pr.number,
1032
- title: pr.title,
1033
- state: pr.state,
1034
- user: pr.user?.login,
1035
- head: pr.head.ref,
1036
- base: pr.base.ref,
1037
- created_at: pr.created_at,
1038
- updated_at: pr.updated_at,
1039
- draft: pr.draft,
1040
- html_url: pr.html_url,
1041
- })),
1042
- },
1043
- null,
1044
- 2
1045
- ),
1046
- },
1047
- ],
1048
- };
1049
- } catch (error: any) {
1050
- const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
1051
- const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
1052
- return {
1053
- content: [
1054
- {
1055
- type: "text",
1056
- text: JSON.stringify({
1057
- error: error.message,
1058
- status: error.status,
1059
- detail: `Failed to search PRs in ${owner}/${repo}`,
1060
- }, null, 2),
1061
- },
1062
- ],
1063
- isError: true,
1064
- };
1065
- }
1066
- }
1067
-
1068
- if (name === "github_list_recent_issues") {
1069
- try {
1070
- const { owner, repo, state, sort, per_page } = ListRecentIssuesSchema.parse(args);
1071
-
1072
- const issues = await octokit.rest.issues.listForRepo({
1073
- owner,
1074
- repo,
1075
- state: state || "open",
1076
- sort: sort || "created",
1077
- direction: "desc",
1078
- per_page: per_page || 30,
1079
- });
1080
-
1081
- // Filter out pull requests (GitHub API returns both issues and PRs)
1082
- const actualIssues = issues.data.filter(issue => !issue.pull_request);
1083
-
1084
- return {
1085
- content: [
1086
- {
1087
- type: "text",
1088
- text: JSON.stringify(
1089
- {
1090
- count: actualIssues.length,
1091
- issues: actualIssues.map(issue => ({
1092
- number: issue.number,
1093
- title: issue.title,
1094
- state: issue.state,
1095
- user: issue.user?.login,
1096
- labels: issue.labels.map((l: any) => typeof l === "string" ? l : l.name),
1097
- created_at: issue.created_at,
1098
- updated_at: issue.updated_at,
1099
- comments: issue.comments,
1100
- html_url: issue.html_url,
1101
- })),
1102
- },
1103
- null,
1104
- 2
1105
- ),
1106
- },
1107
- ],
1108
- };
1109
- } catch (error: any) {
1110
- const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
1111
- const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
1112
- return {
1113
- content: [
1114
- {
1115
- type: "text",
1116
- text: JSON.stringify({
1117
- error: error.message,
1118
- status: error.status,
1119
- detail: `Failed to list recent issues in ${owner}/${repo}`,
1120
- }, null, 2),
1121
- },
1122
- ],
1123
- isError: true,
1124
- };
1125
- }
1126
- }
1127
-
1128
- if (name === "github_merge_pr") {
1129
- try {
1130
- const { owner, repo, pull_number, merge_method, commit_title, commit_message } = MergePRSchema.parse(args);
1131
-
1132
- const result = await octokit.rest.pulls.merge({
1133
- owner,
1134
- repo,
1135
- pull_number,
1136
- merge_method: merge_method as any,
1137
- commit_title,
1138
- commit_message,
1139
- });
1140
-
1141
- return {
1142
- content: [
1143
- {
1144
- type: "text",
1145
- text: JSON.stringify(
1146
- {
1147
- success: true,
1148
- merged: result.data.merged,
1149
- sha: result.data.sha,
1150
- message: result.data.message,
1151
- },
1152
- null,
1153
- 2
1154
- ),
1155
- },
1156
- ],
1157
- };
1158
- } catch (error: any) {
1159
- const pullNum = args && typeof args === 'object' && 'pull_number' in args ? args.pull_number : 'unknown';
1160
- const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
1161
- const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
1162
- return {
1163
- content: [
1164
- {
1165
- type: "text",
1166
- text: JSON.stringify({
1167
- error: error.message,
1168
- status: error.status,
1169
- detail: `Failed to merge PR #${pullNum} in ${owner}/${repo}`,
1170
- }, null, 2),
1171
- },
1172
- ],
1173
- isError: true,
1174
- };
1175
- }
1176
- }
1177
-
1178
- if (name === "github_get_pr_diff") {
1179
- try {
1180
- const { owner, repo, pull_number } = GetPRDiffSchema.parse(args);
1181
-
1182
- const diff = await octokit.rest.pulls.get({
1183
- owner,
1184
- repo,
1185
- pull_number,
1186
- mediaType: {
1187
- format: "diff",
1188
- },
1189
- });
1190
-
1191
- return {
1192
- content: [
1193
- {
1194
- type: "text",
1195
- text: JSON.stringify(
1196
- {
1197
- pull_number,
1198
- diff: diff.data as any,
1199
- },
1200
- null,
1201
- 2
1202
- ),
1203
- },
1204
- ],
1205
- };
1206
- } catch (error: any) {
1207
- const pullNum = args && typeof args === 'object' && 'pull_number' in args ? args.pull_number : 'unknown';
1208
- const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
1209
- const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
1210
- return {
1211
- content: [
1212
- {
1213
- type: "text",
1214
- text: JSON.stringify({
1215
- error: error.message,
1216
- status: error.status,
1217
- detail: `Failed to get diff for PR #${pullNum} in ${owner}/${repo}`,
1218
- }, null, 2),
1219
- },
1220
- ],
1221
- isError: true,
1222
- };
1223
- }
1224
- }
1225
-
1226
- if (name === "github_get_pr_files") {
1227
- try {
1228
- const { owner, repo, pull_number } = GetPRFilesSchema.parse(args);
1229
-
1230
- const files = await octokit.rest.pulls.listFiles({
1231
- owner,
1232
- repo,
1233
- pull_number,
1234
- });
1235
-
1236
- return {
1237
- content: [
1238
- {
1239
- type: "text",
1240
- text: JSON.stringify(
1241
- {
1242
- pull_number,
1243
- total_files: files.data.length,
1244
- files: files.data.map(file => ({
1245
- filename: file.filename,
1246
- status: file.status,
1247
- additions: file.additions,
1248
- deletions: file.deletions,
1249
- changes: file.changes,
1250
- blob_url: file.blob_url,
1251
- raw_url: file.raw_url,
1252
- patch: file.patch,
1253
- })),
1254
- },
1255
- null,
1256
- 2
1257
- ),
1258
- },
1259
- ],
1260
- };
1261
- } catch (error: any) {
1262
- const pullNum = args && typeof args === 'object' && 'pull_number' in args ? args.pull_number : 'unknown';
1263
- const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
1264
- const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
1265
- return {
1266
- content: [
1267
- {
1268
- type: "text",
1269
- text: JSON.stringify({
1270
- error: error.message,
1271
- status: error.status,
1272
- detail: `Failed to get files for PR #${pullNum} in ${owner}/${repo}`,
1273
- }, null, 2),
1274
- },
1275
- ],
1276
- isError: true,
1277
- };
1278
- }
1279
- }
1280
-
1281
- throw new Error(`Unknown tool: ${name}`);
30
+ return dispatchTool(name, args);
1282
31
  });
1283
32
 
1284
33
  async function main() {
1285
34
  const transport = new StdioServerTransport();
1286
35
  await server.connect(transport);
1287
- console.error("issue-scribe-mcp server running on stdio");
36
+ console.error(`issue-scribe-mcp server running on stdio (v${SERVER_VERSION})`);
1288
37
  }
1289
38
 
1290
39
  main().catch((error) => {