Merge pull request #119 from sirkitree/feat__github-issue-tools
feat: add issue management functionalities for github
This commit is contained in:
commit
370198df1b
3 changed files with 237 additions and 38 deletions
|
|
@ -103,7 +103,44 @@ MCP Server for the GitHub API, enabling file operations, repository management,
|
||||||
- `from_branch` (optional string): Source branch (defaults to repo default)
|
- `from_branch` (optional string): Source branch (defaults to repo default)
|
||||||
- Returns: Created branch reference
|
- Returns: Created branch reference
|
||||||
|
|
||||||
10. `search_code`
|
10. `list_issues`
|
||||||
|
- List and filter repository issues
|
||||||
|
- Inputs:
|
||||||
|
- `owner` (string): Repository owner
|
||||||
|
- `repo` (string): Repository name
|
||||||
|
- `state` (optional string): Filter by state ('open', 'closed', 'all')
|
||||||
|
- `labels` (optional string[]): Filter by labels
|
||||||
|
- `sort` (optional string): Sort by ('created', 'updated', 'comments')
|
||||||
|
- `direction` (optional string): Sort direction ('asc', 'desc')
|
||||||
|
- `since` (optional string): Filter by date (ISO 8601 timestamp)
|
||||||
|
- `page` (optional number): Page number
|
||||||
|
- `per_page` (optional number): Results per page
|
||||||
|
- Returns: Array of issue details
|
||||||
|
|
||||||
|
11. `update_issue`
|
||||||
|
- Update an existing issue
|
||||||
|
- Inputs:
|
||||||
|
- `owner` (string): Repository owner
|
||||||
|
- `repo` (string): Repository name
|
||||||
|
- `issue_number` (number): Issue number to update
|
||||||
|
- `title` (optional string): New title
|
||||||
|
- `body` (optional string): New description
|
||||||
|
- `state` (optional string): New state ('open' or 'closed')
|
||||||
|
- `labels` (optional string[]): New labels
|
||||||
|
- `assignees` (optional string[]): New assignees
|
||||||
|
- `milestone` (optional number): New milestone number
|
||||||
|
- Returns: Updated issue details
|
||||||
|
|
||||||
|
12. `add_issue_comment`
|
||||||
|
- Add a comment to an issue
|
||||||
|
- Inputs:
|
||||||
|
- `owner` (string): Repository owner
|
||||||
|
- `repo` (string): Repository name
|
||||||
|
- `issue_number` (number): Issue number to comment on
|
||||||
|
- `body` (string): Comment text
|
||||||
|
- Returns: Created comment details
|
||||||
|
|
||||||
|
13. `search_code`
|
||||||
- Search for code across GitHub repositories
|
- Search for code across GitHub repositories
|
||||||
- Inputs:
|
- Inputs:
|
||||||
- `q` (string): Search query using GitHub code search syntax
|
- `q` (string): Search query using GitHub code search syntax
|
||||||
|
|
@ -113,7 +150,7 @@ MCP Server for the GitHub API, enabling file operations, repository management,
|
||||||
- `page` (optional number): Page number
|
- `page` (optional number): Page number
|
||||||
- Returns: Code search results with repository context
|
- Returns: Code search results with repository context
|
||||||
|
|
||||||
11. `search_issues`
|
14. `search_issues`
|
||||||
- Search for issues and pull requests
|
- Search for issues and pull requests
|
||||||
- Inputs:
|
- Inputs:
|
||||||
- `q` (string): Search query using GitHub issues search syntax
|
- `q` (string): Search query using GitHub issues search syntax
|
||||||
|
|
@ -123,7 +160,7 @@ MCP Server for the GitHub API, enabling file operations, repository management,
|
||||||
- `page` (optional number): Page number
|
- `page` (optional number): Page number
|
||||||
- Returns: Issue and pull request search results
|
- Returns: Issue and pull request search results
|
||||||
|
|
||||||
12. `search_users`
|
15. `search_users`
|
||||||
- Search for GitHub users
|
- Search for GitHub users
|
||||||
- Inputs:
|
- Inputs:
|
||||||
- `q` (string): Search query using GitHub users search syntax
|
- `q` (string): Search query using GitHub users search syntax
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
||||||
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||||
import {
|
import {
|
||||||
|
|
@ -7,54 +6,56 @@ import {
|
||||||
ListToolsRequestSchema,
|
ListToolsRequestSchema,
|
||||||
} from "@modelcontextprotocol/sdk/types.js";
|
} from "@modelcontextprotocol/sdk/types.js";
|
||||||
import fetch from "node-fetch";
|
import fetch from "node-fetch";
|
||||||
|
import { z } from 'zod';
|
||||||
|
import { zodToJsonSchema } from 'zod-to-json-schema';
|
||||||
import {
|
import {
|
||||||
GitHubForkSchema,
|
CreateBranchOptionsSchema,
|
||||||
GitHubReferenceSchema,
|
CreateBranchSchema,
|
||||||
GitHubRepositorySchema,
|
CreateIssueOptionsSchema,
|
||||||
GitHubIssueSchema,
|
CreateIssueSchema,
|
||||||
GitHubPullRequestSchema,
|
CreateOrUpdateFileSchema,
|
||||||
|
CreatePullRequestOptionsSchema,
|
||||||
|
CreatePullRequestSchema,
|
||||||
|
CreateRepositoryOptionsSchema,
|
||||||
|
CreateRepositorySchema,
|
||||||
|
ForkRepositorySchema,
|
||||||
|
GetFileContentsSchema,
|
||||||
|
GitHubCommitSchema,
|
||||||
GitHubContentSchema,
|
GitHubContentSchema,
|
||||||
GitHubCreateUpdateFileResponseSchema,
|
GitHubCreateUpdateFileResponseSchema,
|
||||||
|
GitHubForkSchema,
|
||||||
|
GitHubIssueSchema,
|
||||||
|
GitHubPullRequestSchema,
|
||||||
|
GitHubReferenceSchema,
|
||||||
|
GitHubRepositorySchema,
|
||||||
GitHubSearchResponseSchema,
|
GitHubSearchResponseSchema,
|
||||||
GitHubTreeSchema,
|
GitHubTreeSchema,
|
||||||
GitHubCommitSchema,
|
IssueCommentSchema,
|
||||||
CreateRepositoryOptionsSchema,
|
ListIssuesOptionsSchema,
|
||||||
CreateIssueOptionsSchema,
|
PushFilesSchema,
|
||||||
CreatePullRequestOptionsSchema,
|
SearchCodeResponseSchema,
|
||||||
CreateBranchOptionsSchema,
|
SearchCodeSchema,
|
||||||
type GitHubFork,
|
SearchIssuesResponseSchema,
|
||||||
type GitHubReference,
|
SearchIssuesSchema,
|
||||||
type GitHubRepository,
|
SearchRepositoriesSchema,
|
||||||
type GitHubIssue,
|
SearchUsersResponseSchema,
|
||||||
type GitHubPullRequest,
|
SearchUsersSchema,
|
||||||
|
UpdateIssueOptionsSchema,
|
||||||
|
type FileOperation,
|
||||||
|
type GitHubCommit,
|
||||||
type GitHubContent,
|
type GitHubContent,
|
||||||
type GitHubCreateUpdateFileResponse,
|
type GitHubCreateUpdateFileResponse,
|
||||||
|
type GitHubFork,
|
||||||
|
type GitHubIssue,
|
||||||
|
type GitHubPullRequest,
|
||||||
|
type GitHubReference,
|
||||||
|
type GitHubRepository,
|
||||||
type GitHubSearchResponse,
|
type GitHubSearchResponse,
|
||||||
type GitHubTree,
|
type GitHubTree,
|
||||||
type GitHubCommit,
|
|
||||||
type FileOperation,
|
|
||||||
CreateOrUpdateFileSchema,
|
|
||||||
SearchRepositoriesSchema,
|
|
||||||
CreateRepositorySchema,
|
|
||||||
GetFileContentsSchema,
|
|
||||||
PushFilesSchema,
|
|
||||||
CreateIssueSchema,
|
|
||||||
CreatePullRequestSchema,
|
|
||||||
ForkRepositorySchema,
|
|
||||||
CreateBranchSchema,
|
|
||||||
SearchCodeSchema,
|
|
||||||
SearchIssuesSchema,
|
|
||||||
SearchUsersSchema,
|
|
||||||
SearchCodeResponseSchema,
|
|
||||||
SearchIssuesResponseSchema,
|
|
||||||
SearchUsersResponseSchema,
|
|
||||||
type SearchCodeResponse,
|
type SearchCodeResponse,
|
||||||
type SearchIssuesResponse,
|
type SearchIssuesResponse,
|
||||||
type SearchUsersResponse,
|
type SearchUsersResponse,
|
||||||
} from "./schemas.js";
|
} from './schemas.js';
|
||||||
import { zodToJsonSchema } from "zod-to-json-schema";
|
|
||||||
import { z } from "zod";
|
|
||||||
import type { CallToolRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
||||||
|
|
||||||
const server = new Server(
|
const server = new Server(
|
||||||
{
|
{
|
||||||
|
|
@ -486,6 +487,98 @@ async function createRepository(
|
||||||
return GitHubRepositorySchema.parse(await response.json());
|
return GitHubRepositorySchema.parse(await response.json());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function listIssues(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
options: Omit<z.infer<typeof ListIssuesOptionsSchema>, 'owner' | 'repo'>
|
||||||
|
): Promise<GitHubIssue[]> {
|
||||||
|
const url = new URL(`https://api.github.com/repos/${owner}/${repo}/issues`);
|
||||||
|
|
||||||
|
// Add query parameters
|
||||||
|
if (options.state) url.searchParams.append('state', options.state);
|
||||||
|
if (options.labels) url.searchParams.append('labels', options.labels.join(','));
|
||||||
|
if (options.sort) url.searchParams.append('sort', options.sort);
|
||||||
|
if (options.direction) url.searchParams.append('direction', options.direction);
|
||||||
|
if (options.since) url.searchParams.append('since', options.since);
|
||||||
|
if (options.page) url.searchParams.append('page', options.page.toString());
|
||||||
|
if (options.per_page) url.searchParams.append('per_page', options.per_page.toString());
|
||||||
|
|
||||||
|
const response = await fetch(url.toString(), {
|
||||||
|
headers: {
|
||||||
|
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
|
"Accept": "application/vnd.github.v3+json",
|
||||||
|
"User-Agent": "github-mcp-server"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`GitHub API error: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return z.array(GitHubIssueSchema).parse(await response.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateIssue(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
issueNumber: number,
|
||||||
|
options: Omit<z.infer<typeof UpdateIssueOptionsSchema>, 'owner' | 'repo' | 'issue_number'>
|
||||||
|
): Promise<GitHubIssue> {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}`,
|
||||||
|
{
|
||||||
|
method: "PATCH",
|
||||||
|
headers: {
|
||||||
|
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
|
"Accept": "application/vnd.github.v3+json",
|
||||||
|
"User-Agent": "github-mcp-server",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
title: options.title,
|
||||||
|
body: options.body,
|
||||||
|
state: options.state,
|
||||||
|
labels: options.labels,
|
||||||
|
assignees: options.assignees,
|
||||||
|
milestone: options.milestone
|
||||||
|
})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`GitHub API error: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return GitHubIssueSchema.parse(await response.json());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addIssueComment(
|
||||||
|
owner: string,
|
||||||
|
repo: string,
|
||||||
|
issueNumber: number,
|
||||||
|
body: string
|
||||||
|
): Promise<z.infer<typeof IssueCommentSchema>> {
|
||||||
|
const response = await fetch(
|
||||||
|
`https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}/comments`,
|
||||||
|
{
|
||||||
|
method: "POST",
|
||||||
|
headers: {
|
||||||
|
"Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
|
||||||
|
"Accept": "application/vnd.github.v3+json",
|
||||||
|
"User-Agent": "github-mcp-server",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
},
|
||||||
|
body: JSON.stringify({ body })
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`GitHub API error: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return IssueCommentSchema.parse(await response.json());
|
||||||
|
}
|
||||||
|
|
||||||
async function searchCode(
|
async function searchCode(
|
||||||
params: z.infer<typeof SearchCodeSchema>
|
params: z.infer<typeof SearchCodeSchema>
|
||||||
): Promise<SearchCodeResponse> {
|
): Promise<SearchCodeResponse> {
|
||||||
|
|
@ -612,6 +705,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||||
description: "Create a new branch in a GitHub repository",
|
description: "Create a new branch in a GitHub repository",
|
||||||
inputSchema: zodToJsonSchema(CreateBranchSchema),
|
inputSchema: zodToJsonSchema(CreateBranchSchema),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "list_issues",
|
||||||
|
description: "List issues in a GitHub repository with filtering options",
|
||||||
|
inputSchema: zodToJsonSchema(ListIssuesOptionsSchema)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "update_issue",
|
||||||
|
description: "Update an existing issue in a GitHub repository",
|
||||||
|
inputSchema: zodToJsonSchema(UpdateIssueOptionsSchema)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "add_issue_comment",
|
||||||
|
description: "Add a comment to an existing issue",
|
||||||
|
inputSchema: zodToJsonSchema(IssueCommentSchema)
|
||||||
|
},
|
||||||
{
|
{
|
||||||
name: "search_code",
|
name: "search_code",
|
||||||
description: "Search for code across GitHub repositories",
|
description: "Search for code across GitHub repositories",
|
||||||
|
|
@ -795,6 +903,27 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case "list_issues": {
|
||||||
|
const args = ListIssuesOptionsSchema.parse(request.params.arguments);
|
||||||
|
const { owner, repo, ...options } = args;
|
||||||
|
const issues = await listIssues(owner, repo, options);
|
||||||
|
return { toolResult: issues };
|
||||||
|
}
|
||||||
|
|
||||||
|
case "update_issue": {
|
||||||
|
const args = UpdateIssueOptionsSchema.parse(request.params.arguments);
|
||||||
|
const { owner, repo, issue_number, ...options } = args;
|
||||||
|
const issue = await updateIssue(owner, repo, issue_number, options);
|
||||||
|
return { toolResult: issue };
|
||||||
|
}
|
||||||
|
|
||||||
|
case "add_issue_comment": {
|
||||||
|
const args = IssueCommentSchema.parse(request.params.arguments);
|
||||||
|
const { owner, repo, issue_number, body } = args;
|
||||||
|
const comment = await addIssueComment(owner, repo, issue_number, body);
|
||||||
|
return { toolResult: comment };
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
throw new Error(`Unknown tool: ${request.params.name}`);
|
throw new Error(`Unknown tool: ${request.params.name}`);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -616,6 +616,39 @@ export const SearchUsersSchema = z.object({
|
||||||
page: z.number().min(1).optional().describe("Page number"),
|
page: z.number().min(1).optional().describe("Page number"),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add these schema definitions for issue management
|
||||||
|
|
||||||
|
export const ListIssuesOptionsSchema = z.object({
|
||||||
|
owner: z.string(),
|
||||||
|
repo: z.string(),
|
||||||
|
state: z.enum(['open', 'closed', 'all']).optional(),
|
||||||
|
labels: z.array(z.string()).optional(),
|
||||||
|
sort: z.enum(['created', 'updated', 'comments']).optional(),
|
||||||
|
direction: z.enum(['asc', 'desc']).optional(),
|
||||||
|
since: z.string().optional(), // ISO 8601 timestamp
|
||||||
|
page: z.number().optional(),
|
||||||
|
per_page: z.number().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const UpdateIssueOptionsSchema = z.object({
|
||||||
|
owner: z.string(),
|
||||||
|
repo: z.string(),
|
||||||
|
issue_number: z.number(),
|
||||||
|
title: z.string().optional(),
|
||||||
|
body: z.string().optional(),
|
||||||
|
state: z.enum(['open', 'closed']).optional(),
|
||||||
|
labels: z.array(z.string()).optional(),
|
||||||
|
assignees: z.array(z.string()).optional(),
|
||||||
|
milestone: z.number().optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
export const IssueCommentSchema = z.object({
|
||||||
|
owner: z.string(),
|
||||||
|
repo: z.string(),
|
||||||
|
issue_number: z.number(),
|
||||||
|
body: z.string()
|
||||||
|
});
|
||||||
|
|
||||||
// Export types
|
// Export types
|
||||||
export type GitHubAuthor = z.infer<typeof GitHubAuthorSchema>;
|
export type GitHubAuthor = z.infer<typeof GitHubAuthorSchema>;
|
||||||
export type GitHubFork = z.infer<typeof GitHubForkSchema>;
|
export type GitHubFork = z.infer<typeof GitHubForkSchema>;
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue