@venturewild/workspace 0.3.7 → 0.4.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/LICENSE +21 -21
- package/README.md +112 -112
- package/package.json +83 -83
- package/server/bin/wild-workspace.mjs +1096 -995
- package/server/src/account.mjs +114 -114
- package/server/src/agent-login.mjs +146 -146
- package/server/src/agent-readiness.mjs +200 -200
- package/server/src/agent.mjs +468 -468
- package/server/src/bazaar/core.mjs +579 -579
- package/server/src/bazaar/index.mjs +75 -75
- package/server/src/bazaar/mcp-server.mjs +328 -328
- package/server/src/bazaar/mock-tickup.mjs +97 -97
- package/server/src/bazaar/preview-server.mjs +95 -95
- package/server/src/bazaar/seed-recipes/customer-feedback-form/know-how.md +23 -23
- package/server/src/bazaar/seed-recipes/customer-feedback-form/recipe.json +24 -24
- package/server/src/bazaar/seed-recipes/landing-page-launch/know-how.md +29 -29
- package/server/src/bazaar/seed-recipes/landing-page-launch/recipe.json +25 -25
- package/server/src/bazaar/seed-recipes/personal-portfolio/know-how.md +21 -21
- package/server/src/bazaar/seed-recipes/personal-portfolio/recipe.json +24 -24
- package/server/src/bazaar/seed-recipes/receipt-sorter/know-how.md +31 -31
- package/server/src/bazaar/seed-recipes/receipt-sorter/recipe.json +25 -25
- package/server/src/bazaar/seed-recipes/tickup-hr-matching/know-how.md +79 -79
- package/server/src/bazaar/seed-recipes/tickup-hr-matching/recipe.json +32 -32
- package/server/src/canvas/core.mjs +446 -421
- package/server/src/canvas/index.mjs +42 -42
- package/server/src/canvas/mcp-server.mjs +253 -253
- package/server/src/canvas-rails.mjs +108 -0
- package/server/src/config.mjs +404 -404
- package/server/src/daemon-bin.mjs +110 -110
- package/server/src/daemon-supervisor.mjs +285 -285
- package/server/src/doctor.mjs +375 -375
- package/server/src/inbox.mjs +86 -86
- package/server/src/index.mjs +2766 -2475
- package/server/src/logpaths.mjs +98 -98
- package/server/src/observability.mjs +45 -45
- package/server/src/operator.mjs +92 -92
- package/server/src/pairing.mjs +137 -137
- package/server/src/service.mjs +515 -515
- package/server/src/session-reporter.mjs +201 -201
- package/server/src/settings.mjs +145 -145
- package/server/src/share.mjs +182 -182
- package/server/src/skills.mjs +213 -213
- package/server/src/supervisor.mjs +647 -647
- package/server/src/support-consent.mjs +133 -133
- package/server/src/sync.mjs +248 -248
- package/server/src/transcript.mjs +121 -121
- package/server/src/turn-mcp.mjs +46 -46
- package/server/src/usage.mjs +405 -405
- package/server/src/workspace-registry.mjs +225 -0
- package/server/src/workspaces.mjs +111 -0
- package/web/dist/assets/index-NXZN2LU2.css +1 -0
- package/web/dist/assets/index-PAS8Inwp.js +91 -0
- package/web/dist/index.html +2 -2
- package/web/dist/assets/index-BxRx8EsD.js +0 -91
- package/web/dist/assets/index-DoOPBr3s.css +0 -1
|
@@ -1,328 +1,328 @@
|
|
|
1
|
-
// Bazaar MCP server — the tools the agent uses to "reach onto our shelf" (§3.7).
|
|
2
|
-
//
|
|
3
|
-
// Hand-rolled stdio JSON-RPC 2.0 (newline-delimited), so it needs NO new
|
|
4
|
-
// dependency and stays in the project's wrap-don't-embed style (like the
|
|
5
|
-
// stream-json parser in agent.mjs). `claude` spawns this per turn via --mcp-config
|
|
6
|
-
// and exposes the tools as mcp__bazaar__<name>.
|
|
7
|
-
//
|
|
8
|
-
// It imports the SAME core.mjs the main server uses, so there is one source of
|
|
9
|
-
// truth (state under ~/.wild-workspace/bazaar/) and no HTTP/port handshake. Tool
|
|
10
|
-
// results are JSON envelopes: the AGENT reads them (incl. the absorbed know-how),
|
|
11
|
-
// and the UI parses the same JSON to render rich bazaar cards.
|
|
12
|
-
//
|
|
13
|
-
// stdout carries ONLY JSON-RPC. Any diagnostics go to stderr.
|
|
14
|
-
|
|
15
|
-
import readline from 'node:readline';
|
|
16
|
-
import { createBazaar } from './core.mjs';
|
|
17
|
-
|
|
18
|
-
const bazaar = createBazaar(); // baseDir from WILD_WORKSPACE_GLOBAL_DIR (set by parent)
|
|
19
|
-
const PROTOCOL_VERSION = '2025-06-18';
|
|
20
|
-
|
|
21
|
-
function send(msg) {
|
|
22
|
-
process.stdout.write(`${JSON.stringify(msg)}\n`);
|
|
23
|
-
}
|
|
24
|
-
function result(id, res) {
|
|
25
|
-
send({ jsonrpc: '2.0', id, result: res });
|
|
26
|
-
}
|
|
27
|
-
function errorReply(id, code, message) {
|
|
28
|
-
send({ jsonrpc: '2.0', id, error: { code, message } });
|
|
29
|
-
}
|
|
30
|
-
function textContent(obj) {
|
|
31
|
-
return { content: [{ type: 'text', text: JSON.stringify(obj) }] };
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// --- tool definitions -----------------------------------------------------
|
|
35
|
-
|
|
36
|
-
const TOOLS = [
|
|
37
|
-
{
|
|
38
|
-
name: 'search_shelf',
|
|
39
|
-
description:
|
|
40
|
-
"Search the bazaar shelf for a proven recipe that matches what the user wants to build. " +
|
|
41
|
-
"Call this FIRST when the user describes an outcome that plausibly matches a known build. " +
|
|
42
|
-
"Returns ranked matches with an outcome score (how often the recipe actually gets people a " +
|
|
43
|
-
"working result). Only surface a recipe to the user if there is a STRONG, clearly relevant match.",
|
|
44
|
-
inputSchema: {
|
|
45
|
-
type: 'object',
|
|
46
|
-
properties: {
|
|
47
|
-
need: { type: 'string', description: "Plain-language description of what the user wants." },
|
|
48
|
-
},
|
|
49
|
-
required: ['need'],
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
name: 'open_recipe',
|
|
54
|
-
description:
|
|
55
|
-
"Open a recipe to absorb the producer's know-how (the step-by-step way to build it their way). " +
|
|
56
|
-
"Call this after the user accepts a recipe, then build it one-shot following the know-how.",
|
|
57
|
-
inputSchema: {
|
|
58
|
-
type: 'object',
|
|
59
|
-
properties: { id: { type: 'string', description: 'The recipe id from search_shelf.' } },
|
|
60
|
-
required: ['id'],
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
{
|
|
64
|
-
name: 'launch_preview',
|
|
65
|
-
description:
|
|
66
|
-
"Point the user's live preview at the folder you built, so it opens on their screen. " +
|
|
67
|
-
"Call this right after writing the build files.",
|
|
68
|
-
inputSchema: {
|
|
69
|
-
type: 'object',
|
|
70
|
-
properties: {
|
|
71
|
-
dir: { type: 'string', description: 'The build folder (relative to the workspace), e.g. "candidate-matcher".' },
|
|
72
|
-
recipeId: { type: 'string', description: 'The recipe id this build came from (optional).' },
|
|
73
|
-
},
|
|
74
|
-
required: ['dir'],
|
|
75
|
-
},
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
name: 'record_use',
|
|
79
|
-
description:
|
|
80
|
-
"Record that the user built on a producer's recipe — this credits the producer and records the " +
|
|
81
|
-
"transaction (the three-way win). Call this once the build is done.",
|
|
82
|
-
inputSchema: {
|
|
83
|
-
type: 'object',
|
|
84
|
-
properties: {
|
|
85
|
-
recipeId: { type: 'string', description: 'The recipe id that was used.' },
|
|
86
|
-
summary: { type: 'string', description: 'One short line describing what you built for the user.' },
|
|
87
|
-
},
|
|
88
|
-
required: ['recipeId'],
|
|
89
|
-
},
|
|
90
|
-
},
|
|
91
|
-
{
|
|
92
|
-
name: 'publish_listing',
|
|
93
|
-
description:
|
|
94
|
-
"Package something the user built into a listing on the shelf, so other people's agents can " +
|
|
95
|
-
"build on it and the user earns when they do. Use when the user accepts your offer to package " +
|
|
96
|
-
"it, OR when the user explicitly asks to list/sell what they made.",
|
|
97
|
-
inputSchema: {
|
|
98
|
-
type: 'object',
|
|
99
|
-
properties: {
|
|
100
|
-
title: { type: 'string', description: 'A short title for the listing.' },
|
|
101
|
-
pitch: { type: 'string', description: 'One line that sells what it does.' },
|
|
102
|
-
summary: { type: 'string', description: 'A plain summary of what someone gets.' },
|
|
103
|
-
tags: { type: 'array', items: { type: 'string' }, description: 'Keywords others might search for.' },
|
|
104
|
-
knowHow: { type: 'string', description: "The how-to another agent would absorb to rebuild it." },
|
|
105
|
-
buildDir: { type: 'string', description: 'The folder the build lives in (optional).' },
|
|
106
|
-
},
|
|
107
|
-
required: ['title'],
|
|
108
|
-
},
|
|
109
|
-
},
|
|
110
|
-
{
|
|
111
|
-
name: 'draft_recipe',
|
|
112
|
-
description:
|
|
113
|
-
"Stage a recipe you EXTRACTED from the user's existing/past work for their REVIEW (does NOT " +
|
|
114
|
-
"publish). Use when self-seeding the shelf from past projects. The know-how MUST be " +
|
|
115
|
-
"GENERALIZED — the reusable method/architecture only, with NO client names, data, domains, " +
|
|
116
|
-
"keys, or secrets. Stage one draft per reusable pattern; the user reviews each and tells you " +
|
|
117
|
-
"which to publish.",
|
|
118
|
-
inputSchema: {
|
|
119
|
-
type: 'object',
|
|
120
|
-
properties: {
|
|
121
|
-
title: { type: 'string', description: 'A short title for the recipe.' },
|
|
122
|
-
pitch: { type: 'string', description: 'One line: what it lets someone build.' },
|
|
123
|
-
summary: { type: 'string', description: 'A plain summary of what someone gets.' },
|
|
124
|
-
tags: { type: 'array', items: { type: 'string' }, description: 'Keywords others might search for.' },
|
|
125
|
-
knowHow: { type: 'string', description: "The GENERALIZED how-to another agent would follow — no client specifics." },
|
|
126
|
-
sourceNote: { type: 'string', description: 'For the user to verify: what this was generalized from (e.g. "from 3 client landing pages") — no client names.' },
|
|
127
|
-
},
|
|
128
|
-
required: ['title', 'knowHow'],
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
name: 'publish_draft',
|
|
133
|
-
description:
|
|
134
|
-
"Publish a reviewed draft onto the shelf. Call ONLY after the user has explicitly approved that " +
|
|
135
|
-
"specific draft (by its id).",
|
|
136
|
-
inputSchema: {
|
|
137
|
-
type: 'object',
|
|
138
|
-
properties: { id: { type: 'string', description: 'The draft id from draft_recipe.' } },
|
|
139
|
-
required: ['id'],
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
name: 'publish_theme',
|
|
144
|
-
description:
|
|
145
|
-
"Publish a workspace THEME to the bazaar so others can discover and apply it (the user earns each " +
|
|
146
|
-
"time someone does). Use this when the user asks to share/list/sell a look they like, or after you " +
|
|
147
|
-
"made one they love. A theme is just colours (hex values) — pick a mode (light|dark), an accent, " +
|
|
148
|
-
"and the token colours that define the look. Give it a short title and a one-line pitch.",
|
|
149
|
-
inputSchema: {
|
|
150
|
-
type: 'object',
|
|
151
|
-
properties: {
|
|
152
|
-
title: { type: 'string', description: 'Short theme name, e.g. "Warm Sunset".' },
|
|
153
|
-
pitch: { type: 'string', description: 'One-line description of the vibe.' },
|
|
154
|
-
summary: { type: 'string', description: 'A slightly longer description (optional).' },
|
|
155
|
-
tags: { type: 'array', items: { type: 'string' }, description: 'e.g. ["dark","warm"].' },
|
|
156
|
-
mode: { type: 'string', enum: ['light', 'dark'], description: 'Base palette.' },
|
|
157
|
-
accent: { type: 'string', description: 'Primary accent, hex e.g. "#22d3ee".' },
|
|
158
|
-
bg: { type: 'string', description: 'page background, hex.' },
|
|
159
|
-
surface: { type: 'string', description: 'card face, hex.' },
|
|
160
|
-
text: { type: 'string', description: 'primary text, hex.' },
|
|
161
|
-
textMuted: { type: 'string', description: 'muted text, hex.' },
|
|
162
|
-
border: { type: 'string', description: 'borders, hex.' },
|
|
163
|
-
canvas1: { type: 'string', description: 'wallpaper stop 1, hex.' },
|
|
164
|
-
canvas2: { type: 'string', description: 'wallpaper stop 2, hex.' },
|
|
165
|
-
canvas3: { type: 'string', description: 'wallpaper stop 3, hex.' },
|
|
166
|
-
},
|
|
167
|
-
required: ['title'],
|
|
168
|
-
},
|
|
169
|
-
},
|
|
170
|
-
];
|
|
171
|
-
|
|
172
|
-
// --- tool dispatch --------------------------------------------------------
|
|
173
|
-
|
|
174
|
-
function callTool(name, args = {}) {
|
|
175
|
-
switch (name) {
|
|
176
|
-
case 'search_shelf': {
|
|
177
|
-
const hits = bazaar.search(args.need || '');
|
|
178
|
-
return textContent({
|
|
179
|
-
kind: 'search',
|
|
180
|
-
need: args.need || '',
|
|
181
|
-
count: hits.length,
|
|
182
|
-
hits,
|
|
183
|
-
top: hits[0] || null,
|
|
184
|
-
guidance:
|
|
185
|
-
hits.length === 0
|
|
186
|
-
? 'Nothing on the shelf is a strong match — build from scratch and do not mention the bazaar.'
|
|
187
|
-
: 'Surface the top match to the user only if it is clearly relevant (a strong, high-outcome match).',
|
|
188
|
-
});
|
|
189
|
-
}
|
|
190
|
-
case 'open_recipe': {
|
|
191
|
-
const r = bazaar.getRecipe(args.id);
|
|
192
|
-
if (!r) return { ...textContent({ kind: 'error', error: `no recipe "${args.id}"` }), isError: true };
|
|
193
|
-
return textContent({
|
|
194
|
-
kind: 'recipe',
|
|
195
|
-
card: bazaar.card(r),
|
|
196
|
-
knowHow: r.knowHow || '',
|
|
197
|
-
service: r.service || null,
|
|
198
|
-
note: 'Absorb this know-how and build it one-shot for the user.',
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
case 'launch_preview': {
|
|
202
|
-
const preview = bazaar.setPreview({ dir: args.dir, recipeId: args.recipeId || null });
|
|
203
|
-
return textContent({ kind: 'preview', url: '/preview/', dir: preview.dir, recipeId: preview.recipeId });
|
|
204
|
-
}
|
|
205
|
-
case 'record_use': {
|
|
206
|
-
const res = bazaar.recordUse({ recipeId: args.recipeId, summary: args.summary });
|
|
207
|
-
if (!res.ok) return { ...textContent({ kind: 'error', error: res.error }), isError: true };
|
|
208
|
-
// Backstop the preview: even if the agent forgot launch_preview, target the
|
|
209
|
-
// recipe's build dir so the preview still opens (review must-fix #3).
|
|
210
|
-
let preview = bazaar.getPreview();
|
|
211
|
-
if ((!preview || !preview.dir) && res.recipe?.buildDir) {
|
|
212
|
-
preview = bazaar.setPreview({ dir: res.recipe.buildDir, recipeId: res.recipe.id });
|
|
213
|
-
}
|
|
214
|
-
return textContent({
|
|
215
|
-
kind: 'three-way',
|
|
216
|
-
recipe: res.recipe,
|
|
217
|
-
threeWay: res.threeWay,
|
|
218
|
-
credit: res.credit,
|
|
219
|
-
service: res.service,
|
|
220
|
-
preview: preview?.dir ? { url: '/preview/', dir: preview.dir } : null,
|
|
221
|
-
simulated: true,
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
case 'publish_listing': {
|
|
225
|
-
const res = bazaar.publishListing({
|
|
226
|
-
title: args.title,
|
|
227
|
-
pitch: args.pitch,
|
|
228
|
-
summary: args.summary,
|
|
229
|
-
tags: args.tags || [],
|
|
230
|
-
knowHow: args.knowHow || '',
|
|
231
|
-
buildDir: args.buildDir || null,
|
|
232
|
-
});
|
|
233
|
-
return textContent({ kind: 'publish', listing: res.listing, earning: res.earning, simulated: true });
|
|
234
|
-
}
|
|
235
|
-
case 'draft_recipe': {
|
|
236
|
-
const draft = bazaar.stageDraft({
|
|
237
|
-
title: args.title,
|
|
238
|
-
pitch: args.pitch,
|
|
239
|
-
summary: args.summary,
|
|
240
|
-
tags: args.tags || [],
|
|
241
|
-
knowHow: args.knowHow || '',
|
|
242
|
-
sourceNote: args.sourceNote || '',
|
|
243
|
-
});
|
|
244
|
-
return textContent({ kind: 'draft', draft });
|
|
245
|
-
}
|
|
246
|
-
case 'publish_draft': {
|
|
247
|
-
const res = bazaar.publishDraft(args.id);
|
|
248
|
-
if (!res.ok) return { ...textContent({ kind: 'error', error: res.error }), isError: true };
|
|
249
|
-
return textContent({ kind: 'publish', listing: res.listing, earning: res.earning, simulated: true });
|
|
250
|
-
}
|
|
251
|
-
case 'publish_theme': {
|
|
252
|
-
const res = bazaar.publishTheme({
|
|
253
|
-
title: args.title,
|
|
254
|
-
pitch: args.pitch,
|
|
255
|
-
summary: args.summary,
|
|
256
|
-
tags: args.tags || [],
|
|
257
|
-
theme: {
|
|
258
|
-
mode: args.mode,
|
|
259
|
-
accent: args.accent,
|
|
260
|
-
bg: args.bg, surface: args.surface, text: args.text, textMuted: args.textMuted,
|
|
261
|
-
border: args.border, canvas1: args.canvas1, canvas2: args.canvas2, canvas3: args.canvas3,
|
|
262
|
-
},
|
|
263
|
-
});
|
|
264
|
-
return textContent({ kind: 'publish', listing: res.listing, earning: res.earning, simulated: true });
|
|
265
|
-
}
|
|
266
|
-
default:
|
|
267
|
-
return { ...textContent({ kind: 'error', error: `unknown tool ${name}` }), isError: true };
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// --- JSON-RPC loop --------------------------------------------------------
|
|
272
|
-
|
|
273
|
-
export function handleMessage(msg) {
|
|
274
|
-
if (!msg || msg.jsonrpc !== '2.0') return;
|
|
275
|
-
const { id, method, params } = msg;
|
|
276
|
-
const isNotification = id === undefined || id === null;
|
|
277
|
-
|
|
278
|
-
switch (method) {
|
|
279
|
-
case 'initialize':
|
|
280
|
-
return result(id, {
|
|
281
|
-
protocolVersion: params?.protocolVersion || PROTOCOL_VERSION,
|
|
282
|
-
capabilities: { tools: {} },
|
|
283
|
-
serverInfo: { name: 'bazaar', version: '1.0.0' },
|
|
284
|
-
});
|
|
285
|
-
case 'notifications/initialized':
|
|
286
|
-
case 'initialized':
|
|
287
|
-
return; // notification, no response
|
|
288
|
-
case 'ping':
|
|
289
|
-
return result(id, {});
|
|
290
|
-
case 'tools/list':
|
|
291
|
-
return result(id, { tools: TOOLS });
|
|
292
|
-
case 'tools/call': {
|
|
293
|
-
try {
|
|
294
|
-
const out = callTool(params?.name, params?.arguments || {});
|
|
295
|
-
return result(id, out);
|
|
296
|
-
} catch (e) {
|
|
297
|
-
return errorReply(id, -32603, `tool error: ${e?.message || e}`);
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
default:
|
|
301
|
-
if (!isNotification) errorReply(id, -32601, `method not found: ${method}`);
|
|
302
|
-
return;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
// Only run the stdio loop when executed as the MCP process (not when imported by a test).
|
|
307
|
-
const isDirectRun = process.argv[1] && process.argv[1].endsWith('mcp-server.mjs');
|
|
308
|
-
if (isDirectRun) {
|
|
309
|
-
const rl = readline.createInterface({ input: process.stdin });
|
|
310
|
-
rl.on('line', (line) => {
|
|
311
|
-
const trimmed = line.trim();
|
|
312
|
-
if (!trimmed) return;
|
|
313
|
-
let msg;
|
|
314
|
-
try {
|
|
315
|
-
msg = JSON.parse(trimmed);
|
|
316
|
-
} catch {
|
|
317
|
-
return; // ignore non-JSON noise
|
|
318
|
-
}
|
|
319
|
-
try {
|
|
320
|
-
handleMessage(msg);
|
|
321
|
-
} catch (e) {
|
|
322
|
-
process.stderr.write(`bazaar mcp error: ${e?.message || e}\n`);
|
|
323
|
-
}
|
|
324
|
-
});
|
|
325
|
-
process.stderr.write('bazaar mcp server ready\n');
|
|
326
|
-
}
|
|
327
|
-
|
|
328
|
-
export { TOOLS, callTool };
|
|
1
|
+
// Bazaar MCP server — the tools the agent uses to "reach onto our shelf" (§3.7).
|
|
2
|
+
//
|
|
3
|
+
// Hand-rolled stdio JSON-RPC 2.0 (newline-delimited), so it needs NO new
|
|
4
|
+
// dependency and stays in the project's wrap-don't-embed style (like the
|
|
5
|
+
// stream-json parser in agent.mjs). `claude` spawns this per turn via --mcp-config
|
|
6
|
+
// and exposes the tools as mcp__bazaar__<name>.
|
|
7
|
+
//
|
|
8
|
+
// It imports the SAME core.mjs the main server uses, so there is one source of
|
|
9
|
+
// truth (state under ~/.wild-workspace/bazaar/) and no HTTP/port handshake. Tool
|
|
10
|
+
// results are JSON envelopes: the AGENT reads them (incl. the absorbed know-how),
|
|
11
|
+
// and the UI parses the same JSON to render rich bazaar cards.
|
|
12
|
+
//
|
|
13
|
+
// stdout carries ONLY JSON-RPC. Any diagnostics go to stderr.
|
|
14
|
+
|
|
15
|
+
import readline from 'node:readline';
|
|
16
|
+
import { createBazaar } from './core.mjs';
|
|
17
|
+
|
|
18
|
+
const bazaar = createBazaar(); // baseDir from WILD_WORKSPACE_GLOBAL_DIR (set by parent)
|
|
19
|
+
const PROTOCOL_VERSION = '2025-06-18';
|
|
20
|
+
|
|
21
|
+
function send(msg) {
|
|
22
|
+
process.stdout.write(`${JSON.stringify(msg)}\n`);
|
|
23
|
+
}
|
|
24
|
+
function result(id, res) {
|
|
25
|
+
send({ jsonrpc: '2.0', id, result: res });
|
|
26
|
+
}
|
|
27
|
+
function errorReply(id, code, message) {
|
|
28
|
+
send({ jsonrpc: '2.0', id, error: { code, message } });
|
|
29
|
+
}
|
|
30
|
+
function textContent(obj) {
|
|
31
|
+
return { content: [{ type: 'text', text: JSON.stringify(obj) }] };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// --- tool definitions -----------------------------------------------------
|
|
35
|
+
|
|
36
|
+
const TOOLS = [
|
|
37
|
+
{
|
|
38
|
+
name: 'search_shelf',
|
|
39
|
+
description:
|
|
40
|
+
"Search the bazaar shelf for a proven recipe that matches what the user wants to build. " +
|
|
41
|
+
"Call this FIRST when the user describes an outcome that plausibly matches a known build. " +
|
|
42
|
+
"Returns ranked matches with an outcome score (how often the recipe actually gets people a " +
|
|
43
|
+
"working result). Only surface a recipe to the user if there is a STRONG, clearly relevant match.",
|
|
44
|
+
inputSchema: {
|
|
45
|
+
type: 'object',
|
|
46
|
+
properties: {
|
|
47
|
+
need: { type: 'string', description: "Plain-language description of what the user wants." },
|
|
48
|
+
},
|
|
49
|
+
required: ['need'],
|
|
50
|
+
},
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
name: 'open_recipe',
|
|
54
|
+
description:
|
|
55
|
+
"Open a recipe to absorb the producer's know-how (the step-by-step way to build it their way). " +
|
|
56
|
+
"Call this after the user accepts a recipe, then build it one-shot following the know-how.",
|
|
57
|
+
inputSchema: {
|
|
58
|
+
type: 'object',
|
|
59
|
+
properties: { id: { type: 'string', description: 'The recipe id from search_shelf.' } },
|
|
60
|
+
required: ['id'],
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
name: 'launch_preview',
|
|
65
|
+
description:
|
|
66
|
+
"Point the user's live preview at the folder you built, so it opens on their screen. " +
|
|
67
|
+
"Call this right after writing the build files.",
|
|
68
|
+
inputSchema: {
|
|
69
|
+
type: 'object',
|
|
70
|
+
properties: {
|
|
71
|
+
dir: { type: 'string', description: 'The build folder (relative to the workspace), e.g. "candidate-matcher".' },
|
|
72
|
+
recipeId: { type: 'string', description: 'The recipe id this build came from (optional).' },
|
|
73
|
+
},
|
|
74
|
+
required: ['dir'],
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
name: 'record_use',
|
|
79
|
+
description:
|
|
80
|
+
"Record that the user built on a producer's recipe — this credits the producer and records the " +
|
|
81
|
+
"transaction (the three-way win). Call this once the build is done.",
|
|
82
|
+
inputSchema: {
|
|
83
|
+
type: 'object',
|
|
84
|
+
properties: {
|
|
85
|
+
recipeId: { type: 'string', description: 'The recipe id that was used.' },
|
|
86
|
+
summary: { type: 'string', description: 'One short line describing what you built for the user.' },
|
|
87
|
+
},
|
|
88
|
+
required: ['recipeId'],
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'publish_listing',
|
|
93
|
+
description:
|
|
94
|
+
"Package something the user built into a listing on the shelf, so other people's agents can " +
|
|
95
|
+
"build on it and the user earns when they do. Use when the user accepts your offer to package " +
|
|
96
|
+
"it, OR when the user explicitly asks to list/sell what they made.",
|
|
97
|
+
inputSchema: {
|
|
98
|
+
type: 'object',
|
|
99
|
+
properties: {
|
|
100
|
+
title: { type: 'string', description: 'A short title for the listing.' },
|
|
101
|
+
pitch: { type: 'string', description: 'One line that sells what it does.' },
|
|
102
|
+
summary: { type: 'string', description: 'A plain summary of what someone gets.' },
|
|
103
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Keywords others might search for.' },
|
|
104
|
+
knowHow: { type: 'string', description: "The how-to another agent would absorb to rebuild it." },
|
|
105
|
+
buildDir: { type: 'string', description: 'The folder the build lives in (optional).' },
|
|
106
|
+
},
|
|
107
|
+
required: ['title'],
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'draft_recipe',
|
|
112
|
+
description:
|
|
113
|
+
"Stage a recipe you EXTRACTED from the user's existing/past work for their REVIEW (does NOT " +
|
|
114
|
+
"publish). Use when self-seeding the shelf from past projects. The know-how MUST be " +
|
|
115
|
+
"GENERALIZED — the reusable method/architecture only, with NO client names, data, domains, " +
|
|
116
|
+
"keys, or secrets. Stage one draft per reusable pattern; the user reviews each and tells you " +
|
|
117
|
+
"which to publish.",
|
|
118
|
+
inputSchema: {
|
|
119
|
+
type: 'object',
|
|
120
|
+
properties: {
|
|
121
|
+
title: { type: 'string', description: 'A short title for the recipe.' },
|
|
122
|
+
pitch: { type: 'string', description: 'One line: what it lets someone build.' },
|
|
123
|
+
summary: { type: 'string', description: 'A plain summary of what someone gets.' },
|
|
124
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'Keywords others might search for.' },
|
|
125
|
+
knowHow: { type: 'string', description: "The GENERALIZED how-to another agent would follow — no client specifics." },
|
|
126
|
+
sourceNote: { type: 'string', description: 'For the user to verify: what this was generalized from (e.g. "from 3 client landing pages") — no client names.' },
|
|
127
|
+
},
|
|
128
|
+
required: ['title', 'knowHow'],
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
name: 'publish_draft',
|
|
133
|
+
description:
|
|
134
|
+
"Publish a reviewed draft onto the shelf. Call ONLY after the user has explicitly approved that " +
|
|
135
|
+
"specific draft (by its id).",
|
|
136
|
+
inputSchema: {
|
|
137
|
+
type: 'object',
|
|
138
|
+
properties: { id: { type: 'string', description: 'The draft id from draft_recipe.' } },
|
|
139
|
+
required: ['id'],
|
|
140
|
+
},
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
name: 'publish_theme',
|
|
144
|
+
description:
|
|
145
|
+
"Publish a workspace THEME to the bazaar so others can discover and apply it (the user earns each " +
|
|
146
|
+
"time someone does). Use this when the user asks to share/list/sell a look they like, or after you " +
|
|
147
|
+
"made one they love. A theme is just colours (hex values) — pick a mode (light|dark), an accent, " +
|
|
148
|
+
"and the token colours that define the look. Give it a short title and a one-line pitch.",
|
|
149
|
+
inputSchema: {
|
|
150
|
+
type: 'object',
|
|
151
|
+
properties: {
|
|
152
|
+
title: { type: 'string', description: 'Short theme name, e.g. "Warm Sunset".' },
|
|
153
|
+
pitch: { type: 'string', description: 'One-line description of the vibe.' },
|
|
154
|
+
summary: { type: 'string', description: 'A slightly longer description (optional).' },
|
|
155
|
+
tags: { type: 'array', items: { type: 'string' }, description: 'e.g. ["dark","warm"].' },
|
|
156
|
+
mode: { type: 'string', enum: ['light', 'dark'], description: 'Base palette.' },
|
|
157
|
+
accent: { type: 'string', description: 'Primary accent, hex e.g. "#22d3ee".' },
|
|
158
|
+
bg: { type: 'string', description: 'page background, hex.' },
|
|
159
|
+
surface: { type: 'string', description: 'card face, hex.' },
|
|
160
|
+
text: { type: 'string', description: 'primary text, hex.' },
|
|
161
|
+
textMuted: { type: 'string', description: 'muted text, hex.' },
|
|
162
|
+
border: { type: 'string', description: 'borders, hex.' },
|
|
163
|
+
canvas1: { type: 'string', description: 'wallpaper stop 1, hex.' },
|
|
164
|
+
canvas2: { type: 'string', description: 'wallpaper stop 2, hex.' },
|
|
165
|
+
canvas3: { type: 'string', description: 'wallpaper stop 3, hex.' },
|
|
166
|
+
},
|
|
167
|
+
required: ['title'],
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
];
|
|
171
|
+
|
|
172
|
+
// --- tool dispatch --------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
function callTool(name, args = {}) {
|
|
175
|
+
switch (name) {
|
|
176
|
+
case 'search_shelf': {
|
|
177
|
+
const hits = bazaar.search(args.need || '');
|
|
178
|
+
return textContent({
|
|
179
|
+
kind: 'search',
|
|
180
|
+
need: args.need || '',
|
|
181
|
+
count: hits.length,
|
|
182
|
+
hits,
|
|
183
|
+
top: hits[0] || null,
|
|
184
|
+
guidance:
|
|
185
|
+
hits.length === 0
|
|
186
|
+
? 'Nothing on the shelf is a strong match — build from scratch and do not mention the bazaar.'
|
|
187
|
+
: 'Surface the top match to the user only if it is clearly relevant (a strong, high-outcome match).',
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
case 'open_recipe': {
|
|
191
|
+
const r = bazaar.getRecipe(args.id);
|
|
192
|
+
if (!r) return { ...textContent({ kind: 'error', error: `no recipe "${args.id}"` }), isError: true };
|
|
193
|
+
return textContent({
|
|
194
|
+
kind: 'recipe',
|
|
195
|
+
card: bazaar.card(r),
|
|
196
|
+
knowHow: r.knowHow || '',
|
|
197
|
+
service: r.service || null,
|
|
198
|
+
note: 'Absorb this know-how and build it one-shot for the user.',
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
case 'launch_preview': {
|
|
202
|
+
const preview = bazaar.setPreview({ dir: args.dir, recipeId: args.recipeId || null });
|
|
203
|
+
return textContent({ kind: 'preview', url: '/preview/', dir: preview.dir, recipeId: preview.recipeId });
|
|
204
|
+
}
|
|
205
|
+
case 'record_use': {
|
|
206
|
+
const res = bazaar.recordUse({ recipeId: args.recipeId, summary: args.summary });
|
|
207
|
+
if (!res.ok) return { ...textContent({ kind: 'error', error: res.error }), isError: true };
|
|
208
|
+
// Backstop the preview: even if the agent forgot launch_preview, target the
|
|
209
|
+
// recipe's build dir so the preview still opens (review must-fix #3).
|
|
210
|
+
let preview = bazaar.getPreview();
|
|
211
|
+
if ((!preview || !preview.dir) && res.recipe?.buildDir) {
|
|
212
|
+
preview = bazaar.setPreview({ dir: res.recipe.buildDir, recipeId: res.recipe.id });
|
|
213
|
+
}
|
|
214
|
+
return textContent({
|
|
215
|
+
kind: 'three-way',
|
|
216
|
+
recipe: res.recipe,
|
|
217
|
+
threeWay: res.threeWay,
|
|
218
|
+
credit: res.credit,
|
|
219
|
+
service: res.service,
|
|
220
|
+
preview: preview?.dir ? { url: '/preview/', dir: preview.dir } : null,
|
|
221
|
+
simulated: true,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
case 'publish_listing': {
|
|
225
|
+
const res = bazaar.publishListing({
|
|
226
|
+
title: args.title,
|
|
227
|
+
pitch: args.pitch,
|
|
228
|
+
summary: args.summary,
|
|
229
|
+
tags: args.tags || [],
|
|
230
|
+
knowHow: args.knowHow || '',
|
|
231
|
+
buildDir: args.buildDir || null,
|
|
232
|
+
});
|
|
233
|
+
return textContent({ kind: 'publish', listing: res.listing, earning: res.earning, simulated: true });
|
|
234
|
+
}
|
|
235
|
+
case 'draft_recipe': {
|
|
236
|
+
const draft = bazaar.stageDraft({
|
|
237
|
+
title: args.title,
|
|
238
|
+
pitch: args.pitch,
|
|
239
|
+
summary: args.summary,
|
|
240
|
+
tags: args.tags || [],
|
|
241
|
+
knowHow: args.knowHow || '',
|
|
242
|
+
sourceNote: args.sourceNote || '',
|
|
243
|
+
});
|
|
244
|
+
return textContent({ kind: 'draft', draft });
|
|
245
|
+
}
|
|
246
|
+
case 'publish_draft': {
|
|
247
|
+
const res = bazaar.publishDraft(args.id);
|
|
248
|
+
if (!res.ok) return { ...textContent({ kind: 'error', error: res.error }), isError: true };
|
|
249
|
+
return textContent({ kind: 'publish', listing: res.listing, earning: res.earning, simulated: true });
|
|
250
|
+
}
|
|
251
|
+
case 'publish_theme': {
|
|
252
|
+
const res = bazaar.publishTheme({
|
|
253
|
+
title: args.title,
|
|
254
|
+
pitch: args.pitch,
|
|
255
|
+
summary: args.summary,
|
|
256
|
+
tags: args.tags || [],
|
|
257
|
+
theme: {
|
|
258
|
+
mode: args.mode,
|
|
259
|
+
accent: args.accent,
|
|
260
|
+
bg: args.bg, surface: args.surface, text: args.text, textMuted: args.textMuted,
|
|
261
|
+
border: args.border, canvas1: args.canvas1, canvas2: args.canvas2, canvas3: args.canvas3,
|
|
262
|
+
},
|
|
263
|
+
});
|
|
264
|
+
return textContent({ kind: 'publish', listing: res.listing, earning: res.earning, simulated: true });
|
|
265
|
+
}
|
|
266
|
+
default:
|
|
267
|
+
return { ...textContent({ kind: 'error', error: `unknown tool ${name}` }), isError: true };
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// --- JSON-RPC loop --------------------------------------------------------
|
|
272
|
+
|
|
273
|
+
export function handleMessage(msg) {
|
|
274
|
+
if (!msg || msg.jsonrpc !== '2.0') return;
|
|
275
|
+
const { id, method, params } = msg;
|
|
276
|
+
const isNotification = id === undefined || id === null;
|
|
277
|
+
|
|
278
|
+
switch (method) {
|
|
279
|
+
case 'initialize':
|
|
280
|
+
return result(id, {
|
|
281
|
+
protocolVersion: params?.protocolVersion || PROTOCOL_VERSION,
|
|
282
|
+
capabilities: { tools: {} },
|
|
283
|
+
serverInfo: { name: 'bazaar', version: '1.0.0' },
|
|
284
|
+
});
|
|
285
|
+
case 'notifications/initialized':
|
|
286
|
+
case 'initialized':
|
|
287
|
+
return; // notification, no response
|
|
288
|
+
case 'ping':
|
|
289
|
+
return result(id, {});
|
|
290
|
+
case 'tools/list':
|
|
291
|
+
return result(id, { tools: TOOLS });
|
|
292
|
+
case 'tools/call': {
|
|
293
|
+
try {
|
|
294
|
+
const out = callTool(params?.name, params?.arguments || {});
|
|
295
|
+
return result(id, out);
|
|
296
|
+
} catch (e) {
|
|
297
|
+
return errorReply(id, -32603, `tool error: ${e?.message || e}`);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
default:
|
|
301
|
+
if (!isNotification) errorReply(id, -32601, `method not found: ${method}`);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Only run the stdio loop when executed as the MCP process (not when imported by a test).
|
|
307
|
+
const isDirectRun = process.argv[1] && process.argv[1].endsWith('mcp-server.mjs');
|
|
308
|
+
if (isDirectRun) {
|
|
309
|
+
const rl = readline.createInterface({ input: process.stdin });
|
|
310
|
+
rl.on('line', (line) => {
|
|
311
|
+
const trimmed = line.trim();
|
|
312
|
+
if (!trimmed) return;
|
|
313
|
+
let msg;
|
|
314
|
+
try {
|
|
315
|
+
msg = JSON.parse(trimmed);
|
|
316
|
+
} catch {
|
|
317
|
+
return; // ignore non-JSON noise
|
|
318
|
+
}
|
|
319
|
+
try {
|
|
320
|
+
handleMessage(msg);
|
|
321
|
+
} catch (e) {
|
|
322
|
+
process.stderr.write(`bazaar mcp error: ${e?.message || e}\n`);
|
|
323
|
+
}
|
|
324
|
+
});
|
|
325
|
+
process.stderr.write('bazaar mcp server ready\n');
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
export { TOOLS, callTool };
|