Skip to main content
Glama
bitscorp-mcp

MCP FFmpeg Video Processor

by bitscorp-mcp

resize-video

Resize videos to standard resolutions like 360p, 480p, 720p, or 1080p for compatibility across devices and platforms.

Instructions

Resize a video to one or more standard resolutions

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
videoPathYesPath to the video file to resize
resolutionsYesResolutions to convert the video to
outputDirNoOptional directory to save the output files (defaults to a temporary directory)

Implementation Reference

  • The core handler function that implements the resize-video tool logic. It resolves paths, checks existence and permissions, requests user permission, runs FFmpeg scale commands for each specified resolution, and returns formatted results or errors.
    async ({ videoPath, resolutions, outputDir }) => {
      try {
        // Resolve the absolute path
        const absVideoPath = path.resolve(videoPath);
    
        // Check if file exists
        try {
          await fs.access(absVideoPath);
        } catch (error) {
          return {
            isError: true,
            content: [{
              type: "text" as const,
              text: `Error: Video file not found at ${absVideoPath}`
            }]
          };
        }
    
        // Determine output directory
        let outputDirectory = outputDir ? path.resolve(outputDir) : await ensureDirectoriesExist();
    
        // Check if output directory exists and is writable
        try {
          await fs.access(outputDirectory, fs.constants.W_OK);
        } catch (error) {
          return {
            isError: true,
            content: [{
              type: "text" as const,
              text: `Error: Output directory ${outputDirectory} does not exist or is not writable`
            }]
          };
        }
    
        // Format command for permission request
        const resolutionsStr = resolutions.join(', ');
        const permissionMessage = `Resize video ${path.basename(absVideoPath)} to ${resolutionsStr}`;
    
        // Ask for permission
        const permitted = await askPermission(permissionMessage);
    
        if (!permitted) {
          return {
            isError: true,
            content: [{
              type: "text" as const,
              text: "Permission denied by user"
            }]
          };
        }
    
        // Get video filename without extension
        const videoFilename = path.basename(absVideoPath, path.extname(absVideoPath));
    
        // Define the type for our results
        type ResizeResult = {
          resolution: "360p" | "480p" | "720p" | "1080p";
          outputPath: string;
          success: boolean;
          error?: string;
        };
    
        // Process each resolution
        const results: ResizeResult[] = [];
    
        for (const resolution of resolutions) {
          const { width, height } = RESOLUTIONS[resolution as keyof typeof RESOLUTIONS];
          const outputFilename = `${videoFilename}_${resolution}${path.extname(absVideoPath)}`;
          const outputPath = path.join(outputDirectory, outputFilename);
    
          // Build FFmpeg command
          const command = `ffmpeg -i "${absVideoPath}" -vf "scale=${width}:${height}" -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k "${outputPath}"`;
    
          try {
            // Execute FFmpeg command
            const { stdout, stderr } = await execAsync(command);
    
            results.push({
              resolution,
              outputPath,
              success: true
            });
          } catch (error) {
            const errorMessage = error instanceof Error ? error.message : String(error);
    
            results.push({
              resolution,
              outputPath,
              success: false,
              error: errorMessage
            });
          }
        }
    
        // Format results
        const successCount = results.filter(r => r.success).length;
        const failCount = results.length - successCount;
    
        let resultText = `Processed ${results.length} resolutions (${successCount} successful, ${failCount} failed)\n\n`;
    
        results.forEach(result => {
          if (result.success) {
            resultText += `✅ ${result.resolution}: ${result.outputPath}\n`;
          } else {
            resultText += `❌ ${result.resolution}: Failed - ${result.error}\n`;
          }
        });
    
        return {
          content: [{
            type: "text" as const,
            text: resultText
          }]
        };
      } catch (error) {
        const errorMessage = error instanceof Error ? error.message : String(error);
        return {
          isError: true,
          content: [{
            type: "text" as const,
            text: `Error resizing video: ${errorMessage}`
          }]
        };
      }
    }
  • Zod input schema defining parameters for the resize-video tool: videoPath (required string), resolutions (array of specific enums), outputDir (optional string).
    {
      videoPath: z.string().describe("Path to the video file to resize"),
      resolutions: z.array(z.enum(["360p", "480p", "720p", "1080p"])).describe("Resolutions to convert the video to"),
      outputDir: z.string().optional().describe("Optional directory to save the output files (defaults to a temporary directory)")
    },
  • Registration of the resize-video tool on the MCP server using server.tool(), providing name, description, input schema, and handler function.
    server.tool(
      "resize-video",
      "Resize a video to one or more standard resolutions",
      {
        videoPath: z.string().describe("Path to the video file to resize"),
        resolutions: z.array(z.enum(["360p", "480p", "720p", "1080p"])).describe("Resolutions to convert the video to"),
        outputDir: z.string().optional().describe("Optional directory to save the output files (defaults to a temporary directory)")
      },
      async ({ videoPath, resolutions, outputDir }) => {
        try {
          // Resolve the absolute path
          const absVideoPath = path.resolve(videoPath);
    
          // Check if file exists
          try {
            await fs.access(absVideoPath);
          } catch (error) {
            return {
              isError: true,
              content: [{
                type: "text" as const,
                text: `Error: Video file not found at ${absVideoPath}`
              }]
            };
          }
    
          // Determine output directory
          let outputDirectory = outputDir ? path.resolve(outputDir) : await ensureDirectoriesExist();
    
          // Check if output directory exists and is writable
          try {
            await fs.access(outputDirectory, fs.constants.W_OK);
          } catch (error) {
            return {
              isError: true,
              content: [{
                type: "text" as const,
                text: `Error: Output directory ${outputDirectory} does not exist or is not writable`
              }]
            };
          }
    
          // Format command for permission request
          const resolutionsStr = resolutions.join(', ');
          const permissionMessage = `Resize video ${path.basename(absVideoPath)} to ${resolutionsStr}`;
    
          // Ask for permission
          const permitted = await askPermission(permissionMessage);
    
          if (!permitted) {
            return {
              isError: true,
              content: [{
                type: "text" as const,
                text: "Permission denied by user"
              }]
            };
          }
    
          // Get video filename without extension
          const videoFilename = path.basename(absVideoPath, path.extname(absVideoPath));
    
          // Define the type for our results
          type ResizeResult = {
            resolution: "360p" | "480p" | "720p" | "1080p";
            outputPath: string;
            success: boolean;
            error?: string;
          };
    
          // Process each resolution
          const results: ResizeResult[] = [];
    
          for (const resolution of resolutions) {
            const { width, height } = RESOLUTIONS[resolution as keyof typeof RESOLUTIONS];
            const outputFilename = `${videoFilename}_${resolution}${path.extname(absVideoPath)}`;
            const outputPath = path.join(outputDirectory, outputFilename);
    
            // Build FFmpeg command
            const command = `ffmpeg -i "${absVideoPath}" -vf "scale=${width}:${height}" -c:v libx264 -crf 23 -preset medium -c:a aac -b:a 128k "${outputPath}"`;
    
            try {
              // Execute FFmpeg command
              const { stdout, stderr } = await execAsync(command);
    
              results.push({
                resolution,
                outputPath,
                success: true
              });
            } catch (error) {
              const errorMessage = error instanceof Error ? error.message : String(error);
    
              results.push({
                resolution,
                outputPath,
                success: false,
                error: errorMessage
              });
            }
          }
    
          // Format results
          const successCount = results.filter(r => r.success).length;
          const failCount = results.length - successCount;
    
          let resultText = `Processed ${results.length} resolutions (${successCount} successful, ${failCount} failed)\n\n`;
    
          results.forEach(result => {
            if (result.success) {
              resultText += `✅ ${result.resolution}: ${result.outputPath}\n`;
            } else {
              resultText += `❌ ${result.resolution}: Failed - ${result.error}\n`;
            }
          });
    
          return {
            content: [{
              type: "text" as const,
              text: resultText
            }]
          };
        } catch (error) {
          const errorMessage = error instanceof Error ? error.message : String(error);
          return {
            isError: true,
            content: [{
              type: "text" as const,
              text: `Error resizing video: ${errorMessage}`
            }]
          };
        }
      }
    );
  • Helper constant mapping resolution names to width/height dimensions, used exclusively by the resize-video tool handler.
    const RESOLUTIONS = {
      "360p": { width: 640, height: 360 },
      "480p": { width: 854, height: 480 },
      "720p": { width: 1280, height: 720 },
      "1080p": { width: 1920, height: 1080 }
    };
