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(); }
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