UNPKG

coderabbitai-mcp

Version:

MCP server for interacting with CodeRabbit AI reviews on GitHub pull requests. Enables LLMs to analyze, implement, and resolve CodeRabbit suggestions programmatically.

469 lines (454 loc) 20.5 kB
import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, McpError, } from "@modelcontextprotocol/sdk/types.js"; // Import tool implementations import { getCoderabbitReviews } from "./tools/get-reviews.js"; import { getReviewDetails } from "./tools/get-review-details.js"; import { getReviewComments } from "./tools/get-comments.js"; import { getCommentDetails } from "./tools/get-comment-details.js"; import { resolveComment } from "./tools/resolve-comment.js"; import { resolveConversation } from "./tools/resolve-conversation.js"; import { GitHubClient } from "./github-client.js"; // CodeRabbit review processing prompt template const CODERABBIT_REVIEW_PROMPT = `I'll process CodeRabbit reviews for this pull request systematically. Here's my optimized workflow: ## **Phase 1: Discovery & Assessment** 1. **Find open PR** for current branch 2. **Get CodeRabbit review summary** from \`get_coderabbit_reviews\` (use summary data, avoid large comment responses) 3. **Extract actionable items** from review body using actionable_comments count and summary 4. **Create todo list** with prioritized issues from review summary (avoid calling \`get_review_comments\` initially due to token limits) ## **Phase 2: Issue Classification** **Assessment Guidelines:** - **HIGH PRIORITY (Must Fix):** - Security vulnerabilities - Breaking changes or bugs - TypeScript/compilation errors - Performance issues with significant impact - Logic errors or incorrect implementations - **MEDIUM PRIORITY (Should Fix):** - Type safety improvements - Performance optimizations (moderate impact) - Code maintainability issues - Missing error handling - **LOW PRIORITY (Nice to Have):** - Style/formatting nitpicks - Code organization suggestions - Minor optimizations - Documentation improvements - **SKIP (Not Actionable):** - Purely subjective style preferences - Suggestions without clear benefit - Comments that require significant architecture changes - Out-of-scope recommendations ## **Phase 3: User Approval** Present the categorized todo list to the user for approval before starting work: - Show issue priorities and brief descriptions - Ask user to confirm which issues to address - Allow user to modify priorities or skip items ## **Phase 4: Implementation** For approved issues: 1. **Work on HIGH priority items first** 2. **Use task tracking to monitor progress** (mark in_progress, then completed) 3. **Launch each task as a subtask** using the Task tool for focused execution 4. **Apply fixes systematically** by reading files and making targeted edits 5. **Get individual comment details** only when needed using \`get_comment_details\` 6. **Resolve each comment** with \`resolve_comment\` including fix details ## **Phase 5: Completion** - Mark all todos as completed - Report final status: "Ready to merge" or list remaining issues - Provide summary of all fixes applied ## **Error Handling for Large Responses:** If \`get_review_comments\` exceeds token limits: 1. Extract actionable items from review summary instead 2. Parse review body for specific file/line mentions 3. Use targeted \`get_comment_details\` for individual issues 4. Work from review metadata rather than full comment dump`; /** * CodeRabbit MCP Server * * Provides tools for interacting with CodeRabbit AI reviews on GitHub pull requests */ class CodeRabbitMCPServer { server; githubClient; constructor() { this.server = new Server({ name: "coderabbitai-mcp", version: "1.1.0", }, { capabilities: { tools: {}, prompts: {}, }, }); // Initialize GitHub client with environment variable try { this.githubClient = new GitHubClient(); } catch (error) { console.error("Failed to initialize GitHub client:", error); console.error("Please ensure GITHUB_PAT environment variable is set"); throw error; } this.setupToolHandlers(); this.setupPromptHandlers(); this.setupErrorHandling(); } setupErrorHandling() { this.server.onerror = (error) => { console.error("[MCP Error]", error); }; process.on("SIGINT", async () => { await this.server.close(); process.exit(0); }); } setupPromptHandlers() { // Handle prompt listing this.server.setRequestHandler(ListPromptsRequestSchema, async () => { return { prompts: [ { name: "coderabbit-review", description: "Automated CodeRabbit review processing and issue resolution for the current branch", arguments: [ { name: "owner", description: "Repository owner (username or organization)", required: true }, { name: "repo", description: "Repository name", required: true }, { name: "pullNumber", description: "Pull request number to process", required: true } ] } ] }; }); // Handle prompt execution this.server.setRequestHandler(GetPromptRequestSchema, async (request) => { const { name, arguments: args } = request.params; if (name === "coderabbit-review") { const owner = args?.owner || ""; const repo = args?.repo || ""; const pullNumber = args?.pullNumber || ""; const promptText = CODERABBIT_REVIEW_PROMPT + (owner && repo && pullNumber ? `\n\nLet me start the process for ${owner}/${repo}#${pullNumber}:` : '\n\nPlease provide the repository owner, repo name, and pull request number to begin processing.'); return { messages: [ { role: "user", content: { type: "text", text: promptText } } ] }; } throw new McpError(ErrorCode.MethodNotFound, `Unknown prompt: ${name}`); }); } setupToolHandlers() { // Handle tool listing this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "get_coderabbit_reviews", description: "Get all CodeRabbit reviews for a specific GitHub pull request", inputSchema: { type: "object", properties: { owner: { type: "string", description: "Repository owner (username or organization)" }, repo: { type: "string", description: "Repository name" }, pullNumber: { type: "number", description: "Pull request number" } }, required: ["owner", "repo", "pullNumber"] } }, { name: "get_review_details", description: "Get detailed information about a specific CodeRabbit review", inputSchema: { type: "object", properties: { owner: { type: "string", description: "Repository owner (username or organization)" }, repo: { type: "string", description: "Repository name" }, pullNumber: { type: "number", description: "Pull request number" }, reviewId: { type: "number", description: "Review ID" } }, required: ["owner", "repo", "pullNumber", "reviewId"] } }, { name: "get_review_comments", description: "Get all individual line comments from CodeRabbit reviews", inputSchema: { type: "object", properties: { owner: { type: "string", description: "Repository owner (username or organization)" }, repo: { type: "string", description: "Repository name" }, pullNumber: { type: "number", description: "Pull request number" }, reviewId: { type: "number", description: "Optional: specific review ID to filter comments", optional: true } }, required: ["owner", "repo", "pullNumber"] } }, { name: "get_comment_details", description: "Get detailed information about a specific CodeRabbit comment including AI prompts", inputSchema: { type: "object", properties: { owner: { type: "string", description: "Repository owner (username or organization)" }, repo: { type: "string", description: "Repository name" }, commentId: { type: "number", description: "Comment ID" } }, required: ["owner", "repo", "commentId"] } }, { name: "resolve_comment", description: "Mark a CodeRabbit comment as resolved or addressed", inputSchema: { type: "object", properties: { owner: { type: "string", description: "Repository owner (username or organization)" }, repo: { type: "string", description: "Repository name" }, commentId: { type: "number", description: "Comment ID" }, resolution: { type: "string", enum: ["addressed", "wont_fix", "not_applicable"], description: "Resolution type", default: "addressed" }, note: { type: "string", description: "Optional note about the resolution", optional: true } }, required: ["owner", "repo", "commentId"] } }, { name: "resolve_conversation", description: "Resolve or unresolve a CodeRabbit review conversation in GitHub", inputSchema: { type: "object", properties: { owner: { type: "string", description: "Repository owner (username or organization)" }, repo: { type: "string", description: "Repository name" }, commentId: { type: "number", description: "Comment ID of the conversation to resolve" }, resolved: { type: "boolean", description: "Whether to resolve (true) or unresolve (false) the conversation", default: true }, note: { type: "string", description: "Optional note about the resolution", optional: true } }, required: ["owner", "repo", "commentId"] } } ] }; }); // Handle tool execution this.server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case "get_coderabbit_reviews": { const input = args; const result = await getCoderabbitReviews(input, this.githubClient); return { content: [ { type: "text", text: JSON.stringify(result, null, 2) } ] }; } case "get_review_details": { const input = args; const result = await getReviewDetails(input, this.githubClient); return { content: [ { type: "text", text: JSON.stringify(result, null, 2) } ] }; } case "get_review_comments": { const input = args; const result = await getReviewComments(input, this.githubClient); return { content: [ { type: "text", text: JSON.stringify(result, null, 2) } ] }; } case "get_comment_details": { const input = args; const result = await getCommentDetails(input, this.githubClient); return { content: [ { type: "text", text: JSON.stringify(result, null, 2) } ] }; } case "resolve_comment": { const input = args; const result = await resolveComment(input, this.githubClient); return { content: [ { type: "text", text: JSON.stringify(result, null, 2) } ] }; } case "resolve_conversation": { const input = args; const result = await resolveConversation(input, this.githubClient); return { content: [ { type: "text", text: JSON.stringify(result, null, 2) } ] }; } default: throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`); } } catch (error) { if (error instanceof McpError) { throw error; } const errorMessage = error instanceof Error ? error.message : String(error); throw new McpError(ErrorCode.InternalError, `Tool execution failed: ${errorMessage}`); } }); } /** * Validate GitHub token and connection */ async validateGitHubConnection() { try { const validation = await this.githubClient.validateToken(); if (validation.valid) { console.error(`✅ GitHub connection validated for user: ${validation.user}`); console.error(`📋 Token scopes: ${validation.scopes.join(', ')}`); return true; } else { console.error("❌ GitHub token validation failed"); return false; } } catch (error) { console.error("❌ GitHub connection error:", error); return false; } } async run() { // Validate GitHub connection on startup const isValid = await this.validateGitHubConnection(); if (!isValid) { console.error("⚠️ GitHub connection validation failed, but server will still start"); console.error(" Some features may not work properly"); } const transport = new StdioServerTransport(); await this.server.connect(transport); console.error("🚀 CodeRabbit MCP server running on stdio"); } } export { CodeRabbitMCPServer }; //# sourceMappingURL=server.js.map