github-cicd-mcp 0.0.0-stage → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +43 -0
- package/LICENSE +21 -0
- package/README.md +146 -3
- package/dist/index.d.ts +2 -0
- package/dist/index.js +45 -0
- package/dist/lib/fields.d.ts +4 -0
- package/dist/lib/fields.js +4 -0
- package/dist/lib/github.d.ts +13 -0
- package/dist/lib/github.js +27 -0
- package/dist/lib/response.d.ts +4 -0
- package/dist/lib/response.js +48 -0
- package/dist/tools/actions.d.ts +2 -0
- package/dist/tools/actions.js +257 -0
- package/dist/tools/billing.d.ts +2 -0
- package/dist/tools/billing.js +58 -0
- package/dist/tools/branch-protection.d.ts +2 -0
- package/dist/tools/branch-protection.js +125 -0
- package/dist/tools/commits.d.ts +2 -0
- package/dist/tools/commits.js +203 -0
- package/dist/tools/deployments.d.ts +2 -0
- package/dist/tools/deployments.js +98 -0
- package/dist/tools/graphql.d.ts +2 -0
- package/dist/tools/graphql.js +73 -0
- package/dist/tools/issues.d.ts +2 -0
- package/dist/tools/issues.js +167 -0
- package/dist/tools/local-git.d.ts +2 -0
- package/dist/tools/local-git.js +142 -0
- package/dist/tools/pull-requests.d.ts +2 -0
- package/dist/tools/pull-requests.js +198 -0
- package/dist/tools/releases.d.ts +2 -0
- package/dist/tools/releases.js +146 -0
- package/dist/tools/repos.d.ts +2 -0
- package/dist/tools/repos.js +182 -0
- package/dist/tools/webhooks-collaborators.d.ts +3 -0
- package/dist/tools/webhooks-collaborators.js +132 -0
- package/package.json +61 -6
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { owner, perPage, repo } from "../lib/fields.js";
|
|
3
|
+
import { getOctokit } from "../lib/github.js";
|
|
4
|
+
import { errorResult, jsonResult } from "../lib/response.js";
|
|
5
|
+
export function registerIssueTools(server) {
|
|
6
|
+
server.tool("github_create_issue", "Create an issue in a repository.", {
|
|
7
|
+
owner,
|
|
8
|
+
repo,
|
|
9
|
+
title: z.string(),
|
|
10
|
+
body: z.string().optional(),
|
|
11
|
+
labels: z.string().optional().describe("JSON array of label names"),
|
|
12
|
+
assignees: z.string().optional().describe("JSON array of GitHub usernames"),
|
|
13
|
+
}, async ({ owner, repo, title, body, labels, assignees }) => {
|
|
14
|
+
try {
|
|
15
|
+
const { data } = await getOctokit().rest.issues.create({
|
|
16
|
+
owner,
|
|
17
|
+
repo,
|
|
18
|
+
title,
|
|
19
|
+
body: body ?? undefined,
|
|
20
|
+
labels: labels ? JSON.parse(labels) : undefined,
|
|
21
|
+
assignees: assignees ? JSON.parse(assignees) : undefined,
|
|
22
|
+
});
|
|
23
|
+
return jsonResult({
|
|
24
|
+
number: data.number,
|
|
25
|
+
title: data.title,
|
|
26
|
+
state: data.state,
|
|
27
|
+
html_url: data.html_url,
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (e) {
|
|
31
|
+
return errorResult(e);
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
server.tool("github_list_issues", "List issues (excludes pull requests unless filtered).", {
|
|
35
|
+
owner,
|
|
36
|
+
repo,
|
|
37
|
+
state: z.string().optional().describe("open | closed | all"),
|
|
38
|
+
labels: z.string().optional().describe("Comma-separated label names"),
|
|
39
|
+
per_page: perPage,
|
|
40
|
+
}, async ({ owner, repo, state, labels, per_page }) => {
|
|
41
|
+
try {
|
|
42
|
+
const { data } = await getOctokit().rest.issues.listForRepo({
|
|
43
|
+
owner,
|
|
44
|
+
repo,
|
|
45
|
+
state: state ?? "open",
|
|
46
|
+
labels: labels ?? undefined,
|
|
47
|
+
per_page: per_page ?? 30,
|
|
48
|
+
});
|
|
49
|
+
return jsonResult(data
|
|
50
|
+
.filter((i) => !i.pull_request)
|
|
51
|
+
.map((i) => ({
|
|
52
|
+
number: i.number,
|
|
53
|
+
title: i.title,
|
|
54
|
+
state: i.state,
|
|
55
|
+
user: i.user?.login,
|
|
56
|
+
labels: i.labels.map((l) => (typeof l === "string" ? l : l.name)),
|
|
57
|
+
html_url: i.html_url,
|
|
58
|
+
})));
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
return errorResult(e);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
server.tool("github_get_issue", "Get issue details by number.", {
|
|
65
|
+
owner,
|
|
66
|
+
repo,
|
|
67
|
+
issue_number: z.number(),
|
|
68
|
+
}, async ({ owner, repo, issue_number }) => {
|
|
69
|
+
try {
|
|
70
|
+
const { data } = await getOctokit().rest.issues.get({
|
|
71
|
+
owner,
|
|
72
|
+
repo,
|
|
73
|
+
issue_number,
|
|
74
|
+
});
|
|
75
|
+
return jsonResult({
|
|
76
|
+
number: data.number,
|
|
77
|
+
title: data.title,
|
|
78
|
+
body: data.body,
|
|
79
|
+
state: data.state,
|
|
80
|
+
user: data.user?.login,
|
|
81
|
+
labels: data.labels.map((l) => (typeof l === "string" ? l : l.name)),
|
|
82
|
+
assignees: data.assignees?.map((a) => a.login),
|
|
83
|
+
html_url: data.html_url,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
catch (e) {
|
|
87
|
+
return errorResult(e);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
server.tool("github_update_issue", "Update issue title, body, state, or labels.", {
|
|
91
|
+
owner,
|
|
92
|
+
repo,
|
|
93
|
+
issue_number: z.number(),
|
|
94
|
+
title: z.string().optional(),
|
|
95
|
+
body: z.string().optional(),
|
|
96
|
+
state: z.string().optional().describe("open | closed"),
|
|
97
|
+
labels: z.string().optional().describe("JSON array of label names (replaces all labels)"),
|
|
98
|
+
}, async ({ owner, repo, issue_number, title, body, state, labels }) => {
|
|
99
|
+
try {
|
|
100
|
+
const { data } = await getOctokit().rest.issues.update({
|
|
101
|
+
owner,
|
|
102
|
+
repo,
|
|
103
|
+
issue_number,
|
|
104
|
+
title: title ?? undefined,
|
|
105
|
+
body: body ?? undefined,
|
|
106
|
+
state: state,
|
|
107
|
+
labels: labels ? JSON.parse(labels) : undefined,
|
|
108
|
+
});
|
|
109
|
+
return jsonResult({
|
|
110
|
+
number: data.number,
|
|
111
|
+
title: data.title,
|
|
112
|
+
state: data.state,
|
|
113
|
+
html_url: data.html_url,
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
return errorResult(e);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
server.tool("github_add_issue_comment", "Add a comment to an issue or pull request.", {
|
|
121
|
+
owner,
|
|
122
|
+
repo,
|
|
123
|
+
issue_number: z.number(),
|
|
124
|
+
body: z.string(),
|
|
125
|
+
}, async ({ owner, repo, issue_number, body }) => {
|
|
126
|
+
try {
|
|
127
|
+
const { data } = await getOctokit().rest.issues.createComment({
|
|
128
|
+
owner,
|
|
129
|
+
repo,
|
|
130
|
+
issue_number,
|
|
131
|
+
body,
|
|
132
|
+
});
|
|
133
|
+
return jsonResult({
|
|
134
|
+
id: data.id,
|
|
135
|
+
html_url: data.html_url,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
return errorResult(e);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
server.tool("github_list_issue_comments", "List comments on an issue or PR.", {
|
|
143
|
+
owner,
|
|
144
|
+
repo,
|
|
145
|
+
issue_number: z.number(),
|
|
146
|
+
per_page: perPage,
|
|
147
|
+
}, async ({ owner, repo, issue_number, per_page }) => {
|
|
148
|
+
try {
|
|
149
|
+
const { data } = await getOctokit().rest.issues.listComments({
|
|
150
|
+
owner,
|
|
151
|
+
repo,
|
|
152
|
+
issue_number,
|
|
153
|
+
per_page: per_page ?? 30,
|
|
154
|
+
});
|
|
155
|
+
return jsonResult(data.map((c) => ({
|
|
156
|
+
id: c.id,
|
|
157
|
+
user: c.user?.login,
|
|
158
|
+
body: c.body,
|
|
159
|
+
created_at: c.created_at,
|
|
160
|
+
html_url: c.html_url,
|
|
161
|
+
})));
|
|
162
|
+
}
|
|
163
|
+
catch (e) {
|
|
164
|
+
return errorResult(e);
|
|
165
|
+
}
|
|
166
|
+
});
|
|
167
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import { simpleGit } from "simple-git";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import { errorResult, jsonResult, textResult } from "../lib/response.js";
|
|
6
|
+
const repoPath = z.string().describe("Path to git repository root");
|
|
7
|
+
function gitAt(cwd) {
|
|
8
|
+
const dir = resolve(cwd);
|
|
9
|
+
if (!existsSync(resolve(dir, ".git"))) {
|
|
10
|
+
throw new Error(`Not a git repository: ${dir}`);
|
|
11
|
+
}
|
|
12
|
+
return simpleGit({ baseDir: dir });
|
|
13
|
+
}
|
|
14
|
+
export function registerLocalGitTools(server) {
|
|
15
|
+
server.tool("git_status", "Show working tree status in a local clone (branch, staged/unstaged files).", { path: repoPath }, async ({ path }) => {
|
|
16
|
+
try {
|
|
17
|
+
const git = gitAt(path);
|
|
18
|
+
const status = await git.status();
|
|
19
|
+
return jsonResult({
|
|
20
|
+
current: status.current,
|
|
21
|
+
tracking: status.tracking,
|
|
22
|
+
ahead: status.ahead,
|
|
23
|
+
behind: status.behind,
|
|
24
|
+
staged: status.staged,
|
|
25
|
+
modified: status.modified,
|
|
26
|
+
created: status.created,
|
|
27
|
+
deleted: status.deleted,
|
|
28
|
+
conflicted: status.conflicted,
|
|
29
|
+
isClean: status.isClean(),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
catch (e) {
|
|
33
|
+
return errorResult(e);
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
server.tool("git_commit", "Stage all changes and create a local commit.", {
|
|
37
|
+
path: repoPath,
|
|
38
|
+
message: z.string().describe("Commit message"),
|
|
39
|
+
all: z
|
|
40
|
+
.boolean()
|
|
41
|
+
.optional()
|
|
42
|
+
.describe("Stage all changes (default true). If false, only staged files"),
|
|
43
|
+
}, async ({ path, message, all }) => {
|
|
44
|
+
try {
|
|
45
|
+
const git = gitAt(path);
|
|
46
|
+
if (all !== false) {
|
|
47
|
+
await git.add("-A");
|
|
48
|
+
}
|
|
49
|
+
const result = await git.commit(message);
|
|
50
|
+
return jsonResult({
|
|
51
|
+
commit: result.commit,
|
|
52
|
+
summary: result.summary,
|
|
53
|
+
branch: result.branch,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
return errorResult(e);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
server.tool("git_push", "Push commits to remote (set upstream on first push if needed).", {
|
|
61
|
+
path: repoPath,
|
|
62
|
+
remote: z.string().optional().describe("Remote name (default origin)"),
|
|
63
|
+
branch: z.string().optional().describe("Branch to push; default current branch"),
|
|
64
|
+
set_upstream: z
|
|
65
|
+
.boolean()
|
|
66
|
+
.optional()
|
|
67
|
+
.describe("Use -u on push (default true for new branches)"),
|
|
68
|
+
}, async ({ path, remote, branch, set_upstream }) => {
|
|
69
|
+
try {
|
|
70
|
+
const git = gitAt(path);
|
|
71
|
+
const rem = remote ?? "origin";
|
|
72
|
+
const status = await git.status();
|
|
73
|
+
const br = branch ?? status.current ?? "HEAD";
|
|
74
|
+
const args = set_upstream === false ? [rem, br] : ["-u", rem, br];
|
|
75
|
+
await git.push(args);
|
|
76
|
+
return jsonResult({
|
|
77
|
+
pushed: true,
|
|
78
|
+
remote: rem,
|
|
79
|
+
branch: br,
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
return errorResult(e);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
server.tool("git_pull", "Pull from remote with rebase (ff-only friendly for CI branches).", {
|
|
87
|
+
path: repoPath,
|
|
88
|
+
remote: z.string().optional().describe("Default origin"),
|
|
89
|
+
branch: z.string().optional(),
|
|
90
|
+
rebase: z.boolean().optional().describe("Use --rebase (default true)"),
|
|
91
|
+
}, async ({ path, remote, branch, rebase }) => {
|
|
92
|
+
try {
|
|
93
|
+
const git = gitAt(path);
|
|
94
|
+
const rem = remote ?? "origin";
|
|
95
|
+
const status = await git.status();
|
|
96
|
+
const br = branch ?? status.current ?? undefined;
|
|
97
|
+
if (rebase !== false) {
|
|
98
|
+
await git.pull(rem, br, { "--rebase": "true" });
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
await git.pull(rem, br);
|
|
102
|
+
}
|
|
103
|
+
return textResult(`Pulled ${rem}/${br ?? "current branch"}`);
|
|
104
|
+
}
|
|
105
|
+
catch (e) {
|
|
106
|
+
return errorResult(e);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
server.tool("git_log", "Recent commit log on current branch.", {
|
|
110
|
+
path: repoPath,
|
|
111
|
+
max_count: z.number().optional().describe("Number of commits (default 10)"),
|
|
112
|
+
}, async ({ path, max_count }) => {
|
|
113
|
+
try {
|
|
114
|
+
const git = gitAt(path);
|
|
115
|
+
const log = await git.log({ maxCount: max_count ?? 10 });
|
|
116
|
+
return jsonResult(log.all.map((c) => ({
|
|
117
|
+
hash: c.hash,
|
|
118
|
+
date: c.date,
|
|
119
|
+
message: c.message,
|
|
120
|
+
author_name: c.author_name,
|
|
121
|
+
})));
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
return errorResult(e);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
server.tool("git_clone", "Clone a repository into a directory (uses local git, not API).", {
|
|
128
|
+
url: z.string().describe("HTTPS or SSH clone URL"),
|
|
129
|
+
directory: z.string().describe("Target directory path"),
|
|
130
|
+
branch: z.string().optional().describe("Optional branch to checkout"),
|
|
131
|
+
}, async ({ url, directory, branch }) => {
|
|
132
|
+
try {
|
|
133
|
+
const dir = resolve(directory);
|
|
134
|
+
const git = simpleGit();
|
|
135
|
+
await git.clone(url, dir, branch ? ["--branch", branch] : []);
|
|
136
|
+
return jsonResult({ cloned: url, directory: dir, branch: branch ?? "default" });
|
|
137
|
+
}
|
|
138
|
+
catch (e) {
|
|
139
|
+
return errorResult(e);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
}
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { owner, perPage, repo } from "../lib/fields.js";
|
|
3
|
+
import { getOctokit } from "../lib/github.js";
|
|
4
|
+
import { errorResult, jsonResult } from "../lib/response.js";
|
|
5
|
+
export function registerPullRequestTools(server) {
|
|
6
|
+
server.tool("github_create_pull_request", "Open a pull request (base ← head).", {
|
|
7
|
+
owner,
|
|
8
|
+
repo,
|
|
9
|
+
title: z.string(),
|
|
10
|
+
head: z.string().describe("Head branch (or user:branch for forks)"),
|
|
11
|
+
base: z.string().describe("Base branch (e.g. main)"),
|
|
12
|
+
body: z.string().optional().describe("PR description (markdown)"),
|
|
13
|
+
draft: z.boolean().optional().describe("Create as draft PR"),
|
|
14
|
+
}, async ({ owner, repo, title, head, base, body, draft }) => {
|
|
15
|
+
try {
|
|
16
|
+
const { data } = await getOctokit().rest.pulls.create({
|
|
17
|
+
owner,
|
|
18
|
+
repo,
|
|
19
|
+
title,
|
|
20
|
+
head,
|
|
21
|
+
base,
|
|
22
|
+
body: body ?? undefined,
|
|
23
|
+
draft: draft ?? undefined,
|
|
24
|
+
});
|
|
25
|
+
return jsonResult({
|
|
26
|
+
number: data.number,
|
|
27
|
+
title: data.title,
|
|
28
|
+
state: data.state,
|
|
29
|
+
html_url: data.html_url,
|
|
30
|
+
mergeable: data.mergeable,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch (e) {
|
|
34
|
+
return errorResult(e);
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
server.tool("github_list_pull_requests", "List pull requests for a repository.", {
|
|
38
|
+
owner,
|
|
39
|
+
repo,
|
|
40
|
+
state: z.string().optional().describe("open | closed | all (default open)"),
|
|
41
|
+
per_page: perPage,
|
|
42
|
+
}, async ({ owner, repo, state, per_page }) => {
|
|
43
|
+
try {
|
|
44
|
+
const { data } = await getOctokit().rest.pulls.list({
|
|
45
|
+
owner,
|
|
46
|
+
repo,
|
|
47
|
+
state: state ?? "open",
|
|
48
|
+
per_page: per_page ?? 30,
|
|
49
|
+
});
|
|
50
|
+
return jsonResult(data.map((pr) => ({
|
|
51
|
+
number: pr.number,
|
|
52
|
+
title: pr.title,
|
|
53
|
+
state: pr.state,
|
|
54
|
+
user: pr.user?.login,
|
|
55
|
+
head: pr.head.ref,
|
|
56
|
+
base: pr.base.ref,
|
|
57
|
+
html_url: pr.html_url,
|
|
58
|
+
})));
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
return errorResult(e);
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
server.tool("github_get_pull_request", "Get pull request details including merge status.", {
|
|
65
|
+
owner,
|
|
66
|
+
repo,
|
|
67
|
+
pull_number: z.number(),
|
|
68
|
+
}, async ({ owner, repo, pull_number }) => {
|
|
69
|
+
try {
|
|
70
|
+
const { data } = await getOctokit().rest.pulls.get({
|
|
71
|
+
owner,
|
|
72
|
+
repo,
|
|
73
|
+
pull_number,
|
|
74
|
+
});
|
|
75
|
+
return jsonResult({
|
|
76
|
+
number: data.number,
|
|
77
|
+
title: data.title,
|
|
78
|
+
body: data.body,
|
|
79
|
+
state: data.state,
|
|
80
|
+
merged: data.merged,
|
|
81
|
+
mergeable: data.mergeable,
|
|
82
|
+
mergeable_state: data.mergeable_state,
|
|
83
|
+
head: { ref: data.head.ref, sha: data.head.sha },
|
|
84
|
+
base: { ref: data.base.ref, sha: data.base.sha },
|
|
85
|
+
html_url: data.html_url,
|
|
86
|
+
changed_files: data.changed_files,
|
|
87
|
+
additions: data.additions,
|
|
88
|
+
deletions: data.deletions,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
catch (e) {
|
|
92
|
+
return errorResult(e);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
server.tool("github_merge_pull_request", "Merge a pull request when checks pass (squash, merge, or rebase).", {
|
|
96
|
+
owner,
|
|
97
|
+
repo,
|
|
98
|
+
pull_number: z.number(),
|
|
99
|
+
merge_method: z
|
|
100
|
+
.string()
|
|
101
|
+
.optional()
|
|
102
|
+
.describe("merge | squash | rebase (default squash)"),
|
|
103
|
+
commit_title: z.string().optional(),
|
|
104
|
+
commit_message: z.string().optional(),
|
|
105
|
+
}, async ({ owner, repo, pull_number, merge_method, commit_title, commit_message }) => {
|
|
106
|
+
try {
|
|
107
|
+
const { data } = await getOctokit().rest.pulls.merge({
|
|
108
|
+
owner,
|
|
109
|
+
repo,
|
|
110
|
+
pull_number,
|
|
111
|
+
merge_method: merge_method ?? "squash",
|
|
112
|
+
commit_title: commit_title ?? undefined,
|
|
113
|
+
commit_message: commit_message ?? undefined,
|
|
114
|
+
});
|
|
115
|
+
return jsonResult(data);
|
|
116
|
+
}
|
|
117
|
+
catch (e) {
|
|
118
|
+
return errorResult(e);
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
server.tool("github_update_pull_request", "Update PR title, body, or state (open/closed).", {
|
|
122
|
+
owner,
|
|
123
|
+
repo,
|
|
124
|
+
pull_number: z.number(),
|
|
125
|
+
title: z.string().optional(),
|
|
126
|
+
body: z.string().optional(),
|
|
127
|
+
state: z.string().optional().describe("open | closed"),
|
|
128
|
+
}, async ({ owner, repo, pull_number, title, body, state }) => {
|
|
129
|
+
try {
|
|
130
|
+
const { data } = await getOctokit().rest.pulls.update({
|
|
131
|
+
owner,
|
|
132
|
+
repo,
|
|
133
|
+
pull_number,
|
|
134
|
+
title: title ?? undefined,
|
|
135
|
+
body: body ?? undefined,
|
|
136
|
+
state: state,
|
|
137
|
+
});
|
|
138
|
+
return jsonResult({
|
|
139
|
+
number: data.number,
|
|
140
|
+
title: data.title,
|
|
141
|
+
state: data.state,
|
|
142
|
+
html_url: data.html_url,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
catch (e) {
|
|
146
|
+
return errorResult(e);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
server.tool("github_list_pull_request_files", "List files changed in a pull request.", {
|
|
150
|
+
owner,
|
|
151
|
+
repo,
|
|
152
|
+
pull_number: z.number(),
|
|
153
|
+
}, async ({ owner, repo, pull_number }) => {
|
|
154
|
+
try {
|
|
155
|
+
const { data } = await getOctokit().rest.pulls.listFiles({
|
|
156
|
+
owner,
|
|
157
|
+
repo,
|
|
158
|
+
pull_number,
|
|
159
|
+
per_page: 100,
|
|
160
|
+
});
|
|
161
|
+
return jsonResult(data.map((f) => ({
|
|
162
|
+
filename: f.filename,
|
|
163
|
+
status: f.status,
|
|
164
|
+
additions: f.additions,
|
|
165
|
+
deletions: f.deletions,
|
|
166
|
+
patch: f.patch?.slice(0, 2000),
|
|
167
|
+
})));
|
|
168
|
+
}
|
|
169
|
+
catch (e) {
|
|
170
|
+
return errorResult(e);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
server.tool("github_create_pull_request_review", "Submit a PR review (comment, approve, or request changes).", {
|
|
174
|
+
owner,
|
|
175
|
+
repo,
|
|
176
|
+
pull_number: z.number(),
|
|
177
|
+
event: z.string().describe("COMMENT | APPROVE | REQUEST_CHANGES"),
|
|
178
|
+
body: z.string().optional().describe("Review comment"),
|
|
179
|
+
}, async ({ owner, repo, pull_number, event, body }) => {
|
|
180
|
+
try {
|
|
181
|
+
const { data } = await getOctokit().rest.pulls.createReview({
|
|
182
|
+
owner,
|
|
183
|
+
repo,
|
|
184
|
+
pull_number,
|
|
185
|
+
event: event,
|
|
186
|
+
body: body ?? undefined,
|
|
187
|
+
});
|
|
188
|
+
return jsonResult({
|
|
189
|
+
id: data.id,
|
|
190
|
+
state: data.state,
|
|
191
|
+
html_url: data.html_url,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
catch (e) {
|
|
195
|
+
return errorResult(e);
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { owner, perPage, repo } from "../lib/fields.js";
|
|
3
|
+
import { getOctokit } from "../lib/github.js";
|
|
4
|
+
import { errorResult, jsonResult } from "../lib/response.js";
|
|
5
|
+
export function registerReleaseTools(server) {
|
|
6
|
+
server.tool("github_list_releases", "List releases for a repository.", {
|
|
7
|
+
owner,
|
|
8
|
+
repo,
|
|
9
|
+
per_page: perPage,
|
|
10
|
+
}, async ({ owner, repo, per_page }) => {
|
|
11
|
+
try {
|
|
12
|
+
const { data } = await getOctokit().rest.repos.listReleases({
|
|
13
|
+
owner,
|
|
14
|
+
repo,
|
|
15
|
+
per_page: per_page ?? 30,
|
|
16
|
+
});
|
|
17
|
+
return jsonResult(data.map((r) => ({
|
|
18
|
+
id: r.id,
|
|
19
|
+
tag_name: r.tag_name,
|
|
20
|
+
name: r.name,
|
|
21
|
+
draft: r.draft,
|
|
22
|
+
prerelease: r.prerelease,
|
|
23
|
+
published_at: r.published_at,
|
|
24
|
+
html_url: r.html_url,
|
|
25
|
+
})));
|
|
26
|
+
}
|
|
27
|
+
catch (e) {
|
|
28
|
+
return errorResult(e);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
server.tool("github_get_release_by_tag", "Get a release by tag name.", {
|
|
32
|
+
owner,
|
|
33
|
+
repo,
|
|
34
|
+
tag: z.string().describe("Tag name, e.g. v1.0.0"),
|
|
35
|
+
}, async ({ owner, repo, tag }) => {
|
|
36
|
+
try {
|
|
37
|
+
const { data } = await getOctokit().rest.repos.getReleaseByTag({
|
|
38
|
+
owner,
|
|
39
|
+
repo,
|
|
40
|
+
tag,
|
|
41
|
+
});
|
|
42
|
+
return jsonResult({
|
|
43
|
+
id: data.id,
|
|
44
|
+
tag_name: data.tag_name,
|
|
45
|
+
name: data.name,
|
|
46
|
+
body: data.body,
|
|
47
|
+
draft: data.draft,
|
|
48
|
+
prerelease: data.prerelease,
|
|
49
|
+
html_url: data.html_url,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
catch (e) {
|
|
53
|
+
return errorResult(e);
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
server.tool("github_create_release", "Create a GitHub release (optionally from an existing tag).", {
|
|
57
|
+
owner,
|
|
58
|
+
repo,
|
|
59
|
+
tag_name: z.string().describe("Tag for the release, e.g. v1.0.0"),
|
|
60
|
+
name: z.string().optional().describe("Release title"),
|
|
61
|
+
body: z.string().optional().describe("Release notes (markdown)"),
|
|
62
|
+
draft: z.boolean().optional(),
|
|
63
|
+
prerelease: z.boolean().optional(),
|
|
64
|
+
target_commitish: z.string().optional().describe("Branch or commit if tag is new"),
|
|
65
|
+
}, async ({ owner, repo, tag_name, name, body, draft, prerelease, target_commitish }) => {
|
|
66
|
+
try {
|
|
67
|
+
const { data } = await getOctokit().rest.repos.createRelease({
|
|
68
|
+
owner,
|
|
69
|
+
repo,
|
|
70
|
+
tag_name,
|
|
71
|
+
name: name ?? undefined,
|
|
72
|
+
body: body ?? undefined,
|
|
73
|
+
draft: draft ?? false,
|
|
74
|
+
prerelease: prerelease ?? false,
|
|
75
|
+
target_commitish: target_commitish ?? undefined,
|
|
76
|
+
});
|
|
77
|
+
return jsonResult({
|
|
78
|
+
id: data.id,
|
|
79
|
+
tag_name: data.tag_name,
|
|
80
|
+
html_url: data.html_url,
|
|
81
|
+
upload_url: data.upload_url,
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
catch (e) {
|
|
85
|
+
return errorResult(e);
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
server.tool("github_list_tags", "List git tags in a repository.", {
|
|
89
|
+
owner,
|
|
90
|
+
repo,
|
|
91
|
+
per_page: perPage,
|
|
92
|
+
}, async ({ owner, repo, per_page }) => {
|
|
93
|
+
try {
|
|
94
|
+
const { data } = await getOctokit().rest.repos.listTags({
|
|
95
|
+
owner,
|
|
96
|
+
repo,
|
|
97
|
+
per_page: per_page ?? 30,
|
|
98
|
+
});
|
|
99
|
+
return jsonResult(data.map((t) => ({
|
|
100
|
+
name: t.name,
|
|
101
|
+
sha: t.commit.sha,
|
|
102
|
+
})));
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
return errorResult(e);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
server.tool("github_create_tag", "Create a lightweight or annotated tag ref via the Git API.", {
|
|
109
|
+
owner,
|
|
110
|
+
repo,
|
|
111
|
+
tag: z.string().describe("Tag name, e.g. v1.0.0"),
|
|
112
|
+
sha: z.string().describe("Commit SHA to tag"),
|
|
113
|
+
message: z.string().optional().describe("If set, creates an annotated tag object"),
|
|
114
|
+
}, async ({ owner, repo, tag, sha, message }) => {
|
|
115
|
+
try {
|
|
116
|
+
const octokit = getOctokit();
|
|
117
|
+
if (message) {
|
|
118
|
+
const { data: tagObj } = await octokit.rest.git.createTag({
|
|
119
|
+
owner,
|
|
120
|
+
repo,
|
|
121
|
+
tag,
|
|
122
|
+
message,
|
|
123
|
+
object: sha,
|
|
124
|
+
type: "commit",
|
|
125
|
+
});
|
|
126
|
+
await octokit.rest.git.createRef({
|
|
127
|
+
owner,
|
|
128
|
+
repo,
|
|
129
|
+
ref: `refs/tags/${tag}`,
|
|
130
|
+
sha: tagObj.sha,
|
|
131
|
+
});
|
|
132
|
+
return jsonResult({ tag, sha: tagObj.sha, annotated: true });
|
|
133
|
+
}
|
|
134
|
+
const { data } = await octokit.rest.git.createRef({
|
|
135
|
+
owner,
|
|
136
|
+
repo,
|
|
137
|
+
ref: `refs/tags/${tag}`,
|
|
138
|
+
sha,
|
|
139
|
+
});
|
|
140
|
+
return jsonResult({ ref: data.ref, sha, annotated: false });
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
return errorResult(e);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
}
|