@kolbo/mcp 1.3.0 → 1.3.2
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 +1 -1
- package/src/client.js +312 -130
- package/src/tools/generate.js +1 -0
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -1,130 +1,312 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
});
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const os = require('os');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Kolbo API HTTP client wrapper
|
|
7
|
+
*
|
|
8
|
+
* Auth resolution (first match wins):
|
|
9
|
+
* 1. KOLBO_API_KEY env var — explicit key, always honored
|
|
10
|
+
* 2. CLI auth store (auth.json) — auto-shared with `kolbo auth login`
|
|
11
|
+
*
|
|
12
|
+
* API base resolution (mirrors CLI partner.ts):
|
|
13
|
+
* 1. KOLBO_API_URL env var — explicit override
|
|
14
|
+
* 2. KOLBO_API_BASE env var — same as CLI
|
|
15
|
+
* 3. partner.json on disk — whitelabel config
|
|
16
|
+
* 4. https://api.kolbo.ai/api — default
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
class KolboApiError extends Error {
|
|
20
|
+
constructor(message, { code, status, data } = {}) {
|
|
21
|
+
super(message);
|
|
22
|
+
this.name = 'KolboApiError';
|
|
23
|
+
this.code = code || null;
|
|
24
|
+
this.status = status || null;
|
|
25
|
+
this.data = data || null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
// Partner / whitelabel resolution (mirrors CLI's brand/partner.ts)
|
|
31
|
+
// ---------------------------------------------------------------------------
|
|
32
|
+
|
|
33
|
+
function readJsonSync(file) {
|
|
34
|
+
try {
|
|
35
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
36
|
+
} catch (_) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the API base URL, checking the same sources as the CLI:
|
|
43
|
+
* 1. KOLBO_API_URL / KOLBO_API_BASE env vars
|
|
44
|
+
* 2. partner.json files (KOLBO_PARTNER_PROFILE, XDG_CONFIG_HOME, ~/.config)
|
|
45
|
+
* 3. Default: https://api.kolbo.ai/api
|
|
46
|
+
*/
|
|
47
|
+
function resolveApiBase() {
|
|
48
|
+
// Env vars take priority
|
|
49
|
+
const fromEnv = process.env.KOLBO_API_URL || process.env.KOLBO_API_BASE;
|
|
50
|
+
if (fromEnv) return fromEnv.replace(/\/$/, '');
|
|
51
|
+
|
|
52
|
+
// Partner profile files (same order as CLI)
|
|
53
|
+
const candidates = [];
|
|
54
|
+
if (process.env.KOLBO_PARTNER_PROFILE) {
|
|
55
|
+
candidates.push(process.env.KOLBO_PARTNER_PROFILE);
|
|
56
|
+
}
|
|
57
|
+
const xdgConfig = process.env.XDG_CONFIG_HOME || (
|
|
58
|
+
process.platform === 'win32'
|
|
59
|
+
? path.join(os.homedir(), '.config')
|
|
60
|
+
: path.join(os.homedir(), '.config')
|
|
61
|
+
);
|
|
62
|
+
candidates.push(path.join(xdgConfig, 'kolbo', 'partner.json'));
|
|
63
|
+
|
|
64
|
+
for (const file of candidates) {
|
|
65
|
+
const data = readJsonSync(file);
|
|
66
|
+
if (data && data.apiBase) return data.apiBase.replace(/\/$/, '');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
return 'https://api.kolbo.ai/api';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// ---------------------------------------------------------------------------
|
|
73
|
+
// CLI auth store reader
|
|
74
|
+
// ---------------------------------------------------------------------------
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* XDG data dir — same logic as the `xdg-basedir` npm package the CLI uses.
|
|
78
|
+
* On Windows with Git Bash / MSYS2 this resolves to ~/.local/share (matching
|
|
79
|
+
* what the CLI actually writes to).
|
|
80
|
+
*/
|
|
81
|
+
function xdgDataDir() {
|
|
82
|
+
if (process.env.XDG_DATA_HOME) return process.env.XDG_DATA_HOME;
|
|
83
|
+
if (process.platform === 'win32') {
|
|
84
|
+
// xdg-basedir on Windows: LOCALAPPDATA → ~/.local/share fallback
|
|
85
|
+
return process.env.LOCALAPPDATA || path.join(os.homedir(), '.local', 'share');
|
|
86
|
+
}
|
|
87
|
+
if (process.platform === 'darwin') {
|
|
88
|
+
return path.join(os.homedir(), 'Library', 'Application Support');
|
|
89
|
+
}
|
|
90
|
+
return path.join(os.homedir(), '.local', 'share');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Read the Kolbo CLI's auth store. The CLI writes credentials to
|
|
95
|
+
* <xdg-data>/kolbo/auth.json after device-code login.
|
|
96
|
+
*
|
|
97
|
+
* On Windows (Git Bash / MSYS2) xdg-basedir resolves to ~/.local/share,
|
|
98
|
+
* so we check multiple candidates to be safe.
|
|
99
|
+
*/
|
|
100
|
+
function readCliAuthKey() {
|
|
101
|
+
const dataDir = xdgDataDir();
|
|
102
|
+
const candidates = [
|
|
103
|
+
path.join(dataDir, 'kolbo', 'auth.json'),
|
|
104
|
+
];
|
|
105
|
+
// Windows fallback: also check ~/.local/share if LOCALAPPDATA was primary
|
|
106
|
+
if (process.platform === 'win32' && dataDir !== path.join(os.homedir(), '.local', 'share')) {
|
|
107
|
+
candidates.push(path.join(os.homedir(), '.local', 'share', 'kolbo', 'auth.json'));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Determine the API host for namespaced auth lookup
|
|
111
|
+
const apiBase = process.env.KOLBO_API_URL || process.env.KOLBO_API_BASE || '';
|
|
112
|
+
let apiHost = null;
|
|
113
|
+
try { apiHost = new URL(apiBase).host; } catch (_) {}
|
|
114
|
+
if (!apiHost) {
|
|
115
|
+
// Check partner.json for the API host
|
|
116
|
+
const partnerCandidates = [];
|
|
117
|
+
if (process.env.KOLBO_PARTNER_PROFILE) partnerCandidates.push(process.env.KOLBO_PARTNER_PROFILE);
|
|
118
|
+
const xdgCfg = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
|
|
119
|
+
partnerCandidates.push(path.join(xdgCfg, 'kolbo', 'partner.json'));
|
|
120
|
+
for (const f of partnerCandidates) {
|
|
121
|
+
const p = readJsonSync(f);
|
|
122
|
+
if (p && p.apiBase) { try { apiHost = new URL(p.apiBase).host; } catch (_) {} break; }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!apiHost) apiHost = 'api.kolbo.ai';
|
|
126
|
+
|
|
127
|
+
for (const file of candidates) {
|
|
128
|
+
try {
|
|
129
|
+
const auth = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
130
|
+
// Try namespaced key first (e.g. "kolbo@api.kolbo.ai"), then bare "kolbo"
|
|
131
|
+
const entry = auth[`kolbo@${apiHost}`] || auth.kolbo;
|
|
132
|
+
if (!entry) continue;
|
|
133
|
+
if (entry.type === 'oauth' && entry.refresh) return entry.refresh;
|
|
134
|
+
if (entry.type === 'api' && entry.key) return entry.key;
|
|
135
|
+
} catch (_) {
|
|
136
|
+
// File doesn't exist or isn't valid JSON — try next
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return null;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
// HTTP client
|
|
144
|
+
// ---------------------------------------------------------------------------
|
|
145
|
+
|
|
146
|
+
class KolboClient {
|
|
147
|
+
constructor() {
|
|
148
|
+
this.baseUrl = resolveApiBase();
|
|
149
|
+
this._envKey = process.env.KOLBO_API_KEY || null;
|
|
150
|
+
this._authStoreKey = null; // lazy-loaded
|
|
151
|
+
this.apiKey = this._envKey || this._readAuthStore();
|
|
152
|
+
|
|
153
|
+
if (!this.apiKey) {
|
|
154
|
+
throw new Error(
|
|
155
|
+
'Kolbo API key not found.\n' +
|
|
156
|
+
'Fix: Run "kolbo auth login" in the terminal, then restart this editor.\n' +
|
|
157
|
+
'Or: Get an API key at https://app.kolbo.ai/developer and set KOLBO_API_KEY env var.'
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
_readAuthStore() {
|
|
163
|
+
this._authStoreKey = readCliAuthKey();
|
|
164
|
+
return this._authStoreKey;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* On 401, re-read the CLI auth store in case the user re-authenticated
|
|
169
|
+
* since the MCP server started. Returns true if a new key was found.
|
|
170
|
+
*/
|
|
171
|
+
_tryRefreshKey() {
|
|
172
|
+
if (this._envKey) {
|
|
173
|
+
// Env var is set but invalid — can't override it, but try auth store
|
|
174
|
+
const fresh = readCliAuthKey();
|
|
175
|
+
if (fresh && fresh !== this._envKey) {
|
|
176
|
+
this.apiKey = fresh;
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
const fresh = readCliAuthKey();
|
|
182
|
+
if (fresh && fresh !== this.apiKey) {
|
|
183
|
+
this.apiKey = fresh;
|
|
184
|
+
return true;
|
|
185
|
+
}
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async request(method, reqPath, body = null) {
|
|
190
|
+
const result = await this._doRequest(method, reqPath, body);
|
|
191
|
+
|
|
192
|
+
// On 401, try re-reading auth store and retry once
|
|
193
|
+
if (result._status === 401 && this._tryRefreshKey()) {
|
|
194
|
+
return this._doRequest(method, reqPath, body);
|
|
195
|
+
}
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async _doRequest(method, reqPath, body = null) {
|
|
200
|
+
const url = `${this.baseUrl}${reqPath}`;
|
|
201
|
+
const options = {
|
|
202
|
+
method,
|
|
203
|
+
headers: {
|
|
204
|
+
'X-API-Key': this.apiKey,
|
|
205
|
+
'Content-Type': 'application/json'
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
if (body) {
|
|
210
|
+
options.body = JSON.stringify(body);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
const response = await fetch(url, options);
|
|
214
|
+
let data;
|
|
215
|
+
try {
|
|
216
|
+
data = await response.json();
|
|
217
|
+
} catch (_) {
|
|
218
|
+
throw new KolboApiError(`API error: ${response.status} ${response.statusText}`, {
|
|
219
|
+
status: response.status,
|
|
220
|
+
data: null
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (!response.ok || data.success === false) {
|
|
225
|
+
const message = data.error || data.message || `API error: ${response.status}`;
|
|
226
|
+
const code = data.code || null;
|
|
227
|
+
let fullMessage = code ? `${message} [${code}]` : message;
|
|
228
|
+
if (response.status === 401) {
|
|
229
|
+
// Tag the response so the retry logic in request() can see it
|
|
230
|
+
data._status = 401;
|
|
231
|
+
fullMessage += '\n\nAPI key is invalid or expired. Fix: run "kolbo auth login" in the terminal, then restart this editor. Or get a new key at https://app.kolbo.ai/developer';
|
|
232
|
+
}
|
|
233
|
+
throw new KolboApiError(fullMessage, {
|
|
234
|
+
code,
|
|
235
|
+
status: response.status,
|
|
236
|
+
data
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return data;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async post(reqPath, body) {
|
|
244
|
+
return this.request('POST', reqPath, body);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
async get(reqPath) {
|
|
248
|
+
return this.request('GET', reqPath);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async delete(reqPath) {
|
|
252
|
+
return this.request('DELETE', reqPath);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async postMultipart(reqPath, formData) {
|
|
256
|
+
const result = await this._doMultipart(reqPath, formData);
|
|
257
|
+
if (result._status === 401 && this._tryRefreshKey()) {
|
|
258
|
+
return this._doMultipart(reqPath, formData);
|
|
259
|
+
}
|
|
260
|
+
return result;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async _doMultipart(reqPath, formData) {
|
|
264
|
+
const url = `${this.baseUrl}${reqPath}`;
|
|
265
|
+
const headers = {
|
|
266
|
+
'X-API-Key': this.apiKey,
|
|
267
|
+
...formData.getHeaders()
|
|
268
|
+
};
|
|
269
|
+
|
|
270
|
+
// Serialize form-data to a Buffer before passing to fetch(). Node's
|
|
271
|
+
// built-in fetch (undici) can't consume legacy Node.js streams from
|
|
272
|
+
// the `form-data` package, causing "fetch failed" on local file uploads.
|
|
273
|
+
const body = formData.getBuffer();
|
|
274
|
+
headers['Content-Length'] = String(body.length);
|
|
275
|
+
|
|
276
|
+
const response = await fetch(url, {
|
|
277
|
+
method: 'POST',
|
|
278
|
+
headers,
|
|
279
|
+
body
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
let data;
|
|
283
|
+
try {
|
|
284
|
+
data = await response.json();
|
|
285
|
+
} catch (_) {
|
|
286
|
+
throw new KolboApiError(`API error: ${response.status} ${response.statusText}`, {
|
|
287
|
+
status: response.status,
|
|
288
|
+
data: null
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (!response.ok || data.success === false) {
|
|
293
|
+
const message = data.error || data.message || `API error: ${response.status}`;
|
|
294
|
+
const code = data.code || null;
|
|
295
|
+
let fullMessage = code ? `${message} [${code}]` : message;
|
|
296
|
+
if (response.status === 401) {
|
|
297
|
+
data._status = 401;
|
|
298
|
+
fullMessage += '\n\nAPI key is invalid or expired. Fix: run "kolbo auth login" in the terminal, then restart this editor. Or get a new key at https://app.kolbo.ai/developer';
|
|
299
|
+
}
|
|
300
|
+
throw new KolboApiError(fullMessage, {
|
|
301
|
+
code,
|
|
302
|
+
status: response.status,
|
|
303
|
+
data
|
|
304
|
+
});
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return data;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
module.exports = KolboClient;
|
|
312
|
+
module.exports.KolboApiError = KolboApiError;
|
package/src/tools/generate.js
CHANGED
|
@@ -661,6 +661,7 @@ function registerGenerateTools(server, client) {
|
|
|
661
661
|
text: JSON.stringify({
|
|
662
662
|
text: result.result?.text || '',
|
|
663
663
|
srt_url: result.result?.srt_url || null,
|
|
664
|
+
word_by_word_srt_url: result.result?.word_by_word_srt_url || null,
|
|
664
665
|
txt_url: result.result?.txt_url || null,
|
|
665
666
|
duration: result.result?.duration || null
|
|
666
667
|
}, null, 2)
|