Skip to main content
Glama
issues.ts11.2 kB
import { z } from "zod" import type { Octokit } from "octokit" import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js" export function registerIssueTools(server: McpServer, octokit: Octokit) { // Tool: Get Issue server.tool( "get_issue", "Get details of a specific issue in a GitHub repository.", { owner: z.string().describe("The owner of the repository"), repo: z.string().describe("The name of the repository"), issue_number: z.number().describe("The number of the issue"), }, async ({ owner, repo, issue_number }) => { try { const response = await octokit.rest.issues.get({ owner, repo, issue_number, }) return { content: [{ type: "text", text: JSON.stringify(response.data) }], } } catch (e: any) { return { content: [{ type: "text", text: `Error: ${e.message}` }], } } }, ) // Tool: Add Issue Comment server.tool( "add_issue_comment", "Add a comment to a specific issue in a GitHub repository.", { owner: z.string().describe("Repository owner"), repo: z.string().describe("Repository name"), issue_number: z.number().describe("Issue number to comment on"), body: z.string().describe("Comment content"), }, async ({ owner, repo, issue_number, body }) => { try { const response = await octokit.rest.issues.createComment({ owner, repo, issue_number, body, }) return { content: [{ type: "text", text: JSON.stringify(response.data) }], } } catch (e: any) { return { content: [{ type: "text", text: `Error: ${e.message}` }], } } }, ) // Tool: Search Issues server.tool( "search_issues", "Search for issues in GitHub repositories.", { q: z.string().describe("Search query using GitHub issues search syntax"), sort: z .enum([ "comments", "reactions", "reactions-+1", "reactions--1", "reactions-smile", "reactions-thinking_face", "reactions-heart", "reactions-tada", "interactions", "created", "updated", ]) .optional() .describe( "Sort field by number of matches of categories, defaults to best match", ), order: z.enum(["asc", "desc"]).optional().describe("Sort order"), per_page: z .number() .optional() .default(10) .describe("Results per page (default 10, max 100)"), page: z .number() .optional() .default(1) .describe("Page number (default 1)"), }, async ({ q, sort, order, per_page, page }) => { try { const response = await octokit.rest.search.issuesAndPullRequests({ q, sort, order, per_page, page, }) // Format the response as clean markdown const items = response.data.items const totalCount = response.data.total_count if (items.length === 0) { return { content: [ { type: "text", text: "No issues found matching your search." }, ], } } let markdown = `# Search Results\n\n` markdown += `Found ${totalCount} total result(s), showing ${items.length}\n\n` markdown += `**Query**: ${q}\n\n` items.forEach(item => { const type = item.pull_request ? "PR" : "Issue" markdown += `## ${type} #${item.number}: ${item.title}\n\n` markdown += `- **Repository**: ${item.repository_url.split("/").slice(-2).join("/")}\n` markdown += `- **State**: ${item.state}\n` markdown += `- **Author**: ${item.user?.login || "Unknown"}\n` markdown += `- **Created**: ${new Date(item.created_at).toLocaleDateString()}\n` markdown += `- **Updated**: ${new Date(item.updated_at).toLocaleDateString()}\n` if (item.labels && item.labels.length > 0) { markdown += `- **Labels**: ${item.labels.map(l => l.name).join(", ")}\n` } if (item.assignees && item.assignees.length > 0) { markdown += `- **Assignees**: ${item.assignees.map(a => a.login).join(", ")}\n` } markdown += `- **Comments**: ${item.comments}\n` markdown += `- **URL**: ${item.html_url}\n` markdown += `\n` }) return { content: [{ type: "text", text: markdown }], } } catch (e: any) { return { content: [{ type: "text", text: `Error: ${e.message}` }], } } }, ) // Tool: Create Issue server.tool( "create_issue", "Create a new issue in a GitHub repository.", { owner: z.string().describe("Repository owner"), repo: z.string().describe("Repository name"), title: z.string().describe("Issue title"), body: z.string().optional().describe("Issue body content"), assignees: z .array(z.string()) .optional() .describe("Usernames to assign to this issue"), labels: z .array(z.string()) .optional() .describe("Labels to apply to this issue"), milestone: z.number().optional().describe("Milestone number"), }, async ({ owner, repo, title, body, assignees, labels, milestone }) => { try { const response = await octokit.rest.issues.create({ owner, repo, title, body, assignees, labels, milestone, }) return { content: [{ type: "text", text: JSON.stringify(response.data) }], } } catch (e: any) { return { content: [{ type: "text", text: `Error: ${e.message}` }], } } }, ) // Tool: List Issues server.tool( "list_issues", "List issues in a GitHub repository.", { owner: z.string().describe("Repository owner"), repo: z.string().describe("Repository name"), state: z .enum(["open", "closed", "all"]) .optional() .describe("Filter by state"), labels: z.array(z.string()).optional().describe("Filter by labels"), sort: z .enum(["created", "updated", "comments"]) .optional() .describe("Sort order"), direction: z.enum(["asc", "desc"]).optional().describe("Sort direction"), since: z .string() .optional() .describe("Filter by date (ISO 8601 timestamp)"), per_page: z .number() .optional() .default(10) .describe("Results per page (default 10, max 100)"), page: z .number() .optional() .default(1) .describe("Page number (default 1)"), }, async ({ owner, repo, state, labels, sort, direction, since, per_page, page, }) => { try { const response = await octokit.rest.issues.listForRepo({ owner, repo, state, labels: labels ? labels.join(",") : undefined, sort, direction, since, per_page, page, }) // Format the response as clean markdown const issues = response.data.filter(item => !item.pull_request) // Filter out PRs if (issues.length === 0) { return { content: [{ type: "text", text: "No issues found." }], } } let markdown = `# Issues for ${owner}/${repo}\n\n` markdown += `Showing ${issues.length} issue(s) - Page ${page}\n` if (response.data.length === per_page) { markdown += `*Note: More results may be available. Use 'page' parameter to see next page.*\n` } markdown += `\n` issues.forEach(issue => { markdown += `## #${issue.number}: ${issue.title}\n\n` markdown += `- **State**: ${issue.state}\n` markdown += `- **Author**: ${issue.user?.login || "Unknown"}\n` markdown += `- **Created**: ${new Date(issue.created_at).toLocaleDateString()}\n` markdown += `- **Updated**: ${new Date(issue.updated_at).toLocaleDateString()}\n` if (issue.labels && issue.labels.length > 0) { markdown += `- **Labels**: ${issue.labels.map(l => (typeof l === "string" ? l : l.name)).join(", ")}\n` } if (issue.assignees && issue.assignees.length > 0) { markdown += `- **Assignees**: ${issue.assignees.map(a => a.login).join(", ")}\n` } if (issue.milestone) { markdown += `- **Milestone**: ${issue.milestone.title}\n` } markdown += `- **Comments**: ${issue.comments}\n` markdown += `- **URL**: ${issue.html_url}\n` markdown += `\n` }) return { content: [{ type: "text", text: markdown }], } } catch (e: any) { return { content: [{ type: "text", text: `Error: ${e.message}` }], } } }, ) // Tool: Update Issue server.tool( "update_issue", "Update an existing issue in a GitHub repository.", { owner: z.string().describe("Repository owner"), repo: z.string().describe("Repository name"), issue_number: z.number().describe("Issue number to update"), title: z.string().optional().describe("New title"), body: z.string().optional().describe("New description"), state: z.enum(["open", "closed"]).optional().describe("New state"), labels: z.array(z.string()).optional().describe("New labels"), assignees: z.array(z.string()).optional().describe("New assignees"), milestone: z.number().optional().describe("New milestone number"), }, async ({ owner, repo, issue_number, title, body, state, labels, assignees, milestone, }) => { try { const response = await octokit.rest.issues.update({ owner, repo, issue_number, title, body, state, labels, assignees, milestone, }) return { content: [{ type: "text", text: JSON.stringify(response.data) }], } } catch (e: any) { return { content: [{ type: "text", text: `Error: ${e.message}` }], } } }, ) // Tool: Get Issue Comments server.tool( "get_issue_comments", "Get comments for a specific issue in a GitHub repository.", { owner: z.string().describe("Repository owner"), repo: z.string().describe("Repository name"), issue_number: z.number().describe("Issue number"), page: z.number().optional().default(1).describe("Page number"), per_page: z .number() .optional() .default(10) .describe("Number of records per page"), }, async ({ owner, repo, issue_number, page, per_page }) => { try { const response = await octokit.rest.issues.listComments({ owner, repo, issue_number, page, per_page, }) // Format the response as clean markdown const comments = response.data if (comments.length === 0) { return { content: [ { type: "text", text: "No comments found for this issue." }, ], } } let markdown = `# Comments for Issue #${issue_number}\n\n` markdown += `Showing ${comments.length} comment(s) - Page ${page}\n` if (comments.length === per_page) { markdown += `*Note: More comments may be available. Use 'page' parameter to see next page.*\n` } markdown += `\n` comments.forEach((comment, index) => { markdown += `## Comment ${index + 1}\n\n` markdown += `- **Author**: ${comment.user?.login || "Unknown"}\n` markdown += `- **Created**: ${new Date(comment.created_at).toLocaleDateString()}\n` markdown += `- **Updated**: ${new Date(comment.updated_at).toLocaleDateString()}\n\n` markdown += `**Content**:\n${comment.body}\n\n` markdown += `---\n\n` }) return { content: [{ type: "text", text: markdown }], } } catch (e: any) { return { content: [{ type: "text", text: `Error: ${e.message}` }], } } }, ) }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/hithereiamaliff/mcp-github'

If you have feedback or need assistance with the MCP directory API, please join our Discord server