@kolbo/mcp 1.25.0 → 1.28.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/README.md +21 -0
- package/package.json +1 -1
- package/skill/SKILL.md +2 -0
- package/skill/references/workflows/music-library.md +32 -0
- package/src/index.js +4 -0
- package/src/tools/music_library.js +179 -0
- package/src/tools/stock_library.js +143 -0
package/README.md
CHANGED
|
@@ -190,6 +190,27 @@ Every generation tool also accepts an optional `project_id` arg that routes the
|
|
|
190
190
|
|------|-------------|
|
|
191
191
|
| `publish_html_artifact` | Publish an HTML page, SVG, or Mermaid diagram and get a public shareable URL on `sites.kolbo.ai`. Pass `share_token` from a prior publish to update the same URL in place (old content kept in version history). |
|
|
192
192
|
|
|
193
|
+
**Music Library** (stock / production music)
|
|
194
|
+
| Tool | Description |
|
|
195
|
+
|------|-------------|
|
|
196
|
+
| `search_music_library` | Search the licensed stock-music catalog by keyword + genre/mood/BPM/duration filters. Find a ready-made track (distinct from `generate_music`, which composes a new song). |
|
|
197
|
+
| `analyze_script_for_music` | AI: turn a video/voiceover script into a music search (`query`, `mood`, `genre`, `keywords`). |
|
|
198
|
+
| `browse_music_library` | Browse the catalog without a query (paginated). |
|
|
199
|
+
| `get_music_library_facets` | List available genres, moods, instruments + BPM/duration ranges. |
|
|
200
|
+
| `get_music_track_audio` | Get a track's downloadable 128/320/WAV URLs by id. |
|
|
201
|
+
| `get_music_track_related` | Get stems + alternate versions of a track. |
|
|
202
|
+
| `get_music_track_lyrics` | Get lyrics text, theme, and explicit flag for a track. |
|
|
203
|
+
|
|
204
|
+
**Stock Library** (multi-source stock media: Pexels, Pixabay, Sketchfab 3D, Music)
|
|
205
|
+
| Tool | Description |
|
|
206
|
+
|------|-------------|
|
|
207
|
+
| `search_stock_media` | Search photos/videos/illustrations/vectors/3D/music across providers. `source="all"` returns one interleaved feed. Find ready-made assets / b-roll (distinct from `generate_image`/`generate_video`). |
|
|
208
|
+
| `get_stock_sources` | List enabled sources + which media types/filters each supports. |
|
|
209
|
+
| `get_stock_categories` | List dynamic category/topic chips (pass `providerParam` as the `category` filter). |
|
|
210
|
+
| `get_stock_asset` | Get one asset with all download variants, author, license, and attribution. |
|
|
211
|
+
| `analyze_script_for_stock` | AI: turn a script into b-roll search terms (`queries[]`, `mediaType`, `keywords`). |
|
|
212
|
+
| `import_stock_asset` | Copy a stock asset into the media library (CDN copy, stable URL). Free. |
|
|
213
|
+
|
|
193
214
|
**Discovery & Account**
|
|
194
215
|
| Tool | Description |
|
|
195
216
|
|------|-------------|
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -49,6 +49,7 @@ If the user is on a whitelabel build (`sapir`, etc.), they must use their brande
|
|
|
49
49
|
| Generate a **Veo 3 / 3.1** video | `references/models/veo.md` |
|
|
50
50
|
| Build a **multi-scene set** (Creative Director, storyboard, campaign batch, 4+ angles) | `references/models/creative-director.md` |
|
|
51
51
|
| Generate **music** (Suno, song, lyrics, jingle, score) | `references/models/music.md` |
|
|
52
|
+
| Find an **existing / stock / library / royalty-free track** to score a video, ad, or voiceover | `references/workflows/music-library.md` |
|
|
52
53
|
| Build an **HTML presentation / slide deck** | `references/models/html-presentation.md` |
|
|
53
54
|
| Build a **landing page / marketing site** | `references/models/landing-page.md` |
|
|
54
55
|
| Build a **dashboard / data viz / interactive widget / mini-game / UI mockup** | `references/models/visual-code.md` |
|
|
@@ -95,6 +96,7 @@ Each `references/models/*.md` mirrors the matching skill prompt in `kolbo-api/sr
|
|
|
95
96
|
| `create_visual_dna` / `list_visual_dnas` / `get_visual_dna` / `delete_visual_dna` | Visual DNA — see `workflows/visual-dna.md` |
|
|
96
97
|
| `list_moodboards` / `get_moodboard` / `list_presets` | Style overlays |
|
|
97
98
|
| `chat_send_message` / `chat_list_conversations` / `chat_get_messages` | Kolbo chat with optional `media_urls` (up to 10 per call) |
|
|
99
|
+
| `search_music_library` / `analyze_script_for_music` / `browse_music_library` / `get_music_library_facets` / `get_music_track_audio` / `get_music_track_related` / `get_music_track_lyrics` | **Stock / production music library** — find a licensed ready-made track (NOT `generate_music`, which composes a new song). See `workflows/music-library.md` |
|
|
98
100
|
| `app_builder_*` (9 tools) | Full React app generation — see `workflows/app-builder.md` |
|
|
99
101
|
| `publish_html_artifact` | Publish HTML / SVG / Mermaid to `sites.kolbo.ai`. Server dedupes by content hash. Strict CSP. |
|
|
100
102
|
|
|
@@ -0,0 +1,32 @@
|
|
|
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.
|
package/src/index.js
CHANGED
|
@@ -70,6 +70,8 @@ const { registerAppBuilderTools } = require('./tools/app_builder');
|
|
|
70
70
|
const { registerArtifactTools } = require('./tools/artifacts');
|
|
71
71
|
const { registerProjectTools } = require('./tools/projects');
|
|
72
72
|
const { registerVoiceTools } = require('./tools/voices');
|
|
73
|
+
const { registerMusicLibraryTools } = require('./tools/music_library');
|
|
74
|
+
const { registerStockLibraryTools } = require('./tools/stock_library');
|
|
73
75
|
|
|
74
76
|
/**
|
|
75
77
|
* Build a fully-configured Kolbo MCP server (all tool groups registered)
|
|
@@ -105,6 +107,8 @@ function createServer(opts = {}) {
|
|
|
105
107
|
registerAppBuilderTools(server, client);
|
|
106
108
|
registerArtifactTools(server, client);
|
|
107
109
|
registerProjectTools(server, client);
|
|
110
|
+
registerMusicLibraryTools(server, client);
|
|
111
|
+
registerStockLibraryTools(server, client);
|
|
108
112
|
|
|
109
113
|
return server;
|
|
110
114
|
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/* ⛔ BACKWARD COMPATIBILITY: Tool names and arg names below are a PUBLIC
|
|
2
|
+
* CONTRACT. Never rename, remove, or break an existing tool/arg. Full rules: ../index.js top-of-file. */
|
|
3
|
+
|
|
4
|
+
const { z } = require('zod');
|
|
5
|
+
|
|
6
|
+
// Format a normalized track into a compact human-readable line.
|
|
7
|
+
function trackLine(t) {
|
|
8
|
+
const meta = [
|
|
9
|
+
t.durationSeconds != null ? `${Math.round(t.durationSeconds)}s` : null,
|
|
10
|
+
t.bpm != null ? `${t.bpm} BPM` : null,
|
|
11
|
+
t.musicalKey || null,
|
|
12
|
+
Array.isArray(t.genres) && t.genres.length ? t.genres.join('/') : t.genre,
|
|
13
|
+
Array.isArray(t.moodTags) && t.moodTags.length ? t.moodTags.slice(0, 3).join(', ') : null,
|
|
14
|
+
].filter(Boolean).join(' · ');
|
|
15
|
+
const flags = [t.hasStems ? 'stems' : null, t.hasLyrics ? 'lyrics' : null].filter(Boolean).join(', ');
|
|
16
|
+
return `${t.id} — ${t.title}${t.artist ? ` by ${t.artist}` : ''}\n ${meta}${flags ? ` [${flags}]` : ''}${t.audioUrl ? `\n preview: ${t.audioUrl}` : ''}`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function registerMusicLibraryTools(server, client) {
|
|
20
|
+
// ─── search_music_library ─────────────────────────────────────
|
|
21
|
+
server.tool(
|
|
22
|
+
'search_music_library',
|
|
23
|
+
'Search the Kolbo stock / production music library (licensed background tracks) by keyword with optional filters. Use this to FIND an existing ready-made track to score a video, ad, or voiceover — distinct from generate_music, which composes a brand-new song with Suno. Returns matching tracks with id, title, artist, duration, BPM, key, genres, moods, and preview/download URLs (128/320/wav). To turn a script into a good query first, call analyze_script_for_music.',
|
|
24
|
+
{
|
|
25
|
+
query: z.string().max(200).optional().describe('Keyword search, e.g. "uplifting corporate", "tense cinematic", "lofi hip hop". If omitted, falls back to the mood/genre filter as the search term.'),
|
|
26
|
+
mood: z.string().optional().describe('Mood filter, e.g. "Emotional", "Energetic", "Tense". Use get_music_library_facets to see valid values.'),
|
|
27
|
+
genre: z.string().optional().describe('Genre filter, e.g. "Soundtrack", "Corporate", "Hip Hop". Use get_music_library_facets to see valid values.'),
|
|
28
|
+
bpmMin: z.number().optional().describe('Minimum beats-per-minute.'),
|
|
29
|
+
bpmMax: z.number().optional().describe('Maximum beats-per-minute.'),
|
|
30
|
+
durationMin: z.number().optional().describe('Minimum track duration in seconds.'),
|
|
31
|
+
durationMax: z.number().optional().describe('Maximum track duration in seconds.'),
|
|
32
|
+
hasStems: z.boolean().optional().describe('Only return tracks that have separated stems.'),
|
|
33
|
+
hasLyrics: z.boolean().optional().describe('Only return tracks that have lyrics.'),
|
|
34
|
+
sort: z.enum(['duration-asc', 'duration-desc', 'bpm-asc', 'bpm-desc', 'title']).optional().describe('Optional sort order. Omit for relevance order.'),
|
|
35
|
+
limit: z.number().int().min(1).max(40).optional().describe('Results per page (max 40, default 20).'),
|
|
36
|
+
offset: z.number().int().min(0).optional().describe('Pagination offset for loading more results.')
|
|
37
|
+
},
|
|
38
|
+
async (args) => {
|
|
39
|
+
const result = await client.post('/v1/music-library/search', args);
|
|
40
|
+
const tracks = result.tracks || [];
|
|
41
|
+
if (tracks.length === 0) {
|
|
42
|
+
return { content: [{ type: 'text', text: 'No tracks found matching those filters. Try a broader query or call get_music_library_facets for valid genres/moods.' }] };
|
|
43
|
+
}
|
|
44
|
+
const head = `Found ${tracks.length} track${tracks.length === 1 ? '' : 's'}${result.total ? ` (of ${result.total} sorted)` : ''}:`;
|
|
45
|
+
return { content: [{ type: 'text', text: `${head}\n\n${tracks.map(trackLine).join('\n\n')}\n\nUse the track id with get_music_track_audio to get the downloadable 128/320/wav URLs.` }] };
|
|
46
|
+
}
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// ─── analyze_script_for_music ─────────────────────────────────
|
|
50
|
+
server.tool(
|
|
51
|
+
'analyze_script_for_music',
|
|
52
|
+
'AI helper that turns a video or voiceover script into a music search. Returns { query, mood, genre, keywords } you can pass straight into search_music_library to find a fitting background track. Use this first when the user gives you a script/scene description rather than explicit music keywords.',
|
|
53
|
+
{
|
|
54
|
+
script: z.string().min(1).describe('The video or voiceover script / scene description to analyze (up to ~8000 chars).')
|
|
55
|
+
},
|
|
56
|
+
async ({ script }) => {
|
|
57
|
+
const result = await client.post('/v1/music-library/analyze-script', { script });
|
|
58
|
+
return {
|
|
59
|
+
content: [{
|
|
60
|
+
type: 'text',
|
|
61
|
+
text: JSON.stringify({
|
|
62
|
+
query: result.query,
|
|
63
|
+
mood: result.mood,
|
|
64
|
+
genre: result.genre,
|
|
65
|
+
keywords: result.keywords,
|
|
66
|
+
_followup_hint: 'Pass query + mood + genre into search_music_library.'
|
|
67
|
+
}, null, 2)
|
|
68
|
+
}]
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
// ─── browse_music_library ─────────────────────────────────────
|
|
74
|
+
server.tool(
|
|
75
|
+
'browse_music_library',
|
|
76
|
+
'Browse the music library catalog without a search query (stable paginated listing). Use when the user just wants to see what is available. For a targeted search use search_music_library instead.',
|
|
77
|
+
{
|
|
78
|
+
sort: z.enum(['duration-asc', 'duration-desc', 'bpm-asc', 'bpm-desc', 'title']).optional().describe('Optional sort order.'),
|
|
79
|
+
limit: z.number().int().min(1).max(50).optional().describe('Results per page (max 50, default 50).'),
|
|
80
|
+
offset: z.number().int().min(0).optional().describe('Pagination offset for loading more results.')
|
|
81
|
+
},
|
|
82
|
+
async ({ sort, limit, offset }) => {
|
|
83
|
+
const params = new URLSearchParams();
|
|
84
|
+
if (sort) params.set('sort', sort);
|
|
85
|
+
if (limit != null) params.set('limit', String(limit));
|
|
86
|
+
if (offset != null) params.set('offset', String(offset));
|
|
87
|
+
const path = `/v1/music-library/catalog${params.toString() ? '?' + params.toString() : ''}`;
|
|
88
|
+
const result = await client.get(path);
|
|
89
|
+
const tracks = result.tracks || [];
|
|
90
|
+
if (tracks.length === 0) {
|
|
91
|
+
return { content: [{ type: 'text', text: 'No tracks returned.' }] };
|
|
92
|
+
}
|
|
93
|
+
return { content: [{ type: 'text', text: `Catalog (${tracks.length} track${tracks.length === 1 ? '' : 's'}):\n\n${tracks.map(trackLine).join('\n\n')}` }] };
|
|
94
|
+
}
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
// ─── get_music_library_facets ─────────────────────────────────
|
|
98
|
+
server.tool(
|
|
99
|
+
'get_music_library_facets',
|
|
100
|
+
'List the distinct genres, moods, and instruments available in the music library, plus the BPM and duration ranges. Use these values to build precise search_music_library filters.',
|
|
101
|
+
{},
|
|
102
|
+
async () => {
|
|
103
|
+
const result = await client.get('/v1/music-library/facets');
|
|
104
|
+
return {
|
|
105
|
+
content: [{
|
|
106
|
+
type: 'text',
|
|
107
|
+
text: JSON.stringify({
|
|
108
|
+
genres: result.genres || [],
|
|
109
|
+
moods: result.moods || [],
|
|
110
|
+
instruments: result.instruments || [],
|
|
111
|
+
bpmRange: result.bpmRange || null,
|
|
112
|
+
durationRange: result.durationRange || null
|
|
113
|
+
}, null, 2)
|
|
114
|
+
}]
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// ─── get_music_track_audio ────────────────────────────────────
|
|
120
|
+
server.tool(
|
|
121
|
+
'get_music_track_audio',
|
|
122
|
+
'Get the downloadable audio URLs (128 kbps / 320 kbps / WAV) for a single music-library track by id. Call this after the user picks a track from search_music_library or browse_music_library.',
|
|
123
|
+
{
|
|
124
|
+
track_id: z.string().describe('The track id returned by search_music_library / browse_music_library.')
|
|
125
|
+
},
|
|
126
|
+
async ({ track_id }) => {
|
|
127
|
+
const result = await client.get(`/v1/music-library/track/${encodeURIComponent(track_id)}/audio`);
|
|
128
|
+
return {
|
|
129
|
+
content: [{
|
|
130
|
+
type: 'text',
|
|
131
|
+
text: JSON.stringify({ id: result.id, urls: result.urls }, null, 2)
|
|
132
|
+
}]
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
// ─── get_music_track_related ──────────────────────────────────
|
|
138
|
+
server.tool(
|
|
139
|
+
'get_music_track_related',
|
|
140
|
+
'Get the stems and alternate versions of a music-library master track by id (e.g. instrumental, 30s cut, looped).',
|
|
141
|
+
{
|
|
142
|
+
track_id: z.string().describe('The master track id.')
|
|
143
|
+
},
|
|
144
|
+
async ({ track_id }) => {
|
|
145
|
+
const result = await client.get(`/v1/music-library/track/${encodeURIComponent(track_id)}/related`);
|
|
146
|
+
return {
|
|
147
|
+
content: [{
|
|
148
|
+
type: 'text',
|
|
149
|
+
text: JSON.stringify({ stems: result.stems || [], versions: result.versions || [] }, null, 2)
|
|
150
|
+
}]
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
);
|
|
154
|
+
|
|
155
|
+
// ─── get_music_track_lyrics ───────────────────────────────────
|
|
156
|
+
server.tool(
|
|
157
|
+
'get_music_track_lyrics',
|
|
158
|
+
'Get the lyrics text, lyrical theme, and explicit flag for a single music-library track by id.',
|
|
159
|
+
{
|
|
160
|
+
track_id: z.string().describe('The track id.')
|
|
161
|
+
},
|
|
162
|
+
async ({ track_id }) => {
|
|
163
|
+
const result = await client.get(`/v1/music-library/track/${encodeURIComponent(track_id)}/lyrics`);
|
|
164
|
+
return {
|
|
165
|
+
content: [{
|
|
166
|
+
type: 'text',
|
|
167
|
+
text: JSON.stringify({
|
|
168
|
+
hasLyrics: result.hasLyrics,
|
|
169
|
+
lyrics: result.lyrics,
|
|
170
|
+
lyricalTheme: result.lyricalTheme,
|
|
171
|
+
explicit: result.explicit
|
|
172
|
+
}, null, 2)
|
|
173
|
+
}]
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
module.exports = { registerMusicLibraryTools };
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
/* ⛔ BACKWARD COMPATIBILITY: Tool names and arg names below are a PUBLIC
|
|
2
|
+
* CONTRACT. Never rename, remove, or break an existing tool/arg. Full rules: ../index.js top-of-file. */
|
|
3
|
+
|
|
4
|
+
const { z } = require('zod');
|
|
5
|
+
|
|
6
|
+
// Compact one-line render of a normalized stock asset.
|
|
7
|
+
function assetLine(a) {
|
|
8
|
+
const dims = a.width && a.height ? `${a.width}x${a.height}` : null;
|
|
9
|
+
const dur = a.durationSeconds != null ? `${Math.round(a.durationSeconds)}s` : null;
|
|
10
|
+
const meta = [a.mediaType, dims, dur].filter(Boolean).join(' · ');
|
|
11
|
+
const by = a.author?.name ? ` by ${a.author.name}` : '';
|
|
12
|
+
const variants = Array.isArray(a.downloadVariants) ? a.downloadVariants.map((v) => v.label).join('/') : '';
|
|
13
|
+
return `[${a.source}:${a.sourceId}] ${a.title || '(untitled)'}${by}\n ${meta}${variants ? ` variants: ${variants}` : ''}${a.thumbnailUrl ? `\n thumb: ${a.thumbnailUrl}` : ''}`;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Returns the raw querystring (no leading '?'). Callers inline it as
|
|
17
|
+
// `${q ? '?' + q : ''}` so the parity checker recognizes it as a querystring.
|
|
18
|
+
function buildQuery(obj) {
|
|
19
|
+
const p = new URLSearchParams();
|
|
20
|
+
for (const [k, v] of Object.entries(obj)) if (v != null && v !== '') p.set(k, String(v));
|
|
21
|
+
return p.toString();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function registerStockLibraryTools(server, client) {
|
|
25
|
+
// ─── search_stock_media ───────────────────────────────────────
|
|
26
|
+
server.tool(
|
|
27
|
+
'search_stock_media',
|
|
28
|
+
'Search the Kolbo unified stock media library and return matching assets. Covers external providers (Pexels photos/videos, Pixabay, Sketchfab 3D, licensed Music) AND Kolbo\'s OWN AI-generated library: thousands of SOUND EFFECTS (mediaType="sfx") and MUSIC tracks (source="kolbo-ai"). Use this to FIND ready-made photos, videos, 3D models, music, or sound effects as b-roll/references/project assets — distinct from generate_* tools which create new content.\n\nFor SOUND EFFECTS or MUSIC, Kolbo supports SEMANTIC "VIBE" SEARCH: pass a natural-language description of the feeling/use ("tense ominous build-up for a horror reveal", "uplifting hopeful corporate background", "retro arcade coin pickup") with source="kolbo-ai" and mediaType="sfx" (or "music") — it matches by meaning, not just keywords. For external visual providers, use concrete keywords.\n\nsource="all" interleaves providers for the requested media type; or pick a single source. Returns assets with source, sourceId, mediaType, dimensions, author, attribution, thumbnail, and downloadable variants. To turn a script into queries first, call analyze_script_for_stock.',
|
|
29
|
+
{
|
|
30
|
+
query: z.string().max(200).optional().describe('For visual providers: concrete keywords ("city skyline sunset"). For Kolbo SFX/music (source="kolbo-ai"): a natural-language VIBE works great ("eerie suspenseful drone", "emotional sad piano"). Omit to browse.'),
|
|
31
|
+
source: z.enum(['all', 'kolbo-ai', 'pexels', 'pixabay', 'sketchfab', 'music', 'freesound']).optional().describe('Provider. "all" (default) interleaves. "kolbo-ai" = Kolbo\'s own AI SFX + music (best for vibe search). "freesound" = external CC sound effects.'),
|
|
32
|
+
mediaType: z.enum(['image', 'illustration', 'vector', 'video', '3d', 'music', 'sfx']).optional().describe('Asset type (default "image"). "sfx" = sound effects, "music" = music tracks. Not every source supports every type — call get_stock_sources.'),
|
|
33
|
+
category: z.string().optional().describe('Category/group chip value (providerParam) from get_stock_categories. For Kolbo SFX these are the Artlist groups (transitions/ambience/foley/genre/realistic).'),
|
|
34
|
+
subcategory: z.string().optional().describe('Kolbo SFX sub-filter within a group (providerParam from get_stock_categories, e.g. "risers", "footsteps", "scary-textures").'),
|
|
35
|
+
packId: z.string().optional().describe('Filter to one Kolbo themed pack id (from get_stock_collections, kind="pack").'),
|
|
36
|
+
collectionId: z.string().optional().describe('Filter to one Kolbo collection id (from get_stock_collections).'),
|
|
37
|
+
orientation: z.enum(['horizontal', 'vertical', 'landscape', 'portrait', 'square']).optional().describe('Orientation filter (provider-dependent).'),
|
|
38
|
+
color: z.string().optional().describe('Color filter (Pixabay named color, or Pexels named/hex color).'),
|
|
39
|
+
order: z.enum(['popular', 'latest']).optional().describe('Sort order (Pixabay).'),
|
|
40
|
+
cursor: z.string().optional().describe('Opaque pagination cursor for Sketchfab single-source browse (from a previous response).'),
|
|
41
|
+
page: z.number().int().min(1).optional().describe('1-based page number (default 1).'),
|
|
42
|
+
perPage: z.number().int().min(1).max(80).optional().describe('Results per page (default 24, max 80).')
|
|
43
|
+
},
|
|
44
|
+
async (args) => {
|
|
45
|
+
const q = buildQuery(args);
|
|
46
|
+
const result = await client.get(`/v1/stock/search${q ? '?' + q : ''}`);
|
|
47
|
+
const assets = result.assets || [];
|
|
48
|
+
if (!assets.length) return { content: [{ type: 'text', text: 'No assets found. Try a broader query, a different source/mediaType, or call get_stock_sources.' }] };
|
|
49
|
+
const head = `Found ${assets.length} asset${assets.length === 1 ? '' : 's'}${result.total ? ` (≈${result.total} total)` : ''}${result.hasMore ? ' — more available (increment page)' : ''}:`;
|
|
50
|
+
return { content: [{ type: 'text', text: `${head}\n\n${assets.map(assetLine).join('\n\n')}\n\nUse [source:sourceId] with get_stock_asset for full variants, or import_stock_asset to copy it into the media library.` }] };
|
|
51
|
+
}
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
// ─── get_stock_sources ────────────────────────────────────────
|
|
55
|
+
server.tool(
|
|
56
|
+
'get_stock_sources',
|
|
57
|
+
'List the enabled stock providers and which media types + filters each supports. Call this to know whether a source supports image/video/illustration/vector/3d/music before searching.',
|
|
58
|
+
{},
|
|
59
|
+
async () => {
|
|
60
|
+
const result = await client.get('/v1/stock/sources');
|
|
61
|
+
return { content: [{ type: 'text', text: JSON.stringify({ mediaTypes: result.mediaTypes, sources: result.sources }, null, 2) }] };
|
|
62
|
+
}
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// ─── get_stock_categories ─────────────────────────────────────
|
|
66
|
+
server.tool(
|
|
67
|
+
'get_stock_categories',
|
|
68
|
+
'List the dynamic category chips for stock sources. For external providers: Pixabay/Sketchfab categories + curated Pexels topics. For Kolbo SFX (source="kolbo-ai", mediaType="sfx"): the Artlist groups (top-level, group=null) AND their sub-filters (each has a `group` pointing to its parent). Pass a row\'s `providerParam` as `category` (groups) or `subcategory` (sub-filters) to search_stock_media.',
|
|
69
|
+
{
|
|
70
|
+
source: z.enum(['kolbo-ai', 'pexels', 'pixabay', 'sketchfab']).optional().describe('Restrict to one source. Use "kolbo-ai" to list the SFX groups + sub-filters.'),
|
|
71
|
+
mediaType: z.string().optional().describe('Restrict to one media type (e.g. "image", "video", "3d", "sfx").')
|
|
72
|
+
},
|
|
73
|
+
async (args) => {
|
|
74
|
+
const q = buildQuery(args);
|
|
75
|
+
const result = await client.get(`/v1/stock/categories${q ? '?' + q : ''}`);
|
|
76
|
+
return { content: [{ type: 'text', text: JSON.stringify({ count: result.count, categories: result.categories }, null, 2) }] };
|
|
77
|
+
}
|
|
78
|
+
);
|
|
79
|
+
|
|
80
|
+
// ─── get_stock_collections ────────────────────────────────────
|
|
81
|
+
server.tool(
|
|
82
|
+
'get_stock_collections',
|
|
83
|
+
'List Kolbo\'s own SFX collections — the category collections AND the curated themed packs (kind="pack": e.g. Trailer Hits, Horror & Tension, Gaming FX, Foley Essentials). Use the returned `id` as `packId` or `collectionId` in search_stock_media to browse one pack/collection. Each has a cover image.',
|
|
84
|
+
{
|
|
85
|
+
mediaType: z.string().optional().describe('Media type (default "sfx").'),
|
|
86
|
+
kind: z.enum(['category', 'pack']).optional().describe('Filter to category collections or themed packs only.')
|
|
87
|
+
},
|
|
88
|
+
async (args) => {
|
|
89
|
+
const q = buildQuery(args);
|
|
90
|
+
const result = await client.get(`/v1/stock/collections${q ? '?' + q : ''}`);
|
|
91
|
+
return { content: [{ type: 'text', text: JSON.stringify({ count: result.count, collections: result.collections }, null, 2) }] };
|
|
92
|
+
}
|
|
93
|
+
);
|
|
94
|
+
|
|
95
|
+
// ─── get_stock_asset ──────────────────────────────────────────
|
|
96
|
+
server.tool(
|
|
97
|
+
'get_stock_asset',
|
|
98
|
+
'Get a single normalized stock asset with all downloadable variants, author, license, and attribution, by source + id. Call after search_stock_media to resolve the exact download URLs (incl. WAV master + MP3 for Kolbo SFX/music).',
|
|
99
|
+
{
|
|
100
|
+
source: z.enum(['kolbo-ai', 'pexels', 'pixabay', 'sketchfab', 'music', 'freesound']).describe('The asset source.'),
|
|
101
|
+
id: z.string().describe('The provider asset id (sourceId).'),
|
|
102
|
+
mediaType: z.string().optional().describe('Media type hint (e.g. "video") — needed for sources that share ids across types.')
|
|
103
|
+
},
|
|
104
|
+
async ({ source, id, mediaType }) => {
|
|
105
|
+
const q = buildQuery({ mediaType });
|
|
106
|
+
const result = await client.get(`/v1/stock/asset/${encodeURIComponent(source)}/${encodeURIComponent(id)}${q ? '?' + q : ''}`);
|
|
107
|
+
return { content: [{ type: 'text', text: JSON.stringify(result.asset, null, 2) }] };
|
|
108
|
+
}
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
// ─── analyze_script_for_stock ─────────────────────────────────
|
|
112
|
+
server.tool(
|
|
113
|
+
'analyze_script_for_stock',
|
|
114
|
+
'AI helper that turns a video/voiceover script into stock b-roll search terms. Returns { queries[], mediaType, keywords } you can feed into search_stock_media to auto-source matching footage/photos. Use this first when the user gives you a script rather than explicit search keywords.',
|
|
115
|
+
{
|
|
116
|
+
script: z.string().min(1).describe('The video/voiceover script or scene description to analyze (up to ~8000 chars).')
|
|
117
|
+
},
|
|
118
|
+
async ({ script }) => {
|
|
119
|
+
const result = await client.post('/v1/stock/analyze-script', { script });
|
|
120
|
+
return { content: [{ type: 'text', text: JSON.stringify({ queries: result.queries, mediaType: result.mediaType, keywords: result.keywords, _followup_hint: 'Run each query through search_stock_media (source="all", mediaType=result.mediaType).' }, null, 2) }] };
|
|
121
|
+
}
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
// ─── import_stock_asset ───────────────────────────────────────
|
|
125
|
+
server.tool(
|
|
126
|
+
'import_stock_asset',
|
|
127
|
+
"Copy a stock asset into the account's Kolbo media library (downloaded to Kolbo's CDN with a stable URL) so it can be used in projects/generations. Free. Returns the created media library item. Works for Kolbo SFX (source='kolbo-ai', mediaType='sfx') and external visual/audio sources. Licensed Music (source='music') is not importable here (use the music-library tools).",
|
|
128
|
+
{
|
|
129
|
+
source: z.enum(['kolbo-ai', 'pexels', 'pixabay', 'sketchfab', 'freesound']).describe('The asset source.'),
|
|
130
|
+
id: z.string().describe('The provider asset id (sourceId).'),
|
|
131
|
+
mediaType: z.string().optional().describe('Media type hint (e.g. "video", "image", "vector", "3d").'),
|
|
132
|
+
variant: z.string().optional().describe('Which download variant label to import (from get_stock_asset). Defaults to the best/largest available.'),
|
|
133
|
+
project_id: z.string().optional().describe('Optional project id to associate the imported item with.')
|
|
134
|
+
},
|
|
135
|
+
async (args) => {
|
|
136
|
+
const result = await client.post('/v1/stock/import', args);
|
|
137
|
+
const it = result.libraryItem || {};
|
|
138
|
+
return { content: [{ type: 'text', text: `${result.alreadyImported ? 'Already in library' : 'Imported'}: ${it.url || '(no url)'}\n${JSON.stringify({ id: it._id, mediaType: it.mediaType, filename: it.filename, url: it.url }, null, 2)}` }] };
|
|
139
|
+
}
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
module.exports = { registerStockLibraryTools };
|