Skip to main content
Glama

get_video_info

Get YouTube video metadata and transcript by supplying a video ID. Extract captions, timestamps, and video information via web scraping without API keys.

Instructions

Get information and transcript from a YouTube video

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
video_idYesThe YouTube video ID (11 characters)

Implementation Reference

  • Core implementation: fetchVideoInfo function that fetches YouTube video metadata and caption tracks. Validates video ID, checks cache, fetches HTML, extracts player response via InnerTube API or HTML parsing, extracts metadata and caption tracks, and caches the result.
    export async function fetchVideoInfo(videoId: string): Promise<{ metadata: VideoMetadata; captionTracks: CaptionTrack[] }> {
      // Validate video ID format with Zod
      try {
        VideoIdSchema.parse(videoId);
      } catch (error) {
        if (error instanceof z.ZodError) {
          throw new YouTubeError(error.errors[0].message, 'INVALID_ID');
        }
        throw new YouTubeError('Invalid YouTube video ID format', 'INVALID_ID');
      }
    
      // Check cache first
      const cached = videoInfoCache.get(videoId);
      if (cached) {
        logger.debug(`Cache hit for video ${videoId}`);
        return cached;
      }
      
      logger.info(`Fetching video info for ${videoId}`);
    
      const url = `https://www.youtube.com/watch?v=${videoId}`;
      
      try {
        // Fetch with retry logic
        const { html, response } = await withRetry(async () => {
          const response = await fetch(url, {
            headers: {
              'User-Agent': USER_AGENT,
              'Accept-Language': 'en-US,en;q=0.9',
              'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
            }
          });
    
          if (!response.ok) {
            if (response.status === 404) {
              throw new YouTubeError('Video not found', 'NOT_FOUND');
            }
            const error = new Error(`HTTP error ${response.status}`) as any;
            error.status = response.status;
            throw error;
          }
    
          const html = await response.text();
          return { html, response };
        }, { maxRetries: 3, baseDelay: 1000 });
        
        // First try to extract InnerTube API key and use InnerTube API
        const apiKey = extractInnerTubeApiKey(html);
        let playerResponse: YouTubePlayerResponse | null = null;
        
        if (apiKey) {
          logger.debug('Attempting to fetch via InnerTube API');
          playerResponse = await fetchViaInnerTube(videoId, apiKey);
        }
        
        // Fall back to extracting from HTML if InnerTube didn't work
        if (!playerResponse) {
          logger.debug('Falling back to HTML extraction');
          playerResponse = extractPlayerResponse(html);
        }
    
        if (!playerResponse) {
          throw new YouTubeError('Could not extract video data from page', 'PARSE_ERROR');
        }
    
        // Check if video is available
        if (playerResponse.playabilityStatus?.status !== 'OK') {
          const status = playerResponse.playabilityStatus?.status;
          const reason = playerResponse.playabilityStatus?.reason || 'Video is not available';
          
          // Determine specific error type
          let errorCode: YouTubeErrorCode = 'PRIVATE';
          if (reason.toLowerCase().includes('age')) {
            errorCode = 'AGE_RESTRICTED';
          } else if (reason.toLowerCase().includes('region') || reason.toLowerCase().includes('country')) {
            errorCode = 'REGION_BLOCKED';
          }
          
          throw new YouTubeError(reason, errorCode);
        }
    
        const metadata = extractMetadata(playerResponse);
        const captionTracks = extractCaptionTracks(playerResponse);
    
        const result = { metadata, captionTracks };
        
        // Cache the result
        videoInfoCache.set(videoId, result);
        logger.debug(`Cached video info for ${videoId}`);
        
        return result;
      } catch (error) {
        logger.error(`Failed to fetch video ${videoId}:`, error);
        if (error instanceof YouTubeError) {
          throw error;
        }
        throw new YouTubeError(
          `Failed to fetch video: ${error instanceof Error ? error.message : 'Unknown error'}`,
          'NETWORK_ERROR',
          error
        );
      }
    }
  • Tool execution handler for 'get_video_info'. Validates arguments via Zod schema, calls fetchVideoInfo and fetchTranscript, formats the response as JSON.
    // Handle tool execution
    server.setRequestHandler(CallToolRequestSchema, async (request) => {
      if (request.params.name !== 'get_video_info') {
        logger.warn(`Unknown tool requested: ${request.params.name}`);
        throw new McpError(
          ErrorCode.MethodNotFound,
          `Unknown tool: ${request.params.name}`
        );
      }
      
      logger.debug(`Tool called: ${request.params.name}`);
    
      // Validate arguments with Zod
      let args: z.infer<typeof GetVideoInfoArgsSchema>;
      try {
        args = GetVideoInfoArgsSchema.parse(request.params.arguments);
      } catch (error) {
        if (error instanceof z.ZodError) {
          throw new McpError(
            ErrorCode.InvalidParams,
            `Invalid parameters: ${error.errors.map(e => e.message).join(', ')}`
          );
        }
        throw new McpError(
          ErrorCode.InvalidParams,
          'Invalid parameters'
        );
      }
    
      try {
        // Fetch video metadata and caption tracks
        const { metadata, captionTracks } = await fetchVideoInfo(args.video_id);
        
        let transcript = null;
        let transcriptError: string | undefined;
    
        // Try to fetch transcript if available
        if (captionTracks.length > 0) {
          // Prefer manually created captions over auto-generated
          const manualTrack = captionTracks.find(track => track.kind !== 'asr');
          const trackToUse = manualTrack || captionTracks[0];
          
          try {
            transcript = await fetchTranscript(trackToUse);
          } catch (error) {
            if (error instanceof YouTubeError) {
              transcriptError = error.message;
            } else {
              transcriptError = 'Failed to fetch transcript';
            }
          }
        } else {
          transcriptError = 'No transcript available for this video';
        }
    
        const response: VideoInfoResponse = {
          metadata,
          transcript,
          ...(transcriptError && !transcript ? { error: transcriptError } : {})
        };
    
        return {
          content: [
            {
              type: 'text',
              text: JSON.stringify(response, null, 2)
            }
          ]
        };
      } catch (error) {
        if (error instanceof YouTubeError) {
          throw new McpError(
            ErrorCode.InternalError,
            error.message,
            {
              code: error.code,
              details: error.details
            }
          );
        }
        
        throw new McpError(
          ErrorCode.InternalError,
          `Unexpected error: ${error instanceof Error ? error.message : 'Unknown error'}`
        );
      }
    });
  • GetVideoInfoArgsSchema: Zod schema validating the video_id argument (exactly 11 chars, alphanumeric plus underscore/dash).
    export const GetVideoInfoArgsSchema = z.object({
      video_id: VideoIdSchema
    });
  • src/index.ts:30-50 (registration)
    Tool registration: 'get_video_info' is registered with name, description, and inputSchema listing required 'video_id' parameter.
    server.setRequestHandler(ListToolsRequestSchema, async () => {
      return {
        tools: [
          {
            name: 'get_video_info',
            description: 'Get information and transcript from a YouTube video',
            inputSchema: {
              type: 'object',
              properties: {
                video_id: {
                  type: 'string',
                  description: 'The YouTube video ID (11 characters)',
                  pattern: '^[a-zA-Z0-9_-]{11}$'
                }
              },
              required: ['video_id']
            }
          }
        ]
      };
    });
  • VideoInfoResponse type: Defines the response shape returned by the 'get_video_info' tool, with metadata, transcript, and optional error fields.
    export interface VideoInfoResponse {
      metadata: VideoMetadata;
      transcript: Transcript | null;
      error?: string;
    }
Behavior2/5

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

No annotations exist, and the description does not disclose behavioral traits such as read-only nature, network dependency, or failure handling (e.g., invalid video ID). This lack of transparency could lead to unexpected behavior for the AI agent.

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?

The description is a single, concise sentence with no extraneous information. It efficiently communicates the tool's purpose and is well-structured for quick comprehension.

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

Completeness4/5

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

For a simple tool with one parameter and no output schema, the description is fairly complete. It mentions 'information and transcript', hinting at the return format. However, a bit more detail on what information is returned would enhance completeness.

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

Parameters3/5

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

The input schema already provides a description and pattern for the video_id parameter, achieving 100% coverage. The tool description adds no extra meaning beyond what the schema provides, which is baseline acceptable.

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

Purpose4/5

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

The description clearly states that the tool retrieves information and transcript from a YouTube video. While 'information' is somewhat vague, it is sufficient given there are no sibling tools to differentiate. Adding specifics like 'title, description, statistics' would improve clarity.

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

Usage Guidelines3/5

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

No usage guidelines are provided, but since there are no siblings, the need is less critical. The description implies input of a video ID, and the schema enforces the format. The agent can infer usage from the tool name and description.

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/Limecooler/yt-video-info'

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