Skip to main content
Glama
luma-generate-image-tool.ts6.7 kB
import { z } from 'zod'; import LumaAI from 'lumaai'; import { McpError, ErrorCode } from '@modelcontextprotocol/sdk/types.js'; import { pollLumaTask } from '../utils/luma-polling.js'; // Re-use polling logic // --- Zod Schema for Input Validation --- // Based on Luma docs for image generation const lumaImageAspectRatios = z .enum(['16:9', '1:1', '3:4', '4:3', '9:16', '9:21', '21:9']) .optional() .default('16:9'); const lumaImageModels = z .enum(['photon-1', 'photon-flash-1']) .optional() .default('photon-1'); const imageRefSchema = z.object({ url: z.string().url(), weight: z.number().min(0).max(1).optional().default(0.85), // Default based on docs example }); const styleRefSchema = z.object({ url: z.string().url(), weight: z.number().min(0).max(1).optional().default(0.8), // Default based on docs example }); const characterRefSchema = z.record( z.string().startsWith('identity'), // Keys like identity0, identity1... z.object({ images: z.array(z.string().url()).min(1).max(4), // 1 to 4 image URLs }) ); const modifyImageRefSchema = z.object({ url: z.string().url(), weight: z.number().min(0).max(1).optional().default(1.0), // Default based on docs example }); export const lumaGenerateImageSchema = z.object({ prompt: z.string().min(1, 'Prompt cannot be empty.'), aspect_ratio: lumaImageAspectRatios, model: lumaImageModels, image_ref: z.array(imageRefSchema).max(4).optional(), style_ref: z.array(styleRefSchema).max(1).optional(), // Docs imply only one style ref? Check API spec if needed. character_ref: characterRefSchema.optional(), modify_image_ref: modifyImageRefSchema.optional(), // Add negative_prompt if supported? Docs were conflicting. // negative_prompt: z.string().optional(), }); // Type alias for inferred schema type type LumaImageParams = z.infer<typeof lumaGenerateImageSchema>; // --- Tool Implementation --- export async function lumaGenerateImageTool(args: any, exchange: any) { let validatedArgs: LumaImageParams; try { validatedArgs = lumaGenerateImageSchema.parse(args); } catch (error: any) { console.error( `[${new Date().toISOString()}] Invalid arguments for luma_generate_image:`, error.errors ); throw new McpError( ErrorCode.InvalidParams, `Invalid arguments: ${error.errors .map((e: any) => `${e.path.join('.')} - ${e.message}`) .join(', ')}` ); } const apiKey = process.env.LUMAAI_API_KEY; if (!apiKey) { throw new McpError( ErrorCode.InternalError, 'LUMAAI_API_KEY is not configured on the server.' ); } const lumaClient = new LumaAI({ authToken: apiKey }); const progressToken = exchange?.progressToken; try { console.log( `[${new Date().toISOString()}] Starting Luma AI image generation with prompt: "${validatedArgs.prompt.substring( 0, 50 )}..."` ); // Prepare payload specifically for image generation as a plain object const payload: any = { // Use 'any' for flexibility, SDK should handle validation prompt: validatedArgs.prompt, aspect_ratio: validatedArgs.aspect_ratio, model: validatedArgs.model, // This should be 'photon-1' or 'photon-flash-1' from Zod schema image_ref: validatedArgs.image_ref, style_ref: validatedArgs.style_ref, character_ref: validatedArgs.character_ref, modify_image_ref: validatedArgs.modify_image_ref, // negative_prompt: validatedArgs.negative_prompt, // Add if schema includes it }; // Remove undefined optional parameters Object.keys(payload).forEach( (key) => payload[key] === undefined && delete payload[key] ); // Call the Luma SDK's image creation method // Note: The SDK might have a different structure, e.g., client.generations.image.create // Adjust if necessary based on actual SDK usage. Assuming client.generations.create works for images too. // UPDATE: Based on JS docs, it seems client.generations.image.create is correct. const generation = await lumaClient.generations.image.create(payload); const generationId = generation.id || ''; if (!generationId) { throw new McpError( ErrorCode.InternalError, 'Luma AI API did not return a generation ID.' ); } console.log( `[${new Date().toISOString()}] Luma AI image generation task initiated with ID: ${generationId}` ); // Send initial confirmation exchange.sendProgress?.({ token: progressToken, value: { status: 'INITIATED', taskId: generationId, provider: 'lumaai' }, }); // Start polling (re-use video polling, assuming response structure is similar enough) // It should resolve with the image URL from generation.assets.image pollLumaTask({ lumaClient, generationId, mcpExchange: exchange, progressToken, }) .then((imageUrl) => { console.log( `[${new Date().toISOString()}] Luma image polling finished successfully for task ${generationId}.` ); // Send final image URL via progress (pollLumaTask needs modification to return image URL) // For now, pollLumaTask sends SUCCEEDED with videoUrl. We need to adapt it or handle here. // Let's assume pollLumaTask is adapted or we handle the final message here. // If pollLumaTask resolves with the URL: exchange.sendProgress?.({ token: progressToken, value: { status: 'SUCCEEDED', imageUrl }, }); }) .catch((error: any) => { console.error( `[${new Date().toISOString()}] Luma AI image polling failed for task ${generationId}:`, error.message || error ); }); // Return immediately return { content: [ { type: 'text', text: `Luma AI image generation task initiated with ID: ${generationId}. Generation is in progress. Status updates will follow.`, }, ], }; } catch (error: any) { console.error( `[${new Date().toISOString()}] Error initiating Luma AI image generation:`, error ); if (error instanceof LumaAI.APIError) { let mcpErrorCode = ErrorCode.InternalError; if (error.status === 401 || error.status === 403) mcpErrorCode = ErrorCode.InvalidRequest; else if (error.status === 400 || error.status === 422) mcpErrorCode = ErrorCode.InvalidParams; throw new McpError( mcpErrorCode, `Luma AI API Error: ${error.message} (Status: ${error.status})` ); } throw new McpError( ErrorCode.InternalError, `Failed to initiate Luma AI image 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