mirror of
https://github.com/modelcontextprotocol/servers.git
synced 2026-04-18 08:03:26 +02:00
- Import GitHubError types - Add error formatting utility - Update error handling in request handler
352 lines
12 KiB
JavaScript
352 lines
12 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 { z } from 'zod';
|
|
import { zodToJsonSchema } from 'zod-to-json-schema';
|
|
|
|
import * as repository from './operations/repository.js';
|
|
import * as files from './operations/files.js';
|
|
import * as issues from './operations/issues.js';
|
|
import * as pulls from './operations/pulls.js';
|
|
import * as branches from './operations/branches.js';
|
|
import * as search from './operations/search.js';
|
|
import * as commits from './operations/commits.js';
|
|
import {
|
|
GitHubError,
|
|
GitHubValidationError,
|
|
GitHubResourceNotFoundError,
|
|
GitHubAuthenticationError,
|
|
GitHubPermissionError,
|
|
GitHubRateLimitError,
|
|
GitHubConflictError,
|
|
isGitHubError,
|
|
} from './common/errors.js';
|
|
|
|
const server = new Server(
|
|
{
|
|
name: "github-mcp-server",
|
|
version: "0.1.0",
|
|
},
|
|
{
|
|
capabilities: {
|
|
tools: {},
|
|
},
|
|
}
|
|
);
|
|
|
|
function formatGitHubError(error: GitHubError): string {
|
|
let message = `GitHub API Error: ${error.message}`;
|
|
|
|
if (error instanceof GitHubValidationError) {
|
|
message = `Validation Error: ${error.message}`;
|
|
if (error.response) {
|
|
message += `\nDetails: ${JSON.stringify(error.response)}`;
|
|
}
|
|
} else if (error instanceof GitHubResourceNotFoundError) {
|
|
message = `Not Found: ${error.message}`;
|
|
} else if (error instanceof GitHubAuthenticationError) {
|
|
message = `Authentication Failed: ${error.message}`;
|
|
} else if (error instanceof GitHubPermissionError) {
|
|
message = `Permission Denied: ${error.message}`;
|
|
} else if (error instanceof GitHubRateLimitError) {
|
|
message = `Rate Limit Exceeded: ${error.message}\nResets at: ${error.resetAt.toISOString()}`;
|
|
} else if (error instanceof GitHubConflictError) {
|
|
message = `Conflict: ${error.message}`;
|
|
}
|
|
|
|
return message;
|
|
}
|
|
|
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
return {
|
|
tools: [
|
|
{
|
|
name: "create_or_update_file",
|
|
description: "Create or update a single file in a GitHub repository",
|
|
inputSchema: zodToJsonSchema(files.CreateOrUpdateFileSchema),
|
|
},
|
|
{
|
|
name: "search_repositories",
|
|
description: "Search for GitHub repositories",
|
|
inputSchema: zodToJsonSchema(repository.SearchRepositoriesSchema),
|
|
},
|
|
{
|
|
name: "create_repository",
|
|
description: "Create a new GitHub repository in your account",
|
|
inputSchema: zodToJsonSchema(repository.CreateRepositoryOptionsSchema),
|
|
},
|
|
{
|
|
name: "get_file_contents",
|
|
description: "Get the contents of a file or directory from a GitHub repository",
|
|
inputSchema: zodToJsonSchema(files.GetFileContentsSchema),
|
|
},
|
|
{
|
|
name: "push_files",
|
|
description: "Push multiple files to a GitHub repository in a single commit",
|
|
inputSchema: zodToJsonSchema(files.PushFilesSchema),
|
|
},
|
|
{
|
|
name: "create_issue",
|
|
description: "Create a new issue in a GitHub repository",
|
|
inputSchema: zodToJsonSchema(issues.CreateIssueSchema),
|
|
},
|
|
{
|
|
name: "create_pull_request",
|
|
description: "Create a new pull request in a GitHub repository",
|
|
inputSchema: zodToJsonSchema(pulls.CreatePullRequestSchema),
|
|
},
|
|
{
|
|
name: "fork_repository",
|
|
description: "Fork a GitHub repository to your account or specified organization",
|
|
inputSchema: zodToJsonSchema(repository.ForkRepositorySchema),
|
|
},
|
|
{
|
|
name: "create_branch",
|
|
description: "Create a new branch in a GitHub repository",
|
|
inputSchema: zodToJsonSchema(branches.CreateBranchSchema),
|
|
},
|
|
{
|
|
name: "list_commits",
|
|
description: "Get list of commits of a branch in a GitHub repository",
|
|
inputSchema: zodToJsonSchema(commits.ListCommitsSchema)
|
|
},
|
|
{
|
|
name: "list_issues",
|
|
description: "List issues in a GitHub repository with filtering options",
|
|
inputSchema: zodToJsonSchema(issues.ListIssuesOptionsSchema)
|
|
},
|
|
{
|
|
name: "update_issue",
|
|
description: "Update an existing issue in a GitHub repository",
|
|
inputSchema: zodToJsonSchema(issues.UpdateIssueOptionsSchema)
|
|
},
|
|
{
|
|
name: "add_issue_comment",
|
|
description: "Add a comment to an existing issue",
|
|
inputSchema: zodToJsonSchema(issues.IssueCommentSchema)
|
|
},
|
|
{
|
|
name: "search_code",
|
|
description: "Search for code across GitHub repositories",
|
|
inputSchema: zodToJsonSchema(search.SearchCodeSchema),
|
|
},
|
|
{
|
|
name: "search_issues",
|
|
description: "Search for issues and pull requests across GitHub repositories",
|
|
inputSchema: zodToJsonSchema(search.SearchIssuesSchema),
|
|
},
|
|
{
|
|
name: "search_users",
|
|
description: "Search for users on GitHub",
|
|
inputSchema: zodToJsonSchema(search.SearchUsersSchema),
|
|
},
|
|
{
|
|
name: "get_issue",
|
|
description: "Get details of a specific issue in a GitHub repository.",
|
|
inputSchema: zodToJsonSchema(issues.GetIssueSchema)
|
|
}
|
|
],
|
|
};
|
|
});
|
|
|
|
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 = repository.ForkRepositorySchema.parse(request.params.arguments);
|
|
const fork = await repository.forkRepository(args.owner, args.repo, args.organization);
|
|
return {
|
|
content: [{ type: "text", text: JSON.stringify(fork, null, 2) }],
|
|
};
|
|
}
|
|
|
|
case "create_branch": {
|
|
const args = branches.CreateBranchSchema.parse(request.params.arguments);
|
|
const branch = await branches.createBranchFromRef(
|
|
args.owner,
|
|
args.repo,
|
|
args.branch,
|
|
args.from_branch
|
|
);
|
|
return {
|
|
content: [{ type: "text", text: JSON.stringify(branch, null, 2) }],
|
|
};
|
|
}
|
|
|
|
case "search_repositories": {
|
|
const args = repository.SearchRepositoriesSchema.parse(request.params.arguments);
|
|
const results = await repository.searchRepositories(
|
|
args.query,
|
|
args.page,
|
|
args.perPage
|
|
);
|
|
return {
|
|
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
|
};
|
|
}
|
|
|
|
case "create_repository": {
|
|
const args = repository.CreateRepositoryOptionsSchema.parse(request.params.arguments);
|
|
const result = await repository.createRepository(args);
|
|
return {
|
|
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
};
|
|
}
|
|
|
|
case "get_file_contents": {
|
|
const args = files.GetFileContentsSchema.parse(request.params.arguments);
|
|
const contents = await files.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 = files.CreateOrUpdateFileSchema.parse(request.params.arguments);
|
|
const result = await files.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 = files.PushFilesSchema.parse(request.params.arguments);
|
|
const result = await files.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 = issues.CreateIssueSchema.parse(request.params.arguments);
|
|
const { owner, repo, ...options } = args;
|
|
const issue = await issues.createIssue(owner, repo, options);
|
|
return {
|
|
content: [{ type: "text", text: JSON.stringify(issue, null, 2) }],
|
|
};
|
|
}
|
|
|
|
case "create_pull_request": {
|
|
const args = pulls.CreatePullRequestSchema.parse(request.params.arguments);
|
|
const pullRequest = await pulls.createPullRequest(args);
|
|
return {
|
|
content: [{ type: "text", text: JSON.stringify(pullRequest, null, 2) }],
|
|
};
|
|
}
|
|
|
|
case "search_code": {
|
|
const args = search.SearchCodeSchema.parse(request.params.arguments);
|
|
const results = await search.searchCode(args);
|
|
return {
|
|
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
|
};
|
|
}
|
|
|
|
case "search_issues": {
|
|
const args = search.SearchIssuesSchema.parse(request.params.arguments);
|
|
const results = await search.searchIssues(args);
|
|
return {
|
|
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
|
};
|
|
}
|
|
|
|
case "search_users": {
|
|
const args = search.SearchUsersSchema.parse(request.params.arguments);
|
|
const results = await search.searchUsers(args);
|
|
return {
|
|
content: [{ type: "text", text: JSON.stringify(results, null, 2) }],
|
|
};
|
|
}
|
|
|
|
case "list_issues": {
|
|
const args = issues.ListIssuesOptionsSchema.parse(request.params.arguments);
|
|
const { owner, repo, ...options } = args;
|
|
const result = await issues.listIssues(owner, repo, options);
|
|
return { toolResult: result };
|
|
}
|
|
|
|
case "update_issue": {
|
|
const args = issues.UpdateIssueOptionsSchema.parse(request.params.arguments);
|
|
const { owner, repo, issue_number, ...options } = args;
|
|
const result = await issues.updateIssue(owner, repo, issue_number, options);
|
|
return { toolResult: result };
|
|
}
|
|
|
|
case "add_issue_comment": {
|
|
const args = issues.IssueCommentSchema.parse(request.params.arguments);
|
|
const { owner, repo, issue_number, body } = args;
|
|
const result = await issues.addIssueComment(owner, repo, issue_number, body);
|
|
return { toolResult: result };
|
|
}
|
|
|
|
case "list_commits": {
|
|
const args = commits.ListCommitsSchema.parse(request.params.arguments);
|
|
const results = await commits.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 = issues.GetIssueSchema.parse(request.params.arguments);
|
|
const issue = await issues.getIssue(args.owner, args.repo, args.issue_number);
|
|
return { toolResult: issue };
|
|
}
|
|
|
|
default:
|
|
throw new Error(`Unknown tool: ${request.params.name}`);
|
|
}
|
|
} catch (error) {
|
|
if (error instanceof z.ZodError) {
|
|
throw new Error(`Invalid input: ${JSON.stringify(error.errors)}`);
|
|
}
|
|
if (isGitHubError(error)) {
|
|
throw new Error(formatGitHubError(error));
|
|
}
|
|
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);
|
|
}); |