GitHub PR Comments MCP Server
by shaileshahuja
Verified
import { Octokit } from '@octokit/rest';
import { GetPRCommentsParams, Comment, CommentReply } from './types.js';
export class GitHubService {
private octokit: Octokit;
constructor(token: string) {
this.octokit = new Octokit({
auth: token
});
}
async getPRComments({ owner, repo, pull_number }: GetPRCommentsParams): Promise<Comment[]> {
try {
// Fetch review comments on the pull request (line comments)
const { data: reviewComments } = await this.octokit.pulls.listReviewComments({
owner,
repo,
pull_number,
per_page: 100
});
// Group comments by their path and in_reply_to_id
const groupedComments = new Map<number, Comment>();
const replyMap = new Map<number, CommentReply[]>();
// Process all comments and separate top-level comments from replies
for (const comment of reviewComments) {
if (comment.in_reply_to_id) {
// This is a reply to another comment
const reply: CommentReply = {
id: comment.id,
body: comment.body,
user: {
login: comment.user?.login || 'unknown'
},
created_at: comment.created_at
};
if (!replyMap.has(comment.in_reply_to_id)) {
replyMap.set(comment.in_reply_to_id, []);
}
replyMap.get(comment.in_reply_to_id)?.push(reply);
} else {
// This is a top-level comment
const newComment: Comment = {
id: comment.id,
path: comment.path,
body: comment.body,
line: comment.line || undefined,
start_line: comment.start_line || undefined,
user: {
login: comment.user?.login || 'unknown'
},
created_at: comment.created_at,
replies: []
};
groupedComments.set(comment.id, newComment);
}
}
// Attach replies to their parent comments
for (const [commentId, replies] of replyMap.entries()) {
const parentComment = groupedComments.get(commentId);
if (parentComment) {
parentComment.replies = replies;
}
}
return Array.from(groupedComments.values());
} catch (error) {
console.error('Error fetching PR comments:', error);
throw error;
}
}
}