Skip to main content
Glama

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
NameRequiredDescriptionDefault
commentDetailNoDetail level for comment text. 'SNIPPET' (default, 200 chars) or 'FULL' (entire text).SNIPPET
maxRepliesNoMax replies per comment to return (0-5, default: 0). Use 0 for best performance.
maxResultsNoMax number of top-level comments to return (1-100, default: 20).
orderNoSort order for comments. Use 'relevance' (default) for most helpful or 'time' for newest.relevance
videoIdYesThe 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)." ), });
  • 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 ); }

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/kirbah/mcp-youtube'

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