Merge pull request #119 from sirkitree/feat__github-issue-tools

feat: add issue management functionalities for github
This commit is contained in:
Justin Spahr-Summers
2024-12-05 23:07:42 +00:00
committed by GitHub
3 changed files with 239 additions and 40 deletions

View File

@@ -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)
- 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
- Inputs:
- `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
- Returns: Code search results with repository context
11. `search_issues`
14. `search_issues`
- Search for issues and pull requests
- Inputs:
- `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
- Returns: Issue and pull request search results
12. `search_users`
15. `search_users`
- Search for GitHub users
- Inputs:
- `q` (string): Search query using GitHub users search syntax

View File

@@ -1,5 +1,4 @@
#!/usr/bin/env node
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import {
@@ -7,54 +6,56 @@ import {
ListToolsRequestSchema,
} from "@modelcontextprotocol/sdk/types.js";
import fetch from "node-fetch";
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
import {
GitHubForkSchema,
GitHubReferenceSchema,
GitHubRepositorySchema,
GitHubIssueSchema,
GitHubPullRequestSchema,
CreateBranchOptionsSchema,
CreateBranchSchema,
CreateIssueOptionsSchema,
CreateIssueSchema,
CreateOrUpdateFileSchema,
CreatePullRequestOptionsSchema,
CreatePullRequestSchema,
CreateRepositoryOptionsSchema,
CreateRepositorySchema,
ForkRepositorySchema,
GetFileContentsSchema,
GitHubCommitSchema,
GitHubContentSchema,
GitHubCreateUpdateFileResponseSchema,
GitHubForkSchema,
GitHubIssueSchema,
GitHubPullRequestSchema,
GitHubReferenceSchema,
GitHubRepositorySchema,
GitHubSearchResponseSchema,
GitHubTreeSchema,
GitHubCommitSchema,
CreateRepositoryOptionsSchema,
CreateIssueOptionsSchema,
CreatePullRequestOptionsSchema,
CreateBranchOptionsSchema,
type GitHubFork,
type GitHubReference,
type GitHubRepository,
type GitHubIssue,
type GitHubPullRequest,
IssueCommentSchema,
ListIssuesOptionsSchema,
PushFilesSchema,
SearchCodeResponseSchema,
SearchCodeSchema,
SearchIssuesResponseSchema,
SearchIssuesSchema,
SearchRepositoriesSchema,
SearchUsersResponseSchema,
SearchUsersSchema,
UpdateIssueOptionsSchema,
type FileOperation,
type GitHubCommit,
type GitHubContent,
type GitHubCreateUpdateFileResponse,
type GitHubFork,
type GitHubIssue,
type GitHubPullRequest,
type GitHubReference,
type GitHubRepository,
type GitHubSearchResponse,
type GitHubTree,
type GitHubCommit,
type FileOperation,
CreateOrUpdateFileSchema,
SearchRepositoriesSchema,
CreateRepositorySchema,
GetFileContentsSchema,
PushFilesSchema,
CreateIssueSchema,
CreatePullRequestSchema,
ForkRepositorySchema,
CreateBranchSchema,
SearchCodeSchema,
SearchIssuesSchema,
SearchUsersSchema,
SearchCodeResponseSchema,
SearchIssuesResponseSchema,
SearchUsersResponseSchema,
type SearchCodeResponse,
type SearchIssuesResponse,
type SearchUsersResponse,
} from "./schemas.js";
import { zodToJsonSchema } from "zod-to-json-schema";
import { z } from "zod";
import type { CallToolRequest } from "@modelcontextprotocol/sdk/types.js";
} from './schemas.js';
const server = new Server(
{
@@ -486,6 +487,98 @@ async function createRepository(
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(
params: z.infer<typeof SearchCodeSchema>
): Promise<SearchCodeResponse> {
@@ -612,6 +705,21 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
description: "Create a new branch in a GitHub repository",
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",
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:
throw new Error(`Unknown tool: ${request.params.name}`);
}

View File

@@ -616,6 +616,39 @@ export const SearchUsersSchema = z.object({
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 type GitHubAuthor = z.infer<typeof GitHubAuthorSchema>;
export type GitHubFork = z.infer<typeof GitHubForkSchema>;