@codingmantra/mcp-server 0.1.3 → 0.2.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/build/index.js CHANGED
@@ -3,35 +3,62 @@ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import { CallToolRequestSchema, ListToolsRequestSchema, ErrorCode, McpError } from "@modelcontextprotocol/sdk/types.js";
5
5
  import { apiClient } from './api-client.js';
6
+ import { formatImageToolResult, resolveImageInput, resolveImagesArray } from './image-utils.js';
6
7
  const server = new Server({
7
8
  name: "codingmantra-mcp",
8
- version: "0.1.0",
9
+ version: "0.2.0",
9
10
  }, {
10
11
  capabilities: {
11
12
  tools: {},
12
13
  },
13
14
  });
15
+ // ─── Reusable Parameter Definitions ──────────────────────────────────────────
16
+ const IMAGE_SOURCE_SCHEMA = {
17
+ type: "object",
18
+ properties: {
19
+ url: { type: "string", description: "Public URL of the image." },
20
+ dataUrl: { type: "string", description: "Base64 data URI of the image." },
21
+ path: { type: "string", description: "Local file path on disk (will be automatically resolved to base64)." }
22
+ }
23
+ };
24
+ const COMMON_VIDEO_PROPERTIES = {
25
+ prompt: { type: "string", description: "Detailed video description or creative prompt." },
26
+ mainImage: { type: "string", description: "Base64 data URI, local file path, or public URL for the main subject image (Image-to-Video mode)." },
27
+ startImage: { type: "string", description: "Base64 data URI, local file path, or public URL for the first frame." },
28
+ endImage: { type: "string", description: "Base64 data URI, local file path, or public URL for the last frame." },
29
+ referenceImages: {
30
+ type: "array",
31
+ items: { type: "object", properties: { image: { type: "string" }, label: { type: "string" } } },
32
+ description: "Array of reference images for style matching."
33
+ },
34
+ negativePrompt: { type: "string", description: "Elements to exclude from the video." },
35
+ dialogue: { type: "string", description: "Optional voiceover or dialogue script." },
36
+ soundSuggestion: { type: "string", description: "Background music or sound effects direction." },
37
+ cameraMotion: { type: "string", description: "Camera motion instructions (e.g., 'orbit right', 'slow push-in')." },
38
+ ambientNoise: { type: "string", description: "Ambient sound description." },
39
+ language: { type: "string", description: "Voiceover language." },
40
+ timeDuration: { type: "string", description: "Video duration in seconds. Default is '4'." },
41
+ resolution: { type: "string", description: "Resolution (e.g., '720p', '1080p'). Default is '720p'." },
42
+ model: { type: "string", description: "Model slug (e.g., 'google/veo-3.1-fast', 'openrouter/wan-2.7')." },
43
+ aspectRatio: { type: "string", description: "Aspect ratio (e.g., '9:16', '16:9'). Default is '9:16'." },
44
+ generateAudio: { type: "boolean", description: "Whether to generate synchronized audio. Default is false." }
45
+ };
14
46
  // ─── Tool Definitions ──────────────────────────────────────────────────────────
