mcp-github-issue
Version:
An MCP server that provides LLMs with the ability to use GitHub issues as tasks
132 lines • 4.96 kB
JavaScript
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js";
import { Octokit } from "@octokit/rest";
import { readFile } from "fs/promises";
import { fileURLToPath } from "url";
import { dirname, resolve } from "path";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
class GitHubIssueServer {
server;
octokit;
async initialize() {
const packageJsonPath = resolve(__dirname, "../package.json");
const packageJson = JSON.parse(await readFile(packageJsonPath, "utf-8"));
this.server = new Server({
name: "github-issue-server",
version: packageJson.version,
}, {
capabilities: {
tools: {},
},
});
// Initialize Octokit without auth for public repos
this.octokit = new Octokit();
this.setupToolHandlers();
this.server.onerror = (error) => console.error("[MCP Error]", error);
process.on("SIGINT", async () => {
await this.server.close();
process.exit(0);
});
}
constructor() {
// Properties will be initialized in initialize()
this.server = {};
this.octokit = {};
}
parseGitHubUrl(url) {
const match = url.match(/github\.com\/([^/]+)\/([^/]+)\/issues\/(\d+)/);
if (!match) {
throw new McpError(ErrorCode.InvalidParams, "Invalid GitHub issue URL format. Expected: https://github.com/owner/repo/issues/number");
}
return {
owner: match[1],
repo: match[2],
issue_number: parseInt(match[3], 10),
};
}
async getIssueDetails(url) {
const { owner, repo, issue_number } = this.parseGitHubUrl(url);
try {
const response = await this.octokit.issues.get({
owner,
repo,
issue_number,
});
return {
title: response.data.title,
body: response.data.body || "",
url: response.data.html_url,
};
}
catch (error) {
if (error instanceof Error) {
throw new McpError(ErrorCode.InternalError, `GitHub API error: ${error.message}`);
}
throw error;
}
}
setupToolHandlers() {
this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
tools: [
{
name: "get_issue_task",
description: "Fetch GitHub issue details to use as a task",
inputSchema: {
type: "object",
properties: {
url: {
type: "string",
description: "GitHub issue URL (https://github.com/owner/repo/issues/number)",
},
},
required: ["url"],
},
},
],
}));
this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
if (request.params.name !== "get_issue_task") {
throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}`);
}
const { url } = request.params.arguments;
if (!url) {
throw new McpError(ErrorCode.InvalidParams, "URL parameter is required");
}
try {
const issue = await this.getIssueDetails(url);
return {
content: [
{
type: "text",
text: JSON.stringify({
task: {
title: issue.title,
description: issue.body,
source: issue.url,
},
}, null, 2),
},
],
};
}
catch (error) {
if (error instanceof McpError) {
throw error;
}
throw new McpError(ErrorCode.InternalError, `Unexpected error: ${error}`);
}
});
}
async run() {
await this.initialize();
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error("GitHub Task MCP server running on stdio");
}
}
const server = new GitHubIssueServer();
server.run().catch(console.error);
//# sourceMappingURL=index.js.map