@kolbo/mcp 1.0.0 → 1.1.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/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Use [Kolbo AI](https://kolbo.ai) as native tools in Claude Code and Claude Desktop via MCP (Model Context Protocol).
4
4
 
5
- Generate images, videos, music, speech, and sound effects — all from natural language in your coding environment.
5
+ Generate images, videos, music, speech, sound effects, multi-scene campaigns, and conversational chat — all from natural language in your coding environment. 100+ AI models behind Smart Select routing, with reusable Visual DNA profiles for character/style consistency.
6
6
 
7
7
  ## Quick Setup
8
8
 
@@ -34,22 +34,57 @@ Just ask Claude naturally:
34
34
 
35
35
  - *"Generate an image of a sunset over mountains"*
36
36
  - *"Create a 5-second video of waves crashing"*
37
+ - *"Build a 4-scene storyboard for a coffee shop ad"*
38
+ - *"Remove the background from this image"*
37
39
  - *"Make a lo-fi hip hop beat"*
38
- - *"Convert this text to speech: Hello world"*
40
+ - *"Read this out loud with a British female voice"*
41
+ - *"Ask Claude about the latest AI news with web search on"*
42
+ - *"Create a Visual DNA profile called 'Alex' from these images"*
39
43
 
40
- ## Available Tools
44
+ ## Available Tools (21)
41
45
 
46
+ **Generation**
42
47
  | Tool | Description |
43
48
  |------|-------------|
44
- | `generate_image` | Generate images from text prompts |
45
- | `generate_video` | Generate videos from text |
46
- | `generate_video_from_image` | Animate an image into video |
47
- | `generate_music` | Generate music from descriptions |
48
- | `generate_speech` | Convert text to speech |
49
- | `generate_sound` | Generate sound effects |
50
- | `list_models` | Browse available AI models |
49
+ | `generate_image` | Text image |
50
+ | `generate_image_edit` | Existing image(s) + prompt → edited image |
51
+ | `generate_video` | Text video |
52
+ | `generate_video_from_image` | Still image + motion prompt → video |
53
+ | `generate_creative_director` | One brief N coordinated scenes (image or video) |
54
+ | `generate_music` | Text (+ optional lyrics) → song |
55
+ | `generate_speech` | Text + voice spoken audio |
56
+ | `generate_sound` | Text → sound effect |
57
+
58
+ Every image/video/creative-director tool accepts `visual_dna_ids` and `moodboard_id` for character/style consistency across outputs — you can compose `create_visual_dna` → `generate_image` (with the DNA applied server-side) in a single agent turn. `generate_creative_director` also accepts `moodboard_ids` plural for blending.
59
+
60
+ **Chat**
61
+ | Tool | Description |
62
+ |------|-------------|
63
+ | `chat_send_message` | Multi-turn chat with any Kolbo model; supports web search and deep think |
64
+ | `chat_list_conversations` | List past chat threads |
65
+ | `chat_get_messages` | Fetch messages in a conversation |
66
+
67
+ **Visual DNA** (reusable character/style/product profiles)
68
+ | Tool | Description |
69
+ |------|-------------|
70
+ | `create_visual_dna` | Create a profile from URLs or local files |
71
+ | `list_visual_dnas` | List your profiles |
72
+ | `get_visual_dna` | Fetch one profile |
73
+ | `delete_visual_dna` | Delete a profile |
74
+
75
+ **Moodboards**
76
+ | Tool | Description |
77
+ |------|-------------|
78
+ | `list_moodboards` | Browse presets + your moodboards |
79
+ | `get_moodboard` | Fetch one moodboard with all image URLs |
80
+
81
+ **Discovery & Account**
82
+ | Tool | Description |
83
+ |------|-------------|
84
+ | `list_models` | Current model catalog with costs and capabilities |
85
+ | `list_voices` | TTS voices (presets + cloned) |
51
86
  | `check_credits` | Check credit balance |
52
- | `get_generation_status` | Check a generation's status |
87
+ | `get_generation_status` | Poll a generation by ID (fallback if a tool times out) |
53
88
 
54
89
  ## Environment Variables
55
90
 
package/package.json CHANGED
@@ -1,13 +1,16 @@
1
1
  {
2
2
  "name": "@kolbo/mcp",
3
- "version": "1.0.0",
3
+ "version": "1.1.0",
4
4
  "description": "Kolbo AI MCP Server - Generate images, videos, music, speech, and sound effects from Claude Code",
5
5
  "main": "src/index.js",
6
6
  "bin": {
7
7
  "kolbo-mcp": "./bin/kolbo-mcp.js"
8
8
  },
9
9
  "scripts": {
10
- "start": "node src/index.js"
10
+ "start": "node src/index.js",
11
+ "smoke": "node scripts/smoke.js",
12
+ "check-parity": "node scripts/check-parity.js",
13
+ "prepublishOnly": "node scripts/smoke.js && node scripts/check-parity.js"
11
14
  },
12
15
  "keywords": [
13
16
  "kolbo",
@@ -24,9 +27,9 @@
24
27
  "license": "MIT",
25
28
  "repository": {
26
29
  "type": "git",
27
- "url": "https://github.com/nicenathapong/kolbo-mcp"
30
+ "url": "https://github.com/Zoharvan12/kolbo-mcp"
28
31
  },
29
- "homepage": "https://docs.kolbo.ai/developer-api/claude-code-mcp",
32
+ "homepage": "https://docs.kolbo.ai/developer-api/claude-code-skill",
30
33
  "author": "Kolbo AI <support@kolbo.ai>",
31
34
  "publishConfig": {
32
35
  "access": "public"
@@ -37,7 +40,8 @@
37
40
  "README.md"
38
41
  ],
39
42
  "dependencies": {
40
- "@modelcontextprotocol/sdk": "^1.12.1"
43
+ "@modelcontextprotocol/sdk": "^1.12.1",
44
+ "form-data": "^4.0.5"
41
45
  },
42
46
  "engines": {
43
47
  "node": ">=18.0.0"
package/src/client.js CHANGED
@@ -1,6 +1,23 @@
1
1
  /**
2
2
  * Kolbo API HTTP client wrapper
3
3
  */
4
+
5
+ /**
6
+ * Structured error thrown when the Kolbo API returns a non-OK response.
7
+ * Preserves the SDK's error code, HTTP status, and full response data so
8
+ * MCP tools (and the LLM consuming them) can distinguish NOT_FOUND from
9
+ * INSUFFICIENT_CREDITS from VALIDATION_ERROR etc.
10
+ */
11
+ class KolboApiError extends Error {
12
+ constructor(message, { code, status, data } = {}) {
13
+ super(message);
14
+ this.name = 'KolboApiError';
15
+ this.code = code || null;
16
+ this.status = status || null;
17
+ this.data = data || null;
18
+ }
19
+ }
20
+
4
21
  class KolboClient {
5
22
  constructor() {
6
23
  this.apiKey = process.env.KOLBO_API_KEY;
@@ -26,10 +43,27 @@ class KolboClient {
26
43
  }
27
44
 
28
45
  const response = await fetch(url, options);
29
- const data = await response.json();
46
+ let data;
47
+ try {
48
+ data = await response.json();
49
+ } catch (_) {
50
+ // Non-JSON body (gateway error, HTML etc.)
51
+ throw new KolboApiError(`API error: ${response.status} ${response.statusText}`, {
52
+ status: response.status,
53
+ data: null
54
+ });
55
+ }
30
56
 
31
57
  if (!response.ok || data.success === false) {
32
- throw new Error(data.error || data.message || `API error: ${response.status}`);
58
+ const message = data.error || data.message || `API error: ${response.status}`;
59
+ const code = data.code || null;
60
+ // Surface the code in the message so the LLM sees it even if it ignores the .code property
61
+ const fullMessage = code ? `${message} [${code}]` : message;
62
+ throw new KolboApiError(fullMessage, {
63
+ code,
64
+ status: response.status,
65
+ data
66
+ });
33
67
  }
34
68
 
35
69
  return data;
@@ -42,6 +76,55 @@ class KolboClient {
42
76
  async get(path) {
43
77
  return this.request('GET', path);
44
78
  }
79
+
80
+ async delete(path) {
81
+ return this.request('DELETE', path);
82
+ }
83
+
84
+ async postMultipart(path, formData) {
85
+ const url = `${this.baseUrl}${path}`;
86
+ const headers = {
87
+ 'X-API-Key': this.apiKey,
88
+ ...formData.getHeaders()
89
+ };
90
+
91
+ // form-data exposes getLengthSync for known-size parts; set Content-Length when available.
92
+ try {
93
+ const len = formData.getLengthSync();
94
+ if (len) headers['Content-Length'] = String(len);
95
+ } catch (_) { /* streaming length unavailable — let fetch handle it */ }
96
+
97
+ const response = await fetch(url, {
98
+ method: 'POST',
99
+ headers,
100
+ body: formData,
101
+ duplex: 'half'
102
+ });
103
+
104
+ let data;
105
+ try {
106
+ data = await response.json();
107
+ } catch (_) {
108
+ throw new KolboApiError(`API error: ${response.status} ${response.statusText}`, {
109
+ status: response.status,
110
+ data: null
111
+ });
112
+ }
113
+
114
+ if (!response.ok || data.success === false) {
115
+ const message = data.error || data.message || `API error: ${response.status}`;
116
+ const code = data.code || null;
117
+ const fullMessage = code ? `${message} [${code}]` : message;
118
+ throw new KolboApiError(fullMessage, {
119
+ code,
120
+ status: response.status,
121
+ data
122
+ });
123
+ }
124
+
125
+ return data;
126
+ }
45
127
  }
46
128
 
47
129
  module.exports = KolboClient;
130
+ module.exports.KolboApiError = KolboApiError;
package/src/index.js CHANGED
@@ -1,8 +1,69 @@
1
+ /* ============================================================================
2
+ * @kolbo/mcp — Kolbo AI MCP Server
3
+ *
4
+ * ⛔ STOP. READ THIS BEFORE TOUCHING ANY TOOL REGISTRATION. ⛔
5
+ *
6
+ * This package is published to npm and installed via `npx -y @kolbo/mcp`.
7
+ * Thousands of users have it CACHED on their machines, pinned to old versions
8
+ * by npx's cache. Every tool name, every arg name, every response shape
9
+ * registered below is a PUBLIC CONTRACT. Breaking it silently strands users
10
+ * whose LLM will keep calling tool names their cached server no longer
11
+ * registers — or worse, calls new-style args that the old server can't parse.
12
+ *
13
+ * THE THREE COMMANDMENTS
14
+ *
15
+ * 1. NEVER RENAME AN EXISTING TOOL.
16
+ * Not `generate_image` → `create_image`. Not `list_models` → `get_models`.
17
+ * Not "just cleaning up the name." Old cached clients break the instant
18
+ * you rename. If you must rename, keep the OLD name as an alias that
19
+ * forwards to the new implementation for at least one full major version.
20
+ *
21
+ * 2. NEVER REMOVE AN EXISTING TOOL.
22
+ * Deprecate it in the description ("[DEPRECATED: use X]") and keep it
23
+ * working. Only remove in a major version bump with release notes.
24
+ *
25
+ * 3. NEVER CHANGE AN EXISTING TOOL'S ARG NAMES, TYPES, OR REQUIRED STATUS
26
+ * IN A BACKWARD-INCOMPATIBLE WAY.
27
+ * Adding a new OPTIONAL arg with a sensible default is fine. Everything
28
+ * else below is forbidden in a minor release:
29
+ * - renaming `prompt` to `text`
30
+ * - making a previously-optional arg required
31
+ * - changing `aspect_ratio: string` to `aspect_ratio: { w, h }`
32
+ * - removing an arg (even one you think nobody uses)
33
+ *
34
+ * VERSION BUMPS
35
+ *
36
+ * - minor (1.1.0 → 1.2.0): new tool, new optional arg, description tweak
37
+ * - patch (1.1.0 → 1.1.1): internal refactor, bug fix with no user impact
38
+ * - major (1.1.0 → 2.0.0): ANY breaking change from commandments 1–3 above,
39
+ * AND only after going through the deprecation path in CLAUDE.md.
40
+ *
41
+ * WHY THIS MATTERS
42
+ *
43
+ * Users install via `npx -y @kolbo/mcp` — npx CACHES packages. A user who
44
+ * installed 3 months ago may still be running v1.0 until their cache
45
+ * invalidates. When their Claude Desktop starts the MCP server, it
46
+ * registers whatever tools ITS VERSION knows about. Their LLM sees that
47
+ * list and calls those names. You cannot force-update them.
48
+ *
49
+ * The matching backend SDK routes in
50
+ * `kolbo-api/src/modules/sdk/index.js` are the same kind of public
51
+ * contract and follow the same rules — never rename, never remove.
52
+ *
53
+ * Full rules, deprecation path, and parity-audit instructions: CLAUDE.md
54
+ *
55
+ * If you are a coding agent about to rename/remove a tool or arg: STOP and
56
+ * ask the human first. This is not optional.
57
+ * ==========================================================================*/
58
+
1
59
  const { McpServer } = require('@modelcontextprotocol/sdk/server/mcp.js');
2
60
  const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
3
61
  const KolboClient = require('./client');
4
62
  const { registerGenerateTools } = require('./tools/generate');
5
63
  const { registerModelTools } = require('./tools/models');
64
+ const { registerChatTools } = require('./tools/chat');
65
+ const { registerVisualDnaTools } = require('./tools/visual_dna');
66
+ const { registerMoodboardTools } = require('./tools/moodboards');
6
67
 
7
68
  async function main() {
8
69
  const client = new KolboClient();
@@ -15,6 +76,9 @@ async function main() {
15
76
  // Register all tools
16
77
  registerGenerateTools(server, client);
17
78
  registerModelTools(server, client);
79
+ registerChatTools(server, client);
80
+ registerVisualDnaTools(server, client);
81
+ registerMoodboardTools(server, client);
18
82
 
19
83
  // Start the server with stdio transport
20
84
  const transport = new StdioServerTransport();
package/src/polling.js CHANGED
@@ -1,31 +1,57 @@
1
1
  /**
2
2
  * Poll a generation until it reaches a terminal state
3
3
  */
4
+
5
+ class PollingTimeoutError extends Error {
6
+ constructor(generationId, timeoutMs) {
7
+ const seconds = Math.round(timeoutMs / 1000);
8
+ super(
9
+ `Generation timed out after ${seconds}s of polling. The generation may STILL be running on the server — ` +
10
+ `call get_generation_status with generation_id="${generationId}" to check its current state. ` +
11
+ `Videos, deep-think chat, and large batches can take longer than the default polling window.`
12
+ );
13
+ this.name = 'PollingTimeoutError';
14
+ this.generationId = generationId;
15
+ this.timeoutMs = timeoutMs;
16
+ this.timedOut = true;
17
+ }
18
+ }
19
+
20
+ class GenerationFailedError extends Error {
21
+ constructor(generationId, reason) {
22
+ super(`Generation failed: ${reason || 'unknown error'} (generation_id="${generationId}")`);
23
+ this.name = 'GenerationFailedError';
24
+ this.generationId = generationId;
25
+ }
26
+ }
27
+
4
28
  async function pollUntilDone(client, generationId, options = {}) {
5
29
  const {
6
30
  interval = 5000,
7
- timeout = 300000 // 5 minutes default
31
+ timeout = 300000, // 5 minutes default
32
+ statusUrl
8
33
  } = options;
9
34
 
10
35
  const startTime = Date.now();
36
+ const url = statusUrl || `/v1/generate/${generationId}/status`;
11
37
 
12
38
  while (true) {
13
39
  if (Date.now() - startTime > timeout) {
14
- throw new Error(`Generation ${generationId} timed out after ${timeout / 1000}s`);
40
+ throw new PollingTimeoutError(generationId, timeout);
15
41
  }
16
42
 
17
- const result = await client.get(`/v1/generate/${generationId}/status`);
43
+ const result = await client.get(url);
18
44
 
19
45
  if (result.state === 'completed') {
20
46
  return result;
21
47
  }
22
48
 
23
49
  if (result.state === 'failed') {
24
- throw new Error(result.error || 'Generation failed');
50
+ throw new GenerationFailedError(generationId, result.error);
25
51
  }
26
52
 
27
53
  if (result.state === 'cancelled') {
28
- throw new Error('Generation was cancelled');
54
+ throw new GenerationFailedError(generationId, 'generation was cancelled');
29
55
  }
30
56
 
31
57
  // Wait before next poll
@@ -33,4 +59,4 @@ async function pollUntilDone(client, generationId, options = {}) {
33
59
  }
34
60
  }
35
61
 
36
- module.exports = { pollUntilDone };
62
+ module.exports = { pollUntilDone, PollingTimeoutError, GenerationFailedError };
@@ -0,0 +1,135 @@
1
+ /* ⛔ BACKWARD COMPATIBILITY: Tool names and arg names below are a PUBLIC
2
+ * CONTRACT. Never rename, remove, or break an existing tool/arg — old cached
3
+ * `npx @kolbo/mcp` installs in the wild will break silently. Add new tools or
4
+ * new OPTIONAL args only. Full rules: ../index.js top-of-file and CLAUDE.md. */
5
+
6
+ const { pollUntilDone } = require('../polling');
7
+
8
+ function registerChatTools(server, client) {
9
+ // ─── chat_send_message ─────────────────────────────────────
10
+ server.tool(
11
+ 'chat_send_message',
12
+ 'Send a chat message to Kolbo AI. Starts a new conversation (omit session_id) or continues an existing one. Returns the assistant response when complete. Supports web search and deep think modes.',
13
+ {
14
+ message: { type: 'string', description: 'The user message to send' },
15
+ model: { type: 'string', description: 'Model identifier (e.g. "gpt-4o", "claude-sonnet-4-5"). Omit for Smart Select (auto).' },
16
+ session_id: { type: 'string', description: 'Existing chat session ID to continue. Omit to start a new conversation.' },
17
+ system_prompt: { type: 'string', description: 'System prompt for the conversation. Only applied when creating a new session.' },
18
+ web_search: { type: 'boolean', description: 'Enable web search for this message. Default: false' },
19
+ deep_think: { type: 'boolean', description: 'Enable deep think (extended reasoning). Default: false' },
20
+ enhance_prompt: { type: 'boolean', description: 'Enhance the prompt. Default: true' }
21
+ },
22
+ async ({ message, model, session_id, system_prompt, web_search, deep_think, enhance_prompt }) => {
23
+ const gen = await client.post('/v1/chat', {
24
+ message,
25
+ model,
26
+ session_id,
27
+ system_prompt,
28
+ web_search,
29
+ deep_think,
30
+ enhance_prompt
31
+ });
32
+
33
+ // Deep think reasoning can run far longer than normal chat. Also grant
34
+ // extra time when web_search is on (may fetch + analyze multiple pages).
35
+ const timeout = deep_think ? 600000 : (web_search ? 240000 : 120000);
36
+
37
+ const result = await pollUntilDone(client, gen.message_id, {
38
+ interval: (gen.poll_interval_hint || 2) * 1000,
39
+ timeout
40
+ });
41
+
42
+ // Chat status shape (from extractResult in kolbo-api sdk/controller.js):
43
+ // { content, reasoning_content, image_urls?, video_urls?, audio_urls?, model, created_at }
44
+ const r = result.result || {};
45
+ return {
46
+ content: [{
47
+ type: 'text',
48
+ text: JSON.stringify({
49
+ session_id: gen.session_id,
50
+ message_id: gen.message_id,
51
+ model: r.model || gen.model,
52
+ content: r.content || '',
53
+ reasoning_content: r.reasoning_content || null,
54
+ image_urls: r.image_urls || null,
55
+ video_urls: r.video_urls || null,
56
+ audio_urls: r.audio_urls || null
57
+ }, null, 2)
58
+ }]
59
+ };
60
+ }
61
+ );
62
+
63
+ // ─── chat_list_conversations ───────────────────────────────
64
+ server.tool(
65
+ 'chat_list_conversations',
66
+ 'List your SDK chat conversations, most-recent first. Returns session_id, name, and activity timestamps.',
67
+ {
68
+ page: { type: 'number', description: 'Page number, 1-indexed. Default: 1' },
69
+ limit: { type: 'number', description: 'Results per page, max 50. Default: 20' }
70
+ },
71
+ async ({ page, limit }) => {
72
+ const params = new URLSearchParams();
73
+ if (page) params.set('page', String(page));
74
+ if (limit) params.set('limit', String(limit));
75
+
76
+ const qs = params.toString();
77
+ const result = await client.get(`/v1/chat/conversations${qs ? '?' + qs : ''}`);
78
+
79
+ return {
80
+ content: [{
81
+ type: 'text',
82
+ text: JSON.stringify({
83
+ conversations: result.conversations || [],
84
+ pagination: result.pagination || null
85
+ }, null, 2)
86
+ }]
87
+ };
88
+ }
89
+ );
90
+
91
+ // ─── chat_get_messages ─────────────────────────────────────
92
+ server.tool(
93
+ 'chat_get_messages',
94
+ 'Fetch messages in a chat conversation. Returns role, content, model, and any media URLs attached to each message.',
95
+ {
96
+ session_id: { type: 'string', description: 'The chat session ID' },
97
+ page: { type: 'number', description: 'Page number, 1-indexed. Default: 1' },
98
+ limit: { type: 'number', description: 'Messages per page, max 100. Default: 50' }
99
+ },
100
+ async ({ session_id, page, limit }) => {
101
+ const params = new URLSearchParams();
102
+ if (page) params.set('page', String(page));
103
+ if (limit) params.set('limit', String(limit));
104
+
105
+ const qs = params.toString();
106
+ const result = await client.get(
107
+ `/v1/chat/conversations/${encodeURIComponent(session_id)}/messages${qs ? '?' + qs : ''}`
108
+ );
109
+
110
+ // Trim each message to avoid flooding context.
111
+ const messages = (result.messages || []).map(m => ({
112
+ role: m.role,
113
+ content: m.content,
114
+ model: m.model?.name || m.model?.identifier || null,
115
+ status: m.status,
116
+ created_at: m.createdAt || m.created_at,
117
+ image_url: m.image_url || null,
118
+ video_url: m.video_url || null,
119
+ audio_url: m.audio_url || null
120
+ }));
121
+
122
+ return {
123
+ content: [{
124
+ type: 'text',
125
+ text: JSON.stringify({
126
+ messages,
127
+ pagination: result.pagination || null
128
+ }, null, 2)
129
+ }]
130
+ };
131
+ }
132
+ );
133
+ }
134
+
135
+ module.exports = { registerChatTools };