@fre4x/github 1.0.29 → 1.0.32
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/package.json +8 -6
- package/CHANGELOG.md +0 -63
- package/bun.lock +0 -25
- package/src/api.ts +0 -352
- package/src/index.test.ts +0 -246
- package/src/index.ts +0 -421
- package/src/mock.ts +0 -98
- package/src/types.ts +0 -130
- package/tsconfig.json +0 -11
package/package.json
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fre4x/github",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.32",
|
|
4
4
|
"description": "GitHub MCP server",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
7
6
|
"bin": {
|
|
8
7
|
"github": "dist/index.js"
|
|
9
8
|
},
|
|
9
|
+
"files": [
|
|
10
|
+
"dist"
|
|
11
|
+
],
|
|
10
12
|
"type": "module",
|
|
11
13
|
"scripts": {
|
|
12
|
-
"build": "npx esbuild src/index.ts --bundle --outfile=dist/index.js --platform=node --format=esm --banner:js=\"
|
|
13
|
-
"dev": "tsx src/index.ts",
|
|
14
|
+
"build": "npx esbuild src/index.ts --bundle --outfile=dist/index.js --platform=node --format=esm --banner:js=\"#!/usr/bin/env node\"",
|
|
14
15
|
"typecheck": "NODE_OPTIONS='--max-old-space-size=4096' tsc --noEmit",
|
|
16
|
+
"dev": "tsx src/index.ts",
|
|
17
|
+
"inspector": "MOCK=true npx @modelcontextprotocol/inspector dist/index.js",
|
|
15
18
|
"test": "vitest run --exclude dist",
|
|
16
|
-
"test:watch": "vitest"
|
|
17
|
-
"inspector": "mcp-inspector node dist/index.js"
|
|
19
|
+
"test:watch": "vitest"
|
|
18
20
|
},
|
|
19
21
|
"dependencies": {
|
|
20
22
|
"@modelcontextprotocol/sdk": "^1.26.0",
|
package/CHANGELOG.md
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
# Changelog
|
|
2
|
-
|
|
3
|
-
All notable changes to this package are documented here.
|
|
4
|
-
|
|
5
|
-
## [1.0.29] - 2026-03-01
|
|
6
|
-
|
|
7
|
-
- Minor updates and improvements
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
## [1.0.28] - 2026-03-01
|
|
11
|
-
|
|
12
|
-
- Minor updates and improvements
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
## [1.0.24] - 2026-03-01
|
|
16
|
-
|
|
17
|
-
- Minor updates and improvements
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
## [1.0.21] - 2026-03-01
|
|
21
|
-
|
|
22
|
-
- fix: arxiv test spawn, gemini types, zod v4 deps alignment (91f542f)
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
## [1.0.20] - 2026-03-01
|
|
26
|
-
|
|
27
|
-
- Minor updates and improvements
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
## [1.0.19] - 2026-03-01
|
|
31
|
-
|
|
32
|
-
- Minor updates and improvements
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
## [1.0.18] - 2026-03-01
|
|
36
|
-
|
|
37
|
-
- Minor updates and improvements
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
## [1.0.17] - 2026-03-01
|
|
41
|
-
|
|
42
|
-
- Minor updates and improvements
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
## [1.0.16] - 2026-03-01
|
|
46
|
-
|
|
47
|
-
- fix: add --format=esm to all esbuild scripts (hn, gemini, fred, yahoo-finance, github) (ce2bf91)
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
## [1.0.15] - 2026-03-01
|
|
51
|
-
|
|
52
|
-
- Minor updates and improvements
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
## [1.0.14] - 2026-03-01
|
|
56
|
-
|
|
57
|
-
- fix: always publish all packages, remove 'no changes' early exit (176ed37)
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
## [1.0.13] - 2026-03-01
|
|
61
|
-
|
|
62
|
-
- Minor updates and improvements
|
|
63
|
-
|
package/bun.lock
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"lockfileVersion": 1,
|
|
3
|
-
"workspaces": {
|
|
4
|
-
"": {
|
|
5
|
-
"name": "github",
|
|
6
|
-
"devDependencies": {
|
|
7
|
-
"@types/bun": "latest",
|
|
8
|
-
},
|
|
9
|
-
"peerDependencies": {
|
|
10
|
-
"typescript": "^5",
|
|
11
|
-
},
|
|
12
|
-
},
|
|
13
|
-
},
|
|
14
|
-
"packages": {
|
|
15
|
-
"@types/bun": ["@types/bun@1.3.9", "", { "dependencies": { "bun-types": "1.3.9" } }, "sha512-KQ571yULOdWJiMH+RIWIOZ7B2RXQGpL1YQrBtLIV3FqDcCu6FsbFUBwhdKUlCKUpS3PJDsHlJ1QKlpxoVR+xtw=="],
|
|
16
|
-
|
|
17
|
-
"@types/node": ["@types/node@25.3.2", "", { "dependencies": { "undici-types": "~7.18.0" } }, "sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q=="],
|
|
18
|
-
|
|
19
|
-
"bun-types": ["bun-types@1.3.9", "", { "dependencies": { "@types/node": "*" } }, "sha512-+UBWWOakIP4Tswh0Bt0QD0alpTY8cb5hvgiYeWCMet9YukHbzuruIEeXC2D7nMJPB12kbh8C7XJykSexEqGKJg=="],
|
|
20
|
-
|
|
21
|
-
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
22
|
-
|
|
23
|
-
"undici-types": ["undici-types@7.18.2", "", {}, "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w=="],
|
|
24
|
-
}
|
|
25
|
-
}
|
package/src/api.ts
DELETED
|
@@ -1,352 +0,0 @@
|
|
|
1
|
-
import { Octokit } from '@octokit/rest';
|
|
2
|
-
import type {
|
|
3
|
-
SearchRepositoriesParams,
|
|
4
|
-
SearchRepositoriesResponse,
|
|
5
|
-
SearchCodeParams,
|
|
6
|
-
SearchCodeResponse,
|
|
7
|
-
SearchIssuesAndPrsParams,
|
|
8
|
-
SearchIssuesAndPrsResponse,
|
|
9
|
-
GetRepositoryParams,
|
|
10
|
-
GetRepositoryResponse,
|
|
11
|
-
ListIssuesParams,
|
|
12
|
-
ListIssuesResponse,
|
|
13
|
-
CreateIssueParams,
|
|
14
|
-
CreateIssueResponse,
|
|
15
|
-
GetPullRequestParams,
|
|
16
|
-
GetPullRequestResponse,
|
|
17
|
-
ListPullRequestsParams,
|
|
18
|
-
ListPullRequestsResponse,
|
|
19
|
-
} from './types.js';
|
|
20
|
-
import { IS_MOCK, MOCK_FIXTURES } from './mock.js';
|
|
21
|
-
import { createApiError, createNotFoundError } from '@fre4x/mcp-shared';
|
|
22
|
-
|
|
23
|
-
const getOctokit = () => {
|
|
24
|
-
return new Octokit({
|
|
25
|
-
auth: process.env.GITHUB_PAT || process.env.GITHUB_TOKEN,
|
|
26
|
-
});
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
export async function searchRepositories(
|
|
30
|
-
params: SearchRepositoriesParams,
|
|
31
|
-
): Promise<SearchRepositoriesResponse> {
|
|
32
|
-
if (IS_MOCK()) return MOCK_FIXTURES.searchRepositories;
|
|
33
|
-
|
|
34
|
-
const limit = Math.max(1, params.limit ?? 10);
|
|
35
|
-
const offset = Math.max(0, params.offset ?? 0);
|
|
36
|
-
const per_page = 100;
|
|
37
|
-
const page = Math.floor(offset / per_page) + 1;
|
|
38
|
-
const offsetInPage = offset % per_page;
|
|
39
|
-
|
|
40
|
-
try {
|
|
41
|
-
const octokit = getOctokit();
|
|
42
|
-
const response = await octokit.rest.search.repos({
|
|
43
|
-
q: params.query,
|
|
44
|
-
per_page,
|
|
45
|
-
page,
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const items = response.data.items
|
|
49
|
-
.slice(offsetInPage, offsetInPage + limit)
|
|
50
|
-
.map((item) => ({
|
|
51
|
-
full_name: item.full_name,
|
|
52
|
-
description: item.description,
|
|
53
|
-
stargazers_count: item.stargazers_count,
|
|
54
|
-
html_url: item.html_url,
|
|
55
|
-
}));
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
items,
|
|
59
|
-
total: response.data.total_count,
|
|
60
|
-
};
|
|
61
|
-
} catch (error: unknown) {
|
|
62
|
-
const message =
|
|
63
|
-
error instanceof Error ? error.message : 'GitHub API error';
|
|
64
|
-
const status =
|
|
65
|
-
typeof error === 'object' && error !== null && 'status' in error
|
|
66
|
-
? Number(error.status)
|
|
67
|
-
: undefined;
|
|
68
|
-
throw createApiError(message, status);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export async function searchCode(
|
|
73
|
-
params: SearchCodeParams,
|
|
74
|
-
): Promise<SearchCodeResponse> {
|
|
75
|
-
if (IS_MOCK()) return MOCK_FIXTURES.searchCode;
|
|
76
|
-
|
|
77
|
-
const limit = Math.max(1, params.limit ?? 10);
|
|
78
|
-
const offset = Math.max(0, params.offset ?? 0);
|
|
79
|
-
const per_page = 100;
|
|
80
|
-
const page = Math.floor(offset / per_page) + 1;
|
|
81
|
-
const offsetInPage = offset % per_page;
|
|
82
|
-
|
|
83
|
-
try {
|
|
84
|
-
const octokit = getOctokit();
|
|
85
|
-
const response = await octokit.rest.search.code({
|
|
86
|
-
q: params.query,
|
|
87
|
-
per_page,
|
|
88
|
-
page,
|
|
89
|
-
});
|
|
90
|
-
|
|
91
|
-
const items = response.data.items
|
|
92
|
-
.slice(offsetInPage, offsetInPage + limit)
|
|
93
|
-
.map((item) => ({
|
|
94
|
-
name: item.name,
|
|
95
|
-
path: item.path,
|
|
96
|
-
repository: { full_name: item.repository.full_name },
|
|
97
|
-
html_url: item.html_url,
|
|
98
|
-
}));
|
|
99
|
-
|
|
100
|
-
return {
|
|
101
|
-
items,
|
|
102
|
-
total: response.data.total_count,
|
|
103
|
-
};
|
|
104
|
-
} catch (error: unknown) {
|
|
105
|
-
const message =
|
|
106
|
-
error instanceof Error ? error.message : 'GitHub API error';
|
|
107
|
-
const status =
|
|
108
|
-
typeof error === 'object' && error !== null && 'status' in error
|
|
109
|
-
? Number(error.status)
|
|
110
|
-
: undefined;
|
|
111
|
-
throw createApiError(message, status);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
export async function searchIssuesAndPrs(
|
|
116
|
-
params: SearchIssuesAndPrsParams,
|
|
117
|
-
): Promise<SearchIssuesAndPrsResponse> {
|
|
118
|
-
if (IS_MOCK()) return MOCK_FIXTURES.searchIssuesAndPrs;
|
|
119
|
-
|
|
120
|
-
const limit = Math.max(1, params.limit ?? 10);
|
|
121
|
-
const offset = Math.max(0, params.offset ?? 0);
|
|
122
|
-
const per_page = 100;
|
|
123
|
-
const page = Math.floor(offset / per_page) + 1;
|
|
124
|
-
const offsetInPage = offset % per_page;
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
const octokit = getOctokit();
|
|
128
|
-
const response = await octokit.rest.search.issuesAndPullRequests({
|
|
129
|
-
q: params.query,
|
|
130
|
-
per_page,
|
|
131
|
-
page,
|
|
132
|
-
});
|
|
133
|
-
|
|
134
|
-
const items = response.data.items
|
|
135
|
-
.slice(offsetInPage, offsetInPage + limit)
|
|
136
|
-
.map((item) => ({
|
|
137
|
-
title: item.title,
|
|
138
|
-
number: item.number,
|
|
139
|
-
state: item.state,
|
|
140
|
-
html_url: item.html_url,
|
|
141
|
-
repository_url: item.repository_url,
|
|
142
|
-
}));
|
|
143
|
-
|
|
144
|
-
return {
|
|
145
|
-
items,
|
|
146
|
-
total: response.data.total_count,
|
|
147
|
-
};
|
|
148
|
-
} catch (error: unknown) {
|
|
149
|
-
const message =
|
|
150
|
-
error instanceof Error ? error.message : 'GitHub API error';
|
|
151
|
-
const status =
|
|
152
|
-
typeof error === 'object' && error !== null && 'status' in error
|
|
153
|
-
? Number(error.status)
|
|
154
|
-
: undefined;
|
|
155
|
-
throw createApiError(message, status);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export async function getRepository(
|
|
160
|
-
params: GetRepositoryParams,
|
|
161
|
-
): Promise<GetRepositoryResponse> {
|
|
162
|
-
if (IS_MOCK()) return MOCK_FIXTURES.getRepository;
|
|
163
|
-
|
|
164
|
-
try {
|
|
165
|
-
const octokit = getOctokit();
|
|
166
|
-
const response = await octokit.rest.repos.get({
|
|
167
|
-
owner: params.owner,
|
|
168
|
-
repo: params.repo,
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
return {
|
|
172
|
-
full_name: response.data.full_name,
|
|
173
|
-
description: response.data.description,
|
|
174
|
-
stargazers_count: response.data.stargazers_count,
|
|
175
|
-
subscribers_count: response.data.subscribers_count,
|
|
176
|
-
forks_count: response.data.forks_count,
|
|
177
|
-
html_url: response.data.html_url,
|
|
178
|
-
default_branch: response.data.default_branch,
|
|
179
|
-
};
|
|
180
|
-
} catch (error: unknown) {
|
|
181
|
-
const status =
|
|
182
|
-
typeof error === 'object' && error !== null && 'status' in error
|
|
183
|
-
? Number(error.status)
|
|
184
|
-
: undefined;
|
|
185
|
-
if (status === 404)
|
|
186
|
-
throw createNotFoundError(
|
|
187
|
-
`Repository ${params.owner}/${params.repo} not found`,
|
|
188
|
-
);
|
|
189
|
-
const message =
|
|
190
|
-
error instanceof Error ? error.message : 'GitHub API error';
|
|
191
|
-
throw createApiError(message, status);
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
export async function listIssues(
|
|
196
|
-
params: ListIssuesParams,
|
|
197
|
-
): Promise<ListIssuesResponse> {
|
|
198
|
-
if (IS_MOCK()) return MOCK_FIXTURES.listIssues;
|
|
199
|
-
|
|
200
|
-
const limit = Math.max(1, params.limit ?? 10);
|
|
201
|
-
const offset = Math.max(0, params.offset ?? 0);
|
|
202
|
-
const per_page = 100;
|
|
203
|
-
const page = Math.floor(offset / per_page) + 1;
|
|
204
|
-
const offsetInPage = offset % per_page;
|
|
205
|
-
|
|
206
|
-
try {
|
|
207
|
-
const octokit = getOctokit();
|
|
208
|
-
const response = await octokit.rest.issues.listForRepo({
|
|
209
|
-
owner: params.owner,
|
|
210
|
-
repo: params.repo,
|
|
211
|
-
state: params.state || 'all',
|
|
212
|
-
per_page,
|
|
213
|
-
page,
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
const items = response.data
|
|
217
|
-
.slice(offsetInPage, offsetInPage + limit)
|
|
218
|
-
.map((item) => ({
|
|
219
|
-
title: item.title,
|
|
220
|
-
number: item.number,
|
|
221
|
-
state: item.state,
|
|
222
|
-
html_url: item.html_url,
|
|
223
|
-
body: item.body,
|
|
224
|
-
}));
|
|
225
|
-
|
|
226
|
-
return {
|
|
227
|
-
items,
|
|
228
|
-
total: items.length, // Still using slice length as total for now
|
|
229
|
-
};
|
|
230
|
-
} catch (error: unknown) {
|
|
231
|
-
const message =
|
|
232
|
-
error instanceof Error ? error.message : 'GitHub API error';
|
|
233
|
-
const status =
|
|
234
|
-
typeof error === 'object' && error !== null && 'status' in error
|
|
235
|
-
? Number(error.status)
|
|
236
|
-
: undefined;
|
|
237
|
-
throw createApiError(message, status);
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
export async function createIssue(
|
|
242
|
-
params: CreateIssueParams,
|
|
243
|
-
): Promise<CreateIssueResponse> {
|
|
244
|
-
if (IS_MOCK()) return MOCK_FIXTURES.createIssue;
|
|
245
|
-
|
|
246
|
-
try {
|
|
247
|
-
const octokit = getOctokit();
|
|
248
|
-
const response = await octokit.rest.issues.create({
|
|
249
|
-
owner: params.owner,
|
|
250
|
-
repo: params.repo,
|
|
251
|
-
title: params.title,
|
|
252
|
-
body: params.body,
|
|
253
|
-
});
|
|
254
|
-
|
|
255
|
-
return {
|
|
256
|
-
title: response.data.title,
|
|
257
|
-
number: response.data.number,
|
|
258
|
-
state: response.data.state,
|
|
259
|
-
html_url: response.data.html_url,
|
|
260
|
-
};
|
|
261
|
-
} catch (error: unknown) {
|
|
262
|
-
const message =
|
|
263
|
-
error instanceof Error ? error.message : 'GitHub API error';
|
|
264
|
-
const status =
|
|
265
|
-
typeof error === 'object' && error !== null && 'status' in error
|
|
266
|
-
? Number(error.status)
|
|
267
|
-
: undefined;
|
|
268
|
-
throw createApiError(message, status);
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
export async function getPullRequest(
|
|
273
|
-
params: GetPullRequestParams,
|
|
274
|
-
): Promise<GetPullRequestResponse> {
|
|
275
|
-
if (IS_MOCK()) return MOCK_FIXTURES.getPullRequest;
|
|
276
|
-
|
|
277
|
-
try {
|
|
278
|
-
const octokit = getOctokit();
|
|
279
|
-
const response = await octokit.rest.pulls.get({
|
|
280
|
-
owner: params.owner,
|
|
281
|
-
repo: params.repo,
|
|
282
|
-
pull_number: params.pull_number,
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
return {
|
|
286
|
-
title: response.data.title,
|
|
287
|
-
number: response.data.number,
|
|
288
|
-
state: response.data.state,
|
|
289
|
-
html_url: response.data.html_url,
|
|
290
|
-
body: response.data.body,
|
|
291
|
-
merged: response.data.merged,
|
|
292
|
-
};
|
|
293
|
-
} catch (error: unknown) {
|
|
294
|
-
const status =
|
|
295
|
-
typeof error === 'object' && error !== null && 'status' in error
|
|
296
|
-
? Number(error.status)
|
|
297
|
-
: undefined;
|
|
298
|
-
if (status === 404)
|
|
299
|
-
throw createNotFoundError(
|
|
300
|
-
`Pull request ${params.pull_number} not found`,
|
|
301
|
-
);
|
|
302
|
-
const message =
|
|
303
|
-
error instanceof Error ? error.message : 'GitHub API error';
|
|
304
|
-
throw createApiError(message, status);
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
export async function listPullRequests(
|
|
309
|
-
params: ListPullRequestsParams,
|
|
310
|
-
): Promise<ListPullRequestsResponse> {
|
|
311
|
-
if (IS_MOCK()) return MOCK_FIXTURES.listPullRequests;
|
|
312
|
-
|
|
313
|
-
const limit = Math.max(1, params.limit ?? 10);
|
|
314
|
-
const offset = Math.max(0, params.offset ?? 0);
|
|
315
|
-
const per_page = 100;
|
|
316
|
-
const page = Math.floor(offset / per_page) + 1;
|
|
317
|
-
const offsetInPage = offset % per_page;
|
|
318
|
-
|
|
319
|
-
try {
|
|
320
|
-
const octokit = getOctokit();
|
|
321
|
-
const response = await octokit.rest.pulls.list({
|
|
322
|
-
owner: params.owner,
|
|
323
|
-
repo: params.repo,
|
|
324
|
-
state: params.state || 'all',
|
|
325
|
-
per_page,
|
|
326
|
-
page,
|
|
327
|
-
});
|
|
328
|
-
|
|
329
|
-
const items = response.data
|
|
330
|
-
.slice(offsetInPage, offsetInPage + limit)
|
|
331
|
-
.map((item) => ({
|
|
332
|
-
title: item.title,
|
|
333
|
-
number: item.number,
|
|
334
|
-
state: item.state,
|
|
335
|
-
html_url: item.html_url,
|
|
336
|
-
body: item.body,
|
|
337
|
-
}));
|
|
338
|
-
|
|
339
|
-
return {
|
|
340
|
-
items,
|
|
341
|
-
total: items.length, // Still using slice length as total for now
|
|
342
|
-
};
|
|
343
|
-
} catch (error: unknown) {
|
|
344
|
-
const message =
|
|
345
|
-
error instanceof Error ? error.message : 'GitHub API error';
|
|
346
|
-
const status =
|
|
347
|
-
typeof error === 'object' && error !== null && 'status' in error
|
|
348
|
-
? Number(error.status)
|
|
349
|
-
: undefined;
|
|
350
|
-
throw createApiError(message, status);
|
|
351
|
-
}
|
|
352
|
-
}
|
package/src/index.test.ts
DELETED
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
import { describe, it, expect, beforeAll } from 'vitest';
|
|
2
|
-
import {
|
|
3
|
-
ListToolsResultSchema,
|
|
4
|
-
CallToolResultSchema,
|
|
5
|
-
type TextContent,
|
|
6
|
-
} from '@modelcontextprotocol/sdk/types.js';
|
|
7
|
-
import { server } from './index.js';
|
|
8
|
-
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
|
|
9
|
-
import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js';
|
|
10
|
-
|
|
11
|
-
// Ensure mock mode is enabled for tests
|
|
12
|
-
process.env.MOCK = 'true';
|
|
13
|
-
|
|
14
|
-
describe('GitHub MCP Server', () => {
|
|
15
|
-
let client: Client;
|
|
16
|
-
let transport: ReturnType<typeof InMemoryTransport.createLinkedPair>[0];
|
|
17
|
-
|
|
18
|
-
beforeAll(async () => {
|
|
19
|
-
const [_clientTransport, serverTransport] =
|
|
20
|
-
InMemoryTransport.createLinkedPair();
|
|
21
|
-
transport = _clientTransport;
|
|
22
|
-
|
|
23
|
-
await server.connect(serverTransport);
|
|
24
|
-
|
|
25
|
-
client = new Client({
|
|
26
|
-
name: 'test-client',
|
|
27
|
-
version: '1.0.0',
|
|
28
|
-
});
|
|
29
|
-
await client.connect(transport);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
describe('Tools', () => {
|
|
33
|
-
it('should list all available tools', async () => {
|
|
34
|
-
const response = await client.request(
|
|
35
|
-
{ method: 'tools/list' },
|
|
36
|
-
ListToolsResultSchema,
|
|
37
|
-
);
|
|
38
|
-
expect(response.tools).toBeDefined();
|
|
39
|
-
expect(response.tools.length).toBe(8);
|
|
40
|
-
|
|
41
|
-
const toolNames = response.tools.map((t) => t.name);
|
|
42
|
-
expect(toolNames).toContain('github_search_repositories');
|
|
43
|
-
expect(toolNames).toContain('github_search_code');
|
|
44
|
-
expect(toolNames).toContain('github_search_issues_and_prs');
|
|
45
|
-
expect(toolNames).toContain('github_get_repository');
|
|
46
|
-
expect(toolNames).toContain('github_list_issues');
|
|
47
|
-
expect(toolNames).toContain('github_create_issue');
|
|
48
|
-
expect(toolNames).toContain('github_get_pull_request');
|
|
49
|
-
expect(toolNames).toContain('github_list_pull_requests');
|
|
50
|
-
});
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
describe('Handlers (Mock Mode)', () => {
|
|
54
|
-
it('handles github_search_repositories', async () => {
|
|
55
|
-
const response = await client.request(
|
|
56
|
-
{
|
|
57
|
-
method: 'tools/call',
|
|
58
|
-
params: {
|
|
59
|
-
name: 'github_search_repositories',
|
|
60
|
-
arguments: { query: 'mcp-server', limit: 5 },
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
CallToolResultSchema,
|
|
64
|
-
);
|
|
65
|
-
|
|
66
|
-
expect(response.isError).toBeUndefined();
|
|
67
|
-
expect((response.content[0] as TextContent).text).toContain(
|
|
68
|
-
'fre4x/mcp-server',
|
|
69
|
-
);
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('handles github_search_code', async () => {
|
|
73
|
-
const response = await client.request(
|
|
74
|
-
{
|
|
75
|
-
method: 'tools/call',
|
|
76
|
-
params: {
|
|
77
|
-
name: 'github_search_code',
|
|
78
|
-
arguments: { query: 'index.ts', limit: 5 },
|
|
79
|
-
},
|
|
80
|
-
},
|
|
81
|
-
CallToolResultSchema,
|
|
82
|
-
);
|
|
83
|
-
|
|
84
|
-
expect(response.isError).toBeUndefined();
|
|
85
|
-
expect((response.content[0] as TextContent).text).toContain(
|
|
86
|
-
'index.ts',
|
|
87
|
-
);
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it('handles github_search_issues_and_prs', async () => {
|
|
91
|
-
const response = await client.request(
|
|
92
|
-
{
|
|
93
|
-
method: 'tools/call',
|
|
94
|
-
params: {
|
|
95
|
-
name: 'github_search_issues_and_prs',
|
|
96
|
-
arguments: { query: 'mock issue', limit: 5 },
|
|
97
|
-
},
|
|
98
|
-
},
|
|
99
|
-
CallToolResultSchema,
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
expect(response.isError).toBeUndefined();
|
|
103
|
-
expect((response.content[0] as TextContent).text).toContain(
|
|
104
|
-
'Mock issue title',
|
|
105
|
-
);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
it('handles github_get_repository', async () => {
|
|
109
|
-
const response = await client.request(
|
|
110
|
-
{
|
|
111
|
-
method: 'tools/call',
|
|
112
|
-
params: {
|
|
113
|
-
name: 'github_get_repository',
|
|
114
|
-
arguments: { owner: 'fre4x', repo: 'mcp-server' },
|
|
115
|
-
},
|
|
116
|
-
},
|
|
117
|
-
CallToolResultSchema,
|
|
118
|
-
);
|
|
119
|
-
|
|
120
|
-
expect(response.isError).toBeUndefined();
|
|
121
|
-
expect((response.content[0] as TextContent).text).toContain(
|
|
122
|
-
'fre4x/mcp-server',
|
|
123
|
-
);
|
|
124
|
-
expect((response.content[0] as TextContent).text).toContain(
|
|
125
|
-
'Mock repository.',
|
|
126
|
-
);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('handles github_list_issues', async () => {
|
|
130
|
-
const response = await client.request(
|
|
131
|
-
{
|
|
132
|
-
method: 'tools/call',
|
|
133
|
-
params: {
|
|
134
|
-
name: 'github_list_issues',
|
|
135
|
-
arguments: { owner: 'fre4x', repo: 'mcp-server' },
|
|
136
|
-
},
|
|
137
|
-
},
|
|
138
|
-
CallToolResultSchema,
|
|
139
|
-
);
|
|
140
|
-
|
|
141
|
-
expect(response.isError).toBeUndefined();
|
|
142
|
-
expect((response.content[0] as TextContent).text).toContain(
|
|
143
|
-
'Fix bug in handler',
|
|
144
|
-
);
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('handles github_create_issue', async () => {
|
|
148
|
-
const response = await client.request(
|
|
149
|
-
{
|
|
150
|
-
method: 'tools/call',
|
|
151
|
-
params: {
|
|
152
|
-
name: 'github_create_issue',
|
|
153
|
-
arguments: {
|
|
154
|
-
owner: 'fre4x',
|
|
155
|
-
repo: 'mcp-server',
|
|
156
|
-
title: 'New mocked issue',
|
|
157
|
-
body: 'test',
|
|
158
|
-
},
|
|
159
|
-
},
|
|
160
|
-
},
|
|
161
|
-
CallToolResultSchema,
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
expect(response.isError).toBeUndefined();
|
|
165
|
-
expect((response.content[0] as TextContent).text).toContain(
|
|
166
|
-
'New mocked issue',
|
|
167
|
-
);
|
|
168
|
-
});
|
|
169
|
-
|
|
170
|
-
it('handles github_get_pull_request', async () => {
|
|
171
|
-
const response = await client.request(
|
|
172
|
-
{
|
|
173
|
-
method: 'tools/call',
|
|
174
|
-
params: {
|
|
175
|
-
name: 'github_get_pull_request',
|
|
176
|
-
arguments: {
|
|
177
|
-
owner: 'fre4x',
|
|
178
|
-
repo: 'mcp-server',
|
|
179
|
-
pull_number: 4,
|
|
180
|
-
},
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
CallToolResultSchema,
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
expect(response.isError).toBeUndefined();
|
|
187
|
-
expect((response.content[0] as TextContent).text).toContain(
|
|
188
|
-
'Mock PR title',
|
|
189
|
-
);
|
|
190
|
-
});
|
|
191
|
-
|
|
192
|
-
it('handles github_list_pull_requests', async () => {
|
|
193
|
-
const response = await client.request(
|
|
194
|
-
{
|
|
195
|
-
method: 'tools/call',
|
|
196
|
-
params: {
|
|
197
|
-
name: 'github_list_pull_requests',
|
|
198
|
-
arguments: { owner: 'fre4x', repo: 'mcp-server' },
|
|
199
|
-
},
|
|
200
|
-
},
|
|
201
|
-
CallToolResultSchema,
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
expect(response.isError).toBeUndefined();
|
|
205
|
-
expect((response.content[0] as TextContent).text).toContain(
|
|
206
|
-
'Update dependencies',
|
|
207
|
-
);
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it('returns validation error for missing required arguments', async () => {
|
|
211
|
-
const response = await client.request(
|
|
212
|
-
{
|
|
213
|
-
method: 'tools/call',
|
|
214
|
-
params: {
|
|
215
|
-
name: 'github_get_repository',
|
|
216
|
-
arguments: { owner: 'fre4x' }, // Missing repo
|
|
217
|
-
},
|
|
218
|
-
},
|
|
219
|
-
CallToolResultSchema,
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
expect(response.isError).toBe(true);
|
|
223
|
-
expect((response.content[0] as TextContent).text).toContain(
|
|
224
|
-
'Validation Error',
|
|
225
|
-
);
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
it('returns error for unknown tool', async () => {
|
|
229
|
-
const response = await client.request(
|
|
230
|
-
{
|
|
231
|
-
method: 'tools/call',
|
|
232
|
-
params: {
|
|
233
|
-
name: 'unknown_tool',
|
|
234
|
-
arguments: {},
|
|
235
|
-
},
|
|
236
|
-
},
|
|
237
|
-
CallToolResultSchema,
|
|
238
|
-
);
|
|
239
|
-
|
|
240
|
-
expect(response.isError).toBe(true);
|
|
241
|
-
expect((response.content[0] as TextContent).text).toContain(
|
|
242
|
-
'Unknown tool',
|
|
243
|
-
);
|
|
244
|
-
});
|
|
245
|
-
});
|
|
246
|
-
});
|
package/src/index.ts
DELETED
|
@@ -1,421 +0,0 @@
|
|
|
1
|
-
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
2
|
-
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
3
|
-
import {
|
|
4
|
-
CallToolRequestSchema,
|
|
5
|
-
ListToolsRequestSchema,
|
|
6
|
-
type Tool,
|
|
7
|
-
type CallToolResult,
|
|
8
|
-
} from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
-
import * as zod from 'zod';
|
|
10
|
-
// biome-ignore lint/suspicious/noExplicitAny: compatibility pattern
|
|
11
|
-
const z = zod.z || (zod as any).default || zod;
|
|
12
|
-
import { pathToFileURL } from 'node:url';
|
|
13
|
-
import {
|
|
14
|
-
truncateToLimit,
|
|
15
|
-
createInternalError,
|
|
16
|
-
createValidationError,
|
|
17
|
-
formatMarkdownSection,
|
|
18
|
-
} from '@fre4x/mcp-shared';
|
|
19
|
-
|
|
20
|
-
import {
|
|
21
|
-
searchRepositories,
|
|
22
|
-
searchCode,
|
|
23
|
-
searchIssuesAndPrs,
|
|
24
|
-
getRepository,
|
|
25
|
-
listIssues,
|
|
26
|
-
createIssue,
|
|
27
|
-
getPullRequest,
|
|
28
|
-
listPullRequests,
|
|
29
|
-
} from './api.js';
|
|
30
|
-
|
|
31
|
-
const server = new Server(
|
|
32
|
-
{
|
|
33
|
-
name: '@fre4x/github',
|
|
34
|
-
version: '1.0.0',
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
capabilities: {
|
|
38
|
-
tools: {},
|
|
39
|
-
},
|
|
40
|
-
},
|
|
41
|
-
);
|
|
42
|
-
|
|
43
|
-
const paginationShape = {
|
|
44
|
-
limit: z
|
|
45
|
-
.number()
|
|
46
|
-
.int()
|
|
47
|
-
.min(1)
|
|
48
|
-
.max(100)
|
|
49
|
-
.default(20)
|
|
50
|
-
.describe('Maximum number of results to return (1–100, default 20)'),
|
|
51
|
-
offset: z
|
|
52
|
-
.number()
|
|
53
|
-
.int()
|
|
54
|
-
.min(0)
|
|
55
|
-
.default(0)
|
|
56
|
-
.describe('Number of results to skip for pagination (default 0)'),
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
const searchRepositoriesSchema = z.object({
|
|
60
|
-
query: z.string().describe('Query to search repositories'),
|
|
61
|
-
...paginationShape,
|
|
62
|
-
});
|
|
63
|
-
|
|
64
|
-
const searchCodeSchema = z.object({
|
|
65
|
-
query: z.string().describe('Query to search code'),
|
|
66
|
-
...paginationShape,
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
const searchIssuesAndPrsSchema = z.object({
|
|
70
|
-
query: z.string().describe('Query to search issues and PRs'),
|
|
71
|
-
...paginationShape,
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
const getRepositorySchema = z.object({
|
|
75
|
-
owner: z.string().describe('Repository owner'),
|
|
76
|
-
repo: z.string().describe('Repository name'),
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
const listIssuesSchema = z.object({
|
|
80
|
-
owner: z.string().describe('Repository owner'),
|
|
81
|
-
repo: z.string().describe('Repository name'),
|
|
82
|
-
state: z
|
|
83
|
-
.enum(['open', 'closed', 'all'])
|
|
84
|
-
.optional()
|
|
85
|
-
.describe('State of issues to list'),
|
|
86
|
-
...paginationShape,
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
const createIssueSchema = z.object({
|
|
90
|
-
owner: z.string().describe('Repository owner'),
|
|
91
|
-
repo: z.string().describe('Repository name'),
|
|
92
|
-
title: z.string().describe('Title of the new issue'),
|
|
93
|
-
body: z.string().optional().describe('Body of the new issue'),
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
const getPullRequestSchema = z.object({
|
|
97
|
-
owner: z.string().describe('Repository owner'),
|
|
98
|
-
repo: z.string().describe('Repository name'),
|
|
99
|
-
pull_number: z.number().describe('Pull request number'),
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
const listPullRequestsSchema = z.object({
|
|
103
|
-
owner: z.string().describe('Repository owner'),
|
|
104
|
-
repo: z.string().describe('Repository name'),
|
|
105
|
-
state: z
|
|
106
|
-
.enum(['open', 'closed', 'all'])
|
|
107
|
-
.optional()
|
|
108
|
-
.describe('State of PRs to list'),
|
|
109
|
-
...paginationShape,
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
// --- Tools ---
|
|
113
|
-
const tools: Tool[] = [
|
|
114
|
-
{
|
|
115
|
-
name: 'github_search_repositories',
|
|
116
|
-
description: 'Search for GitHub repositories',
|
|
117
|
-
inputSchema: {
|
|
118
|
-
type: 'object',
|
|
119
|
-
properties: {
|
|
120
|
-
query: { type: 'string' },
|
|
121
|
-
limit: { type: 'number' },
|
|
122
|
-
offset: { type: 'number' },
|
|
123
|
-
},
|
|
124
|
-
required: ['query'],
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
{
|
|
128
|
-
name: 'github_search_code',
|
|
129
|
-
description: 'Search for code within GitHub repositories',
|
|
130
|
-
inputSchema: {
|
|
131
|
-
type: 'object',
|
|
132
|
-
properties: {
|
|
133
|
-
query: { type: 'string' },
|
|
134
|
-
limit: { type: 'number' },
|
|
135
|
-
offset: { type: 'number' },
|
|
136
|
-
},
|
|
137
|
-
required: ['query'],
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
{
|
|
141
|
-
name: 'github_search_issues_and_prs',
|
|
142
|
-
description: 'Search for issues and pull requests',
|
|
143
|
-
inputSchema: {
|
|
144
|
-
type: 'object',
|
|
145
|
-
properties: {
|
|
146
|
-
query: { type: 'string' },
|
|
147
|
-
limit: { type: 'number' },
|
|
148
|
-
offset: { type: 'number' },
|
|
149
|
-
},
|
|
150
|
-
required: ['query'],
|
|
151
|
-
},
|
|
152
|
-
},
|
|
153
|
-
{
|
|
154
|
-
name: 'github_get_repository',
|
|
155
|
-
description: 'Get details for a specific repository',
|
|
156
|
-
inputSchema: {
|
|
157
|
-
type: 'object',
|
|
158
|
-
properties: {
|
|
159
|
-
owner: { type: 'string' },
|
|
160
|
-
repo: { type: 'string' },
|
|
161
|
-
},
|
|
162
|
-
required: ['owner', 'repo'],
|
|
163
|
-
},
|
|
164
|
-
},
|
|
165
|
-
{
|
|
166
|
-
name: 'github_list_issues',
|
|
167
|
-
description: 'List issues in a specific repository',
|
|
168
|
-
inputSchema: {
|
|
169
|
-
type: 'object',
|
|
170
|
-
properties: {
|
|
171
|
-
owner: { type: 'string' },
|
|
172
|
-
repo: { type: 'string' },
|
|
173
|
-
state: { type: 'string', enum: ['open', 'closed', 'all'] },
|
|
174
|
-
limit: { type: 'number' },
|
|
175
|
-
offset: { type: 'number' },
|
|
176
|
-
},
|
|
177
|
-
required: ['owner', 'repo'],
|
|
178
|
-
},
|
|
179
|
-
},
|
|
180
|
-
{
|
|
181
|
-
name: 'github_create_issue',
|
|
182
|
-
description: 'Create an issue in a repository',
|
|
183
|
-
inputSchema: {
|
|
184
|
-
type: 'object',
|
|
185
|
-
properties: {
|
|
186
|
-
owner: { type: 'string' },
|
|
187
|
-
repo: { type: 'string' },
|
|
188
|
-
title: { type: 'string' },
|
|
189
|
-
body: { type: 'string' },
|
|
190
|
-
},
|
|
191
|
-
required: ['owner', 'repo', 'title'],
|
|
192
|
-
},
|
|
193
|
-
},
|
|
194
|
-
{
|
|
195
|
-
name: 'github_get_pull_request',
|
|
196
|
-
description: 'Get details for a specific pull request',
|
|
197
|
-
inputSchema: {
|
|
198
|
-
type: 'object',
|
|
199
|
-
properties: {
|
|
200
|
-
owner: { type: 'string' },
|
|
201
|
-
repo: { type: 'string' },
|
|
202
|
-
pull_number: { type: 'number' },
|
|
203
|
-
},
|
|
204
|
-
required: ['owner', 'repo', 'pull_number'],
|
|
205
|
-
},
|
|
206
|
-
},
|
|
207
|
-
{
|
|
208
|
-
name: 'github_list_pull_requests',
|
|
209
|
-
description: 'List pull requests in a specific repository',
|
|
210
|
-
inputSchema: {
|
|
211
|
-
type: 'object',
|
|
212
|
-
properties: {
|
|
213
|
-
owner: { type: 'string' },
|
|
214
|
-
repo: { type: 'string' },
|
|
215
|
-
state: { type: 'string', enum: ['open', 'closed', 'all'] },
|
|
216
|
-
limit: { type: 'number' },
|
|
217
|
-
offset: { type: 'number' },
|
|
218
|
-
},
|
|
219
|
-
required: ['owner', 'repo'],
|
|
220
|
-
},
|
|
221
|
-
},
|
|
222
|
-
];
|
|
223
|
-
|
|
224
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
225
|
-
return { tools };
|
|
226
|
-
});
|
|
227
|
-
|
|
228
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
229
|
-
try {
|
|
230
|
-
switch (request.params.name) {
|
|
231
|
-
case 'github_search_repositories': {
|
|
232
|
-
const rawArgs = request.params.arguments ?? {};
|
|
233
|
-
const args = searchRepositoriesSchema.parse(rawArgs);
|
|
234
|
-
const limit = args.limit;
|
|
235
|
-
const offset = args.offset;
|
|
236
|
-
const result = await searchRepositories({
|
|
237
|
-
query: args.query,
|
|
238
|
-
limit,
|
|
239
|
-
offset,
|
|
240
|
-
});
|
|
241
|
-
return {
|
|
242
|
-
content: [
|
|
243
|
-
{
|
|
244
|
-
type: 'text',
|
|
245
|
-
text: truncateToLimit(
|
|
246
|
-
JSON.stringify(result, null, 2),
|
|
247
|
-
),
|
|
248
|
-
},
|
|
249
|
-
],
|
|
250
|
-
};
|
|
251
|
-
}
|
|
252
|
-
case 'github_search_code': {
|
|
253
|
-
const rawArgs = request.params.arguments ?? {};
|
|
254
|
-
const args = searchCodeSchema.parse(rawArgs);
|
|
255
|
-
const limit = args.limit;
|
|
256
|
-
const offset = args.offset;
|
|
257
|
-
const result = await searchCode({
|
|
258
|
-
query: args.query,
|
|
259
|
-
limit,
|
|
260
|
-
offset,
|
|
261
|
-
});
|
|
262
|
-
return {
|
|
263
|
-
content: [
|
|
264
|
-
{
|
|
265
|
-
type: 'text',
|
|
266
|
-
text: truncateToLimit(
|
|
267
|
-
JSON.stringify(result, null, 2),
|
|
268
|
-
),
|
|
269
|
-
},
|
|
270
|
-
],
|
|
271
|
-
};
|
|
272
|
-
}
|
|
273
|
-
case 'github_search_issues_and_prs': {
|
|
274
|
-
const rawArgs = request.params.arguments ?? {};
|
|
275
|
-
const args = searchIssuesAndPrsSchema.parse(rawArgs);
|
|
276
|
-
const limit = args.limit;
|
|
277
|
-
const offset = args.offset;
|
|
278
|
-
const result = await searchIssuesAndPrs({
|
|
279
|
-
query: args.query,
|
|
280
|
-
limit,
|
|
281
|
-
offset,
|
|
282
|
-
});
|
|
283
|
-
return {
|
|
284
|
-
content: [
|
|
285
|
-
{
|
|
286
|
-
type: 'text',
|
|
287
|
-
text: truncateToLimit(
|
|
288
|
-
JSON.stringify(result, null, 2),
|
|
289
|
-
),
|
|
290
|
-
},
|
|
291
|
-
],
|
|
292
|
-
};
|
|
293
|
-
}
|
|
294
|
-
case 'github_get_repository': {
|
|
295
|
-
const rawArgs = request.params.arguments ?? {};
|
|
296
|
-
const args = getRepositorySchema.parse(rawArgs);
|
|
297
|
-
const result = await getRepository(args);
|
|
298
|
-
return {
|
|
299
|
-
content: [
|
|
300
|
-
{
|
|
301
|
-
type: 'text',
|
|
302
|
-
text: formatMarkdownSection(
|
|
303
|
-
result.full_name,
|
|
304
|
-
JSON.stringify(result, null, 2),
|
|
305
|
-
),
|
|
306
|
-
},
|
|
307
|
-
],
|
|
308
|
-
};
|
|
309
|
-
}
|
|
310
|
-
case 'github_list_issues': {
|
|
311
|
-
const rawArgs = request.params.arguments ?? {};
|
|
312
|
-
const args = listIssuesSchema.parse(rawArgs);
|
|
313
|
-
const limit = args.limit;
|
|
314
|
-
const offset = args.offset;
|
|
315
|
-
const result = await listIssues({
|
|
316
|
-
owner: args.owner,
|
|
317
|
-
repo: args.repo,
|
|
318
|
-
state: args.state,
|
|
319
|
-
limit,
|
|
320
|
-
offset,
|
|
321
|
-
});
|
|
322
|
-
return {
|
|
323
|
-
content: [
|
|
324
|
-
{
|
|
325
|
-
type: 'text',
|
|
326
|
-
text: truncateToLimit(
|
|
327
|
-
JSON.stringify(result, null, 2),
|
|
328
|
-
),
|
|
329
|
-
},
|
|
330
|
-
],
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
case 'github_create_issue': {
|
|
334
|
-
const rawArgs = request.params.arguments ?? {};
|
|
335
|
-
const args = createIssueSchema.parse(rawArgs);
|
|
336
|
-
const result = await createIssue(args);
|
|
337
|
-
return {
|
|
338
|
-
content: [
|
|
339
|
-
{
|
|
340
|
-
type: 'text',
|
|
341
|
-
text: formatMarkdownSection(
|
|
342
|
-
result.title,
|
|
343
|
-
JSON.stringify(result, null, 2),
|
|
344
|
-
),
|
|
345
|
-
},
|
|
346
|
-
],
|
|
347
|
-
};
|
|
348
|
-
}
|
|
349
|
-
case 'github_get_pull_request': {
|
|
350
|
-
const rawArgs = request.params.arguments ?? {};
|
|
351
|
-
const args = getPullRequestSchema.parse(rawArgs);
|
|
352
|
-
const result = await getPullRequest(args);
|
|
353
|
-
return {
|
|
354
|
-
content: [
|
|
355
|
-
{
|
|
356
|
-
type: 'text',
|
|
357
|
-
text: formatMarkdownSection(
|
|
358
|
-
result.title,
|
|
359
|
-
JSON.stringify(result, null, 2),
|
|
360
|
-
),
|
|
361
|
-
},
|
|
362
|
-
],
|
|
363
|
-
};
|
|
364
|
-
}
|
|
365
|
-
case 'github_list_pull_requests': {
|
|
366
|
-
const rawArgs = request.params.arguments ?? {};
|
|
367
|
-
const args = listPullRequestsSchema.parse(rawArgs);
|
|
368
|
-
const limit = args.limit;
|
|
369
|
-
const offset = args.offset;
|
|
370
|
-
const result = await listPullRequests({
|
|
371
|
-
owner: args.owner,
|
|
372
|
-
repo: args.repo,
|
|
373
|
-
state: args.state,
|
|
374
|
-
limit,
|
|
375
|
-
offset,
|
|
376
|
-
});
|
|
377
|
-
return {
|
|
378
|
-
content: [
|
|
379
|
-
{
|
|
380
|
-
type: 'text',
|
|
381
|
-
text: truncateToLimit(
|
|
382
|
-
JSON.stringify(result, null, 2),
|
|
383
|
-
),
|
|
384
|
-
},
|
|
385
|
-
],
|
|
386
|
-
};
|
|
387
|
-
}
|
|
388
|
-
default:
|
|
389
|
-
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
390
|
-
}
|
|
391
|
-
} catch (error: unknown) {
|
|
392
|
-
if (error instanceof z.ZodError) {
|
|
393
|
-
return createValidationError('Arguments', error.message);
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
const isMcpError = (err: unknown): err is CallToolResult =>
|
|
397
|
-
!!err &&
|
|
398
|
-
typeof err === 'object' &&
|
|
399
|
-
'isError' in err &&
|
|
400
|
-
(err as Record<string, unknown>).isError === true &&
|
|
401
|
-
'content' in err &&
|
|
402
|
-
Array.isArray((err as Record<string, unknown>).content);
|
|
403
|
-
|
|
404
|
-
if (isMcpError(error)) {
|
|
405
|
-
return error;
|
|
406
|
-
}
|
|
407
|
-
return createInternalError(error);
|
|
408
|
-
}
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
async function run() {
|
|
412
|
-
const transport = new StdioServerTransport();
|
|
413
|
-
await server.connect(transport);
|
|
414
|
-
console.error('GitHub MCP server running on stdio');
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
if (import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
418
|
-
run().catch(console.error);
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
export { server };
|
package/src/mock.ts
DELETED
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
SearchRepositoriesResponse,
|
|
3
|
-
SearchCodeResponse,
|
|
4
|
-
SearchIssuesAndPrsResponse,
|
|
5
|
-
GetRepositoryResponse,
|
|
6
|
-
ListIssuesResponse,
|
|
7
|
-
CreateIssueResponse,
|
|
8
|
-
GetPullRequestResponse,
|
|
9
|
-
ListPullRequestsResponse,
|
|
10
|
-
} from './types.js';
|
|
11
|
-
|
|
12
|
-
export const IS_MOCK = () =>
|
|
13
|
-
process.env.MOCK === 'true' || process.env.GITHUB_MOCK === 'true';
|
|
14
|
-
|
|
15
|
-
export const MOCK_FIXTURES = {
|
|
16
|
-
searchRepositories: {
|
|
17
|
-
items: [
|
|
18
|
-
{
|
|
19
|
-
full_name: 'fre4x/mcp-server',
|
|
20
|
-
description: 'Mock MCP server repository.',
|
|
21
|
-
stargazers_count: 42,
|
|
22
|
-
html_url: 'https://github.com/fre4x/mcp-server',
|
|
23
|
-
},
|
|
24
|
-
],
|
|
25
|
-
total: 1,
|
|
26
|
-
} as SearchRepositoriesResponse,
|
|
27
|
-
searchCode: {
|
|
28
|
-
items: [
|
|
29
|
-
{
|
|
30
|
-
name: 'index.ts',
|
|
31
|
-
path: 'src/index.ts',
|
|
32
|
-
repository: { full_name: 'fre4x/mcp-server' },
|
|
33
|
-
html_url:
|
|
34
|
-
'https://github.com/fre4x/mcp-server/blob/main/src/index.ts',
|
|
35
|
-
},
|
|
36
|
-
],
|
|
37
|
-
total: 1,
|
|
38
|
-
} as SearchCodeResponse,
|
|
39
|
-
searchIssuesAndPrs: {
|
|
40
|
-
items: [
|
|
41
|
-
{
|
|
42
|
-
title: 'Mock issue title',
|
|
43
|
-
number: 1,
|
|
44
|
-
state: 'open',
|
|
45
|
-
html_url: 'https://github.com/fre4x/mcp-server/issues/1',
|
|
46
|
-
repository_url: 'https://api.github.com/repos/fre4x/mcp-server',
|
|
47
|
-
},
|
|
48
|
-
],
|
|
49
|
-
total: 1,
|
|
50
|
-
} as SearchIssuesAndPrsResponse,
|
|
51
|
-
getRepository: {
|
|
52
|
-
full_name: 'fre4x/mcp-server',
|
|
53
|
-
description: 'Mock repository.',
|
|
54
|
-
stargazers_count: 100,
|
|
55
|
-
subscribers_count: 10,
|
|
56
|
-
forks_count: 5,
|
|
57
|
-
html_url: 'https://github.com/fre4x/mcp-server',
|
|
58
|
-
default_branch: 'main',
|
|
59
|
-
} as GetRepositoryResponse,
|
|
60
|
-
listIssues: {
|
|
61
|
-
items: [
|
|
62
|
-
{
|
|
63
|
-
title: 'Fix bug in handler',
|
|
64
|
-
number: 2,
|
|
65
|
-
state: 'open',
|
|
66
|
-
html_url: 'https://github.com/fre4x/mcp-server/issues/2',
|
|
67
|
-
body: 'Body of the issue',
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
|
-
total: 1,
|
|
71
|
-
} as ListIssuesResponse,
|
|
72
|
-
createIssue: {
|
|
73
|
-
title: 'New mocked issue',
|
|
74
|
-
number: 3,
|
|
75
|
-
state: 'open',
|
|
76
|
-
html_url: 'https://github.com/fre4x/mcp-server/issues/3',
|
|
77
|
-
} as CreateIssueResponse,
|
|
78
|
-
getPullRequest: {
|
|
79
|
-
title: 'Mock PR title',
|
|
80
|
-
number: 4,
|
|
81
|
-
state: 'closed',
|
|
82
|
-
html_url: 'https://github.com/fre4x/mcp-server/pull/4',
|
|
83
|
-
body: 'Mock PR body',
|
|
84
|
-
merged: true,
|
|
85
|
-
} as GetPullRequestResponse,
|
|
86
|
-
listPullRequests: {
|
|
87
|
-
items: [
|
|
88
|
-
{
|
|
89
|
-
title: 'Update dependencies',
|
|
90
|
-
number: 5,
|
|
91
|
-
state: 'open',
|
|
92
|
-
html_url: 'https://github.com/fre4x/mcp-server/pull/5',
|
|
93
|
-
body: 'Dependency updates',
|
|
94
|
-
},
|
|
95
|
-
],
|
|
96
|
-
total: 1,
|
|
97
|
-
} as ListPullRequestsResponse,
|
|
98
|
-
};
|
package/src/types.ts
DELETED
|
@@ -1,130 +0,0 @@
|
|
|
1
|
-
export interface SearchRepositoriesParams {
|
|
2
|
-
query: string;
|
|
3
|
-
limit?: number;
|
|
4
|
-
offset?: number;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export interface SearchRepositoriesResponse {
|
|
8
|
-
items: Array<{
|
|
9
|
-
full_name: string;
|
|
10
|
-
description: string | null;
|
|
11
|
-
stargazers_count: number;
|
|
12
|
-
html_url: string;
|
|
13
|
-
}>;
|
|
14
|
-
total: number;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export interface SearchCodeParams {
|
|
18
|
-
query: string;
|
|
19
|
-
limit?: number;
|
|
20
|
-
offset?: number;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export interface SearchCodeResponse {
|
|
24
|
-
items: Array<{
|
|
25
|
-
name: string;
|
|
26
|
-
path: string;
|
|
27
|
-
repository: { full_name: string };
|
|
28
|
-
html_url: string;
|
|
29
|
-
}>;
|
|
30
|
-
total: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface SearchIssuesAndPrsParams {
|
|
34
|
-
query: string;
|
|
35
|
-
limit?: number;
|
|
36
|
-
offset?: number;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export interface SearchIssuesAndPrsResponse {
|
|
40
|
-
items: Array<{
|
|
41
|
-
title: string;
|
|
42
|
-
number: number;
|
|
43
|
-
state: string;
|
|
44
|
-
html_url: string;
|
|
45
|
-
repository_url: string;
|
|
46
|
-
}>;
|
|
47
|
-
total: number;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export interface GetRepositoryParams {
|
|
51
|
-
owner: string;
|
|
52
|
-
repo: string;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export interface GetRepositoryResponse {
|
|
56
|
-
full_name: string;
|
|
57
|
-
description: string | null;
|
|
58
|
-
stargazers_count: number;
|
|
59
|
-
subscribers_count: number;
|
|
60
|
-
forks_count: number;
|
|
61
|
-
html_url: string;
|
|
62
|
-
default_branch: string;
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export interface ListIssuesParams {
|
|
66
|
-
owner: string;
|
|
67
|
-
repo: string;
|
|
68
|
-
state?: 'open' | 'closed' | 'all';
|
|
69
|
-
limit?: number;
|
|
70
|
-
offset?: number;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
export interface ListIssuesResponse {
|
|
74
|
-
items: Array<{
|
|
75
|
-
title: string;
|
|
76
|
-
number: number;
|
|
77
|
-
state: string;
|
|
78
|
-
html_url: string;
|
|
79
|
-
body: string | null | undefined;
|
|
80
|
-
}>;
|
|
81
|
-
total: number;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
export interface CreateIssueParams {
|
|
85
|
-
owner: string;
|
|
86
|
-
repo: string;
|
|
87
|
-
title: string;
|
|
88
|
-
body?: string;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
export interface CreateIssueResponse {
|
|
92
|
-
title: string;
|
|
93
|
-
number: number;
|
|
94
|
-
state: string;
|
|
95
|
-
html_url: string;
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export interface GetPullRequestParams {
|
|
99
|
-
owner: string;
|
|
100
|
-
repo: string;
|
|
101
|
-
pull_number: number;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export interface GetPullRequestResponse {
|
|
105
|
-
title: string;
|
|
106
|
-
number: number;
|
|
107
|
-
state: string;
|
|
108
|
-
html_url: string;
|
|
109
|
-
body: string | null;
|
|
110
|
-
merged: boolean;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
export interface ListPullRequestsParams {
|
|
114
|
-
owner: string;
|
|
115
|
-
repo: string;
|
|
116
|
-
state?: 'open' | 'closed' | 'all';
|
|
117
|
-
limit?: number;
|
|
118
|
-
offset?: number;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
export interface ListPullRequestsResponse {
|
|
122
|
-
items: Array<{
|
|
123
|
-
title: string;
|
|
124
|
-
number: number;
|
|
125
|
-
state: string;
|
|
126
|
-
html_url: string;
|
|
127
|
-
body: string | null | undefined;
|
|
128
|
-
}>;
|
|
129
|
-
total: number;
|
|
130
|
-
}
|
package/tsconfig.json
DELETED