@kolbo/mcp 1.51.2 → 1.52.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.51.2",
3
+ "version": "1.52.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
@@ -109,6 +109,9 @@ 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
+ 'PROMPT CONVENTIONS (Kolbo-specific — these change the OUTPUT, not just the metadata):',
113
+ '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`.',
114
+ '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
115
  'PROJECT CONTRACT (read this before generating anything):',
113
116
  'Everything in Kolbo lives inside a PROJECT — sessions, generations, and media are all project-scoped.',
114
117
  '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 +151,9 @@ function createServer(opts = {}) {
148
151
  // MCP Apps widget resources (ui://kolbo/*). Registering resources is inert
149
152
  // for text-only hosts — they never fetch them.
150
153
  registerApps(server);
154
+ // Serve skill/ as standard MCP resources so connector clients — which never
155
+ // run `npx @kolbo/mcp install` — can still read the operating guidance.
156
+ registerSkillResources(server);
151
157
  // Declaration-level `_meta['ui/resourceUri']` on every widget-carrying tool —
152
158
  // claude.ai prepares the widget iframe from tools/list, not from the result.
153
159
  attachToolWidgetMeta(server);
@@ -171,6 +177,7 @@ async function main() {
171
177
  // internally, re-exported so a host never has to re-derive them. Additive
172
178
  // only — existing consumers (claude.ai, Desktop, npx) are unaffected.
173
179
  const { UI, TOOL_WIDGETS, uiMeta, widgetHtml } = require('./apps');
180
+ const { registerSkillResources } = require('./skillResources');
174
181
 
175
182
  module.exports = { main, createServer, UI, TOOL_WIDGETS, uiMeta, widgetHtml };
176
183
 
@@ -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 };