Skip to main content
Glama
luma-management-tool.ts13.3 kB
import { z } from 'zod'; import LumaAI from 'lumaai'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { pollLumaTask } from '../utils/luma-polling.js'; // Import polling for upscale // --- Schemas --- export const lumaListGenerationsSchema = z.object({ limit: z.number().int().positive().optional().default(10), offset: z.number().int().nonnegative().optional().default(0), }); export const lumaGetGenerationSchema = z.object({ generation_id: z.string().uuid('Invalid Generation ID format.'), }); export const lumaDeleteGenerationSchema = z.object({ generation_id: z.string().uuid('Invalid Generation ID format.'), }); export const lumaGetCameraMotionsSchema = z.object({}); // No parameters needed // Schema for Add Audio export const lumaAddAudioSchema = z.object({ generation_id: z.string().uuid('Invalid Generation ID format.'), prompt: z.string().min(1, 'Audio prompt cannot be empty.'), negative_prompt: z.string().optional(), // callback_url: z.string().url().optional(), // Callback not handled in this simple version }); // Schema for Upscale // Need to confirm valid resolution values from Luma docs/SDK if possible const lumaUpscaleResolutions = z.enum(['1080p', '4k']).optional().default('1080p'); // Example values export const lumaUpscaleSchema = z.object({ generation_id: z.string().uuid('Invalid Generation ID format.'), resolution: lumaUpscaleResolutions, // callback_url: z.string().url().optional(), // Callback not handled }); // --- Tool Implementations --- // List Generations export async function lumaListGenerationsTool(args: any, exchange: any) { let validatedArgs: z.infer<typeof lumaListGenerationsSchema>; try { validatedArgs = lumaListGenerationsSchema.parse(args); } catch (error: any) { throw new McpError( ErrorCode.InvalidParams, `Invalid arguments: ${error.errors.map((e: any) => e.message).join(', ')}` ); } const apiKey = process.env.LUMAAI_API_KEY; if (!apiKey) { throw new McpError(ErrorCode.InternalError, 'LUMAAI_API_KEY is not configured.'); } const lumaClient = new LumaAI({ authToken: apiKey }); try { console.log( `[${new Date().toISOString()}] Listing Luma generations (limit: ${ validatedArgs.limit }, offset: ${validatedArgs.offset})` ); const generations = await lumaClient.generations.list(validatedArgs); return { content: [{ type: 'text', text: JSON.stringify(generations, null, 2) }], }; } catch (error: any) { console.error('[Luma List Generations Error]', error); if (error instanceof LumaAI.APIError) { throw new McpError(ErrorCode.InternalError, `Luma API Error: ${error.message}`); } throw new McpError(ErrorCode.InternalError, `Failed to list Luma generations: ${error.message}`); } } // Get Generation export async function lumaGetGenerationTool(args: any, exchange: any) { let validatedArgs: z.infer<typeof lumaGetGenerationSchema>; try { validatedArgs = lumaGetGenerationSchema.parse(args); } catch (error: any) { throw new McpError( ErrorCode.InvalidParams, `Invalid arguments: ${error.errors.map((e: any) => e.message).join(', ')}` ); } const apiKey = process.env.LUMAAI_API_KEY; if (!apiKey) { throw new McpError(ErrorCode.InternalError, 'LUMAAI_API_KEY is not configured.'); } const lumaClient = new LumaAI({ authToken: apiKey }); try { console.log(`[${new Date().toISOString()}] Getting Luma generation: ${validatedArgs.generation_id}`); const generation = await lumaClient.generations.get(validatedArgs.generation_id); return { content: [{ type: 'text', text: JSON.stringify(generation, null, 2) }], }; } catch (error: any) { console.error('[Luma Get Generation Error]', error); if (error instanceof LumaAI.APIError) { if (error.status === 404) { throw new McpError(ErrorCode.InvalidRequest, `Luma generation ID ${validatedArgs.generation_id} not found.`); // Use InvalidRequest for 404 } throw new McpError(ErrorCode.InternalError, `Luma API Error: ${error.message}`); } throw new McpError(ErrorCode.InternalError, `Failed to get Luma generation: ${error.message}`); } } // Delete Generation export async function lumaDeleteGenerationTool(args: any, exchange: any) { let validatedArgs: z.infer<typeof lumaDeleteGenerationSchema>; try { validatedArgs = lumaDeleteGenerationSchema.parse(args); } catch (error: any) { throw new McpError( ErrorCode.InvalidParams, `Invalid arguments: ${error.errors.map((e: any) => e.message).join(', ')}` ); } const apiKey = process.env.LUMAAI_API_KEY; if (!apiKey) { throw new McpError(ErrorCode.InternalError, 'LUMAAI_API_KEY is not configured.'); } const lumaClient = new LumaAI({ authToken: apiKey }); try { console.log(`[${new Date().toISOString()}] Deleting Luma generation: ${validatedArgs.generation_id}`); await lumaClient.generations.delete(validatedArgs.generation_id); return { content: [{ type: 'text', text: `Luma generation ${validatedArgs.generation_id} deleted successfully.` }], }; } catch (error: any) { console.error('[Luma Delete Generation Error]', error); if (error instanceof LumaAI.APIError) { if (error.status === 404) { throw new McpError(ErrorCode.InvalidRequest, `Luma generation ID ${validatedArgs.generation_id} not found.`); // Use InvalidRequest for 404 } throw new McpError(ErrorCode.InternalError, `Luma API Error: ${error.message}`); } throw new McpError(ErrorCode.InternalError, `Failed to delete Luma generation: ${error.message}`); } } // Get Camera Motions export async function lumaGetCameraMotionsTool(args: any, exchange: any) { // No validation needed as there are no args const apiKey = process.env.LUMAAI_API_KEY; if (!apiKey) { throw new McpError(ErrorCode.InternalError, 'LUMAAI_API_KEY is not configured.'); } const lumaClient = new LumaAI({ authToken: apiKey }); try { console.log(`[${new Date().toISOString()}] Getting Luma camera motions`); const motions = await lumaClient.generations.cameraMotion.list(); return { content: [{ type: 'text', text: JSON.stringify(motions, null, 2) }], }; } catch (error: any) { console.error('[Luma Get Camera Motions Error]', error); if (error instanceof LumaAI.APIError) { throw new McpError(ErrorCode.InternalError, `Luma API Error: ${error.message}`); } throw new McpError(ErrorCode.InternalError, `Failed to get Luma camera motions: ${error.message}`); } } // Add Audio to Generation export async function lumaAddAudioTool(args: any, exchange: any) { let validatedArgs: z.infer<typeof lumaAddAudioSchema>; try { validatedArgs = lumaAddAudioSchema.parse(args); } catch (error: any) { throw new McpError( ErrorCode.InvalidParams, `Invalid arguments: ${error.errors.map((e: any) => e.message).join(', ')}` ); } const apiKey = process.env.LUMAAI_API_KEY; if (!apiKey) { throw new McpError(ErrorCode.InternalError, 'LUMAAI_API_KEY is not configured.'); } const lumaClient = new LumaAI({ authToken: apiKey }); try { console.log(`[${new Date().toISOString()}] Adding audio to Luma generation: ${validatedArgs.generation_id}`); // Prepare payload for add audio const payload: LumaAI.GenerationAudioParams = { // Use suggested type GenerationAudioParams prompt: validatedArgs.prompt, negative_prompt: validatedArgs.negative_prompt, // generation_type: 'add_audio', // SDK might handle this or require it }; // Remove undefined optional parameters Object.keys(payload).forEach( (key) => payload[key as keyof LumaAI.GenerationAudioParams] === undefined && delete payload[key as keyof LumaAI.GenerationAudioParams] // Use correct type here too ); // Call the SDK method - Assuming it's under generations.addAudio or similar // The exact method might differ, adjust based on SDK v1.7.1 structure // Example: await lumaClient.generations.addAudio(validatedArgs.generation_id, payload); // Using generic request as placeholder: const updatedGeneration: LumaAI.Generation = await lumaClient.request({ // Explicitly type response method: 'post', // Use lowercase 'post' path: `/generations/${validatedArgs.generation_id}/audio`, body: payload, // Removed castTo, rely on explicit typing }); // Check if the operation was successful (API might return updated generation or just status) // For simplicity, returning the response directly. Client might need to re-fetch if needed. return { content: [{ type: 'text', text: `Audio addition requested for ${validatedArgs.generation_id}. Response: ${JSON.stringify(updatedGeneration, null, 2)}` }], }; } catch (error: any) { console.error('[Luma Add Audio Error]', error); if (error instanceof LumaAI.APIError) { if (error.status === 404) { throw new McpError(ErrorCode.InvalidRequest, `Luma generation ID ${validatedArgs.generation_id} not found.`); } throw new McpError(ErrorCode.InternalError, `Luma API Error: ${error.message}`); } throw new McpError(ErrorCode.InternalError, `Failed to add audio to Luma generation: ${error.message}`); } } // Upscale Generation export async function lumaUpscaleTool(args: any, exchange: any) { let validatedArgs: z.infer<typeof lumaUpscaleSchema>; try { validatedArgs = lumaUpscaleSchema.parse(args); } catch (error: any) { throw new McpError( ErrorCode.InvalidParams, `Invalid arguments: ${error.errors.map((e: any) => e.message).join(', ')}` ); } const apiKey = process.env.LUMAAI_API_KEY; if (!apiKey) { throw new McpError(ErrorCode.InternalError, 'LUMAAI_API_KEY is not configured.'); } const lumaClient = new LumaAI({ authToken: apiKey }); const progressToken = exchange?.progressToken; // Get progress token try { console.log(`[${new Date().toISOString()}] Upscaling Luma generation: ${validatedArgs.generation_id} to ${validatedArgs.resolution}`); // Prepare payload for upscale const payload: LumaAI.GenerationUpscaleParams = { resolution: validatedArgs.resolution as '1080p' | '4k' | undefined, // Cast based on schema // generation_type: 'upscale_video', // SDK might handle this }; // Remove undefined optional parameters Object.keys(payload).forEach( (key) => payload[key as keyof LumaAI.GenerationUpscaleParams] === undefined && delete payload[key as keyof LumaAI.GenerationUpscaleParams] ); // Call the SDK method - Assuming generations.upscale exists // Example: const upscaleResponse = await lumaClient.generations.upscale(validatedArgs.generation_id, payload); // Using generic request as placeholder: const upscaleResponse: LumaAI.Generation = await lumaClient.request({ // Explicitly type response method: 'post', // Use lowercase 'post' path: `/generations/${validatedArgs.generation_id}/upscale`, body: payload, // Removed castTo, rely on explicit typing }); // Upscaling is likely async, so we need the ID of the *new* upscale task if the API returns one, // or poll the original ID if the API updates it in place. // Assuming the response contains the ID of the task to poll (might be the original ID or a new one) const taskIdToPoll = upscaleResponse.id || validatedArgs.generation_id; // Access id from typed response if (!taskIdToPoll) { throw new McpError( ErrorCode.InternalError, 'Luma AI Upscale API did not return a pollable task ID.' ); } console.log(`[${new Date().toISOString()}] Luma AI upscale task initiated/updated with ID: ${taskIdToPoll}`); // Send initial confirmation exchange.sendProgress?.({ token: progressToken, value: { status: 'INITIATED', taskId: taskIdToPoll, provider: 'lumaai', operation: 'upscale' }, }); // Start polling for the upscale result pollLumaTask({ lumaClient, generationId: taskIdToPoll, mcpExchange: exchange, progressToken, expectedAssetType: 'upscaled_video', // Specify we expect an upscale result }).catch((error: any) => { console.error( `[${new Date().toISOString()}] Luma AI upscale polling failed for task ${taskIdToPoll}:`, error.message || error ); }); // Return immediately return { content: [ { type: 'text', text: `Luma AI upscale task initiated for ID: ${taskIdToPoll}. Upscaling is in progress. Status updates will follow.`, }, ], }; } catch (error: any) { console.error('[Luma Upscale Error]', error); if (error instanceof LumaAI.APIError) { if (error.status === 404) { throw new McpError(ErrorCode.InvalidRequest, `Luma generation ID ${validatedArgs.generation_id} not found for upscaling.`); } throw new McpError(ErrorCode.InternalError, `Luma API Error: ${error.message}`); } throw new McpError(ErrorCode.InternalError, `Failed to upscale Luma generation: ${error.message}`); } }

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/wheattoast11/mcp-video-gen'

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