Skip to main content
Glama

getChannelTopVideos

Read-onlyIdempotent

Retrieve a YouTube channel's most popular videos using its channel ID. Optionally set result count, include tags, or select description detail to manage token cost.

Instructions

Retrieves a channel's most popular videos. CRITICAL: Requires a valid channelId (starting with 'UC...'), NOT a channel handle or name. Use searchVideos (type='channel') first to find the channelId if you only have a name.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
channelIdYesYouTube channel ID to get top videos from
maxResultsNoMaximum number of top videos to return (1-500, default: 10)
includeTagsNoSpecify 'true' to include the video's 'tags' array in the response, which is useful for extracting niche keywords. The 'tags' are omitted by default to conserve tokens.
descriptionDetailNoControls video description detail to manage token cost. Options: 'NONE' (default, no text), 'SNIPPET' (a brief preview for broad scans), 'LONG' (a 500-char text for deep analysis of specific targets).NONE

Implementation Reference

  • Core handler: Calls YouTube Search API (order: viewCount) to find channel's top videos, then fetches details via Videos API. Caches results. Returns LeanChannelTopVideo[] with engagement ratios.
    async getChannelTopVideos(
      options: ChannelOptions
    ): Promise<LeanChannelTopVideo[]> {
      const cacheKey = this.cacheService.createOperationKey(
        "getChannelTopVideos",
        options
      );
    
      const operation = async (): Promise<LeanChannelTopVideo[]> => {
        try {
          const {
            channelId,
            maxResults = 10,
            includeTags = false,
            descriptionDetail = "NONE",
          } = options;
    
          const searchResults: youtube_v3.Schema$SearchResult[] = [];
          let nextPageToken: string | undefined = undefined;
          const targetResults = Math.min(maxResults, this.ABSOLUTE_MAX_RESULTS);
    
          while (searchResults.length < targetResults) {
            const response = await this.trackCost(
              () =>
                this.youtube.search.list({
                  part: ["id"],
                  channelId: channelId,
                  maxResults: Math.min(
                    this.MAX_RESULTS_PER_PAGE,
                    targetResults - searchResults.length
                  ),
                  order: "viewCount",
                  type: ["video"],
                  pageToken: nextPageToken,
                }),
              API_COSTS["search.list"]
            );
            const searchResponse: youtube_v3.Schema$SearchListResponse =
              response.data;
    
            if (!searchResponse.items?.length) {
              break;
            }
    
            searchResults.push(...searchResponse.items);
            nextPageToken = searchResponse.nextPageToken || undefined;
    
            if (!nextPageToken) {
              break;
            }
          }
    
          if (!searchResults.length) {
            throw new Error("No videos found.");
          }
    
          const videoIds = searchResults
            .map((item) => item.id?.videoId)
            .filter((id): id is string => id !== undefined);
    
          const videoDetails: youtube_v3.Schema$Video[] = [];
          const detailPromises = [];
          for (let i = 0; i < videoIds.length; i += this.MAX_RESULTS_PER_PAGE) {
            const batch = videoIds.slice(i, i + this.MAX_RESULTS_PER_PAGE);
            const promise = this.trackCost(
              () =>
                this.youtube.videos.list({
                  part: ["snippet", "statistics", "contentDetails"],
                  id: batch,
                }),
              API_COSTS["videos.list"]
            );
            detailPromises.push(promise);
          }
    
          const detailOutcomes = await Promise.allSettled(detailPromises);
          for (const outcome of detailOutcomes) {
            if (outcome.status === "fulfilled") {
              const response = outcome.value;
              if (response.data.items) {
                videoDetails.push(...response.data.items);
              }
            } else {
              if (outcome.reason instanceof AppError) throw outcome.reason;
              console.error(
                "A video details batch in getChannelTopVideos failed:",
                outcome.reason
              );
            }
          }
    
          // If we had IDs to fetch but got no details, it means all batches failed.
          if (videoDetails.length === 0 && videoIds.length > 0) {
            throw new Error("All batches failed to retrieve video details.");
          }
    
          return videoDetails.slice(0, targetResults).map((video) => {
            const viewCount = parseYouTubeNumber(video.statistics?.viewCount);
            const likeCount = parseYouTubeNumber(video.statistics?.likeCount);
            const commentCount = parseYouTubeNumber(
              video.statistics?.commentCount
            );
    
            const formattedDescription = formatDescription(
              video.snippet?.description,
              descriptionDetail
            );
    
            const baseVideo = {
              id: video.id,
              title: video.snippet?.title,
              publishedAt: video.snippet?.publishedAt,
              duration: video.contentDetails?.duration,
              viewCount: viewCount,
              likeCount: likeCount,
              commentCount: commentCount,
              likeToViewRatio: calculateLikeToViewRatio(viewCount, likeCount),
              commentToViewRatio: calculateCommentToViewRatio(
                viewCount,
                commentCount
              ),
              categoryId: video.snippet?.categoryId ?? null,
              defaultLanguage: video.snippet?.defaultLanguage ?? null,
            };
    
            const videoWithDescription =
              formattedDescription !== undefined
                ? { ...baseVideo, description: formattedDescription }
                : baseVideo;
    
            return includeTags
              ? { ...videoWithDescription, tags: video.snippet?.tags ?? [] }
              : videoWithDescription;
          });
        } catch (error) {
          if (error instanceof AppError) throw error;
          throw new YouTubeApiError(
            `YouTube API call for getChannelTopVideos failed for channelId: ${options.channelId}`,
            error
          );
        }
      };
    
      return this.cacheService.getOrSet(
        cacheKey,
        operation,
        CACHE_TTLS.SEMI_STATIC,
        CACHE_COLLECTIONS.CHANNEL_TOP_VIDEOS,
        options
      );
    }
  • Zod schema defining input parameters: channelId (required), maxResults (default 10), includeTags (default false), descriptionDetail (enum NONE/SNIPPET/LONG).
    export const getChannelTopVideosSchema = z.object({
      channelId: channelIdSchema.describe(
        "YouTube channel ID to get top videos from"
      ),
      maxResults: maxResultsSchema
        .optional()
        .default(10)
        .describe("Maximum number of top videos to return (1-500, default: 10)"),
      includeTags: z
        .boolean()
        .optional()
        .default(false)
        .describe(
          "Specify 'true' to include the video's 'tags' array in the response, which is useful for extracting niche keywords. The 'tags' are omitted by default to conserve tokens."
        ),
      descriptionDetail: z
        .enum(["NONE", "SNIPPET", "LONG"])
        .optional()
        .default("NONE")
        .describe(
          "Controls video description detail to manage token cost. Options: 'NONE' (default, no text), 'SNIPPET' (a brief preview for broad scans), 'LONG' (a 500-char text for deep analysis of specific targets)."
        ),
    });
  • Tool class registered in TOOL_CLASSES array for MCP server registration.
    GetChannelTopVideosTool,
  • Type definition for the returned video data structure (LeanChannelTopVideo).
    export interface LeanChannelTopVideo {
      id: string | null | undefined;
      title: string | null | undefined;
      description?: string | null | undefined;
      publishedAt: string | null | undefined;
      duration: string | null | undefined;
      viewCount: number | null | undefined;
      likeCount: number | null | undefined;
      commentCount: number | null | undefined;
      likeToViewRatio: number | null | undefined;
      commentToViewRatio: number | null | undefined;
      tags?: string[] | null | undefined;
      categoryId: string | null | undefined;
      defaultLanguage: string | null | undefined;
    }
  • ChannelOptions interface for the handler.s parameters.
    export interface ChannelOptions {
      channelId: string;
      maxResults?: number;
      includeTags?: boolean;
      descriptionDetail?: "NONE" | "SNIPPET" | "LONG";
    }
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint and idempotentHint. Description adds critical format requirement for channelId (UC...) and explains token optimization via includeTags and descriptionDetail. No contradictions.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two concise sentences with purpose and critical usage note, plus parameter explanations. Every sentence earns its place. Front-loaded with purpose.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

No output schema, so return values not explained. Lacks specification of the ranking metric for 'top videos' (e.g., most viewed, most liked). Otherwise covers inputs and token management well.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% with descriptions for all 4 parameters. Description reinforces the channelId format constraint and provides token conservation context for includeTags and descriptionDetail, adding value beyond schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description clearly states it retrieves a channel's most popular videos. It specifies the required input (channelId) and distinguishes from siblings like searchVideos and getTrendingVideos.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicitly says when to use: needs channelId starting with 'UC...'. It provides an alternative: use searchVideos if only have a name. Also explains descriptionDetail for token management.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

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