GitHub MCP Server

import { z } from 'zod'; // Base schemas for common types export const GitHubAuthorSchema = z.object({ name: z.string(), email: z.string(), date: z.string(), }); // Repository related schemas export const GitHubOwnerSchema = z.object({ login: z.string(), id: z.number(), node_id: z.string(), avatar_url: z.string(), url: z.string(), html_url: z.string(), type: z.string(), }); export const GitHubRepositorySchema = z.object({ id: z.number(), node_id: z.string(), name: z.string(), full_name: z.string(), private: z.boolean(), owner: GitHubOwnerSchema, html_url: z.string(), description: z.string().nullable(), fork: z.boolean(), url: z.string(), created_at: z.string(), updated_at: z.string(), pushed_at: z.string(), git_url: z.string(), ssh_url: z.string(), clone_url: z.string(), default_branch: z.string(), }); // File content schemas export const GitHubFileContentSchema = z.object({ type: z.string(), encoding: z.string(), size: z.number(), name: z.string(), path: z.string(), content: z.string(), sha: z.string(), url: z.string(), git_url: z.string(), html_url: z.string(), download_url: z.string(), }); export const GitHubDirectoryContentSchema = z.object({ type: z.string(), size: z.number(), name: z.string(), path: z.string(), sha: z.string(), url: z.string(), git_url: z.string(), html_url: z.string(), download_url: z.string().nullable(), }); export const GitHubContentSchema = z.union([ GitHubFileContentSchema, z.array(GitHubDirectoryContentSchema), ]); // Operation schemas export const FileOperationSchema = z.object({ path: z.string(), content: z.string(), }); // Tree and commit schemas export const GitHubTreeEntrySchema = z.object({ path: z.string(), mode: z.enum(['100644', '100755', '040000', '160000', '120000']), type: z.enum(['blob', 'tree', 'commit']), size: z.number().optional(), sha: z.string(), url: z.string(), }); export const GitHubTreeSchema = z.object({ sha: z.string(), url: z.string(), tree: z.array(GitHubTreeEntrySchema), 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(), url: z.string(), author: GitHubAuthorSchema, committer: GitHubAuthorSchema, message: z.string(), tree: z.object({ sha: z.string(), url: z.string(), }), parents: z.array( z.object({ sha: z.string(), url: z.string(), }) ), }); // Reference schema export const GitHubReferenceSchema = z.object({ ref: z.string(), node_id: z.string(), url: z.string(), object: z.object({ sha: z.string(), type: z.string(), url: z.string(), }), }); // Input schemas for operations export const CreateRepositoryOptionsSchema = z.object({ name: z.string(), description: z.string().optional(), private: z.boolean().optional(), auto_init: z.boolean().optional(), }); export const CreateIssueOptionsSchema = z.object({ title: z.string(), body: z.string().optional(), assignees: z.array(z.string()).optional(), milestone: z.number().optional(), labels: z.array(z.string()).optional(), }); export const CreatePullRequestOptionsSchema = z.object({ title: z.string(), body: z.string().optional(), head: z.string(), base: z.string(), maintainer_can_modify: z.boolean().optional(), draft: z.boolean().optional(), }); export const CreateBranchOptionsSchema = z.object({ ref: z.string(), sha: z.string(), }); // Response schemas for operations export const GitHubCreateUpdateFileResponseSchema = z.object({ content: GitHubFileContentSchema.nullable(), commit: z.object({ sha: z.string(), node_id: z.string(), url: z.string(), html_url: z.string(), author: GitHubAuthorSchema, committer: GitHubAuthorSchema, message: z.string(), tree: z.object({ sha: z.string(), url: z.string(), }), parents: z.array( z.object({ sha: z.string(), url: z.string(), html_url: z.string(), }) ), }), }); export const GitHubSearchResponseSchema = z.object({ total_count: z.number(), incomplete_results: z.boolean(), items: z.array(GitHubRepositorySchema), }); // Fork related schemas export const GitHubForkParentSchema = z.object({ name: z.string(), full_name: z.string(), owner: z.object({ login: z.string(), id: z.number(), avatar_url: z.string(), }), html_url: z.string(), }); export const GitHubForkSchema = GitHubRepositorySchema.extend({ parent: GitHubForkParentSchema, source: GitHubForkParentSchema, }); // Issue related schemas export const GitHubLabelSchema = z.object({ id: z.number(), node_id: z.string(), url: z.string(), name: z.string(), color: z.string(), default: z.boolean(), description: z.string().optional(), }); export const GitHubIssueAssigneeSchema = z.object({ login: z.string(), id: z.number(), avatar_url: z.string(), url: z.string(), html_url: z.string(), }); export const GitHubMilestoneSchema = z.object({ url: z.string(), html_url: z.string(), labels_url: z.string(), id: z.number(), node_id: z.string(), number: z.number(), title: z.string(), description: z.string(), state: z.string(), }); export const GitHubIssueSchema = z.object({ url: z.string(), repository_url: z.string(), labels_url: z.string(), comments_url: z.string(), events_url: z.string(), html_url: z.string(), id: z.number(), node_id: z.string(), number: z.number(), title: z.string(), user: GitHubIssueAssigneeSchema, labels: z.array(GitHubLabelSchema), state: z.string(), locked: z.boolean(), assignee: GitHubIssueAssigneeSchema.nullable(), assignees: z.array(GitHubIssueAssigneeSchema), milestone: GitHubMilestoneSchema.nullable(), comments: z.number(), created_at: z.string(), updated_at: z.string(), closed_at: z.string().nullable(), body: z.string().nullable(), }); // Pull Request related schemas export const GitHubPullRequestHeadSchema = z.object({ label: z.string(), ref: z.string(), sha: z.string(), user: GitHubIssueAssigneeSchema, repo: GitHubRepositorySchema, }); export const GitHubPullRequestSchema = z.object({ url: z.string(), id: z.number(), node_id: z.string(), html_url: z.string(), diff_url: z.string(), patch_url: z.string(), issue_url: z.string(), number: z.number(), state: z.string(), locked: z.boolean(), title: z.string(), user: GitHubIssueAssigneeSchema, body: z.string(), created_at: z.string(), updated_at: z.string(), closed_at: z.string().nullable(), merged_at: z.string().nullable(), merge_commit_sha: z.string().nullable(), assignee: GitHubIssueAssigneeSchema.nullable(), assignees: z.array(GitHubIssueAssigneeSchema), head: GitHubPullRequestHeadSchema, base: GitHubPullRequestHeadSchema, }); const RepoParamsSchema = z.object({ owner: z.string().describe('Repository owner (username or organization)'), repo: z.string().describe('Repository name'), }); export const CreateOrUpdateFileSchema = RepoParamsSchema.extend({ path: z.string().describe('Path where to create/update the file'), content: z.string().describe('Content of the file'), message: z.string().describe('Commit message'), branch: z.string().describe('Branch to create/update the file in'), sha: z .string() .optional() .describe( 'SHA of the file being replaced (required when updating existing files)' ), }); export const SearchRepositoriesSchema = z.object({ query: z.string().describe('Search query (see GitHub search syntax)'), 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)'), }); 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'), private: z .boolean() .optional() .describe('Whether the repository should be private'), autoInit: z.boolean().optional().describe('Initialize with README.md'), }); export const GetFileContentsSchema = RepoParamsSchema.extend({ path: z.string().describe('Path to the file or directory'), branch: z.string().optional().describe('Branch to get contents from'), }); export const PushFilesSchema = RepoParamsSchema.extend({ branch: z.string().describe("Branch to push to (e.g., 'main' or 'master')"), files: z .array( z.object({ path: z.string().describe('Path where to create the file'), content: z.string().describe('Content of the file'), }) ) .describe('Array of files to push'), message: z.string().describe('Commit message'), }); export const CreateIssueSchema = RepoParamsSchema.extend({ title: z.string().describe('Issue title'), body: z.string().optional().describe('Issue body/description'), assignees: z .array(z.string()) .optional() .describe('Array of usernames to assign'), labels: z.array(z.string()).optional().describe('Array of label names'), milestone: z.number().optional().describe('Milestone number to assign'), }); export const CreatePullRequestSchema = RepoParamsSchema.extend({ title: z.string().describe('Pull request title'), body: z.string().optional().describe('Pull request body/description'), head: z .string() .describe('The name of the branch where your changes are implemented'), base: z .string() .describe('The name of the branch you want the changes pulled into'), draft: z .boolean() .optional() .describe('Whether to create the pull request as a draft'), maintainer_can_modify: z .boolean() .optional() .describe('Whether maintainers can modify the pull request'), }); export const ForkRepositorySchema = RepoParamsSchema.extend({ organization: z .string() .optional() .describe( 'Optional: organization to fork to (defaults to your personal account)' ), }); export const CreateBranchSchema = RepoParamsSchema.extend({ branch: z.string().describe('Name for the new branch'), from_branch: z .string() .optional() .describe( "Optional: source branch to create from (defaults to the repository's default branch)" ), }); /** * Response schema for a code search result item * @see https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-code */ export const SearchCodeItemSchema = z.object({ name: z.string().describe('The name of the file'), path: z.string().describe('The path to the file in the repository'), sha: z.string().describe('The SHA hash of the file'), url: z.string().describe('The API URL for this file'), git_url: z.string().describe('The Git URL for this file'), html_url: z.string().describe('The HTML URL to view this file on GitHub'), repository: GitHubRepositorySchema.describe( 'The repository where this file was found' ), score: z.number().describe('The search result score'), }); /** * Response schema for code search results */ export const SearchCodeResponseSchema = z.object({ total_count: z.number().describe('Total number of matching results'), incomplete_results: z .boolean() .describe('Whether the results are incomplete'), items: z.array(SearchCodeItemSchema).describe('The search results'), }); /** * Response schema for an issue search result item * @see https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-issues-and-pull-requests */ export const SearchIssueItemSchema = z.object({ url: z.string().describe('The API URL for this issue'), repository_url: z .string() .describe('The API URL for the repository where this issue was found'), labels_url: z.string().describe('The API URL for the labels of this issue'), comments_url: z.string().describe('The API URL for comments of this issue'), events_url: z.string().describe('The API URL for events of this issue'), html_url: z.string().describe('The HTML URL to view this issue on GitHub'), id: z.number().describe('The ID of this issue'), node_id: z.string().describe('The Node ID of this issue'), number: z.number().describe('The number of this issue'), title: z.string().describe('The title of this issue'), user: GitHubIssueAssigneeSchema.describe('The user who created this issue'), labels: z.array(GitHubLabelSchema).describe('The labels of this issue'), state: z.string().describe('The state of this issue'), locked: z.boolean().describe('Whether this issue is locked'), assignee: GitHubIssueAssigneeSchema.nullable().describe( 'The assignee of this issue' ), assignees: z .array(GitHubIssueAssigneeSchema) .describe('The assignees of this issue'), comments: z.number().describe('The number of comments on this issue'), created_at: z.string().describe('The creation time of this issue'), updated_at: z.string().describe('The last update time of this issue'), closed_at: z.string().nullable().describe('The closure time of this issue'), body: z.string().describe('The body of this issue'), score: z.number().describe('The search result score'), pull_request: z .object({ url: z.string().describe('The API URL for this pull request'), html_url: z.string().describe('The HTML URL to view this pull request'), diff_url: z.string().describe('The URL to view the diff'), patch_url: z.string().describe('The URL to view the patch'), }) .optional() .describe('Pull request details if this is a PR'), }); /** * Response schema for issue search results */ export const SearchIssuesResponseSchema = z.object({ total_count: z.number().describe('Total number of matching results'), incomplete_results: z .boolean() .describe('Whether the results are incomplete'), items: z.array(SearchIssueItemSchema).describe('The search results'), }); /** * Response schema for a user search result item * @see https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-users */ export const SearchUserItemSchema = z.object({ login: z.string().describe('The username of the user'), id: z.number().describe('The ID of the user'), node_id: z.string().describe('The Node ID of the user'), avatar_url: z.string().describe('The avatar URL of the user'), gravatar_id: z.string().describe('The Gravatar ID of the user'), url: z.string().describe('The API URL for this user'), html_url: z.string().describe('The HTML URL to view this user on GitHub'), followers_url: z.string().describe('The API URL for followers of this user'), following_url: z.string().describe('The API URL for following of this user'), gists_url: z.string().describe('The API URL for gists of this user'), starred_url: z .string() .describe('The API URL for starred repositories of this user'), subscriptions_url: z .string() .describe('The API URL for subscriptions of this user'), organizations_url: z .string() .describe('The API URL for organizations of this user'), repos_url: z.string().describe('The API URL for repositories of this user'), events_url: z.string().describe('The API URL for events of this user'), received_events_url: z .string() .describe('The API URL for received events of this user'), type: z.string().describe('The type of this user'), site_admin: z.boolean().describe('Whether this user is a site administrator'), score: z.number().describe('The search result score'), }); /** * Response schema for user search results */ export const SearchUsersResponseSchema = z.object({ total_count: z.number().describe('Total number of matching results'), incomplete_results: z .boolean() .describe('Whether the results are incomplete'), items: z.array(SearchUserItemSchema).describe('The search results'), }); /** * Input schema for code search * @see https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-code--parameters */ export const SearchCodeSchema = z.object({ q: z .string() .describe( 'Search query. See GitHub code search syntax: https://docs.github.com/en/search-github/searching-on-github/searching-code' ), order: z .enum(['asc', 'desc']) .optional() .describe('Sort order (asc or desc)'), per_page: z .number() .min(1) .max(100) .optional() .describe('Results per page (max 100)'), page: z.number().min(1).optional().describe('Page number'), }); /** * Input schema for issues search * @see https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-issues-and-pull-requests--parameters */ export const SearchIssuesSchema = z.object({ q: z .string() .describe( 'Search query. See GitHub issues search syntax: https://docs.github.com/en/search-github/searching-on-github/searching-issues-and-pull-requests' ), sort: z .enum([ 'comments', 'reactions', 'reactions-+1', 'reactions--1', 'reactions-smile', 'reactions-thinking_face', 'reactions-heart', 'reactions-tada', 'interactions', 'created', 'updated', ]) .optional() .describe('Sort field'), order: z .enum(['asc', 'desc']) .optional() .describe('Sort order (asc or desc)'), per_page: z .number() .min(1) .max(100) .optional() .describe('Results per page (max 100)'), page: z.number().min(1).optional().describe('Page number'), }); /** * Input schema for users search * @see https://docs.github.com/en/rest/search/search?apiVersion=2022-11-28#search-users--parameters */ export const SearchUsersSchema = z.object({ q: z .string() .describe( 'Search query. See GitHub users search syntax: https://docs.github.com/en/search-github/searching-on-github/searching-users' ), sort: z .enum(['followers', 'repositories', 'joined']) .optional() .describe('Sort field'), order: z .enum(['asc', 'desc']) .optional() .describe('Sort order (asc or desc)'), per_page: z .number() .min(1) .max(100) .optional() .describe('Results per page (max 100)'), 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 const GetIssueSchema = z.object({ owner: z.string().describe('Repository owner (username or organization)'), repo: z.string().describe('Repository name'), issue_number: z.number().describe('Issue number'), }); // Comment related schemas export const GitHubCommentSchema = z.object({ id: z.number(), node_id: z.string(), url: z.string(), html_url: z.string(), body: z.string(), user: GitHubIssueAssigneeSchema, created_at: z.string(), updated_at: z.string(), }); export const GetIssueCommentsSchema = z.object({ owner: z.string().describe('Repository owner (username or organization)'), repo: z.string().describe('Repository name'), issue_number: z.number().describe('Issue/PR number'), }); // Export types export type GitHubAuthor = z.infer<typeof GitHubAuthorSchema>; export type GitHubFork = z.infer<typeof GitHubForkSchema>; export type GitHubIssue = z.infer<typeof GitHubIssueSchema>; export type GitHubPullRequest = z.infer<typeof GitHubPullRequestSchema>; export type GitHubRepository = z.infer<typeof GitHubRepositorySchema>; export type GitHubFileContent = z.infer<typeof GitHubFileContentSchema>; export type GitHubDirectoryContent = z.infer< typeof GitHubDirectoryContentSchema >; export type GitHubContent = z.infer<typeof GitHubContentSchema>; export type FileOperation = z.infer<typeof FileOperationSchema>; export type GitHubTree = z.infer<typeof GitHubTreeSchema>; export type GitHubCommit = z.infer<typeof GitHubCommitSchema>; export type GitHubListCommits = z.infer<typeof GitHubListCommitsSchema>; export type GitHubReference = z.infer<typeof GitHubReferenceSchema>; export type CreateRepositoryOptions = z.infer< typeof CreateRepositoryOptionsSchema >; export type CreateIssueOptions = z.infer<typeof CreateIssueOptionsSchema>; export type CreatePullRequestOptions = z.infer< typeof CreatePullRequestOptionsSchema >; export type CreateBranchOptions = z.infer<typeof CreateBranchOptionsSchema>; export type GitHubCreateUpdateFileResponse = z.infer< typeof GitHubCreateUpdateFileResponseSchema >; export type GitHubSearchResponse = z.infer<typeof GitHubSearchResponseSchema>; export type SearchCodeItem = z.infer<typeof SearchCodeItemSchema>; export type SearchCodeResponse = z.infer<typeof SearchCodeResponseSchema>; export type SearchIssueItem = z.infer<typeof SearchIssueItemSchema>; export type SearchIssuesResponse = z.infer<typeof SearchIssuesResponseSchema>; export type SearchUserItem = z.infer<typeof SearchUserItemSchema>; export type SearchUsersResponse = z.infer<typeof SearchUsersResponseSchema>; export type GitHubComment = z.infer<typeof GitHubCommentSchema>;