mirror of
https://github.com/modelcontextprotocol/servers.git
synced 2026-04-18 08:03:26 +02:00
692 lines
20 KiB
JavaScript
692 lines
20 KiB
JavaScript
#!/usr/bin/env node
|
|
|
|
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 {
|
|
GitHubForkSchema,
|
|
GitHubReferenceSchema,
|
|
GitHubRepositorySchema,
|
|
GitHubIssueSchema,
|
|
GitHubPullRequestSchema,
|
|
GitHubContentSchema,
|
|
GitHubCreateUpdateFileResponseSchema,
|
|
GitHubSearchResponseSchema,
|
|
GitHubTreeSchema,
|
|
GitHubCommitSchema,
|
|
CreateRepositoryOptionsSchema,
|
|
CreateIssueOptionsSchema,
|
|
CreatePullRequestOptionsSchema,
|
|
CreateBranchOptionsSchema,
|
|
type GitHubFork,
|
|
type GitHubReference,
|
|
type GitHubRepository,
|
|
type GitHubIssue,
|
|
type GitHubPullRequest,
|
|
type GitHubContent,
|
|
type GitHubCreateUpdateFileResponse,
|
|
type GitHubSearchResponse,
|
|
type GitHubTree,
|
|
type GitHubCommit,
|
|
type FileOperation,
|
|
CreateOrUpdateFileSchema,
|
|
SearchRepositoriesSchema,
|
|
CreateRepositorySchema,
|
|
GetFileContentsSchema,
|
|
PushFilesSchema,
|
|
CreateIssueSchema,
|
|
CreatePullRequestSchema,
|
|
ForkRepositorySchema,
|
|
CreateBranchSchema,
|
|
ListCommitsSchema
|
|
} from './schemas.js';
|
|
import { z } from 'zod';
|
|
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
|
|
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<GitHubCommit[]> {
|
|
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 GitHubCommitSchema.array().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)
|
|
}
|
|
]
|
|
};
|
|
});
|
|
|
|
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 "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}`);
|
|
}
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
throw new Error(`Invalid arguments: ${error.errors.map(e => `${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);
|
|
}); |