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.
@@ -0,0 +1,58 @@
1
+ import { z } from "zod";
2
+ import { getOctokit } from "../lib/github.js";
3
+ import { errorResult, jsonResult } from "../lib/response.js";
4
+ const org = z.string().describe("Organization login");
5
+ export function registerBillingTools(server) {
6
+ server.tool("github_get_user_billing_actions", "Get GitHub Actions billing summary for the authenticated user (minutes, included vs paid).", {}, async () => {
7
+ try {
8
+ const octokit = getOctokit();
9
+ const { data: me } = await octokit.rest.users.getAuthenticated();
10
+ const { data } = await octokit.rest.billing.getGithubActionsBillingUser({
11
+ username: me.login,
12
+ });
13
+ return jsonResult(data);
14
+ }
15
+ catch (e) {
16
+ return errorResult(e);
17
+ }
18
+ });
19
+ server.tool("github_get_org_billing_actions", "Get GitHub Actions billing for an organization.", { org }, async ({ org }) => {
20
+ try {
21
+ const { data } = await getOctokit().rest.billing.getGithubActionsBillingOrg({
22
+ org,
23
+ });
24
+ return jsonResult(data);
25
+ }
26
+ catch (e) {
27
+ return errorResult(e);
28
+ }
29
+ });
30
+ server.tool("github_get_org_billing_overview", "Get combined billing overview for an org (Actions, Packages, shared storage).", { org }, async ({ org }) => {
31
+ try {
32
+ const octokit = getOctokit();
33
+ const [actions, packages, storage] = await Promise.allSettled([
34
+ octokit.rest.billing.getGithubActionsBillingOrg({ org }),
35
+ octokit.rest.billing.getGithubPackagesBillingOrg({ org }),
36
+ octokit.rest.billing.getSharedStorageBillingOrg({ org }),
37
+ ]);
38
+ return jsonResult({
39
+ actions: actions.status === "fulfilled" ? actions.value.data : { error: String(actions.reason) },
40
+ packages: packages.status === "fulfilled" ? packages.value.data : { error: String(packages.reason) },
41
+ shared_storage: storage.status === "fulfilled" ? storage.value.data : { error: String(storage.reason) },
42
+ });
43
+ }
44
+ catch (e) {
45
+ return errorResult(e);
46
+ }
47
+ });
48
+ server.tool("github_get_org_billing_usage_report", "Request billing usage export metadata (org plans — requires billing admin).", { org }, async ({ org }) => {
49
+ try {
50
+ const octokit = getOctokit();
51
+ const { data: usage } = await octokit.request("GET /orgs/{org}/settings/billing/usage", { org });
52
+ return jsonResult(usage);
53
+ }
54
+ catch (e) {
55
+ return errorResult(e);
56
+ }
57
+ });
58
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerBranchProtectionTools(server: McpServer): void;
@@ -0,0 +1,125 @@
1
+ import { z } from "zod";
2
+ import { owner, repo } from "../lib/fields.js";
3
+ import { getOctokit } from "../lib/github.js";
4
+ import { errorResult, jsonResult } from "../lib/response.js";
5
+ export function registerBranchProtectionTools(server) {
6
+ server.tool("github_get_branch", "Get branch metadata including protection status.", {
7
+ owner,
8
+ repo,
9
+ branch: z.string(),
10
+ }, async ({ owner, repo, branch }) => {
11
+ try {
12
+ const { data } = await getOctokit().rest.repos.getBranch({
13
+ owner,
14
+ repo,
15
+ branch,
16
+ });
17
+ return jsonResult({
18
+ name: data.name,
19
+ protected: data.protected,
20
+ commit_sha: data.commit.sha,
21
+ protection: data.protection,
22
+ });
23
+ }
24
+ catch (e) {
25
+ return errorResult(e);
26
+ }
27
+ });
28
+ server.tool("github_get_branch_protection", "Get branch protection rules for a branch.", {
29
+ owner,
30
+ repo,
31
+ branch: z.string(),
32
+ }, async ({ owner, repo, branch }) => {
33
+ try {
34
+ const { data } = await getOctokit().rest.repos.getBranchProtection({
35
+ owner,
36
+ repo,
37
+ branch,
38
+ });
39
+ return jsonResult(data);
40
+ }
41
+ catch (e) {
42
+ return errorResult(e);
43
+ }
44
+ });
45
+ server.tool("github_update_branch_protection", "Update branch protection (require PR reviews, status checks, admins).", {
46
+ owner,
47
+ repo,
48
+ branch: z.string(),
49
+ required_status_checks: z
50
+ .string()
51
+ .optional()
52
+ .describe('JSON: { "strict": true, "contexts": ["ci/build"] }'),
53
+ enforce_admins: z.boolean().optional(),
54
+ required_pull_request_reviews: z
55
+ .string()
56
+ .optional()
57
+ .describe('JSON: { "required_approving_review_count": 1 }'),
58
+ restrictions: z
59
+ .string()
60
+ .optional()
61
+ .describe('JSON: { "users": [], "teams": [], "apps": [] } or null to disable'),
62
+ allow_force_pushes: z.boolean().optional(),
63
+ allow_deletions: z.boolean().optional(),
64
+ }, async ({ owner, repo, branch, required_status_checks, enforce_admins, required_pull_request_reviews, restrictions, allow_force_pushes, allow_deletions, }) => {
65
+ try {
66
+ const { data } = await getOctokit().rest.repos.updateBranchProtection({
67
+ owner,
68
+ repo,
69
+ branch,
70
+ required_status_checks: required_status_checks
71
+ ? JSON.parse(required_status_checks)
72
+ : null,
73
+ enforce_admins: enforce_admins ?? null,
74
+ required_pull_request_reviews: required_pull_request_reviews
75
+ ? JSON.parse(required_pull_request_reviews)
76
+ : null,
77
+ restrictions: restrictions ? JSON.parse(restrictions) : null,
78
+ allow_force_pushes: allow_force_pushes ?? false,
79
+ allow_deletions: allow_deletions ?? false,
80
+ });
81
+ return jsonResult(data);
82
+ }
83
+ catch (e) {
84
+ return errorResult(e);
85
+ }
86
+ });
87
+ server.tool("github_delete_branch_protection", "Remove branch protection from a branch.", {
88
+ owner,
89
+ repo,
90
+ branch: z.string(),
91
+ }, async ({ owner, repo, branch }) => {
92
+ try {
93
+ await getOctokit().rest.repos.deleteBranchProtection({
94
+ owner,
95
+ repo,
96
+ branch,
97
+ });
98
+ return jsonResult({ ok: true, branch, message: "Branch protection removed" });
99
+ }
100
+ catch (e) {
101
+ return errorResult(e);
102
+ }
103
+ });
104
+ server.tool("github_list_branches", "List branches in a repository.", {
105
+ owner,
106
+ repo,
107
+ per_page: z.number().optional(),
108
+ }, async ({ owner, repo, per_page }) => {
109
+ try {
110
+ const { data } = await getOctokit().rest.repos.listBranches({
111
+ owner,
112
+ repo,
113
+ per_page: per_page ?? 100,
114
+ });
115
+ return jsonResult(data.map((b) => ({
116
+ name: b.name,
117
+ protected: b.protected,
118
+ sha: b.commit.sha,
119
+ })));
120
+ }
121
+ catch (e) {
122
+ return errorResult(e);
123
+ }
124
+ });
125
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerCommitTools(server: McpServer): void;
@@ -0,0 +1,203 @@
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 registerCommitTools(server) {
6
+ server.tool("github_list_commits", "List commits on a branch or for the whole repo.", {
7
+ owner,
8
+ repo,
9
+ sha: z.string().optional().describe("Branch or commit SHA to start from"),
10
+ per_page: perPage,
11
+ }, async ({ owner, repo, sha, per_page }) => {
12
+ try {
13
+ const { data } = await getOctokit().rest.repos.listCommits({
14
+ owner,
15
+ repo,
16
+ sha: sha ?? undefined,
17
+ per_page: per_page ?? 30,
18
+ });
19
+ return jsonResult(data.map((c) => ({
20
+ sha: c.sha,
21
+ message: c.commit.message,
22
+ author: c.commit.author?.name,
23
+ date: c.commit.author?.date,
24
+ html_url: c.html_url,
25
+ })));
26
+ }
27
+ catch (e) {
28
+ return errorResult(e);
29
+ }
30
+ });
31
+ server.tool("github_get_commit", "Get a single commit with files changed.", {
32
+ owner,
33
+ repo,
34
+ ref: z.string().describe("Commit SHA"),
35
+ }, async ({ owner, repo, ref }) => {
36
+ try {
37
+ const { data } = await getOctokit().rest.repos.getCommit({
38
+ owner,
39
+ repo,
40
+ ref,
41
+ });
42
+ return jsonResult({
43
+ sha: data.sha,
44
+ message: data.commit.message,
45
+ author: data.commit.author,
46
+ stats: data.stats,
47
+ files: data.files?.map((f) => ({
48
+ filename: f.filename,
49
+ status: f.status,
50
+ additions: f.additions,
51
+ deletions: f.deletions,
52
+ })),
53
+ html_url: data.html_url,
54
+ });
55
+ }
56
+ catch (e) {
57
+ return errorResult(e);
58
+ }
59
+ });
60
+ server.tool("github_compare_commits", "Compare two commits (base...head) — useful for release notes and PR scope.", {
61
+ owner,
62
+ repo,
63
+ base: z.string().describe("Base ref or SHA"),
64
+ head: z.string().describe("Head ref or SHA"),
65
+ }, async ({ owner, repo, base, head }) => {
66
+ try {
67
+ const { data } = await getOctokit().rest.repos.compareCommitsWithBasehead({
68
+ owner,
69
+ repo,
70
+ basehead: `${base}...${head}`,
71
+ });
72
+ return jsonResult({
73
+ status: data.status,
74
+ ahead_by: data.ahead_by,
75
+ behind_by: data.behind_by,
76
+ total_commits: data.total_commits,
77
+ commits: data.commits?.map((c) => ({
78
+ sha: c.sha,
79
+ message: c.commit.message,
80
+ })),
81
+ files: data.files?.map((f) => f.filename),
82
+ });
83
+ }
84
+ catch (e) {
85
+ return errorResult(e);
86
+ }
87
+ });
88
+ server.tool("github_create_commit", "Create a commit on a branch by updating one or more files (GitHub Git API — no local clone required).", {
89
+ owner,
90
+ repo,
91
+ branch: z.string().describe("Branch to commit to"),
92
+ message: z.string().describe("Commit message"),
93
+ files: z
94
+ .string()
95
+ .describe('JSON array of { "path": "src/a.ts", "content": "...", "encoding": "utf-8" | "base64" }'),
96
+ }, async ({ owner, repo, branch, message, files }) => {
97
+ try {
98
+ const fileList = JSON.parse(files);
99
+ if (!Array.isArray(fileList) || fileList.length === 0) {
100
+ throw new Error("files must be a non-empty JSON array");
101
+ }
102
+ const octokit = getOctokit();
103
+ const refRes = await octokit.rest.git.getRef({
104
+ owner,
105
+ repo,
106
+ ref: `heads/${branch}`,
107
+ });
108
+ const parentSha = refRes.data.object.sha;
109
+ const { data: parentCommit } = await octokit.rest.git.getCommit({
110
+ owner,
111
+ repo,
112
+ commit_sha: parentSha,
113
+ });
114
+ const treeItems = [];
115
+ for (const file of fileList) {
116
+ const encoding = file.encoding ?? "utf-8";
117
+ const { data: blob } = await octokit.rest.git.createBlob({
118
+ owner,
119
+ repo,
120
+ content: file.content,
121
+ encoding: encoding === "base64" ? "base64" : "utf-8",
122
+ });
123
+ treeItems.push({
124
+ path: file.path,
125
+ mode: "100644",
126
+ type: "blob",
127
+ sha: blob.sha,
128
+ });
129
+ }
130
+ const { data: tree } = await octokit.rest.git.createTree({
131
+ owner,
132
+ repo,
133
+ base_tree: parentCommit.tree.sha,
134
+ tree: treeItems,
135
+ });
136
+ const { data: commit } = await octokit.rest.git.createCommit({
137
+ owner,
138
+ repo,
139
+ message,
140
+ tree: tree.sha,
141
+ parents: [parentSha],
142
+ });
143
+ await octokit.rest.git.updateRef({
144
+ owner,
145
+ repo,
146
+ ref: `heads/${branch}`,
147
+ sha: commit.sha,
148
+ });
149
+ return jsonResult({
150
+ sha: commit.sha,
151
+ message: commit.message,
152
+ branch,
153
+ html_url: `https://github.com/${owner}/${repo}/commit/${commit.sha}`,
154
+ });
155
+ }
156
+ catch (e) {
157
+ return errorResult(e);
158
+ }
159
+ });
160
+ server.tool("github_get_combined_status", "Get combined commit status (legacy statuses + check runs summary) for CI/CD gates.", {
161
+ owner,
162
+ repo,
163
+ ref: z.string().describe("Commit SHA or ref"),
164
+ }, async ({ owner, repo, ref }) => {
165
+ try {
166
+ const { data } = await getOctokit().rest.repos.getCombinedStatusForRef({
167
+ owner,
168
+ repo,
169
+ ref,
170
+ });
171
+ return jsonResult(data);
172
+ }
173
+ catch (e) {
174
+ return errorResult(e);
175
+ }
176
+ });
177
+ server.tool("github_list_check_runs_for_ref", "List GitHub Actions check runs for a commit ref.", {
178
+ owner,
179
+ repo,
180
+ ref: z.string().describe("Commit SHA"),
181
+ }, async ({ owner, repo, ref }) => {
182
+ try {
183
+ const { data } = await getOctokit().rest.checks.listForRef({
184
+ owner,
185
+ repo,
186
+ ref,
187
+ per_page: 100,
188
+ });
189
+ return jsonResult(data.check_runs.map((c) => ({
190
+ id: c.id,
191
+ name: c.name,
192
+ status: c.status,
193
+ conclusion: c.conclusion,
194
+ html_url: c.html_url,
195
+ started_at: c.started_at,
196
+ completed_at: c.completed_at,
197
+ })));
198
+ }
199
+ catch (e) {
200
+ return errorResult(e);
201
+ }
202
+ });
203
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerDeploymentTools(server: McpServer): void;
@@ -0,0 +1,98 @@
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 registerDeploymentTools(server) {
6
+ server.tool("github_list_deployments", "List deployments for a repository (CD history).", {
7
+ owner,
8
+ repo,
9
+ environment: z.string().optional().describe("Filter by environment name"),
10
+ per_page: perPage,
11
+ }, async ({ owner, repo, environment, per_page }) => {
12
+ try {
13
+ const { data } = await getOctokit().rest.repos.listDeployments({
14
+ owner,
15
+ repo,
16
+ environment: environment ?? undefined,
17
+ per_page: per_page ?? 30,
18
+ });
19
+ return jsonResult(data.map((d) => ({
20
+ id: d.id,
21
+ ref: d.ref,
22
+ sha: d.sha,
23
+ environment: d.environment,
24
+ description: d.description,
25
+ created_at: d.created_at,
26
+ })));
27
+ }
28
+ catch (e) {
29
+ return errorResult(e);
30
+ }
31
+ });
32
+ server.tool("github_create_deployment", "Create a deployment for a ref (triggers deployment workflows / environments).", {
33
+ owner,
34
+ repo,
35
+ ref: z.string().describe("Branch, tag, or SHA"),
36
+ environment: z.string().optional().describe("e.g. production, staging"),
37
+ description: z.string().optional(),
38
+ auto_merge: z.boolean().optional().describe("Merge default branch into ref"),
39
+ required_contexts: z
40
+ .string()
41
+ .optional()
42
+ .describe('JSON array of status check names, or "[]" for none'),
43
+ }, async ({ owner, repo, ref, environment, description, auto_merge, required_contexts }) => {
44
+ try {
45
+ const contexts = required_contexts ? JSON.parse(required_contexts) : undefined;
46
+ const { data } = await getOctokit().rest.repos.createDeployment({
47
+ owner,
48
+ repo,
49
+ ref,
50
+ environment: environment ?? "production",
51
+ description: description ?? undefined,
52
+ auto_merge: auto_merge ?? false,
53
+ required_contexts: contexts,
54
+ });
55
+ return jsonResult(data);
56
+ }
57
+ catch (e) {
58
+ return errorResult(e);
59
+ }
60
+ });
61
+ server.tool("github_create_deployment_status", "Update deployment status (success, failure, in_progress) for CD observability.", {
62
+ owner,
63
+ repo,
64
+ deployment_id: z.number(),
65
+ state: z
66
+ .string()
67
+ .describe("error | failure | inactive | in_progress | queued | pending | success"),
68
+ environment_url: z.string().optional().describe("Live site URL"),
69
+ description: z.string().optional(),
70
+ }, async ({ owner, repo, deployment_id, state, environment_url, description }) => {
71
+ try {
72
+ const { data } = await getOctokit().rest.repos.createDeploymentStatus({
73
+ owner,
74
+ repo,
75
+ deployment_id,
76
+ state: state,
77
+ environment_url: environment_url ?? undefined,
78
+ description: description ?? undefined,
79
+ });
80
+ return jsonResult(data);
81
+ }
82
+ catch (e) {
83
+ return errorResult(e);
84
+ }
85
+ });
86
+ server.tool("github_list_environments", "List deployment environments and protection rules.", { owner, repo }, async ({ owner, repo }) => {
87
+ try {
88
+ const { data } = await getOctokit().rest.repos.getAllEnvironments({
89
+ owner,
90
+ repo,
91
+ });
92
+ return jsonResult(data.environments);
93
+ }
94
+ catch (e) {
95
+ return errorResult(e);
96
+ }
97
+ });
98
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerGraphqlTools(server: McpServer): void;
@@ -0,0 +1,73 @@
1
+ import { graphql } from "@octokit/graphql";
2
+ import { z } from "zod";
3
+ import { getToken } from "../lib/github.js";
4
+ import { errorResult, jsonResult } from "../lib/response.js";
5
+ function graphqlClient() {
6
+ const baseUrl = process.env.GITHUB_GRAPHQL_URL ??
7
+ (process.env.GITHUB_HOST && process.env.GITHUB_HOST !== "github.com"
8
+ ? `https://${process.env.GITHUB_HOST}/api/graphql`
9
+ : undefined);
10
+ return graphql.defaults({
11
+ headers: {
12
+ authorization: `token ${getToken()}`,
13
+ },
14
+ ...(baseUrl ? { baseUrl } : {}),
15
+ });
16
+ }
17
+ export function registerGraphqlTools(server) {
18
+ server.tool("github_graphql_query", "Run a read-only GitHub GraphQL query (https://docs.github.com/en/graphql). Variables optional.", {
19
+ query: z.string().describe("GraphQL query string"),
20
+ variables: z.string().optional().describe("JSON object of GraphQL variables"),
21
+ }, async ({ query, variables }) => {
22
+ try {
23
+ const trimmed = query.trim();
24
+ if (/^\s*mutation\b/i.test(trimmed)) {
25
+ throw new Error("Mutations are blocked in this tool. Use REST tools or github_graphql_mutation explicitly.");
26
+ }
27
+ const vars = variables ? JSON.parse(variables) : undefined;
28
+ const data = await graphqlClient()(query, vars);
29
+ return jsonResult(data);
30
+ }
31
+ catch (e) {
32
+ return errorResult(e);
33
+ }
34
+ });
35
+ server.tool("github_graphql_mutation", "Run a GitHub GraphQL mutation (create/update via GraphQL API). Use with care.", {
36
+ query: z.string().describe("GraphQL mutation string"),
37
+ variables: z.string().optional().describe("JSON object of GraphQL variables"),
38
+ }, async ({ query, variables }) => {
39
+ try {
40
+ const vars = variables ? JSON.parse(variables) : undefined;
41
+ const data = await graphqlClient()(query, vars);
42
+ return jsonResult(data);
43
+ }
44
+ catch (e) {
45
+ return errorResult(e);
46
+ }
47
+ });
48
+ server.tool("github_graphql_repository_overview", "Quick GraphQL snapshot: repo, default branch, latest commit, open PR count.", {
49
+ owner: z.string(),
50
+ name: z.string().describe("Repository name"),
51
+ }, async ({ owner, name }) => {
52
+ try {
53
+ const data = await graphqlClient()(`
54
+ query RepoOverview($owner: String!, $name: String!) {
55
+ repository(owner: $owner, name: $name) {
56
+ nameWithOwner
57
+ description
58
+ isPrivate
59
+ defaultBranchRef { name }
60
+ pushedAt
61
+ openPullRequests: pullRequests(states: OPEN) { totalCount }
62
+ openIssues: issues(states: OPEN) { totalCount }
63
+ primaryLanguage { name }
64
+ }
65
+ }
66
+ `, { owner, name });
67
+ return jsonResult(data);
68
+ }
69
+ catch (e) {
70
+ return errorResult(e);
71
+ }
72
+ });
73
+ }
@@ -0,0 +1,2 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ export declare function registerIssueTools(server: McpServer): void;