@kolbo/mcp 1.6.8 → 1.6.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolbo/mcp",
3
- "version": "1.6.8",
3
+ "version": "1.6.9",
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": {
package/src/client.js CHANGED
@@ -198,12 +198,21 @@ class KolboClient {
198
198
 
199
199
  async _doRequest(method, reqPath, body = null) {
200
200
  const url = `${this.baseUrl}${reqPath}`;
201
+ const headers = {
202
+ 'X-API-Key': this.apiKey,
203
+ 'Content-Type': 'application/json'
204
+ };
205
+ // Stable per-app-launch identifier from the parent process (Kolbo Code
206
+ // sets this in the MCP env when spawning us). kolbo-api tags every
207
+ // CreditUsage record with it so the desktop UI and the get_session_usage
208
+ // tool can aggregate spend without enumerating individual generations.
209
+ const callerSessionId = process.env.KOLBO_CALLER_SESSION_ID;
210
+ if (callerSessionId) {
211
+ headers['X-Kolbo-Caller-Session-Id'] = callerSessionId;
212
+ }
201
213
  const options = {
202
214
  method,
203
- headers: {
204
- 'X-API-Key': this.apiKey,
205
- 'Content-Type': 'application/json'
206
- }
215
+ headers,
207
216
  };
208
217
 
209
218
  if (body) {
@@ -270,6 +279,11 @@ class KolboClient {
270
279
  'X-API-Key': this.apiKey,
271
280
  ...formData.getHeaders()
272
281
  };
282
+ // Same caller-session header as JSON requests — see _doRequest.
283
+ const callerSessionId = process.env.KOLBO_CALLER_SESSION_ID;
284
+ if (callerSessionId) {
285
+ headers['X-Kolbo-Caller-Session-Id'] = callerSessionId;
286
+ }
273
287
 
274
288
  // Serialize form-data to a Buffer before passing to fetch(). Node's
275
289
  // built-in fetch (undici) can't consume legacy Node.js streams from
@@ -200,6 +200,36 @@ async function resolveToBuffer(source, kind, opts = {}) {
200
200
  };
201
201
  }
202
202
 
203
+ /**
204
+ * Extract real, multiplier-adjusted credit cost from a polled getStatus
205
+ * response. kolbo-api returns `credits_used` (final number deducted) and
206
+ * `credits_breakdown` (per-CreditUsage detail) when the generation is
207
+ * complete. Returns `{}` when the API didn't include them so spreading
208
+ * the result into a tool's response object is a no-op (forward-compatible
209
+ * with old kolbo-api versions).
210
+ *
211
+ * Usage in every generation tool:
212
+ * return {
213
+ * content: [{ type: 'text', text: JSON.stringify({
214
+ * urls: result.result.urls,
215
+ * model: result.result.model,
216
+ * ...creditFields(result), // adds credits_used + credits_breakdown
217
+ * _followup_hint: '...',
218
+ * }, null, 2) }]
219
+ * };
220
+ */
221
+ function creditFields(polledResult) {
222
+ if (!polledResult) return {};
223
+ const out = {};
224
+ if (typeof polledResult.credits_used === 'number') {
225
+ out.credits_used = polledResult.credits_used;
226
+ }
227
+ if (Array.isArray(polledResult.credits_breakdown) && polledResult.credits_breakdown.length) {
228
+ out.credits_breakdown = polledResult.credits_breakdown;
229
+ }
230
+ return out;
231
+ }
232
+
203
233
  module.exports = {
204
234
  MAX_FILE_BYTES,
205
235
  VISUAL_DNA_MAX_BYTES,
@@ -208,5 +238,6 @@ module.exports = {
208
238
  safeFetch,
209
239
  guessFilename,
210
240
  guessContentType,
211
- resolveToBuffer
241
+ resolveToBuffer,
242
+ creditFields,
212
243
  };
package/src/tools/chat.js CHANGED
@@ -5,6 +5,7 @@
5
5
 
6
6
  const { z } = require('zod');
7
7
  const { pollUntilDone } = require('../polling');
8
+ const { creditFields } = require('./_shared');
8
9
 
9
10
  function registerChatTools(server, client) {
10
11
  // ─── chat_send_message ─────────────────────────────────────
@@ -49,6 +50,7 @@ function registerChatTools(server, client) {
49
50
  content: [{
50
51
  type: 'text',
51
52
  text: JSON.stringify({
53
+ ...creditFields(result),
52
54
  session_id: gen.session_id,
53
55
  message_id: gen.message_id,
54
56
  model: r.model || gen.model,
@@ -6,7 +6,7 @@
6
6
  const { z } = require('zod');
7
7
  const FormData = require('form-data');
8
8
  const { pollUntilDone } = require('../polling');
9
- const { resolveToBuffer } = require('./_shared');
9
+ const { resolveToBuffer, creditFields } = require('./_shared');
10
10
 
11
11
  function registerGenerateTools(server, client) {
12
12
  // ─── generate_image ────────────────────────────────────────
@@ -41,6 +41,7 @@ function registerGenerateTools(server, client) {
41
41
  content: [{
42
42
  type: 'text',
43
43
  text: JSON.stringify({
44
+ ...creditFields(result),
44
45
  urls: result.result.urls,
45
46
  model: result.result.model,
46
47
  prompt_used: result.result.prompt_used,
@@ -86,6 +87,7 @@ function registerGenerateTools(server, client) {
86
87
  content: [{
87
88
  type: 'text',
88
89
  text: JSON.stringify({
90
+ ...creditFields(result),
89
91
  urls: result.result.urls,
90
92
  model: result.result.model,
91
93
  prompt_used: result.result.prompt_used,
@@ -139,6 +141,7 @@ function registerGenerateTools(server, client) {
139
141
  content: [{
140
142
  type: 'text',
141
143
  text: JSON.stringify({
144
+ ...creditFields(result),
142
145
  scenes,
143
146
  total_scenes: result.scenes?.length || 0,
144
147
  completed_scenes: scenes.length,
@@ -181,6 +184,7 @@ function registerGenerateTools(server, client) {
181
184
  content: [{
182
185
  type: 'text',
183
186
  text: JSON.stringify({
187
+ ...creditFields(result),
184
188
  urls: result.result.urls,
185
189
  model: result.result.model,
186
190
  duration: result.result.duration,
@@ -221,6 +225,7 @@ function registerGenerateTools(server, client) {
221
225
  content: [{
222
226
  type: 'text',
223
227
  text: JSON.stringify({
228
+ ...creditFields(result),
224
229
  urls: result.result.urls,
225
230
  model: result.result.model,
226
231
  duration: result.result.duration,
@@ -260,6 +265,7 @@ function registerGenerateTools(server, client) {
260
265
  content: [{
261
266
  type: 'text',
262
267
  text: JSON.stringify({
268
+ ...creditFields(result),
263
269
  urls: result.result.urls,
264
270
  title: result.result.title,
265
271
  duration: result.result.duration,
@@ -294,6 +300,7 @@ function registerGenerateTools(server, client) {
294
300
  content: [{
295
301
  type: 'text',
296
302
  text: JSON.stringify({
303
+ ...creditFields(result),
297
304
  urls: result.result.urls,
298
305
  voice: result.result.voice,
299
306
  duration: result.result.duration
@@ -327,6 +334,7 @@ function registerGenerateTools(server, client) {
327
334
  content: [{
328
335
  type: 'text',
329
336
  text: JSON.stringify({
337
+ ...creditFields(result),
330
338
  urls: result.result.urls,
331
339
  duration: result.result.duration
332
340
  }, null, 2)
@@ -454,6 +462,7 @@ function registerGenerateTools(server, client) {
454
462
  content: [{
455
463
  type: 'text',
456
464
  text: JSON.stringify({
465
+ ...creditFields(result),
457
466
  urls: result.result?.urls || [],
458
467
  thumbnail_url: result.result?.thumbnail_url || null,
459
468
  duration: result.result?.duration || null,
@@ -523,6 +532,7 @@ function registerGenerateTools(server, client) {
523
532
  content: [{
524
533
  type: 'text',
525
534
  text: JSON.stringify({
535
+ ...creditFields(result),
526
536
  urls: result.result?.urls || [],
527
537
  thumbnail_url: result.result?.thumbnail_url || null,
528
538
  duration: result.result?.duration || null,
@@ -593,6 +603,7 @@ function registerGenerateTools(server, client) {
593
603
  content: [{
594
604
  type: 'text',
595
605
  text: JSON.stringify({
606
+ ...creditFields(result),
596
607
  urls: result.result?.urls || [],
597
608
  thumbnail_url: result.result?.thumbnail_url || null,
598
609
  duration: result.result?.duration || null,
@@ -657,6 +668,7 @@ function registerGenerateTools(server, client) {
657
668
  content: [{
658
669
  type: 'text',
659
670
  text: JSON.stringify({
671
+ ...creditFields(result),
660
672
  urls: result.result?.urls || [],
661
673
  thumbnail_url: result.result?.thumbnail_url || null,
662
674
  duration: result.result?.duration || null,
@@ -697,6 +709,7 @@ function registerGenerateTools(server, client) {
697
709
  content: [{
698
710
  type: 'text',
699
711
  text: JSON.stringify({
712
+ ...creditFields(result),
700
713
  text: result.result?.text || '',
701
714
  srt_url: result.result?.srt_url || null,
702
715
  word_by_word_srt_url: result.result?.word_by_word_srt_url || null,
@@ -749,6 +762,7 @@ function registerGenerateTools(server, client) {
749
762
  content: [{
750
763
  type: 'text',
751
764
  text: JSON.stringify({
765
+ ...creditFields(result),
752
766
  urls: result.result?.urls || [],
753
767
  thumbnail_url: result.result?.thumbnail_url || null,
754
768
  mode: result.result?.mode || null,
@@ -790,6 +804,7 @@ function registerGenerateTools(server, client) {
790
804
  content: [{
791
805
  type: 'text',
792
806
  text: JSON.stringify({
807
+ ...creditFields(result),
793
808
  urls: result.result?.urls || [],
794
809
  edit_type: result.result?.edit_type || null,
795
810
  model: result.result?.model || null
@@ -838,6 +853,7 @@ function registerGenerateTools(server, client) {
838
853
  content: [{
839
854
  type: 'text',
840
855
  text: JSON.stringify({
856
+ ...creditFields(result),
841
857
  urls: result.result?.urls || [],
842
858
  download_url: result.result?.download_url || null,
843
859
  edit_type: result.result?.edit_type || null,
@@ -113,6 +113,38 @@ function registerModelTools(server, client) {
113
113
  };
114
114
  }
115
115
  );
116
+
117
+ // ─── get_session_usage ─────────────────────────────────────
118
+ // Real, multiplier-adjusted credit spend tagged with the caller's
119
+ // X-Kolbo-Caller-Session-Id (set automatically by the parent process —
120
+ // no need to pass it). Use this to give the user an honest "you've spent
121
+ // X credits in this app session" instead of estimating from base credits.
122
+ server.tool(
123
+ 'get_session_usage',
124
+ 'Fetch real, multiplier-adjusted credit spend for the current Kolbo Code app session. Use when the user asks "how much did I spend?" or before/after a large bulk job so you can quote actual cost (not an estimate from base credits). Returns total + per-tool breakdown + per-model breakdown + a recent list. The caller-session-id is forwarded automatically by the MCP HTTP client.',
125
+ {},
126
+ async () => {
127
+ try {
128
+ const r = await client.get('/credit-usage/by-caller-session');
129
+ // The endpoint returns { message, data: { total, count, by_tool, by_model, recent[] } }
130
+ return {
131
+ content: [{
132
+ type: 'text',
133
+ text: JSON.stringify(r.data || r, null, 2)
134
+ }]
135
+ };
136
+ } catch (err) {
137
+ // 400 from the endpoint means no caller-session-id was forwarded —
138
+ // surface a clear hint instead of a generic API error.
139
+ const hint = err?.status === 400
140
+ ? 'No caller-session-id was forwarded. Ensure the parent process (Kolbo Code / desktop sidecar) sets KOLBO_CALLER_SESSION_ID in this MCP\'s env, or call again after at least one media generation has fired.'
141
+ : err?.message || 'Failed to fetch session usage';
142
+ return {
143
+ content: [{ type: 'text', text: JSON.stringify({ error: hint }, null, 2) }]
144
+ };
145
+ }
146
+ }
147
+ );
116
148
  }
117
149
 
118
150
  module.exports = { registerModelTools };