Skip to main content
Glama
radireddy

GitHub MCP Server

by radireddy

github.getUserComments

Fetch and analyze all GitHub comments by a specific user in a repository within a time range, combining review and issue comments into a unified format for activity tracking and feedback analysis.

Instructions

Fetch all comments (review comments and issue comments) added by a user for a given repository within a time duration. Combines PR review comments (inline comments from PR reviews) and PR issue comments (general comments on PRs), normalizes them into a unified format, and deduplicates by comment ID. Filters by comment.createdAt and author. Automatically filters out auto-generated comments and comments on auto-created PRs. Use this tool to get a complete view of all user comments in a repository.

Example use cases:

  • Get all comments by a user in a repository for analysis

  • Track comment activity and engagement

  • Analyze comment patterns and types

  • Extract all user feedback for sentiment analysis

Returns: Array of comment objects with id, body, createdAt, author, prId, prNumber, prTitle, prRepo, commentType (review/issue), filePath, lineNumber, reviewId

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
usernameYesGitHub username (case-insensitive, @ prefix optional). Examples: "octocat", "@octocat"
repoYesRepository in owner/repo format. Required - only comments for PRs in this repository will be returned. Example: "owner/repo"
fromYesStart timestamp in ISO 8601 format. Example: "2024-01-01T00:00:00Z"
toYesEnd timestamp in ISO 8601 format. Example: "2024-12-31T23:59:59Z"

