github-pro-mcp 0.0.2 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +76 -0
- package/README.md +166 -70
- package/THIRD-PARTY-NOTICES.md +635 -0
- package/dist-package/index.js +28601 -0
- package/package.json +20 -15
- package/dist/client.d.ts +0 -28
- package/dist/client.js +0 -85
- package/dist/index.d.ts +0 -2
- package/dist/index.js +0 -70
- package/dist/tools/issue_fields.d.ts +0 -4
- package/dist/tools/issue_fields.js +0 -108
- package/dist/tools/issues.d.ts +0 -12
- package/dist/tools/issues.js +0 -634
- package/dist/tools/labels.d.ts +0 -3
- package/dist/tools/labels.js +0 -87
- package/dist/tools/organizations.d.ts +0 -4
- package/dist/tools/organizations.js +0 -89
- package/dist/tools/projects.d.ts +0 -4
- package/dist/tools/projects.js +0 -440
- package/dist/tools/pull_requests.d.ts +0 -4
- package/dist/tools/pull_requests.js +0 -127
- package/dist/tools/repos.d.ts +0 -9
- package/dist/tools/repos.js +0 -251
- package/dist/tools/server.d.ts +0 -14
- package/dist/tools/server.js +0 -37
- package/dist/types/github-api.d.ts +0 -63
- package/dist/types/github-api.js +0 -1
- package/dist/utils/circuitBreaker.d.ts +0 -11
- package/dist/utils/circuitBreaker.js +0 -125
- package/dist/utils/errors.d.ts +0 -17
- package/dist/utils/errors.js +0 -107
- package/dist/utils/logger.d.ts +0 -14
- package/dist/utils/logger.js +0 -40
- package/dist/utils/metrics.d.ts +0 -11
- package/dist/utils/metrics.js +0 -70
- package/dist/utils/rateLimit.d.ts +0 -21
- package/dist/utils/rateLimit.js +0 -40
- package/dist/utils/retry.d.ts +0 -9
- package/dist/utils/retry.js +0 -76
- package/dist/utils/types.d.ts +0 -33
- package/dist/utils/types.js +0 -33
- package/dist/utils/validation.d.ts +0 -13
- package/dist/utils/validation.js +0 -88
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
import * as z from "zod/v4";
|
|
2
|
-
import { createClient } from "../client.js";
|
|
3
|
-
import { handleGitHubError } from "../utils/errors.js";
|
|
4
|
-
import { validatePinnedOwnerRepo } from "../utils/validation.js";
|
|
5
|
-
import { OwnerRepoSchema, PullNumberSchema, PrIncludeFlagsSchema } from "../utils/types.js";
|
|
6
|
-
export function registerPullRequestRead(server) {
|
|
7
|
-
server.registerTool("pull_request_read", {
|
|
8
|
-
description: "Get details of a pull request. Optionally include commits and reviews. Each include_* flag makes additional API calls.",
|
|
9
|
-
inputSchema: OwnerRepoSchema.extend({
|
|
10
|
-
pull_number: PullNumberSchema,
|
|
11
|
-
include_commits: PrIncludeFlagsSchema.shape.include_commits,
|
|
12
|
-
include_reviews: PrIncludeFlagsSchema.shape.include_reviews,
|
|
13
|
-
max_commits: z.number().int().positive().optional().describe("Maximum number of commits to return (default: all)"),
|
|
14
|
-
max_reviews: z.number().int().positive().optional().describe("Maximum number of reviews to return (default: all)"),
|
|
15
|
-
}),
|
|
16
|
-
}, async ({ owner, repo, pull_number, include_commits, include_reviews, max_commits, max_reviews }) => {
|
|
17
|
-
try {
|
|
18
|
-
const pinErr = validatePinnedOwnerRepo(owner, repo);
|
|
19
|
-
if (pinErr)
|
|
20
|
-
return pinErr;
|
|
21
|
-
const octokit = createClient();
|
|
22
|
-
const { data: pr } = await octokit.rest.pulls.get({ owner, repo, pull_number });
|
|
23
|
-
const result = {
|
|
24
|
-
number: pr.number,
|
|
25
|
-
title: pr.title,
|
|
26
|
-
body: pr.body,
|
|
27
|
-
state: pr.state,
|
|
28
|
-
html_url: pr.html_url,
|
|
29
|
-
draft: pr.draft,
|
|
30
|
-
mergeable: pr.mergeable,
|
|
31
|
-
merged: pr.merged,
|
|
32
|
-
created_at: pr.created_at,
|
|
33
|
-
updated_at: pr.updated_at,
|
|
34
|
-
closed_at: pr.closed_at,
|
|
35
|
-
merged_at: pr.merged_at,
|
|
36
|
-
user: { login: pr.user?.login, avatar_url: pr.user?.avatar_url },
|
|
37
|
-
head: { ref: pr.head.ref, sha: pr.head.sha, repo: pr.head.repo?.full_name ?? null },
|
|
38
|
-
base: { ref: pr.base.ref, sha: pr.base.sha, repo: pr.base.repo?.full_name ?? null },
|
|
39
|
-
};
|
|
40
|
-
if (include_commits) {
|
|
41
|
-
const { data: commits } = await octokit.rest.pulls.listCommits({ owner, repo, pull_number, per_page: max_commits ?? 100 });
|
|
42
|
-
result.commits = commits.map((c) => ({
|
|
43
|
-
sha: c.sha,
|
|
44
|
-
message: c.commit.message.split("\n")[0],
|
|
45
|
-
author: c.commit.author?.name,
|
|
46
|
-
date: c.commit.author?.date,
|
|
47
|
-
}));
|
|
48
|
-
}
|
|
49
|
-
if (include_reviews) {
|
|
50
|
-
const { data: reviews } = await octokit.rest.pulls.listReviews({ owner, repo, pull_number, per_page: max_reviews ?? 100 });
|
|
51
|
-
result.reviews = reviews.map((r) => ({
|
|
52
|
-
id: r.id,
|
|
53
|
-
state: r.state,
|
|
54
|
-
user: r.user?.login,
|
|
55
|
-
body: r.body,
|
|
56
|
-
submitted_at: r.submitted_at,
|
|
57
|
-
}));
|
|
58
|
-
}
|
|
59
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
60
|
-
}
|
|
61
|
-
catch (err) {
|
|
62
|
-
return handleGitHubError(err);
|
|
63
|
-
}
|
|
64
|
-
});
|
|
65
|
-
}
|
|
66
|
-
export function registerPullRequestCreate(server) {
|
|
67
|
-
server.registerTool("pull_request_create", {
|
|
68
|
-
description: "Create a new pull request in a GitHub repository.",
|
|
69
|
-
inputSchema: OwnerRepoSchema.extend({
|
|
70
|
-
title: z.string().min(1).describe("Pull request title"),
|
|
71
|
-
head: z.string().min(1).describe("Branch containing changes"),
|
|
72
|
-
base: z.string().min(1).describe("Branch to merge into"),
|
|
73
|
-
body: z.string().optional().describe("Pull request description"),
|
|
74
|
-
draft: z.boolean().optional().default(false).describe("Create as draft pull request"),
|
|
75
|
-
}),
|
|
76
|
-
}, async ({ owner, repo, title, head, base, body, draft }) => {
|
|
77
|
-
try {
|
|
78
|
-
const pinErr = validatePinnedOwnerRepo(owner, repo);
|
|
79
|
-
if (pinErr)
|
|
80
|
-
return pinErr;
|
|
81
|
-
const octokit = createClient();
|
|
82
|
-
const { data } = await octokit.rest.pulls.create({ owner, repo, title, head, base, body, draft });
|
|
83
|
-
return {
|
|
84
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
85
|
-
number: data.number,
|
|
86
|
-
title: data.title,
|
|
87
|
-
html_url: data.html_url,
|
|
88
|
-
state: data.state,
|
|
89
|
-
draft: data.draft,
|
|
90
|
-
created_at: data.created_at,
|
|
91
|
-
}, null, 2) }],
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
catch (err) {
|
|
95
|
-
return handleGitHubError(err);
|
|
96
|
-
}
|
|
97
|
-
});
|
|
98
|
-
}
|
|
99
|
-
export function registerPullRequestMerge(server) {
|
|
100
|
-
server.registerTool("pull_request_merge", {
|
|
101
|
-
description: "Merge a pull request in a GitHub repository.",
|
|
102
|
-
inputSchema: OwnerRepoSchema.extend({
|
|
103
|
-
pull_number: PullNumberSchema,
|
|
104
|
-
merge_method: z.enum(["merge", "squash", "rebase"]).optional().default("merge").describe("Merge method — GitHub accepts: merge, squash, rebase"),
|
|
105
|
-
commit_title: z.string().optional().describe("Title for merge commit"),
|
|
106
|
-
commit_message: z.string().optional().describe("Extra detail for merge commit"),
|
|
107
|
-
}),
|
|
108
|
-
}, async ({ owner, repo, pull_number, merge_method, commit_title, commit_message }) => {
|
|
109
|
-
try {
|
|
110
|
-
const pinErr = validatePinnedOwnerRepo(owner, repo);
|
|
111
|
-
if (pinErr)
|
|
112
|
-
return pinErr;
|
|
113
|
-
const octokit = createClient();
|
|
114
|
-
const { data } = await octokit.rest.pulls.merge({ owner, repo, pull_number, merge_method, commit_title, commit_message });
|
|
115
|
-
return {
|
|
116
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
117
|
-
merged: data.merged,
|
|
118
|
-
message: data.message,
|
|
119
|
-
sha: data.sha,
|
|
120
|
-
}, null, 2) }],
|
|
121
|
-
};
|
|
122
|
-
}
|
|
123
|
-
catch (err) {
|
|
124
|
-
return handleGitHubError(err);
|
|
125
|
-
}
|
|
126
|
-
});
|
|
127
|
-
}
|
package/dist/tools/repos.d.ts
DELETED
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
export declare function registerAuthGetCurrentLogin(server: McpServer): void;
|
|
3
|
-
export declare function registerFileGetContent(server: McpServer): void;
|
|
4
|
-
export declare function registerProfileGet(server: McpServer): void;
|
|
5
|
-
export declare function registerOwnerListRepository(server: McpServer): void;
|
|
6
|
-
export declare function registerFileCreateUpdate(server: McpServer): void;
|
|
7
|
-
export declare function registerCodeSearch(server: McpServer): void;
|
|
8
|
-
export declare function registerFileDelete(server: McpServer): void;
|
|
9
|
-
export declare function registerRepositorySearch(server: McpServer): void;
|
package/dist/tools/repos.js
DELETED
|
@@ -1,251 +0,0 @@
|
|
|
1
|
-
import * as z from "zod/v4";
|
|
2
|
-
import { createClient } from "../client.js";
|
|
3
|
-
import { handleGitHubError } from "../utils/errors.js";
|
|
4
|
-
import { validatePinnedOwnerRepo } from "../utils/validation.js";
|
|
5
|
-
import { OwnerRepoSchema, PaginationSchema, formatRepo } from "../utils/types.js";
|
|
6
|
-
function toBase64(content) {
|
|
7
|
-
return Buffer.from(content, "utf-8").toString("base64");
|
|
8
|
-
}
|
|
9
|
-
export function registerAuthGetCurrentLogin(server) {
|
|
10
|
-
server.registerTool("auth_get_current_login", {
|
|
11
|
-
description: "Get the authenticated GitHub user's profile. Use this to verify your GitHub token and identity.",
|
|
12
|
-
inputSchema: z.object({}),
|
|
13
|
-
}, async () => {
|
|
14
|
-
try {
|
|
15
|
-
const octokit = createClient();
|
|
16
|
-
const { data } = await octokit.rest.users.getAuthenticated();
|
|
17
|
-
return {
|
|
18
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
19
|
-
login: data.login,
|
|
20
|
-
name: data.name,
|
|
21
|
-
avatar_url: data.avatar_url,
|
|
22
|
-
html_url: data.html_url,
|
|
23
|
-
type: data.type,
|
|
24
|
-
plan: data.plan?.name ?? null,
|
|
25
|
-
}, null, 2) }],
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
catch (err) {
|
|
29
|
-
return handleGitHubError(err);
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
export function registerFileGetContent(server) {
|
|
34
|
-
server.registerTool("file_get_content", {
|
|
35
|
-
description: "Get the contents of a file from a GitHub repository. Returns the decoded file content as text.",
|
|
36
|
-
inputSchema: OwnerRepoSchema.extend({
|
|
37
|
-
path: z.string().min(1).describe("File path within the repository (e.g., 'README.md' or 'src/index.ts')"),
|
|
38
|
-
ref: z.string().optional().describe("Git branch, tag, or commit SHA (defaults to repository default branch)"),
|
|
39
|
-
}),
|
|
40
|
-
}, async ({ owner, repo, path, ref }) => {
|
|
41
|
-
try {
|
|
42
|
-
const pinErr = validatePinnedOwnerRepo(owner, repo);
|
|
43
|
-
if (pinErr)
|
|
44
|
-
return pinErr;
|
|
45
|
-
const octokit = createClient();
|
|
46
|
-
const { data } = await octokit.rest.repos.getContent({ owner, repo, path, ref });
|
|
47
|
-
if (Array.isArray(data)) {
|
|
48
|
-
return {
|
|
49
|
-
content: [{ type: "text", text: `Path "${path}" is a directory, not a file. Use with a file path instead.` }],
|
|
50
|
-
isError: true,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
if (!("content" in data)) {
|
|
54
|
-
return {
|
|
55
|
-
content: [{ type: "text", text: `File "${path}" is not a text file.` }],
|
|
56
|
-
isError: true,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
const decoded = Buffer.from(data.content, "base64").toString("utf-8");
|
|
60
|
-
return { content: [{ type: "text", text: decoded }] };
|
|
61
|
-
}
|
|
62
|
-
catch (err) {
|
|
63
|
-
return handleGitHubError(err);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
export function registerProfileGet(server) {
|
|
68
|
-
server.registerTool("profile_get", {
|
|
69
|
-
description: "Get profile info for any GitHub owner (user or organization). Useful for checking if an owner is an organization before calling organization-only tools like owner_list_issue_field.",
|
|
70
|
-
inputSchema: z.object({
|
|
71
|
-
owner: z.string().min(1).describe("Repository owner (user or organization)"),
|
|
72
|
-
}),
|
|
73
|
-
}, async ({ owner }) => {
|
|
74
|
-
try {
|
|
75
|
-
const pinErr = validatePinnedOwnerRepo(owner);
|
|
76
|
-
if (pinErr)
|
|
77
|
-
return pinErr;
|
|
78
|
-
const octokit = createClient();
|
|
79
|
-
const { data } = await octokit.rest.users.getByUsername({ username: owner });
|
|
80
|
-
const ud = data;
|
|
81
|
-
return {
|
|
82
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
83
|
-
login: ud.login,
|
|
84
|
-
name: ud.name,
|
|
85
|
-
type: ud.type,
|
|
86
|
-
avatar_url: ud.avatar_url,
|
|
87
|
-
html_url: ud.html_url,
|
|
88
|
-
description: ud.description ?? ud.bio ?? null,
|
|
89
|
-
}, null, 2) }],
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
catch (err) {
|
|
93
|
-
return handleGitHubError(err);
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
export function registerOwnerListRepository(server) {
|
|
98
|
-
server.registerTool("owner_list_repository", {
|
|
99
|
-
description: "List all repositories for a given owner (user or organization). Unlike search_repositories, this returns all repos the token has access to without relevance ranking.",
|
|
100
|
-
inputSchema: z.object({
|
|
101
|
-
owner: z.string().min(1).describe("Repository owner (user or organization)"),
|
|
102
|
-
type: z.enum(["all", "owner", "member"]).optional().default("all").describe("Type of repositories to list (all, owner, member)"),
|
|
103
|
-
sort: z.enum(["created", "updated", "pushed", "full_name", "name"]).optional().default("created").describe("Sort field"),
|
|
104
|
-
direction: z.enum(["asc", "desc"]).optional().describe("Sort direction (asc or desc)"),
|
|
105
|
-
page: PaginationSchema.shape.page,
|
|
106
|
-
per_page: PaginationSchema.shape.per_page,
|
|
107
|
-
}),
|
|
108
|
-
}, async ({ owner, type, sort, direction, page, per_page }) => {
|
|
109
|
-
try {
|
|
110
|
-
const pinErr = validatePinnedOwnerRepo(owner);
|
|
111
|
-
if (pinErr)
|
|
112
|
-
return pinErr;
|
|
113
|
-
const octokit = createClient();
|
|
114
|
-
const { data } = await octokit.rest.repos.listForUser({ username: owner, type, sort: sort, direction, page, per_page });
|
|
115
|
-
return {
|
|
116
|
-
content: [{ type: "text", text: JSON.stringify(data.map((r) => formatRepo(r)), null, 2) }],
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
catch (err) {
|
|
120
|
-
return handleGitHubError(err);
|
|
121
|
-
}
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
export function registerFileCreateUpdate(server) {
|
|
125
|
-
server.registerTool("file_create_update", {
|
|
126
|
-
description: "Create or update a single file in a GitHub repository. Content is base64-encoded automatically. Provide sha when updating an existing file (obtain sha from the previous file_create_update response).",
|
|
127
|
-
inputSchema: OwnerRepoSchema.extend({
|
|
128
|
-
path: z.string().min(1).describe("Path where to create/update the file"),
|
|
129
|
-
content: z.string().min(1).describe("File content (will be base64-encoded automatically)"),
|
|
130
|
-
message: z.string().min(1).describe("Commit message"),
|
|
131
|
-
branch: z.string().min(1).describe("Branch to create/update the file in"),
|
|
132
|
-
sha: z.string().optional().describe("SHA of existing file (required for updates, obtained from file_get_content)"),
|
|
133
|
-
}),
|
|
134
|
-
}, async ({ owner, repo, path, content, message, branch, sha }) => {
|
|
135
|
-
try {
|
|
136
|
-
const pinErr = validatePinnedOwnerRepo(owner, repo);
|
|
137
|
-
if (pinErr)
|
|
138
|
-
return pinErr;
|
|
139
|
-
const octokit = createClient();
|
|
140
|
-
const response = await octokit.rest.repos.createOrUpdateFileContents({
|
|
141
|
-
owner, repo, path, content: toBase64(content), message, branch, sha,
|
|
142
|
-
});
|
|
143
|
-
const fileData = response.data;
|
|
144
|
-
return {
|
|
145
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
146
|
-
commit: {
|
|
147
|
-
sha: fileData.commit.sha,
|
|
148
|
-
message: fileData.commit.message,
|
|
149
|
-
html_url: fileData.commit.html_url,
|
|
150
|
-
},
|
|
151
|
-
content: {
|
|
152
|
-
sha: fileData.content?.sha ?? null,
|
|
153
|
-
},
|
|
154
|
-
}, null, 2) }],
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
catch (err) {
|
|
158
|
-
return handleGitHubError(err);
|
|
159
|
-
}
|
|
160
|
-
});
|
|
161
|
-
}
|
|
162
|
-
export function registerCodeSearch(server) {
|
|
163
|
-
server.registerTool("code_search", {
|
|
164
|
-
description: "Search for code across GitHub repositories. Supports GitHub search qualifiers like 'repo:...', 'language:...', 'path:...'.",
|
|
165
|
-
inputSchema: z.object({
|
|
166
|
-
query: z.string().min(1).max(1000).describe("Search query with GitHub qualifiers, e.g., 'function repo:owner/repo language:python'"),
|
|
167
|
-
page: PaginationSchema.shape.page,
|
|
168
|
-
per_page: PaginationSchema.shape.per_page,
|
|
169
|
-
}),
|
|
170
|
-
}, async ({ query, page, per_page }) => {
|
|
171
|
-
try {
|
|
172
|
-
const octokit = createClient();
|
|
173
|
-
const { data } = await octokit.rest.search.code({ q: query, page, per_page });
|
|
174
|
-
return {
|
|
175
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
176
|
-
total_count: data.total_count,
|
|
177
|
-
items: data.items.map((f) => ({
|
|
178
|
-
name: f.name,
|
|
179
|
-
path: f.path,
|
|
180
|
-
html_url: f.html_url,
|
|
181
|
-
repository: { full_name: f.repository.full_name },
|
|
182
|
-
})),
|
|
183
|
-
}, null, 2) }],
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
catch (err) {
|
|
187
|
-
return handleGitHubError(err);
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
export function registerFileDelete(server) {
|
|
192
|
-
server.registerTool("file_delete", {
|
|
193
|
-
description: "Delete a file from a GitHub repository. Requires the SHA of the file (obtain from file_get_content).",
|
|
194
|
-
inputSchema: OwnerRepoSchema.extend({
|
|
195
|
-
path: z.string().min(1).describe("Path to the file to delete"),
|
|
196
|
-
message: z.string().min(1).describe("Commit message"),
|
|
197
|
-
branch: z.string().min(1).describe("Branch to delete the file from"),
|
|
198
|
-
sha: z.string().min(1).describe("SHA of the file to delete (obtain from file_get_content or file_create_update)"),
|
|
199
|
-
}),
|
|
200
|
-
}, async ({ owner, repo, path, message, branch, sha }) => {
|
|
201
|
-
try {
|
|
202
|
-
const pinErr = validatePinnedOwnerRepo(owner, repo);
|
|
203
|
-
if (pinErr)
|
|
204
|
-
return pinErr;
|
|
205
|
-
const octokit = createClient();
|
|
206
|
-
const response = await octokit.rest.repos.deleteFile({
|
|
207
|
-
owner, repo, path, message, branch, sha,
|
|
208
|
-
});
|
|
209
|
-
return {
|
|
210
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
211
|
-
commit: {
|
|
212
|
-
sha: response.data.commit.sha,
|
|
213
|
-
message: response.data.commit.message,
|
|
214
|
-
html_url: response.data.commit.html_url,
|
|
215
|
-
},
|
|
216
|
-
content: null,
|
|
217
|
-
}, null, 2) }],
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
catch (err) {
|
|
221
|
-
return handleGitHubError(err);
|
|
222
|
-
}
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
export function registerRepositorySearch(server) {
|
|
226
|
-
server.registerTool("repository_search", {
|
|
227
|
-
description: "Search for GitHub repositories by query. Supports GitHub search qualifiers like 'org:...', 'language:...', 'stars:>100'.",
|
|
228
|
-
inputSchema: z.object({
|
|
229
|
-
query: z.string().min(1).max(1000).describe("Search query with GitHub qualifiers, e.g., 'org:microsoft language:python'"),
|
|
230
|
-
page: PaginationSchema.shape.page,
|
|
231
|
-
per_page: PaginationSchema.shape.per_page,
|
|
232
|
-
}),
|
|
233
|
-
}, async ({ query, page, per_page }) => {
|
|
234
|
-
try {
|
|
235
|
-
const octokit = createClient();
|
|
236
|
-
const { data } = await octokit.rest.search.repos({ q: query, page, per_page });
|
|
237
|
-
return {
|
|
238
|
-
content: [{ type: "text", text: JSON.stringify({
|
|
239
|
-
total_count: data.total_count,
|
|
240
|
-
items: data.items.map((r) => ({
|
|
241
|
-
...formatRepo(r),
|
|
242
|
-
html_url: r.html_url,
|
|
243
|
-
})),
|
|
244
|
-
}, null, 2) }],
|
|
245
|
-
};
|
|
246
|
-
}
|
|
247
|
-
catch (err) {
|
|
248
|
-
return handleGitHubError(err);
|
|
249
|
-
}
|
|
250
|
-
});
|
|
251
|
-
}
|
package/dist/tools/server.d.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
-
export interface ServerMetadata {
|
|
3
|
-
name: string;
|
|
4
|
-
version: string;
|
|
5
|
-
description: string;
|
|
6
|
-
author: string;
|
|
7
|
-
license: string;
|
|
8
|
-
repository: string;
|
|
9
|
-
homepage: string;
|
|
10
|
-
bug_tracker: string;
|
|
11
|
-
bin: Record<string, string>;
|
|
12
|
-
tool_count: number;
|
|
13
|
-
}
|
|
14
|
-
export declare function registerServerInfo(server: McpServer, meta: ServerMetadata): void;
|
package/dist/tools/server.js
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
import * as z from "zod/v4";
|
|
2
|
-
export function registerServerInfo(server, meta) {
|
|
3
|
-
server.registerTool("server_info", {
|
|
4
|
-
description: "Get information about this MCP server, including version, support links, and runtime configuration.",
|
|
5
|
-
inputSchema: z.object({}),
|
|
6
|
-
}, async () => {
|
|
7
|
-
const runtime = {
|
|
8
|
-
node_version: process.version,
|
|
9
|
-
platform: process.platform,
|
|
10
|
-
pinned_owner: process.env.GITHUB_OWNER ?? null,
|
|
11
|
-
pinned_repo: process.env.GITHUB_REPO ?? null,
|
|
12
|
-
log_level: process.env.GITHUB_LOG_LEVEL ?? "error",
|
|
13
|
-
retry_max_attempts: parseInt(process.env.GITHUB_RETRY_MAX_ATTEMPTS ?? "3", 10),
|
|
14
|
-
circuit_breaker_threshold: parseInt(process.env.GITHUB_CIRCUIT_BREAKER_THRESHOLD ?? "5", 10),
|
|
15
|
-
};
|
|
16
|
-
return {
|
|
17
|
-
content: [{
|
|
18
|
-
type: "text",
|
|
19
|
-
text: JSON.stringify({
|
|
20
|
-
name: meta.name,
|
|
21
|
-
version: meta.version,
|
|
22
|
-
description: meta.description,
|
|
23
|
-
author: meta.author,
|
|
24
|
-
license: meta.license,
|
|
25
|
-
repository: meta.repository,
|
|
26
|
-
homepage: meta.homepage,
|
|
27
|
-
bug_tracker: meta.bug_tracker,
|
|
28
|
-
npm_package: "https://www.npmjs.com/package/github-pro-mcp",
|
|
29
|
-
docker_image: "https://hub.docker.com/r/anantanandgupta/github-pro-mcp",
|
|
30
|
-
bin: meta.bin,
|
|
31
|
-
tools_registered: meta.tool_count,
|
|
32
|
-
runtime,
|
|
33
|
-
}, null, 2),
|
|
34
|
-
}],
|
|
35
|
-
};
|
|
36
|
-
});
|
|
37
|
-
}
|
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
export interface IssueResponse {
|
|
2
|
-
number: number;
|
|
3
|
-
title: string;
|
|
4
|
-
body: string | null;
|
|
5
|
-
state: string;
|
|
6
|
-
html_url: string;
|
|
7
|
-
created_at: string;
|
|
8
|
-
updated_at: string;
|
|
9
|
-
closed_at: string | null;
|
|
10
|
-
user: {
|
|
11
|
-
login: string;
|
|
12
|
-
avatar_url: string;
|
|
13
|
-
} | null;
|
|
14
|
-
labels: Array<{
|
|
15
|
-
name?: string;
|
|
16
|
-
color?: string;
|
|
17
|
-
} | string>;
|
|
18
|
-
assignees?: Array<{
|
|
19
|
-
login: string;
|
|
20
|
-
}>;
|
|
21
|
-
milestone: {
|
|
22
|
-
title: string;
|
|
23
|
-
number: number;
|
|
24
|
-
} | null;
|
|
25
|
-
issue_field_values?: Array<{
|
|
26
|
-
issue_field_id: number;
|
|
27
|
-
node_id: string;
|
|
28
|
-
data_type: string;
|
|
29
|
-
value: string | number | null;
|
|
30
|
-
single_select_option?: {
|
|
31
|
-
id: number;
|
|
32
|
-
name: string;
|
|
33
|
-
};
|
|
34
|
-
}>;
|
|
35
|
-
}
|
|
36
|
-
export interface CreateOrUpdateFileResponse {
|
|
37
|
-
commit: {
|
|
38
|
-
sha: string;
|
|
39
|
-
message: string;
|
|
40
|
-
html_url: string;
|
|
41
|
-
};
|
|
42
|
-
content?: {
|
|
43
|
-
sha: string;
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
export interface UserResponse {
|
|
47
|
-
login: string;
|
|
48
|
-
name: string | null;
|
|
49
|
-
type: string;
|
|
50
|
-
avatar_url: string;
|
|
51
|
-
html_url: string;
|
|
52
|
-
description?: string | null;
|
|
53
|
-
bio?: string | null;
|
|
54
|
-
}
|
|
55
|
-
export interface SearchUserItem {
|
|
56
|
-
login: string;
|
|
57
|
-
description?: string | null;
|
|
58
|
-
html_url: string;
|
|
59
|
-
}
|
|
60
|
-
export interface LabelItem {
|
|
61
|
-
name?: string;
|
|
62
|
-
color?: string;
|
|
63
|
-
}
|
package/dist/types/github-api.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
type CircuitState = "CLOSED" | "OPEN" | "HALF_OPEN";
|
|
2
|
-
export declare class CircuitBreakerError extends Error {
|
|
3
|
-
constructor(breakerName: string);
|
|
4
|
-
}
|
|
5
|
-
export declare function withCircuitBreaker<T>(fn: () => Promise<T>, name: string): Promise<T>;
|
|
6
|
-
export declare function getCircuitBreakerState(name: string): {
|
|
7
|
-
state: CircuitState;
|
|
8
|
-
failureCount: number;
|
|
9
|
-
lastFailureTime: number;
|
|
10
|
-
} | null;
|
|
11
|
-
export {};
|
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
import { createLogger } from "./logger.js";
|
|
2
|
-
const log = createLogger("circuit-breaker");
|
|
3
|
-
const breakers = new Map();
|
|
4
|
-
function defaultConfig() {
|
|
5
|
-
return {
|
|
6
|
-
failureThreshold: parseInt(process.env.GITHUB_CIRCUIT_BREAKER_THRESHOLD ?? "5", 10),
|
|
7
|
-
resetTimeoutMs: parseInt(process.env.GITHUB_CIRCUIT_BREAKER_RESET_MS ?? "30000", 10),
|
|
8
|
-
halfOpenMaxRequests: 1,
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
function getOrCreateBreaker(name) {
|
|
12
|
-
let breaker = breakers.get(name);
|
|
13
|
-
if (!breaker) {
|
|
14
|
-
breaker = {
|
|
15
|
-
state: "CLOSED",
|
|
16
|
-
failureCount: 0,
|
|
17
|
-
lastFailureTime: 0,
|
|
18
|
-
halfOpenRequestCount: 0,
|
|
19
|
-
};
|
|
20
|
-
breakers.set(name, breaker);
|
|
21
|
-
}
|
|
22
|
-
return breaker;
|
|
23
|
-
}
|
|
24
|
-
function transitionToOpen(breaker, name) {
|
|
25
|
-
if (breaker.state === "OPEN")
|
|
26
|
-
return;
|
|
27
|
-
breaker.state = "OPEN";
|
|
28
|
-
breaker.failureCount = 0;
|
|
29
|
-
breaker.halfOpenRequestCount = 0;
|
|
30
|
-
log.warn(`Circuit "${name}" OPEN — failing fast for ${defaultConfig().resetTimeoutMs / 1000}s`);
|
|
31
|
-
}
|
|
32
|
-
function transitionToHalfOpen(breaker, name) {
|
|
33
|
-
breaker.state = "HALF_OPEN";
|
|
34
|
-
breaker.halfOpenRequestCount = 0;
|
|
35
|
-
log.info(`Circuit "${name}" HALF_OPEN — testing service availability`);
|
|
36
|
-
}
|
|
37
|
-
function transitionToClosed(breaker, name) {
|
|
38
|
-
breaker.state = "CLOSED";
|
|
39
|
-
breaker.failureCount = 0;
|
|
40
|
-
breaker.halfOpenRequestCount = 0;
|
|
41
|
-
log.info(`Circuit "${name}" CLOSED — service recovered`);
|
|
42
|
-
}
|
|
43
|
-
function shouldAllowRequest(breaker, config) {
|
|
44
|
-
switch (breaker.state) {
|
|
45
|
-
case "CLOSED":
|
|
46
|
-
return true;
|
|
47
|
-
case "OPEN": {
|
|
48
|
-
const elapsed = Date.now() - breaker.lastFailureTime;
|
|
49
|
-
if (elapsed >= config.resetTimeoutMs) {
|
|
50
|
-
transitionToHalfOpen(breaker, "");
|
|
51
|
-
return true;
|
|
52
|
-
}
|
|
53
|
-
return false;
|
|
54
|
-
}
|
|
55
|
-
case "HALF_OPEN":
|
|
56
|
-
return breaker.halfOpenRequestCount < config.halfOpenMaxRequests;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
function recordSuccess(breaker, name) {
|
|
60
|
-
if (breaker.state === "HALF_OPEN") {
|
|
61
|
-
transitionToClosed(breaker, name);
|
|
62
|
-
}
|
|
63
|
-
if (breaker.state === "CLOSED") {
|
|
64
|
-
breaker.failureCount = 0;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
function recordFailure(breaker, config, name) {
|
|
68
|
-
breaker.lastFailureTime = Date.now();
|
|
69
|
-
switch (breaker.state) {
|
|
70
|
-
case "CLOSED":
|
|
71
|
-
breaker.failureCount++;
|
|
72
|
-
if (breaker.failureCount >= config.failureThreshold) {
|
|
73
|
-
transitionToOpen(breaker, name);
|
|
74
|
-
}
|
|
75
|
-
break;
|
|
76
|
-
case "HALF_OPEN":
|
|
77
|
-
transitionToOpen(breaker, name);
|
|
78
|
-
break;
|
|
79
|
-
case "OPEN":
|
|
80
|
-
break;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
export class CircuitBreakerError extends Error {
|
|
84
|
-
constructor(breakerName) {
|
|
85
|
-
super(`Circuit breaker "${breakerName}" is OPEN. ` +
|
|
86
|
-
`GitHub API requests are temporarily blocked due to repeated failures. ` +
|
|
87
|
-
`The circuit will test the service automatically after the reset timeout.`);
|
|
88
|
-
this.name = "CircuitBreakerError";
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
export async function withCircuitBreaker(fn, name) {
|
|
92
|
-
const config = defaultConfig();
|
|
93
|
-
const breaker = getOrCreateBreaker(name);
|
|
94
|
-
if (!shouldAllowRequest(breaker, config)) {
|
|
95
|
-
throw new CircuitBreakerError(name);
|
|
96
|
-
}
|
|
97
|
-
if (breaker.state === "HALF_OPEN") {
|
|
98
|
-
breaker.halfOpenRequestCount++;
|
|
99
|
-
}
|
|
100
|
-
try {
|
|
101
|
-
const result = await fn();
|
|
102
|
-
recordSuccess(breaker, name);
|
|
103
|
-
return result;
|
|
104
|
-
}
|
|
105
|
-
catch (error) {
|
|
106
|
-
const status = error?.status;
|
|
107
|
-
const isServiceFailure = typeof status === "number" &&
|
|
108
|
-
(status >= 500 || status === 429);
|
|
109
|
-
const isNetworkError = error?.code && ["ECONNRESET", "ECONNREFUSED", "ETIMEDOUT", "ENOTFOUND", "EPIPE"].includes(error.code);
|
|
110
|
-
if (isServiceFailure || isNetworkError) {
|
|
111
|
-
recordFailure(breaker, config, name);
|
|
112
|
-
}
|
|
113
|
-
throw error;
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
export function getCircuitBreakerState(name) {
|
|
117
|
-
const breaker = breakers.get(name);
|
|
118
|
-
if (!breaker)
|
|
119
|
-
return null;
|
|
120
|
-
return {
|
|
121
|
-
state: breaker.state,
|
|
122
|
-
failureCount: breaker.failureCount,
|
|
123
|
-
lastFailureTime: breaker.lastFailureTime,
|
|
124
|
-
};
|
|
125
|
-
}
|
package/dist/utils/errors.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
interface ErrorResponse {
|
|
2
|
-
content: [{
|
|
3
|
-
type: "text";
|
|
4
|
-
text: string;
|
|
5
|
-
}];
|
|
6
|
-
isError: true;
|
|
7
|
-
[x: string]: unknown;
|
|
8
|
-
}
|
|
9
|
-
/**
|
|
10
|
-
* Convert any GitHub API error (REST or GraphQL) into a structured
|
|
11
|
-
* MCP error response with actionable guidance.
|
|
12
|
-
*
|
|
13
|
-
* Automatically increments the metrics counter for the error category.
|
|
14
|
-
*/
|
|
15
|
-
export declare function handleGitHubError(err: unknown): ErrorResponse;
|
|
16
|
-
export declare function formatZodErrors(err: unknown): string | null;
|
|
17
|
-
export {};
|