diff --git a/src/github/README.md b/src/github/README.md index 9d98b1b..cc0a09c 100644 --- a/src/github/README.md +++ b/src/github/README.md @@ -170,6 +170,16 @@ MCP Server for the GitHub API, enabling file operations, repository management, - `page` (optional number): Page number - Returns: User search results +16. `list_commits` + - Gets commits of a branch in a repository + - Inputs: + - `owner` (string): Repository owner + - `repo` (string): Repository name + - `page` (optional string): page number + - `per_page` (optional string): number of record per page + - `sha` (optional string): branch name + - Returns: List of commits + ## Search Query Syntax ### Code Search diff --git a/src/github/index.ts b/src/github/index.ts index a8341a9..287cfdc 100644 --- a/src/github/index.ts +++ b/src/github/index.ts @@ -25,12 +25,15 @@ import { GitHubCreateUpdateFileResponseSchema, GitHubForkSchema, GitHubIssueSchema, + GitHubListCommits, + GitHubListCommitsSchema, GitHubPullRequestSchema, GitHubReferenceSchema, GitHubRepositorySchema, GitHubSearchResponseSchema, GitHubTreeSchema, IssueCommentSchema, + ListCommitsSchema, ListIssuesOptionsSchema, PushFilesSchema, SearchCodeResponseSchema, @@ -54,7 +57,7 @@ import { type GitHubTree, type SearchCodeResponse, type SearchIssuesResponse, - type SearchUsersResponse, + type SearchUsersResponse } from './schemas.js'; const server = new Server( @@ -487,6 +490,40 @@ async function createRepository( return GitHubRepositorySchema.parse(await response.json()); } +async function listCommits( + owner: string, + repo: string, + page: number = 1, + perPage: number = 30, + sha?: string, +): Promise { + const url = new URL(`https://api.github.com/repos/${owner}/${repo}/commits`); + url.searchParams.append("page", page.toString()); + url.searchParams.append("per_page", perPage.toString()); + if (sha) { + url.searchParams.append("sha", sha); + } + + const response = await fetch( + url.toString(), + { + method: "GET", + headers: { + "Authorization": `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`, + "Accept": "application/vnd.github.v3+json", + "User-Agent": "github-mcp-server", + "Content-Type": "application/json" + }, + } + ); + + if (!response.ok) { + throw new Error(`GitHub API error: ${response.statusText}`); + } + + return GitHubListCommitsSchema.parse(await response.json()); +} + async function listIssues( owner: string, repo: string, @@ -705,6 +742,11 @@ server.setRequestHandler(ListToolsRequestSchema, async () => { description: "Create a new branch in a GitHub repository", inputSchema: zodToJsonSchema(CreateBranchSchema), }, + { + name: "list_commits", + description: "Get list of commits of a branch in a GitHub repository", + inputSchema: zodToJsonSchema(ListCommitsSchema) + }, { name: "list_issues", description: "List issues in a GitHub repository with filtering options", @@ -924,6 +966,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => { return { toolResult: comment }; } + case "list_commits": { + const args = ListCommitsSchema.parse(request.params.arguments); + const results = await listCommits(args.owner, args.repo, args.page, args.perPage, args.sha); + return { content: [{ type: "text", text: JSON.stringify(results, null, 2) }] }; + } + default: throw new Error(`Unknown tool: ${request.params.name}`); } diff --git a/src/github/schemas.ts b/src/github/schemas.ts index f40da15..69ad944 100644 --- a/src/github/schemas.ts +++ b/src/github/schemas.ts @@ -93,6 +93,25 @@ export const GitHubTreeSchema = z.object({ truncated: z.boolean(), }); +export const GitHubListCommitsSchema = z.array(z.object({ + sha: z.string(), + node_id: z.string(), + commit: z.object({ + author: GitHubAuthorSchema, + committer: GitHubAuthorSchema, + message: z.string(), + tree: z.object({ + sha: z.string(), + url: z.string() + }), + url: z.string(), + comment_count: z.number(), + }), + url: z.string(), + html_url: z.string(), + comments_url: z.string() +})); + export const GitHubCommitSchema = z.object({ sha: z.string(), node_id: z.string(), @@ -322,6 +341,15 @@ export const SearchRepositoriesSchema = z.object({ .describe("Number of results per page (default: 30, max: 100)"), }); +export const ListCommitsSchema = z.object({ + owner: z.string().describe("Repository owner (username or organization)"), + repo: z.string().describe("Repository name"), + page: z.number().optional().describe("Page number for pagination (default: 1)"), + perPage: z.number().optional().describe("Number of results per page (default: 30, max: 100)"), + sha: z.string().optional() + .describe("SHA of the file being replaced (required when updating existing files)") +}); + export const CreateRepositorySchema = z.object({ name: z.string().describe("Repository name"), description: z.string().optional().describe("Repository description"), @@ -663,6 +691,7 @@ export type GitHubContent = z.infer; export type FileOperation = z.infer; export type GitHubTree = z.infer; export type GitHubCommit = z.infer; +export type GitHubListCommits = z.infer; export type GitHubReference = z.infer; export type CreateRepositoryOptions = z.infer< typeof CreateRepositoryOptionsSchema