@kolbo/mcp 1.19.2 → 1.20.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/README.md +35 -18
- package/package.json +1 -1
- package/src/auth.js +155 -0
- package/src/client.js +35 -5
package/README.md
CHANGED
|
@@ -4,32 +4,44 @@ Use [Kolbo AI](https://kolbo.ai) as native tools in Claude Code and Claude Deskt
|
|
|
4
4
|
|
|
5
5
|
Generate images, videos, music, speech, sound effects, multi-scene campaigns, and conversational chat — all from natural language in your coding environment. 100+ AI models behind Smart Select routing, with reusable Visual DNA profiles for character/style consistency.
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Set up — paste one prompt, or one config block (keyless, no API key)
|
|
8
|
+
|
|
9
|
+
### Easiest: paste this prompt to your AI
|
|
10
|
+
|
|
11
|
+
Copy this and paste it to Claude, ChatGPT, Cursor, or any AI assistant — it installs Kolbo itself (picks local config or remote connector based on what it can do):
|
|
12
|
+
|
|
13
|
+
```
|
|
14
|
+
Set up the Kolbo AI MCP server so you can generate images, video, music and more.
|
|
15
|
+
|
|
16
|
+
- If you can edit files (Claude Code, Cursor, Claude Desktop): add this to my MCP config, then tell me to restart the app:
|
|
17
|
+
{"mcpServers":{"kolbo":{"command":"npx","args":["-y","@kolbo/mcp@latest"]}}}
|
|
18
|
+
On first use a Kolbo login opens in my browser — I'll click Allow (no API key needed).
|
|
19
|
+
- If you support remote connectors (claude.ai, ChatGPT): add the custom connector URL https://api.kolbo.ai/mcp and walk me through logging in.
|
|
20
|
+
|
|
21
|
+
Then confirm Kolbo is connected and offer to generate a test image of a sunset.
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
### Or add the config yourself
|
|
8
25
|
|
|
9
26
|
<!--
|
|
10
27
|
AI ASSISTANT SETUP INSTRUCTIONS — if you are an AI agent reading this to set up Kolbo for a user, do exactly this:
|
|
11
|
-
1.
|
|
12
|
-
|
|
13
|
-
- Claude Code: .claude/settings.json (or run: claude mcp add-json kolbo '{"command":"npx","args":["-y","@kolbo/mcp@latest"],"env":{"KOLBO_API_KEY":"kolbo_live_..."}}')
|
|
28
|
+
1. If you can edit files (Claude Code, Cursor, Claude Desktop): write the JSON block below — it has NO api key — into the user's MCP config, merging into any existing "mcpServers":
|
|
29
|
+
- Claude Code: .claude/settings.json (or run: claude mcp add kolbo -- npx -y @kolbo/mcp@latest)
|
|
14
30
|
- Claude Desktop: claude_desktop_config.json
|
|
15
31
|
- Cursor: .cursor/mcp.json
|
|
16
|
-
|
|
17
|
-
|
|
32
|
+
Then tell the user to restart. On their first generation a Kolbo login opens in the browser — they click Allow (no API key to create).
|
|
33
|
+
2. If you're a web chat that can't edit files (claude.ai, ChatGPT): tell the user to add the connector URL https://api.kolbo.ai/mcp under Settings → Connectors instead.
|
|
34
|
+
The JSON config alone enables every tool. The plugin below is an optional slash-command upgrade.
|
|
18
35
|
-->
|
|
19
36
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
**Step 2 — Add one config block.** This single block is identical for every MCP client — paste your key in and you're done:
|
|
37
|
+
This single block is identical for every MCP client and carries **no API key** — on first use it logs you in via the browser:
|
|
23
38
|
|
|
24
39
|
```json
|
|
25
40
|
{
|
|
26
41
|
"mcpServers": {
|
|
27
42
|
"kolbo": {
|
|
28
43
|
"command": "npx",
|
|
29
|
-
"args": ["-y", "@kolbo/mcp@latest"]
|
|
30
|
-
"env": {
|
|
31
|
-
"KOLBO_API_KEY": "kolbo_live_..."
|
|
32
|
-
}
|
|
44
|
+
"args": ["-y", "@kolbo/mcp@latest"]
|
|
33
45
|
}
|
|
34
46
|
}
|
|
35
47
|
}
|
|
@@ -37,13 +49,16 @@ Generate images, videos, music, speech, sound effects, multi-scene campaigns, an
|
|
|
37
49
|
|
|
38
50
|
| Client | Where the config goes |
|
|
39
51
|
|--------|----------------------|
|
|
40
|
-
| **Claude Code** | `.claude/settings.json` (or `claude mcp add-
|
|
52
|
+
| **Claude Code** | `.claude/settings.json` (or `claude mcp add kolbo -- npx -y @kolbo/mcp@latest`) |
|
|
41
53
|
| **Claude Desktop** | `claude_desktop_config.json` |
|
|
42
54
|
| **Cursor** | `.cursor/mcp.json` |
|
|
43
|
-
| **
|
|
44
|
-
| **Kolbo Code** | configured automatically on `kolbo auth login` — nothing to do |
|
|
55
|
+
| **Kolbo Code** | configured automatically on `kolbo auth login` |
|
|
45
56
|
|
|
46
|
-
Restart your app
|
|
57
|
+
Restart your app, then ask it to generate something. The first time, a Kolbo login opens in your browser — click **Allow** (no API key to create). _Prefer an API key? Create one at [app.kolbo.ai/developer](https://app.kolbo.ai/developer) and add `"env": { "KOLBO_API_KEY": "kolbo_live_..." }` to the block above._
|
|
58
|
+
|
|
59
|
+
### Browser-only (claude.ai / ChatGPT): the connector
|
|
60
|
+
|
|
61
|
+
No install at all — add the custom connector **`https://api.kolbo.ai/mcp`** under Settings → Connectors, then Connect → log in → Allow. Great for generating from text or URLs; to upload your own local files, use the config install above (it runs on your machine).
|
|
47
62
|
|
|
48
63
|
### Optional upgrade: add the Kolbo skill for slash-commands + smart routing
|
|
49
64
|
|
|
@@ -187,9 +202,11 @@ Every generation tool also accepts an optional `project_id` arg that routes the
|
|
|
187
202
|
|
|
188
203
|
## Environment Variables
|
|
189
204
|
|
|
205
|
+
Both are optional — the local install logs in via the browser on first use.
|
|
206
|
+
|
|
190
207
|
| Variable | Required | Description |
|
|
191
208
|
|----------|----------|-------------|
|
|
192
|
-
| `KOLBO_API_KEY` |
|
|
209
|
+
| `KOLBO_API_KEY` | No | Set a `kolbo_live_` key to skip the browser login (create one at [app.kolbo.ai/developer](https://app.kolbo.ai/developer)). |
|
|
193
210
|
| `KOLBO_API_URL` | No | Custom API URL (default: `https://api.kolbo.ai/api`) |
|
|
194
211
|
|
|
195
212
|
## Links
|
package/package.json
CHANGED
package/src/auth.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Keyless browser login for the LOCAL (stdio) Kolbo MCP server.
|
|
3
|
+
*
|
|
4
|
+
* When the server runs on the user's machine with no KOLBO_API_KEY and no
|
|
5
|
+
* stored credential, the first tool call triggers this: we open the browser to
|
|
6
|
+
* Kolbo's OAuth login (the same server that powers the remote connector), the
|
|
7
|
+
* user clicks Allow, and we capture a token via a loopback redirect — no API
|
|
8
|
+
* key to create or paste. The token is cached so every later run is silent.
|
|
9
|
+
*
|
|
10
|
+
* Standard "native app" OAuth: authorization-code + PKCE with a
|
|
11
|
+
* http://localhost:<port>/callback redirect (already allow-listed by the Kolbo
|
|
12
|
+
* OAuth server). This path is NOT used by the remote connector (it always
|
|
13
|
+
* injects the caller's key, and passes allowBrowserLogin:false).
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const http = require('http');
|
|
17
|
+
const crypto = require('crypto');
|
|
18
|
+
const { exec } = require('child_process');
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
const path = require('path');
|
|
21
|
+
const os = require('os');
|
|
22
|
+
|
|
23
|
+
function b64url(buf) {
|
|
24
|
+
return Buffer.from(buf).toString('base64url');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function openBrowser(url) {
|
|
28
|
+
const cmd =
|
|
29
|
+
process.platform === 'win32' ? `start "" "${url}"`
|
|
30
|
+
: process.platform === 'darwin' ? `open "${url}"`
|
|
31
|
+
: `xdg-open "${url}"`;
|
|
32
|
+
try { exec(cmd, () => {}); } catch (_) { /* best effort */ }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Where we cache the token — same location + shape that client.js reads back
|
|
36
|
+
// (`<xdg-data>/kolbo/auth.json` → { "kolbo@<host>": { type: 'api', key } }).
|
|
37
|
+
function authStorePath() {
|
|
38
|
+
const dataDir =
|
|
39
|
+
process.env.XDG_DATA_HOME ||
|
|
40
|
+
(process.platform === 'win32'
|
|
41
|
+
? (process.env.LOCALAPPDATA || path.join(os.homedir(), '.local', 'share'))
|
|
42
|
+
: process.platform === 'darwin'
|
|
43
|
+
? path.join(os.homedir(), 'Library', 'Application Support')
|
|
44
|
+
: path.join(os.homedir(), '.local', 'share'));
|
|
45
|
+
return path.join(dataDir, 'kolbo', 'auth.json');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function storeKey(apiHost, key) {
|
|
49
|
+
try {
|
|
50
|
+
const file = authStorePath();
|
|
51
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
52
|
+
let store = {};
|
|
53
|
+
try { store = JSON.parse(fs.readFileSync(file, 'utf8')); } catch (_) {}
|
|
54
|
+
store[`kolbo@${apiHost}`] = { type: 'api', key, savedAt: new Date().toISOString() };
|
|
55
|
+
fs.writeFileSync(file, JSON.stringify(store, null, 2), { mode: 0o600 });
|
|
56
|
+
} catch (_) { /* non-fatal — the key still works for this process */ }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function donePage(ok) {
|
|
60
|
+
const title = ok ? 'Connected to Kolbo' : 'Connection cancelled';
|
|
61
|
+
const sub = ok ? 'You can close this tab and return to your app.' : 'You can close this tab.';
|
|
62
|
+
const mark = ok ? '✓' : '✕';
|
|
63
|
+
return `<!doctype html><meta charset="utf-8"><title>${title}</title>` +
|
|
64
|
+
`<body style="margin:0;font-family:Inter,system-ui,sans-serif;background:#05050f;color:#fff;` +
|
|
65
|
+
`display:flex;align-items:center;justify-content:center;height:100vh">` +
|
|
66
|
+
`<div style="text-align:center"><div style="font-size:42px;color:#8B5CF6;margin-bottom:8px">${mark}</div>` +
|
|
67
|
+
`<h2 style="margin:0 0 6px">${title}</h2><p style="opacity:.55;font-size:14px">${sub}</p></div></body>`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Run the interactive browser login. Resolves with the kolbo_live_ key.
|
|
72
|
+
* @param {object} opts
|
|
73
|
+
* @param {string} opts.apiBase e.g. https://api.kolbo.ai/api
|
|
74
|
+
*/
|
|
75
|
+
async function browserLogin({ apiBase }) {
|
|
76
|
+
// The OAuth endpoints live at the host root, not under /api.
|
|
77
|
+
const oauthBase = apiBase.replace(/\/api\/?$/, '');
|
|
78
|
+
let apiHost = 'api.kolbo.ai';
|
|
79
|
+
try { apiHost = new URL(apiBase).host; } catch (_) {}
|
|
80
|
+
|
|
81
|
+
const verifier = b64url(crypto.randomBytes(32));
|
|
82
|
+
const challenge = b64url(crypto.createHash('sha256').update(verifier).digest());
|
|
83
|
+
const state = b64url(crypto.randomBytes(16));
|
|
84
|
+
|
|
85
|
+
// Loopback callback server on a random free port.
|
|
86
|
+
const server = http.createServer();
|
|
87
|
+
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
88
|
+
const port = server.address().port;
|
|
89
|
+
const redirectUri = `http://localhost:${port}/callback`;
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
// 1. Dynamic client registration (public + PKCE).
|
|
93
|
+
const regRes = await fetch(`${oauthBase}/oauth/register`, {
|
|
94
|
+
method: 'POST',
|
|
95
|
+
headers: { 'Content-Type': 'application/json' },
|
|
96
|
+
body: JSON.stringify({ client_name: 'Kolbo MCP (local)', redirect_uris: [redirectUri] }),
|
|
97
|
+
});
|
|
98
|
+
if (!regRes.ok) throw new Error(`client registration failed (${regRes.status})`);
|
|
99
|
+
const { client_id } = await regRes.json();
|
|
100
|
+
|
|
101
|
+
// 2. Wait for the browser redirect to hit our loopback server.
|
|
102
|
+
const codePromise = new Promise((resolve, reject) => {
|
|
103
|
+
const timer = setTimeout(() => reject(new Error('login timed out (5 min)')), 5 * 60 * 1000);
|
|
104
|
+
server.on('request', (req, resp) => {
|
|
105
|
+
let u;
|
|
106
|
+
try { u = new URL(req.url, redirectUri); } catch (_) { resp.writeHead(400); resp.end(); return; }
|
|
107
|
+
if (u.pathname !== '/callback') { resp.writeHead(404); resp.end(); return; }
|
|
108
|
+
clearTimeout(timer);
|
|
109
|
+
const code = u.searchParams.get('code');
|
|
110
|
+
const st = u.searchParams.get('state');
|
|
111
|
+
const err = u.searchParams.get('error');
|
|
112
|
+
resp.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
113
|
+
resp.end(donePage(!err && !!code));
|
|
114
|
+
if (err) return reject(new Error(`login denied: ${err}`));
|
|
115
|
+
if (!code || st !== state) return reject(new Error('login: invalid callback'));
|
|
116
|
+
resolve(code);
|
|
117
|
+
});
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// 3. Open the consent/login page.
|
|
121
|
+
const authUrl =
|
|
122
|
+
`${oauthBase}/oauth/authorize?response_type=code&client_id=${encodeURIComponent(client_id)}` +
|
|
123
|
+
`&redirect_uri=${encodeURIComponent(redirectUri)}&code_challenge=${challenge}` +
|
|
124
|
+
`&code_challenge_method=S256&state=${state}&scope=kolbo`;
|
|
125
|
+
openBrowser(authUrl);
|
|
126
|
+
process.stderr.write(
|
|
127
|
+
`\n[kolbo] Connect your Kolbo account in the browser. If it didn't open, visit:\n${authUrl}\n\n`
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const code = await codePromise;
|
|
131
|
+
|
|
132
|
+
// 4. Exchange the code (with the PKCE verifier) for the token.
|
|
133
|
+
const tokRes = await fetch(`${oauthBase}/oauth/token`, {
|
|
134
|
+
method: 'POST',
|
|
135
|
+
headers: { 'Content-Type': 'application/json' },
|
|
136
|
+
body: JSON.stringify({
|
|
137
|
+
grant_type: 'authorization_code',
|
|
138
|
+
code,
|
|
139
|
+
code_verifier: verifier,
|
|
140
|
+
redirect_uri: redirectUri,
|
|
141
|
+
client_id,
|
|
142
|
+
}),
|
|
143
|
+
});
|
|
144
|
+
if (!tokRes.ok) throw new Error(`token exchange failed (${tokRes.status})`);
|
|
145
|
+
const tok = await tokRes.json();
|
|
146
|
+
if (!tok.access_token) throw new Error('login: no access_token returned');
|
|
147
|
+
|
|
148
|
+
storeKey(apiHost, tok.access_token);
|
|
149
|
+
return tok.access_token;
|
|
150
|
+
} finally {
|
|
151
|
+
try { server.close(); } catch (_) {}
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
module.exports = { browserLogin };
|
package/src/client.js
CHANGED
|
@@ -158,17 +158,45 @@ class KolboClient {
|
|
|
158
158
|
this._explicitKey = opts.apiKey || null;
|
|
159
159
|
this._envKey = process.env.KOLBO_API_KEY || null;
|
|
160
160
|
this._authStoreKey = null; // lazy-loaded
|
|
161
|
+
// Local stdio servers (raw `npx @kolbo/mcp` in Claude Desktop / Code / Cursor)
|
|
162
|
+
// may start with no key and log in via the browser on first use. Disabled when:
|
|
163
|
+
// - the host opts out (remote HTTP connector passes allowBrowserLogin:false —
|
|
164
|
+
// it always injects the caller's key, and opening a browser on a server is
|
|
165
|
+
// nonsensical), or
|
|
166
|
+
// - we're spawned by Kolbo Code (it sets KOLBO_CALLER_SESSION_ID and runs its
|
|
167
|
+
// OWN in-app sign-in off the [KOLBO_AUTH_MISSING] error — don't double up).
|
|
168
|
+
this._allowBrowserLogin =
|
|
169
|
+
opts.allowBrowserLogin !== undefined
|
|
170
|
+
? opts.allowBrowserLogin
|
|
171
|
+
: !process.env.KOLBO_CALLER_SESSION_ID;
|
|
172
|
+
this._loginPromise = null;
|
|
161
173
|
this.apiKey = this._explicitKey || this._envKey || this._readAuthStore();
|
|
162
174
|
|
|
163
|
-
if (!this.apiKey) {
|
|
164
|
-
// No key in env OR auth store. The Kolbo Code parent process should
|
|
165
|
-
// never spawn us in this state (it injects the key into env after the
|
|
166
|
-
// user signs in). If this fires, the parent will catch it via the
|
|
167
|
-
// [KOLBO_AUTH_MISSING] tag and surface the in-app sign-in flow.
|
|
175
|
+
if (!this.apiKey && !this._allowBrowserLogin) {
|
|
168
176
|
throw new Error(
|
|
169
177
|
'Kolbo API key not found. Sign in to Kolbo to continue. [KOLBO_AUTH_MISSING]'
|
|
170
178
|
);
|
|
171
179
|
}
|
|
180
|
+
// When allowBrowserLogin is on and there's no key yet, we DON'T throw —
|
|
181
|
+
// the first request triggers an interactive browser login (see _ensureLogin).
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Ensure we have a key before a request. If none, run the one-time browser
|
|
186
|
+
* login (single-flight so concurrent first calls share one login window).
|
|
187
|
+
*/
|
|
188
|
+
async _ensureLogin() {
|
|
189
|
+
if (this.apiKey) return;
|
|
190
|
+
if (!this._allowBrowserLogin) {
|
|
191
|
+
throw new Error('Kolbo API key not found. Sign in to Kolbo to continue. [KOLBO_AUTH_MISSING]');
|
|
192
|
+
}
|
|
193
|
+
if (!this._loginPromise) {
|
|
194
|
+
const { browserLogin } = require('./auth');
|
|
195
|
+
this._loginPromise = browserLogin({ apiBase: this.baseUrl })
|
|
196
|
+
.then((key) => { this.apiKey = key; this._explicitKey = key; return key; })
|
|
197
|
+
.catch((err) => { this._loginPromise = null; throw err; });
|
|
198
|
+
}
|
|
199
|
+
await this._loginPromise;
|
|
172
200
|
}
|
|
173
201
|
|
|
174
202
|
_readAuthStore() {
|
|
@@ -202,6 +230,7 @@ class KolboClient {
|
|
|
202
230
|
}
|
|
203
231
|
|
|
204
232
|
async request(method, reqPath, body = null) {
|
|
233
|
+
if (!this.apiKey) await this._ensureLogin();
|
|
205
234
|
const result = await this._doRequest(method, reqPath, body);
|
|
206
235
|
|
|
207
236
|
// On 401, try re-reading auth store and retry once
|
|
@@ -291,6 +320,7 @@ class KolboClient {
|
|
|
291
320
|
}
|
|
292
321
|
|
|
293
322
|
async postMultipart(reqPath, formData) {
|
|
323
|
+
if (!this.apiKey) await this._ensureLogin();
|
|
294
324
|
const result = await this._doMultipart(reqPath, formData);
|
|
295
325
|
if (result._status === 401 && this._tryRefreshKey()) {
|
|
296
326
|
return this._doMultipart(reqPath, formData);
|