@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 +21 -0
- package/README.md +313 -0
- package/dist/database.d.ts +12 -0
- package/dist/database.js +105 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +455 -0
- package/dist/kie-ai-client.d.ts +11 -0
- package/dist/kie-ai-client.js +85 -0
- package/dist/types.d.ts +76 -0
- package/dist/types.js +19 -0
- package/package.json +58 -0
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
|
+
}
|
package/dist/database.js
ADDED
|
@@ -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
|
+
}
|
package/dist/index.d.ts
ADDED
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
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -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
|
+
}
|