UNPKG

mcp-github-issue

Version:

An MCP server that provides LLMs with the ability to use GitHub issues as tasks

132 lines 4.96 kB
#!/usr/bin/env node 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