47
+ // 1. Product Photography
15
48
  const GENERATE_PRODUCT_PHOTO_TOOL = {
16
49
  name: "generate_product_photo",
17
- description: "Generates an AI product photography image with a specific scene prompt. Automatically deducts credits from your CodingMantra account. Returns the generated image directly (synchronous).",
50
+ description: "Generates studio-grade commercial product photography with customizable background scenes, ambient occlusion contact shadows, camera optics, resolutions (1K, 2K, 4K), aspect ratios, and dynamic logo/text overlays. Returns the image directly for inline display (synchronous).",
18
51
  inputSchema: {
19
52
  type: "object",
20
53
  properties: {
21
54
  productImages: {
22
55
  type: "array",
23
- items: {
24
- type: "object",
25
- properties: {
26
- url: { type: "string", description: "Public URL of the product image." },
27
- dataUrl: { type: "string", description: "Base64 data URI of the product image." }
28
- }
29
- },
30
- description: "Array of product images. Each must have either a 'url' or 'dataUrl'. At least one required."
56
+ items: IMAGE_SOURCE_SCHEMA,
57
+ description: "Array of product images. Each item can have 'url', 'dataUrl', or local 'path'. At least one required."
31
58
  },
32
59
  scenePrompt: {
33
60
  type: "string",
34
- description: "A detailed description of the background scene and setting for the product photo."
61
+ description: "A detailed description of the background scene, surface, and environment for the product photo."
35
62
  },
36
63
  model: {
37
64
  type: "string",
@@ -39,23 +66,23 @@ const GENERATE_PRODUCT_PHOTO_TOOL = {
39
66
  },
40
67
  resolution: {
41
68
  type: "string",
42
- description: "Output resolution (e.g., '1K', '2K', '4K'). Default is '1K'.",
69
+ description: "Output resolution ('1K', '2K', '4K'). Default is '1K'.",
43
70
  },
44
71
  aspectRatio: {
45
72
  type: "string",
46
- description: "Output aspect ratio (e.g., '1:1', '16:9', '9:16'). Default is '1:1'.",
73
+ description: "Output aspect ratio ('1:1', '16:9', '9:16', '4:5'). Default is '1:1'.",
47
74
  },
48
75
  referenceImage: {
49
76
  type: "string",
50
- description: "An optional URL or data URI of a reference image to guide style."
77
+ description: "Optional URL, data URI, or local file path of a style reference image."
51
78
  },
52
79
  promptId: {
53
80
  type: "string",
54
- description: "Optional ID of a pre-defined prompt template."
81
+ description: "Optional ID of a pre-defined background template."
55
82
  },
56
83
  logoImage: {
57
84
  type: "string",
58
- description: "Optional URL or Base64 data URI of a logo to place on the image."
85
+ description: "Optional URL, data URI, or local file path of a brand logo to place on the image."
59
86
  },
60
87
  logoPlacement: {
61
88
  type: "string",
@@ -63,7 +90,7 @@ const GENERATE_PRODUCT_PHOTO_TOOL = {
63
90
  },
64
91
  logoSize: {
65
92
  type: "number",
66
- description: "Scale of the logo overlay (0.1 to 1.0)."
93
+ description: "Scale of the logo overlay (0.05 to 0.5). Default is 0.15."
67
94
  },
68
95
  overlayText: {
69
96
  type: "string",
@@ -71,15 +98,16 @@ const GENERATE_PRODUCT_PHOTO_TOOL = {
71
98
  },
72
99
  marketingText: {
73
100
  type: "string",
74
- description: "Optional marketing text to overlay."
101
+ description: "Optional marketing text or slogan to overlay."
75
102
  }
76
103
  },
77
104
  required: ["productImages", "scenePrompt"]
78
105
  }
79
106
  };
107
+ // 2. Logo Creator
80
108
  const GENERATE_LOGO_TOOL = {
81
109
  name: "generate_logo",
82
- description: "Generates an AI brand logo. Requires a company name and style description. Automatically deducts credits from your CodingMantra account. Returns the generated logo directly (synchronous).",
110
+ description: "Generates bespoke vector-style or 3D brand logos with customizable typography, colors, industries, and reference images. Returns the generated logo directly for inline display (synchronous).",
83
111
  inputSchema: {
84
112
  type: "object",
85
113
  properties: {
@@ -89,7 +117,7 @@ const GENERATE_LOGO_TOOL = {
89
117
  },
90
118
  stylePrompt: {
91
119
  type: "string",
92
- description: "A detailed style description for the logo (e.g., 'minimalist flat vector logo, dark blue and gold color scheme, modern tech feel')."
120
+ description: "A detailed style description for the logo (e.g., 'minimalist flat vector logo, navy blue and gold, clean tech aesthetic')."
93
121
  },
94
122
  subtitle: {
95
123
  type: "string",
@@ -97,15 +125,15 @@ const GENERATE_LOGO_TOOL = {
97
125
  },
98
126
  industry: {
99
127
  type: "string",
100
- description: "The industry the company operates in (e.g., 'Technology', 'Food & Beverage')."
128
+ description: "The industry the company operates in (e.g., 'Technology', 'Luxury Jewelry', 'Fashion')."
101
129
  },
102
130
  ideas: {
103
131
  type: "string",
104
- description: "User-provided ideas, keywords, or concepts for the logo."
132
+ description: "Core keywords, symbols, or concepts for the logo."
105
133
  },
106
134
  colors: {
107
135
  type: "string",
108
- description: "Preferred color palette description (e.g., 'blue and silver', '#FF5733 and white')."
136
+ description: "Preferred color palette description (e.g., 'emerald green and brushed gold')."
109
137
  },
110
138
  aspectRatio: {
111
139
  type: "string",
@@ -113,27 +141,331 @@ const GENERATE_LOGO_TOOL = {
113
141
  },
114
142
  modelId: {
115
143
  type: "string",
116
- description: "The image model ID. Default uses the platform default.",
144
+ description: "The image model ID. Default uses platform default.",
117
145
  },
118
146
  resolution: {
119
147
  type: "string",
120
- description: "Output resolution (e.g., '1K', '2K'). Default is '1K'.",
148
+ description: "Output resolution ('1K', '2K'). Default is '1K'.",
121
149
  },
122
- promptId: {
150
+ referenceImage: {
151
+ type: "string",
152
+ description: "Optional style reference image URL, data URI, or local file path."
153
+ }
154
+ },
155
+ required: ["companyName", "stylePrompt"]
156
+ }
157
+ };
158
+ // 3. Jewelry Try-On
159
+ const TRY_ON_JEWELRY_TOOL = {
160
+ name: "try_on_jewelry",
161
+ description: "Luxury jewelry virtual try-on. Places necklaces, rings, earrings, and bracelets naturally on human models with prongs/gemstone preservation, authentic optical dispersion, and anatomical contact shadows. Returns generated image directly (synchronous).",
162
+ inputSchema: {
163
+ type: "object",
164
+ properties: {
165
+ jewelryImages: {
166
+ type: "array",
167
+ items: IMAGE_SOURCE_SCHEMA,
168
+ description: "Array of jewelry product images. Each can have 'url', 'dataUrl', or local 'path'. At least one required."
169
+ },
170
+ personImage: {
171
+ type: "string",
172
+ description: "Optional photo of a person to model the jewelry (URL, data URI, or local file path). If omitted, an elegant editorial model is generated."
173
+ },
174
+ prompt: {
175
+ type: "string",
176
+ description: "Creative styling directions (e.g., 'wearing black velvet evening gown, soft studio rim lighting, Parisian luxury ambiance')."
177
+ },
178
+ sizeMeasurement: {
179
+ type: "string",
180
+ description: "Physical dimensions (e.g., '2 cm pendant', 'ring size 7') to maintain accurate scale."
181
+ },
182
+ aspectRatio: {
123
183
  type: "string",
124
- description: "Optional ID or value of a pre-defined style template."
184
+ description: "Output aspect ratio ('1:1', '9:16', '16:9'). Default is '1:1'."
185
+ },
186
+ resolution: {
187
+ type: "string",
188
+ description: "Output resolution ('1K', '2K', '4K'). Default is '1K'."
189
+ },
190
+ model: {
191
+ type: "string",
192
+ description: "Model tier slug ('standard', 'pro', 'flash'). Default is 'pro'."
125
193
  },
126
194
  referenceImage: {
127
195
  type: "string",
128
- description: "An optional general style reference image URL or data URI."
196
+ description: "Optional campaign reference image (URL, data URI, or local path) to copy lighting and mood from."
197
+ },
198
+ logoImage: {
199
+ type: "string",
200
+ description: "Optional brand logo overlay."
201
+ },
202
+ logoPlacement: {
203
+ type: "string",
204
+ description: "Logo placement ('top-left', 'bottom-right', etc.)."
205
+ },
206
+ overlayText: {
207
+ type: "string",
208
+ description: "Optional text to overlay on the image."
209
+ },
210
+ marketingText: {
211
+ type: "string",
212
+ description: "Optional marketing text or slogan."
129
213
  }
130
214
  },
131
- required: ["companyName", "stylePrompt"]
215
+ required: ["jewelryImages"]
216
+ }
217
+ };
218
+ // 4. Apparel Try-On
219
+ const TRY_ON_APPAREL_TOOL = {
220
+ name: "try_on_apparel",
221
+ description: "Virtual fashion apparel try-on. Drapes garments realistically along model anatomy with natural gravitational folds, realistic tension, Hasselblad medium-format camera optics, and ground contact shadows. Returns generated image directly (synchronous).",
222
+ inputSchema: {
223
+ type: "object",
224
+ properties: {
225
+ apparelImages: {
226
+ type: "array",
227
+ items: IMAGE_SOURCE_SCHEMA,
228
+ description: "Array of clothing/apparel images. Each can have 'url', 'dataUrl', or local 'path'. At least one required."
229
+ },
230
+ modelImage: {
231
+ type: "string",
232
+ description: "Optional photo of a person/model (URL, data URI, or local path). If omitted, a professional fashion model is generated."
233
+ },
234
+ prompt: {
235
+ type: "string",
236
+ description: "Styling and scene creative direction (e.g., 'standing on Milan street, golden hour sunlight, editorial fashion magazine style')."
237
+ },
238
+ aspectRatio: {
239
+ type: "string",
240
+ description: "Output aspect ratio ('1:1', '9:16', '16:9', '4:5'). Default is '1:1'."
241
+ },
242
+ resolution: {
243
+ type: "string",
244
+ description: "Output resolution ('1K', '2K', '4K'). Default is '1K'."
245
+ },
246
+ referenceImage: {
247
+ type: "string",
248
+ description: "Optional style reference image URL, data URI, or local path."
249
+ },
250
+ logoImage: {
251
+ type: "string",
252
+ description: "Optional brand logo overlay."
253
+ },
254
+ overlayText: {
255
+ type: "string",
256
+ description: "Optional text overlay."
257
+ },
258
+ marketingText: {
259
+ type: "string",
260
+ description: "Optional marketing copy."
261
+ }
262
+ },
263
+ required: ["apparelImages"]
264
+ }
265
+ };
266
+ // 5. Product Try-On
267
+ const TRY_ON_PRODUCT_TOOL = {
268
+ name: "try_on_product",
269
+ description: "Universal commercial product staging and try-on across footwear, accessories, watches, eyewear, cosmetics, and furniture with 3-point studio lighting and ambient occlusion contact shadows. Returns generated image directly (synchronous).",
270
+ inputSchema: {
271
+ type: "object",
272
+ properties: {
273
+ productImages: {
274
+ type: "array",
275
+ items: IMAGE_SOURCE_SCHEMA,
276
+ description: "Array of product images. At least one required."
277
+ },
278
+ mainImage: {
279
+ type: "string",
280
+ description: "Optional base image (model portrait, empty room, or tabletop) to place the product into."
281
+ },
282
+ prompt: {
283
+ type: "string",
284
+ description: "Creative styling and environment directions."
285
+ },
286
+ sizeMeasurement: {
287
+ type: "string",
288
+ description: "Physical dimensions (e.g., '10 inches', '30 cm') to maintain exact scale."
289
+ },
290
+ aspectRatio: {
291
+ type: "string",
292
+ description: "Output aspect ratio ('1:1', '16:9', '9:16'). Default is '1:1'."
293
+ },
294
+ resolution: {
295
+ type: "string",
296
+ description: "Output resolution ('1K', '2K', '4K'). Default is '1K'."
297
+ },
298
+ referenceImage: {
299
+ type: "string",
300
+ description: "Optional style reference image."
301
+ },
302
+ logoImage: {
303
+ type: "string",
304
+ description: "Optional brand logo overlay."
305
+ },
306
+ overlayText: {
307
+ type: "string",
308
+ description: "Optional text overlay."
309
+ }
310
+ },
311
+ required: ["productImages"]
312
+ }
313
+ };
314
+ // 6. Festival Post Generator
315
+ const GENERATE_FESTIVAL_POST_TOOL = {
316
+ name: "generate_festival_post",
317
+ description: "Generates high-conversion festival marketing graphics, holiday greetings (Diwali, Eid, Christmas, New Year, Valentine's), and promotional event banners with product and brand logo integration. Returns generated image directly (synchronous).",
318
+ inputSchema: {
319
+ type: "object",
320
+ properties: {
321
+ festivalPrompt: {
322
+ type: "string",
323
+ description: "Festival theme or campaign message (e.g., 'Diwali luxury jewelry promotion with warm golden diyas and floral rangoli')."
324
+ },
325
+ productImage: {
326
+ type: "string",
327
+ description: "Optional hero product image (URL, data URI, or local file path) to place centrally in the festive composition."
328
+ },
329
+ logoImage: {
330
+ type: "string",
331
+ description: "Optional brand logo image."
332
+ },
333
+ overlayText: {
334
+ type: "string",
335
+ description: "Main headline or festive greeting text (e.g., 'Happy Diwali - 20% Off')."
336
+ },
337
+ marketingText: {
338
+ type: "string",
339
+ description: "Supporting promotional message or contact details."
340
+ },
341
+ aspectRatio: {
342
+ type: "string",
343
+ description: "Output aspect ratio ('1:1', '9:16', '16:9'). Default is '1:1'."
344
+ },
345
+ resolution: {
346
+ type: "string",
347
+ description: "Output resolution ('1K', '2K'). Default is '1K'."
348
+ }
349
+ },
350
+ required: ["festivalPrompt"]
351
+ }
352
+ };
353
+ // 7. YouTube Thumbnail Generator
354
+ const GENERATE_YOUTUBE_THUMBNAIL_TOOL = {
355
+ name: "generate_youtube_thumbnail",
356
+ description: "Generates high-CTR YouTube thumbnails with rule-of-thirds composition, emotional human expressions, vibrant contrast separation, and legible bold text overlays. Returns generated image directly (synchronous).",
357
+ inputSchema: {
358
+ type: "object",
359
+ properties: {
360
+ prompt: {
361
+ type: "string",
362
+ description: "Core video concept or thumbnail description (e.g., 'Shocked tech reviewer holding futuristic glowing AI smartphone, neon studio background')."
363
+ },
364
+ overlayText: {
365
+ type: "string",
366
+ description: "Short, punchy thumbnail headline text (e.g., 'DON\\'T BUY THIS!')."
367
+ },
368
+ personImage: {
369
+ type: "string",
370
+ description: "Optional photo of the YouTube creator/subject (URL, data URI, or local path) to feature prominently."
371
+ },
372
+ backgroundImage: {
373
+ type: "string",
374
+ description: "Optional custom background image."
375
+ },
376
+ logoImage: {
377
+ type: "string",
378
+ description: "Optional channel/brand logo to place in corner."
379
+ },
380
+ aspectRatio: {
381
+ type: "string",
382
+ description: "Thumbnail aspect ratio. Default is '16:9'."
383
+ },
384
+ resolution: {
385
+ type: "string",
386
+ description: "Output resolution ('1K', '2K'). Default is '1K'."
387
+ }
388
+ },
389
+ required: ["prompt"]
390
+ }
391
+ };
392
+ // 8. Passport Photo Creator
393
+ const GENERATE_PASSPORT_PHOTO_TOOL = {
394
+ name: "generate_passport_photo",
395
+ description: "Converts any portrait into an official, compliant passport or visa identity photo with pixel-level facial identity preservation, neutral lighting, and plain white/off-white background. Returns generated image directly (synchronous).",
396
+ inputSchema: {
397
+ type: "object",
398
+ properties: {
399
+ photoDataUri: {
400
+ type: "string",
401
+ description: "Source portrait photo (URL, data URI, or local file path)."
402
+ },
403
+ prompt: {
404
+ type: "string",
405
+ description: "Adjustments (e.g., 'white background, formal dark navy business suit, centered frontal view')."
406
+ },
407
+ aspectRatio: {
408
+ type: "string",
409
+ description: "Passport aspect ratio ('1:1', '4:5'). Default is '1:1'."
410
+ },
411
+ resolution: {
412
+ type: "string",
413
+ description: "Output resolution ('1K', '2K'). Default is '1K'."
414
+ }
415
+ },
416
+ required: ["photoDataUri"]
417
+ }
418
+ };
419
+ // 9. Sketch to Real
420
+ const SKETCH_TO_REAL_TOOL = {
421
+ name: "sketch_to_real",
422
+ description: "Transforms hand-drawn sketches, line drawings, or product drafts into photorealistic commercial images while strictly preserving the sketched silhouette, proportions, and outlines. Returns generated image directly (synchronous).",
423
+ inputSchema: {
424
+ type: "object",
425
+ properties: {
426
+ sketchImage: {
427
+ type: "string",
428
+ description: "The source sketch/drawing image (URL, data URI, or local file path)."
429
+ },
430
+ prompt: {
431
+ type: "string",
432
+ description: "Description of materials, textures, colors, and lighting (e.g., 'matte black carbon fiber body, polished titanium accents, studio lighting')."
433
+ },
434
+ style: {
435
+ type: "string",
436
+ description: "Target render style ('Photorealistic', 'Cinematic', '3D Render'). Default is 'Photorealistic'."
437
+ },
438
+ aspectRatio: {
439
+ type: "string",
440
+ description: "Output aspect ratio ('1:1', '16:9', '9:16'). Default is '1:1'."
441
+ },
442
+ resolution: {
443
+ type: "string",
444
+ description: "Output resolution ('1K', '2K', '4K'). Default is '1K'."
445
+ }
446
+ },
447
+ required: ["sketchImage"]
448
+ }
449
+ };
450
+ // 10. Upscale Image
451
+ const UPSCALE_IMAGE_TOOL = {
452
+ name: "upscale_image",
453
+ description: "Upscales and enhances any image with AI detail recovery, removing compression artifacts and increasing resolution. Returns upscaled image directly (synchronous).",
454
+ inputSchema: {
455
+ type: "object",
456
+ properties: {
457
+ imageDataUri: {
458
+ type: "string",
459
+ description: "The image to upscale (URL, data URI, or local file path)."
460
+ }
461
+ },
462
+ required: ["imageDataUri"]
132
463
  }
133
464
  };
465
+ // 11. QR Code Generator (Full-featured)
134
466
  const GENERATE_QR_CODE_TOOL = {
135
467
  name: "generate_qr_code",
136
- description: "Generates a QR code for a given text, URL, WiFi, email, vCard, or UPI payment. Returns a base64 encoded image data URI. Supports customization of colors, size, error correction level, and optional logo overlay.",
468
+ description: "Generates custom QR codes for URLs, plain text, Wi-Fi credentials, vCards, emails, and UPI payment strings with customizable colors, sizing, and error correction. Returns QR image directly (synchronous).",
137
469
  inputSchema: {
138
470
  type: "object",
139
471
  properties: {
@@ -144,7 +476,7 @@ const GENERATE_QR_CODE_TOOL = {
144
476
  },
145
477
  value: {
146
478
  type: "string",
147
- description: "The text or URL to encode (required for 'url' and 'text' types)."
479
+ description: "The text, URL, or data payload to encode."
148
480
  },
149
481
  size: {
150
482
  type: "number",
@@ -167,9 +499,10 @@ const GENERATE_QR_CODE_TOOL = {
167
499
  required: ["type", "value"]
168
500
  }
169
501
  };
502
+ // 12. QR Code Simple
170
503
  const GENERATE_QR_CODE_SIMPLE_TOOL = {
171
504
  name: "generate_qr_code_simple",
172
- description: "Quick QR code generation from plain text or URL. Uses a simple GET-based endpoint. Returns a base64 encoded QR code image.",
505
+ description: "Quick single-parameter QR code generation from plain text or URL. Returns QR image directly (synchronous).",
173
506
  inputSchema: {
174
507
  type: "object",
175
508
  properties: {
@@ -181,60 +514,62 @@ const GENERATE_QR_CODE_SIMPLE_TOOL = {
181
514
  required: ["text"]
182
515
  }
183
516
  };
184
- const COMMON_VIDEO_PROPERTIES = {
185
- prompt: { type: "string", description: "Detailed video description or prompt." },
186
- mainImage: { type: "string", description: "Base64 data URI or URL for the main image (Image-to-Video mode)." },
187
- startImage: { type: "string", description: "Base64 data URI or URL for the first frame." },
188
- endImage: { type: "string", description: "Base64 data URI or URL for the last frame." },
189
- referenceImages: {
190
- type: "array",
191
- items: { type: "object", properties: { image: { type: "string" }, label: { type: "string" } } },
192
- description: "Array of reference images for style matching."
193
- },
194
- negativePrompt: { type: "string", description: "Elements to exclude from the video." },
195
- dialogue: { type: "string", description: "Optional voiceover or dialogue." },
196
- soundSuggestion: { type: "string", description: "Background music or sound effects." },
197
- cameraMotion: { type: "string", description: "Camera motion instructions." },
198
- ambientNoise: { type: "string", description: "Ambient sound description." },
199
- language: { type: "string", description: "Voiceover language." },
200
- timeDuration: { type: "string", description: "Video duration in seconds. Default is '4'." },
201
- resolution: { type: "string", description: "Resolution (e.g., '720p', '1080p'). Default is '720p'." },
202
- model: { type: "string", description: "Model slug (e.g., 'google/veo-3.1-fast', 'openrouter/wan-2.7')." },
203
- aspectRatio: { type: "string", description: "Aspect ratio (e.g., '9:16', '16:9'). Default is '9:16'." },
204
- generateAudio: { type: "boolean", description: "Whether to generate audio. Default is false." }
517
+ // 13. Get User Credits
518
+ const GET_USER_CREDITS_TOOL = {
519
+ name: "get_user_credits",
520
+ description: "Checks your current CodingMantra AI wallet credit balance. Useful before launching batches or large video/image tasks.",
521
+ inputSchema: {
522
+ type: "object",
523
+ properties: {}
524
+ }
525
+ };
526
+ // 14. List Image Models
527
+ const LIST_IMAGE_MODELS_TOOL = {
528
+ name: "list_image_models",
529
+ description: "Lists all available CodingMantra AI image generation models (e.g., Gemini Flash, Gemini Pro, Flux Pro), including supported resolutions, aspect ratios, and credit costs.",
530
+ inputSchema: {
531
+ type: "object",
532
+ properties: {}
533
+ }
205
534
  };
535
+ // 15. Product Video Generation (Async)
206
536
  const GENERATE_PRODUCT_VIDEO_TOOL = {
207
537
  name: "generate_product_video",
208
- description: "Generates a product video. Asynchronous: returns a jobId. You MUST use check_video_job_status with the jobId to poll for completion. Automatically deducts credits.",
538
+ description: "Generates commercial product videography. Asynchronous: returns a jobId. You MUST use check_video_job_status with the jobId to poll for completion.",
209
539
  inputSchema: { type: "object", properties: COMMON_VIDEO_PROPERTIES }
210
540
  };
541
+ // 16. Apparel Video Generation (Async)
211
542
  const GENERATE_APPAREL_VIDEO_TOOL = {
212
543
  name: "generate_apparel_video",
213
- description: "Generates an apparel try-on video. Asynchronous: returns a jobId. You MUST use check_video_job_status with the jobId to poll for completion. Automatically deducts credits.",
544
+ description: "Generates runway fashion and apparel motion showcase videos. Asynchronous: returns a jobId. You MUST use check_video_job_status with the jobId to poll for completion.",
214
545
  inputSchema: { type: "object", properties: COMMON_VIDEO_PROPERTIES }
215
546
  };
547
+ // 17. Festival Video Generation (Async)
216
548
  const GENERATE_FESTIVAL_VIDEO_TOOL = {
217
549
  name: "generate_festival_video",
218
- description: "Generates a festival or event video. Asynchronous: returns a jobId. You MUST use check_video_job_status with the jobId to poll for completion. Automatically deducts credits.",
550
+ description: "Generates celebratory festival greetings and motion graphics. Asynchronous: returns a jobId. You MUST use check_video_job_status with the jobId to poll for completion.",
219
551
  inputSchema: { type: "object", properties: COMMON_VIDEO_PROPERTIES }
220
552
  };
553
+ // 18. Jewelry Video Generation (Async)
221
554
  const GENERATE_JEWELRY_VIDEO_TOOL = {
222
555
  name: "generate_jewelry_video",
223
- description: "Generates a jewelry showcase video. Asynchronous: returns a jobId. You MUST use check_video_job_status with the jobId to poll for completion. Automatically deducts credits.",
556
+ description: "Generates luxury jewelry videography with optical brilliance and rotating display. Asynchronous: returns a jobId. You MUST use check_video_job_status with the jobId to poll for completion.",
224
557
  inputSchema: { type: "object", properties: COMMON_VIDEO_PROPERTIES }
225
558
  };
559
+ // 19. Logo Video Generation (Async)
226
560
  const GENERATE_LOGO_VIDEO_TOOL = {
227
561
  name: "generate_logo_video",
228
- description: "Generates a logo animation video. Asynchronous: returns a jobId. You MUST use check_video_job_status with the jobId to poll for completion. Automatically deducts credits.",
562
+ description: "Generates 3D logo reveal and motion graphic video intros. Asynchronous: returns a jobId. You MUST use check_video_job_status with the jobId to poll for completion.",
229
563
  inputSchema: { type: "object", properties: COMMON_VIDEO_PROPERTIES }
230
564
  };
565
+ // 20. Check Video Job Status (Polling)
231
566
  const CHECK_JOB_STATUS_TOOL = {
232
567
  name: "check_video_job_status",
233
- description: "Checks the status of an asynchronous video generation job. Call this repeatedly (e.g. every 10-30 seconds) until the job status is 'completed' or 'failed'.",
568
+ description: "Checks the real-time status of an asynchronous video generation job. Call this repeatedly (every 10-30 seconds) until the job status is 'completed' or 'failed'.",
234
569
  inputSchema: {
235
570
  type: "object",
236
571
  properties: {
237
- jobId: { type: "string", description: "The ID of the job to check." }
572
+ jobId: { type: "string", description: "The ID of the video job to track." }
238
573
  },
239
574
  required: ["jobId"]
240
575
  }
@@ -245,8 +580,18 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
245
580
  tools: [
246
581
  GENERATE_PRODUCT_PHOTO_TOOL,
247
582
  GENERATE_LOGO_TOOL,
583
+ TRY_ON_JEWELRY_TOOL,
584
+ TRY_ON_APPAREL_TOOL,
585
+ TRY_ON_PRODUCT_TOOL,
586
+ GENERATE_FESTIVAL_POST_TOOL,
587
+ GENERATE_YOUTUBE_THUMBNAIL_TOOL,
588
+ GENERATE_PASSPORT_PHOTO_TOOL,
589
+ SKETCH_TO_REAL_TOOL,
590
+ UPSCALE_IMAGE_TOOL,
248
591
  GENERATE_QR_CODE_TOOL,
249
592
  GENERATE_QR_CODE_SIMPLE_TOOL,
593
+ GET_USER_CREDITS_TOOL,
594
+ LIST_IMAGE_MODELS_TOOL,
250
595
  GENERATE_PRODUCT_VIDEO_TOOL,
251
596
  GENERATE_APPAREL_VIDEO_TOOL,
252
597
  GENERATE_FESTIVAL_VIDEO_TOOL,
@@ -259,86 +604,195 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
259
604
  // ─── Tool Execution ─────────────────────────────────────────────────────────────
260
605
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
261
606
  try {
262
- const { name, arguments: args } = request.params;
263
- // ── Product Photography (synchronous) ────────────────────────────────────
607
+ const { name, arguments: rawArgs } = request.params;
608
+ const args = rawArgs || {};
609
+ // ── 1. Product Photography (synchronous) ──────────────────────────────────
264
610
  if (name === 'generate_product_photo') {
611
+ const payload = {
612
+ ...args,
613
+ productImages: resolveImagesArray(args.productImages),
614
+ referenceImage: resolveImageInput(args.referenceImage),
615
+ logoImage: resolveImageInput(args.logoImage),
616
+ };
265
617
  const response = await apiClient('/image/product-photography', {
266
618
  method: 'POST',
267
- body: JSON.stringify(args),
619
+ body: JSON.stringify(payload),
268
620
  });
269
- const result = response.data;
270
- let text = '✅ Product photo generated successfully!\n\n';
271
- if (result?.imageUrl) {
272
- text += `Image URL: ${result.imageUrl}`;
273
- }
274
- else if (result?.photoDataUri) {
275
- text += `Image data returned (base64). Length: ${String(result.photoDataUri).length} characters.\nData URI: ${String(result.photoDataUri).substring(0, 100)}...`;
276
- }
277
- else if (result?.output) {
278
- text += `Image data returned (base64). Length: ${String(result.output).length} characters.`;
279
- }
280
- else {
281
- text += `Result: ${JSON.stringify(result).substring(0, 500)}`;
282
- }
283
- return { content: [{ type: "text", text }] };
621
+ return formatImageToolResult(response, 'Product Photo Generated');
284
622
  }
285
- // ── Logo Creator (synchronous) ───────────────────────────────────────────
623
+ // ── 2. Logo Creator (synchronous) ─────────────────────────────────────────
286
624
  if (name === 'generate_logo') {
625
+ const payload = {
626
+ ...args,
627
+ referenceImage: resolveImageInput(args.referenceImage),
628
+ };
287
629
  const response = await apiClient('/image/logo-creator', {
288
630
  method: 'POST',
289
- body: JSON.stringify(args),
631
+ body: JSON.stringify(payload),
290
632
  });
291
- const result = response.data;
292
- let text = '✅ Logo generated successfully!\n\n';
293
- if (result?.imageUrl) {
294
- text += `Image URL: ${result.imageUrl}`;
295
- }
296
- else if (result?.photoDataUri) {
297
- text += `Image data returned (base64). Length: ${String(result.photoDataUri).length} characters.\nData URI: ${String(result.photoDataUri).substring(0, 100)}...`;
298
- }
299
- else if (result?.output) {
300
- text += `Image data returned (base64). Length: ${String(result.output).length} characters.`;
301
- }
302
- else {
303
- text += `Result: ${JSON.stringify(result).substring(0, 500)}`;
304
- }
305
- return { content: [{ type: "text", text }] };
633
+ return formatImageToolResult(response, 'Brand Logo Generated');
634
+ }
635
+ // ── 3. Jewelry Try-On (synchronous) ───────────────────────────────────────
636
+ if (name === 'try_on_jewelry') {
637
+ const payload = {
638
+ ...args,
639
+ jewelryImages: resolveImagesArray(args.jewelryImages),
640
+ personImage: resolveImageInput(args.personImage),
641
+ referenceImage: resolveImageInput(args.referenceImage),
642
+ logoImage: resolveImageInput(args.logoImage),
643
+ };
644
+ const response = await apiClient('/image/jewelry-try-on', {
645
+ method: 'POST',
646
+ body: JSON.stringify(payload),
647
+ });
648
+ return formatImageToolResult(response, 'Jewelry Try-On Image Generated');
649
+ }
650
+ // ── 4. Apparel Try-On (synchronous) ───────────────────────────────────────
651
+ if (name === 'try_on_apparel') {
652
+ const payload = {
653
+ ...args,
654
+ apparelImages: resolveImagesArray(args.apparelImages),
655
+ modelImage: resolveImageInput(args.modelImage),
656
+ referenceImage: resolveImageInput(args.referenceImage),
657
+ logoImage: resolveImageInput(args.logoImage),
658
+ };
659
+ const response = await apiClient('/image/apparel-try-on', {
660
+ method: 'POST',
661
+ body: JSON.stringify(payload),
662
+ });
663
+ return formatImageToolResult(response, 'Apparel Try-On Image Generated');
664
+ }
665
+ // ── 5. Product Try-On (synchronous) ───────────────────────────────────────
666
+ if (name === 'try_on_product') {
667
+ const payload = {
668
+ ...args,
669
+ productImages: resolveImagesArray(args.productImages),
670
+ mainImage: resolveImageInput(args.mainImage),
671
+ referenceImage: resolveImageInput(args.referenceImage),
672
+ logoImage: resolveImageInput(args.logoImage),
673
+ };
674
+ const response = await apiClient('/image/product-try-on', {
675
+ method: 'POST',
676
+ body: JSON.stringify(payload),
677
+ });
678
+ return formatImageToolResult(response, 'Product Try-On Image Generated');
679
+ }
680
+ // ── 6. Festival Post (synchronous) ────────────────────────────────────────
681
+ if (name === 'generate_festival_post') {
682
+ const payload = {
683
+ ...args,
684
+ productImage: resolveImageInput(args.productImage),
685
+ logoImage: resolveImageInput(args.logoImage),
686
+ };
687
+ const response = await apiClient('/image/festival-post-generator', {
688
+ method: 'POST',
689
+ body: JSON.stringify(payload),
690
+ });
691
+ return formatImageToolResult(response, 'Festival Post Generated');
692
+ }
693
+ // ── 7. YouTube Thumbnail (synchronous) ────────────────────────────────────
694
+ if (name === 'generate_youtube_thumbnail') {
695
+ const payload = {
696
+ ...args,
697
+ personImage: resolveImageInput(args.personImage),
698
+ backgroundImage: resolveImageInput(args.backgroundImage),
699
+ logoImage: resolveImageInput(args.logoImage),
700
+ };
701
+ const response = await apiClient('/image/youtube-thumbnail-generator', {
702
+ method: 'POST',
703
+ body: JSON.stringify(payload),
704
+ });
705
+ return formatImageToolResult(response, 'YouTube Thumbnail Generated');
706
+ }
707
+ // ── 8. Passport Photo (synchronous) ───────────────────────────────────────
708
+ if (name === 'generate_passport_photo') {
709
+ const payload = {
710
+ ...args,
711
+ photoDataUri: resolveImageInput(args.photoDataUri),
712
+ };
713
+ const response = await apiClient('/image/passport-photo', {
714
+ method: 'POST',
715
+ body: JSON.stringify(payload),
716
+ });
717
+ return formatImageToolResult(response, 'Passport Photo Generated');
718
+ }
719
+ // ── 9. Sketch to Real (synchronous) ───────────────────────────────────────
720
+ if (name === 'sketch_to_real') {
721
+ const payload = {
722
+ ...args,
723
+ sketchImage: resolveImageInput(args.sketchImage),
724
+ referenceImage: resolveImageInput(args.referenceImage),
725
+ logoImage: resolveImageInput(args.logoImage),
726
+ };
727
+ const response = await apiClient('/image/sketch-to-real', {
728
+ method: 'POST',
729
+ body: JSON.stringify(payload),
730
+ });
731
+ return formatImageToolResult(response, 'Sketch-to-Real Image Generated');
732
+ }
733
+ // ── 10. Upscale Image (synchronous) ───────────────────────────────────────
734
+ if (name === 'upscale_image') {
735
+ const payload = {
736
+ ...args,
737
+ imageDataUri: resolveImageInput(args.imageDataUri),
738
+ };
739
+ const response = await apiClient('/image/upscale-image', {
740
+ method: 'POST',
741
+ body: JSON.stringify(payload),
742
+ });
743
+ return formatImageToolResult(response, 'Image Upscaled Successfully');
306
744
  }
307
- // ── QR Code (POST, full-featured) ────────────────────────────────────────
745
+ // ── 11. QR Code (POST, full-featured) ─────────────────────────────────────
308
746
  if (name === 'generate_qr_code') {
309
747
  const response = await apiClient('/qr-code/generate', {
310
748
  method: 'POST',
311
749
  body: JSON.stringify(args),
312
750
  });
313
- const qrDataUrl = response.data?.qrCodeDataUrl;
314
- if (qrDataUrl) {
315
- return {
316
- content: [{ type: "text", text: `✅ QR Code generated successfully!\n\nData URI: ${qrDataUrl}` }],
317
- };
318
- }
319
- return {
320
- content: [{ type: "text", text: `✅ QR Code generated. Result: ${JSON.stringify(response.data).substring(0, 500)}` }],
321
- };
751
+ return formatImageToolResult(response, 'QR Code Generated');
322
752
  }
323
- // ── QR Code Simple (GET, path-based — legacy/backward compatible) ────────
753
+ // ── 12. QR Code Simple (GET, legacy) ──────────────────────────────────────
324
754
  if (name === 'generate_qr_code_simple') {
325
755
  const { text } = args;
326
756
  const encodedText = encodeURIComponent(text);
327
- const apiKey = process.env.CODINGMANTRA_API_KEY;
757
+ const apiKey = process.env.CODINGMANTRA_API_KEY || '';
328
758
  const response = await apiClient(`/qr-code/generate-base64/${apiKey}/${encodedText}`, {
329
759
  method: 'GET',
330
760
  });
331
- const qrDataUrl = response.data?.qrCodeDataUrl;
332
- if (qrDataUrl) {
333
- return {
334
- content: [{ type: "text", text: `✅ QR Code generated successfully!\n\nData URI: ${qrDataUrl}` }],
335
- };
761
+ return formatImageToolResult(response, 'QR Code Generated');
762
+ }
763
+ // ── 13. Get User Credits ──────────────────────────────────────────────────
764
+ if (name === 'get_user_credits') {
765
+ const response = await apiClient('/user/credits', {
766
+ method: 'GET',
767
+ });
768
+ const credits = response.data?.credits ?? 0;
769
+ return {
770
+ content: [{
771
+ type: "text",
772
+ text: `💳 **CodingMantra Wallet Balance**\n\n- Available Credits: **${credits}**\n- Need more credits? Top up at [CodingMantra Pricing](https://codingmantra.com/pricing)\n- View full usage history in your [Account Dashboard](https://codingmantra.com/dashboard)`
773
+ }]
774
+ };
775
+ }
776
+ // ── 14. List Image Models ─────────────────────────────────────────────────
777
+ if (name === 'list_image_models') {
778
+ const response = await apiClient('/image/image-model-options', {
779
+ method: 'GET',
780
+ });
781
+ const models = response.data || [];
782
+ let text = `🤖 **Available CodingMantra Image Models**\n\n`;
783
+ for (const m of models) {
784
+ text += `#### ${m.name} (\`${m.model}\`)\n`;
785
+ text += `- **Provider:** ${m.provider}\n`;
786
+ text += `- **Description:** ${m.description || 'High-performance generative model'}\n`;
787
+ text += `- **Supported Resolutions:** ${m.supportedResolutions?.join(', ') || '1K'}\n`;
788
+ text += `- **Supported Aspect Ratios:** ${m.supportedAspectRatios?.join(', ') || '1:1'}\n`;
789
+ text += `- **Credit Cost:** ${m.estimatedCost?.credits ?? 10} credits\n\n`;
336
790
  }
337
791
  return {
338
- content: [{ type: "text", text: `✅ QR Code generated. Result: ${JSON.stringify(response.data).substring(0, 500)}` }],
792
+ content: [{ type: "text", text: text.trim() }]
339
793
  };
340
794
  }
341
- // ── Video Generation (Asynchronous) ──────────────────────────────────────
795
+ // ── 15-19. Video Generation (Asynchronous) ────────────────────────────────
342
796
  const videoEndpoints = {
343
797
  generate_product_video: '/product-video-generation',
344
798
  generate_apparel_video: '/apparel-video-generation',
@@ -347,22 +801,28 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
347
801
  generate_logo_video: '/logo-video-generation'
348
802
  };
349
803
  if (videoEndpoints[name]) {
804
+ const payload = {
805
+ ...args,
806
+ mainImage: resolveImageInput(args.mainImage),
807
+ startImage: resolveImageInput(args.startImage),
808
+ endImage: resolveImageInput(args.endImage),
809
+ };
350
810
  const response = await apiClient(videoEndpoints[name], {
351
811
  method: 'POST',
352
- body: JSON.stringify(args),
812
+ body: JSON.stringify(payload),
353
813
  });
354
814
  const result = response.data || response;
355
815
  if (result?.jobId) {
356
816
  return {
357
817
  content: [{
358
818
  type: "text",
359
- text: `✅ Job created successfully!\nJob ID: ${result.jobId}\nMessage: ${result.message}\n\nPlease use the check_video_job_status tool with this Job ID to poll for the result. Wait about 10-30 seconds between polls. Video generation can take several minutes.`
819
+ text: `🎬 **Video Generation Job Queued Successfully!**\n\n- **Job ID:** \`${result.jobId}\`\n- **Status:** Queued\n- **Message:** ${result.message || 'Video job dispatched to rendering pipeline.'}\n\n👉 **Next Step:** Call the \`check_video_job_status\` tool with \`jobId: "${result.jobId}"\` every 15-30 seconds until completed. Rendering typically takes 1-3 minutes.`
360
820
  }]
361
821
  };
362
822
  }
363
823
  return { content: [{ type: "text", text: `Job started. Result: ${JSON.stringify(result).substring(0, 500)}` }] };
364
824
  }
365
- // ── Check Job Status ─────────────────────────────────────────────────────
825
+ // ── 20. Check Video Job Status ────────────────────────────────────────────
366
826
  if (name === 'check_video_job_status') {
367
827
  const { jobId } = args;
368
828
  const response = await apiClient(`/jobs/status?jobId=${encodeURIComponent(jobId)}`, {
@@ -371,15 +831,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
371
831
  const result = response.data || response;
372
832
  if (result?.job) {
373
833
  const job = result.job;
374
- let text = `Job Status: ${job.status}\nProgress: ${job.progress}%\nStage: ${job.stage || 'N/A'}`;
834
+ let text = `### 🎬 Video Job Status: **${job.status.toUpperCase()}**\n\n`;
835
+ text += `- **Progress:** ${job.progress}%\n`;
836
+ text += `- **Stage:** ${job.stage || 'Processing'}\n`;
375
837
  if (job.status === 'completed' && job.result?.videoUrl) {
376
- text += `\n\n✅ Video generated successfully!\nVideo URL: ${job.result.videoUrl}`;
838
+ text += `\n✅ **Video Ready:** [Watch / Download Video](${job.result.videoUrl})\n`;
839
+ text += `\n📁 **Gallery:** View in your [CodingMantra Gallery](https://codingmantra.com/my-gallery)`;
377
840
  }
378
841
  else if (job.status === 'failed') {
379
- text += `\n\n❌ Job failed: ${job.error || 'Unknown error'}`;
842
+ text += `\n❌ **Job Failed:** ${job.error || 'Unknown error occurred during generation.'}\n`;
843
+ text += `Your credits have been refunded automatically.`;
380
844
  }
381
845
  else {
382
- text += `\n\nJob is still running. Please check back again shortly.`;
846
+ text += `\n⏳ **Job is actively rendering.** Please poll again in 15-20 seconds.`;
383
847
  }
384
848
  return { content: [{ type: "text", text }] };
385
849
  }
@@ -389,7 +853,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
389
853
  }
390
854
  catch (error) {
391
855
  return {
392
- content: [{ type: "text", text: `❌ Error: ${error.message}` }],
856
+ content: [{ type: "text", text: `❌ **Error:** ${error.message || error}` }],
393
857
  isError: true,
394
858
  };
395
859
  }
@@ -398,9 +862,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
398
862
  async function run() {
399
863
  const transport = new StdioServerTransport();
400
864
  await server.connect(transport);
401
- console.error("CodingMantra MCP Server running on stdio");
865
+ console.error("[CodingMantra MCP] Server v0.2.0 initialized and running on stdio (20 tools active)");
402
866
  }
403
867
  run().catch((error) => {
404
- console.error("Fatal error running server:", error);
868
+ console.error("Fatal error running CodingMantra MCP server:", error);
405
869
  process.exit(1);
406
870
  });