getVideoComments
Retrieve YouTube video comments with configurable order, count, and reply depth. Choose relevance or time sorting, snippet or full text, and limit replies to reduce API costs.
Instructions
Retrieves comments for a video. Warning: Setting maxReplies > 0 significantly increases API cost. Only set maxReplies if you specifically need to analyze conversation threads. Defaults to top-level comments only.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| videoId | Yes | The 11-character ID of the YouTube video. | |
| maxResults | No | Max number of top-level comments to return (1-100, default: 20). | |
| order | No | Sort order for comments. Use 'relevance' (default) for most helpful or 'time' for newest. | relevance |
| maxReplies | No | Max replies per comment to return (0-5, default: 0). Use 0 for best performance. | |
| commentDetail | No | Detail level for comment text. 'SNIPPET' (default, 200 chars) or 'FULL' (entire text). | SNIPPET |
Implementation Reference
- The GetVideoCommentsTool class that executes the tool logic. Its executeImpl method calls youtubeService.getVideoComments(params) and formats the result.
export class GetVideoCommentsTool extends BaseTool< typeof getVideoCommentsSchema > { name = "getVideoComments"; description = "Retrieves comments for a video. Warning: Setting `maxReplies` > 0 significantly increases API cost. Only set `maxReplies` if you specifically need to analyze conversation threads. Defaults to top-level comments only."; schema = getVideoCommentsSchema; protected async executeImpl( params: z.infer<typeof getVideoCommentsSchema> ): Promise<CallToolResult> { // Call the service with the validated parameters const comments = await this.container.youtubeService.getVideoComments(params); // Use the standard success formatter return formatSuccess(comments); } } - The youtubeService.getVideoComments method that implements the core business logic: fetches comment threads from YouTube API, optionally fetches replies per comment, and maps results to LeanComment objects. Handles the 'commentsDisabled' error case and uses caching.
async getVideoComments( options: GetVideoCommentsParams ): Promise<LeanComment[]> { const cacheKey = this.cacheService.createOperationKey( "getVideoComments", options ); const operation = async (): Promise<LeanComment[]> => { try { const { videoId, maxResults, order, maxReplies = 0, commentDetail, } = options; const commentThreadsResponse = await this.trackCost( () => this.youtube.commentThreads.list({ part: ["snippet"], videoId: videoId, maxResults: maxResults, order: order, }), API_COSTS["commentThreads.list"] ); const topLevelComments = commentThreadsResponse.data.items || []; let allReplies: youtube_v3.Schema$Comment[][] = []; if (maxReplies > 0 && topLevelComments.length > 0) { const replyPromises = topLevelComments.map((commentThread) => { const parentId = commentThread.id; if (!parentId) return Promise.resolve([]); return this.trackCost( () => this.youtube.comments.list({ part: ["snippet"], parentId: parentId, maxResults: maxReplies, }), API_COSTS["comments.list"] ).then((res) => res.data.items || []); }); allReplies = await Promise.all(replyPromises); } return topLevelComments.map((commentThread, index) => { const topLevelSnippet = commentThread.snippet?.topLevelComment?.snippet; const replies = allReplies[index] || []; const leanReplies: LeanReply[] = replies.map((reply) => { const replySnippet = reply.snippet; return { replyId: reply.id ?? "", author: replySnippet?.authorDisplayName ?? "", text: commentDetail === "SNIPPET" ? replySnippet?.textDisplay?.substring(0, 200) || "" : replySnippet?.textDisplay || "", publishedAt: replySnippet?.publishedAt ?? "", likeCount: replySnippet?.likeCount || 0, }; }); return { commentId: commentThread.id ?? "", author: topLevelSnippet?.authorDisplayName ?? "", text: commentDetail === "SNIPPET" ? topLevelSnippet?.textDisplay?.substring(0, 200) || "" : topLevelSnippet?.textDisplay || "", publishedAt: topLevelSnippet?.publishedAt ?? "", likeCount: topLevelSnippet?.likeCount || 0, replies: leanReplies, }; }); } catch (error: unknown) { if ( error && typeof error === "object" && "response" in error && error.response && typeof error.response === "object" && "status" in error.response && error.response.status === 403 ) { // Define a type for the expected error structure type YouTubeApiError = { error: { errors: [{ reason: string }]; }; }; // You might need to adjust the type assertion based on your error structure const errorData = (error.response as { data?: YouTubeApiError }).data; if (errorData?.error?.errors?.[0]?.reason === "commentsDisabled") { return []; } } if (error instanceof AppError) throw error; throw new YouTubeApiError( `YouTube API call for getVideoComments failed for videoId: ${options.videoId}`, error ); } }; return this.cacheService.getOrSet( cacheKey, operation, CACHE_TTLS.DYNAMIC, CACHE_COLLECTIONS.VIDEO_COMMENTS, options ); } - Zod schema (getVideoCommentsSchema) defining input validation: videoId, maxResults (1-100, default 20), order (relevance/time), maxReplies (0-5, default 0), commentDetail (SNIPPET/FULL).
export const getVideoCommentsSchema = z.object({ videoId: z .string() .min(1) .describe("The 11-character ID of the YouTube video."), maxResults: z .number() .min(1) .max(100) .default(20) .describe( "Max number of top-level comments to return (1-100, default: 20)." ), order: z .enum(["relevance", "time"]) .default("relevance") .describe( "Sort order for comments. Use 'relevance' (default) for most helpful or 'time' for newest." ), maxReplies: z .number() .min(0) .max(5) .default(0) .describe( "Max replies per comment to return (0-5, default: 0). Use 0 for best performance." ), commentDetail: z .enum(["SNIPPET", "FULL"]) .default("SNIPPET") .describe( "Detail level for comment text. 'SNIPPET' (default, 200 chars) or 'FULL' (entire text)." ), }); - src/types/tools.ts:61-67 (schema)TypeScript interface GetVideoCommentsParams used as the parameter type for the service method.
export interface GetVideoCommentsParams { videoId: string; maxResults?: number; order?: "relevance" | "time"; maxReplies?: number; commentDetail?: "SNIPPET" | "FULL"; } - src/tools/index.ts:11-11 (registration)Import of GetVideoCommentsTool class in the tools registry.
import { GetVideoCommentsTool } from "./video/getVideoComments.js"; - src/tools/index.ts:32-32 (registration)GetVideoCommentsTool added to the TOOL_CLASSES array for auto-registration.
GetVideoCommentsTool, - src/tools/index.ts:39-89 (registration)The registerTools function that iterates over TOOL_CLASSES and registers each tool with the MCP server via server.registerTool().
export function registerTools(server: McpServer, container: IServiceContainer) { const hasYoutubeKey = !!process.env.YOUTUBE_API_KEY; const toolsToRegister: ToolConstructor[] = []; if (hasYoutubeKey) { // Register all standard YouTube tools toolsToRegister.push(...(TOOL_CLASSES as ToolConstructor[])); // Register analytics tools if connection string is present if (process.env.MDB_MCP_CONNECTION_STRING) { toolsToRegister.push(FindConsistentOutlierChannelsTool); } } else { // Only register tools that don't require the API key toolsToRegister.push(GetTranscriptsTool); } for (const ToolClass of toolsToRegister) { // Instantiate with DI container const toolInstance = new ToolClass(container); const humanReadableTitle = toolInstance.name .replace(/([A-Z])/g, " $1") .replace(/^./, (str: string) => str.toUpperCase()); // Register with MCP Server server.registerTool( toolInstance.name, { description: toolInstance.description, inputSchema: toolInstance.schema, annotations: { title: humanReadableTitle, readOnlyHint: true, idempotentHint: true, }, }, // eslint-disable-next-line @typescript-eslint/no-unsafe-argument (async ( args: z.infer<typeof toolInstance.schema> ): Promise<CallToolResult> => { try { return await toolInstance.execute(args); } catch (err) { return formatError(err); } // eslint-disable-next-line @typescript-eslint/no-explicit-any }) as any ); } } - src/types/youtube.ts:151-158 (helper)LeanComment type definition - the return type of getVideoComments.
export interface LeanComment { commentId: string; author: string; text: string; publishedAt: string; likeCount: number; replies: LeanReply[]; } - src/types/youtube.ts:143-149 (helper)LeanReply type definition - nested within LeanComment.
export interface LeanReply { replyId: string; author: string; text: string; publishedAt: string; likeCount: number; }