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.
Files changed (3) hide show
  1. package/README.md +47 -32
  2. package/package.json +20 -5
  3. 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 your Cursor settings (`Cmd+Shift+P` → "Cursor Settings: Open User Settings"):
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 will open your browser to authenticate. After signing in, your token is cached locally at `~/.circuit/token.json`.
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
- Connected!
40
+ Connected!
44
41
  ```
45
42
 
46
- ## Commands
43
+ ## Available Tools
47
44
 
48
- ```bash
49
- # Start MCP server (used by Cursor/Claude)
50
- npx circuit-mcp
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
- # Show setup instructions
53
- npx circuit-mcp setup
53
+ ## Example Usage
54
54
 
55
- # Re-authenticate
56
- npx circuit-mcp auth
55
+ Ask your AI assistant:
57
56
 
58
- # Log out (clear stored token)
59
- npx circuit-mcp logout
60
- ```
57
+ > "What are my top 5 priorities?"
61
58
 
62
- ## Available Tools
59
+ > "Get the brief for priority #1"
63
60
 
64
- Once connected, your AI coding assistant can use these tools:
61
+ > "Search feedback about login issues"
65
62
 
66
- | Tool | Description |
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
- ### Example Usage in Cursor/Claude
65
+ ## Commands
73
66
 
74
- > "What are the top 5 customer priorities?"
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
- > "Get the brief for priority #1 and help me implement it"
90
+ ## Links
77
91
 
78
- > "Show me recent feedback about authentication issues"
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
- MIT
97
+ Proprietary - © Circuit (withcircuit.com)
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "circuit-mcp",
3
- "version": "1.0.13",
4
- "description": "Circuit MCP server for Cursor and Claude Code",
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": "MIT",
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: JSON.stringify(result, null, 2) }]
485
+ content: [{ type: 'text', text: formattedText }]
222
486
  }
223
487
  };
224
488