@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 CHANGED
@@ -1,20 +1,22 @@
1
1
  {
2
2
  "name": "@fre4x/github",
3
- "version": "1.0.29",
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=\"import{createRequire}from'module';const require=createRequire(import.meta.url);\"",
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
@@ -1,11 +0,0 @@
1
- {
2
- "extends": "../tsconfig.base.json",
3
- "compilerOptions": {
4
- "outDir": "./dist",
5
- "rootDir": "./src",
6
- "module": "NodeNext",
7
- "moduleResolution": "NodeNext"
8
- },
9
- "include": ["src/**/*"],
10
- "exclude": ["node_modules", "dist"]
11
- }