circuit-mcp 2.3.3 → 2.4.0
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/LICENSE +21 -0
- package/README.md +40 -114
- package/bin/circuit-mcp.js +0 -0
- package/package.json +11 -4
- package/src/auth.js +60 -13
- package/src/index.js +22 -9
- package/src/server.js +64 -7
- package/preview-error.html +0 -57
- package/preview-success.html +0 -57
- package/publish.sh +0 -30
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024-2025 Circuit (withcircuit.com)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
# circuit-mcp
|
|
2
|
+
Feedback in. Specs out. Build it with your AI coding tool.
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Connect [Circuit](https://withcircuit.com) to **Cursor** and **Claude Code** via the Model Context Protocol.
|
|
6
|
-
|
|
7
|
-
---
|
|
4
|
+
Connect Circuit to Cursor and Claude Code via the Model Context Protocol.
|
|
8
5
|
|
|
9
6
|
## Setup
|
|
10
7
|
|
|
@@ -33,144 +30,73 @@ First run opens your browser to authenticate. Token is cached at `~/.circuit/tok
|
|
|
33
30
|
|
|
34
31
|
---
|
|
35
32
|
|
|
36
|
-
## 4
|
|
33
|
+
## 4 tools
|
|
37
34
|
|
|
38
|
-
###
|
|
35
|
+
### circuit.priorities
|
|
39
36
|
|
|
40
|
-
What should I work on? Ranked customer priorities with
|
|
37
|
+
What should I work on? Ranked customer priorities with revenue impact and trend data.
|
|
41
38
|
|
|
42
39
|
| Parameter | Type | Description |
|
|
43
40
|
|-----------|------|-------------|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
46
|
-
|
|
|
47
|
-
|
|
|
48
|
-
|
|
|
49
|
-
|
|
50
|
-
**Also returns:** Session context (last ship, in-progress builds, suggested next action) and instinct confidence (pattern matching from your shipping history).
|
|
51
|
-
|
|
52
|
-
**Weekly mode** (`weekly: true`): Returns priority movements (rank changes), new entries, dropped items, and volume spikes compared to last week.
|
|
41
|
+
| weekly | boolean | Weekly digest (movements, new entries, drops, spikes) |
|
|
42
|
+
| lens | string | How to rank: volume, urgency, revenue, retention, delight, feature |
|
|
43
|
+
| segment | string | enterprise, smb, all |
|
|
44
|
+
| limit | number | Default: 5, max: 20 |
|
|
45
|
+
| category | string | Filter: Bug, Feature, Improvement, Praise |
|
|
53
46
|
|
|
54
|
-
###
|
|
47
|
+
### circuit.spec
|
|
55
48
|
|
|
56
|
-
Full
|
|
49
|
+
Full spec. 5 sections: What to Build, Why It Matters, Customer Voice, Files to Touch, Done When.
|
|
57
50
|
|
|
58
51
|
| Parameter | Type | Description |
|
|
59
52
|
|-----------|------|-------------|
|
|
60
|
-
|
|
|
61
|
-
|
|
|
62
|
-
|
|
|
63
|
-
|
|
|
64
|
-
| `spec_ids` | string[] | Specific spec IDs to export (batch mode) |
|
|
65
|
-
| `status` | string | Filter specs by status in batch mode: `ready`, `building`, `shipped` |
|
|
66
|
-
| `limit` | number | Number of specs in batch mode (default: 10, max: 50) |
|
|
53
|
+
| priority_id | string | From circuit.priorities |
|
|
54
|
+
| batch | boolean | Export multiple specs as markdown |
|
|
55
|
+
| status | string | Filter: ready, building, shipped |
|
|
56
|
+
| limit | number | Default: 10, max: 50 |
|
|
67
57
|
|
|
68
|
-
|
|
58
|
+
### circuit.act
|
|
69
59
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
### `circuit.act`
|
|
73
|
-
|
|
74
|
-
Take action. Start building, ship it, share back, assign, correct, submit feedback, or submit a transcript.
|
|
60
|
+
Start building, ship, notify customers, assign, correct, submit feedback or transcript.
|
|
75
61
|
|
|
76
62
|
| Action | What it does |
|
|
77
63
|
|--------|-------------|
|
|
78
|
-
|
|
|
79
|
-
|
|
|
80
|
-
|
|
|
81
|
-
|
|
|
82
|
-
|
|
|
83
|
-
|
|
|
84
|
-
|
|
|
64
|
+
| build | Mark as "building" |
|
|
65
|
+
| ship | Mark as shipped, record memory |
|
|
66
|
+
| share | Notify customers (email, widget, or both) |
|
|
67
|
+
| assign | Assign to team member |
|
|
68
|
+
| correct | Fix category classification |
|
|
69
|
+
| submit | Add new feedback |
|
|
70
|
+
| transcript | Submit customer interview/call |
|
|
85
71
|
|
|
86
|
-
|
|
72
|
+
### circuit.ask
|
|
87
73
|
|
|
88
|
-
|
|
89
|
-
|-----------|------|-------------|
|
|
90
|
-
| `spec_id` | string | Spec ID (required) |
|
|
91
|
-
| `channel` | string | `email`, `widget`, `both`, `skip` |
|
|
92
|
-
| `message` | string | Custom message to include in notifications (optional) |
|
|
93
|
-
|
|
94
|
-
**Transcript parameters** (when `action: "transcript"`):
|
|
95
|
-
|
|
96
|
-
| Parameter | Type | Description |
|
|
97
|
-
|-----------|------|-------------|
|
|
98
|
-
| `text` | string | Full transcript text (min 50 chars, required) |
|
|
99
|
-
| `title` | string | Title, e.g. "Sales call with Acme Corp" |
|
|
100
|
-
| `type` | string | `interview`, `sales_call`, `support`, `other` |
|
|
101
|
-
| `customer_name` | string | Customer name (optional) |
|
|
102
|
-
| `customer_email` | string | Customer email (optional, for close-the-loop notifications) |
|
|
103
|
-
| `revenue_band` | string | `enterprise`, `paid`, `free` |
|
|
104
|
-
|
|
105
|
-
### `circuit.ask`
|
|
106
|
-
|
|
107
|
-
Semantic search across all your data: feedback, priorities, specs, and help articles. Returns your shipping patterns and behavioral insights. Also searches your connected GitHub repo for code context.
|
|
108
|
-
|
|
109
|
-
| Parameter | Type | Description |
|
|
110
|
-
|-----------|------|-------------|
|
|
111
|
-
| `question` | string | Natural language, e.g. "What are enterprise customers saying about onboarding?" |
|
|
74
|
+
Semantic search across feedback, priorities, specs, help articles and your GitHub repo.
|
|
112
75
|
|
|
113
76
|
---
|
|
114
77
|
|
|
115
78
|
## Workflow
|
|
116
79
|
|
|
117
|
-
```
|
|
118
|
-
> circuit.priorities {weekly: true} # What changed since last week?
|
|
119
|
-
> circuit.priorities # See what matters
|
|
120
|
-
> circuit.spec {priority_id: "..."} # Get the spec
|
|
121
|
-
> circuit.act {action: "build", ...} # Start building
|
|
122
|
-
> ... write the code ...
|
|
123
|
-
> circuit.act {action: "ship", ...} # Ship it.
|
|
124
|
-
> circuit.act {action: "share", channel: "both"} # Share back. Customers notified.
|
|
125
|
-
> circuit.spec {batch: true, status: "shipped"} # Export shipped specs
|
|
126
|
-
```
|
|
127
|
-
|
|
128
|
-
---
|
|
129
|
-
|
|
130
|
-
## CLI Commands
|
|
131
|
-
|
|
132
80
|
```bash
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
81
|
+
circuit.priorities {weekly: true} # What changed since last week?
|
|
82
|
+
circuit.priorities # See what matters
|
|
83
|
+
circuit.spec {priority_id: "..."} # Get the spec
|
|
84
|
+
circuit.act {action: "build", ...} # Start building
|
|
85
|
+
circuit.act {action: "ship", ...} # Ship.
|
|
86
|
+
circuit.act {action: "share", channel: "both"} # Customers notified.
|
|
138
87
|
```
|
|
139
88
|
|
|
140
89
|
---
|
|
141
90
|
|
|
142
|
-
##
|
|
143
|
-
|
|
144
|
-
**"Token expired"** — Run `npx circuit-mcp auth` to re-authenticate.
|
|
145
|
-
|
|
146
|
-
**"Connection refused"** — Check your internet connection. The MCP connects to Circuit's API.
|
|
91
|
+
## CLI commands
|
|
147
92
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
## How it works
|
|
155
|
-
|
|
156
|
-
```
|
|
157
|
-
Your editor (Cursor / Claude Code)
|
|
158
|
-
|
|
|
159
|
-
| JSON-RPC over stdio
|
|
160
|
-
|
|
|
161
|
-
circuit-mcp (this package)
|
|
162
|
-
|
|
|
163
|
-
| HTTPS + Bearer token
|
|
164
|
-
|
|
|
165
|
-
Circuit API
|
|
166
|
-
|
|
|
167
|
-
| PostgreSQL + pgvector
|
|
168
|
-
|
|
|
169
|
-
Your feedback data
|
|
93
|
+
```bash
|
|
94
|
+
npx circuit-mcp # Start MCP server
|
|
95
|
+
npx circuit-mcp auth # Re-authenticate
|
|
96
|
+
npx circuit-mcp logout # Clear stored token
|
|
97
|
+
npx circuit-mcp fix # Troubleshooting guide
|
|
170
98
|
```
|
|
171
99
|
|
|
172
|
-
The MCP server runs as a local process, communicating with your editor via stdin/stdout. All data stays in your Circuit account, authenticated via OAuth 2.0.
|
|
173
|
-
|
|
174
100
|
---
|
|
175
101
|
|
|
176
|
-
Built by
|
|
102
|
+
Built by Circuit. Feedback in. Specs out.
|
package/bin/circuit-mcp.js
CHANGED
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "circuit-mcp",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.4.0",
|
|
4
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"
|
|
8
8
|
},
|
|
9
9
|
"main": "./src/index.js",
|
|
10
|
+
"exports": "./src/index.js",
|
|
11
|
+
"files": [
|
|
12
|
+
"bin/",
|
|
13
|
+
"src/",
|
|
14
|
+
"LICENSE",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
10
17
|
"scripts": {
|
|
11
18
|
"start": "node bin/circuit-mcp.js"
|
|
12
19
|
},
|
|
@@ -25,15 +32,15 @@
|
|
|
25
32
|
"customer-feedback"
|
|
26
33
|
],
|
|
27
34
|
"author": "Circuit <hello@withcircuit.com>",
|
|
28
|
-
"license": "
|
|
35
|
+
"license": "MIT",
|
|
29
36
|
"homepage": "https://withcircuit.com",
|
|
30
37
|
"repository": {
|
|
31
38
|
"type": "git",
|
|
32
|
-
"url": "https://github.com/
|
|
39
|
+
"url": "https://github.com/withcircuit/Circuit.git",
|
|
33
40
|
"directory": "circuit-mcp"
|
|
34
41
|
},
|
|
35
42
|
"bugs": {
|
|
36
|
-
"url": "https://github.com/
|
|
43
|
+
"url": "https://github.com/withcircuit/Circuit/issues"
|
|
37
44
|
},
|
|
38
45
|
"dependencies": {
|
|
39
46
|
"chalk": "^5.3.0",
|
package/src/auth.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import http from 'http';
|
|
2
2
|
import { URL } from 'url';
|
|
3
|
-
import { randomBytes } from 'crypto';
|
|
3
|
+
import { randomBytes, createHash } from 'crypto';
|
|
4
4
|
import fs from 'fs/promises';
|
|
5
5
|
import path from 'path';
|
|
6
6
|
import os from 'os';
|
|
@@ -12,20 +12,53 @@ const CIRCUIT_URL = process.env.CIRCUIT_APP_URL || 'https://app.withcircuit.com'
|
|
|
12
12
|
const TOKEN_FILE = path.join(os.homedir(), '.circuit', 'token.json');
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
* Get stored token from disk
|
|
15
|
+
* Get stored token from disk. Attempts refresh if access token is expired.
|
|
16
16
|
*/
|
|
17
17
|
export async function getStoredToken() {
|
|
18
18
|
try {
|
|
19
19
|
const data = await fs.readFile(TOKEN_FILE, 'utf-8');
|
|
20
|
-
const { token, expiresAt } = JSON.parse(data);
|
|
20
|
+
const { token, expiresAt, refreshToken, refreshExpiresAt } = JSON.parse(data);
|
|
21
21
|
|
|
22
|
-
// Check if
|
|
23
|
-
if (expiresAt && new Date(expiresAt)
|
|
24
|
-
|
|
25
|
-
return null;
|
|
22
|
+
// Check if access token is still valid
|
|
23
|
+
if (expiresAt && new Date(expiresAt) > new Date()) {
|
|
24
|
+
return token;
|
|
26
25
|
}
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
// Access token expired — try refresh
|
|
28
|
+
if (refreshToken && refreshExpiresAt && new Date(refreshExpiresAt) > new Date()) {
|
|
29
|
+
const refreshed = await refreshAccessToken(refreshToken);
|
|
30
|
+
if (refreshed) return refreshed;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Both expired
|
|
34
|
+
await clearToken();
|
|
35
|
+
return null;
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Refresh an expired access token using the refresh token.
|
|
43
|
+
*/
|
|
44
|
+
async function refreshAccessToken(refreshToken) {
|
|
45
|
+
const CIRCUIT_API = process.env.CIRCUIT_API_URL || 'https://api.withcircuit.com';
|
|
46
|
+
try {
|
|
47
|
+
const response = await fetch(`${CIRCUIT_API}/mcp/token`, {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
headers: { 'Content-Type': 'application/json' },
|
|
50
|
+
body: JSON.stringify({
|
|
51
|
+
grant_type: 'refresh_token',
|
|
52
|
+
refresh_token: refreshToken,
|
|
53
|
+
}),
|
|
54
|
+
});
|
|
55
|
+
if (!response.ok) return null;
|
|
56
|
+
const data = await response.json();
|
|
57
|
+
if (data.access_token) {
|
|
58
|
+
await storeToken(data.access_token, data.expires_in, data.refresh_token);
|
|
59
|
+
return data.access_token;
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
29
62
|
} catch {
|
|
30
63
|
return null;
|
|
31
64
|
}
|
|
@@ -34,12 +67,18 @@ export async function getStoredToken() {
|
|
|
34
67
|
/**
|
|
35
68
|
* Store token to disk
|
|
36
69
|
*/
|
|
37
|
-
async function storeToken(token, expiresIn = 86400 * 30) {
|
|
70
|
+
async function storeToken(token, expiresIn = 86400 * 30, refreshToken = null) {
|
|
38
71
|
const dir = path.dirname(TOKEN_FILE);
|
|
39
72
|
await fs.mkdir(dir, { recursive: true });
|
|
40
73
|
|
|
41
74
|
const expiresAt = new Date(Date.now() + expiresIn * 1000).toISOString();
|
|
42
|
-
|
|
75
|
+
const tokenData = { token, expiresAt };
|
|
76
|
+
if (refreshToken) {
|
|
77
|
+
tokenData.refreshToken = refreshToken;
|
|
78
|
+
// Refresh tokens last 90 days
|
|
79
|
+
tokenData.refreshExpiresAt = new Date(Date.now() + 90 * 86400 * 1000).toISOString();
|
|
80
|
+
}
|
|
81
|
+
await fs.writeFile(TOKEN_FILE, JSON.stringify(tokenData, null, 2));
|
|
43
82
|
}
|
|
44
83
|
|
|
45
84
|
/**
|
|
@@ -66,11 +105,18 @@ export async function authenticate() {
|
|
|
66
105
|
const callbackUrl = `http://127.0.0.1:${port}/callback`;
|
|
67
106
|
const state = randomBytes(16).toString('hex');
|
|
68
107
|
|
|
69
|
-
//
|
|
108
|
+
// PKCE (RFC 7636): generate code_verifier and code_challenge
|
|
109
|
+
const codeVerifier = randomBytes(32).toString('base64url');
|
|
110
|
+
const codeChallenge = createHash('sha256').update(codeVerifier).digest('base64url');
|
|
111
|
+
|
|
112
|
+
// Build auth URL with PKCE (RFC 7636)
|
|
70
113
|
const authUrl = new URL(`${CIRCUIT_URL}/mcp/auth`);
|
|
71
114
|
authUrl.searchParams.set('redirect_uri', callbackUrl);
|
|
72
115
|
authUrl.searchParams.set('state', state);
|
|
73
116
|
authUrl.searchParams.set('response_type', 'code');
|
|
117
|
+
authUrl.searchParams.set('code_challenge', codeChallenge);
|
|
118
|
+
authUrl.searchParams.set('code_challenge_method', 'S256');
|
|
119
|
+
authUrl.searchParams.set('code_verifier', codeVerifier);
|
|
74
120
|
|
|
75
121
|
// Handle callback
|
|
76
122
|
server.on('request', async (req, res) => {
|
|
@@ -78,6 +124,7 @@ export async function authenticate() {
|
|
|
78
124
|
|
|
79
125
|
if (url.pathname === '/callback') {
|
|
80
126
|
const token = url.searchParams.get('access_token');
|
|
127
|
+
const refreshTokenParam = url.searchParams.get('refresh_token');
|
|
81
128
|
const error = url.searchParams.get('error');
|
|
82
129
|
const returnedState = url.searchParams.get('state');
|
|
83
130
|
|
|
@@ -97,8 +144,8 @@ export async function authenticate() {
|
|
|
97
144
|
return;
|
|
98
145
|
}
|
|
99
146
|
|
|
100
|
-
// Store token
|
|
101
|
-
await storeToken(token);
|
|
147
|
+
// Store token (with refresh token if available)
|
|
148
|
+
await storeToken(token, 86400 * 30, refreshTokenParam);
|
|
102
149
|
|
|
103
150
|
// Send success page and close after response is fully sent
|
|
104
151
|
res.writeHead(200, { 'Content-Type': 'text/html' });
|
package/src/index.js
CHANGED
|
@@ -132,15 +132,28 @@ function prompt(question) {
|
|
|
132
132
|
function showUsageGuide() {
|
|
133
133
|
console.log(chalk.dim(' ─────────────────────────────────────────\n'));
|
|
134
134
|
console.log(chalk.white.bold(' How to use Circuit\n'));
|
|
135
|
-
console.log(chalk.dim(' Open Claude Code or Cursor and
|
|
136
|
-
|
|
137
|
-
console.log(chalk.
|
|
138
|
-
console.log(chalk.
|
|
139
|
-
console.log(chalk.
|
|
140
|
-
console.log(chalk.
|
|
141
|
-
|
|
142
|
-
console.log(chalk.
|
|
143
|
-
console.log(chalk.dim('
|
|
135
|
+
console.log(chalk.dim(' Open Claude Code or Cursor and try:\n'));
|
|
136
|
+
|
|
137
|
+
console.log(chalk.cyan(' circuit.priorities') + chalk.dim(' — What should I work on?'));
|
|
138
|
+
console.log(chalk.dim(' "What are my top priorities?"'));
|
|
139
|
+
console.log(chalk.dim(' "Show priorities by revenue impact"'));
|
|
140
|
+
console.log(chalk.dim(' "What\'s changed this week?"\n'));
|
|
141
|
+
|
|
142
|
+
console.log(chalk.cyan(' circuit.spec') + chalk.dim(' — Full engineering spec for a priority'));
|
|
143
|
+
console.log(chalk.dim(' "Show me the spec for priority #1"'));
|
|
144
|
+
console.log(chalk.dim(' "Get the brief for <priority_id>"\n'));
|
|
145
|
+
|
|
146
|
+
console.log(chalk.cyan(' circuit.act') + chalk.dim(' — Take action on a spec'));
|
|
147
|
+
console.log(chalk.dim(' "Start building <spec_id>"'));
|
|
148
|
+
console.log(chalk.dim(' "Mark <spec_id> as shipped"'));
|
|
149
|
+
console.log(chalk.dim(' "Tell customers we shipped <spec_id>"\n'));
|
|
150
|
+
|
|
151
|
+
console.log(chalk.cyan(' circuit.ask') + chalk.dim(' — Search across all your feedback'));
|
|
152
|
+
console.log(chalk.dim(' "What feedback mentions dark mode?"'));
|
|
153
|
+
console.log(chalk.dim(' "What are enterprise customers asking for?"\n'));
|
|
154
|
+
|
|
155
|
+
console.log(chalk.dim(' Circuit fetches your real customer data.'));
|
|
156
|
+
console.log(chalk.dim(' Just ask naturally — your AI handles the rest.\n'));
|
|
144
157
|
}
|
|
145
158
|
|
|
146
159
|
// Configure Cursor by writing to ~/.cursor/mcp.json
|
package/src/server.js
CHANGED
|
@@ -83,9 +83,15 @@ async function handleMessage(message, token) {
|
|
|
83
83
|
jsonrpc: '2.0',
|
|
84
84
|
id,
|
|
85
85
|
result: {
|
|
86
|
-
protocolVersion: '
|
|
87
|
-
serverInfo: { name: 'circuit-mcp', version: '2.
|
|
88
|
-
capabilities: { tools: {}, resources: {} }
|
|
86
|
+
protocolVersion: '2025-03-26',
|
|
87
|
+
serverInfo: { name: 'circuit-mcp', version: '2.4.0' },
|
|
88
|
+
capabilities: { tools: {}, resources: {} },
|
|
89
|
+
instructions: `Circuit is connected. 4 tools available:
|
|
90
|
+
• circuit.priorities — what to work on next
|
|
91
|
+
• circuit.spec — engineering spec for any priority
|
|
92
|
+
• circuit.act — ship and close the loop with customers
|
|
93
|
+
• circuit.ask — search across all your feedback
|
|
94
|
+
Run circuit.help for examples.`
|
|
89
95
|
}
|
|
90
96
|
};
|
|
91
97
|
|
|
@@ -195,14 +201,14 @@ const TOOLS = [
|
|
|
195
201
|
},
|
|
196
202
|
{
|
|
197
203
|
name: 'circuit.act',
|
|
198
|
-
description: "Take action. Start building, ship it, share back with customers, assign, correct a classification, submit feedback, or submit a transcript.",
|
|
204
|
+
description: "Take action. Start building, ship it, share back with customers, assign, correct a classification, park a priority, submit feedback, or submit a transcript.",
|
|
199
205
|
inputSchema: {
|
|
200
206
|
type: 'object',
|
|
201
207
|
properties: {
|
|
202
208
|
action: {
|
|
203
209
|
type: 'string',
|
|
204
|
-
description: "'build' (start building), 'ship' (mark shipped), 'share' (notify customers via email/widget), 'assign' (assign to team member), 'correct' (fix classification), 'submit' (add feedback), 'transcript' (submit a transcript)",
|
|
205
|
-
enum: ['build', 'ship', 'share', 'assign', 'correct', 'submit', 'transcript']
|
|
210
|
+
description: "'build' (start building), 'ship' (mark shipped), 'share' (notify customers via email/widget), 'assign' (assign to team member), 'correct' (fix classification), 'park' (park priority with reason), 'submit' (add feedback), 'transcript' (submit a transcript)",
|
|
211
|
+
enum: ['build', 'ship', 'share', 'assign', 'correct', 'park', 'submit', 'transcript']
|
|
206
212
|
},
|
|
207
213
|
spec_id: {
|
|
208
214
|
type: 'string',
|
|
@@ -223,7 +229,11 @@ const TOOLS = [
|
|
|
223
229
|
},
|
|
224
230
|
priority_id: {
|
|
225
231
|
type: 'string',
|
|
226
|
-
description: "Priority ID (for correct)"
|
|
232
|
+
description: "Priority ID (for correct, park)"
|
|
233
|
+
},
|
|
234
|
+
park_reason: {
|
|
235
|
+
type: 'string',
|
|
236
|
+
description: "Reason for parking this priority (for park action). E.g. 'Out of scope this quarter', 'Waiting for more signal', 'Low revenue impact'"
|
|
227
237
|
},
|
|
228
238
|
correction_type: {
|
|
229
239
|
type: 'string',
|
|
@@ -290,6 +300,14 @@ const TOOLS = [
|
|
|
290
300
|
},
|
|
291
301
|
required: ['question']
|
|
292
302
|
}
|
|
303
|
+
},
|
|
304
|
+
{
|
|
305
|
+
name: 'circuit.help',
|
|
306
|
+
description: 'Show what Circuit can do — all available tools, example prompts, and how to use them.',
|
|
307
|
+
inputSchema: {
|
|
308
|
+
type: 'object',
|
|
309
|
+
properties: {}
|
|
310
|
+
}
|
|
293
311
|
}
|
|
294
312
|
];
|
|
295
313
|
|
|
@@ -524,6 +542,44 @@ function formatBatchExport(data) {
|
|
|
524
542
|
return output;
|
|
525
543
|
}
|
|
526
544
|
|
|
545
|
+
function formatHelp() {
|
|
546
|
+
return `# Circuit — What I can do
|
|
547
|
+
|
|
548
|
+
## circuit.priorities — What should I work on?
|
|
549
|
+
Ranked customer priorities with revenue impact and trend data.
|
|
550
|
+
|
|
551
|
+
* "What are my top priorities?"
|
|
552
|
+
* "Show priorities by revenue impact"
|
|
553
|
+
* "Show urgent bugs only"
|
|
554
|
+
* "What's changed this week?"
|
|
555
|
+
* "Show enterprise priorities"
|
|
556
|
+
|
|
557
|
+
## circuit.spec — Engineering spec for a priority
|
|
558
|
+
5 sections: What to Build, Why It Matters, Customer Voice, Files to Touch, Done When.
|
|
559
|
+
* "Show me the spec for priority #1"
|
|
560
|
+
* "Get the brief for <priority_id>"
|
|
561
|
+
* "Export all ready specs for sprint planning"
|
|
562
|
+
|
|
563
|
+
## circuit.act — Ship it and close the loop
|
|
564
|
+
Start building, mark as shipped, notify customers, assign, park a priority, or submit new feedback.
|
|
565
|
+
* "Start building <spec_id>"
|
|
566
|
+
* "Mark <spec_id> as shipped"
|
|
567
|
+
* "Tell customers we shipped <spec_id>"
|
|
568
|
+
* "Assign <spec_id> to Catherine"
|
|
569
|
+
* "Park <priority_id> — waiting for more signal"
|
|
570
|
+
* "Submit feedback: users want dark mode"
|
|
571
|
+
* "Submit transcript: <transcript text>"
|
|
572
|
+
|
|
573
|
+
## circuit.ask — Search across all your feedback
|
|
574
|
+
Ask anything about your customers, feedback or priorities.
|
|
575
|
+
* "What feedback mentions dark mode?"
|
|
576
|
+
* "What are enterprise customers asking for?"
|
|
577
|
+
* "Have we shipped anything related to CSV exports?"
|
|
578
|
+
* "What's our most common bug type?"
|
|
579
|
+
|
|
580
|
+
Ask naturally — Circuit picks the right tool.`;
|
|
581
|
+
}
|
|
582
|
+
|
|
527
583
|
// ─────────────────────────────────────────────────────────────
|
|
528
584
|
// Tool Call Handler
|
|
529
585
|
// ─────────────────────────────────────────────────────────────
|
|
@@ -539,6 +595,7 @@ async function handleToolCall(id, params, token) {
|
|
|
539
595
|
'circuit.spec': formatSpec,
|
|
540
596
|
'circuit.act': formatAct,
|
|
541
597
|
'circuit.ask': formatAsk,
|
|
598
|
+
'circuit.help': () => formatHelp(),
|
|
542
599
|
};
|
|
543
600
|
|
|
544
601
|
const formatter = formatters[name] || ((d) => JSON.stringify(d, null, 2));
|
package/preview-error.html
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<title>Circuit - Connection Failed</title>
|
|
5
|
-
<style>
|
|
6
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
7
|
-
body {
|
|
8
|
-
font-family: 'Geist', -apple-system, BlinkMacSystemFont, system-ui, 'Segoe UI', Roboto, sans-serif;
|
|
9
|
-
background: #F5F3F0;
|
|
10
|
-
min-height: 100vh;
|
|
11
|
-
display: flex;
|
|
12
|
-
align-items: center;
|
|
13
|
-
justify-content: center;
|
|
14
|
-
color: #1C1A18;
|
|
15
|
-
padding: 24px;
|
|
16
|
-
}
|
|
17
|
-
.content {
|
|
18
|
-
text-align: center;
|
|
19
|
-
max-width: 400px;
|
|
20
|
-
}
|
|
21
|
-
.icon {
|
|
22
|
-
margin-bottom: 24px;
|
|
23
|
-
}
|
|
24
|
-
.icon svg {
|
|
25
|
-
width: 48px;
|
|
26
|
-
height: 48px;
|
|
27
|
-
color: #D64545;
|
|
28
|
-
}
|
|
29
|
-
h1 {
|
|
30
|
-
font-size: 24px;
|
|
31
|
-
font-weight: 600;
|
|
32
|
-
color: #1C1A18;
|
|
33
|
-
margin-bottom: 12px;
|
|
34
|
-
}
|
|
35
|
-
p {
|
|
36
|
-
font-size: 14px;
|
|
37
|
-
color: rgba(28, 26, 24, 0.6);
|
|
38
|
-
line-height: 1.6;
|
|
39
|
-
}
|
|
40
|
-
.hint {
|
|
41
|
-
font-size: 12px;
|
|
42
|
-
color: rgba(28, 26, 24, 0.6);
|
|
43
|
-
margin-top: 16px;
|
|
44
|
-
}
|
|
45
|
-
</style>
|
|
46
|
-
</head>
|
|
47
|
-
<body>
|
|
48
|
-
<div class="content">
|
|
49
|
-
<div class="icon">
|
|
50
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>
|
|
51
|
-
</div>
|
|
52
|
-
<h1>Connection Failed</h1>
|
|
53
|
-
<p>Authentication timed out</p>
|
|
54
|
-
<p class="hint">Please try connecting again from your AI assistant.</p>
|
|
55
|
-
</div>
|
|
56
|
-
</body>
|
|
57
|
-
</html>
|
package/preview-success.html
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html>
|
|
3
|
-
<head>
|
|
4
|
-
<title>Circuit - Connected</title>
|
|
5
|
-
<style>
|
|
6
|
-
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
7
|
-
body {
|
|
8
|
-
font-family: 'Geist', -apple-system, BlinkMacSystemFont, system-ui, 'Segoe UI', Roboto, sans-serif;
|
|
9
|
-
background: #F5F3F0;
|
|
10
|
-
min-height: 100vh;
|
|
11
|
-
display: flex;
|
|
12
|
-
align-items: center;
|
|
13
|
-
justify-content: center;
|
|
14
|
-
color: #1C1A18;
|
|
15
|
-
padding: 24px;
|
|
16
|
-
}
|
|
17
|
-
.content {
|
|
18
|
-
text-align: center;
|
|
19
|
-
max-width: 400px;
|
|
20
|
-
}
|
|
21
|
-
.icon {
|
|
22
|
-
margin-bottom: 24px;
|
|
23
|
-
}
|
|
24
|
-
.icon svg {
|
|
25
|
-
width: 48px;
|
|
26
|
-
height: 48px;
|
|
27
|
-
color: #1C1A18;
|
|
28
|
-
}
|
|
29
|
-
h1 {
|
|
30
|
-
font-size: 24px;
|
|
31
|
-
font-weight: 600;
|
|
32
|
-
color: #1C1A18;
|
|
33
|
-
margin-bottom: 12px;
|
|
34
|
-
}
|
|
35
|
-
p {
|
|
36
|
-
font-size: 14px;
|
|
37
|
-
color: rgba(28, 26, 24, 0.6);
|
|
38
|
-
line-height: 1.6;
|
|
39
|
-
}
|
|
40
|
-
.hint {
|
|
41
|
-
font-size: 12px;
|
|
42
|
-
color: rgba(28, 26, 24, 0.6);
|
|
43
|
-
margin-top: 16px;
|
|
44
|
-
}
|
|
45
|
-
</style>
|
|
46
|
-
</head>
|
|
47
|
-
<body>
|
|
48
|
-
<div class="content">
|
|
49
|
-
<div class="icon">
|
|
50
|
-
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/><polyline points="22 4 12 14.01 9 11.01"/></svg>
|
|
51
|
-
</div>
|
|
52
|
-
<h1>Connected.</h1>
|
|
53
|
-
<p>Your AI coding assistant is now connected to Circuit.</p>
|
|
54
|
-
<p class="hint">This window will close automatically...</p>
|
|
55
|
-
</div>
|
|
56
|
-
</body>
|
|
57
|
-
</html>
|
package/publish.sh
DELETED
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
# Publish script for circuit-mcp package
|
|
4
|
-
# Uses token to bypass 2FA
|
|
5
|
-
#
|
|
6
|
-
# Usage:
|
|
7
|
-
# NPM_TOKEN=your_token ./publish.sh
|
|
8
|
-
#
|
|
9
|
-
# Or set NPM_TOKEN in your environment
|
|
10
|
-
|
|
11
|
-
set -e
|
|
12
|
-
|
|
13
|
-
# Use NPM_TOKEN from environment, or prompt if not set
|
|
14
|
-
if [ -z "$NPM_TOKEN" ]; then
|
|
15
|
-
echo "Error: NPM_TOKEN environment variable is required"
|
|
16
|
-
echo "Usage: NPM_TOKEN=your_token ./publish.sh"
|
|
17
|
-
exit 1
|
|
18
|
-
fi
|
|
19
|
-
|
|
20
|
-
# Create temporary .npmrc with token
|
|
21
|
-
echo "//registry.npmjs.org/:_authToken=$NPM_TOKEN" > .npmrc
|
|
22
|
-
|
|
23
|
-
# Publish the package
|
|
24
|
-
npm publish --access public
|
|
25
|
-
|
|
26
|
-
# Clean up .npmrc
|
|
27
|
-
rm .npmrc
|
|
28
|
-
|
|
29
|
-
echo "✅ Published successfully!"
|
|
30
|
-
|