getVideoComments
Retrieve and analyze YouTube video comments with sorting options, result limits, and reply fetching to gather audience feedback and insights.
Instructions
Retrieves comments for a YouTube video. Allows sorting, limiting results, and fetching a small number of replies per comment.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| commentDetail | No | Detail level for comment text. 'SNIPPET' (default, 200 chars) or 'FULL' (entire text). | SNIPPET |
| maxReplies | No | Max replies per comment to return (0-5, default: 0). Use 0 for best performance. | |
| 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 |
| videoId | Yes | The 11-character ID of the YouTube video. |
Implementation Reference
- MCP tool handler: validates params with schema and calls YoutubeService.getVideoComments, formats success/error.// 4. Create the Placeholder Tool Handler (`getVideoCommentsHandler`) export async function getVideoCommentsHandler( params: z.infer<typeof getVideoCommentsSchema>, youtubeService: YoutubeService ): Promise<CallToolResult> { try { // First, validate the parameters (this is the standard pattern) const validatedParams = getVideoCommentsSchema.parse(params); // Call the service with the validated parameters const comments = await youtubeService.getVideoComments(validatedParams); // Use the standard success formatter return formatSuccess(comments); } catch (error: unknown) { // Use the standard error formatter return formatError(error); } }
- Input schema using Zod for validating tool parameters: videoId, maxResults, order, maxReplies, commentDetail.// 2. Define the Input Schema (`getVideoCommentsSchema`) 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/tools/index.ts:98-104 (registration)Tool registration in allTools(): adds getVideoComments with its config and a wrapper handler injecting youtubeService.config: getVideoCommentsConfig, handler: (params) => getVideoCommentsHandler( params as unknown as z.infer<typeof getVideoCommentsSchema>, youtubeService ), },
- YoutubeService.getVideoComments: core logic fetches comment threads and optional replies via YouTube API v3, processes into LeanComment[], handles caching, API costs, and special case for commentsDisabled.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 []; } } 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 ); }