circuit-mcp 1.0.13 → 1.0.15
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 +47 -32
- package/package.json +20 -5
- package/src/server.js +265 -1
package/README.md
CHANGED
|
@@ -2,11 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
Connect [Circuit](https://withcircuit.com) to Cursor and Claude Code via MCP (Model Context Protocol).
|
|
4
4
|
|
|
5
|
+
**Circuit** transforms customer feedback into engineering specs. The MCP brings your priorities and briefs directly into your AI coding assistant.
|
|
6
|
+
|
|
5
7
|
## Quick Start
|
|
6
8
|
|
|
7
9
|
### Cursor
|
|
8
10
|
|
|
9
|
-
Add to
|
|
11
|
+
Add to `~/.cursor/mcp.json`:
|
|
10
12
|
|
|
11
13
|
```json
|
|
12
14
|
{
|
|
@@ -21,62 +23,75 @@ Add to your Cursor settings (`Cmd+Shift+P` → "Cursor Settings: Open User Setti
|
|
|
21
23
|
|
|
22
24
|
### Claude Code
|
|
23
25
|
|
|
24
|
-
Run this command:
|
|
25
|
-
|
|
26
26
|
```bash
|
|
27
27
|
claude mcp add circuit -- npx circuit-mcp
|
|
28
28
|
```
|
|
29
29
|
|
|
30
30
|
## First Run
|
|
31
31
|
|
|
32
|
-
On first use, Circuit
|
|
32
|
+
On first use, Circuit opens your browser to authenticate. Your token is cached at `~/.circuit/token.json`.
|
|
33
33
|
|
|
34
34
|
```
|
|
35
|
-
|
|
36
|
-
│ ⚡ Circuit MCP │
|
|
37
|
-
╰──────────────────────────────────╯
|
|
35
|
+
Circuit MCP
|
|
38
36
|
|
|
39
37
|
First time setup - let's connect your account.
|
|
40
|
-
|
|
41
38
|
Opening browser to authenticate...
|
|
42
39
|
|
|
43
|
-
|
|
40
|
+
Connected!
|
|
44
41
|
```
|
|
45
42
|
|
|
46
|
-
##
|
|
43
|
+
## Available Tools
|
|
47
44
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
45
|
+
| Tool | Description |
|
|
46
|
+
|------|-------------|
|
|
47
|
+
| `get_priorities` | Get top customer priorities ranked by volume, urgency, or sentiment |
|
|
48
|
+
| `get_brief` | Get the engineering spec for a priority (what to build, why, done criteria) |
|
|
49
|
+
| `search_feedback` | Search raw customer feedback by keyword |
|
|
50
|
+
| `start_building` | Mark a brief as "building" - you're working on it |
|
|
51
|
+
| `mark_done` | Mark a brief as "done" - it shipped! |
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
npx circuit-mcp setup
|
|
53
|
+
## Example Usage
|
|
54
54
|
|
|
55
|
-
|
|
56
|
-
npx circuit-mcp auth
|
|
55
|
+
Ask your AI assistant:
|
|
57
56
|
|
|
58
|
-
|
|
59
|
-
npx circuit-mcp logout
|
|
60
|
-
```
|
|
57
|
+
> "What are my top 5 priorities?"
|
|
61
58
|
|
|
62
|
-
|
|
59
|
+
> "Get the brief for priority #1"
|
|
63
60
|
|
|
64
|
-
|
|
61
|
+
> "Search feedback about login issues"
|
|
65
62
|
|
|
66
|
-
|
|
67
|
-
|------|-------------|
|
|
68
|
-
| `get_priorities` | Get top customer feedback priorities |
|
|
69
|
-
| `get_brief` | Get the engineering brief for a priority |
|
|
70
|
-
| `get_feedback` | Get raw customer feedback items |
|
|
63
|
+
> "Mark that brief as done"
|
|
71
64
|
|
|
72
|
-
|
|
65
|
+
## Commands
|
|
73
66
|
|
|
74
|
-
|
|
67
|
+
```bash
|
|
68
|
+
npx circuit-mcp # Start MCP server (used by Cursor/Claude)
|
|
69
|
+
npx circuit-mcp setup # Show setup instructions
|
|
70
|
+
npx circuit-mcp auth # Re-authenticate
|
|
71
|
+
npx circuit-mcp logout # Clear stored token
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## How It Works
|
|
75
|
+
|
|
76
|
+
```
|
|
77
|
+
Circuit (app.withcircuit.com)
|
|
78
|
+
│
|
|
79
|
+
│ Feedback → Priorities → Briefs
|
|
80
|
+
│
|
|
81
|
+
▼
|
|
82
|
+
Circuit MCP ◄─── Cursor / Claude Code
|
|
83
|
+
│
|
|
84
|
+
│ get_priorities, get_brief, etc.
|
|
85
|
+
│
|
|
86
|
+
▼
|
|
87
|
+
Your AI assistant has context
|
|
88
|
+
```
|
|
75
89
|
|
|
76
|
-
|
|
90
|
+
## Links
|
|
77
91
|
|
|
78
|
-
|
|
92
|
+
- [Circuit](https://withcircuit.com) - Customer feedback intelligence
|
|
93
|
+
- [MCP Protocol](https://modelcontextprotocol.io) - Model Context Protocol spec
|
|
79
94
|
|
|
80
95
|
## License
|
|
81
96
|
|
|
82
|
-
|
|
97
|
+
Proprietary - © Circuit (withcircuit.com)
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "circuit-mcp",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "Circuit
|
|
3
|
+
"version": "1.0.15",
|
|
4
|
+
"description": "Connect Circuit to Cursor and Claude Code - bring customer priorities and engineering briefs into your AI coding assistant",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"circuit-mcp": "./bin/circuit-mcp.js"
|
|
@@ -13,13 +13,28 @@
|
|
|
13
13
|
"keywords": [
|
|
14
14
|
"circuit",
|
|
15
15
|
"mcp",
|
|
16
|
+
"model-context-protocol",
|
|
16
17
|
"cursor",
|
|
17
18
|
"claude",
|
|
19
|
+
"claude-code",
|
|
18
20
|
"ai",
|
|
19
|
-
"feedback"
|
|
21
|
+
"feedback",
|
|
22
|
+
"priorities",
|
|
23
|
+
"engineering",
|
|
24
|
+
"specs",
|
|
25
|
+
"customer-feedback"
|
|
20
26
|
],
|
|
21
|
-
"author": "Circuit",
|
|
22
|
-
"license": "
|
|
27
|
+
"author": "Circuit <hello@withcircuit.com>",
|
|
28
|
+
"license": "UNLICENSED",
|
|
29
|
+
"homepage": "https://withcircuit.com",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/CatherineWilliamsTreloar/Circuit.git",
|
|
33
|
+
"directory": "circuit-mcp"
|
|
34
|
+
},
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/CatherineWilliamsTreloar/Circuit/issues"
|
|
37
|
+
},
|
|
23
38
|
"dependencies": {
|
|
24
39
|
"chalk": "^5.3.0",
|
|
25
40
|
"open": "^10.1.0"
|
package/src/server.js
CHANGED
|
@@ -173,6 +173,19 @@ async function handleMessage(message, token) {
|
|
|
173
173
|
},
|
|
174
174
|
required: ['query']
|
|
175
175
|
}
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
name: 'get_insights',
|
|
179
|
+
description: 'Get high-level insights and patterns across your priorities. Identifies themes, trends, and strategic recommendations.',
|
|
180
|
+
inputSchema: {
|
|
181
|
+
type: 'object',
|
|
182
|
+
properties: {
|
|
183
|
+
limit: {
|
|
184
|
+
type: 'number',
|
|
185
|
+
description: 'Number of priorities to analyze (default: 10)'
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
176
189
|
}
|
|
177
190
|
]
|
|
178
191
|
}
|
|
@@ -204,6 +217,233 @@ async function handleMessage(message, token) {
|
|
|
204
217
|
}
|
|
205
218
|
}
|
|
206
219
|
|
|
220
|
+
/**
|
|
221
|
+
* Format priorities for display (matches Circuit UI exactly)
|
|
222
|
+
*/
|
|
223
|
+
function formatPriorities(data) {
|
|
224
|
+
if (data.message) {
|
|
225
|
+
return data.message;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (!data.priorities || data.priorities.length === 0) {
|
|
229
|
+
return 'No priorities found. Upload feedback to Circuit to get started.';
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const lines = [];
|
|
233
|
+
|
|
234
|
+
for (const p of data.priorities) {
|
|
235
|
+
// Format trend indicator like Circuit UI
|
|
236
|
+
let trendText = '';
|
|
237
|
+
if (p.trend === 'up') {
|
|
238
|
+
trendText = ' ↑';
|
|
239
|
+
} else if (p.trend === 'down') {
|
|
240
|
+
trendText = ' ↓';
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Format: #Rank. Title
|
|
244
|
+
lines.push(`**#${p.rank}. ${p.theme}**`);
|
|
245
|
+
|
|
246
|
+
// Badges row: Category | X users | Trend | Status
|
|
247
|
+
const badges = [];
|
|
248
|
+
badges.push(p.category || 'Other');
|
|
249
|
+
badges.push(`${p.volume} users`);
|
|
250
|
+
if (trendText) badges.push(trendText.trim());
|
|
251
|
+
if (p.brief_status && p.brief_status !== 'no_brief') {
|
|
252
|
+
const statusLabel = { ready: 'Ready', building: 'Building', done: 'Done' }[p.brief_status];
|
|
253
|
+
if (statusLabel) badges.push(statusLabel);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
lines.push(badges.join(' · '));
|
|
257
|
+
|
|
258
|
+
// Key quote (customer voice)
|
|
259
|
+
if (p.key_quote) {
|
|
260
|
+
lines.push(`> "${p.key_quote}"`);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
lines.push(`priority_id: \`${p.id}\``);
|
|
264
|
+
lines.push('');
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
lines.push('---');
|
|
268
|
+
lines.push('Use `get_brief` with a priority_id to see the full engineering spec.');
|
|
269
|
+
|
|
270
|
+
return lines.join('\n');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Format brief for display (matches Circuit UI format for Cursor/Claude)
|
|
275
|
+
*/
|
|
276
|
+
function formatBrief(data) {
|
|
277
|
+
if (data.error) {
|
|
278
|
+
if (data.error === 'no_brief' && data.priority) {
|
|
279
|
+
const p = data.priority;
|
|
280
|
+
let output = `# ${p.theme || 'Priority'}\n\n`;
|
|
281
|
+
output += `${p.category || 'Other'} · ${p.volume || 0} users\n\n`;
|
|
282
|
+
output += `**No brief generated yet**\n\n`;
|
|
283
|
+
|
|
284
|
+
if (p.summary) {
|
|
285
|
+
output += `${p.summary}\n\n`;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (p.key_quote) {
|
|
289
|
+
output += `> "${p.key_quote}"\n\n`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
output += `---\n\n`;
|
|
293
|
+
|
|
294
|
+
// Include deep link to Circuit
|
|
295
|
+
if (data.circuit_url) {
|
|
296
|
+
output += `**Generate brief:** ${data.circuit_url}\n\n`;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
output += `Or I can help you draft implementation notes based on the customer feedback above.`;
|
|
300
|
+
return output;
|
|
301
|
+
}
|
|
302
|
+
return `Error: ${data.message || data.error}`;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
const spec = data.spec_content || '';
|
|
306
|
+
|
|
307
|
+
// Build header like Circuit UI exports
|
|
308
|
+
// Format: # Title
|
|
309
|
+
// Priority: #X | Mentions: Y | Type: Z
|
|
310
|
+
// ---
|
|
311
|
+
// {spec content}
|
|
312
|
+
|
|
313
|
+
let output = '';
|
|
314
|
+
|
|
315
|
+
// Status line
|
|
316
|
+
const statusText = {
|
|
317
|
+
'ready': 'Ready',
|
|
318
|
+
'building': 'Building',
|
|
319
|
+
'done': 'Done'
|
|
320
|
+
}[data.status] || data.status;
|
|
321
|
+
|
|
322
|
+
output += `Status: ${statusText}\n\n`;
|
|
323
|
+
|
|
324
|
+
// The spec_content should already be formatted markdown
|
|
325
|
+
// Clean up any XML-style tags to proper headers
|
|
326
|
+
let cleanSpec = spec
|
|
327
|
+
.replace(/<what_to_build>/gi, '## WHAT TO BUILD\n')
|
|
328
|
+
.replace(/<\/what_to_build>/gi, '\n')
|
|
329
|
+
.replace(/<why_it_matters>/gi, '## WHY IT MATTERS\n')
|
|
330
|
+
.replace(/<\/why_it_matters>/gi, '\n')
|
|
331
|
+
.replace(/<customer_voice>/gi, '## CUSTOMER VOICE\n')
|
|
332
|
+
.replace(/<\/customer_voice>/gi, '\n')
|
|
333
|
+
.replace(/<files_to_touch>/gi, '## FILES TO TOUCH\n')
|
|
334
|
+
.replace(/<\/files_to_touch>/gi, '\n')
|
|
335
|
+
.replace(/<done_when>/gi, '## DONE WHEN\n')
|
|
336
|
+
.replace(/<\/done_when>/gi, '\n');
|
|
337
|
+
|
|
338
|
+
output += cleanSpec;
|
|
339
|
+
|
|
340
|
+
output += `\n---\n`;
|
|
341
|
+
output += `build_id: \`${data.build_id}\``;
|
|
342
|
+
|
|
343
|
+
return output;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* Format search results for display
|
|
348
|
+
*/
|
|
349
|
+
function formatSearchResults(data) {
|
|
350
|
+
if (data.message) {
|
|
351
|
+
return data.message;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (!data.results || data.results.length === 0) {
|
|
355
|
+
return 'No feedback found matching your search.';
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
const lines = [`**${data.total} results found**\n`];
|
|
359
|
+
|
|
360
|
+
for (const f of data.results) {
|
|
361
|
+
lines.push(`**${f.source}** · Urgency: ${f.urgency}/5`);
|
|
362
|
+
lines.push(`> "${f.text}"`);
|
|
363
|
+
lines.push('');
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return lines.join('\n');
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
/**
|
|
370
|
+
* Format status change response
|
|
371
|
+
*/
|
|
372
|
+
function formatStatusChange(data) {
|
|
373
|
+
if (data.error) {
|
|
374
|
+
return `Error: ${data.error}`;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (data.success) {
|
|
378
|
+
return data.message;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return JSON.stringify(data, null, 2);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* Format insights for display
|
|
386
|
+
*/
|
|
387
|
+
function formatInsights(data) {
|
|
388
|
+
if (data.message) {
|
|
389
|
+
return data.message;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
const lines = [];
|
|
393
|
+
|
|
394
|
+
lines.push(`## Insights from ${data.analyzed} priorities\n`);
|
|
395
|
+
lines.push(`**Total feedback volume:** ${data.total_feedback_volume} mentions\n`);
|
|
396
|
+
|
|
397
|
+
// Category breakdown
|
|
398
|
+
if (data.category_breakdown) {
|
|
399
|
+
lines.push('**Category breakdown:**');
|
|
400
|
+
for (const [cat, count] of Object.entries(data.category_breakdown)) {
|
|
401
|
+
lines.push(`- ${cat}: ${count}`);
|
|
402
|
+
}
|
|
403
|
+
lines.push('');
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
// Top 3 by volume
|
|
407
|
+
if (data.top_3_by_volume && data.top_3_by_volume.length > 0) {
|
|
408
|
+
lines.push('**Top priorities by volume:**');
|
|
409
|
+
for (const p of data.top_3_by_volume) {
|
|
410
|
+
lines.push(`- ${p.theme} (${p.volume} users, ${p.category})`);
|
|
411
|
+
}
|
|
412
|
+
lines.push('');
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// Trends
|
|
416
|
+
if (data.trending_up && data.trending_up.length > 0) {
|
|
417
|
+
lines.push(`**Trending up:** ${data.trending_up.join(', ')}`);
|
|
418
|
+
}
|
|
419
|
+
if (data.trending_down && data.trending_down.length > 0) {
|
|
420
|
+
lines.push(`**Trending down:** ${data.trending_down.join(', ')}`);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// High urgency
|
|
424
|
+
if (data.high_urgency && data.high_urgency.length > 0) {
|
|
425
|
+
lines.push('\n**High urgency items:**');
|
|
426
|
+
for (const h of data.high_urgency) {
|
|
427
|
+
lines.push(`- ${h.theme} (urgency: ${h.urgency})`);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Recommendations
|
|
432
|
+
if (data.recommendations && data.recommendations.length > 0) {
|
|
433
|
+
lines.push('\n**Recommendations:**');
|
|
434
|
+
for (const r of data.recommendations) {
|
|
435
|
+
lines.push(`- ${r}`);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
lines.push('\n---');
|
|
440
|
+
if (data.circuit_url) {
|
|
441
|
+
lines.push(`View full details: ${data.circuit_url}`);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
return lines.join('\n');
|
|
445
|
+
}
|
|
446
|
+
|
|
207
447
|
/**
|
|
208
448
|
* Handle tool calls
|
|
209
449
|
*/
|
|
@@ -214,11 +454,35 @@ async function handleToolCall(id, params, token) {
|
|
|
214
454
|
// Call the Circuit MCP backend
|
|
215
455
|
const result = await callMcpApi(token, name, args || {});
|
|
216
456
|
|
|
457
|
+
// Format response based on tool type
|
|
458
|
+
let formattedText;
|
|
459
|
+
|
|
460
|
+
switch (name) {
|
|
461
|
+
case 'get_priorities':
|
|
462
|
+
formattedText = formatPriorities(result);
|
|
463
|
+
break;
|
|
464
|
+
case 'get_brief':
|
|
465
|
+
formattedText = formatBrief(result);
|
|
466
|
+
break;
|
|
467
|
+
case 'search_feedback':
|
|
468
|
+
formattedText = formatSearchResults(result);
|
|
469
|
+
break;
|
|
470
|
+
case 'start_building':
|
|
471
|
+
case 'mark_done':
|
|
472
|
+
formattedText = formatStatusChange(result);
|
|
473
|
+
break;
|
|
474
|
+
case 'get_insights':
|
|
475
|
+
formattedText = formatInsights(result);
|
|
476
|
+
break;
|
|
477
|
+
default:
|
|
478
|
+
formattedText = JSON.stringify(result, null, 2);
|
|
479
|
+
}
|
|
480
|
+
|
|
217
481
|
return {
|
|
218
482
|
jsonrpc: '2.0',
|
|
219
483
|
id,
|
|
220
484
|
result: {
|
|
221
|
-
content: [{ type: 'text', text:
|
|
485
|
+
content: [{ type: 'text', text: formattedText }]
|
|
222
486
|
}
|
|
223
487
|
};
|
|
224
488
|
|