@kolbo/mcp 1.28.1 → 1.30.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.
@@ -6,6 +6,7 @@
6
6
  const { z } = require('zod');
7
7
  const FormData = require('form-data');
8
8
  const { resolveToBuffer: sharedResolveToBuffer, VISUAL_DNA_MAX_BYTES } = require('./_shared');
9
+ const { UI, uiResult, appsEnabled } = require('../apps');
9
10
 
10
11
  // Visual DNA caps reference media at 25MB per file (stricter than the
11
12
  // default _shared.resolveToBuffer cap — DNA profiles only need enough
@@ -14,7 +15,8 @@ function resolveToBuffer(source, kind) {
14
15
  return sharedResolveToBuffer(source, kind, { maxBytes: VISUAL_DNA_MAX_BYTES });
15
16
  }
16
17
 
17
- function registerVisualDnaTools(server, client) {
18
+ function registerVisualDnaTools(server, client, options = {}) {
19
+ const ui = () => appsEnabled(server, options);
18
20
  // ─── create_visual_dna ─────────────────────────────────────
19
21
  server.tool(
20
22
  'create_visual_dna',
@@ -91,15 +93,30 @@ function registerVisualDnaTools(server, client) {
91
93
  if (tags) params.set('tags', tags);
92
94
  const qs = params.toString();
93
95
  const result = await client.get(`/v1/visual-dna${qs ? '?' + qs : ''}`);
94
- return {
95
- content: [{
96
- type: 'text',
97
- text: JSON.stringify({
98
- visual_dnas: result.visual_dnas || [],
99
- count: result.count || 0
100
- }, null, 2)
101
- }]
102
- };
96
+ const dnas = result.visual_dnas || [];
97
+ const text = JSON.stringify({
98
+ visual_dnas: dnas,
99
+ count: result.count || 0
100
+ }, null, 2);
101
+
102
+ if (ui()) {
103
+ return uiResult(UI.mediaGrid, text, {
104
+ widget: 'media-grid',
105
+ title: 'Visual DNA Profiles',
106
+ items: dnas.slice(0, 24).map(d => ({
107
+ id: d.id,
108
+ title: d.name,
109
+ subtitle: (d.dna_type || '') + (Array.isArray(d.tags) && d.tags.length ? ' · ' + d.tags.slice(0, 3).join(', ') : ''),
110
+ thumbnail: d.thumbnail,
111
+ media_type: 'image',
112
+ use_hint: 'Use Visual DNA "{TITLE}" (id: {ID}) in my next generation for character/style consistency.'
113
+ })),
114
+ total: result.count || dnas.length,
115
+ has_more: dnas.length > 24
116
+ });
117
+ }
118
+
119
+ return { content: [{ type: 'text', text }] };
103
120
  }
104
121
  );
105
122
 
@@ -2,8 +2,10 @@
2
2
  * CONTRACT. Never rename, remove, or break an existing tool/arg. Full rules: ../index.js top-of-file. */
3
3
 
4
4
  const { z } = require('zod');
5
+ const { UI, uiResult, appsEnabled } = require('../apps');
5
6
 
6
- function registerVoiceTools(server, client) {
7
+ function registerVoiceTools(server, client, options = {}) {
8
+ const ui = () => appsEnabled(server, options);
7
9
  // ─── list_voices ──────────────────────────────────────────────
8
10
  server.tool(
9
11
  'list_voices',
@@ -36,12 +38,26 @@ function registerVoiceTools(server, client) {
36
38
  return `${v.voice_id} — ${v.name} (${v.provider})${v3}\n ${tags}${styles}${v.description ? `\n ${v.description}` : ''}`;
37
39
  });
38
40
 
39
- return {
40
- content: [{
41
- type: 'text',
42
- text: `Available voices (${voices.length}):\n\n${lines.join('\n\n')}\n\nUse the "voice_id" value in generate_speech calls.`
43
- }]
44
- };
41
+ const text = `Available voices (${voices.length}):\n\n${lines.join('\n\n')}\n\nUse the "voice_id" value in generate_speech calls.`;
42
+
43
+ if (ui()) {
44
+ return uiResult(UI.mediaGrid, text, {
45
+ widget: 'media-grid',
46
+ title: 'Voices',
47
+ items: voices.slice(0, 24).map(v => ({
48
+ id: v.voice_id,
49
+ title: v.name,
50
+ subtitle: [v.provider, v.language, v.gender, v.accent].filter(Boolean).join(' · '),
51
+ media_type: 'audio',
52
+ preview_audio: v.preview_url,
53
+ use_hint: 'Use voice "{TITLE}" (voice_id: {ID}) for text-to-speech — ask me what text to speak.'
54
+ })),
55
+ total: voices.length,
56
+ has_more: voices.length > 24
57
+ });
58
+ }
59
+
60
+ return { content: [{ type: 'text', text }] };
45
61
  }
46
62
  );
47
63
  }
@@ -1,32 +0,0 @@
1
- # Music Library (stock / production music)
2
-
3
- The music library is Kolbo's catalog of **licensed, ready-made background tracks**. Use it to score a video, ad, or voiceover with an existing track.
4
-
5
- > **Library vs generation.** `search_music_library` finds an existing track. `generate_music` composes a brand-new song with Suno. If the user wants "a track for my ad", reach for the library first — it's free (no credits) and instant. Use `generate_music` only when they want something original/custom.
6
-
7
- ## Tools
8
-
9
- | Tool | Use |
10
- |------|-----|
11
- | `search_music_library` | Keyword search + filters (genre, mood, bpm, duration, has_stems, has_lyrics) + sort. Returns tracks with id, title, artist, duration, BPM, key, genres, moods, preview URL. |
12
- | `analyze_script_for_music` | Turn a script/scene description into `{ query, mood, genre, keywords }`. |
13
- | `browse_music_library` | Paginated browse with no query. |
14
- | `get_music_library_facets` | Valid genres, moods, instruments + BPM/duration ranges. |
15
- | `get_music_track_audio` | A track's downloadable 128 / 320 / WAV URLs. |
16
- | `get_music_track_related` | Stems + alternate versions of a master track. |
17
- | `get_music_track_lyrics` | Lyrics text, theme, explicit flag. |
18
-
19
- All tools are **free** (no credits) and read-only.
20
-
21
- ## Typical flow
22
-
23
- 1. **From a script** → call `analyze_script_for_music` to derive `query` / `mood` / `genre`.
24
- 2. **Search** → `search_music_library` with that query (+ optional filters). Show the user the top matches by title + vibe; include the `preview` URL so they can listen.
25
- 3. **Pick** → once the user chooses, call `get_music_track_audio` with the track `id` to get the final downloadable URLs (offer WAV for editing, 320 for delivery).
26
- 4. Optionally `get_music_track_related` for an instrumental/stems cut, or `get_music_track_lyrics` if it's a vocal track.
27
-
28
- ## Tips
29
-
30
- - Don't dump every field — surface title, artist, duration, BPM, mood, and the preview link.
31
- - If a filtered search returns nothing, call `get_music_library_facets` to use exact valid genre/mood values, then retry.
32
- - `sort` options: `duration-asc`, `duration-desc`, `bpm-asc`, `bpm-desc`, `title`. Omit for relevance order.