Skip to main content
Glama
by el-el-san
index.ts8.08 kB
#!/usr/bin/env node import { config } from 'dotenv'; import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import * as falClient from "@fal-ai/serverless-client"; import { z } from "zod"; // Load environment variables config(); // Set FAL API key if (process.env.FAL_KEY) { falClient.config({ credentials: process.env.FAL_KEY }); } else { console.error("Warning: FAL_KEY environment variable is not set. API calls will fail."); } // Define model URLs const MODELS = { luma: "fal-ai/luma-dream-machine/ray-2-flash/image-to-video", kling: "fal-ai/kling-video/v1.6/pro/image-to-video" }; // Create server const server = new Server( { name: "video-generator", version: "1.0.0" }, { capabilities: { tools: {} } } ); // Import MCP schemas import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types.js"; // Define available tools server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: "generate-video", description: "Generate a video from text prompt and/or images using AI models (Luma or Kling)", inputSchema: { type: "object", properties: { prompt: { type: "string", description: "Text description of the desired video content" }, image_url: { type: "string", description: "Initial image to start the video from (URL or base64 data URI)" }, end_image_url: { type: "string", description: "Final image to end the video with (URL or base64 data URI)" }, aspect_ratio: { type: "string", enum: ["16:9", "9:16", "4:3", "3:4", "21:9", "9:21"], default: "16:9", description: "Aspect ratio of the video" }, resolution: { type: "string", enum: ["540p", "720p", "1080p"], default: "540p", description: "Resolution of the video (higher resolutions use more credits)" }, duration: { type: "string", enum: ["5s", "9s"], default: "5s", description: "Duration of the video (9s costs 2x more)" }, loop: { type: "boolean", default: false, description: "Whether the video should loop (blend end with beginning)" }, model: { type: "string", enum: ["luma", "kling"], default: "luma", description: "AI model to use (luma=Ray2, kling=Kling)" } }, required: ["prompt"] } }, { name: "check-video-status", description: "Check the status of a video generation request", inputSchema: { type: "object", properties: { request_id: { type: "string", description: "The request ID to check" }, model: { type: "string", enum: ["luma", "kling"], default: "luma", description: "AI model used for the request (luma=Ray2, kling=Kling)" } }, required: ["request_id"] } } ] }; }); // Handle tool calls server.setRequestHandler(CallToolRequestSchema, async (request: any) => { const { name, arguments: args } = request.params; if (name === "generate-video") { try { // Determine which model to use const modelName = args.model || "luma"; const modelUrl = MODELS[modelName as keyof typeof MODELS]; if (!modelUrl) { return { isError: true, content: [ { type: "text", text: `Invalid model: ${modelName}. Supported models are: luma, kling` } ] }; } console.error(`Generating video with ${modelName} model and prompt: "${args.prompt}"`); // Clone args and remove model parameter that isn't used by the API const apiArgs = {...args}; delete apiArgs.model; // Call the FAL API const result: any = await falClient.subscribe(modelUrl, { input: apiArgs, logs: true, onQueueUpdate: (update: any) => { if (update.status === "IN_PROGRESS" && update.logs) { update.logs.forEach((log: any) => { if (typeof log === 'string') { console.error(`Progress: ${log}`); } else if (log.message) { console.error(`Progress: ${log.message}`); } }); } }, }); console.error(`Video generation completed successfully using ${modelName} model`); // Extract the video URL from the response let videoUrl = ''; let requestId = 'Unknown'; if (result) { if (result.video && result.video.url) { videoUrl = result.video.url; } else if (result.data && result.data.video && result.data.video.url) { videoUrl = result.data.video.url; } if (result.requestId) { requestId = result.requestId; } } // Return result return { content: [ { type: "text", text: JSON.stringify({ model: modelName, video_url: videoUrl || 'Video URL not found in response', message: `Video generated successfully using ${modelName} model`, request_id: requestId }, null, 2) } ] }; } catch (error: any) { console.error("Error generating video:", error); return { isError: true, content: [ { type: "text", text: `Error generating video: ${error instanceof Error ? error.message : String(error)}` } ] }; } } else if (name === "check-video-status") { try { // Determine which model to use const modelName = args.model || "luma"; const modelUrl = MODELS[modelName as keyof typeof MODELS]; if (!modelUrl) { return { isError: true, content: [ { type: "text", text: `Invalid model: ${modelName}. Supported models are: luma, kling` } ] }; } const status: any = await falClient.queue.status(modelUrl, { requestId: args.request_id, logs: true }); // Safely extract properties const statusLogs = Array.isArray(status.logs) ? status.logs : []; const position = status.position || status.queue_position || 0; return { content: [ { type: "text", text: JSON.stringify({ model: modelName, status: status.status, logs: statusLogs, position: position }, null, 2) } ] }; } catch (error: any) { return { isError: true, content: [ { type: "text", text: `Error checking video status: ${error instanceof Error ? error.message : String(error)}` } ] }; } } else { return { isError: true, content: [ { type: "text", text: `Unknown tool: ${name}` } ] }; } }); // Start the server async function main() { try { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Video Generator MCP Server running on stdio"); console.error("Supported models: Luma Ray2, Kling v1.6 Pro"); } catch (error: any) { console.error("Error starting server:", error); process.exit(1); } } main();

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/el-el-san/fal-mcp-server'

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