Skip to main content
Glama

get_frame_at

Read-onlyIdempotent

Retrieve a single video frame at a specific timestamp from a video URL. Ideal for inspecting key moments in Loom or direct video files (mp4, webm).

Instructions

Extract a single video frame at a specific timestamp.

Useful for inspecting what's on screen at a particular moment. The AI reads the transcript, identifies a critical moment, and requests the exact frame at that timestamp.

Supports: Loom (loom.com/share/...) and direct video URLs (.mp4, .webm, .mov). Requires video download capability — direct URLs work best.

Args:

  • url: Video URL

  • timestamp: Time position (e.g., "1:23", "0:05", "01:23:45")

Returns: A single image of the video frame at the specified timestamp.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
urlYesVideo URL (Loom share link or direct mp4/webm URL)
timestampYesTimestamp to extract frame at (e.g., "1:23", "0:05", "01:23:45")
returnBase64NoReturn frame as base64 inline instead of file path

Implementation Reference

  • Zod schema for get_frame_at tool: validates url (string URL), timestamp (e.g., '1:23'), and optional returnBase64 boolean.
    const GetFrameAtSchema = z.object({
      url: z.string().url().describe('Video URL (Loom share link or direct mp4/webm URL)'),
      timestamp: z
        .string()
        .describe('Timestamp to extract frame at (e.g., "1:23", "0:05", "01:23:45")'),
      returnBase64: z
        .boolean()
        .default(false)
        .optional()
        .describe('Return frame as base64 inline instead of file path'),
    });
  • Full tool handler registration and execution logic for 'get_frame_at'. Registers the tool via server.addTool with name, description, schema, and execute function. The execute function attempts Strategy 1 (download video + ffmpeg extraction) and falls back to Strategy 2 (browser-based extraction).
    export function registerGetFrameAt(server: FastMCP): void {
      server.addTool({
        name: 'get_frame_at',
        description: `Extract a single video frame at a specific timestamp.
    
    Useful for inspecting what's on screen at a particular moment. The AI reads the transcript,
    identifies a critical moment, and requests the exact frame at that timestamp.
    
    Supports: Loom (loom.com/share/...) and direct video URLs (.mp4, .webm, .mov).
    Requires video download capability — direct URLs work best.
    
    Args:
      - url: Video URL
      - timestamp: Time position (e.g., "1:23", "0:05", "01:23:45")
    
    Returns: A single image of the video frame at the specified timestamp.`,
        parameters: GetFrameAtSchema,
        annotations: {
          title: 'Get Frame at Timestamp',
          readOnlyHint: true,
          destructiveHint: false,
          idempotentHint: true,
          openWorldHint: true,
        },
        execute: async (args, { reportProgress }) => {
          const progress = createProgressReporter(reportProgress);
          const { url, timestamp } = args;
    
          const adapter = getAdapter(url);
    
          await progress(0, 'Starting frame extraction...');
    
          const tempDir = await createTempDir();
    
          // Strategy 1: Download video + ffmpeg extraction
          if (adapter.capabilities.videoDownload) {
            const videoPath = await adapter.downloadVideo(url, tempDir);
    
            if (videoPath) {
              await progress(50, `Extracting frame at ${timestamp}...`);
    
              const frame = await extractFrameAt(videoPath, tempDir, timestamp);
              const optimizedPath = getTempFilePath(tempDir, `opt_frame_at.jpg`);
              await optimizeFrame(frame.filePath, optimizedPath);
    
              await progress(100, 'Frame extracted');
    
              return {
                content: [
                  { type: 'text' as const, text: `Frame extracted at ${timestamp}` },
                  await imageContent({ path: optimizedPath }),
                ],
              };
            }
          }
    
          // Strategy 2: Browser-based extraction (fallback)
          await progress(30, 'Extracting frame via browser fallback...');
          const seconds = parseTimestamp(timestamp);
          const browserFrames = await extractBrowserFrames(url, tempDir, {
            timestamps: [seconds],
          });
    
          if (browserFrames.length > 0) {
            await progress(100, 'Frame extracted');
            return {
              content: [
                {
                  type: 'text' as const,
                  text: `Frame extracted at ${timestamp} (via browser)`,
                },
                await imageContent({ path: browserFrames[0].filePath }),
              ],
            };
          }
    
          throw new UserError(
            'Failed to extract frame. Install yt-dlp or Chrome/Chromium for frame extraction.',
          );
        },
      });
    }
  • Imports: getAdapter for URL adapter resolution, extractFrameAt/parseTimestamp from frame-extractor for core ffmpeg logic, extractBrowserFrames as fallback, optimizeFrame for JPEG optimization, createProgressReporter, and createTempDir/getTempFilePath for temp file management.
    import type { FastMCP } from 'fastmcp';
    import { UserError, imageContent } from 'fastmcp';
    import { z } from 'zod';
    import { getAdapter } from '../adapters/adapter.interface.js';
    import { extractBrowserFrames } from '../processors/browser-frame-extractor.js';
    import { extractFrameAt, parseTimestamp } from '../processors/frame-extractor.js';
    import { optimizeFrame } from '../processors/image-optimizer.js';
    import { createProgressReporter } from '../utils/progress.js';
    import { createTempDir, getTempFilePath } from '../utils/temp-files.js';
  • Core helper function extractFrameAt: uses ffmpeg to seek to a specific timestamp (-ss), extract a single frame (-frames:v 1), and save as JPEG. Parses the timestamp string to seconds, then runs ffmpeg with timeout.
    export async function extractFrameAt(
      videoPath: string,
      outputDir: string,
      timestamp: string,
    ): Promise<IFrameResult> {
      const seconds = parseTimestamp(timestamp);
      const outputPath = ffmpegPath_(join(outputDir, `frame_at_${seconds}.jpg`));
    
      try {
        await execFile(
          ffmpegPath,
          ['-ss', String(seconds), '-i', videoPath, '-frames:v', '1', '-q:v', '2', outputPath, '-y'],
          { timeout: 30000 },
        );
      } catch (error: unknown) {
        const msg = error instanceof Error ? error.message : String(error);
        throw new Error(`Frame extraction at ${timestamp} failed: ${msg}`, { cause: error });
      }
    
      return {
        time: timestamp,
        filePath: outputPath,
        mimeType: 'image/jpeg',
      };
    }
  • Helper function parseTimestamp: converts timestamp strings like '1:23' or '01:23:45' into total seconds for ffmpeg seeking.
    export function parseTimestamp(ts: string): number {
      const parts = ts.split(':').map(Number);
      if (parts.some((p) => isNaN(p))) {
        throw new Error(`Invalid timestamp format: "${ts}"`);
      }
    
      if (parts.length === 3) {
        return parts[0] * 3600 + parts[1] * 60 + parts[2];
      }
      if (parts.length === 2) {
        return parts[0] * 60 + parts[1];
      }
      throw new Error(`Invalid timestamp format: "${ts}". Expected "M:SS" or "H:MM:SS".`);
    }
Behavior4/5

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

Annotations already indicate read-only and idempotent behavior. The description adds that it requires video download capability and that direct URLs work best. No contraction with annotations.

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 concise and well-structured: purpose, use case, supported sources, parameters, and return value. No unnecessary words.

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?

Given the simple tool, high schema coverage, and no output schema, the description covers essentials. It could explicitly mention default return format (file path) but is otherwise complete.

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%, but the description adds workflow context ('AI reads transcript, identifies critical moment') and clarifies the return type. This goes beyond mere parameter listing.

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?

The description clearly states the action: 'Extract a single video frame at a specific timestamp.' It specifies the resource (video frame) and distinguishes from sibling tools like get_frame_burst by focusing on a single frame extraction.

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

Usage Guidelines4/5

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

The description provides context for when to use this tool ('inspecting what's on screen at a particular moment') and lists supported sources (Loom, direct URLs). However, it does not explicitly contrast with alternatives or state when not to use it.

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/guimatheus92/mcp-video-analyzer'

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