Skip to main content
Glama
clipsense

ClipSense MCP Server

by clipsense

analyze-video

Analyze mobile app bug videos to identify errors, crashes, and UI issues, then receive code fix suggestions for React Native, iOS, and Android apps.

Instructions

Use this tool to analyze video files on the user's computer that show mobile app bugs. Reads local video files (MP4, MOV, WebM, AVI, MKV, FLV, MPEG, 3GP, WMV) and provides AI-powered analysis to identify errors, crashes, UI issues, and suggests code fixes. Works with React Native, iOS (Swift/Objective-C), and Android (Kotlin/Java) apps. Use this when the user asks you to analyze, examine, or debug a video file showing app behavior.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
videoPathYesAbsolute path to the video file on the user's computer (e.g., /Users/username/Desktop/bug.mp4). Max 500MB, max 10 minutes.
questionNoSpecific question about the bug (optional). Example: 'Why does the button not respond when tapped?' If not provided, a general analysis will be performed.

Implementation Reference

  • Core implementation of video analysis: validates local video file (size, format, existence), uploads to presigned URL, starts API analysis job, polls for completion, formats result.
    async analyzeVideo(
      videoPath: string,
      question: string
    ): Promise<{ jobId: string; analysis: string }> {
      // Validate file exists and get stats
      let stats;
      try {
        stats = statSync(videoPath);
      } catch (error: any) {
        if (error.code === 'ENOENT') {
          throw new Error(
            `Video file not found: ${videoPath}\n\n` +
            `Please check:\n` +
            `  • The file path is correct\n` +
            `  • You have permission to read the file\n` +
            `  • The file exists at the specified location`
          );
        }
        throw new Error(
          `Failed to access video file: ${error.message}\n\n` +
          `Path: ${videoPath}`
        );
      }
    
      // Validate it's a file, not a directory
      if (!stats.isFile()) {
        throw new Error(
          `Path is not a file: ${videoPath}\n\n` +
          `Please provide a path to a video file, not a directory.`
        );
      }
    
      // Validate file size
      if (stats.size === 0) {
        throw new Error(
          `Video file is empty (0 bytes): ${videoPath}\n\n` +
          `Please ensure the video file is valid and not corrupted.`
        );
      }
    
      if (stats.size > MAX_FILE_SIZE) {
        const fileSizeMB = (stats.size / (1024 * 1024)).toFixed(2);
        const maxSizeMB = (MAX_FILE_SIZE / (1024 * 1024)).toFixed(0);
        throw new Error(
          `Video file too large: ${fileSizeMB}MB (max: ${maxSizeMB}MB)\n\n` +
          `To fix this:\n` +
          `  • Trim the video to show only the bug (crash moment + 10 seconds before)\n` +
          `  • Compress with: ffmpeg -i input.mp4 -vcodec h264 -acodec aac output.mp4\n` +
          `  • Use a shorter screen recording\n\n` +
          `File: ${videoPath}`
        );
      }
    
      // Validate file extension
      const ext = videoPath.toLowerCase().split(".").pop() || "";
      const supportedFormats = ["mp4", "mov", "webm", "avi", "mkv", "flv", "mpeg", "mpg", "3gp", "wmv"];
      if (!supportedFormats.includes(ext)) {
        throw new Error(
          `Unsupported video format: .${ext}\n\n` +
          `Supported formats: ${supportedFormats.join(", ")}\n\n` +
          `To convert your video:\n` +
          `  ffmpeg -i ${videoPath} output.mp4\n\n` +
          `File: ${videoPath}`
        );
      }
    
      try {
        // Step 1: Get presigned upload URL
        const { data: presignData } = await this.client.post("/upload/presign", {
          filename: basename(videoPath),
          content_type: this.getContentType(videoPath),
          file_size: stats.size,
        });
    
        const { upload_url, video_key } = presignData;
    
        // Step 2: Upload video to Cloudflare R2
        console.error(`Uploading ${basename(videoPath)} (${(stats.size / (1024 * 1024)).toFixed(2)}MB)...`);
        const fileStream = createReadStream(videoPath);
        await axios.put(upload_url, fileStream, {
          headers: {
            "Content-Type": this.getContentType(videoPath),
          },
          timeout: 120000, // 2 minutes for upload
          maxBodyLength: MAX_FILE_SIZE,
          maxContentLength: MAX_FILE_SIZE,
        });
        console.error(`Upload complete. Starting analysis...`);
    
        // Step 3: Start analysis job
        const { data: jobData } = await this.client.post("/analyze/start", {
          video_key: video_key,
          filename: basename(videoPath),
          question,
          analysis_type: "mobile_bug",
        });
    
        const jobId = jobData.id;
        console.error(`Analysis job started (ID: ${jobId}). This may take 2-3 minutes...`);
    
        // Step 4: Poll for results
        const result = await this.pollJobStatus(jobId);
    
        return {
          jobId,
          analysis: this.formatAnalysis(result),
        };
      } catch (error: any) {
        // Handle specific error types
        if (error.response) {
          // HTTP error from API
          const status = error.response.status;
          const message = error.response.data?.detail || error.response.data?.error || error.message;
    
          if (status === 401 || status === 403) {
            throw new Error(
              `Authentication failed\n\n` +
              `Your API key is invalid or expired.\n\n` +
              `To fix this:\n` +
              `  1. Get a new API key: curl -X POST "https://api.clipsense.app/api/v1/keys/request" -H "Content-Type: application/json" -d '{"email":"your-email@example.com"}'\n` +
              `  2. Update your MCP settings with the new key\n` +
              `  3. Restart your IDE\n\n` +
              `Error: ${message}`
            );
          }
    
          if (status === 429) {
            throw new Error(
              `Rate limit exceeded\n\n` +
              `You've used all your monthly analyses.\n\n` +
              `To fix this:\n` +
              `  • Wait until next month (free tier resets monthly)\n` +
              `  • Upgrade to PRO: https://clipsense.app/pricing\n\n` +
              `Error: ${message}`
            );
          }
    
          if (status === 413) {
            throw new Error(
              `Video file too large for server\n\n` +
              `The server rejected your video file.\n\n` +
              `To fix this:\n` +
              `  • Trim the video to only show the bug\n` +
              `  • Compress with: ffmpeg -i ${videoPath} -vcodec h264 -acodec aac output.mp4\n\n` +
              `Error: ${message}`
            );
          }
    
          if (status >= 500) {
            throw new Error(
              `ClipSense server error (${status})\n\n` +
              `The ClipSense API is experiencing issues.\n\n` +
              `To fix this:\n` +
              `  • Try again in a few minutes\n` +
              `  • Check status: https://clipsense.app/status\n` +
              `  • Contact support: support@clipsense.app\n\n` +
              `Error: ${message}`
            );
          }
    
          throw new Error(
            `API error (${status}): ${message}\n\n` +
            `Please contact support@clipsense.app if this persists.`
          );
        }
    
        if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') {
          throw new Error(
            `Cannot connect to ClipSense API\n\n` +
            `Please check:\n` +
            `  • Your internet connection\n` +
            `  • Firewall settings (allow https://api.clipsense.app)\n` +
            `  • VPN/proxy settings\n\n` +
            `Error: ${error.message}`
          );
        }
    
        if (error.code === 'ETIMEDOUT' || error.code === 'ECONNABORTED') {
          throw new Error(
            `Request timeout\n\n` +
            `The upload or API request took too long.\n\n` +
            `To fix this:\n` +
            `  • Check your internet connection\n` +
            `  • Try a smaller video file\n` +
            `  • Try again in a few minutes\n\n` +
            `Error: ${error.message}`
          );
        }
    
        // Re-throw if already formatted
        throw error;
      }
    }
  • MCP CallToolRequest handler for 'analyze-video': extracts parameters, calls ClipSenseClient.analyzeVideo, returns formatted text result or error.
    if (request.params.name === "analyze-video") {
      const { videoPath, question } = request.params.arguments as {
        videoPath: string;
        question?: string;
      };
    
      try {
        // Upload video and analyze
        const result = await client.analyzeVideo(videoPath, question || "Analyze this bug video and identify the issue.");
    
        return {
          content: [
            {
              type: "text",
              text: result.analysis,
            },
          ],
        };
      } catch (error: any) {
        return {
          content: [
            {
              type: "text",
              text: `❌ Error: ${error.message}\n\n${error.details || ""}`,
            },
          ],
          isError: true,
        };
      }
    }
  • src/index.ts:48-72 (registration)
    Registers the 'analyze-video' tool with MCP server, providing name, description, readOnlyHint, and input schema.
    server.setRequestHandler(ListToolsRequestSchema, async () => {
      return {
        tools: [
          {
            name: "analyze-video",
            description: "Use this tool to analyze video files on the user's computer that show mobile app bugs. Reads local video files (MP4, MOV, WebM, AVI, MKV, FLV, MPEG, 3GP, WMV) and provides AI-powered analysis to identify errors, crashes, UI issues, and suggests code fixes. Works with React Native, iOS (Swift/Objective-C), and Android (Kotlin/Java) apps. Use this when the user asks you to analyze, examine, or debug a video file showing app behavior.",
            readOnlyHint: true,
            inputSchema: {
              type: "object",
              properties: {
                videoPath: {
                  type: "string",
                  description: "Absolute path to the video file on the user's computer (e.g., /Users/username/Desktop/bug.mp4). Max 500MB, max 10 minutes.",
                },
                question: {
                  type: "string",
                  description: "Specific question about the bug (optional). Example: 'Why does the button not respond when tapped?' If not provided, a general analysis will be performed.",
                },
              },
              required: ["videoPath"],
            },
          },
        ],
      };
    });
  • Private helper method that polls the ClipSense API job status until completion or failure/timeout.
    private async pollJobStatus(jobId: string): Promise<any> {
      const maxAttempts = 120; // 10 minutes max (5s interval)
      let attempts = 0;
    
      while (attempts < maxAttempts) {
        try {
          const { data } = await this.client.get(`/analyze/jobs/${jobId}/status`);
    
          if (data.status === "completed") {
            console.error(`Analysis complete!`);
            // Get full job details
            const { data: fullJob } = await this.client.get(`/analyze/jobs/${jobId}`);
            return fullJob;
          }
    
          if (data.status === "failed") {
            throw new Error(
              `Analysis failed\n\n` +
              `The video analysis encountered an error.\n\n` +
              `Error details: ${data.error_message || "Unknown error"}\n\n` +
              `To fix this:\n` +
              `  • Ensure the video file is valid and not corrupted\n` +
              `  • Try a different video format\n` +
              `  • Contact support@clipsense.app with job ID: ${jobId}`
            );
          }
    
          // Progress indicator (every 30 seconds)
          if (attempts % 6 === 0 && attempts > 0) {
            const elapsed = (attempts * 5) / 60;
            console.error(`Still processing... (${elapsed.toFixed(1)} min elapsed)`);
          }
    
          // Wait 5 seconds before next poll
          await new Promise((resolve) => setTimeout(resolve, 5000));
          attempts++;
        } catch (error: any) {
          // If polling fails due to network error, provide helpful message
          if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND' || error.code === 'ETIMEDOUT') {
            throw new Error(
              `Lost connection to ClipSense API\n\n` +
              `Your video is still being processed (Job ID: ${jobId})\n\n` +
              `To check status:\n` +
              `  • Visit: https://clipsense.app/results/${jobId}\n` +
              `  • Or try again in a few minutes\n\n` +
              `Error: ${error.message}`
            );
          }
          throw error;
        }
      }
    
      throw new Error(
        `Analysis timeout\n\n` +
        `The analysis is taking longer than expected (>10 minutes).\n\n` +
        `Your job is still processing. To check status:\n` +
        `  • Visit: https://clipsense.app/results/${jobId}\n` +
        `  • Contact support@clipsense.app with job ID: ${jobId}\n\n` +
        `This usually happens with very long videos (>5 minutes).`
      );
    }
  • Private helper to format the API job result into a markdown response with details.
      private formatAnalysis(job: any): string {
        const { result, cost_total, tokens_used } = job;
    
        if (!result?.response) {
          throw new Error("No analysis result found");
        }
    
        return `
    ## Mobile Bug Analysis
    
    ${result.response}
    
    ---
    **Analysis Details:**
    - Frames analyzed: ${job.frames_extracted || "N/A"}
    - Tokens used: ${tokens_used || 0}
    - Cost: $${(cost_total || 0).toFixed(4)}
    `.trim();
      }