Implementation Reference

  • The core handler method in GitHubTools class that implements github.getUserComments tool. Fetches both PR review comments (using efficient contributionsCollection) and PR issue comments, applies filters (auto-generated content, repositories, time range), normalizes to UserComment format, deduplicates by ID, and sorts by creation date.
    async getUserComments( username: string, repos: string[], from?: string, to?: string ): Promise<{ comments: UserComment[] }> { const { normalizedUsername, normalizedRepos, from: validatedFrom, to: validatedTo } = this.validateCommonParameters(username, repos, from, to); const fromTime = new Date(validatedFrom).getTime(); const toTime = new Date(validatedTo).getTime(); const allComments: UserComment[] = []; // OPTIMIZED APPROACH: Use contributionsCollection for review comments (much more efficient) // This only fetches PRs where the user actually commented, not all PRs in the repo console.error(`[getUserComments] Step 1/2: Fetching PR review comments using contributionsCollection (efficient)...`); const reviewCommentsFromContributions = await fetchAllPages( async (cursor: string | null) => { // Check rate limit before making call try { const response = await this.client.query(QUERIES.ReviewComments, { username: normalizedUsername, from: validateTimestamp(validatedFrom), to: validateTimestamp(validatedTo), after: cursor, }); // Check rate limit after call const rateLimit = extractRateLimit(response.headers || {}); if (rateLimit && rateLimit.remaining < 50) { console.error(`[getUserComments] Rate limit warning: ${rateLimit.remaining} requests remaining`); } return response.data; } catch (error: any) { if (error.message?.includes('rate limit')) { console.error(`[getUserComments] Rate limit exceeded, stopping to avoid further errors`); throw error; } throw error; } }, (data: any) => { const contributions = data.user?.contributionsCollection ?.pullRequestReviewContributions?.nodes || []; const comments: UserComment[] = []; for (const contribution of contributions) { const review = contribution.pullRequestReview; const pr = review.pullRequest; // Filter by repository (must match one of the provided repos) const prRepo = pr.repository?.nameWithOwner?.toLowerCase(); if (!prRepo || !normalizedRepos.includes(prRepo)) { continue; } // Filter out auto-created PRs if (this.isAutoCreatedPR(pr.title)) { continue; } // Extract review comments const reviewComments = mapReviewComments(contribution); for (const comment of reviewComments) { // Filter by time (contributionsCollection already filters, but double-check) const commentTime = new Date(comment.timestamp).getTime(); if (commentTime >= fromTime && commentTime <= toTime) { // Use shared filter method if (!this.filterReviewComment(comment, normalizedRepos)) { continue; } // Convert ReviewComment to UserComment comments.push({ id: comment.id, body: comment.body, createdAt: comment.timestamp, author: normalizedUsername, prId: comment.prId, prNumber: comment.prNumber, prTitle: comment.prTitle, prRepo: comment.prRepo, commentType: 'review', filePath: comment.filePath, lineNumber: comment.lineNumber, reviewId: comment.reviewId, }); } } } return comments; }, (data: any) => { const pageInfo = data.user?.contributionsCollection ?.pullRequestReviewContributions?.pageInfo || {}; return extractPageInfo(pageInfo); } ); console.error(`[getUserComments] Found ${reviewCommentsFromContributions.length} review comments from contributionsCollection`); allComments.push(...reviewCommentsFromContributions); // Step 2: Fetch PR issue comments // For issue comments, we still need to fetch PRs, but we can optimize by: // 1. Using search to find PRs commented on by the user (if possible) // 2. Limiting the number of PRs processed // 3. Adding rate limit checks // For now, use a more limited approach: only fetch recent PRs or use search // Since GitHub search doesn't support commenter filter well, we'll use a hybrid: // Try to get issue comments from PRs we already know about (from review comments) const prIdsFromReviewComments = new Set( reviewCommentsFromContributions.map(c => c.prId) ); console.error(`[getUserComments] Step 2/2: Fetching PR issue comments from ${prIdsFromReviewComments.size} known PRs...`); if (prIdsFromReviewComments.size > 0) { // Fetch issue comments only from PRs we know the user interacted with const prIdsArray = Array.from(prIdsFromReviewComments); const batchSize = 100; for (let i = 0; i < prIdsArray.length; i += batchSize) { const batch = prIdsArray.slice(i, i + batchSize); try { const response = await this.client.query(QUERIES.PRIssueCommentsByPRIds, { prIds: batch, }); const prs = response.data.nodes || []; for (const pr of prs) { if (!pr || !pr.id) continue; // Filter out auto-created PRs if (this.isAutoCreatedPR(pr.title)) { continue; } const issueComments = pr.comments?.nodes || []; for (const comment of issueComments) { if (!comment) continue; const commentAuthor = comment.author?.login?.toLowerCase(); const commentTime = new Date(comment.createdAt).getTime(); if ( commentAuthor === normalizedUsername && commentTime >= fromTime && commentTime <= toTime ) { // Use shared filter method (checking auto-generated comments) if (this.isAutoGeneratedComment(comment.body)) { continue; } allComments.push(mapIssueCommentToUserComment(comment, pr)); } } } } catch (error: any) { console.error(`[getUserComments] Error fetching issue comments for batch: ${error.message}`); // Continue with other batches } } } else { // Fallback: If no review comments, we still need to get issue comments // But limit to a reasonable number of PRs to avoid rate limits const MAX_PRS_TO_PROCESS = 500; // Limit to prevent rate limit issues console.error(`[getUserComments] No review comments found, fetching issue comments for ${normalizedRepos.length} repo(s) (limited to ${MAX_PRS_TO_PROCESS} PRs per repo)...`); // Process each repo separately for (const repo of normalizedRepos) { const { owner, name: repoName } = this.validateAndParseRepo(repo); let pageCount = 0; let totalPRsProcessed = 0; const issueComments = await fetchAllPages( async (cursor: string | null) => { // Check if we've processed too many PRs if (totalPRsProcessed >= MAX_PRS_TO_PROCESS) { console.error(`[getUserComments] Reached limit of ${MAX_PRS_TO_PROCESS} PRs for ${repo}, stopping to avoid rate limits`); return { repository: { pullRequests: { nodes: [], pageInfo: { hasNextPage: false, endCursor: null } } } }; } pageCount++; if (pageCount % 10 === 0) { console.error(`[getUserComments] Processing page ${pageCount} of PRs (issue comments) for ${repo}...`); } const response = await this.client.query(QUERIES.PRIssueCommentsByRepo, { owner, repo: repoName, cursor, }); return response.data; }, (data: any) => { const prs = data.repository?.pullRequests?.nodes || []; totalPRsProcessed += prs.length; const comments: UserComment[] = []; for (const pr of prs) { if (!pr || !pr.id) continue; // Filter out auto-created PRs if (this.isAutoCreatedPR(pr.title)) { continue; } const issueComments = pr.comments?.nodes || []; for (const comment of issueComments) { if (!comment) continue; const commentAuthor = comment.author?.login?.toLowerCase(); const commentTime = new Date(comment.createdAt).getTime(); if ( commentAuthor === normalizedUsername && commentTime >= fromTime && commentTime <= toTime ) { // Filter out auto-generated comments if (this.isAutoGeneratedComment(comment.body)) { continue; } comments.push(mapIssueCommentToUserComment(comment, pr)); } } } return comments; }, (data: any) => { const pageInfo = data.repository?.pullRequests?.pageInfo || {}; // Stop if we've processed too many PRs if (totalPRsProcessed >= MAX_PRS_TO_PROCESS) { return { hasNextPage: false, endCursor: null }; } return extractPageInfo(pageInfo); } ); console.error(`[getUserComments] Found ${issueComments.length} issue comments for ${repo} after processing ${totalPRsProcessed} PRs`); allComments.push(...issueComments); } } console.error(`[getUserComments] Total comments before deduplication: ${allComments.length}`); // Step 3: Deduplicate comments (same comment ID might appear in both) const commentMap = new Map<string, UserComment>(); for (const comment of allComments) { if (!commentMap.has(comment.id)) { commentMap.set(comment.id, comment); } } // Sort by createdAt const uniqueComments = Array.from(commentMap.values()).sort((a, b) => { return new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime(); }); console.error(`[getUserComments] Completed: ${uniqueComments.length} unique comments found`); return { comments: uniqueComments }; }
  • MCP server request handler registration for 'github.getUserComments' tool. Dispatches to GitHubTools.getUserComments and formats response as MCP content.
    case 'github.getUserComments': { const result = await tools.getUserComments( args.username as string, args.repos as string[], args.from as string | undefined, args.to as string | undefined ); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; }
  • Tool schema definition including input parameters (username, repo, from, to), description, and examples for the github.getUserComments tool.
    name: 'github.getUserComments', description: `Fetch all comments (review comments and issue comments) added by a user for a given repository within a time duration. Combines PR review comments (inline comments from PR reviews) and PR issue comments (general comments on PRs), normalizes them into a unified format, and deduplicates by comment ID. Filters by comment.createdAt and author. Automatically filters out auto-generated comments and comments on auto-created PRs. Use this tool to get a complete view of all user comments in a repository. Example use cases: - Get all comments by a user in a repository for analysis - Track comment activity and engagement - Analyze comment patterns and types - Extract all user feedback for sentiment analysis Returns: Array of comment objects with id, body, createdAt, author, prId, prNumber, prTitle, prRepo, commentType (review/issue), filePath, lineNumber, reviewId`, inputSchema: { type: 'object', properties: { username: { type: 'string', description: 'GitHub username (case-insensitive, @ prefix optional). Examples: "octocat", "@octocat"', examples: ['octocat', '@octocat'], }, repo: { type: 'string', description: 'Repository in owner/repo format. Required - only comments for PRs in this repository will be returned. Example: "owner/repo"', examples: ['owner/repo', 'radireddy/AiApps'], }, from: { type: 'string', description: 'Start timestamp in ISO 8601 format. Example: "2024-01-01T00:00:00Z"', examples: ['2024-01-01T00:00:00Z'], }, to: { type: 'string', description: 'End timestamp in ISO 8601 format. Example: "2024-12-31T23:59:59Z"', examples: ['2024-12-31T23:59:59Z'], }, }, required: ['username', 'repo', 'from', 'to'], }, },

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/radireddy/github-mcp'

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