kaushalstack-mcp 0.1.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 +105 -0
- package/package.json +24 -0
- package/src/index.js +240 -0
package/README.md
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# kaushalstack-mcp
|
|
2
|
+
|
|
3
|
+
> Headless kaushalstack — recommend agents, run round tables, generate specs from any MCP host.
|
|
4
|
+
|
|
5
|
+
MCP server that exposes [kaushalstack](https://kaushalstack.com) as tools for
|
|
6
|
+
any MCP host — Claude Desktop, Codex, Cursor, your own scripts.
|
|
7
|
+
|
|
8
|
+
Use this when you want an LLM agent to **assemble a domain round table,
|
|
9
|
+
convene a tech round table, generate a spec, or browse past chats** —
|
|
10
|
+
without using the web UI.
|
|
11
|
+
|
|
12
|
+
## What it exposes
|
|
13
|
+
|
|
14
|
+
| Tool | Maps to | Purpose |
|
|
15
|
+
|---|---|---|
|
|
16
|
+
| `recommend_agents` | `POST /api/recommend` | Pick 6–10 domain specialists for a prompt |
|
|
17
|
+
| `recommend_tech_agents` | `POST /api/recommend/tech` | Pick 4–8 engineers off a spec |
|
|
18
|
+
| `run_roundtable` | `POST /api/roundtable` | Run a discussion (domain or tech mode) |
|
|
19
|
+
| `generate_spec` | `POST /api/spec` | Aisha synthesizes a spec from a chat |
|
|
20
|
+
| `list_chats` | `GET /api/roundtable/chats` | List recent chats + their state |
|
|
21
|
+
|
|
22
|
+
## Auth
|
|
23
|
+
|
|
24
|
+
Get your kaushalstack PocketBase token:
|
|
25
|
+
|
|
26
|
+
1. Sign in at https://kaushalstack.com
|
|
27
|
+
2. Open DevTools → Application → Local Storage → `https://kaushalstack.com`
|
|
28
|
+
3. Copy the `token` field out of the `pb_auth` entry
|
|
29
|
+
|
|
30
|
+
Set it as `KAUSHALSTACK_API_TOKEN` in the host's MCP config (examples below).
|
|
31
|
+
|
|
32
|
+
> The recommend tools work without a token (they only call public
|
|
33
|
+
> endpoints). Everything else — running a round table, generating a spec,
|
|
34
|
+
> listing chats — needs the token because the result is bound to your
|
|
35
|
+
> user account.
|
|
36
|
+
|
|
37
|
+
## Install
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Once published to npm
|
|
41
|
+
npx -y kaushalstack-mcp
|
|
42
|
+
|
|
43
|
+
# Until then, run from the monorepo
|
|
44
|
+
cd apps/mcp && npm install && node src/index.js
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Claude Desktop config
|
|
48
|
+
|
|
49
|
+
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`
|
|
50
|
+
(macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"mcpServers": {
|
|
55
|
+
"kaushalstack": {
|
|
56
|
+
"command": "npx",
|
|
57
|
+
"args": ["-y", "kaushalstack-mcp"],
|
|
58
|
+
"env": {
|
|
59
|
+
"KAUSHALSTACK_API_URL": "https://kaushalstack.com",
|
|
60
|
+
"KAUSHALSTACK_API_TOKEN": "eyJhbGciOi..."
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Restart Claude Desktop. The tools appear under the 🔌 attachment menu.
|
|
68
|
+
|
|
69
|
+
## Codex / Cursor / other MCP hosts
|
|
70
|
+
|
|
71
|
+
Same shape — point at the `kaushalstack-mcp` binary and pass the two env
|
|
72
|
+
vars. Stdio transport, no extra setup.
|
|
73
|
+
|
|
74
|
+
## Typical session
|
|
75
|
+
|
|
76
|
+
A host (Claude / Codex) using these tools might do:
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
1. recommend_agents({ query: "I want to start a kirana shop online" })
|
|
80
|
+
→ 6 domain specialists matching the prompt
|
|
81
|
+
2. run_roundtable({ query, team: <the 6>, kind: "domain" })
|
|
82
|
+
→ 6 perspectives, returns chat_id
|
|
83
|
+
3. generate_spec({ chat_id })
|
|
84
|
+
→ one-page spec drafted from the discussion
|
|
85
|
+
4. recommend_tech_agents({ query: <spec_text> })
|
|
86
|
+
→ 5 engineers scored against the spec, not the original prompt
|
|
87
|
+
5. run_roundtable({ query: <"review this spec…">, team: <the 5>,
|
|
88
|
+
chat_id, kind: "tech" })
|
|
89
|
+
→ tech-team perspectives appended to the chat
|
|
90
|
+
6. generate_spec({ chat_id })
|
|
91
|
+
→ v2 spec absorbs both transcripts; Authors line includes everyone
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
That's the entire kaushalstack core loop, callable headlessly.
|
|
95
|
+
|
|
96
|
+
## Config reference
|
|
97
|
+
|
|
98
|
+
| Env var | Default | Purpose |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| `KAUSHALSTACK_API_URL` | `https://kaushalstack.com` | Override for self-hosted deploys |
|
|
101
|
+
| `KAUSHALSTACK_API_TOKEN` | _(unset)_ | Required for run_roundtable, generate_spec, list_chats |
|
|
102
|
+
|
|
103
|
+
## License
|
|
104
|
+
|
|
105
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "kaushalstack-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server exposing kaushalstack — recommendation, round table, spec — as tools for Claude Desktop / Codex / any MCP host.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"kaushalstack-mcp": "src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "src/index.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"start": "node src/index.js"
|
|
12
|
+
},
|
|
13
|
+
"engines": {
|
|
14
|
+
"node": ">=20"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"src/",
|
|
21
|
+
"README.md"
|
|
22
|
+
],
|
|
23
|
+
"license": "MIT"
|
|
24
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// kaushalstack MCP server — stdio transport.
|
|
3
|
+
//
|
|
4
|
+
// Exposes five tools to any MCP host (Claude Desktop, Codex, etc.):
|
|
5
|
+
// recommend_agents → /api/recommend (domain round-table team)
|
|
6
|
+
// recommend_tech_agents → /api/recommend/tech (tech round-table team)
|
|
7
|
+
// run_roundtable → /api/roundtable (domain or tech RT)
|
|
8
|
+
// generate_spec → /api/spec (Aisha synthesis)
|
|
9
|
+
// list_chats → /api/roundtable/chats (recent chats + state)
|
|
10
|
+
//
|
|
11
|
+
// Config (env vars):
|
|
12
|
+
// KAUSHALSTACK_API_URL default https://kaushalstack.com
|
|
13
|
+
// KAUSHALSTACK_API_TOKEN required — PocketBase auth token, copy from
|
|
14
|
+
// kaushalstack.com after signing in (devtools →
|
|
15
|
+
// Application → Local Storage → pocketbase_auth).
|
|
16
|
+
//
|
|
17
|
+
// The host configures the server via stdio. Tool inputs use plain JSON
|
|
18
|
+
// schemas so the host's LLM can call them without extra adapters.
|
|
19
|
+
|
|
20
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
21
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
22
|
+
import {
|
|
23
|
+
CallToolRequestSchema,
|
|
24
|
+
ListToolsRequestSchema,
|
|
25
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
26
|
+
|
|
27
|
+
const API_URL = (process.env.KAUSHALSTACK_API_URL || 'https://kaushalstack.com').replace(/\/$/, '');
|
|
28
|
+
const API_TOKEN = process.env.KAUSHALSTACK_API_TOKEN || '';
|
|
29
|
+
|
|
30
|
+
function authHeaders() {
|
|
31
|
+
return API_TOKEN ? { Authorization: `Bearer ${API_TOKEN}` } : {};
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Tiny fetch wrapper that throws on non-2xx so tool handlers stay short.
|
|
35
|
+
async function apiCall(method, path, body) {
|
|
36
|
+
const res = await fetch(`${API_URL}/api${path}`, {
|
|
37
|
+
method,
|
|
38
|
+
headers: {
|
|
39
|
+
'Content-Type': 'application/json',
|
|
40
|
+
...authHeaders(),
|
|
41
|
+
},
|
|
42
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
43
|
+
});
|
|
44
|
+
const text = await res.text();
|
|
45
|
+
if (!res.ok) {
|
|
46
|
+
let detail = text;
|
|
47
|
+
try { detail = JSON.parse(text)?.error || text; } catch { /* leave as text */ }
|
|
48
|
+
throw new Error(`${method} ${path} → ${res.status}: ${detail.slice(0, 300)}`);
|
|
49
|
+
}
|
|
50
|
+
try { return JSON.parse(text); } catch { return { raw: text }; }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── Tool definitions ────────────────────────────────────────────────────
|
|
54
|
+
|
|
55
|
+
const TOOLS = [
|
|
56
|
+
{
|
|
57
|
+
name: 'recommend_agents',
|
|
58
|
+
description:
|
|
59
|
+
'Recommend a round-table team of DOMAIN specialists for a given user prompt. ' +
|
|
60
|
+
'Returns 6–10 skills matched by semantic similarity. Excludes tech (use ' +
|
|
61
|
+
'recommend_tech_agents for engineers) and pipeline-only agents (Maya, Ananya, ' +
|
|
62
|
+
'Hostinger). Optional phase narrows to ideation / execution / marketing.',
|
|
63
|
+
inputSchema: {
|
|
64
|
+
type: 'object',
|
|
65
|
+
properties: {
|
|
66
|
+
query: { type: 'string', description: 'The user prompt to recommend a team for.' },
|
|
67
|
+
size: { type: 'integer', minimum: 6, maximum: 10, default: 6 },
|
|
68
|
+
phase: { type: 'string', enum: ['ideation', 'execution', 'marketing'], description: 'Optional phase filter.' },
|
|
69
|
+
},
|
|
70
|
+
required: ['query'],
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
name: 'recommend_tech_agents',
|
|
75
|
+
description:
|
|
76
|
+
'Recommend a parallel round-table team of TECH specialists (engineers) for ' +
|
|
77
|
+
'a given spec or technical brief. Use this AFTER a domain round table has ' +
|
|
78
|
+
'produced a spec — pass the spec text as the query so the recommendation ' +
|
|
79
|
+
'matches the actual engineering surface area. Returns 4–8 skills, all in ' +
|
|
80
|
+
'the Tech category.',
|
|
81
|
+
inputSchema: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
properties: {
|
|
84
|
+
query: { type: 'string', description: 'The spec text or technical brief.' },
|
|
85
|
+
size: { type: 'integer', minimum: 4, maximum: 8, default: 5 },
|
|
86
|
+
},
|
|
87
|
+
required: ['query'],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'run_roundtable',
|
|
92
|
+
description:
|
|
93
|
+
'Run a round-table discussion. Domain mode (default) creates or continues a ' +
|
|
94
|
+
'chat with specialist agents discussing the user prompt. Tech mode (kind="tech") ' +
|
|
95
|
+
'requires an existing chat_id and appends a parallel tech-team discussion ' +
|
|
96
|
+
'on the spec. Returns the agents\' responses and the chat_id for follow-ups.',
|
|
97
|
+
inputSchema: {
|
|
98
|
+
type: 'object',
|
|
99
|
+
properties: {
|
|
100
|
+
query: { type: 'string', description: 'The user prompt (domain) or spec text (tech).' },
|
|
101
|
+
team: {
|
|
102
|
+
type: 'array',
|
|
103
|
+
items: {
|
|
104
|
+
type: 'object',
|
|
105
|
+
properties: {
|
|
106
|
+
id: { type: 'string' },
|
|
107
|
+
agent_name: { type: 'string' },
|
|
108
|
+
name: { type: 'string' },
|
|
109
|
+
category: { type: 'string' },
|
|
110
|
+
},
|
|
111
|
+
required: ['id'],
|
|
112
|
+
},
|
|
113
|
+
description: 'List of skill objects (from recommend_agents / recommend_tech_agents).',
|
|
114
|
+
},
|
|
115
|
+
chat_id: { type: 'string', description: 'Optional. Required for follow-ups and tech mode.' },
|
|
116
|
+
prior_turns: { type: 'array', description: 'Optional. Prior turns from this chat for multi-turn continuity.' },
|
|
117
|
+
kind: { type: 'string', enum: ['domain', 'tech'], default: 'domain' },
|
|
118
|
+
},
|
|
119
|
+
required: ['query', 'team'],
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
name: 'generate_spec',
|
|
124
|
+
description:
|
|
125
|
+
'Have the Spec Engineer (Aisha) synthesize a one-page spec document from a ' +
|
|
126
|
+
'round-table chat. Includes Title, Problem, Goals, Non-goals, Requirements, ' +
|
|
127
|
+
'Proposed approach, Failure modes, Success criteria, Open questions, Rollout. ' +
|
|
128
|
+
'If the chat already has a tech round table on tech_turns, the spec absorbs ' +
|
|
129
|
+
'both transcripts (domain → Problem/Goals, tech → Approach/Risks). Authors ' +
|
|
130
|
+
'line credits every agent that contributed.',
|
|
131
|
+
inputSchema: {
|
|
132
|
+
type: 'object',
|
|
133
|
+
properties: {
|
|
134
|
+
chat_id: { type: 'string', description: 'The chat ID to synthesize.' },
|
|
135
|
+
},
|
|
136
|
+
required: ['chat_id'],
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
{
|
|
140
|
+
name: 'list_chats',
|
|
141
|
+
description:
|
|
142
|
+
'List the authenticated user\'s recent kaushalstack chats with their state — ' +
|
|
143
|
+
'query, team, turns, tech_team, tech_turns, tool_results (spec, mockup, ' +
|
|
144
|
+
'build, etc). Newest first. Use this to find a chat_id to continue or to ' +
|
|
145
|
+
'inspect what\'s been built.',
|
|
146
|
+
inputSchema: { type: 'object', properties: {} },
|
|
147
|
+
},
|
|
148
|
+
];
|
|
149
|
+
|
|
150
|
+
// ── Tool handlers ───────────────────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
const HANDLERS = {
|
|
153
|
+
async recommend_agents({ query, size, phase }) {
|
|
154
|
+
const body = { query };
|
|
155
|
+
if (size !== undefined) body.size = size;
|
|
156
|
+
if (phase !== undefined) body.phase = phase;
|
|
157
|
+
const data = await apiCall('POST', '/recommend', body);
|
|
158
|
+
return { skills: data.skills || [] };
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
async recommend_tech_agents({ query, size }) {
|
|
162
|
+
const body = { query };
|
|
163
|
+
if (size !== undefined) body.size = size;
|
|
164
|
+
const data = await apiCall('POST', '/recommend/tech', body);
|
|
165
|
+
return { skills: data.skills || [] };
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
async run_roundtable({ query, team, chat_id, prior_turns, kind }) {
|
|
169
|
+
const body = { query, team };
|
|
170
|
+
if (chat_id) body.chat_id = chat_id;
|
|
171
|
+
if (prior_turns) body.prior_turns = prior_turns;
|
|
172
|
+
if (kind) body.kind = kind;
|
|
173
|
+
const data = await apiCall('POST', '/roundtable', body);
|
|
174
|
+
return {
|
|
175
|
+
responses: data.responses || [],
|
|
176
|
+
chat_id: data.chatId || null,
|
|
177
|
+
is_follow_up: data.is_follow_up || false,
|
|
178
|
+
byok_fell_back: data.byok_fell_back || false,
|
|
179
|
+
turn_limit_reached: data.turn_limit_reached || false,
|
|
180
|
+
};
|
|
181
|
+
},
|
|
182
|
+
|
|
183
|
+
async generate_spec({ chat_id }) {
|
|
184
|
+
const data = await apiCall('POST', '/spec', { chat_id });
|
|
185
|
+
return {
|
|
186
|
+
spec_text: data.spec_text || '',
|
|
187
|
+
authors: data.authors || [],
|
|
188
|
+
generated_at: data.generated_at,
|
|
189
|
+
byok_fell_back: data.byok_fell_back || false,
|
|
190
|
+
};
|
|
191
|
+
},
|
|
192
|
+
|
|
193
|
+
async list_chats() {
|
|
194
|
+
const data = await apiCall('GET', '/roundtable/chats');
|
|
195
|
+
return { chats: data.chats || [] };
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// ── MCP wiring ──────────────────────────────────────────────────────────
|
|
200
|
+
|
|
201
|
+
const server = new Server(
|
|
202
|
+
{ name: 'kaushalstack', version: '0.1.0' },
|
|
203
|
+
{ capabilities: { tools: {} } },
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
207
|
+
|
|
208
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
209
|
+
const { name, arguments: args = {} } = req.params;
|
|
210
|
+
const handler = HANDLERS[name];
|
|
211
|
+
if (!handler) {
|
|
212
|
+
return {
|
|
213
|
+
isError: true,
|
|
214
|
+
content: [{ type: 'text', text: `Unknown tool: ${name}` }],
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
if (!API_TOKEN && name !== 'recommend_agents' && name !== 'recommend_tech_agents') {
|
|
218
|
+
// Recommend endpoints are unauthenticated; everything else needs a token.
|
|
219
|
+
return {
|
|
220
|
+
isError: true,
|
|
221
|
+
content: [{ type: 'text', text: 'KAUSHALSTACK_API_TOKEN not set — required for this tool. See README for how to get one.' }],
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
try {
|
|
225
|
+
const result = await handler(args);
|
|
226
|
+
return {
|
|
227
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
228
|
+
};
|
|
229
|
+
} catch (err) {
|
|
230
|
+
return {
|
|
231
|
+
isError: true,
|
|
232
|
+
content: [{ type: 'text', text: err.message || String(err) }],
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
const transport = new StdioServerTransport();
|
|
238
|
+
await server.connect(transport);
|
|
239
|
+
// Server is now alive on stdio; the host will issue requests until it
|
|
240
|
+
// disconnects. No explicit shutdown loop needed.
|