GitHub MCP Server
by asbloom-py
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import fetch from 'node-fetch';
import { z } from 'zod';
import { zodToJsonSchema } from 'zod-to-json-schema';
import {
CreateBranchOptionsSchema,
CreateBranchSchema,
CreateIssueOptionsSchema,
CreateIssueSchema,
CreateOrUpdateFileSchema,
CreatePullRequestOptionsSchema,
CreatePullRequestSchema,
CreateRepositoryOptionsSchema,
CreateRepositorySchema,
ForkRepositorySchema,
GetFileContentsSchema,
GetIssueSchema,
GetIssueCommentsSchema,
GitHubComment,
GitHubCommentSchema,
GitHubCommitSchema,
GitHubContentSchema,
GitHubCreateUpdateFileResponseSchema,
GitHubForkSchema,
GitHubIssueSchema,
GitHubListCommits,
GitHubListCommitsSchema,
GitHubPullRequestSchema,
GitHubReferenceSchema,
GitHubRepositorySchema,
GitHubSearchResponseSchema,
GitHubTreeSchema,
IssueCommentSchema,
ListCommitsSchema,
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 SearchCodeResponse,
type SearchIssuesResponse,
type SearchUsersResponse,
} from './schemas.js';
const server = new Server(
{
name: 'github-mcp-server',
version: '0.1.0',
},
{
capabilities: {
tools: {},
},
}
);
const GITHUB_PERSONAL_ACCESS_TOKEN = process.env.GITHUB_PERSONAL_ACCESS_TOKEN;
if (!GITHUB_PERSONAL_ACCESS_TOKEN) {
console.error('GITHUB_PERSONAL_ACCESS_TOKEN environment variable is not set');
process.exit(1);
}
async function forkRepository(
owner: string,
repo: string,
organization?: string
): Promise<GitHubFork> {
const url = organization
? `https://api.github.com/repos/${owner}/${repo}/forks?organization=${organization}`
: `https://api.github.com/repos/${owner}/${repo}/forks`;
const response = await fetch(url, {
method: 'POST',
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 GitHubForkSchema.parse(await response.json());
}
async function createBranch(
owner: string,
repo: string,
options: z.infer<typeof CreateBranchOptionsSchema>
): Promise<GitHubReference> {
const fullRef = `refs/heads/${options.ref}`;
const response = await fetch(
`https://api.github.com/repos/${owner}/${repo}/git/refs`,
{
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({
ref: fullRef,
sha: options.sha,
}),
}
);
if (!response.ok) {
throw new Error(`GitHub API error: ${response.statusText}`);
}
return GitHubReferenceSchema.parse(await response.json());
}
async function getDefaultBranchSHA(
owner: string,
repo: string
): Promise<string> {
const response = await fetch(
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/main`,
{
headers: {
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: 'application/vnd.github.v3+json',
'User-Agent': 'github-mcp-server',
},
}
);
if (!response.ok) {
const masterResponse = await fetch(
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/master`,
{
headers: {
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: 'application/vnd.github.v3+json',
'User-Agent': 'github-mcp-server',
},
}
);
if (!masterResponse.ok) {
throw new Error(
"Could not find default branch (tried 'main' and 'master')"
);
}
const data = GitHubReferenceSchema.parse(await masterResponse.json());
return data.object.sha;
}
const data = GitHubReferenceSchema.parse(await response.json());
return data.object.sha;
}
async function getFileContents(
owner: string,
repo: string,
path: string,
branch?: string
): Promise<GitHubContent> {
let url = `https://api.github.com/repos/${owner}/${repo}/contents/${path}`;
if (branch) {
url += `?ref=${branch}`;
}
const response = await fetch(url, {
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}`);
}
const data = GitHubContentSchema.parse(await response.json());
// If it's a file, decode the content
if (!Array.isArray(data) && data.content) {
data.content = Buffer.from(data.content, 'base64').toString('utf8');
}
return data;
}
async function createIssue(
owner: string,
repo: string,
options: z.infer<typeof CreateIssueOptionsSchema>
): Promise<GitHubIssue> {
const response = await fetch(
`https://api.github.com/repos/${owner}/${repo}/issues`,
{
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(options),
}
);
if (!response.ok) {
throw new Error(`GitHub API error: ${response.statusText}`);
}
return GitHubIssueSchema.parse(await response.json());
}
async function createPullRequest(
owner: string,
repo: string,
options: z.infer<typeof CreatePullRequestOptionsSchema>
): Promise<GitHubPullRequest> {
const response = await fetch(
`https://api.github.com/repos/${owner}/${repo}/pulls`,
{
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(options),
}
);
if (!response.ok) {
throw new Error(`GitHub API error: ${response.statusText}`);
}
return GitHubPullRequestSchema.parse(await response.json());
}
async function createOrUpdateFile(
owner: string,
repo: string,
path: string,
content: string,
message: string,
branch: string,
sha?: string
): Promise<GitHubCreateUpdateFileResponse> {
const encodedContent = Buffer.from(content).toString('base64');
let currentSha = sha;
if (!currentSha) {
try {
const existingFile = await getFileContents(owner, repo, path, branch);
if (!Array.isArray(existingFile)) {
currentSha = existingFile.sha;
}
} catch (error) {
console.error(
'Note: File does not exist in branch, will create new file'
);
}
}
const url = `https://api.github.com/repos/${owner}/${repo}/contents/${path}`;
const body = {
message,
content: encodedContent,
branch,
...(currentSha ? { sha: currentSha } : {}),
};
const response = await fetch(url, {
method: 'PUT',
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 GitHubCreateUpdateFileResponseSchema.parse(await response.json());
}
async function createTree(
owner: string,
repo: string,
files: FileOperation[],
baseTree?: string
): Promise<GitHubTree> {
const tree = files.map((file) => ({
path: file.path,
mode: '100644' as const,
type: 'blob' as const,
content: file.content,
}));
const response = await fetch(
`https://api.github.com/repos/${owner}/${repo}/git/trees`,
{
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({
tree,
base_tree: baseTree,
}),
}
);
if (!response.ok) {
throw new Error(`GitHub API error: ${response.statusText}`);
}
return GitHubTreeSchema.parse(await response.json());
}
async function createCommit(
owner: string,
repo: string,
message: string,
tree: string,
parents: string[]
): Promise<GitHubCommit> {
const response = await fetch(
`https://api.github.com/repos/${owner}/${repo}/git/commits`,
{
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({
message,
tree,
parents,
}),
}
);
if (!response.ok) {
throw new Error(`GitHub API error: ${response.statusText}`);
}
return GitHubCommitSchema.parse(await response.json());
}
async function updateReference(
owner: string,
repo: string,
ref: string,
sha: string
): Promise<GitHubReference> {
const response = await fetch(
`https://api.github.com/repos/${owner}/${repo}/git/refs/${ref}`,
{
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({
sha,
force: true,
}),
}
);
if (!response.ok) {
throw new Error(`GitHub API error: ${response.statusText}`);
}
return GitHubReferenceSchema.parse(await response.json());
}
async function pushFiles(
owner: string,
repo: string,
branch: string,
files: FileOperation[],
message: string
): Promise<GitHubReference> {
const refResponse = await fetch(
`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/${branch}`,
{
headers: {
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: 'application/vnd.github.v3+json',
'User-Agent': 'github-mcp-server',
},
}
);
if (!refResponse.ok) {
throw new Error(`GitHub API error: ${refResponse.statusText}`);
}
const ref = GitHubReferenceSchema.parse(await refResponse.json());
const commitSha = ref.object.sha;
const tree = await createTree(owner, repo, files, commitSha);
const commit = await createCommit(owner, repo, message, tree.sha, [
commitSha,
]);
return await updateReference(owner, repo, `heads/${branch}`, commit.sha);
}
async function searchRepositories(
query: string,
page: number = 1,
perPage: number = 30
): Promise<GitHubSearchResponse> {
const url = new URL('https://api.github.com/search/repositories');
url.searchParams.append('q', query);
url.searchParams.append('page', page.toString());
url.searchParams.append('per_page', perPage.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 GitHubSearchResponseSchema.parse(await response.json());
}
async function createRepository(
options: z.infer<typeof CreateRepositoryOptionsSchema>
): Promise<GitHubRepository> {
const response = await fetch('https://api.github.com/user/repos', {
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(options),
});
if (!response.ok) {
throw new Error(`GitHub API error: ${response.statusText}`);
}
return GitHubRepositorySchema.parse(await response.json());
}
async function listCommits(
owner: string,
repo: string,
page: number = 1,
perPage: number = 30,
sha?: string
): Promise<GitHubListCommits> {
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,
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> {
const url = new URL('https://api.github.com/search/code');
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
url.searchParams.append(key, value.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 SearchCodeResponseSchema.parse(await response.json());
}
async function searchIssues(
params: z.infer<typeof SearchIssuesSchema>
): Promise<SearchIssuesResponse> {
const url = new URL('https://api.github.com/search/issues');
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
url.searchParams.append(key, value.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 SearchIssuesResponseSchema.parse(await response.json());
}
async function searchUsers(
params: z.infer<typeof SearchUsersSchema>
): Promise<SearchUsersResponse> {
const url = new URL('https://api.github.com/search/users');
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined && value !== null) {
url.searchParams.append(key, value.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 SearchUsersResponseSchema.parse(await response.json());
}
async function getIssue(
owner: string,
repo: string,
issueNumber: number
): Promise<GitHubIssue> {
const response = await fetch(
`https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}`,
{
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 GitHubIssueSchema.parse(await response.json());
}
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'create_or_update_file',
description: 'Create or update a single file in a GitHub repository',
inputSchema: zodToJsonSchema(CreateOrUpdateFileSchema),
},
{
name: 'search_repositories',
description: 'Search for GitHub repositories',
inputSchema: zodToJsonSchema(SearchRepositoriesSchema),
},
{
name: 'create_repository',
description: 'Create a new GitHub repository in your account',
inputSchema: zodToJsonSchema(CreateRepositorySchema),
},
{
name: 'get_file_contents',
description:
'Get the contents of a file or directory from a GitHub repository',
inputSchema: zodToJsonSchema(GetFileContentsSchema),
},
{
name: 'push_files',
description:
'Push multiple files to a GitHub repository in a single commit',
inputSchema: zodToJsonSchema(PushFilesSchema),
},
{
name: 'create_issue',
description: 'Create a new issue in a GitHub repository',
inputSchema: zodToJsonSchema(CreateIssueSchema),
},
{
name: 'create_pull_request',
description: 'Create a new pull request in a GitHub repository',
inputSchema: zodToJsonSchema(CreatePullRequestSchema),
},
{
name: 'fork_repository',
description:
'Fork a GitHub repository to your account or specified organization',
inputSchema: zodToJsonSchema(ForkRepositorySchema),
},
{
name: 'create_branch',
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',
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',
inputSchema: zodToJsonSchema(SearchCodeSchema),
},
{
name: 'search_issues',
description:
'Search for issues and pull requests across GitHub repositories',
inputSchema: zodToJsonSchema(SearchIssuesSchema),
},
{
name: 'search_users',
description: 'Search for users on GitHub',
inputSchema: zodToJsonSchema(SearchUsersSchema),
},
{
name: 'get_issue',
description: 'Get details of a specific issue in a GitHub repository.',
inputSchema: zodToJsonSchema(GetIssueSchema),
},
{
name: 'get_issue_comments',
description: 'Get comments on an issue or pull request',
inputSchema: zodToJsonSchema(GetIssueCommentsSchema),
},
],
};
});
// Add this function near the other GitHub API functions
async function getIssueComments(
owner: string,
repo: string,
issueNumber: number
): Promise<GitHubComment[]> {
const response = await fetch(
`https://api.github.com/repos/${owner}/${repo}/issues/${issueNumber}/comments`,
{
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(GitHubCommentSchema).parse(await response.json());
}
server.setRequestHandler(CallToolRequestSchema, async (request) => {
try {
if (!request.params.arguments) {
throw new Error('Arguments are required');
}
switch (request.params.name) {
case 'fork_repository': {
const args = ForkRepositorySchema.parse(request.params.arguments);
const fork = await forkRepository(
args.owner,
args.repo,
args.organization
);
return {
content: [{ type: 'text', text: JSON.stringify(fork, null, 2) }],
};
}
case 'create_branch': {
const args = CreateBranchSchema.parse(request.params.arguments);
let sha: string;
if (args.from_branch) {
const response = await fetch(
`https://api.github.com/repos/${args.owner}/${args.repo}/git/refs/heads/${args.from_branch}`,
{
headers: {
Authorization: `token ${GITHUB_PERSONAL_ACCESS_TOKEN}`,
Accept: 'application/vnd.github.v3+json',
'User-Agent': 'github-mcp-server',
},
}
);
if (!response.ok) {
throw new Error(`Source branch '${args.from_branch}' not found`);
}
const data = GitHubReferenceSchema.parse(await response.json());
sha = data.object.sha;
} else {
sha = await getDefaultBranchSHA(args.owner, args.repo);
}
const branch = await createBranch(args.owner, args.repo, {
ref: args.branch,
sha,
});
return {
content: [{ type: 'text', text: JSON.stringify(branch, null, 2) }],
};
}
case 'search_repositories': {
const args = SearchRepositoriesSchema.parse(request.params.arguments);
const results = await searchRepositories(
args.query,
args.page,
args.perPage
);
return {
content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
};
}
case 'create_repository': {
const args = CreateRepositorySchema.parse(request.params.arguments);
const repository = await createRepository(args);
return {
content: [
{ type: 'text', text: JSON.stringify(repository, null, 2) },
],
};
}
case 'get_file_contents': {
const args = GetFileContentsSchema.parse(request.params.arguments);
const contents = await getFileContents(
args.owner,
args.repo,
args.path,
args.branch
);
return {
content: [{ type: 'text', text: JSON.stringify(contents, null, 2) }],
};
}
case 'create_or_update_file': {
const args = CreateOrUpdateFileSchema.parse(request.params.arguments);
const result = await createOrUpdateFile(
args.owner,
args.repo,
args.path,
args.content,
args.message,
args.branch,
args.sha
);
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
}
case 'push_files': {
const args = PushFilesSchema.parse(request.params.arguments);
const result = await pushFiles(
args.owner,
args.repo,
args.branch,
args.files,
args.message
);
return {
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
};
}
case 'create_issue': {
const args = CreateIssueSchema.parse(request.params.arguments);
const { owner, repo, ...options } = args;
const issue = await createIssue(owner, repo, options);
return {
content: [{ type: 'text', text: JSON.stringify(issue, null, 2) }],
};
}
case 'create_pull_request': {
const args = CreatePullRequestSchema.parse(request.params.arguments);
const { owner, repo, ...options } = args;
const pullRequest = await createPullRequest(owner, repo, options);
return {
content: [
{ type: 'text', text: JSON.stringify(pullRequest, null, 2) },
],
};
}
case 'search_code': {
const args = SearchCodeSchema.parse(request.params.arguments);
const results = await searchCode(args);
return {
content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
};
}
case 'search_issues': {
const args = SearchIssuesSchema.parse(request.params.arguments);
const results = await searchIssues(args);
return {
content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
};
}
case 'search_users': {
const args = SearchUsersSchema.parse(request.params.arguments);
const results = await searchUsers(args);
return {
content: [{ type: 'text', text: JSON.stringify(results, null, 2) }],
};
}
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 };
}
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) }],
};
}
case 'get_issue': {
const args = z
.object({
owner: z.string(),
repo: z.string(),
issue_number: z.number(),
})
.parse(request.params.arguments);
const issue = await getIssue(args.owner, args.repo, args.issue_number);
return {
content: [
{
type: 'text',
text: JSON.stringify(
{
task: {
title: issue.title,
description: issue.body,
source: issue.html_url,
},
},
null,
2
),
mimeType: 'application/json',
},
],
};
}
case 'get_issue_comments': {
const args = GetIssueCommentsSchema.parse(request.params.arguments);
const comments = await getIssueComments(
args.owner,
args.repo,
args.issue_number
);
return {
content: [{ type: 'text', text: JSON.stringify(comments, null, 2) }],
};
}
default:
throw new Error(`Unknown tool: ${request.params.name}`);
}
} catch (error) {
if (error instanceof z.ZodError) {
throw new Error(
`Invalid arguments: ${error.errors
.map(
(e: z.ZodError['errors'][number]) =>
`${e.path.join('.')}: ${e.message}`
)
.join(', ')}`
);
}
throw error;
}
});
async function runServer() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('GitHub MCP Server running on stdio');
}
runServer().catch((error) => {
console.error('Fatal error in main():', error);
process.exit(1);
});