Behavior3/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. It discloses key behavioral traits: it reads local files (implies no network calls), lists supported video formats, mentions AI-powered analysis, and specifies the types of issues identified. However, it doesn't cover important aspects like performance characteristics (processing time), error handling (what happens with invalid files), or output format details. The description adds value but leaves gaps in behavioral context.

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

Conciseness4/5

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

The description is appropriately sized and front-loaded with the core purpose in the first sentence. Each subsequent sentence adds value: supported formats, analysis capabilities, supported platforms, and usage guidance. There's minimal redundancy, though the final sentence could be slightly more concise. Overall, it's well-structured with information density.

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 complexity (video analysis with AI) and lack of both annotations and output schema, the description does an adequate but incomplete job. It covers the what and when reasonably well but lacks details about the analysis output format, limitations (beyond file size/duration in schema), error conditions, and processing behavior. For a tool with no structured output documentation, more completeness 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%, providing good documentation for both parameters. The description doesn't add significant meaning beyond the schema - it mentions video files and analysis questions generally but doesn't elaborate on parameter usage, constraints, or examples beyond what's in the schema descriptions. With high schema coverage, the baseline score of 3 is appropriate as the description doesn't compensate with additional parameter insights.

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 tool's purpose with specific verbs ('analyze video files', 'reads local video files', 'provides AI-powered analysis') and resources ('video files on the user's computer', 'mobile app bugs'). It distinguishes what it analyzes (video files showing app bugs) and what it provides (error identification, crash detection, UI issue spotting, code fix suggestions). No siblings exist, but the description is sufficiently specific.

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 clear context for when to use the tool ('when the user asks you to analyze, examine, or debug a video file showing app behavior') and specifies supported platforms (React Native, iOS, Android). However, it doesn't mention when NOT to use it or alternatives (though no siblings exist, so this is less critical). The guidance is explicit but lacks exclusion criteria.

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/clipsense/-mcp-server'

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