circuit-mcp 1.0.13 → 1.0.14
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/server.js +176 -1
package/package.json
CHANGED
package/src/server.js
CHANGED
|
@@ -204,6 +204,160 @@ async function handleMessage(message, token) {
|
|
|
204
204
|
}
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
+
/**
|
|
208
|
+
* Format priorities for display (matches Circuit UI exactly)
|
|
209
|
+
*/
|
|
210
|
+
function formatPriorities(data) {
|
|
211
|
+
if (data.message) {
|
|
212
|
+
return data.message;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (!data.priorities || data.priorities.length === 0) {
|
|
216
|
+
return 'No priorities found. Upload feedback to Circuit to get started.';
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const lines = [];
|
|
220
|
+
|
|
221
|
+
for (const p of data.priorities) {
|
|
222
|
+
// Format trend indicator like Circuit UI
|
|
223
|
+
let trendText = '';
|
|
224
|
+
if (p.trend === 'up') {
|
|
225
|
+
trendText = ' ↑';
|
|
226
|
+
} else if (p.trend === 'down') {
|
|
227
|
+
trendText = ' ↓';
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Format: #Rank. Title
|
|
231
|
+
lines.push(`**#${p.rank}. ${p.theme}**`);
|
|
232
|
+
|
|
233
|
+
// Badges row: Category | X users | Trend
|
|
234
|
+
const badges = [];
|
|
235
|
+
badges.push(p.category || 'Other');
|
|
236
|
+
badges.push(`${p.volume} users`);
|
|
237
|
+
if (trendText) badges.push(trendText.trim());
|
|
238
|
+
|
|
239
|
+
lines.push(badges.join(' · '));
|
|
240
|
+
|
|
241
|
+
// Key quote (customer voice)
|
|
242
|
+
if (p.key_quote) {
|
|
243
|
+
lines.push(`> "${p.key_quote}"`);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
lines.push(`priority_id: \`${p.id}\``);
|
|
247
|
+
lines.push('');
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
lines.push('---');
|
|
251
|
+
lines.push('Use `get_brief` with a priority_id to see the full engineering spec.');
|
|
252
|
+
|
|
253
|
+
return lines.join('\n');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* Format brief for display (matches Circuit UI format for Cursor/Claude)
|
|
258
|
+
*/
|
|
259
|
+
function formatBrief(data) {
|
|
260
|
+
if (data.error) {
|
|
261
|
+
if (data.error === 'no_brief' && data.priority) {
|
|
262
|
+
const p = data.priority;
|
|
263
|
+
let output = `# ${p.theme || 'Priority'}\n\n`;
|
|
264
|
+
output += `${p.category || 'Other'} · ${p.volume || 0} users\n\n`;
|
|
265
|
+
output += `**No brief generated yet**\n\n`;
|
|
266
|
+
|
|
267
|
+
if (p.summary) {
|
|
268
|
+
output += `${p.summary}\n\n`;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (p.key_quote) {
|
|
272
|
+
output += `> "${p.key_quote}"\n\n`;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
output += `---\n`;
|
|
276
|
+
output += data.suggestion;
|
|
277
|
+
return output;
|
|
278
|
+
}
|
|
279
|
+
return `Error: ${data.message || data.error}`;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const spec = data.spec_content || '';
|
|
283
|
+
|
|
284
|
+
// Build header like Circuit UI exports
|
|
285
|
+
// Format: # Title
|
|
286
|
+
// Priority: #X | Mentions: Y | Type: Z
|
|
287
|
+
// ---
|
|
288
|
+
// {spec content}
|
|
289
|
+
|
|
290
|
+
let output = '';
|
|
291
|
+
|
|
292
|
+
// Status line
|
|
293
|
+
const statusText = {
|
|
294
|
+
'ready': 'Ready',
|
|
295
|
+
'building': 'Building',
|
|
296
|
+
'done': 'Done'
|
|
297
|
+
}[data.status] || data.status;
|
|
298
|
+
|
|
299
|
+
output += `Status: ${statusText}\n\n`;
|
|
300
|
+
|
|
301
|
+
// The spec_content should already be formatted markdown
|
|
302
|
+
// Clean up any XML-style tags to proper headers
|
|
303
|
+
let cleanSpec = spec
|
|
304
|
+
.replace(/<what_to_build>/gi, '## WHAT TO BUILD\n')
|
|
305
|
+
.replace(/<\/what_to_build>/gi, '\n')
|
|
306
|
+
.replace(/<why_it_matters>/gi, '## WHY IT MATTERS\n')
|
|
307
|
+
.replace(/<\/why_it_matters>/gi, '\n')
|
|
308
|
+
.replace(/<customer_voice>/gi, '## CUSTOMER VOICE\n')
|
|
309
|
+
.replace(/<\/customer_voice>/gi, '\n')
|
|
310
|
+
.replace(/<files_to_touch>/gi, '## FILES TO TOUCH\n')
|
|
311
|
+
.replace(/<\/files_to_touch>/gi, '\n')
|
|
312
|
+
.replace(/<done_when>/gi, '## DONE WHEN\n')
|
|
313
|
+
.replace(/<\/done_when>/gi, '\n');
|
|
314
|
+
|
|
315
|
+
output += cleanSpec;
|
|
316
|
+
|
|
317
|
+
output += `\n---\n`;
|
|
318
|
+
output += `build_id: \`${data.build_id}\``;
|
|
319
|
+
|
|
320
|
+
return output;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* Format search results for display
|
|
325
|
+
*/
|
|
326
|
+
function formatSearchResults(data) {
|
|
327
|
+
if (data.message) {
|
|
328
|
+
return data.message;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
if (!data.results || data.results.length === 0) {
|
|
332
|
+
return 'No feedback found matching your search.';
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
const lines = [`**${data.total} results found**\n`];
|
|
336
|
+
|
|
337
|
+
for (const f of data.results) {
|
|
338
|
+
lines.push(`**${f.source}** · Urgency: ${f.urgency}/5`);
|
|
339
|
+
lines.push(`> "${f.text}"`);
|
|
340
|
+
lines.push('');
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return lines.join('\n');
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Format status change response
|
|
348
|
+
*/
|
|
349
|
+
function formatStatusChange(data) {
|
|
350
|
+
if (data.error) {
|
|
351
|
+
return `Error: ${data.error}`;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (data.success) {
|
|
355
|
+
return data.message;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
return JSON.stringify(data, null, 2);
|
|
359
|
+
}
|
|
360
|
+
|
|
207
361
|
/**
|
|
208
362
|
* Handle tool calls
|
|
209
363
|
*/
|
|
@@ -214,11 +368,32 @@ async function handleToolCall(id, params, token) {
|
|
|
214
368
|
// Call the Circuit MCP backend
|
|
215
369
|
const result = await callMcpApi(token, name, args || {});
|
|
216
370
|
|
|
371
|
+
// Format response based on tool type
|
|
372
|
+
let formattedText;
|
|
373
|
+
|
|
374
|
+
switch (name) {
|
|
375
|
+
case 'get_priorities':
|
|
376
|
+
formattedText = formatPriorities(result);
|
|
377
|
+
break;
|
|
378
|
+
case 'get_brief':
|
|
379
|
+
formattedText = formatBrief(result);
|
|
380
|
+
break;
|
|
381
|
+
case 'search_feedback':
|
|
382
|
+
formattedText = formatSearchResults(result);
|
|
383
|
+
break;
|
|
384
|
+
case 'start_building':
|
|
385
|
+
case 'mark_done':
|
|
386
|
+
formattedText = formatStatusChange(result);
|
|
387
|
+
break;
|
|
388
|
+
default:
|
|
389
|
+
formattedText = JSON.stringify(result, null, 2);
|
|
390
|
+
}
|
|
391
|
+
|
|
217
392
|
return {
|
|
218
393
|
jsonrpc: '2.0',
|
|
219
394
|
id,
|
|
220
395
|
result: {
|
|
221
|
-
content: [{ type: 'text', text:
|
|
396
|
+
content: [{ type: 'text', text: formattedText }]
|
|
222
397
|
}
|
|
223
398
|
};
|
|
224
399
|
|