issue-scribe-mcp 1.0.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/.env.example +1 -0
- package/README.md +97 -0
- package/dist/index.js +421 -0
- package/package.json +43 -0
- package/src/index.ts +476 -0
- package/tsconfig.json +15 -0
package/.env.example
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
GITHUB_TOKEN=your_github_token_here
|
package/README.md
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# issue-scribe-mcp
|
|
2
|
+
|
|
3
|
+
GitHub Issue/PR 컨텍스트를 수집하는 MCP 서버
|
|
4
|
+
|
|
5
|
+
## 설치
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g issue-scribe-mcp
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 로컬 개발
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
# 의존성 설치
|
|
15
|
+
npm install
|
|
16
|
+
|
|
17
|
+
# 빌드
|
|
18
|
+
npm run build
|
|
19
|
+
|
|
20
|
+
# 환경변수 설정
|
|
21
|
+
export GITHUB_TOKEN=your_github_token_here
|
|
22
|
+
|
|
23
|
+
# 직접 실행
|
|
24
|
+
node dist/index.js
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## MCP 클라이언트 설정
|
|
28
|
+
|
|
29
|
+
Claude Desktop 등의 MCP 클라이언트에서 사용:
|
|
30
|
+
|
|
31
|
+
```json
|
|
32
|
+
{
|
|
33
|
+
"mcpServers": {
|
|
34
|
+
"issue-scribe": {
|
|
35
|
+
"command": "node",
|
|
36
|
+
"args": ["/path/to/issue-scribe-mcp/dist/index.js"],
|
|
37
|
+
"env": {
|
|
38
|
+
"GITHUB_TOKEN": "your_github_token_here"
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 제공 Tools
|
|
46
|
+
|
|
47
|
+
### github_get_issue_context
|
|
48
|
+
GitHub Issue의 전체 컨텍스트 조회
|
|
49
|
+
|
|
50
|
+
- owner: 저장소 소유자
|
|
51
|
+
- repo: 저장소 이름
|
|
52
|
+
- issue_number: 이슈 번호
|
|
53
|
+
|
|
54
|
+
### github_get_pr_context
|
|
55
|
+
GitHub Pull Request의 전체 컨텍스트 조회 (커밋 포함)
|
|
56
|
+
|
|
57
|
+
- owner: 저장소 소유자
|
|
58
|
+
- repo: 저장소 이름
|
|
59
|
+
- pull_number: PR 번호
|
|
60
|
+
|
|
61
|
+
### github_create_issue
|
|
62
|
+
새로운 GitHub Issue 생성
|
|
63
|
+
|
|
64
|
+
- owner: 저장소 소유자
|
|
65
|
+
- repo: 저장소 이름
|
|
66
|
+
- title: 이슈 제목 (필수)
|
|
67
|
+
- body: 이슈 본문 (옵션)
|
|
68
|
+
- labels: 라벨 배열 (옵션)
|
|
69
|
+
- assignees: 담당자 배열 (옵션)
|
|
70
|
+
|
|
71
|
+
### github_update_issue
|
|
72
|
+
기존 GitHub Issue 수정
|
|
73
|
+
|
|
74
|
+
- owner: 저장소 소유자
|
|
75
|
+
- repo: 저장소 이름
|
|
76
|
+
- issue_number: 이슈 번호
|
|
77
|
+
- title: 새 제목 (옵션)
|
|
78
|
+
- body: 새 본문 (옵션)
|
|
79
|
+
- state: "open" 또는 "closed" (옵션)
|
|
80
|
+
- labels: 새 라벨 배열 (옵션)
|
|
81
|
+
- assignees: 새 담당자 배열 (옵션)
|
|
82
|
+
|
|
83
|
+
### github_create_pr
|
|
84
|
+
새로운 GitHub Pull Request 생성
|
|
85
|
+
|
|
86
|
+
- owner: 저장소 소유자
|
|
87
|
+
- repo: 저장소 이름
|
|
88
|
+
- title: PR 제목 (필수)
|
|
89
|
+
- body: PR 설명 (옵션)
|
|
90
|
+
- head: 병합할 브랜치 (필수, 예: "feature-branch")
|
|
91
|
+
- base: 병합 대상 브랜치 (필수, 예: "main")
|
|
92
|
+
- draft: Draft PR로 생성 (옵션)
|
|
93
|
+
- maintainer_can_modify: 메인테이너 수정 허용 (옵션)
|
|
94
|
+
|
|
95
|
+
## 라이선스
|
|
96
|
+
|
|
97
|
+
ISC
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
5
|
+
import { Octokit } from "@octokit/rest";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
|
|
8
|
+
if (!GITHUB_TOKEN) {
|
|
9
|
+
console.error("Error: GITHUB_TOKEN environment variable is required");
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
const octokit = new Octokit({ auth: GITHUB_TOKEN });
|
|
13
|
+
const GetIssueSchema = z.object({
|
|
14
|
+
owner: z.string(),
|
|
15
|
+
repo: z.string(),
|
|
16
|
+
issue_number: z.number(),
|
|
17
|
+
});
|
|
18
|
+
const GetPRSchema = z.object({
|
|
19
|
+
owner: z.string(),
|
|
20
|
+
repo: z.string(),
|
|
21
|
+
pull_number: z.number(),
|
|
22
|
+
});
|
|
23
|
+
const CreateIssueSchema = z.object({
|
|
24
|
+
owner: z.string(),
|
|
25
|
+
repo: z.string(),
|
|
26
|
+
title: z.string(),
|
|
27
|
+
body: z.string().optional(),
|
|
28
|
+
labels: z.array(z.string()).optional(),
|
|
29
|
+
assignees: z.array(z.string()).optional(),
|
|
30
|
+
});
|
|
31
|
+
const UpdateIssueSchema = z.object({
|
|
32
|
+
owner: z.string(),
|
|
33
|
+
repo: z.string(),
|
|
34
|
+
issue_number: z.number(),
|
|
35
|
+
title: z.string().optional(),
|
|
36
|
+
body: z.string().optional(),
|
|
37
|
+
state: z.enum(["open", "closed"]).optional(),
|
|
38
|
+
labels: z.array(z.string()).optional(),
|
|
39
|
+
assignees: z.array(z.string()).optional(),
|
|
40
|
+
});
|
|
41
|
+
const CreatePRSchema = z.object({
|
|
42
|
+
owner: z.string(),
|
|
43
|
+
repo: z.string(),
|
|
44
|
+
title: z.string(),
|
|
45
|
+
body: z.string().optional(),
|
|
46
|
+
head: z.string(), // branch to merge FROM (e.g., "feature-branch")
|
|
47
|
+
base: z.string(), // branch to merge INTO (e.g., "main")
|
|
48
|
+
draft: z.boolean().optional(),
|
|
49
|
+
maintainer_can_modify: z.boolean().optional(),
|
|
50
|
+
});
|
|
51
|
+
const server = new Server({
|
|
52
|
+
name: "issue-scribe-mcp",
|
|
53
|
+
version: "1.0.0",
|
|
54
|
+
}, {
|
|
55
|
+
capabilities: {
|
|
56
|
+
tools: {},
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
60
|
+
return {
|
|
61
|
+
tools: [
|
|
62
|
+
{
|
|
63
|
+
name: "github_get_issue_context",
|
|
64
|
+
description: "Get GitHub Issue context including title, body, comments, and metadata",
|
|
65
|
+
inputSchema: {
|
|
66
|
+
type: "object",
|
|
67
|
+
properties: {
|
|
68
|
+
owner: { type: "string", description: "Repository owner" },
|
|
69
|
+
repo: { type: "string", description: "Repository name" },
|
|
70
|
+
issue_number: { type: "number", description: "Issue number" },
|
|
71
|
+
},
|
|
72
|
+
required: ["owner", "repo", "issue_number"],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: "github_get_pr_context",
|
|
77
|
+
description: "Get GitHub Pull Request context including title, body, comments, commits, and metadata",
|
|
78
|
+
inputSchema: {
|
|
79
|
+
type: "object",
|
|
80
|
+
properties: {
|
|
81
|
+
owner: { type: "string", description: "Repository owner" },
|
|
82
|
+
repo: { type: "string", description: "Repository name" },
|
|
83
|
+
pull_number: { type: "number", description: "Pull request number" },
|
|
84
|
+
},
|
|
85
|
+
required: ["owner", "repo", "pull_number"],
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "github_create_issue",
|
|
90
|
+
description: "Create a new GitHub Issue",
|
|
91
|
+
inputSchema: {
|
|
92
|
+
type: "object",
|
|
93
|
+
properties: {
|
|
94
|
+
owner: { type: "string", description: "Repository owner" },
|
|
95
|
+
repo: { type: "string", description: "Repository name" },
|
|
96
|
+
title: { type: "string", description: "Issue title" },
|
|
97
|
+
body: { type: "string", description: "Issue body (optional)" },
|
|
98
|
+
labels: { type: "array", items: { type: "string" }, description: "Labels to add (optional)" },
|
|
99
|
+
assignees: { type: "array", items: { type: "string" }, description: "Assignees (optional)" },
|
|
100
|
+
},
|
|
101
|
+
required: ["owner", "repo", "title"],
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: "github_update_issue",
|
|
106
|
+
description: "Update an existing GitHub Issue",
|
|
107
|
+
inputSchema: {
|
|
108
|
+
type: "object",
|
|
109
|
+
properties: {
|
|
110
|
+
owner: { type: "string", description: "Repository owner" },
|
|
111
|
+
repo: { type: "string", description: "Repository name" },
|
|
112
|
+
issue_number: { type: "number", description: "Issue number" },
|
|
113
|
+
title: { type: "string", description: "New title (optional)" },
|
|
114
|
+
body: { type: "string", description: "New body (optional)" },
|
|
115
|
+
state: { type: "string", enum: ["open", "closed"], description: "Issue state (optional)" },
|
|
116
|
+
labels: { type: "array", items: { type: "string" }, description: "New labels (optional)" },
|
|
117
|
+
assignees: { type: "array", items: { type: "string" }, description: "New assignees (optional)" },
|
|
118
|
+
},
|
|
119
|
+
required: ["owner", "repo", "issue_number"],
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: "github_create_pr",
|
|
124
|
+
description: "Create a new GitHub Pull Request",
|
|
125
|
+
inputSchema: {
|
|
126
|
+
type: "object",
|
|
127
|
+
properties: {
|
|
128
|
+
owner: { type: "string", description: "Repository owner" },
|
|
129
|
+
repo: { type: "string", description: "Repository name" },
|
|
130
|
+
title: { type: "string", description: "PR title" },
|
|
131
|
+
body: { type: "string", description: "PR body/description (optional)" },
|
|
132
|
+
head: { type: "string", description: "Branch to merge FROM (e.g., 'feature-branch')" },
|
|
133
|
+
base: { type: "string", description: "Branch to merge INTO (e.g., 'main')" },
|
|
134
|
+
draft: { type: "boolean", description: "Create as draft PR (optional)" },
|
|
135
|
+
maintainer_can_modify: { type: "boolean", description: "Allow maintainer edits (optional)" },
|
|
136
|
+
},
|
|
137
|
+
required: ["owner", "repo", "title", "head", "base"],
|
|
138
|
+
},
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
144
|
+
const { name, arguments: args } = request.params;
|
|
145
|
+
if (name === "github_get_issue_context") {
|
|
146
|
+
try {
|
|
147
|
+
const { owner, repo, issue_number } = GetIssueSchema.parse(args);
|
|
148
|
+
const [issue, comments] = await Promise.all([
|
|
149
|
+
octokit.rest.issues.get({ owner, repo, issue_number }),
|
|
150
|
+
octokit.rest.issues.listComments({ owner, repo, issue_number }),
|
|
151
|
+
]);
|
|
152
|
+
return {
|
|
153
|
+
content: [
|
|
154
|
+
{
|
|
155
|
+
type: "text",
|
|
156
|
+
text: JSON.stringify({
|
|
157
|
+
issue: {
|
|
158
|
+
number: issue.data.number,
|
|
159
|
+
title: issue.data.title,
|
|
160
|
+
body: issue.data.body,
|
|
161
|
+
state: issue.data.state,
|
|
162
|
+
user: issue.data.user?.login,
|
|
163
|
+
created_at: issue.data.created_at,
|
|
164
|
+
updated_at: issue.data.updated_at,
|
|
165
|
+
labels: issue.data.labels.map((l) => typeof l === "string" ? l : l.name),
|
|
166
|
+
},
|
|
167
|
+
comments: comments.data.map((c) => ({
|
|
168
|
+
user: c.user?.login,
|
|
169
|
+
body: c.body,
|
|
170
|
+
created_at: c.created_at,
|
|
171
|
+
})),
|
|
172
|
+
}, null, 2),
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
const issueNum = args && typeof args === 'object' && 'issue_number' in args ? args.issue_number : 'unknown';
|
|
179
|
+
const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
|
|
180
|
+
const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
|
|
181
|
+
return {
|
|
182
|
+
content: [
|
|
183
|
+
{
|
|
184
|
+
type: "text",
|
|
185
|
+
text: JSON.stringify({
|
|
186
|
+
error: error.message,
|
|
187
|
+
status: error.status,
|
|
188
|
+
detail: `Failed to fetch issue #${issueNum} from ${owner}/${repo}`,
|
|
189
|
+
}, null, 2),
|
|
190
|
+
},
|
|
191
|
+
],
|
|
192
|
+
isError: true,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
if (name === "github_get_pr_context") {
|
|
197
|
+
try {
|
|
198
|
+
const { owner, repo, pull_number } = GetPRSchema.parse(args);
|
|
199
|
+
const [pr, comments, commits] = await Promise.all([
|
|
200
|
+
octokit.rest.pulls.get({ owner, repo, pull_number }),
|
|
201
|
+
octokit.rest.issues.listComments({ owner, repo, issue_number: pull_number }),
|
|
202
|
+
octokit.rest.pulls.listCommits({ owner, repo, pull_number }),
|
|
203
|
+
]);
|
|
204
|
+
return {
|
|
205
|
+
content: [
|
|
206
|
+
{
|
|
207
|
+
type: "text",
|
|
208
|
+
text: JSON.stringify({
|
|
209
|
+
pull_request: {
|
|
210
|
+
number: pr.data.number,
|
|
211
|
+
title: pr.data.title,
|
|
212
|
+
body: pr.data.body,
|
|
213
|
+
state: pr.data.state,
|
|
214
|
+
user: pr.data.user?.login,
|
|
215
|
+
created_at: pr.data.created_at,
|
|
216
|
+
updated_at: pr.data.updated_at,
|
|
217
|
+
merged_at: pr.data.merged_at,
|
|
218
|
+
base: pr.data.base.ref,
|
|
219
|
+
head: pr.data.head.ref,
|
|
220
|
+
labels: pr.data.labels.map((l) => typeof l === "string" ? l : l.name),
|
|
221
|
+
},
|
|
222
|
+
comments: comments.data.map((c) => ({
|
|
223
|
+
user: c.user?.login,
|
|
224
|
+
body: c.body,
|
|
225
|
+
created_at: c.created_at,
|
|
226
|
+
})),
|
|
227
|
+
commits: commits.data.map((c) => ({
|
|
228
|
+
sha: c.sha,
|
|
229
|
+
message: c.commit.message,
|
|
230
|
+
author: c.commit.author?.name,
|
|
231
|
+
date: c.commit.author?.date,
|
|
232
|
+
})),
|
|
233
|
+
}, null, 2),
|
|
234
|
+
},
|
|
235
|
+
],
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
catch (error) {
|
|
239
|
+
const pullNum = args && typeof args === 'object' && 'pull_number' in args ? args.pull_number : 'unknown';
|
|
240
|
+
const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
|
|
241
|
+
const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
|
|
242
|
+
return {
|
|
243
|
+
content: [
|
|
244
|
+
{
|
|
245
|
+
type: "text",
|
|
246
|
+
text: JSON.stringify({
|
|
247
|
+
error: error.message,
|
|
248
|
+
status: error.status,
|
|
249
|
+
detail: `Failed to fetch PR #${pullNum} from ${owner}/${repo}`,
|
|
250
|
+
}, null, 2),
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
isError: true,
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
if (name === "github_create_issue") {
|
|
258
|
+
try {
|
|
259
|
+
const { owner, repo, title, body, labels, assignees } = CreateIssueSchema.parse(args);
|
|
260
|
+
const issue = await octokit.rest.issues.create({
|
|
261
|
+
owner,
|
|
262
|
+
repo,
|
|
263
|
+
title,
|
|
264
|
+
body,
|
|
265
|
+
labels,
|
|
266
|
+
assignees,
|
|
267
|
+
});
|
|
268
|
+
return {
|
|
269
|
+
content: [
|
|
270
|
+
{
|
|
271
|
+
type: "text",
|
|
272
|
+
text: JSON.stringify({
|
|
273
|
+
success: true,
|
|
274
|
+
issue: {
|
|
275
|
+
number: issue.data.number,
|
|
276
|
+
title: issue.data.title,
|
|
277
|
+
state: issue.data.state,
|
|
278
|
+
html_url: issue.data.html_url,
|
|
279
|
+
created_at: issue.data.created_at,
|
|
280
|
+
},
|
|
281
|
+
message: `Issue #${issue.data.number} created successfully`,
|
|
282
|
+
}, null, 2),
|
|
283
|
+
},
|
|
284
|
+
],
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
|
|
289
|
+
const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
|
|
290
|
+
const title = args && typeof args === 'object' && 'title' in args ? args.title : 'unknown';
|
|
291
|
+
return {
|
|
292
|
+
content: [
|
|
293
|
+
{
|
|
294
|
+
type: "text",
|
|
295
|
+
text: JSON.stringify({
|
|
296
|
+
error: error.message,
|
|
297
|
+
status: error.status,
|
|
298
|
+
detail: `Failed to create issue "${title}" in ${owner}/${repo}`,
|
|
299
|
+
}, null, 2),
|
|
300
|
+
},
|
|
301
|
+
],
|
|
302
|
+
isError: true,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
if (name === "github_update_issue") {
|
|
307
|
+
try {
|
|
308
|
+
const { owner, repo, issue_number, title, body, state, labels, assignees } = UpdateIssueSchema.parse(args);
|
|
309
|
+
const issue = await octokit.rest.issues.update({
|
|
310
|
+
owner,
|
|
311
|
+
repo,
|
|
312
|
+
issue_number,
|
|
313
|
+
title,
|
|
314
|
+
body,
|
|
315
|
+
state,
|
|
316
|
+
labels,
|
|
317
|
+
assignees,
|
|
318
|
+
});
|
|
319
|
+
return {
|
|
320
|
+
content: [
|
|
321
|
+
{
|
|
322
|
+
type: "text",
|
|
323
|
+
text: JSON.stringify({
|
|
324
|
+
success: true,
|
|
325
|
+
issue: {
|
|
326
|
+
number: issue.data.number,
|
|
327
|
+
title: issue.data.title,
|
|
328
|
+
state: issue.data.state,
|
|
329
|
+
html_url: issue.data.html_url,
|
|
330
|
+
updated_at: issue.data.updated_at,
|
|
331
|
+
},
|
|
332
|
+
message: `Issue #${issue.data.number} updated successfully`,
|
|
333
|
+
}, null, 2),
|
|
334
|
+
},
|
|
335
|
+
],
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
catch (error) {
|
|
339
|
+
const issueNum = args && typeof args === 'object' && 'issue_number' in args ? args.issue_number : 'unknown';
|
|
340
|
+
const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
|
|
341
|
+
const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
|
|
342
|
+
return {
|
|
343
|
+
content: [
|
|
344
|
+
{
|
|
345
|
+
type: "text",
|
|
346
|
+
text: JSON.stringify({
|
|
347
|
+
error: error.message,
|
|
348
|
+
status: error.status,
|
|
349
|
+
detail: `Failed to update issue #${issueNum} in ${owner}/${repo}`,
|
|
350
|
+
}, null, 2),
|
|
351
|
+
},
|
|
352
|
+
],
|
|
353
|
+
isError: true,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
if (name === "github_create_pr") {
|
|
358
|
+
try {
|
|
359
|
+
const { owner, repo, title, body, head, base, draft, maintainer_can_modify } = CreatePRSchema.parse(args);
|
|
360
|
+
const pr = await octokit.rest.pulls.create({
|
|
361
|
+
owner,
|
|
362
|
+
repo,
|
|
363
|
+
title,
|
|
364
|
+
body,
|
|
365
|
+
head,
|
|
366
|
+
base,
|
|
367
|
+
draft,
|
|
368
|
+
maintainer_can_modify,
|
|
369
|
+
});
|
|
370
|
+
return {
|
|
371
|
+
content: [
|
|
372
|
+
{
|
|
373
|
+
type: "text",
|
|
374
|
+
text: JSON.stringify({
|
|
375
|
+
success: true,
|
|
376
|
+
pull_request: {
|
|
377
|
+
number: pr.data.number,
|
|
378
|
+
title: pr.data.title,
|
|
379
|
+
state: pr.data.state,
|
|
380
|
+
html_url: pr.data.html_url,
|
|
381
|
+
draft: pr.data.draft,
|
|
382
|
+
head: pr.data.head.ref,
|
|
383
|
+
base: pr.data.base.ref,
|
|
384
|
+
created_at: pr.data.created_at,
|
|
385
|
+
},
|
|
386
|
+
message: `PR #${pr.data.number} created successfully`,
|
|
387
|
+
}, null, 2),
|
|
388
|
+
},
|
|
389
|
+
],
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
catch (error) {
|
|
393
|
+
const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
|
|
394
|
+
const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
|
|
395
|
+
const title = args && typeof args === 'object' && 'title' in args ? args.title : 'unknown';
|
|
396
|
+
return {
|
|
397
|
+
content: [
|
|
398
|
+
{
|
|
399
|
+
type: "text",
|
|
400
|
+
text: JSON.stringify({
|
|
401
|
+
error: error.message,
|
|
402
|
+
status: error.status,
|
|
403
|
+
detail: `Failed to create PR "${title}" in ${owner}/${repo}`,
|
|
404
|
+
}, null, 2),
|
|
405
|
+
},
|
|
406
|
+
],
|
|
407
|
+
isError: true,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
412
|
+
});
|
|
413
|
+
async function main() {
|
|
414
|
+
const transport = new StdioServerTransport();
|
|
415
|
+
await server.connect(transport);
|
|
416
|
+
console.error("issue-scribe-mcp server running on stdio");
|
|
417
|
+
}
|
|
418
|
+
main().catch((error) => {
|
|
419
|
+
console.error("Fatal error:", error);
|
|
420
|
+
process.exit(1);
|
|
421
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "issue-scribe-mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP server for GitHub Issue/PR context aggregation",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"bin": {
|
|
8
|
+
"issue-scribe-mcp": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"prepare": "npm run build",
|
|
13
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/gay00ung/issue-scribe-mcp.git"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mcp",
|
|
21
|
+
"github",
|
|
22
|
+
"issue",
|
|
23
|
+
"pr"
|
|
24
|
+
],
|
|
25
|
+
"author": "",
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/gay00ung/issue-scribe-mcp/issues"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/gay00ung/issue-scribe-mcp#readme",
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"@types/node": "^25.0.3",
|
|
33
|
+
"tsx": "^4.21.0",
|
|
34
|
+
"typescript": "^5.9.3"
|
|
35
|
+
},
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.25.1",
|
|
38
|
+
"@octokit/rest": "^22.0.1",
|
|
39
|
+
"dotenv": "^17.2.3",
|
|
40
|
+
"settings": "^0.1.1",
|
|
41
|
+
"zod": "^4.2.1"
|
|
42
|
+
}
|
|
43
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,476 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
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";
|
|
11
|
+
|
|
12
|
+
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
|
|
13
|
+
|
|
14
|
+
if (!GITHUB_TOKEN) {
|
|
15
|
+
console.error("Error: GITHUB_TOKEN environment variable is required");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const octokit = new Octokit({ auth: GITHUB_TOKEN });
|
|
20
|
+
|
|
21
|
+
const GetIssueSchema = z.object({
|
|
22
|
+
owner: z.string(),
|
|
23
|
+
repo: z.string(),
|
|
24
|
+
issue_number: z.number(),
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const GetPRSchema = z.object({
|
|
28
|
+
owner: z.string(),
|
|
29
|
+
repo: z.string(),
|
|
30
|
+
pull_number: z.number(),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const CreateIssueSchema = z.object({
|
|
34
|
+
owner: z.string(),
|
|
35
|
+
repo: z.string(),
|
|
36
|
+
title: z.string(),
|
|
37
|
+
body: z.string().optional(),
|
|
38
|
+
labels: z.array(z.string()).optional(),
|
|
39
|
+
assignees: z.array(z.string()).optional(),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const UpdateIssueSchema = z.object({
|
|
43
|
+
owner: z.string(),
|
|
44
|
+
repo: z.string(),
|
|
45
|
+
issue_number: z.number(),
|
|
46
|
+
title: z.string().optional(),
|
|
47
|
+
body: z.string().optional(),
|
|
48
|
+
state: z.enum(["open", "closed"]).optional(),
|
|
49
|
+
labels: z.array(z.string()).optional(),
|
|
50
|
+
assignees: z.array(z.string()).optional(),
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
const CreatePRSchema = z.object({
|
|
54
|
+
owner: z.string(),
|
|
55
|
+
repo: z.string(),
|
|
56
|
+
title: z.string(),
|
|
57
|
+
body: z.string().optional(),
|
|
58
|
+
head: z.string(), // branch to merge FROM (e.g., "feature-branch")
|
|
59
|
+
base: z.string(), // branch to merge INTO (e.g., "main")
|
|
60
|
+
draft: z.boolean().optional(),
|
|
61
|
+
maintainer_can_modify: z.boolean().optional(),
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
const server = new Server(
|
|
65
|
+
{
|
|
66
|
+
name: "issue-scribe-mcp",
|
|
67
|
+
version: "1.0.0",
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
capabilities: {
|
|
71
|
+
tools: {},
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
77
|
+
return {
|
|
78
|
+
tools: [
|
|
79
|
+
{
|
|
80
|
+
name: "github_get_issue_context",
|
|
81
|
+
description: "Get GitHub Issue context including title, body, comments, and metadata",
|
|
82
|
+
inputSchema: {
|
|
83
|
+
type: "object",
|
|
84
|
+
properties: {
|
|
85
|
+
owner: { type: "string", description: "Repository owner" },
|
|
86
|
+
repo: { type: "string", description: "Repository name" },
|
|
87
|
+
issue_number: { type: "number", description: "Issue number" },
|
|
88
|
+
},
|
|
89
|
+
required: ["owner", "repo", "issue_number"],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
name: "github_get_pr_context",
|
|
94
|
+
description: "Get GitHub Pull Request context including title, body, comments, commits, and metadata",
|
|
95
|
+
inputSchema: {
|
|
96
|
+
type: "object",
|
|
97
|
+
properties: {
|
|
98
|
+
owner: { type: "string", description: "Repository owner" },
|
|
99
|
+
repo: { type: "string", description: "Repository name" },
|
|
100
|
+
pull_number: { type: "number", description: "Pull request number" },
|
|
101
|
+
},
|
|
102
|
+
required: ["owner", "repo", "pull_number"],
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
name: "github_create_issue",
|
|
107
|
+
description: "Create a new GitHub Issue",
|
|
108
|
+
inputSchema: {
|
|
109
|
+
type: "object",
|
|
110
|
+
properties: {
|
|
111
|
+
owner: { type: "string", description: "Repository owner" },
|
|
112
|
+
repo: { type: "string", description: "Repository name" },
|
|
113
|
+
title: { type: "string", description: "Issue title" },
|
|
114
|
+
body: { type: "string", description: "Issue body (optional)" },
|
|
115
|
+
labels: { type: "array", items: { type: "string" }, description: "Labels to add (optional)" },
|
|
116
|
+
assignees: { type: "array", items: { type: "string" }, description: "Assignees (optional)" },
|
|
117
|
+
},
|
|
118
|
+
required: ["owner", "repo", "title"],
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
name: "github_update_issue",
|
|
123
|
+
description: "Update an existing GitHub Issue",
|
|
124
|
+
inputSchema: {
|
|
125
|
+
type: "object",
|
|
126
|
+
properties: {
|
|
127
|
+
owner: { type: "string", description: "Repository owner" },
|
|
128
|
+
repo: { type: "string", description: "Repository name" },
|
|
129
|
+
issue_number: { type: "number", description: "Issue number" },
|
|
130
|
+
title: { type: "string", description: "New title (optional)" },
|
|
131
|
+
body: { type: "string", description: "New body (optional)" },
|
|
132
|
+
state: { type: "string", enum: ["open", "closed"], description: "Issue state (optional)" },
|
|
133
|
+
labels: { type: "array", items: { type: "string" }, description: "New labels (optional)" },
|
|
134
|
+
assignees: { type: "array", items: { type: "string" }, description: "New assignees (optional)" },
|
|
135
|
+
},
|
|
136
|
+
required: ["owner", "repo", "issue_number"],
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: "github_create_pr",
|
|
141
|
+
description: "Create a new GitHub Pull Request",
|
|
142
|
+
inputSchema: {
|
|
143
|
+
type: "object",
|
|
144
|
+
properties: {
|
|
145
|
+
owner: { type: "string", description: "Repository owner" },
|
|
146
|
+
repo: { type: "string", description: "Repository name" },
|
|
147
|
+
title: { type: "string", description: "PR title" },
|
|
148
|
+
body: { type: "string", description: "PR body/description (optional)" },
|
|
149
|
+
head: { type: "string", description: "Branch to merge FROM (e.g., 'feature-branch')" },
|
|
150
|
+
base: { type: "string", description: "Branch to merge INTO (e.g., 'main')" },
|
|
151
|
+
draft: { type: "boolean", description: "Create as draft PR (optional)" },
|
|
152
|
+
maintainer_can_modify: { type: "boolean", description: "Allow maintainer edits (optional)" },
|
|
153
|
+
},
|
|
154
|
+
required: ["owner", "repo", "title", "head", "base"],
|
|
155
|
+
},
|
|
156
|
+
},
|
|
157
|
+
],
|
|
158
|
+
};
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
162
|
+
const { name, arguments: args } = request.params;
|
|
163
|
+
|
|
164
|
+
if (name === "github_get_issue_context") {
|
|
165
|
+
try {
|
|
166
|
+
const { owner, repo, issue_number } = GetIssueSchema.parse(args);
|
|
167
|
+
|
|
168
|
+
const [issue, comments] = await Promise.all([
|
|
169
|
+
octokit.rest.issues.get({ owner, repo, issue_number }),
|
|
170
|
+
octokit.rest.issues.listComments({ owner, repo, issue_number }),
|
|
171
|
+
]);
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
content: [
|
|
175
|
+
{
|
|
176
|
+
type: "text",
|
|
177
|
+
text: JSON.stringify(
|
|
178
|
+
{
|
|
179
|
+
issue: {
|
|
180
|
+
number: issue.data.number,
|
|
181
|
+
title: issue.data.title,
|
|
182
|
+
body: issue.data.body,
|
|
183
|
+
state: issue.data.state,
|
|
184
|
+
user: issue.data.user?.login,
|
|
185
|
+
created_at: issue.data.created_at,
|
|
186
|
+
updated_at: issue.data.updated_at,
|
|
187
|
+
labels: issue.data.labels.map((l: any) =>
|
|
188
|
+
typeof l === "string" ? l : l.name
|
|
189
|
+
),
|
|
190
|
+
},
|
|
191
|
+
comments: comments.data.map((c) => ({
|
|
192
|
+
user: c.user?.login,
|
|
193
|
+
body: c.body,
|
|
194
|
+
created_at: c.created_at,
|
|
195
|
+
})),
|
|
196
|
+
},
|
|
197
|
+
null,
|
|
198
|
+
2
|
|
199
|
+
),
|
|
200
|
+
},
|
|
201
|
+
],
|
|
202
|
+
};
|
|
203
|
+
} catch (error: any) {
|
|
204
|
+
const issueNum = args && typeof args === 'object' && 'issue_number' in args ? args.issue_number : 'unknown';
|
|
205
|
+
const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
|
|
206
|
+
const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
|
|
207
|
+
return {
|
|
208
|
+
content: [
|
|
209
|
+
{
|
|
210
|
+
type: "text",
|
|
211
|
+
text: JSON.stringify({
|
|
212
|
+
error: error.message,
|
|
213
|
+
status: error.status,
|
|
214
|
+
detail: `Failed to fetch issue #${issueNum} from ${owner}/${repo}`,
|
|
215
|
+
}, null, 2),
|
|
216
|
+
},
|
|
217
|
+
],
|
|
218
|
+
isError: true,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (name === "github_get_pr_context") {
|
|
224
|
+
try {
|
|
225
|
+
const { owner, repo, pull_number } = GetPRSchema.parse(args);
|
|
226
|
+
|
|
227
|
+
const [pr, comments, commits] = await Promise.all([
|
|
228
|
+
octokit.rest.pulls.get({ owner, repo, pull_number }),
|
|
229
|
+
octokit.rest.issues.listComments({ owner, repo, issue_number: pull_number }),
|
|
230
|
+
octokit.rest.pulls.listCommits({ owner, repo, pull_number }),
|
|
231
|
+
]);
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
content: [
|
|
235
|
+
{
|
|
236
|
+
type: "text",
|
|
237
|
+
text: JSON.stringify(
|
|
238
|
+
{
|
|
239
|
+
pull_request: {
|
|
240
|
+
number: pr.data.number,
|
|
241
|
+
title: pr.data.title,
|
|
242
|
+
body: pr.data.body,
|
|
243
|
+
state: pr.data.state,
|
|
244
|
+
user: pr.data.user?.login,
|
|
245
|
+
created_at: pr.data.created_at,
|
|
246
|
+
updated_at: pr.data.updated_at,
|
|
247
|
+
merged_at: pr.data.merged_at,
|
|
248
|
+
base: pr.data.base.ref,
|
|
249
|
+
head: pr.data.head.ref,
|
|
250
|
+
labels: pr.data.labels.map((l: any) =>
|
|
251
|
+
typeof l === "string" ? l : l.name
|
|
252
|
+
),
|
|
253
|
+
},
|
|
254
|
+
comments: comments.data.map((c) => ({
|
|
255
|
+
user: c.user?.login,
|
|
256
|
+
body: c.body,
|
|
257
|
+
created_at: c.created_at,
|
|
258
|
+
})),
|
|
259
|
+
commits: commits.data.map((c) => ({
|
|
260
|
+
sha: c.sha,
|
|
261
|
+
message: c.commit.message,
|
|
262
|
+
author: c.commit.author?.name,
|
|
263
|
+
date: c.commit.author?.date,
|
|
264
|
+
})),
|
|
265
|
+
},
|
|
266
|
+
null,
|
|
267
|
+
2
|
|
268
|
+
),
|
|
269
|
+
},
|
|
270
|
+
],
|
|
271
|
+
};
|
|
272
|
+
} catch (error: any) {
|
|
273
|
+
const pullNum = args && typeof args === 'object' && 'pull_number' in args ? args.pull_number : 'unknown';
|
|
274
|
+
const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
|
|
275
|
+
const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
|
|
276
|
+
return {
|
|
277
|
+
content: [
|
|
278
|
+
{
|
|
279
|
+
type: "text",
|
|
280
|
+
text: JSON.stringify({
|
|
281
|
+
error: error.message,
|
|
282
|
+
status: error.status,
|
|
283
|
+
detail: `Failed to fetch PR #${pullNum} from ${owner}/${repo}`,
|
|
284
|
+
}, null, 2),
|
|
285
|
+
},
|
|
286
|
+
],
|
|
287
|
+
isError: true,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (name === "github_create_issue") {
|
|
293
|
+
try {
|
|
294
|
+
const { owner, repo, title, body, labels, assignees } = CreateIssueSchema.parse(args);
|
|
295
|
+
|
|
296
|
+
const issue = await octokit.rest.issues.create({
|
|
297
|
+
owner,
|
|
298
|
+
repo,
|
|
299
|
+
title,
|
|
300
|
+
body,
|
|
301
|
+
labels,
|
|
302
|
+
assignees,
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
content: [
|
|
307
|
+
{
|
|
308
|
+
type: "text",
|
|
309
|
+
text: JSON.stringify(
|
|
310
|
+
{
|
|
311
|
+
success: true,
|
|
312
|
+
issue: {
|
|
313
|
+
number: issue.data.number,
|
|
314
|
+
title: issue.data.title,
|
|
315
|
+
state: issue.data.state,
|
|
316
|
+
html_url: issue.data.html_url,
|
|
317
|
+
created_at: issue.data.created_at,
|
|
318
|
+
},
|
|
319
|
+
message: `Issue #${issue.data.number} created successfully`,
|
|
320
|
+
},
|
|
321
|
+
null,
|
|
322
|
+
2
|
|
323
|
+
),
|
|
324
|
+
},
|
|
325
|
+
],
|
|
326
|
+
};
|
|
327
|
+
} catch (error: any) {
|
|
328
|
+
const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
|
|
329
|
+
const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
|
|
330
|
+
const title = args && typeof args === 'object' && 'title' in args ? args.title : 'unknown';
|
|
331
|
+
return {
|
|
332
|
+
content: [
|
|
333
|
+
{
|
|
334
|
+
type: "text",
|
|
335
|
+
text: JSON.stringify({
|
|
336
|
+
error: error.message,
|
|
337
|
+
status: error.status,
|
|
338
|
+
detail: `Failed to create issue "${title}" in ${owner}/${repo}`,
|
|
339
|
+
}, null, 2),
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
isError: true,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
if (name === "github_update_issue") {
|
|
348
|
+
try {
|
|
349
|
+
const { owner, repo, issue_number, title, body, state, labels, assignees } = UpdateIssueSchema.parse(args);
|
|
350
|
+
|
|
351
|
+
const issue = await octokit.rest.issues.update({
|
|
352
|
+
owner,
|
|
353
|
+
repo,
|
|
354
|
+
issue_number,
|
|
355
|
+
title,
|
|
356
|
+
body,
|
|
357
|
+
state,
|
|
358
|
+
labels,
|
|
359
|
+
assignees,
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
return {
|
|
363
|
+
content: [
|
|
364
|
+
{
|
|
365
|
+
type: "text",
|
|
366
|
+
text: JSON.stringify(
|
|
367
|
+
{
|
|
368
|
+
success: true,
|
|
369
|
+
issue: {
|
|
370
|
+
number: issue.data.number,
|
|
371
|
+
title: issue.data.title,
|
|
372
|
+
state: issue.data.state,
|
|
373
|
+
html_url: issue.data.html_url,
|
|
374
|
+
updated_at: issue.data.updated_at,
|
|
375
|
+
},
|
|
376
|
+
message: `Issue #${issue.data.number} updated successfully`,
|
|
377
|
+
},
|
|
378
|
+
null,
|
|
379
|
+
2
|
|
380
|
+
),
|
|
381
|
+
},
|
|
382
|
+
],
|
|
383
|
+
};
|
|
384
|
+
} catch (error: any) {
|
|
385
|
+
const issueNum = args && typeof args === 'object' && 'issue_number' in args ? args.issue_number : 'unknown';
|
|
386
|
+
const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
|
|
387
|
+
const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
|
|
388
|
+
return {
|
|
389
|
+
content: [
|
|
390
|
+
{
|
|
391
|
+
type: "text",
|
|
392
|
+
text: JSON.stringify({
|
|
393
|
+
error: error.message,
|
|
394
|
+
status: error.status,
|
|
395
|
+
detail: `Failed to update issue #${issueNum} in ${owner}/${repo}`,
|
|
396
|
+
}, null, 2),
|
|
397
|
+
},
|
|
398
|
+
],
|
|
399
|
+
isError: true,
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
if (name === "github_create_pr") {
|
|
405
|
+
try {
|
|
406
|
+
const { owner, repo, title, body, head, base, draft, maintainer_can_modify } = CreatePRSchema.parse(args);
|
|
407
|
+
|
|
408
|
+
const pr = await octokit.rest.pulls.create({
|
|
409
|
+
owner,
|
|
410
|
+
repo,
|
|
411
|
+
title,
|
|
412
|
+
body,
|
|
413
|
+
head,
|
|
414
|
+
base,
|
|
415
|
+
draft,
|
|
416
|
+
maintainer_can_modify,
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
return {
|
|
420
|
+
content: [
|
|
421
|
+
{
|
|
422
|
+
type: "text",
|
|
423
|
+
text: JSON.stringify(
|
|
424
|
+
{
|
|
425
|
+
success: true,
|
|
426
|
+
pull_request: {
|
|
427
|
+
number: pr.data.number,
|
|
428
|
+
title: pr.data.title,
|
|
429
|
+
state: pr.data.state,
|
|
430
|
+
html_url: pr.data.html_url,
|
|
431
|
+
draft: pr.data.draft,
|
|
432
|
+
head: pr.data.head.ref,
|
|
433
|
+
base: pr.data.base.ref,
|
|
434
|
+
created_at: pr.data.created_at,
|
|
435
|
+
},
|
|
436
|
+
message: `PR #${pr.data.number} created successfully`,
|
|
437
|
+
},
|
|
438
|
+
null,
|
|
439
|
+
2
|
|
440
|
+
),
|
|
441
|
+
},
|
|
442
|
+
],
|
|
443
|
+
};
|
|
444
|
+
} catch (error: any) {
|
|
445
|
+
const owner = args && typeof args === 'object' && 'owner' in args ? args.owner : 'unknown';
|
|
446
|
+
const repo = args && typeof args === 'object' && 'repo' in args ? args.repo : 'unknown';
|
|
447
|
+
const title = args && typeof args === 'object' && 'title' in args ? args.title : 'unknown';
|
|
448
|
+
return {
|
|
449
|
+
content: [
|
|
450
|
+
{
|
|
451
|
+
type: "text",
|
|
452
|
+
text: JSON.stringify({
|
|
453
|
+
error: error.message,
|
|
454
|
+
status: error.status,
|
|
455
|
+
detail: `Failed to create PR "${title}" in ${owner}/${repo}`,
|
|
456
|
+
}, null, 2),
|
|
457
|
+
},
|
|
458
|
+
],
|
|
459
|
+
isError: true,
|
|
460
|
+
};
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
async function main() {
|
|
468
|
+
const transport = new StdioServerTransport();
|
|
469
|
+
await server.connect(transport);
|
|
470
|
+
console.error("issue-scribe-mcp server running on stdio");
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
main().catch((error) => {
|
|
474
|
+
console.error("Fatal error:", error);
|
|
475
|
+
process.exit(1);
|
|
476
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"rootDir": "src",
|
|
7
|
+
"outDir": "dist",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true
|
|
11
|
+
},
|
|
12
|
+
"include": [
|
|
13
|
+
"src"
|
|
14
|
+
]
|
|
15
|
+
}
|