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 +1 -1
- package/src/auth.js +49 -19
- package/src/index.js +37 -3
- package/src/server.js +74 -127
package/package.json
CHANGED
package/src/auth.js
CHANGED
|
@@ -148,39 +148,69 @@ function getSuccessPage() {
|
|
|
148
148
|
justify-content: center;
|
|
149
149
|
color: #1C1A18;
|
|
150
150
|
}
|
|
151
|
-
.
|
|
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
|
-
|
|
157
|
+
max-width: 420px;
|
|
154
158
|
}
|
|
155
|
-
.
|
|
156
|
-
|
|
157
|
-
|
|
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:
|
|
192
|
+
font-size: 28px;
|
|
161
193
|
font-weight: 600;
|
|
162
|
-
margin-bottom:
|
|
194
|
+
margin-bottom: 12px;
|
|
163
195
|
}
|
|
164
196
|
p {
|
|
165
|
-
font-size:
|
|
197
|
+
font-size: 15px;
|
|
166
198
|
color: rgba(28,26,24,0.6);
|
|
167
|
-
|
|
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="
|
|
178
|
-
<div class="
|
|
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(),
|
|
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('
|
|
247
|
-
|
|
248
|
-
|
|
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
|
|
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: '
|
|
104
|
+
description: 'Number of priorities to return (default: 5, max: 20)'
|
|
81
105
|
},
|
|
82
|
-
|
|
106
|
+
sort_by: {
|
|
83
107
|
type: 'string',
|
|
84
|
-
description: '
|
|
85
|
-
enum: ['volume', '
|
|
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
|
|
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: ['
|
|
156
|
+
required: ['build_id']
|
|
102
157
|
}
|
|
103
158
|
},
|
|
104
159
|
{
|
|
105
|
-
name: '
|
|
106
|
-
description: '
|
|
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: '
|
|
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
|
-
|
|
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
|
-
}
|