@kolbo/mcp 1.51.2 → 1.52.1

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.51.2",
3
+ "version": "1.52.1",
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
@@ -109,6 +109,10 @@ function createServer(opts = {}) {
109
109
  // The single most common failure mode is project confusion — spell out
110
110
  // the project contract here so every client gets it without a skill file.
111
111
  instructions: [
112
+ 'LOCAL FILES: never upload a user file yourself with cloud credentials, a shell command, or an S3/Spaces script — Kolbo owns this. Over a remote connector (claude.ai / Claude Desktop) the server cannot see local paths at all: call `media_upload_widget` so the user picks the file, then pass the returned https:// URL to the generation/transcription tool. Use `upload_media` only when the path IS reachable from where the MCP server runs (local stdio installs). `list_media` returns URLs for assets already in the library.',
113
+ 'PROMPT CONVENTIONS (Kolbo-specific — these change the OUTPUT, not just the metadata):',
114
+ 'A. Visual DNA: passing `visual_dna_ids` is not enough — every DNA in play must ALSO be tagged inside the prompt text as `@Name`, using the DNA name (e.g. "@Kobi walks into frame"). Moodboards are referenced the same way with `#Name`. Resolve names via `list_visual_dnas` / `list_moodboards`.',
115
+ 'B. The full Kolbo skill is available to you as MCP RESOURCES under `kolbo://skill/`. Read `kolbo://skill/SKILL.md` first — it is the core rules plus a routing index — then read the matching `kolbo://skill/references/...` file before writing prompts for a specific model or workflow (per-model prompt rules, Visual DNA workflow, Creative Director, marketing, cost validation). Do this instead of guessing; the references exist precisely because the rules differ per model.',
112
116
  'PROJECT CONTRACT (read this before generating anything):',
113
117
  'Everything in Kolbo lives inside a PROJECT — sessions, generations, and media are all project-scoped.',
114
118
  '1. When the user names a project ("in my Acme project", "for the summer campaign"), call `list_projects` ONCE to resolve the name to an id, then pass that id as `project_id` on EVERY subsequent generate_* / chat_send_message / upload_media call in the conversation. The target project is per-call, NOT sticky — any call that omits `project_id` silently lands in the default "API Generations" bucket (flagged is_default:true), which users experience as their work going to the wrong project.',
@@ -148,6 +152,9 @@ function createServer(opts = {}) {
148
152
  // MCP Apps widget resources (ui://kolbo/*). Registering resources is inert
149
153
  // for text-only hosts — they never fetch them.
150
154
  registerApps(server);
155
+ // Serve skill/ as standard MCP resources so connector clients — which never
156
+ // run `npx @kolbo/mcp install` — can still read the operating guidance.
157
+ registerSkillResources(server);
151
158
  // Declaration-level `_meta['ui/resourceUri']` on every widget-carrying tool —
152
159
  // claude.ai prepares the widget iframe from tools/list, not from the result.
153
160
  attachToolWidgetMeta(server);
@@ -171,6 +178,7 @@ async function main() {
171
178
  // internally, re-exported so a host never has to re-derive them. Additive
172
179
  // only — existing consumers (claude.ai, Desktop, npx) are unaffected.
173
180
  const { UI, TOOL_WIDGETS, uiMeta, widgetHtml } = require('./apps');
181
+ const { registerSkillResources } = require('./skillResources');
174
182
 
175
183
  module.exports = { main, createServer, UI, TOOL_WIDGETS, uiMeta, widgetHtml };
176
184
 
@@ -0,0 +1,85 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * Kolbo skill, served as standard MCP resources.
5
+ *
6
+ * Why: `skill/` is installed into a LOCAL agent by `npx @kolbo/mcp install`.
7
+ * Connector clients (claude.ai / Claude Desktop) never run that install, so
8
+ * they get the tools but none of the operating guidance — which is why models
9
+ * on those surfaces write generation prompts without the `@VisualDNA` /
10
+ * `#Moodboard` conventions the skill mandates.
11
+ *
12
+ * Serving the same files as resources closes that gap for every client without
13
+ * duplicating the content: `skill/` stays the single source of truth (it is
14
+ * auto-mirrored from kolbo-code — never hand-edit it).
15
+ *
16
+ * These are registered with the SDK's own `server.resource(...)`, NOT the
17
+ * ext-apps `registerAppResource` used for widgets — widgets are deliberately
18
+ * hidden from generic resource listings, whereas the skill must be discoverable.
19
+ *
20
+ * Shape mirrors how the skill already works: SKILL.md is the always-loaded core
21
+ * with a routing index, and `references/**` are pulled on demand. Resources are
22
+ * pull-based, so nothing costs tokens until the model actually reads it.
23
+ */
24
+
25
+ const fs = require('fs');
26
+ const path = require('path');
27
+
28
+ const SKILL_DIR = path.join(__dirname, '..', 'skill');
29
+ const URI_PREFIX = 'kolbo://skill/';
30
+
31
+ /** Every markdown file under skill/, as posix-style paths relative to skill/. */
32
+ function listSkillDocs(dir = SKILL_DIR, base = SKILL_DIR) {
33
+ if (!fs.existsSync(dir)) return [];
34
+ const out = [];
35
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
36
+ const full = path.join(dir, entry.name);
37
+ if (entry.isDirectory()) out.push(...listSkillDocs(full, base));
38
+ else if (entry.isFile() && entry.name.endsWith('.md')) {
39
+ out.push(path.relative(base, full).split(path.sep).join('/'));
40
+ }
41
+ }
42
+ return out;
43
+ }
44
+
45
+ /** First markdown heading or frontmatter description, for the resource blurb. */
46
+ function describe(text, rel) {
47
+ const heading = text.match(/^#\s+(.+)$/m);
48
+ if (heading) return heading[1].trim().slice(0, 160);
49
+ return `Kolbo skill reference: ${rel}`;
50
+ }
51
+
52
+ function registerSkillResources(server) {
53
+ const docs = listSkillDocs();
54
+ if (!docs.length) return 0;
55
+
56
+ for (const rel of docs) {
57
+ const uri = URI_PREFIX + rel;
58
+ const abs = path.join(SKILL_DIR, rel);
59
+ // Name the core doc distinctly so it stands out in a resource list.
60
+ const name = rel === 'SKILL.md'
61
+ ? 'Kolbo skill — START HERE (core rules + routing index)'
62
+ : `Kolbo skill — ${rel.replace(/^references\//, '').replace(/\.md$/, '')}`;
63
+
64
+ let blurb;
65
+ try {
66
+ blurb = describe(fs.readFileSync(abs, 'utf8'), rel);
67
+ } catch {
68
+ blurb = `Kolbo skill reference: ${rel}`;
69
+ }
70
+
71
+ server.resource(
72
+ name,
73
+ uri,
74
+ { mimeType: 'text/markdown', description: blurb },
75
+ // Read lazily on each request so a mirrored skill update is picked up
76
+ // without restarting the server.
77
+ async () => ({
78
+ contents: [{ uri, mimeType: 'text/markdown', text: fs.readFileSync(abs, 'utf8') }],
79
+ })
80
+ );
81
+ }
82
+ return docs.length;
83
+ }
84
+
85
+ module.exports = { registerSkillResources, listSkillDocs, SKILL_DIR, URI_PREFIX };
@@ -186,8 +186,11 @@ async function resolveToBuffer(source, kind, opts = {}) {
186
186
  if (!path.isAbsolute(source)) {
187
187
  throw new Error(
188
188
  `Local file paths must be absolute: ${source}. ` +
189
- `If you are using Kolbo over a remote connector (e.g. claude.ai), local files are not reachable ` +
190
- `pass a public https:// URL instead (upload the file somewhere first, or use a URL from list_media).`
189
+ `If you are using Kolbo over a remote connector (e.g. claude.ai), local files are not reachable. ` +
190
+ `DO NOT upload the file yourself with cloud credentials or a shell command — Kolbo has a tool for this. ` +
191
+ `Call \`media_upload_widget\` to have the user pick the file (remote connectors), or \`upload_media\` ` +
192
+ `when the file IS reachable from where the MCP server runs, then pass the returned https:// URL here. ` +
193
+ `A URL from \`list_media\` also works if the asset is already in the library.`
191
194
  );
192
195
  }
193
196
  let stat;
@@ -196,8 +199,11 @@ async function resolveToBuffer(source, kind, opts = {}) {
196
199
  } catch (err) {
197
200
  throw new Error(
198
201
  `Local file not found or unreadable: ${source}. ` +
199
- `If you are using Kolbo over a remote connector (e.g. claude.ai), local file paths are not reachable ` +
200
- `pass a public https:// URL instead (upload the file somewhere first, or use a URL from list_media).` +
202
+ `If you are using Kolbo over a remote connector (e.g. claude.ai), local file paths are not reachable. ` +
203
+ `DO NOT upload the file yourself with cloud credentials or a shell command — Kolbo has a tool for this. ` +
204
+ `Call \`media_upload_widget\` to have the user pick the file (remote connectors), or \`upload_media\` ` +
205
+ `when the file IS reachable from where the MCP server runs, then pass the returned https:// URL here. ` +
206
+ `A URL from \`list_media\` also works if the asset is already in the library.` +
201
207
  (err && err.code ? ` [${err.code}]` : '')
202
208
  );
203
209
  }