circuit-mcp 1.0.8 → 1.0.10

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "circuit-mcp",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Circuit MCP server for Cursor and Claude Code",
5
5
  "type": "module",
6
6
  "bin": {
package/src/auth.js CHANGED
@@ -148,39 +148,69 @@ function getSuccessPage() {
148
148
  justify-content: center;
149
149
  color: #1C1A18;
150
150
  }
151
- .container {
151
+ .card {
152
+ background: white;
153
+ border-radius: 16px;
154
+ box-shadow: 0 4px 24px rgba(0,0,0,0.08);
155
+ padding: 48px 64px;
152
156
  text-align: center;
153
- padding: 48px;
157
+ max-width: 420px;
154
158
  }
155
- .icon {
156
- font-size: 48px;
157
- margin-bottom: 24px;
159
+ .logo {
160
+ display: flex;
161
+ align-items: center;
162
+ justify-content: center;
163
+ gap: 8px;
164
+ margin-bottom: 32px;
165
+ }
166
+ .logo-icon {
167
+ width: 28px;
168
+ height: 28px;
169
+ background: #1C1A18;
170
+ border-radius: 50%;
171
+ display: flex;
172
+ align-items: center;
173
+ justify-content: center;
174
+ }
175
+ .logo-icon::after {
176
+ content: '';
177
+ width: 12px;
178
+ height: 12px;
179
+ border: 2px solid white;
180
+ border-radius: 50%;
181
+ }
182
+ .logo-text {
183
+ font-size: 16px;
184
+ font-weight: 500;
185
+ }
186
+ .woo {
187
+ font-size: 14px;
188
+ color: rgba(28,26,24,0.5);
189
+ margin-bottom: 8px;
158
190
  }
159
191
  h1 {
160
- font-size: 24px;
192
+ font-size: 28px;
161
193
  font-weight: 600;
162
- margin-bottom: 16px;
194
+ margin-bottom: 12px;
163
195
  }
164
196
  p {
165
- font-size: 16px;
197
+ font-size: 15px;
166
198
  color: rgba(28,26,24,0.6);
167
- margin-bottom: 8px;
168
- }
169
- .hint {
170
- margin-top: 24px;
171
- font-size: 14px;
172
- color: rgba(28,26,24,0.4);
199
+ line-height: 1.5;
173
200
  }
174
201
  </style>
175
202
  </head>
176
203
  <body>
177
- <div class="container">
178
- <div class="icon">◉</div>
204
+ <div class="card">
205
+ <div class="logo">
206
+ <div class="logo-icon"></div>
207
+ <span class="logo-text">Circuit</span>
208
+ </div>
209
+ <p class="woo">Woo hoo!</p>
179
210
  <h1>Connected to Circuit</h1>
180
- <p>You can close this window.</p>
181
- <p class="hint">Return to your terminal to continue.</p>
211
+ <p>You can close this window to continue Circuiting.</p>
182
212
  </div>
183
- <script>setTimeout(() => window.close(), 2000);</script>
213
+ <script>setTimeout(() => window.close(), 3000);</script>
184
214
  </body>
185
215
  </html>`;
186
216
  }
package/src/index.js CHANGED
@@ -129,6 +129,20 @@ function prompt(question) {
129
129
  });
130
130
  }
131
131
 
132
+ function showUsageGuide() {
133
+ console.log(chalk.dim(' ─────────────────────────────────────────\n'));
134
+ console.log(chalk.white.bold(' How to use Circuit\n'));
135
+ console.log(chalk.dim(' Open Claude Code or Cursor and ask:\n'));
136
+ console.log(chalk.white(' "What are my top priorities?"'));
137
+ console.log(chalk.white(' "Show me the brief for priority #1"'));
138
+ console.log(chalk.white(' "What feedback mentions dark mode?"'));
139
+ console.log(chalk.white(' "What are my most urgent issues?"'));
140
+ console.log(chalk.white(' "Show priorities by revenue impact"'));
141
+ console.log();
142
+ console.log(chalk.dim(' Circuit will fetch your real customer data.'));
143
+ console.log(chalk.dim(' Just ask naturally and your AI will handle the rest.\n'));
144
+ }
145
+
132
146
  // Configure Cursor by writing to ~/.cursor/mcp.json
133
147
  async function configureCursor() {
134
148
  const configPath = path.join(os.homedir(), '.cursor', 'mcp.json');
@@ -243,9 +257,28 @@ async function runSetup() {
243
257
 
244
258
  console.log(chalk.dim(' ─────────────────────────────────────────\n'));
245
259
  console.log(chalk.white.bold(' Almost done!\n'));
246
- console.log(chalk.dim(' When you restart your editor, Circuit will'));
247
- console.log(chalk.dim(' prompt you to sign in on first use.\n'));
248
- console.log(chalk.dim(' That\'s it. You\'re all set.\n'));
260
+ console.log(chalk.dim(' Now let\'s connect your Circuit account.\n'));
261
+
262
+ const authChoice = await prompt(chalk.cyan(' Sign in now? (Y/n): '));
263
+
264
+ if (authChoice.toLowerCase() !== 'n') {
265
+ console.log();
266
+ console.log(chalk.dim(' Opening browser to sign in...\n'));
267
+
268
+ try {
269
+ const token = await authenticate();
270
+ showSuccess('Signed in to Circuit');
271
+ console.log();
272
+ } catch (err) {
273
+ showError('Could not sign in automatically.');
274
+ console.log(chalk.dim(' Run this later: npx circuit-mcp auth\n'));
275
+ }
276
+ }
277
+
278
+ console.log(chalk.dim(' ─────────────────────────────────────────\n'));
279
+ console.log(chalk.white.bold(' You\'re all set!\n'));
280
+ console.log(chalk.dim(' Restart your editor and start using Circuit.\n'));
281
+ showUsageGuide();
249
282
  }
250
283
 
251
284
  async function runAuth() {
@@ -258,6 +291,7 @@ async function runAuth() {
258
291
  spinner.stop();
259
292
  showSuccess('Authenticated successfully!');
260
293
  console.log(chalk.dim('\n Your token is stored locally.\n'));
294
+ showUsageGuide();
261
295
  } catch (err) {
262
296
  spinner.stop();
263
297
  showError(`Authentication failed: ${err.message}`);
package/src/server.js CHANGED
@@ -35,6 +35,30 @@ export async function startMcpServer(token) {
35
35
  process.stdin.resume();
36
36
  }
37
37
 
38
+ /**
39
+ * Call Circuit MCP backend API
40
+ */
41
+ async function callMcpApi(token, toolName, args = {}) {
42
+ const response = await fetch(`${CIRCUIT_API}/mcp/call`, {
43
+ method: 'POST',
44
+ headers: {
45
+ 'Authorization': `Bearer ${token}`,
46
+ 'Content-Type': 'application/json'
47
+ },
48
+ body: JSON.stringify({
49
+ tool: toolName,
50
+ arguments: args
51
+ })
52
+ });
53
+
54
+ if (!response.ok) {
55
+ const text = await response.text();
56
+ throw new Error(`API error ${response.status}: ${text}`);
57
+ }
58
+
59
+ return response.json();
60
+ }
61
+
38
62
  /**
39
63
  * Handle incoming MCP message
40
64
  */
@@ -71,47 +95,83 @@ async function handleMessage(message, token) {
71
95
  tools: [
72
96
  {
73
97
  name: 'get_priorities',
74
- description: 'Get the top customer feedback priorities from Circuit',
98
+ description: 'Get your top priorities from Circuit, ranked by customer volume. These are the features/fixes that matter most to your users.',
75
99
  inputSchema: {
76
100
  type: 'object',
77
101
  properties: {
78
102
  limit: {
79
103
  type: 'number',
80
- description: 'Maximum number of priorities to return (default: 10)'
104
+ description: 'Number of priorities to return (default: 5, max: 20)'
81
105
  },
82
- focus: {
106
+ sort_by: {
83
107
  type: 'string',
84
- description: 'Focus type: volume, revenue, urgency, negative, positive, feature',
85
- enum: ['volume', 'revenue', 'urgency', 'negative', 'positive', 'feature']
108
+ description: 'Sort criterion: volume, urgency, revenue, negative, positive',
109
+ enum: ['volume', 'urgency', 'revenue', 'negative', 'positive']
86
110
  }
87
111
  }
88
112
  }
89
113
  },
90
114
  {
91
115
  name: 'get_brief',
92
- description: 'Get the engineering brief for a specific priority',
116
+ description: 'Get the full engineering brief for a specific priority. Includes what to build, why it matters, customer quotes, files to touch, and done criteria.',
93
117
  inputSchema: {
94
118
  type: 'object',
95
119
  properties: {
96
120
  priority_id: {
97
121
  type: 'string',
98
- description: 'The ID of the priority to get the brief for'
122
+ description: 'The priority ID (from get_priorities)'
123
+ },
124
+ build_id: {
125
+ type: 'string',
126
+ description: 'The build ID directly (alternative to priority_id)'
127
+ }
128
+ }
129
+ }
130
+ },
131
+ {
132
+ name: 'start_building',
133
+ description: "Mark a brief as 'building' - you're actively working on this feature.",
134
+ inputSchema: {
135
+ type: 'object',
136
+ properties: {
137
+ build_id: {
138
+ type: 'string',
139
+ description: 'The build ID to mark as building'
140
+ }
141
+ },
142
+ required: ['build_id']
143
+ }
144
+ },
145
+ {
146
+ name: 'mark_done',
147
+ description: "Mark a brief as 'done' - the feature has shipped! This closes the feedback loop.",
148
+ inputSchema: {
149
+ type: 'object',
150
+ properties: {
151
+ build_id: {
152
+ type: 'string',
153
+ description: 'The build ID to mark as done'
99
154
  }
100
155
  },
101
- required: ['priority_id']
156
+ required: ['build_id']
102
157
  }
103
158
  },
104
159
  {
105
- name: 'get_feedback',
106
- description: 'Get raw customer feedback items',
160
+ name: 'search_feedback',
161
+ description: 'Search customer feedback by keyword. Useful for understanding pain points around specific features.',
107
162
  inputSchema: {
108
163
  type: 'object',
109
164
  properties: {
165
+ query: {
166
+ type: 'string',
167
+ description: 'Search query (keywords or phrase)'
168
+ },
110
169
  limit: {
111
170
  type: 'number',
112
- description: 'Maximum number of feedback items to return (default: 20)'
171
+ description: 'Max results (default: 10)'
113
172
  }
114
- }
173
+ },
174
+ required: ['query']
115
175
  }
116
176
  }
117
177
  ]
@@ -151,28 +211,8 @@ async function handleToolCall(id, params, token) {
151
211
  const { name, arguments: args } = params;
152
212
 
153
213
  try {
154
- let result;
155
-
156
- switch (name) {
157
- case 'get_priorities':
158
- result = await fetchPriorities(token, args);
159
- break;
160
-
161
- case 'get_brief':
162
- result = await fetchBrief(token, args);
163
- break;
164
-
165
- case 'get_feedback':
166
- result = await fetchFeedback(token, args);
167
- break;
168
-
169
- default:
170
- return {
171
- jsonrpc: '2.0',
172
- id,
173
- error: { code: -32602, message: `Unknown tool: ${name}` }
174
- };
175
- }
214
+ // Call the Circuit MCP backend
215
+ const result = await callMcpApi(token, name, args || {});
176
216
 
177
217
  return {
178
218
  jsonrpc: '2.0',
@@ -191,96 +231,3 @@ async function handleToolCall(id, params, token) {
191
231
  }
192
232
  }
193
233
 
194
- /**
195
- * Fetch priorities from Circuit API
196
- */
197
- async function fetchPriorities(token, args = {}) {
198
- const limit = args.limit || 10;
199
- const focus = args.focus || 'volume';
200
-
201
- const url = `${CIRCUIT_API}/api/focus?limit=${limit}&focus_type=${focus}`;
202
-
203
- const response = await fetch(url, {
204
- headers: { 'Authorization': `Bearer ${token}` }
205
- });
206
-
207
- if (!response.ok) {
208
- throw new Error(`API error: ${response.status}`);
209
- }
210
-
211
- const data = await response.json();
212
-
213
- // Format for readability
214
- return (data.priorities || data.clusters || []).map((p, i) => ({
215
- rank: i + 1,
216
- id: p.cluster_id || p.id,
217
- title: p.summary || p.theme,
218
- category: p.category,
219
- mentions: p.volume || p.count,
220
- urgency: p.urgency_score,
221
- trend: p.trend
222
- }));
223
- }
224
-
225
- /**
226
- * Fetch a specific brief from Circuit API
227
- */
228
- async function fetchBrief(token, args) {
229
- if (!args.priority_id) {
230
- throw new Error('priority_id is required');
231
- }
232
-
233
- const url = `${CIRCUIT_API}/api/builds?cluster_id=${args.priority_id}`;
234
-
235
- const response = await fetch(url, {
236
- headers: { 'Authorization': `Bearer ${token}` }
237
- });
238
-
239
- if (!response.ok) {
240
- throw new Error(`API error: ${response.status}`);
241
- }
242
-
243
- const data = await response.json();
244
- const build = (data.builds || []).find(b =>
245
- b.cluster_id === args.priority_id || b.clusterId === args.priority_id
246
- );
247
-
248
- if (!build) {
249
- throw new Error('Brief not found for this priority');
250
- }
251
-
252
- return {
253
- id: build.id,
254
- title: build.title,
255
- status: build.status,
256
- content: build.build || build.spec_content
257
- };
258
- }
259
-
260
- /**
261
- * Fetch raw feedback from Circuit API
262
- */
263
- async function fetchFeedback(token, args = {}) {
264
- const limit = args.limit || 20;
265
-
266
- const url = `${CIRCUIT_API}/api/feedback/all?limit=${limit}`;
267
-
268
- const response = await fetch(url, {
269
- headers: { 'Authorization': `Bearer ${token}` }
270
- });
271
-
272
- if (!response.ok) {
273
- throw new Error(`API error: ${response.status}`);
274
- }
275
-
276
- const data = await response.json();
277
-
278
- return (data.feedback || []).map(f => ({
279
- id: f.id,
280
- text: f.text,
281
- source: f.source,
282
- sentiment: f.analysis?.sentiment,
283
- intent: f.analysis?.intent,
284
- created_at: f.created_at
285
- }));
286
- }