UNPKG

@z_ai/mcp-server

Version:

MCP Server for Z.AI - A Model Context Protocol server that provides AI capabilities

137 lines (136 loc) 6.25 kB
import { z } from 'zod'; import { FileNotFoundError, ApiError } from '../types/index.js'; import { ToolExecutionError } from '../core/error-handler.js'; import { CommonSchemas, ToolSchemaBuilder } from '../core/validation.js'; import { createMultiModalMessage, createVideoContent, formatMcpResponse, createSuccessResponse, createErrorResponse, withRetry } from '../utils/common.js'; import { fileService } from '../core/file-service.js'; import { chatService } from '../core/chat-service.js'; /** * Video analysis service class */ export class VideoAnalysisService { chatService = chatService; fileService = fileService; MAX_VIDEO_SIZE_MB = 8; /** * Execute video analysis * @param request Video analysis request * @returns Analysis result */ async analyzeVideo(request) { console.info('Starting video analysis', { videoSource: request.videoSource, prompt: request.prompt }); try { // Validate video source (file or URL) and size await this.fileService.validateVideoSource(request.videoSource, this.MAX_VIDEO_SIZE_MB); // Validate prompt if (!request.prompt || request.prompt.trim().length === 0) { throw new ToolExecutionError('Prompt is required for video analysis', 'video-analysis', 'VALIDATION_ERROR', { toolName: 'video-analysis', operation: 'analyzeVideo', metadata: { videoSource: request.videoSource } }); } // Handle video source (URL or local file) let videoContent; if (this.fileService.isUrl(request.videoSource)) { // For URLs, pass directly without base64 encoding videoContent = createVideoContent(request.videoSource); } else { // For local files, encode to base64 const videoData = await this.fileService.encodeVideoToBase64(request.videoSource); videoContent = createVideoContent(videoData); } // Create multimodal message const messages = createMultiModalMessage([videoContent], request.prompt); // Call API for analysis const analysisOptions = { temperature: request.options?.temperature, topP: request.options?.topP, maxTokens: request.options?.maxTokens }; const result = await this.chatService.visionCompletions(messages, analysisOptions); console.info('Video analysis completed', { videoSource: request.videoSource }); return result; } catch (error) { console.error('Video analysis failed', { error: error instanceof Error ? error.message : String(error), videoSource: request.videoSource }); if (error instanceof ToolExecutionError) { throw error; } // Wrap unknown errors throw new ToolExecutionError(`Video analysis failed: ${error.message}`, 'video-analysis', 'EXECUTION_ERROR', { toolName: 'video-analysis', operation: 'analyzeVideo', metadata: { videoSource: request.videoSource, originalError: error } }, error); } } } /** * Register video analysis tool with MCP server * @param server MCP server instance */ export function registerVideoAnalysisTool(server) { const analysisService = new VideoAnalysisService(); const retryableAnalyze = withRetry(analysisService.analyzeVideo.bind(analysisService), 2, // Maximum 2 retries 1000 // 1 second delay ); server.tool('analyze_video', 'Analyze a video using advanced AI vision models with comprehensive understanding capabilities. Supports both local files and remote URL. Maximum local file size: 8MB', { video_source: z.string().describe('Local file path or remote URL to the video (supports MP4, MOV, M4V)'), prompt: z.string().describe('Detailed text prompt describing what to analyze, extract, or understand from the video') }, async (params) => { try { // Validate parameters const validationSchema = new ToolSchemaBuilder() .required('video_source', CommonSchemas.nonEmptyString) .required('prompt', CommonSchemas.nonEmptyString) .build(); validationSchema.parse(params); // Build request object const request = { videoSource: params.video_source, prompt: params.prompt, options: { temperature: params.temperature, topP: params.top_p, maxTokens: params.max_tokens } }; // Execute analysis const result = await retryableAnalyze(request); const response = createSuccessResponse(result); return formatMcpResponse(response); } catch (error) { console.error('Tool execution failed', { error: error instanceof Error ? error.message : String(error), params }); let errorResponse; if (error instanceof z.ZodError) { const validationErrors = error.issues.map((e) => `${e.path.join('.')}: ${e.message}`).join(', '); errorResponse = createErrorResponse(`Validation failed: ${validationErrors}`); } else if (error instanceof FileNotFoundError) { errorResponse = createErrorResponse(`Video file not found: ${error.message}`); } else if (error instanceof ApiError) { errorResponse = createErrorResponse(`API error: ${error.message}`); } else { errorResponse = createErrorResponse(`Unexpected error: ${error instanceof Error ? error.message : String(error)}`); } return formatMcpResponse(errorResponse); } }); console.info('Video analysis tool registered successfully'); }