Behavior2/5

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

No annotations are provided, so the description carries the full burden of behavioral disclosure. It states the action ('resize a video') but doesn't describe what happens (e.g., creates new files, overwrites existing ones, requires specific permissions, or has performance/rate limits). For a mutation tool with zero annotation coverage, this leaves significant gaps in understanding the tool's behavior.

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, efficient sentence that directly states the tool's purpose without unnecessary words. It is front-loaded with the core action ('resize a video') and adds clarifying detail ('to one or more standard resolutions'). Every part of the sentence earns its place by specifying scope and output.

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?

Given the tool's moderate complexity (a mutation operation with 3 parameters), no annotations, and no output schema, the description is minimally adequate. It covers the basic purpose but lacks details on behavioral traits, error handling, or output format. For a video processing tool, more context on file formats, processing time, or result location would be beneficial.

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?

Schema description coverage is 100%, so the schema already documents all three parameters thoroughly. The description adds minimal value beyond the schema by mentioning 'standard resolutions', which aligns with the enum in the schema but doesn't provide additional context like aspect ratio preservation or quality settings. Baseline 3 is appropriate when the schema does the heavy lifting.

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 the verb 'resize' and resource 'video', specifying it converts to 'standard resolutions'. It distinguishes from sibling tools like 'extract-audio' or 'get-video-info' by focusing on resolution transformation rather than extraction or metadata retrieval. However, it doesn't explicitly differentiate from all siblings (e.g., 'get-ffmpeg-version' is clearly different).

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

Usage Guidelines2/5

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

The description provides no guidance on when to use this tool versus alternatives. It doesn't mention prerequisites (e.g., video file format compatibility), when not to use it (e.g., for non-standard resolutions), or how it relates to sibling tools like 'extract-audio' for audio-only processing. Usage is implied only by 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/bitscorp-mcp/mcp-ffmpeg'

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