github-pro-mcp 0.0.1
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/README.md +133 -0
- package/dist/client.d.ts +27 -0
- package/dist/client.js +80 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +52 -0
- package/dist/tools/issue_fields.d.ts +4 -0
- package/dist/tools/issue_fields.js +104 -0
- package/dist/tools/issues.d.ts +12 -0
- package/dist/tools/issues.js +607 -0
- package/dist/tools/labels.d.ts +3 -0
- package/dist/tools/labels.js +87 -0
- package/dist/tools/organizations.d.ts +4 -0
- package/dist/tools/organizations.js +89 -0
- package/dist/tools/projects.d.ts +4 -0
- package/dist/tools/projects.js +474 -0
- package/dist/tools/pull_requests.d.ts +4 -0
- package/dist/tools/pull_requests.js +127 -0
- package/dist/tools/repos.d.ts +8 -0
- package/dist/tools/repos.js +217 -0
- package/dist/types/github-api.d.ts +63 -0
- package/dist/types/github-api.js +1 -0
- package/dist/utils/circuitBreaker.d.ts +11 -0
- package/dist/utils/circuitBreaker.js +125 -0
- package/dist/utils/errors.d.ts +17 -0
- package/dist/utils/errors.js +107 -0
- package/dist/utils/logger.d.ts +14 -0
- package/dist/utils/logger.js +40 -0
- package/dist/utils/metrics.d.ts +11 -0
- package/dist/utils/metrics.js +70 -0
- package/dist/utils/rateLimit.d.ts +21 -0
- package/dist/utils/rateLimit.js +40 -0
- package/dist/utils/retry.d.ts +9 -0
- package/dist/utils/retry.js +76 -0
- package/dist/utils/types.d.ts +33 -0
- package/dist/utils/types.js +33 -0
- package/dist/utils/validation.d.ts +12 -0
- package/dist/utils/validation.js +75 -0
- package/package.json +61 -0
package/README.md
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# GitHub Pro MCP
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/github-pro-mcp)
|
|
4
|
+
[](https://hub.docker.com/r/anantanandgupta/github-pro-mcp)
|
|
5
|
+
|
|
6
|
+
An MCP (Model Context Protocol) server providing comprehensive GitHub API access via hybrid REST + GraphQL — **32 tools across 7 domains**.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **32 Tools** — issues, pull requests, repositories, projects v2, labels, organizations, file operations
|
|
11
|
+
- **Hybrid API** — REST for CRUD/search, GraphQL for cursor pagination and composite queries
|
|
12
|
+
- **Resilient by Default** — automatic retry with exponential backoff + jitter, circuit breaker for API outages, proactive rate limit management
|
|
13
|
+
- **Structured Logging** — multi-level (error/warn/info/debug) stderr output
|
|
14
|
+
- **Owner/Repo Pinning** — lock the server to a specific owner or repository as a safety guardrail
|
|
15
|
+
|
|
16
|
+
## Installation
|
|
17
|
+
|
|
18
|
+
### npm
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Install globally
|
|
22
|
+
npm install -g github-pro-mcp
|
|
23
|
+
|
|
24
|
+
# Or run directly without installing
|
|
25
|
+
npx github-pro-mcp
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### Docker
|
|
29
|
+
|
|
30
|
+
Multi-arch images for `linux/amd64` and `linux/arm64`:
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
docker pull anantanandgupta/github-pro-mcp
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Quick Start
|
|
37
|
+
|
|
38
|
+
Set your GitHub token and run:
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
export GITHUB_PERSONAL_ACCESS_TOKEN=ghp_your_token_here
|
|
42
|
+
github-pro-mcp
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Or with Docker:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
docker run -i --rm \
|
|
49
|
+
-e GITHUB_PERSONAL_ACCESS_TOKEN=ghp_your_token_here \
|
|
50
|
+
anantanandgupta/github-pro-mcp
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Configuration
|
|
54
|
+
|
|
55
|
+
### Required
|
|
56
|
+
|
|
57
|
+
| Variable | Description |
|
|
58
|
+
|----------|-------------|
|
|
59
|
+
| `GITHUB_PERSONAL_ACCESS_TOKEN` | GitHub PAT with appropriate scopes |
|
|
60
|
+
|
|
61
|
+
### Optional
|
|
62
|
+
|
|
63
|
+
| Variable | Default | Description |
|
|
64
|
+
|----------|---------|-------------|
|
|
65
|
+
| `GITHUB_HOST` | — | For GHES: `https://{hostname}/api/v3` |
|
|
66
|
+
| `GITHUB_OWNER` | — | Pin server to a single owner (rejects mismatched calls) |
|
|
67
|
+
| `GITHUB_REPO` | — | Pin server to a single repository (rejects mismatched calls) |
|
|
68
|
+
| `GITHUB_RETRY_MAX_ATTEMPTS` | `3` | Max retries for transient errors |
|
|
69
|
+
| `GITHUB_RETRY_BACKOFF_MS` | `1000` | Initial retry backoff (ms) |
|
|
70
|
+
| `GITHUB_CIRCUIT_BREAKER_THRESHOLD` | `5` | Failures before circuit opens |
|
|
71
|
+
| `GITHUB_CIRCUIT_BREAKER_RESET_MS` | `30000` | Circuit recovery timeout (ms) |
|
|
72
|
+
| `GITHUB_LOG_LEVEL` | `error` | Log level: `error`, `warn`, `info`, `debug` |
|
|
73
|
+
|
|
74
|
+
## Usage with opencode
|
|
75
|
+
|
|
76
|
+
Add to your `opencode.json`:
|
|
77
|
+
|
|
78
|
+
```json
|
|
79
|
+
{
|
|
80
|
+
"mcp": {
|
|
81
|
+
"github_pro": {
|
|
82
|
+
"type": "local",
|
|
83
|
+
"command": ["node", "path/to/github-pro-mcp/dist/index.js"],
|
|
84
|
+
"environment": {
|
|
85
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN": "{env:GITHUB_TOKEN}"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
For Docker-based usage:
|
|
93
|
+
|
|
94
|
+
```json
|
|
95
|
+
{
|
|
96
|
+
"mcp": {
|
|
97
|
+
"github_pro": {
|
|
98
|
+
"type": "local",
|
|
99
|
+
"command": ["docker", "run", "-i", "--rm", "-e", "GITHUB_PERSONAL_ACCESS_TOKEN", "anantanandgupta/github-pro-mcp"],
|
|
100
|
+
"environment": {
|
|
101
|
+
"GITHUB_PERSONAL_ACCESS_TOKEN": "{env:GITHUB_TOKEN}"
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## Tool Reference
|
|
109
|
+
|
|
110
|
+
| Domain | Tools | Count |
|
|
111
|
+
|--------|-------|-------|
|
|
112
|
+
| **User & Repos** | `auth_get_current_login`, `file_get_content`, `profile_get`, `owner_list_repository`, `file_create_update`, `code_search`, `repository_search` | 7 |
|
|
113
|
+
| **Issues** | `issue_read`, `issue_list`, `issue_search`, `issue_add_comment`, `sub_issue_write`, `issue_create`, `issue_close`, `issue_reopen`, `issue_update`, `issue_add_label`, `issue_remove_label` | 11 |
|
|
114
|
+
| **Issue Fields** | `owner_list_issue_field`, `issue_get_field_value`, `issue_set_field_value` | 3 |
|
|
115
|
+
| **Pull Requests** | `pull_request_read`, `pull_request_create`, `pull_request_merge` | 3 |
|
|
116
|
+
| **Organizations** | `organization_search`, `owner_list_issue_type`, `owner_list_organization` | 3 |
|
|
117
|
+
| **Projects v2** | `owner_list_project`, `projects_get`, `projects_write` | 3 |
|
|
118
|
+
| **Labels** | `label_list`, `label_write` | 2 |
|
|
119
|
+
| **Total** | | **32** |
|
|
120
|
+
|
|
121
|
+
## FAQ / Troubleshooting
|
|
122
|
+
|
|
123
|
+
| Error | Likely Cause |
|
|
124
|
+
|-------|-------------|
|
|
125
|
+
| `401` | Token expired or invalid |
|
|
126
|
+
| `403` | Missing OAuth scopes or SSO |
|
|
127
|
+
| `404` | Resource doesn't exist or no access |
|
|
128
|
+
| `429` | Rate limited — wait and retry |
|
|
129
|
+
| Circuit breaker open | GitHub API returning repeated errors — auto-recovers in 30s |
|
|
130
|
+
|
|
131
|
+
## License
|
|
132
|
+
|
|
133
|
+
ISC
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Octokit } from "@octokit/core";
|
|
2
|
+
import type { Api } from "@octokit/plugin-rest-endpoint-methods";
|
|
3
|
+
export type OctokitWithRest = Octokit & {
|
|
4
|
+
rest: Api["rest"];
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Creates an authenticated Octokit client with all plugins configured.
|
|
8
|
+
*
|
|
9
|
+
* The returned client wraps all REST requests through:
|
|
10
|
+
* circuit breaker → retry → throttling → GitHub API
|
|
11
|
+
*
|
|
12
|
+
* Rate limit headers from responses are automatically tracked
|
|
13
|
+
* via `updateRateLimits()`.
|
|
14
|
+
*/
|
|
15
|
+
export declare function createClient(): OctokitWithRest;
|
|
16
|
+
/**
|
|
17
|
+
* Execute a raw GraphQL query against the GitHub API.
|
|
18
|
+
* The query is wrapped with circuit breaker and retry protection.
|
|
19
|
+
*/
|
|
20
|
+
export declare function gql<T>(query: string, variables?: Record<string, unknown>): Promise<T>;
|
|
21
|
+
/**
|
|
22
|
+
* Derive the web UI base URL from the GITHUB_HOST env var.
|
|
23
|
+
* For api.github.com, returns https://github.com.
|
|
24
|
+
* For GHES, derives from the hostname.
|
|
25
|
+
*/
|
|
26
|
+
export declare function getWebBaseUrl(): string;
|
|
27
|
+
export declare function gqlPaginate<T>(query: string, variables?: Record<string, unknown>): Promise<T[]>;
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { Octokit } from "@octokit/core";
|
|
2
|
+
import { restEndpointMethods } from "@octokit/plugin-rest-endpoint-methods";
|
|
3
|
+
import { paginateGraphQL } from "@octokit/plugin-paginate-graphql";
|
|
4
|
+
import { throttling } from "@octokit/plugin-throttling";
|
|
5
|
+
import { withRetry } from "./utils/retry.js";
|
|
6
|
+
import { withCircuitBreaker } from "./utils/circuitBreaker.js";
|
|
7
|
+
import { createLogger } from "./utils/logger.js";
|
|
8
|
+
import { updateRateLimits } from "./utils/rateLimit.js";
|
|
9
|
+
const log = createLogger("client");
|
|
10
|
+
const MyOctokit = Octokit
|
|
11
|
+
.plugin(restEndpointMethods)
|
|
12
|
+
.plugin(paginateGraphQL)
|
|
13
|
+
.plugin(throttling);
|
|
14
|
+
/**
|
|
15
|
+
* Creates an authenticated Octokit client with all plugins configured.
|
|
16
|
+
*
|
|
17
|
+
* The returned client wraps all REST requests through:
|
|
18
|
+
* circuit breaker → retry → throttling → GitHub API
|
|
19
|
+
*
|
|
20
|
+
* Rate limit headers from responses are automatically tracked
|
|
21
|
+
* via `updateRateLimits()`.
|
|
22
|
+
*/
|
|
23
|
+
export function createClient() {
|
|
24
|
+
const token = process.env.GITHUB_PERSONAL_ACCESS_TOKEN;
|
|
25
|
+
if (!token) {
|
|
26
|
+
throw new Error("GITHUB_PERSONAL_ACCESS_TOKEN environment variable is not set. " +
|
|
27
|
+
"Create a token at https://github.com/settings/tokens " +
|
|
28
|
+
"and set it as GITHUB_PERSONAL_ACCESS_TOKEN in your environment.");
|
|
29
|
+
}
|
|
30
|
+
const onRateLimitStrategy = process.env.GITHUB_THROTTLE_ON_RATE_LIMIT !== "queue";
|
|
31
|
+
const octokit = new MyOctokit({
|
|
32
|
+
auth: token,
|
|
33
|
+
baseUrl: process.env.GITHUB_HOST ?? "https://api.github.com",
|
|
34
|
+
throttle: {
|
|
35
|
+
onRateLimit: (retryAfter, options) => {
|
|
36
|
+
log.warn(`Primary rate limit hit. Retrying after ${retryAfter}s. ` +
|
|
37
|
+
`Request: ${options.method} ${options.url}`);
|
|
38
|
+
return onRateLimitStrategy;
|
|
39
|
+
},
|
|
40
|
+
onSecondaryRateLimit: (retryAfter, options) => {
|
|
41
|
+
log.warn(`Secondary rate limit hit. Retrying after ${retryAfter}s. ` +
|
|
42
|
+
`Request: ${options.method} ${options.url}`);
|
|
43
|
+
return process.env.GITHUB_THROTTLE_ON_SECONDARY_RATE_LIMIT !== "queue";
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
octokit.hook.after("request", (response) => {
|
|
48
|
+
if (response?.headers) {
|
|
49
|
+
updateRateLimits(response.headers);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
octokit.hook.wrap("request", async (request, options) => {
|
|
53
|
+
const label = `REST ${options.method} ${options.url}`;
|
|
54
|
+
return withCircuitBreaker(() => withRetry(() => Promise.resolve(request(options)), label), "github-rest");
|
|
55
|
+
});
|
|
56
|
+
return octokit;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Execute a raw GraphQL query against the GitHub API.
|
|
60
|
+
* The query is wrapped with circuit breaker and retry protection.
|
|
61
|
+
*/
|
|
62
|
+
export async function gql(query, variables) {
|
|
63
|
+
const octokit = createClient();
|
|
64
|
+
return withCircuitBreaker(() => withRetry(() => octokit.graphql(query, variables), `GraphQL ${query.trim().split("\n")[0].slice(0, 60)}`), "github-graphql");
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Derive the web UI base URL from the GITHUB_HOST env var.
|
|
68
|
+
* For api.github.com, returns https://github.com.
|
|
69
|
+
* For GHES, derives from the hostname.
|
|
70
|
+
*/
|
|
71
|
+
export function getWebBaseUrl() {
|
|
72
|
+
const apiUrl = new URL(process.env.GITHUB_HOST ?? "https://api.github.com");
|
|
73
|
+
if (apiUrl.hostname === "api.github.com")
|
|
74
|
+
return "https://github.com";
|
|
75
|
+
return `https://${apiUrl.hostname}`;
|
|
76
|
+
}
|
|
77
|
+
export async function gqlPaginate(query, variables) {
|
|
78
|
+
const octokit = createClient();
|
|
79
|
+
return withCircuitBreaker(() => withRetry(() => octokit.graphql.paginate(query, variables), `GraphQL paginate ${query.trim().split("\n")[0].slice(0, 60)}`), "github-graphql");
|
|
80
|
+
}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { registerAuthGetCurrentLogin, registerFileGetContent, registerProfileGet, registerOwnerListRepository, registerFileCreateUpdate, registerCodeSearch, registerRepositorySearch, } from "./tools/repos.js";
|
|
5
|
+
import { registerOrganizationSearch, registerOwnerListIssueType, registerOwnerListOrganization, } from "./tools/organizations.js";
|
|
6
|
+
import { registerPullRequestRead, registerPullRequestCreate, registerPullRequestMerge, } from "./tools/pull_requests.js";
|
|
7
|
+
import { registerIssueRead, registerIssueList, registerIssueSearch, registerIssueAddComment, registerSubIssueWrite, registerIssueCreate, registerIssueClose, registerIssueReopen, registerIssueUpdate, registerIssueAddLabel, registerIssueRemoveLabel, } from "./tools/issues.js";
|
|
8
|
+
import { registerOwnerListIssueField, registerIssueGetFieldValue, registerIssueSetFieldValue, } from "./tools/issue_fields.js";
|
|
9
|
+
import { registerLabelList, registerLabelWrite, } from "./tools/labels.js";
|
|
10
|
+
import { registerOwnerListProject, registerProjectsGet, registerProjectsWrite, } from "./tools/projects.js";
|
|
11
|
+
const server = new McpServer({ name: "github-pro-mcp", version: "2.0.1" }, { capabilities: { tools: {} } });
|
|
12
|
+
registerAuthGetCurrentLogin(server);
|
|
13
|
+
registerFileGetContent(server);
|
|
14
|
+
registerProfileGet(server);
|
|
15
|
+
registerOwnerListRepository(server);
|
|
16
|
+
registerFileCreateUpdate(server);
|
|
17
|
+
registerCodeSearch(server);
|
|
18
|
+
registerRepositorySearch(server);
|
|
19
|
+
registerOrganizationSearch(server);
|
|
20
|
+
registerOwnerListIssueType(server);
|
|
21
|
+
registerOwnerListOrganization(server);
|
|
22
|
+
registerPullRequestRead(server);
|
|
23
|
+
registerPullRequestCreate(server);
|
|
24
|
+
registerPullRequestMerge(server);
|
|
25
|
+
registerIssueRead(server);
|
|
26
|
+
registerIssueList(server);
|
|
27
|
+
registerIssueSearch(server);
|
|
28
|
+
registerIssueAddComment(server);
|
|
29
|
+
registerSubIssueWrite(server);
|
|
30
|
+
registerIssueCreate(server);
|
|
31
|
+
registerIssueClose(server);
|
|
32
|
+
registerIssueReopen(server);
|
|
33
|
+
registerIssueUpdate(server);
|
|
34
|
+
registerIssueAddLabel(server);
|
|
35
|
+
registerIssueRemoveLabel(server);
|
|
36
|
+
registerOwnerListIssueField(server);
|
|
37
|
+
registerIssueGetFieldValue(server);
|
|
38
|
+
registerIssueSetFieldValue(server);
|
|
39
|
+
registerLabelList(server);
|
|
40
|
+
registerLabelWrite(server);
|
|
41
|
+
registerOwnerListProject(server);
|
|
42
|
+
registerProjectsGet(server);
|
|
43
|
+
registerProjectsWrite(server);
|
|
44
|
+
async function main() {
|
|
45
|
+
const transport = new StdioServerTransport();
|
|
46
|
+
await server.connect(transport);
|
|
47
|
+
console.error("github-pro-mcp running on stdio");
|
|
48
|
+
}
|
|
49
|
+
main().catch((err) => {
|
|
50
|
+
console.error("Fatal error:", err);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
});
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export declare function registerOwnerListIssueField(server: McpServer): void;
|
|
3
|
+
export declare function registerIssueGetFieldValue(server: McpServer): void;
|
|
4
|
+
export declare function registerIssueSetFieldValue(server: McpServer): void;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import * as z from "zod/v4";
|
|
2
|
+
import { createClient } from "../client.js";
|
|
3
|
+
import { handleGitHubError } from "../utils/errors.js";
|
|
4
|
+
import { requireOrg, validatePinnedOwnerRepo } from "../utils/validation.js";
|
|
5
|
+
import { OwnerRepoSchema, IssueNumberSchema } from "../utils/types.js";
|
|
6
|
+
export function registerOwnerListIssueField(server) {
|
|
7
|
+
server.registerTool("owner_list_issue_field", {
|
|
8
|
+
description: "List all issue field definitions for an organization (e.g., Priority, Effort, Start date, Target date). Owner must be an organization.",
|
|
9
|
+
inputSchema: z.object({
|
|
10
|
+
owner: z.string().min(1).describe("Organization owner"),
|
|
11
|
+
}),
|
|
12
|
+
}, async ({ owner }) => {
|
|
13
|
+
try {
|
|
14
|
+
const pinErr = validatePinnedOwnerRepo(owner);
|
|
15
|
+
if (pinErr)
|
|
16
|
+
return pinErr;
|
|
17
|
+
const octokit = createClient();
|
|
18
|
+
const orgErr = await requireOrg(owner);
|
|
19
|
+
if (orgErr)
|
|
20
|
+
return orgErr;
|
|
21
|
+
const { data } = await octokit.request('GET /orgs/{owner}/issue-fields', { owner });
|
|
22
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
return handleGitHubError(err);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
export function registerIssueGetFieldValue(server) {
|
|
30
|
+
server.registerTool("issue_get_field_value", {
|
|
31
|
+
description: "Get the current field values (Priority, Effort, dates, etc.) assigned to an issue.",
|
|
32
|
+
inputSchema: OwnerRepoSchema.extend({
|
|
33
|
+
issue_number: IssueNumberSchema,
|
|
34
|
+
}),
|
|
35
|
+
}, async ({ owner, repo, issue_number }) => {
|
|
36
|
+
try {
|
|
37
|
+
const pinErr = validatePinnedOwnerRepo(owner, repo);
|
|
38
|
+
if (pinErr)
|
|
39
|
+
return pinErr;
|
|
40
|
+
const octokit = createClient();
|
|
41
|
+
const { data } = await octokit.rest.issues.get({ owner, repo, issue_number });
|
|
42
|
+
const issueData = data;
|
|
43
|
+
return { content: [{ type: "text", text: JSON.stringify(issueData.issue_field_values ?? [], null, 2) }] };
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
return handleGitHubError(err);
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
export function registerIssueSetFieldValue(server) {
|
|
51
|
+
server.registerTool("issue_set_field_value", {
|
|
52
|
+
description: "Set field values (Priority, Effort, dates, etc.) on an issue. Accepts either field_id (numeric ID) or field_name (e.g. 'Priority'). For single-select fields, value can be the option name (e.g. 'Medium') or option ID. For dates, use ISO 8601 (e.g. '2026-06-15'). Pass null to clear a field.",
|
|
53
|
+
inputSchema: OwnerRepoSchema.extend({
|
|
54
|
+
issue_number: IssueNumberSchema,
|
|
55
|
+
fields: z.array(z.object({
|
|
56
|
+
field_id: z.number().int().positive().optional().describe("Field ID from owner_list_issue_field"),
|
|
57
|
+
field_name: z.string().optional().describe("Field name (e.g. 'Priority') as alternative to field_id"),
|
|
58
|
+
value: z.union([z.string(), z.number(), z.null()]).describe("Field value: option name/ID for single-select, ISO date for dates, number for numeric fields, or null to clear"),
|
|
59
|
+
})).min(1).describe("Array of field updates"),
|
|
60
|
+
}),
|
|
61
|
+
}, async ({ owner, repo, issue_number, fields }) => {
|
|
62
|
+
try {
|
|
63
|
+
const pinErr = validatePinnedOwnerRepo(owner, repo);
|
|
64
|
+
if (pinErr)
|
|
65
|
+
return pinErr;
|
|
66
|
+
const octokit = createClient();
|
|
67
|
+
const resolvedFields = [];
|
|
68
|
+
for (const f of fields) {
|
|
69
|
+
let fieldId = f.field_id;
|
|
70
|
+
if (!fieldId && f.field_name) {
|
|
71
|
+
const fieldName = f.field_name;
|
|
72
|
+
const { data: fieldDefs } = await octokit.request('GET /orgs/{owner}/issue-fields', { owner });
|
|
73
|
+
const matching = fieldDefs.find((fd) => fd.name.toLowerCase() === fieldName.toLowerCase());
|
|
74
|
+
if (!matching) {
|
|
75
|
+
return {
|
|
76
|
+
content: [{ type: "text", text: `Field "${f.field_name}" not found. Available fields: ${fieldDefs.map((fd) => fd.name).join(", ")}` }],
|
|
77
|
+
isError: true,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
fieldId = matching.id;
|
|
81
|
+
}
|
|
82
|
+
if (!fieldId) {
|
|
83
|
+
return {
|
|
84
|
+
content: [{ type: "text", text: "Either field_id or field_name is required for each field update." }],
|
|
85
|
+
isError: true,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
resolvedFields.push({ field_id: fieldId, value: f.value });
|
|
89
|
+
}
|
|
90
|
+
const response = await octokit.request('PATCH /repos/{owner}/{repo}/issues/{issue_number}', {
|
|
91
|
+
owner, repo, issue_number,
|
|
92
|
+
issue_field_values: resolvedFields,
|
|
93
|
+
});
|
|
94
|
+
const data = response.data;
|
|
95
|
+
return { content: [{ type: "text", text: JSON.stringify({
|
|
96
|
+
success: true,
|
|
97
|
+
issue_field_values: data.issue_field_values ?? [],
|
|
98
|
+
}, null, 2) }] };
|
|
99
|
+
}
|
|
100
|
+
catch (err) {
|
|
101
|
+
return handleGitHubError(err);
|
|
102
|
+
}
|
|
103
|
+
});
|
|
104
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
export declare function registerIssueRead(server: McpServer): void;
|
|
3
|
+
export declare function registerIssueList(server: McpServer): void;
|
|
4
|
+
export declare function registerIssueSearch(server: McpServer): void;
|
|
5
|
+
export declare function registerIssueAddComment(server: McpServer): void;
|
|
6
|
+
export declare function registerSubIssueWrite(server: McpServer): void;
|
|
7
|
+
export declare function registerIssueCreate(server: McpServer): void;
|
|
8
|
+
export declare function registerIssueClose(server: McpServer): void;
|
|
9
|
+
export declare function registerIssueReopen(server: McpServer): void;
|
|
10
|
+
export declare function registerIssueUpdate(server: McpServer): void;
|
|
11
|
+
export declare function registerIssueAddLabel(server: McpServer): void;
|
|
12
|
+
export declare function registerIssueRemoveLabel(server: McpServer): void;
|