@felores/kie-ai-mcp-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 andrewlwn77
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,313 @@
1
+ # Kie.ai MCP Server
2
+
3
+ An MCP (Model Context Protocol) server that provides access to Kie.ai's AI APIs including Nano Banana image generation/editing and Veo3 video generation.
4
+
5
+ ## Features
6
+
7
+ - **Nano Banana Image Generation**: Text-to-image generation using Google's Gemini 2.5 Flash Image Preview
8
+ - **Nano Banana Image Editing**: Natural language image editing with up to 5 input images
9
+ - **Veo3 Video Generation**: Professional-quality video generation with text-to-video and image-to-video capabilities
10
+ - **1080p Video Upgrade**: Get high-definition versions of Veo3 videos
11
+ - **Task Management**: SQLite-based task tracking with status polling
12
+ - **Smart Endpoint Routing**: Automatic detection of task types for status checking
13
+ - **Error Handling**: Comprehensive error handling and validation
14
+
15
+ ## Prerequisites
16
+
17
+ - Node.js 18+
18
+ - Kie.ai API key from https://kie.ai/api-key
19
+
20
+ ## Installation
21
+
22
+ ### From NPM
23
+
24
+ ```bash
25
+ npm install -g @andrewlwn77/kie-ai-mcp-server
26
+ ```
27
+
28
+ ### From Source
29
+
30
+ ```bash
31
+ # Clone the repository
32
+ git clone https://github.com/andrewlwn77/kie-ai-mcp-server.git
33
+ cd kie-ai-mcp-server
34
+
35
+ # Install dependencies
36
+ npm install
37
+
38
+ # Build the project
39
+ npm run build
40
+ ```
41
+
42
+ ## Configuration
43
+
44
+ ### Environment Variables
45
+
46
+ ```bash
47
+ # Required
48
+ export KIE_AI_API_KEY="your-api-key-here"
49
+
50
+ # Optional
51
+ export KIE_AI_BASE_URL="https://api.kie.ai/api/v1" # Default
52
+ export KIE_AI_TIMEOUT="60000" # Default: 60 seconds
53
+ export KIE_AI_DB_PATH="./tasks.db" # Default: ./tasks.db
54
+ ```
55
+
56
+ ### MCP Configuration
57
+
58
+ Add to your Claude Desktop or MCP client configuration:
59
+
60
+ ```json
61
+ {
62
+ "kie-ai-mcp-server": {
63
+ "command": "node",
64
+ "args": ["/path/to/kie-ai-mcp-server/dist/index.js"],
65
+ "env": {
66
+ "KIE_AI_API_KEY": "your-api-key-here"
67
+ }
68
+ }
69
+ }
70
+ ```
71
+
72
+ Or if installed globally:
73
+
74
+ ```json
75
+ {
76
+ "kie-ai-mcp-server": {
77
+ "command": "npx",
78
+ "args": ["-y", "@andrewlwn77/kie-ai-mcp-server"],
79
+ "env": {
80
+ "KIE_AI_API_KEY": "your-api-key-here"
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ ## Available Tools
87
+
88
+ ### 1. `generate_nano_banana`
89
+ Generate images using Nano Banana.
90
+
91
+ **Parameters:**
92
+ - `prompt` (string, required): Text description of the image to generate
93
+
94
+ **Example:**
95
+ ```json
96
+ {
97
+ "prompt": "A surreal painting of a giant banana floating in space"
98
+ }
99
+ ```
100
+
101
+ ### 2. `edit_nano_banana`
102
+ Edit images using natural language prompts.
103
+
104
+ **Parameters:**
105
+ - `prompt` (string, required): Description of edits to make
106
+ - `image_urls` (array, required): URLs of images to edit (max 5)
107
+
108
+ **Example:**
109
+ ```json
110
+ {
111
+ "prompt": "Add a rainbow arching over the mountains",
112
+ "image_urls": ["https://example.com/image.jpg"]
113
+ }
114
+ ```
115
+
116
+ ### 3. `generate_veo3_video`
117
+ Generate videos using Veo3.
118
+
119
+ **Parameters:**
120
+ - `prompt` (string, required): Video description
121
+ - `imageUrls` (array, optional): Image for image-to-video (max 1)
122
+ - `model` (enum, optional): "veo3" or "veo3_fast" (default: "veo3")
123
+ - `aspectRatio` (enum, optional): "16:9" or "9:16" (default: "16:9")
124
+ - `seeds` (integer, optional): Random seed 10000-99999
125
+ - `watermark` (string, optional): Watermark text
126
+ - `enableFallback` (boolean, optional): Enable fallback mechanism
127
+
128
+ **Example:**
129
+ ```json
130
+ {
131
+ "prompt": "A dog playing in a park",
132
+ "model": "veo3",
133
+ "aspectRatio": "16:9",
134
+ "seeds": 12345
135
+ }
136
+ ```
137
+
138
+ ### 4. `get_task_status`
139
+ Check the status of a generation task.
140
+
141
+ **Parameters:**
142
+ - `task_id` (string, required): Task ID to check
143
+
144
+ ### 5. `list_tasks`
145
+ List recent tasks with their status.
146
+
147
+ **Parameters:**
148
+ - `limit` (integer, optional): Max tasks to return (default: 20, max: 100)
149
+ - `status` (string, optional): Filter by status ("pending", "processing", "completed", "failed")
150
+
151
+ ### 6. `get_veo3_1080p_video`
152
+ Get 1080P high-definition version of a Veo3 video.
153
+
154
+ **Parameters:**
155
+ - `task_id` (string, required): Veo3 task ID to get 1080p video for
156
+ - `index` (integer, optional): Video index (for multiple video results)
157
+
158
+ **Note**: Not available for videos generated with fallback mode.
159
+
160
+ ## API Endpoints
161
+
162
+ The server interfaces with these Kie.ai API endpoints:
163
+
164
+ - **Veo3 Video Generation**: `POST /api/v1/veo/generate` ✅ **VALIDATED**
165
+ - **Veo3 Video Status**: `GET /api/v1/veo/record-info` ✅ **VALIDATED**
166
+ - **Veo3 1080p Upgrade**: `GET /api/v1/veo/get-1080p-video` ✅ **VALIDATED**
167
+ - **Nano Banana Generation**: `POST /api/v1/playground/createTask` ✅ **VALIDATED**
168
+ - **Nano Banana Status**: `GET /api/v1/playground/recordInfo` ✅ **VALIDATED**
169
+
170
+ All endpoints have been tested and validated with live API responses.
171
+
172
+ ## Database Schema
173
+
174
+ The server uses SQLite to track tasks:
175
+
176
+ ```sql
177
+ CREATE TABLE tasks (
178
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
179
+ task_id TEXT UNIQUE NOT NULL,
180
+ api_type TEXT NOT NULL, -- 'nano-banana', 'nano-banana-edit', 'veo3'
181
+ status TEXT DEFAULT 'pending',
182
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
183
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
184
+ result_url TEXT,
185
+ error_message TEXT
186
+ );
187
+ ```
188
+
189
+ ## Usage Examples
190
+
191
+ ### Basic Image Generation
192
+ ```bash
193
+ # Generate an image
194
+ curl -X POST http://localhost:3000/tools/call \
195
+ -H "Content-Type: application/json" \
196
+ -d '{
197
+ "name": "generate_nano_banana",
198
+ "arguments": {
199
+ "prompt": "A cat wearing a space helmet"
200
+ }
201
+ }'
202
+ ```
203
+
204
+ ### Video Generation with Options
205
+ ```bash
206
+ # Generate a video
207
+ curl -X POST http://localhost:3000/tools/call \
208
+ -H "Content-Type: application/json" \
209
+ -d '{
210
+ "name": "generate_veo3_video",
211
+ "arguments": {
212
+ "prompt": "A peaceful garden with blooming flowers",
213
+ "aspectRatio": "16:9",
214
+ "model": "veo3_fast"
215
+ }
216
+ }'
217
+ ```
218
+
219
+ ## Error Handling
220
+
221
+ The server handles these HTTP error codes from Kie.ai:
222
+
223
+ - **200**: Success
224
+ - **400**: Content policy violation / English prompts only
225
+ - **401**: Unauthorized (invalid API key)
226
+ - **402**: Insufficient credits
227
+ - **404**: Resource not found
228
+ - **422**: Validation error / record is null
229
+ - **429**: Rate limited
230
+ - **451**: Image access limits
231
+ - **455**: Service maintenance
232
+ - **500**: Server error / timeout
233
+ - **501**: Generation failed
234
+
235
+ ## Development
236
+
237
+ ```bash
238
+ # Run tests
239
+ npm test
240
+
241
+ # Development mode with auto-reload
242
+ npm run dev
243
+
244
+ # Type checking
245
+ npx tsc --noEmit
246
+
247
+ # Build for production
248
+ npm run build
249
+ ```
250
+
251
+ ## Pricing
252
+
253
+ Based on Kie.ai documentation:
254
+ - **Nano Banana**: $0.020 per image (4 credits)
255
+ - **Veo3 Quality**: Higher cost tier
256
+ - **Veo3 Fast**: ~20% of Quality model pricing
257
+
258
+ See https://kie.ai/billing for detailed pricing.
259
+
260
+ ## Production Tips
261
+
262
+ 1. **Database Location**: Set `KIE_AI_DB_PATH` to a persistent location
263
+ 2. **API Key Security**: Never commit API keys to version control
264
+ 3. **Rate Limiting**: Implement client-side rate limiting for high-volume usage
265
+ 4. **Monitoring**: Monitor task status and handle failed generations appropriately
266
+ 5. **Storage**: Consider automatic cleanup of old task records
267
+
268
+ ## Troubleshooting
269
+
270
+ ### Common Issues
271
+
272
+ **"Unauthorized" errors**
273
+ - Verify `KIE_AI_API_KEY` is set correctly
274
+ - Check API key is valid at https://kie.ai/api-key
275
+
276
+ **"Task not found" errors**
277
+ - Tasks may expire after 14 days
278
+ - Check task ID format matches expected pattern
279
+
280
+ **Generation failures**
281
+ - Check content policy compliance
282
+ - Verify prompt is in English
283
+ - Ensure sufficient API credits
284
+
285
+ ## Support
286
+
287
+ For issues related to:
288
+ - **MCP Server**: Open an issue at https://github.com/andrewlwn77/kie-ai-mcp-server/issues
289
+ - **Kie.ai API**: Contact support@kie.ai or check https://docs.kie.ai/
290
+ - **API Keys**: Visit https://kie.ai/api-key
291
+
292
+ ## License
293
+
294
+ MIT License - see LICENSE file for details.
295
+
296
+ ## Contributing
297
+
298
+ 1. Fork the repository
299
+ 2. Create a feature branch
300
+ 3. Make your changes
301
+ 4. Add tests if applicable
302
+ 5. Submit a pull request
303
+
304
+ ## Changelog
305
+
306
+ ### v1.0.0
307
+ - Initial release
308
+ - Nano Banana image generation and editing
309
+ - Veo3 video generation
310
+ - 1080p video upgrade support
311
+ - SQLite task tracking
312
+ - Smart endpoint routing
313
+ - Comprehensive error handling
@@ -0,0 +1,12 @@
1
+ import { TaskRecord } from './types.js';
2
+ export declare class TaskDatabase {
3
+ private db;
4
+ constructor(dbPath?: string);
5
+ private initializeDatabase;
6
+ createTask(taskData: Omit<TaskRecord, 'id' | 'created_at' | 'updated_at'>): Promise<void>;
7
+ getTask(taskId: string): Promise<TaskRecord | null>;
8
+ updateTask(taskId: string, updates: Partial<TaskRecord>): Promise<void>;
9
+ getAllTasks(limit?: number): Promise<TaskRecord[]>;
10
+ getTasksByStatus(status: string, limit?: number): Promise<TaskRecord[]>;
11
+ close(): Promise<void>;
12
+ }
@@ -0,0 +1,105 @@
1
+ import sqlite3 from 'sqlite3';
2
+ export class TaskDatabase {
3
+ db;
4
+ constructor(dbPath = './tasks.db') {
5
+ this.db = new sqlite3.Database(dbPath);
6
+ this.initializeDatabase();
7
+ }
8
+ initializeDatabase() {
9
+ this.db.serialize(() => {
10
+ this.db.run(`
11
+ CREATE TABLE IF NOT EXISTS tasks (
12
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
13
+ task_id TEXT UNIQUE NOT NULL,
14
+ api_type TEXT NOT NULL,
15
+ status TEXT DEFAULT 'pending',
16
+ created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
17
+ updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
18
+ result_url TEXT,
19
+ error_message TEXT
20
+ )
21
+ `);
22
+ this.db.run(`CREATE INDEX IF NOT EXISTS idx_task_id ON tasks(task_id)`);
23
+ this.db.run(`CREATE INDEX IF NOT EXISTS idx_status ON tasks(status)`);
24
+ });
25
+ }
26
+ async createTask(taskData) {
27
+ return new Promise((resolve, reject) => {
28
+ this.db.run(`INSERT INTO tasks (task_id, api_type, status, result_url, error_message)
29
+ VALUES (?, ?, ?, ?, ?)`, [taskData.task_id, taskData.api_type, taskData.status, taskData.result_url || null, taskData.error_message || null], function (err) {
30
+ if (err)
31
+ reject(err);
32
+ else
33
+ resolve();
34
+ });
35
+ });
36
+ }
37
+ async getTask(taskId) {
38
+ return new Promise((resolve, reject) => {
39
+ this.db.get(`SELECT * FROM tasks WHERE task_id = ?`, [taskId], (err, row) => {
40
+ if (err)
41
+ reject(err);
42
+ else
43
+ resolve(row || null);
44
+ });
45
+ });
46
+ }
47
+ async updateTask(taskId, updates) {
48
+ const updateFields = [];
49
+ const values = [];
50
+ if (updates.status) {
51
+ updateFields.push('status = ?');
52
+ values.push(updates.status);
53
+ }
54
+ if (updates.result_url) {
55
+ updateFields.push('result_url = ?');
56
+ values.push(updates.result_url);
57
+ }
58
+ if (updates.error_message) {
59
+ updateFields.push('error_message = ?');
60
+ values.push(updates.error_message);
61
+ }
62
+ updateFields.push('updated_at = CURRENT_TIMESTAMP');
63
+ values.push(taskId);
64
+ if (updateFields.length > 1) {
65
+ return new Promise((resolve, reject) => {
66
+ this.db.run(`UPDATE tasks SET ${updateFields.join(', ')} WHERE task_id = ?`, values, function (err) {
67
+ if (err)
68
+ reject(err);
69
+ else
70
+ resolve();
71
+ });
72
+ });
73
+ }
74
+ }
75
+ async getAllTasks(limit = 100) {
76
+ return new Promise((resolve, reject) => {
77
+ this.db.all(`SELECT * FROM tasks ORDER BY created_at DESC LIMIT ?`, [limit], (err, rows) => {
78
+ if (err)
79
+ reject(err);
80
+ else
81
+ resolve(rows);
82
+ });
83
+ });
84
+ }
85
+ async getTasksByStatus(status, limit = 50) {
86
+ return new Promise((resolve, reject) => {
87
+ this.db.all(`SELECT * FROM tasks WHERE status = ? ORDER BY created_at DESC LIMIT ?`, [status, limit], (err, rows) => {
88
+ if (err)
89
+ reject(err);
90
+ else
91
+ resolve(rows);
92
+ });
93
+ });
94
+ }
95
+ async close() {
96
+ return new Promise((resolve, reject) => {
97
+ this.db.close((err) => {
98
+ if (err)
99
+ reject(err);
100
+ else
101
+ resolve();
102
+ });
103
+ });
104
+ }
105
+ }
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,455 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema, ErrorCode, ListToolsRequestSchema, McpError, } from '@modelcontextprotocol/sdk/types.js';
5
+ import { KieAiClient } from './kie-ai-client.js';
6
+ import { TaskDatabase } from './database.js';
7
+ import { NanoBananaGenerateSchema, NanoBananaEditSchema, Veo3GenerateSchema } from './types.js';
8
+ class KieAiMcpServer {
9
+ server;
10
+ client;
11
+ db;
12
+ constructor() {
13
+ this.server = new Server({
14
+ name: 'kie-ai-mcp-server',
15
+ version: '1.0.0',
16
+ });
17
+ // Initialize client with config from environment
18
+ const config = {
19
+ apiKey: process.env.KIE_AI_API_KEY || '',
20
+ baseUrl: process.env.KIE_AI_BASE_URL || 'https://api.kie.ai/api/v1',
21
+ timeout: parseInt(process.env.KIE_AI_TIMEOUT || '60000')
22
+ };
23
+ if (!config.apiKey) {
24
+ throw new Error('KIE_AI_API_KEY environment variable is required');
25
+ }
26
+ this.client = new KieAiClient(config);
27
+ this.db = new TaskDatabase(process.env.KIE_AI_DB_PATH);
28
+ this.setupHandlers();
29
+ }
30
+ setupHandlers() {
31
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => {
32
+ return {
33
+ tools: [
34
+ {
35
+ name: 'generate_nano_banana',
36
+ description: 'Generate images using Google\'s Gemini 2.5 Flash Image Preview (Nano Banana)',
37
+ inputSchema: {
38
+ type: 'object',
39
+ properties: {
40
+ prompt: {
41
+ type: 'string',
42
+ description: 'Text prompt for image generation',
43
+ minLength: 1,
44
+ maxLength: 1000
45
+ }
46
+ },
47
+ required: ['prompt']
48
+ }
49
+ },
50
+ {
51
+ name: 'edit_nano_banana',
52
+ description: 'Edit images using natural language prompts with Nano Banana Edit',
53
+ inputSchema: {
54
+ type: 'object',
55
+ properties: {
56
+ prompt: {
57
+ type: 'string',
58
+ description: 'Text prompt for image editing',
59
+ minLength: 1,
60
+ maxLength: 1000
61
+ },
62
+ image_urls: {
63
+ type: 'array',
64
+ description: 'URLs of input images for editing (max 5)',
65
+ items: { type: 'string', format: 'uri' },
66
+ minItems: 1,
67
+ maxItems: 5
68
+ }
69
+ },
70
+ required: ['prompt', 'image_urls']
71
+ }
72
+ },
73
+ {
74
+ name: 'generate_veo3_video',
75
+ description: 'Generate professional-quality videos using Google\'s Veo3 API',
76
+ inputSchema: {
77
+ type: 'object',
78
+ properties: {
79
+ prompt: {
80
+ type: 'string',
81
+ description: 'Text prompt describing desired video content',
82
+ minLength: 1,
83
+ maxLength: 2000
84
+ },
85
+ imageUrls: {
86
+ type: 'array',
87
+ description: 'Image URLs for image-to-video generation (max 1)',
88
+ items: { type: 'string', format: 'uri' },
89
+ maxItems: 1
90
+ },
91
+ model: {
92
+ type: 'string',
93
+ enum: ['veo3', 'veo3_fast'],
94
+ description: 'Model type: veo3 (quality) or veo3_fast (cost-efficient)',
95
+ default: 'veo3'
96
+ },
97
+ watermark: {
98
+ type: 'string',
99
+ description: 'Watermark text to add to video',
100
+ maxLength: 100
101
+ },
102
+ aspectRatio: {
103
+ type: 'string',
104
+ enum: ['16:9', '9:16'],
105
+ description: 'Video aspect ratio',
106
+ default: '16:9'
107
+ },
108
+ seeds: {
109
+ type: 'integer',
110
+ description: 'Random seed for consistent results',
111
+ minimum: 10000,
112
+ maximum: 99999
113
+ },
114
+ enableFallback: {
115
+ type: 'boolean',
116
+ description: 'Enable fallback mechanism for content policy failures',
117
+ default: false
118
+ }
119
+ },
120
+ required: ['prompt']
121
+ }
122
+ },
123
+ {
124
+ name: 'get_task_status',
125
+ description: 'Get the status of a generation task',
126
+ inputSchema: {
127
+ type: 'object',
128
+ properties: {
129
+ task_id: {
130
+ type: 'string',
131
+ description: 'Task ID to check status for'
132
+ }
133
+ },
134
+ required: ['task_id']
135
+ }
136
+ },
137
+ {
138
+ name: 'list_tasks',
139
+ description: 'List recent tasks with their status',
140
+ inputSchema: {
141
+ type: 'object',
142
+ properties: {
143
+ limit: {
144
+ type: 'integer',
145
+ description: 'Maximum number of tasks to return',
146
+ default: 20,
147
+ maximum: 100
148
+ },
149
+ status: {
150
+ type: 'string',
151
+ description: 'Filter by status',
152
+ enum: ['pending', 'processing', 'completed', 'failed']
153
+ }
154
+ }
155
+ }
156
+ },
157
+ {
158
+ name: 'get_veo3_1080p_video',
159
+ description: 'Get 1080P high-definition version of a Veo3 video (not available for fallback mode videos)',
160
+ inputSchema: {
161
+ type: 'object',
162
+ properties: {
163
+ task_id: {
164
+ type: 'string',
165
+ description: 'Veo3 task ID to get 1080p video for'
166
+ },
167
+ index: {
168
+ type: 'integer',
169
+ description: 'Video index (optional, for multiple video results)',
170
+ minimum: 0
171
+ }
172
+ },
173
+ required: ['task_id']
174
+ }
175
+ }
176
+ ]
177
+ };
178
+ });
179
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
180
+ try {
181
+ const { name, arguments: args } = request.params;
182
+ switch (name) {
183
+ case 'generate_nano_banana':
184
+ return await this.handleGenerateNanoBanana(args);
185
+ case 'edit_nano_banana':
186
+ return await this.handleEditNanoBanana(args);
187
+ case 'generate_veo3_video':
188
+ return await this.handleGenerateVeo3Video(args);
189
+ case 'get_task_status':
190
+ return await this.handleGetTaskStatus(args);
191
+ case 'list_tasks':
192
+ return await this.handleListTasks(args);
193
+ case 'get_veo3_1080p_video':
194
+ return await this.handleGetVeo1080pVideo(args);
195
+ default:
196
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${name}`);
197
+ }
198
+ }
199
+ catch (error) {
200
+ if (error instanceof McpError) {
201
+ throw error;
202
+ }
203
+ const message = error instanceof Error ? error.message : 'Unknown error';
204
+ throw new McpError(ErrorCode.InternalError, message);
205
+ }
206
+ });
207
+ }
208
+ async handleGenerateNanoBanana(args) {
209
+ const request = NanoBananaGenerateSchema.parse(args);
210
+ try {
211
+ const response = await this.client.generateNanoBanana(request);
212
+ if (response.data?.taskId) {
213
+ await this.db.createTask({
214
+ task_id: response.data.taskId,
215
+ api_type: 'nano-banana',
216
+ status: 'pending',
217
+ result_url: response.data.imageUrl
218
+ });
219
+ }
220
+ return {
221
+ content: [
222
+ {
223
+ type: 'text',
224
+ text: JSON.stringify({
225
+ success: true,
226
+ response: response,
227
+ message: 'Nano Banana image generation initiated'
228
+ }, null, 2)
229
+ }
230
+ ]
231
+ };
232
+ }
233
+ catch (error) {
234
+ const message = error instanceof Error ? error.message : 'Generation failed';
235
+ return {
236
+ content: [
237
+ {
238
+ type: 'text',
239
+ text: JSON.stringify({
240
+ success: false,
241
+ error: message
242
+ }, null, 2)
243
+ }
244
+ ]
245
+ };
246
+ }
247
+ }
248
+ async handleEditNanoBanana(args) {
249
+ const request = NanoBananaEditSchema.parse(args);
250
+ try {
251
+ const response = await this.client.editNanoBanana(request);
252
+ if (response.data?.taskId) {
253
+ await this.db.createTask({
254
+ task_id: response.data.taskId,
255
+ api_type: 'nano-banana-edit',
256
+ status: 'pending',
257
+ result_url: response.data.imageUrl
258
+ });
259
+ }
260
+ return {
261
+ content: [
262
+ {
263
+ type: 'text',
264
+ text: JSON.stringify({
265
+ success: true,
266
+ response: response,
267
+ message: 'Nano Banana image editing initiated'
268
+ }, null, 2)
269
+ }
270
+ ]
271
+ };
272
+ }
273
+ catch (error) {
274
+ const message = error instanceof Error ? error.message : 'Editing failed';
275
+ return {
276
+ content: [
277
+ {
278
+ type: 'text',
279
+ text: JSON.stringify({
280
+ success: false,
281
+ error: message
282
+ }, null, 2)
283
+ }
284
+ ]
285
+ };
286
+ }
287
+ }
288
+ async handleGenerateVeo3Video(args) {
289
+ const request = Veo3GenerateSchema.parse(args);
290
+ try {
291
+ const response = await this.client.generateVeo3Video(request);
292
+ if (response.data?.taskId) {
293
+ await this.db.createTask({
294
+ task_id: response.data.taskId,
295
+ api_type: 'veo3',
296
+ status: 'pending'
297
+ });
298
+ }
299
+ return {
300
+ content: [
301
+ {
302
+ type: 'text',
303
+ text: JSON.stringify({
304
+ success: true,
305
+ task_id: response.data?.taskId,
306
+ message: 'Veo3 video generation task created successfully',
307
+ note: 'Use get_task_status to check progress'
308
+ }, null, 2)
309
+ }
310
+ ]
311
+ };
312
+ }
313
+ catch (error) {
314
+ const message = error instanceof Error ? error.message : 'Video generation failed';
315
+ return {
316
+ content: [
317
+ {
318
+ type: 'text',
319
+ text: JSON.stringify({
320
+ success: false,
321
+ error: message
322
+ }, null, 2)
323
+ }
324
+ ]
325
+ };
326
+ }
327
+ }
328
+ async handleGetTaskStatus(args) {
329
+ const { task_id } = args;
330
+ if (!task_id || typeof task_id !== 'string') {
331
+ throw new McpError(ErrorCode.InvalidParams, 'task_id is required and must be a string');
332
+ }
333
+ try {
334
+ const localTask = await this.db.getTask(task_id);
335
+ // Always try to get updated status from API, passing api_type if available
336
+ let apiResponse = null;
337
+ try {
338
+ apiResponse = await this.client.getTaskStatus(task_id, localTask?.api_type);
339
+ }
340
+ catch (error) {
341
+ // API call failed, use local data if available
342
+ }
343
+ return {
344
+ content: [
345
+ {
346
+ type: 'text',
347
+ text: JSON.stringify({
348
+ success: true,
349
+ local_task: localTask,
350
+ api_response: apiResponse,
351
+ message: localTask ? 'Task found' : 'Task not found in local database'
352
+ }, null, 2)
353
+ }
354
+ ]
355
+ };
356
+ }
357
+ catch (error) {
358
+ const message = error instanceof Error ? error.message : 'Failed to get task status';
359
+ return {
360
+ content: [
361
+ {
362
+ type: 'text',
363
+ text: JSON.stringify({
364
+ success: false,
365
+ error: message
366
+ }, null, 2)
367
+ }
368
+ ]
369
+ };
370
+ }
371
+ }
372
+ async handleListTasks(args) {
373
+ const { limit = 20, status } = args;
374
+ try {
375
+ let tasks;
376
+ if (status) {
377
+ tasks = await this.db.getTasksByStatus(status, limit);
378
+ }
379
+ else {
380
+ tasks = await this.db.getAllTasks(limit);
381
+ }
382
+ return {
383
+ content: [
384
+ {
385
+ type: 'text',
386
+ text: JSON.stringify({
387
+ success: true,
388
+ tasks: tasks,
389
+ count: tasks.length,
390
+ message: `Retrieved ${tasks.length} tasks`
391
+ }, null, 2)
392
+ }
393
+ ]
394
+ };
395
+ }
396
+ catch (error) {
397
+ const message = error instanceof Error ? error.message : 'Failed to list tasks';
398
+ return {
399
+ content: [
400
+ {
401
+ type: 'text',
402
+ text: JSON.stringify({
403
+ success: false,
404
+ error: message
405
+ }, null, 2)
406
+ }
407
+ ]
408
+ };
409
+ }
410
+ }
411
+ async handleGetVeo1080pVideo(args) {
412
+ const { task_id, index } = args;
413
+ if (!task_id || typeof task_id !== 'string') {
414
+ throw new McpError(ErrorCode.InvalidParams, 'task_id is required and must be a string');
415
+ }
416
+ try {
417
+ const response = await this.client.getVeo1080pVideo(task_id, index);
418
+ return {
419
+ content: [
420
+ {
421
+ type: 'text',
422
+ text: JSON.stringify({
423
+ success: true,
424
+ task_id: task_id,
425
+ response: response,
426
+ message: 'Retrieved 1080p video URL',
427
+ note: 'Not available for videos generated with fallback mode'
428
+ }, null, 2)
429
+ }
430
+ ]
431
+ };
432
+ }
433
+ catch (error) {
434
+ const message = error instanceof Error ? error.message : 'Failed to get 1080p video';
435
+ return {
436
+ content: [
437
+ {
438
+ type: 'text',
439
+ text: JSON.stringify({
440
+ success: false,
441
+ error: message
442
+ }, null, 2)
443
+ }
444
+ ]
445
+ };
446
+ }
447
+ }
448
+ async run() {
449
+ const transport = new StdioServerTransport();
450
+ await this.server.connect(transport);
451
+ }
452
+ }
453
+ // Start the server
454
+ const server = new KieAiMcpServer();
455
+ server.run().catch(console.error);
@@ -0,0 +1,11 @@
1
+ import { KieAiConfig, KieAiResponse, NanoBananaGenerateRequest, NanaBananaEditRequest, Veo3GenerateRequest, ImageResponse, TaskResponse } from './types.js';
2
+ export declare class KieAiClient {
3
+ private config;
4
+ constructor(config: KieAiConfig);
5
+ private makeRequest;
6
+ generateNanoBanana(request: NanoBananaGenerateRequest): Promise<KieAiResponse<ImageResponse>>;
7
+ editNanoBanana(request: NanaBananaEditRequest): Promise<KieAiResponse<ImageResponse>>;
8
+ generateVeo3Video(request: Veo3GenerateRequest): Promise<KieAiResponse<TaskResponse>>;
9
+ getTaskStatus(taskId: string, apiType?: string): Promise<KieAiResponse<any>>;
10
+ getVeo1080pVideo(taskId: string, index?: number): Promise<KieAiResponse<any>>;
11
+ }
@@ -0,0 +1,85 @@
1
+ export class KieAiClient {
2
+ config;
3
+ constructor(config) {
4
+ this.config = config;
5
+ }
6
+ async makeRequest(endpoint, method = 'POST', body) {
7
+ const url = `${this.config.baseUrl}${endpoint}`;
8
+ const headers = {
9
+ 'Authorization': `Bearer ${this.config.apiKey}`,
10
+ 'Content-Type': 'application/json'
11
+ };
12
+ const requestOptions = {
13
+ method,
14
+ headers,
15
+ signal: AbortSignal.timeout(this.config.timeout)
16
+ };
17
+ if (body && method === 'POST') {
18
+ requestOptions.body = JSON.stringify(body);
19
+ }
20
+ try {
21
+ const response = await fetch(url, requestOptions);
22
+ const data = await response.json();
23
+ if (!response.ok) {
24
+ throw new Error(`HTTP ${response.status}: ${data.msg || 'Unknown error'}`);
25
+ }
26
+ return data;
27
+ }
28
+ catch (error) {
29
+ if (error instanceof Error) {
30
+ throw new Error(`Request failed: ${error.message}`);
31
+ }
32
+ throw error;
33
+ }
34
+ }
35
+ async generateNanoBanana(request) {
36
+ const playgroundRequest = {
37
+ model: 'google/nano-banana',
38
+ input: {
39
+ prompt: request.prompt
40
+ }
41
+ };
42
+ return this.makeRequest('/playground/createTask', 'POST', playgroundRequest);
43
+ }
44
+ async editNanoBanana(request) {
45
+ const playgroundRequest = {
46
+ model: 'google/nano-banana-edit',
47
+ input: {
48
+ prompt: request.prompt,
49
+ image_urls: request.image_urls
50
+ }
51
+ };
52
+ return this.makeRequest('/playground/createTask', 'POST', playgroundRequest);
53
+ }
54
+ async generateVeo3Video(request) {
55
+ return this.makeRequest('/veo/generate', 'POST', request);
56
+ }
57
+ async getTaskStatus(taskId, apiType) {
58
+ // Use api_type to determine correct endpoint, with fallback strategy
59
+ if (apiType === 'veo3') {
60
+ return this.makeRequest(`/veo/record-info?taskId=${taskId}`, 'GET');
61
+ }
62
+ else if (apiType === 'nano-banana' || apiType === 'nano-banana-edit') {
63
+ return this.makeRequest(`/playground/recordInfo?taskId=${taskId}`, 'GET');
64
+ }
65
+ // Fallback: try playground first, then veo (for tasks not in database)
66
+ try {
67
+ return await this.makeRequest(`/playground/recordInfo?taskId=${taskId}`, 'GET');
68
+ }
69
+ catch (error) {
70
+ try {
71
+ return await this.makeRequest(`/veo/record-info?taskId=${taskId}`, 'GET');
72
+ }
73
+ catch (veoError) {
74
+ throw error;
75
+ }
76
+ }
77
+ }
78
+ async getVeo1080pVideo(taskId, index) {
79
+ const params = new URLSearchParams({ taskId });
80
+ if (index !== undefined) {
81
+ params.append('index', index.toString());
82
+ }
83
+ return this.makeRequest(`/veo/get-1080p-video?${params}`, 'GET');
84
+ }
85
+ }
@@ -0,0 +1,76 @@
1
+ import { z } from 'zod';
2
+ export declare const NanoBananaGenerateSchema: z.ZodObject<{
3
+ prompt: z.ZodString;
4
+ }, "strip", z.ZodTypeAny, {
5
+ prompt: string;
6
+ }, {
7
+ prompt: string;
8
+ }>;
9
+ export declare const NanoBananaEditSchema: z.ZodObject<{
10
+ prompt: z.ZodString;
11
+ image_urls: z.ZodArray<z.ZodString, "many">;
12
+ }, "strip", z.ZodTypeAny, {
13
+ prompt: string;
14
+ image_urls: string[];
15
+ }, {
16
+ prompt: string;
17
+ image_urls: string[];
18
+ }>;
19
+ export declare const Veo3GenerateSchema: z.ZodObject<{
20
+ prompt: z.ZodString;
21
+ imageUrls: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
22
+ model: z.ZodDefault<z.ZodEnum<["veo3", "veo3_fast"]>>;
23
+ watermark: z.ZodOptional<z.ZodString>;
24
+ aspectRatio: z.ZodDefault<z.ZodEnum<["16:9", "9:16"]>>;
25
+ seeds: z.ZodOptional<z.ZodNumber>;
26
+ callBackUrl: z.ZodOptional<z.ZodString>;
27
+ enableFallback: z.ZodDefault<z.ZodBoolean>;
28
+ }, "strip", z.ZodTypeAny, {
29
+ prompt: string;
30
+ model: "veo3" | "veo3_fast";
31
+ aspectRatio: "16:9" | "9:16";
32
+ enableFallback: boolean;
33
+ imageUrls?: string[] | undefined;
34
+ watermark?: string | undefined;
35
+ seeds?: number | undefined;
36
+ callBackUrl?: string | undefined;
37
+ }, {
38
+ prompt: string;
39
+ imageUrls?: string[] | undefined;
40
+ model?: "veo3" | "veo3_fast" | undefined;
41
+ watermark?: string | undefined;
42
+ aspectRatio?: "16:9" | "9:16" | undefined;
43
+ seeds?: number | undefined;
44
+ callBackUrl?: string | undefined;
45
+ enableFallback?: boolean | undefined;
46
+ }>;
47
+ export type NanoBananaGenerateRequest = z.infer<typeof NanoBananaGenerateSchema>;
48
+ export type NanaBananaEditRequest = z.infer<typeof NanoBananaEditSchema>;
49
+ export type Veo3GenerateRequest = z.infer<typeof Veo3GenerateSchema>;
50
+ export interface KieAiResponse<T = any> {
51
+ code: number;
52
+ msg: string;
53
+ data?: T;
54
+ }
55
+ export interface ImageResponse {
56
+ imageUrl?: string;
57
+ taskId?: string;
58
+ }
59
+ export interface TaskResponse {
60
+ taskId: string;
61
+ }
62
+ export interface TaskRecord {
63
+ id?: number;
64
+ task_id: string;
65
+ api_type: 'nano-banana' | 'nano-banana-edit' | 'veo3';
66
+ status: 'pending' | 'processing' | 'completed' | 'failed';
67
+ created_at: string;
68
+ updated_at: string;
69
+ result_url?: string;
70
+ error_message?: string;
71
+ }
72
+ export interface KieAiConfig {
73
+ apiKey: string;
74
+ baseUrl: string;
75
+ timeout: number;
76
+ }
package/dist/types.js ADDED
@@ -0,0 +1,19 @@
1
+ import { z } from 'zod';
2
+ // Zod schemas for request validation
3
+ export const NanoBananaGenerateSchema = z.object({
4
+ prompt: z.string().min(1).max(1000)
5
+ });
6
+ export const NanoBananaEditSchema = z.object({
7
+ prompt: z.string().min(1).max(1000),
8
+ image_urls: z.array(z.string().url()).min(1).max(5)
9
+ });
10
+ export const Veo3GenerateSchema = z.object({
11
+ prompt: z.string().min(1).max(2000),
12
+ imageUrls: z.array(z.string().url()).max(1).optional(),
13
+ model: z.enum(['veo3', 'veo3_fast']).default('veo3'),
14
+ watermark: z.string().max(100).optional(),
15
+ aspectRatio: z.enum(['16:9', '9:16']).default('16:9'),
16
+ seeds: z.number().int().min(10000).max(99999).optional(),
17
+ callBackUrl: z.string().url().optional(),
18
+ enableFallback: z.boolean().default(false)
19
+ });
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@felores/kie-ai-mcp-server",
3
+ "version": "1.0.0",
4
+ "description": "MCP server for Kie.ai APIs (Nano Banana image generation/editing and Veo3 video generation)",
5
+ "main": "dist/index.js",
6
+ "bin": {
7
+ "kie-ai-mcp-server": "dist/index.js"
8
+ },
9
+ "type": "module",
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "start": "node dist/index.js",
13
+ "dev": "tsx src/index.ts",
14
+ "test": "jest",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "keywords": [
18
+ "mcp",
19
+ "kie.ai",
20
+ "ai",
21
+ "image-generation",
22
+ "video-generation",
23
+ "nano-banana",
24
+ "veo3",
25
+ "model-context-protocol"
26
+ ],
27
+ "author": "felores",
28
+ "license": "MIT",
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "git+https://github.com/felo/kie-ai-mcp-server.git"
32
+ },
33
+ "homepage": "https://github.com/felo/kie-ai-mcp-server#readme",
34
+ "bugs": {
35
+ "url": "https://github.com/felo/kie-ai-mcp-server/issues"
36
+ },
37
+ "dependencies": {
38
+ "@modelcontextprotocol/sdk": "^0.4.0",
39
+ "sqlite3": "^5.1.6",
40
+ "zod": "^3.22.4"
41
+ },
42
+ "devDependencies": {
43
+ "@types/node": "^20.10.0",
44
+ "@types/sqlite3": "^3.1.11",
45
+ "typescript": "^5.3.0",
46
+ "tsx": "^4.6.0",
47
+ "jest": "^29.7.0",
48
+ "@types/jest": "^29.5.8"
49
+ },
50
+ "engines": {
51
+ "node": ">=18.0.0"
52
+ },
53
+ "files": [
54
+ "dist",
55
+ "README.md",
56
+ "LICENSE"
57
+ ]
58
+ }