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.
167 lines โข 6.91 kB
JavaScript
import { z } from 'zod';
const GetCommentDetailsSchema = z.object({
owner: z.string().min(1, "Repository owner is required"),
repo: z.string().min(1, "Repository name is required"),
commentId: z.number().int().positive("Comment ID must be positive")
});
/**
* Extract fix examples from CodeRabbit comment body
*/
function extractFixExamples(body) {
const examples = [];
// Extract committable suggestions
const committableMatches = body.matchAll(/๐ Committable suggestion[\s\S]*?```suggestion\n([\s\S]*?)\n```/g);
for (const match of committableMatches) {
examples.push(match[1].trim());
}
// Extract diff suggestions
const diffMatches = body.matchAll(/```diff\n([\s\S]*?)\n```/g);
for (const match of diffMatches) {
examples.push(match[1].trim());
}
// Extract code block examples
const codeMatches = body.matchAll(/```(?:javascript|typescript|python|java|go|rust|cpp|c)\n([\s\S]*?)\n```/g);
for (const match of codeMatches) {
examples.push(match[1].trim());
}
return examples;
}
/**
* Extract file context around the comment
*/
function extractFileContext(diffHunk, path) {
if (!diffHunk)
return '';
// Clean up the diff hunk to provide readable context
const lines = diffHunk.split('\n');
const contextLines = lines.filter(line => !line.startsWith('@@') && line.trim().length > 0).map(line => {
// Remove diff markers but keep indentation
if (line.startsWith('+') || line.startsWith('-')) {
return line.substring(1);
}
return line.startsWith(' ') ? line.substring(1) : line;
});
return `File: ${path}\n\n${contextLines.join('\n')}`;
}
/**
* Find related comments based on file path and proximity
*/
function findRelatedComments(currentComment, allComments) {
const related = [];
for (const comment of allComments) {
if (comment.id === currentComment.id)
continue;
// Same file
if (comment.path === currentComment.path) {
// Check if lines are close (within 10 lines)
const currentLine = currentComment.original_line || 0;
const commentLine = comment.original_line || 0;
if (Math.abs(currentLine - commentLine) <= 10) {
related.push(comment.id);
}
}
}
return related;
}
/**
* Get detailed information about a specific CodeRabbit comment
*/
export async function getCommentDetails(input, githubClient) {
// Validate input
const validatedInput = GetCommentDetailsSchema.parse(input);
const { owner, repo, commentId } = validatedInput;
try {
// Use the GitHub client's helper method to find the comment across recent PRs
const result = await githubClient.findCommentInRecentPRs(owner, repo, commentId);
if (!result) {
throw new Error(`Comment with ID ${commentId} not found in recent pull requests`);
}
const { comment: targetComment, pr } = result;
// Get all comments from this PR for context
const allPRComments = await githubClient.getPullRequestComments(owner, repo, pr.number);
// Verify this is a CodeRabbit comment
if (targetComment.user.login !== 'coderabbitai[bot]') {
throw new Error(`Comment ${commentId} is not from CodeRabbit AI`);
}
const body = targetComment.body;
// Parse severity and category
let severity = 'info';
let category = 'General';
if (body.includes('โ ๏ธ Potential issue')) {
severity = 'warning';
category = 'Potential Issue';
}
else if (body.includes('๐ ๏ธ Refactor suggestion')) {
severity = 'suggestion';
category = 'Refactor Suggestion';
}
else if (body.includes('๐งน Nitpick')) {
severity = 'info';
category = 'Nitpick';
}
else if (body.includes('๐ Security')) {
severity = 'error';
category = 'Security';
}
// Extract AI prompt
const aiPromptMatch = body.match(/๐ค Prompt for AI Agents[\s\S]*?```\n([\s\S]*?)\n```/);
const aiPrompt = aiPromptMatch ? aiPromptMatch[1].trim() : undefined;
// Extract committable suggestion
const committableMatch = body.match(/๐ Committable suggestion[\s\S]*?```suggestion\n([\s\S]*?)\n```/);
const committableSuggestion = committableMatch ? committableMatch[1].trim() : undefined;
// Extract description
const descLines = body.split('\n').filter(line => line.trim() &&
!line.includes('_โ ๏ธ') &&
!line.includes('_๐ ๏ธ') &&
!line.includes('๐ค Prompt') &&
!line.includes('๐ Committable') &&
!line.trim().startsWith('<') &&
!line.trim().startsWith('```'));
const description = descLines.slice(0, 3).join(' ').trim() || 'CodeRabbit suggestion';
// Parse line range
let lineRange = { start: 1, end: 1 };
if (targetComment.original_start_line && targetComment.original_line) {
lineRange = {
start: targetComment.original_start_line,
end: targetComment.original_line
};
}
else if (targetComment.original_line) {
lineRange = {
start: targetComment.original_line,
end: targetComment.original_line
};
}
// Check if resolved
const isResolved = body.includes('โ
Addressed') || body.includes('โ
Fixed') || body.includes('โ
Resolved');
// Extract additional details
const fileContext = extractFileContext(targetComment.diff_hunk, targetComment.path);
const relatedComments = findRelatedComments(targetComment, allPRComments);
const fixExamples = extractFixExamples(body);
const commentDetails = {
id: targetComment.id,
body: targetComment.body,
path: targetComment.path,
line_range: lineRange,
side: targetComment.side,
severity,
category,
description,
ai_prompt: aiPrompt,
committable_suggestion: committableSuggestion,
html_url: targetComment.html_url,
diff_hunk: targetComment.diff_hunk,
created_at: targetComment.created_at,
updated_at: targetComment.updated_at,
is_resolved: isResolved,
file_context: fileContext,
related_comments: relatedComments,
fix_examples: fixExamples
};
return commentDetails;
}
catch (error) {
throw new Error(`Failed to get comment details: ${error instanceof Error ? error.message : String(error)}`);
}
}
//# sourceMappingURL=get-comment-details.js.map