Skip to main content
Glama
index.ts14.4 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js'; import { KieAiClient } from './kie-ai-client.js'; import { TaskDatabase } from './database.js'; import { NanoBananaGenerateSchema, NanoBananaEditSchema, Veo3GenerateSchema, KieAiConfig } from './types.js'; class KieAiMcpServer { private server: Server; private client: KieAiClient; private db: TaskDatabase; constructor() { this.server = new Server({ name: 'kie-ai-mcp-server', version: '1.0.0', }); // Initialize client with config from environment const config: KieAiConfig = { apiKey: process.env.KIE_AI_API_KEY || '', baseUrl: process.env.KIE_AI_BASE_URL || 'https://api.kie.ai/api/v1', timeout: parseInt(process.env.KIE_AI_TIMEOUT || '60000') }; if (!config.apiKey) { throw new Error('KIE_AI_API_KEY environment variable is required'); } this.client = new KieAiClient(config); this.db = new TaskDatabase(process.env.KIE_AI_DB_PATH); this.setupHandlers(); } private setupHandlers(): void { this.server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'generate_nano_banana', description: 'Generate images using Google\'s Gemini 2.5 Flash Image Preview (Nano Banana)', inputSchema: { type: 'object', properties: { prompt: { type: 'string', description: 'Text prompt for image generation', minLength: 1, maxLength: 1000 } }, required: ['prompt'] } }, { name: 'edit_nano_banana', description: 'Edit images using natural language prompts with Nano Banana Edit', inputSchema: { type: 'object', properties: { prompt: { type: 'string', description: 'Text prompt for image editing', minLength: 1, maxLength: 1000 }, image_urls: { type: 'array', description: 'URLs of input images for editing (max 5)', items: { type: 'string', format: 'uri' }, minItems: 1, maxItems: 5 } }, required: ['prompt', 'image_urls'] } }, { name: 'generate_veo3_video', description: 'Generate professional-quality videos using Google\'s Veo3 API', inputSchema: { type: 'object', properties: { prompt: { type: 'string', description: 'Text prompt describing desired video content', minLength: 1, maxLength: 2000 }, imageUrls: { type: 'array', description: 'Image URLs for image-to-video generation (max 1)', items: { type: 'string', format: 'uri' }, maxItems: 1 }, model: { type: 'string', enum: ['veo3', 'veo3_fast'], description: 'Model type: veo3 (quality) or veo3_fast (cost-efficient)', default: 'veo3' }, watermark: { type: 'string', description: 'Watermark text to add to video', maxLength: 100 }, aspectRatio: { type: 'string', enum: ['16:9', '9:16'], description: 'Video aspect ratio', default: '16:9' }, seeds: { type: 'integer', description: 'Random seed for consistent results', minimum: 10000, maximum: 99999 }, enableFallback: { type: 'boolean', description: 'Enable fallback mechanism for content policy failures', default: false } }, required: ['prompt'] } }, { name: 'get_task_status', description: 'Get the status of a generation task', inputSchema: { type: 'object', properties: { task_id: { type: 'string', description: 'Task ID to check status for' } }, required: ['task_id'] } }, { name: 'list_tasks', description: 'List recent tasks with their status', inputSchema: { type: 'object', properties: { limit: { type: 'integer', description: 'Maximum number of tasks to return', default: 20, maximum: 100 }, status: { type: 'string', description: 'Filter by status', enum: ['pending', 'processing', 'completed', 'failed'] } } } }, { name: 'get_veo3_1080p_video', description: 'Get 1080P high-definition version of a Veo3 video (not available for fallback mode videos)', inputSchema: { type: 'object', properties: { task_id: { type: 'string', description: 'Veo3 task ID to get 1080p video for' }, index: { type: 'integer', description: 'Video index (optional, for multiple video results)', minimum: 0 } }, required: ['task_id'] } } ] }; }); this.server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; switch (name) { case 'generate_nano_banana': return await this.handleGenerateNanoBanana(args); case 'edit_nano_banana': return await this.handleEditNanoBanana(args); case 'generate_veo3_video': return await this.handleGenerateVeo3Video(args); case 'get_task_status': return await this.handleGetTaskStatus(args); case 'list_tasks': return await this.handleListTasks(args); case 'get_veo3_1080p_video': return await this.handleGetVeo1080pVideo(args); default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${name}` ); } } catch (error) { if (error instanceof McpError) { throw error; } const message = error instanceof Error ? error.message : 'Unknown error'; throw new McpError(ErrorCode.InternalError, message); } }); } private async handleGenerateNanoBanana(args: any) { const request = NanoBananaGenerateSchema.parse(args); try { const response = await this.client.generateNanoBanana(request); if (response.data?.taskId) { await this.db.createTask({ task_id: response.data.taskId, api_type: 'nano-banana', status: 'pending', result_url: response.data.imageUrl }); } return { content: [ { type: 'text', text: JSON.stringify({ success: true, response: response, message: 'Nano Banana image generation initiated' }, null, 2) } ] }; } catch (error) { const message = error instanceof Error ? error.message : 'Generation failed'; return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: message }, null, 2) } ] }; } } private async handleEditNanoBanana(args: any) { const request = NanoBananaEditSchema.parse(args); try { const response = await this.client.editNanoBanana(request); if (response.data?.taskId) { await this.db.createTask({ task_id: response.data.taskId, api_type: 'nano-banana-edit', status: 'pending', result_url: response.data.imageUrl }); } return { content: [ { type: 'text', text: JSON.stringify({ success: true, response: response, message: 'Nano Banana image editing initiated' }, null, 2) } ] }; } catch (error) { const message = error instanceof Error ? error.message : 'Editing failed'; return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: message }, null, 2) } ] }; } } private async handleGenerateVeo3Video(args: any) { const request = Veo3GenerateSchema.parse(args); try { const response = await this.client.generateVeo3Video(request); if (response.data?.taskId) { await this.db.createTask({ task_id: response.data.taskId, api_type: 'veo3', status: 'pending' }); } return { content: [ { type: 'text', text: JSON.stringify({ success: true, task_id: response.data?.taskId, message: 'Veo3 video generation task created successfully', note: 'Use get_task_status to check progress' }, null, 2) } ] }; } catch (error) { const message = error instanceof Error ? error.message : 'Video generation failed'; return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: message }, null, 2) } ] }; } } private async handleGetTaskStatus(args: any) { const { task_id } = args; if (!task_id || typeof task_id !== 'string') { throw new McpError(ErrorCode.InvalidParams, 'task_id is required and must be a string'); } try { const localTask = await this.db.getTask(task_id); // Always try to get updated status from API, passing api_type if available let apiResponse = null; try { apiResponse = await this.client.getTaskStatus(task_id, localTask?.api_type); } catch (error) { // API call failed, use local data if available } return { content: [ { type: 'text', text: JSON.stringify({ success: true, local_task: localTask, api_response: apiResponse, message: localTask ? 'Task found' : 'Task not found in local database' }, null, 2) } ] }; } catch (error) { const message = error instanceof Error ? error.message : 'Failed to get task status'; return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: message }, null, 2) } ] }; } } private async handleListTasks(args: any) { const { limit = 20, status } = args; try { let tasks; if (status) { tasks = await this.db.getTasksByStatus(status, limit); } else { tasks = await this.db.getAllTasks(limit); } return { content: [ { type: 'text', text: JSON.stringify({ success: true, tasks: tasks, count: tasks.length, message: `Retrieved ${tasks.length} tasks` }, null, 2) } ] }; } catch (error) { const message = error instanceof Error ? error.message : 'Failed to list tasks'; return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: message }, null, 2) } ] }; } } private async handleGetVeo1080pVideo(args: any) { const { task_id, index } = args; if (!task_id || typeof task_id !== 'string') { throw new McpError(ErrorCode.InvalidParams, 'task_id is required and must be a string'); } try { const response = await this.client.getVeo1080pVideo(task_id, index); return { content: [ { type: 'text', text: JSON.stringify({ success: true, task_id: task_id, response: response, message: 'Retrieved 1080p video URL', note: 'Not available for videos generated with fallback mode' }, null, 2) } ] }; } catch (error) { const message = error instanceof Error ? error.message : 'Failed to get 1080p video'; return { content: [ { type: 'text', text: JSON.stringify({ success: false, error: message }, null, 2) } ] }; } } async run(): Promise<void> { const transport = new StdioServerTransport(); await this.server.connect(transport); } } // Start the server const server = new KieAiMcpServer(); server.run().catch(console.error);

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/andrewlwn77/kie-ai-mcp-server'

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