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.

169 lines โ€ข 7.97 kB
import { z } from 'zod'; const GetReviewDetailsSchema = z.object({ owner: z.string().min(1, "Repository owner is required"), repo: z.string().min(1, "Repository name is required"), pullNumber: z.number().int().positive("Pull request number must be positive"), reviewId: z.number().int().positive("Review ID must be positive") }); /** * Parse CodeRabbit review body to extract detailed structured information */ function parseCoderabbitReviewBodyDetailed(body) { const lines = body.split('\n'); // Extract actionable comments count const actionableMatch = body.match(/\*\*Actionable comments posted: (\d+)\*\*/); const actionableCount = actionableMatch ? parseInt(actionableMatch[1]) : 0; // Extract duplicate comments count const duplicateMatch = body.match(/โ™ป๏ธ Duplicate comments \((\d+)\)/); const duplicateCount = duplicateMatch ? parseInt(duplicateMatch[1]) : 0; // Extract nitpick comments count const nitpickMatch = body.match(/๐Ÿงน Nitpick comments \((\d+)\)/); const nitpickCount = nitpickMatch ? parseInt(nitpickMatch[1]) : 0; // Extract configuration information const configMatch = body.match(/\*\*Configuration used: (.+?)\*\*/); const configuration = configMatch ? configMatch[1] : 'Unknown'; // Extract review profile const profileMatch = body.match(/\*\*Review profile: (.+?)\*\*/); const reviewProfile = profileMatch ? profileMatch[1] : 'Unknown'; // Extract files reviewed from the "Files selected for processing" section const filesSection = body.match(/๐Ÿ“’ Files selected for processing.*?\n(.*?)(?=\n\n|\n<|$)/s); const filesReviewed = []; if (filesSection) { const fileLines = filesSection[1].split('\n'); for (const line of fileLines) { const fileMatch = line.match(/\* `([^`]+)`/); if (fileMatch) { filesReviewed.push(fileMatch[1]); } } } // Extract summary from review details section let summary = ""; const summaryMatch = body.match(/๐Ÿ“œ Review details[\s\S]*?(?=\n\n|\n<|$)/); if (summaryMatch) { summary = summaryMatch[0].replace(/๐Ÿ“œ Review details/, '').trim(); } // Parse detailed comments from the review body const comments = []; // Look for comment blocks in details sections const commentSections = body.split('<details>'); for (const section of commentSections) { if (!section.includes('</details>')) continue; // Extract category from summary const summaryMatch = section.match(/<summary>(.*?)<\/summary>/); if (!summaryMatch) continue; const category = summaryMatch[1].trim(); // Skip non-code-review sections if (category.includes('Commits') || category.includes('Files ignored')) continue; // Parse individual comments within this section const commentBlocks = section.split('</blockquote>'); for (const block of commentBlocks) { if (!block.trim()) continue; // Determine severity from emoji/markers let severity = 'info'; if (block.includes('โš ๏ธ Potential issue') || block.includes('_โš ๏ธ Potential issue_')) { severity = 'warning'; } else if (block.includes('๐Ÿ› ๏ธ Refactor suggestion') || block.includes('_๐Ÿ› ๏ธ Refactor suggestion_')) { severity = 'suggestion'; } else if (block.includes('๐Ÿงน Nitpick')) { severity = 'info'; } else if (block.includes('๐Ÿ”’ Security')) { severity = 'error'; } // Extract AI prompt const aiPromptMatch = block.match(/๐Ÿค– Prompt for AI Agents[\s\S]*?```\n([\s\S]*?)\n```/); const aiPrompt = aiPromptMatch ? aiPromptMatch[1].trim() : undefined; // Extract committable suggestion const committableMatch = block.match(/๐Ÿ“ Committable suggestion[\s\S]*?```suggestion\n([\s\S]*?)\n```/); const committableSuggestion = committableMatch ? committableMatch[1].trim() : undefined; // Extract file path from the block const filePathMatch = block.match(/`([^`]+\.(js|ts|tsx|jsx|py|java|cpp|c|h|go|rs|rb|php|cs|kt|swift))`/); const filePath = filePathMatch ? filePathMatch[1] : undefined; // Extract line range const lineRangeMatch = block.match(/lines? (\d+(?:-\d+)?)/i); const lineRange = lineRangeMatch ? lineRangeMatch[1] : undefined; // Extract description (remove markdown formatting) const descLines = block.split('\n').filter(line => line.trim() && !line.includes('โš ๏ธ') && !line.includes('๐Ÿ› ๏ธ') && !line.includes('๐Ÿค– Prompt') && !line.includes('๐Ÿ“ Committable') && !line.trim().startsWith('<') && !line.trim().startsWith('```') && !line.trim().startsWith('---') && line.trim() !== 'suggestion'); let description = descLines.slice(0, 2).join(' ').trim(); description = description.replace(/\*\*/g, '').replace(/`([^`]+)`/g, '$1'); if (description && (aiPrompt || committableSuggestion || filePath)) { comments.push({ category, severity, description: description || 'CodeRabbit suggestion', ai_prompt: aiPrompt, committable_suggestion: committableSuggestion, file_path: filePath, line_range: lineRange }); } } } return { actionable_comments: actionableCount, duplicate_comments: duplicateCount, nitpick_comments: nitpickCount, summary, comments, files_reviewed: filesReviewed, configuration_used: configuration, review_profile: reviewProfile }; } /** * Get detailed information about a specific CodeRabbit review */ export async function getReviewDetails(input, githubClient) { // Validate input const validatedInput = GetReviewDetailsSchema.parse(input); const { owner, repo, pullNumber, reviewId } = validatedInput; try { // Get all reviews for the PR const reviews = await githubClient.getPullRequestReviews(owner, repo, pullNumber); // Find the specific CodeRabbit review const targetReview = reviews.find(review => review.id === reviewId && review.user.login === 'coderabbitai[bot]'); if (!targetReview) { throw new Error(`CodeRabbit review with ID ${reviewId} not found in PR #${pullNumber}`); } // Parse the review body for detailed information const parsed = parseCoderabbitReviewBodyDetailed(targetReview.body); const reviewDetails = { id: targetReview.id, submitted_at: targetReview.submitted_at, html_url: targetReview.html_url, state: targetReview.state, commit_id: targetReview.commit_id, body: targetReview.body, parsed_content: { actionable_comments: parsed.actionable_comments, duplicate_comments: parsed.duplicate_comments, nitpick_comments: parsed.nitpick_comments, summary: parsed.summary, comments: parsed.comments }, files_reviewed: parsed.files_reviewed, configuration_used: parsed.configuration_used, review_profile: parsed.review_profile }; return reviewDetails; } catch (error) { throw new Error(`Failed to get review details: ${error instanceof Error ? error.message : String(error)}`); } } //# sourceMappingURL=get-review-details.js.map