@kolbo/mcp 1.46.0 → 1.48.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kolbo/mcp",
3
- "version": "1.46.0",
3
+ "version": "1.48.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": {
package/src/index.js CHANGED
@@ -116,7 +116,7 @@ function createServer(opts = {}) {
116
116
  '4. If the user has not mentioned any project, omit `project_id` — the default bucket is correct in that case. Do not ask which project to use unless the user\'s intent is ambiguous.',
117
117
  '5. Written deliverables (plans, briefs, scripts, research summaries) can live in Kolbo too: author them as AI Docs with `create_doc` (project-scoped, editable in the app, shareable via `share_doc`).',
118
118
  '6. DIRECTOR / BATCH JOBS: generate_creative_director runs its scenes (image OR video) in parallel and only reports state="completed" once EVERY scene is terminal. Video batches can take many minutes. If the tool returns `_timed_out:true`, the batch is STILL RUNNING on the server — call `get_creative_director_status` with the returned generation_id and keep checking until state="completed" to collect all scene outputs. NEVER conclude a Director run failed and fall back to plain generate_image/generate_video without first checking status — doing so wastes the user\'s credits by paying twice. If scenes already carry image_urls/video_urls, they are done; do not regenerate.',
119
- '7. SESSION CONTINUITY: keep one workflow in ONE session. chat_send_message and the generation tools return a `session_id` for follow-ups, refinements, retries, or additional steps on the SAME task/theme, pass that same `session_id` back on the next call instead of starting fresh. Only OMIT session_id (start a new session) when the user genuinely switches to an unrelated task. Do not open a new conversation/session for every message of the same workflow it fragments the user\'s history and loses context.',
119
+ '7. SESSION CONTINUITY one task, one session, always: every generation tool returns a `session_id`. For ANY follow-up, refinement, retry, or next step on the SAME task, pass that session_id back never start fresh. BATCH RULE (critical): when a single user request produces multiple parallel generations (e.g. "animate these 5 images", "generate 3 variants"), do NOT launch them all at once without a session_id. Instead: (1) run the FIRST generation without session_id to create the session, (2) capture the session_id from its response, (3) pass that session_id to ALL remaining generations in the batch. This keeps the entire batch in one session. Exception: only omit session_id and start fresh when the user explicitly starts an unrelated new task.',
120
120
  '8. LOCAL FILES / REFERENCE MEDIA — HOW TO HANDLE EVERY CASE: (A) User has a LOCAL file (audio, video, image, document) on their machine: if you have filesystem access (Claude Desktop / Code / IDE / any stdio MCP client) → call `upload_media` with the absolute local path OR pass the path directly to tools like `transcribe_audio` which accept local paths natively. If you have NO filesystem access (claude.ai browser/mobile) → call `media_upload_widget` IMMEDIATELY, an upload card appears, the user uploads, and a `media.kolbo.ai` CDN URL comes back — use that URL for any follow-up tool call. (B) You already have a public URL (media.kolbo.ai, any CDN, any direct link) → pass it directly to the tool. All Kolbo tools accept public URLs. NEVER search for DO Spaces keys, DigitalOcean credentials, or server-side upload credentials. NEVER ask the user to put the file on Google Drive, Dropbox, or Loom. NEVER invent or guess a URL. NEVER base64 a large file — use upload_media instead.',
121
121
  '9. MODEL SELECTION: ALWAYS pass a specific `model` on every generation tool — do NOT omit it. Omitting falls back to "Smart Select" auto-routing, which we deliberately avoid because it hides the model choice from the user and often picks a generic default. Choose the model that best fits the task and the user\'s intent (quality, speed, style, capability). If you are unsure which model to use for a given type, call `list_models` with the matching `type` and pick the recommended/flagship one, then pass its `identifier`. Only use Smart Select (omit `model`) if the user EXPLICITLY asks you to auto-pick.',
122
122
  '10. IMAGE EDITING: for ANY prompt-driven / content edit of an existing image — "make it night", changing scene/lighting/colors, adding/removing/replacing objects, restyling — use `generate_image_edit` (it runs on strong dedicated editing models, same as image generation). Do NOT use `edit_image` for content edits — `edit_image` is ONLY for mechanical enhancements (upscale, reframe, remove-background, skin retouch). Its `magic_edit` operation is deprecated in favor of `generate_image_edit`.'
@@ -254,6 +254,14 @@ const projectIdField = z.string().optional().describe(
254
254
  'Project ObjectId to drop this generation into. Call `list_projects` to discover IDs (the API has no concept of project names — only ObjectIds). IMPORTANT: this is per-call, NOT sticky — once the user has named a working project, pass its id on EVERY generation call in the conversation; any call that omits it silently lands in the default "API Generations" project instead. Requires owner / edit / full permission on the project; view-only is rejected.'
255
255
  );
256
256
 
257
+ // Read-scope variant for list/get tools that can surface a SHARED project's
258
+ // assets (a teammate's Visual DNAs / moodboards). Pass a project id you have
259
+ // edit+ on to also see that project owner's shared assets; omit to see only your
260
+ // own + global/org. View-only members and non-members get nothing extra.
261
+ const projectScopeReadField = z.string().optional().describe(
262
+ "Project ObjectId (from `list_projects`) to ALSO surface that shared project's assets (a teammate's, when the project is shared with you). Requires edit / full / owner on the project — view-only members and non-members get only their own. Omit to see just your own + global/org."
263
+ );
264
+
257
265
  // ─── Optional inline-image content blocks ────────────────────────────────────
258
266
  // When a host opts in (the remote HTTP connector sets inlineImages:true), turn
259
267
  // generated IMAGE urls into MCP `image` content blocks so clients render them
@@ -440,6 +448,7 @@ module.exports = {
440
448
  resolveToBuffer,
441
449
  creditFields,
442
450
  projectIdField,
451
+ projectScopeReadField,
443
452
  inlineImageBlocks,
444
453
  buildOpenUrl,
445
454
  uiGenerating,
@@ -5,6 +5,7 @@
5
5
 
6
6
  const { z } = require('zod');
7
7
  const { UI, uiResult, appsEnabled } = require('../apps');
8
+ const { projectScopeReadField } = require('./_shared');
8
9
 
9
10
  function registerMoodboardTools(server, client, options = {}) {
10
11
  const ui = () => appsEnabled(server, options);
@@ -13,11 +14,13 @@ function registerMoodboardTools(server, client, options = {}) {
13
14
  'list_moodboards',
14
15
  'List moodboards. By default returns ALL (personal + system presets + organization). Use "scope" to filter: "personal" (user\'s own), "preset" or "global" (system presets), or "organization" (org-shared). Returns id, name, master_prompt, thumbnail, and image URLs for each.',
15
16
  {
16
- scope: z.enum(['all', 'personal', 'preset', 'global', 'organization']).optional().describe('Filter by scope. Default: "all" (everything accessible). "personal" = only your own. "preset"/"global" = system presets. "organization" = org-shared.')
17
+ scope: z.enum(['all', 'personal', 'preset', 'global', 'organization']).optional().describe('Filter by scope. Default: "all" (everything accessible). "personal" = only your own. "preset"/"global" = system presets. "organization" = org-shared.'),
18
+ project_id: projectScopeReadField
17
19
  },
18
- async ({ scope } = {}) => {
20
+ async ({ scope, project_id } = {}) => {
19
21
  const params = new URLSearchParams();
20
22
  if (scope && scope !== 'all') params.set('scope', scope);
23
+ if (project_id) params.set('project_id', project_id);
21
24
  const qs = params.toString();
22
25
  const result = await client.get(`/v1/moodboards${qs ? '?' + qs : ''}`);
23
26
  const moodboards = result.moodboards || [];
@@ -5,7 +5,7 @@
5
5
 
6
6
  const { z } = require('zod');
7
7
  const FormData = require('form-data');
8
- const { resolveToBuffer: sharedResolveToBuffer, VISUAL_DNA_MAX_BYTES } = require('./_shared');
8
+ const { resolveToBuffer: sharedResolveToBuffer, VISUAL_DNA_MAX_BYTES, projectScopeReadField } = require('./_shared');
9
9
  const { UI, uiResult, appsEnabled } = require('../apps');
10
10
 
11
11
  // Visual DNA caps reference media at 25MB per file (stricter than the
@@ -85,14 +85,16 @@ function registerVisualDnaTools(server, client, options = {}) {
85
85
  scope: z.enum(['all', 'personal', 'global', 'organization']).optional().describe('Filter by scope. Default: "all" (everything accessible). "personal" = only your own. "global" = system presets/cast. "organization" = org-shared.'),
86
86
  search: z.string().optional().describe('Search by name, tags, or description (case-insensitive)'),
87
87
  collection: z.string().optional().describe('Filter global presets by collection: cast, influencers, props, locations, styles, glamour, street'),
88
- tags: z.string().optional().describe('Comma-separated tags to filter by (OR logic)')
88
+ tags: z.string().optional().describe('Comma-separated tags to filter by (OR logic)'),
89
+ project_id: projectScopeReadField
89
90
  },
90
- async ({ scope, search, collection, tags } = {}) => {
91
+ async ({ scope, search, collection, tags, project_id } = {}) => {
91
92
  const params = new URLSearchParams();
92
93
  if (scope && scope !== 'all') params.set('scope', scope);
93
94
  if (search) params.set('search', search);
94
95
  if (collection) params.set('collection', collection);
95
96
  if (tags) params.set('tags', tags);
97
+ if (project_id) params.set('project_id', project_id);
96
98
  const qs = params.toString();
97
99
  const result = await client.get(`/v1/visual-dna${qs ? '?' + qs : ''}`);
98
100
  const dnas = result.visual_dnas || [];
@@ -125,12 +127,14 @@ function registerVisualDnaTools(server, client, options = {}) {
125
127
  // ─── get_visual_dna ────────────────────────────────────────
126
128
  server.tool(
127
129
  'get_visual_dna',
128
- 'Fetch a single Visual DNA profile by ID. Returns the full profile including system_prompt and all reference images.',
130
+ 'Fetch a single Visual DNA profile by ID. Returns the full profile including system_prompt and all reference images. To fetch a teammate\'s Visual DNA that lives in a shared project, pass project_id (you need edit+ on it).',
129
131
  {
130
- visual_dna_id: z.string().describe('The Visual DNA profile ID')
132
+ visual_dna_id: z.string().describe('The Visual DNA profile ID'),
133
+ project_id: projectScopeReadField
131
134
  },
132
- async ({ visual_dna_id }) => {
133
- const result = await client.get(`/v1/visual-dna/${encodeURIComponent(visual_dna_id)}`);
135
+ async ({ visual_dna_id, project_id }) => {
136
+ const suffix = project_id ? `?project_id=${encodeURIComponent(project_id)}` : '';
137
+ const result = await client.get(`/v1/visual-dna/${encodeURIComponent(visual_dna_id)}` + suffix);
134
138
  return {
135
139
  content: [{
136
140
  type: 'text',