mcp-twin 1.2.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/.claude-plugin/marketplace.json +23 -0
- package/LICENSE +21 -0
- package/PLUGIN_SPEC.md +388 -0
- package/README.md +306 -0
- package/dist/cli.d.ts +16 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +215 -0
- package/dist/cli.js.map +1 -0
- package/dist/config-detector.d.ts +53 -0
- package/dist/config-detector.d.ts.map +1 -0
- package/dist/config-detector.js +319 -0
- package/dist/config-detector.js.map +1 -0
- package/dist/index.d.ts +84 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +272 -0
- package/dist/index.js.map +1 -0
- package/dist/twin-manager.d.ts +40 -0
- package/dist/twin-manager.d.ts.map +1 -0
- package/dist/twin-manager.js +518 -0
- package/dist/twin-manager.js.map +1 -0
- package/examples/http-server.py +247 -0
- package/package.json +97 -0
- package/skills/twin.md +186 -0
- package/src/cli.ts +217 -0
- package/src/config-detector.ts +340 -0
- package/src/index.ts +309 -0
- package/src/twin-manager.ts +596 -0
- package/tsconfig.json +19 -0
package/README.md
ADDED
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
# MCP Twin
|
|
2
|
+
|
|
3
|
+
> Zero-downtime MCP server updates for **any** MCP client
|
|
4
|
+
|
|
5
|
+
**⚡ Powered by [Prax Chat](https://prax.chat)**
|
|
6
|
+
|
|
7
|
+
Works with: **Claude Desktop** · **Claude Code** · **VS Code** · **Cursor** · **Cline** · **Windsurf**
|
|
8
|
+
|
|
9
|
+
Stop restarting your AI assistant every time you change your MCP server.
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<img src="https://github.com/nothinginfinity/mcp-twin/raw/main/demo.gif" alt="MCP Twin Demo" width="600">
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
## Demo
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
┌─────────────────────────────────────────────────────────────────┐
|
|
19
|
+
│ $ /twin start inbox-collab │
|
|
20
|
+
│ │
|
|
21
|
+
│ Started twins for inbox-collab │
|
|
22
|
+
│ Server A: port 8101 ● running ← ACTIVE │
|
|
23
|
+
│ Server B: port 8102 ● running standby │
|
|
24
|
+
│ │
|
|
25
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
26
|
+
│ $ vim server.py # Edit your MCP server │
|
|
27
|
+
│ $ /twin reload inbox-collab │
|
|
28
|
+
│ │
|
|
29
|
+
│ Reloaded inbox-collab standby │
|
|
30
|
+
│ Standby (B): port 8102 │
|
|
31
|
+
│ Health: passing ● │
|
|
32
|
+
│ │
|
|
33
|
+
├─────────────────────────────────────────────────────────────────┤
|
|
34
|
+
│ $ /twin swap inbox-collab │
|
|
35
|
+
│ │
|
|
36
|
+
│ Swapped inbox-collab │
|
|
37
|
+
│ Previous: A (port 8101) │
|
|
38
|
+
│ Now active: B (port 8102) ✓ │
|
|
39
|
+
│ │
|
|
40
|
+
│ Your changes are now live! No restart needed. │
|
|
41
|
+
└─────────────────────────────────────────────────────────────────┘
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## The Problem
|
|
45
|
+
|
|
46
|
+
Every time you edit an MCP server, you have to:
|
|
47
|
+
1. Stop Claude Code
|
|
48
|
+
2. Restart the MCP server
|
|
49
|
+
3. Restart Claude Code
|
|
50
|
+
4. Lose your conversation context
|
|
51
|
+
|
|
52
|
+
Sound familiar? **MCP Twin fixes this.**
|
|
53
|
+
|
|
54
|
+
## The Solution
|
|
55
|
+
|
|
56
|
+
MCP Twin runs two instances of your MCP server simultaneously:
|
|
57
|
+
- **Server A**: Active (receiving traffic)
|
|
58
|
+
- **Server B**: Standby (ready to take over)
|
|
59
|
+
|
|
60
|
+
When you edit code:
|
|
61
|
+
1. Reload standby with new code
|
|
62
|
+
2. Swap traffic to standby
|
|
63
|
+
3. Keep working - zero downtime!
|
|
64
|
+
|
|
65
|
+
## Quick Start
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Start twins for your MCP server
|
|
69
|
+
/twin start my-server
|
|
70
|
+
|
|
71
|
+
# Edit your server code...
|
|
72
|
+
|
|
73
|
+
# Reload standby with changes
|
|
74
|
+
/twin reload my-server
|
|
75
|
+
|
|
76
|
+
# Switch traffic to new version
|
|
77
|
+
/twin swap my-server
|
|
78
|
+
|
|
79
|
+
# Done! No restart needed.
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Commands
|
|
83
|
+
|
|
84
|
+
| Command | Description |
|
|
85
|
+
|---------|-------------|
|
|
86
|
+
| `/twin start [server]` | Start twin servers |
|
|
87
|
+
| `/twin stop [server]` | Stop twin servers |
|
|
88
|
+
| `/twin reload <server>` | Reload standby with new code |
|
|
89
|
+
| `/twin swap <server>` | Switch traffic to standby |
|
|
90
|
+
| `/twin status [server]` | Show twin status |
|
|
91
|
+
| `/twin detect` | Auto-detect MCP servers |
|
|
92
|
+
| `/twin help` | Show help |
|
|
93
|
+
|
|
94
|
+
## Example Workflow
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
$ /twin start inbox-collab
|
|
98
|
+
|
|
99
|
+
Started twins for inbox-collab
|
|
100
|
+
|
|
101
|
+
Server A: port 8101 ● running
|
|
102
|
+
Server B: port 8102 ● running
|
|
103
|
+
Active: A
|
|
104
|
+
|
|
105
|
+
Edit your code, then: /twin reload inbox-collab
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
$ /twin reload inbox-collab
|
|
110
|
+
|
|
111
|
+
Reloaded inbox-collab standby
|
|
112
|
+
|
|
113
|
+
standby (b) on port 8102
|
|
114
|
+
Health: passing ●
|
|
115
|
+
Reload count: 1
|
|
116
|
+
|
|
117
|
+
Run: /twin swap inbox-collab
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
$ /twin swap inbox-collab
|
|
122
|
+
|
|
123
|
+
Swapped inbox-collab
|
|
124
|
+
|
|
125
|
+
Previous: a (port 8101)
|
|
126
|
+
Now active: b (port 8102)
|
|
127
|
+
|
|
128
|
+
Your changes are now live!
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Auto-Detection
|
|
132
|
+
|
|
133
|
+
MCP Twin automatically detects servers from your Claude config:
|
|
134
|
+
|
|
135
|
+
```
|
|
136
|
+
$ /twin detect
|
|
137
|
+
|
|
138
|
+
Detected 3 MCP servers:
|
|
139
|
+
- inbox-collab
|
|
140
|
+
- phi-proxy
|
|
141
|
+
- zti-server
|
|
142
|
+
|
|
143
|
+
3 configured for twins.
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Installation
|
|
147
|
+
|
|
148
|
+
### Universal CLI (any client)
|
|
149
|
+
```bash
|
|
150
|
+
# Install globally
|
|
151
|
+
npm install -g mcp-twin
|
|
152
|
+
|
|
153
|
+
# Or run directly with npx
|
|
154
|
+
npx mcp-twin start my-server
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Claude Code Plugin
|
|
158
|
+
```
|
|
159
|
+
/install mcp-twin
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### From Source
|
|
163
|
+
```bash
|
|
164
|
+
git clone https://github.com/nothinginfinity/mcp-twin
|
|
165
|
+
cd mcp-twin
|
|
166
|
+
npm install
|
|
167
|
+
npm run build
|
|
168
|
+
npm link # Makes 'mcp-twin' command available globally
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
## Supported Clients
|
|
172
|
+
|
|
173
|
+
MCP Twin auto-detects servers from all major MCP clients:
|
|
174
|
+
|
|
175
|
+
| Client | Config Location |
|
|
176
|
+
|--------|-----------------|
|
|
177
|
+
| Claude Desktop | `~/Library/Application Support/Claude/` |
|
|
178
|
+
| Claude Code | `~/.claude/` |
|
|
179
|
+
| VS Code | `.vscode/mcp.json` |
|
|
180
|
+
| Cursor | `~/.cursor/mcp.json` |
|
|
181
|
+
| Windsurf | `~/.windsurf/mcp.json` |
|
|
182
|
+
| Cline | VS Code settings |
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
# Auto-detect all configured servers
|
|
186
|
+
mcp-twin detect
|
|
187
|
+
|
|
188
|
+
# Detected 3 MCP servers:
|
|
189
|
+
# - inbox-collab (from Claude Code)
|
|
190
|
+
# - my-api (from Cursor)
|
|
191
|
+
# - data-tools (from VS Code)
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## Configuration
|
|
195
|
+
|
|
196
|
+
Config stored at `~/.mcp-twin/config.json`:
|
|
197
|
+
|
|
198
|
+
```json
|
|
199
|
+
{
|
|
200
|
+
"servers": {
|
|
201
|
+
"my-server": {
|
|
202
|
+
"script": "/path/to/server.py",
|
|
203
|
+
"ports": [8101, 8102],
|
|
204
|
+
"python": "python3"
|
|
205
|
+
}
|
|
206
|
+
},
|
|
207
|
+
"settings": {
|
|
208
|
+
"startupWait": 2000,
|
|
209
|
+
"shutdownTimeout": 5000
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## How It Works
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
┌─────────────────────────────────────────┐
|
|
218
|
+
│ Claude Code │
|
|
219
|
+
│ │ │
|
|
220
|
+
│ ▼ │
|
|
221
|
+
│ ┌─────────────────┐ │
|
|
222
|
+
│ │ MCP Client │ │
|
|
223
|
+
│ └────────┬────────┘ │
|
|
224
|
+
│ │ │
|
|
225
|
+
│ ┌─────┴─────┐ │
|
|
226
|
+
│ ▼ ▼ │
|
|
227
|
+
│ ┌───────┐ ┌───────┐ │
|
|
228
|
+
│ │ MCP-A │ │ MCP-B │ │
|
|
229
|
+
│ │:8101 │ │:8102 │ │
|
|
230
|
+
│ │ACTIVE │ │STANDBY│ │
|
|
231
|
+
│ └───────┘ └───────┘ │
|
|
232
|
+
│ │
|
|
233
|
+
└─────────────────────────────────────────┘
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
1. **Start**: Both A and B running, A is active
|
|
237
|
+
2. **Edit**: Change your server code
|
|
238
|
+
3. **Reload**: Stop B, start B with new code
|
|
239
|
+
4. **Swap**: Route traffic to B, A becomes standby
|
|
240
|
+
5. **Repeat**: Next edit reloads A, swap back
|
|
241
|
+
|
|
242
|
+
## Server Requirements
|
|
243
|
+
|
|
244
|
+
Your MCP server must support HTTP mode with these flags:
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
python3 server.py --http --port 8101
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
The server should expose:
|
|
251
|
+
- `POST /mcp` - JSON-RPC 2.0 endpoint for MCP protocol
|
|
252
|
+
- `GET /health` - Health check endpoint (returns 200 OK)
|
|
253
|
+
|
|
254
|
+
### Adding HTTP Mode to Your Server
|
|
255
|
+
|
|
256
|
+
Add this to your MCP server's main block:
|
|
257
|
+
|
|
258
|
+
```python
|
|
259
|
+
if __name__ == "__main__":
|
|
260
|
+
import argparse
|
|
261
|
+
|
|
262
|
+
parser = argparse.ArgumentParser()
|
|
263
|
+
parser.add_argument("--http", action="store_true", help="Run as HTTP server")
|
|
264
|
+
parser.add_argument("--port", type=int, default=8101, help="HTTP port")
|
|
265
|
+
args = parser.parse_args()
|
|
266
|
+
|
|
267
|
+
if args.http:
|
|
268
|
+
run_http_server(args.port) # Your HTTP server implementation
|
|
269
|
+
else:
|
|
270
|
+
main() # Original stdio mode
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
See the [example implementation](examples/http-server.py) for a complete template.
|
|
274
|
+
|
|
275
|
+
## Pro Features (Coming Soon)
|
|
276
|
+
|
|
277
|
+
**[Prax Chat Pro](https://prax.chat/mcp-twin/pro)** - Enterprise MCP management
|
|
278
|
+
|
|
279
|
+
| Feature | Free | Pro |
|
|
280
|
+
|---------|------|-----|
|
|
281
|
+
| Twin servers | ✅ | ✅ |
|
|
282
|
+
| Manual reload/swap | ✅ | ✅ |
|
|
283
|
+
| Auto-reload on save | ❌ | ✅ |
|
|
284
|
+
| Health dashboard | ❌ | ✅ |
|
|
285
|
+
| Rollback history | ❌ | ✅ |
|
|
286
|
+
| Slack/Discord alerts | ❌ | ✅ |
|
|
287
|
+
| Team sharing | ❌ | ✅ |
|
|
288
|
+
| Priority support | ❌ | ✅ |
|
|
289
|
+
|
|
290
|
+
[Join the waitlist →](https://prax.chat/mcp-twin/pro)
|
|
291
|
+
|
|
292
|
+
## License
|
|
293
|
+
|
|
294
|
+
MIT
|
|
295
|
+
|
|
296
|
+
## Credits
|
|
297
|
+
|
|
298
|
+
Built with ❤️ by [Prax Chat](https://prax.chat)
|
|
299
|
+
|
|
300
|
+
---
|
|
301
|
+
|
|
302
|
+
<p align="center">
|
|
303
|
+
<a href="https://prax.chat">
|
|
304
|
+
<img src="https://img.shields.io/badge/Powered%20by-Prax%20Chat-blue?style=for-the-badge" alt="Powered by Prax Chat">
|
|
305
|
+
</a>
|
|
306
|
+
</p>
|
package/dist/cli.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* MCP Twin CLI - Universal zero-downtime MCP server management
|
|
4
|
+
*
|
|
5
|
+
* Works with ANY MCP client: Claude Desktop, VS Code, Cursor, Cline, etc.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npx mcp-twin start my-server
|
|
9
|
+
* npx mcp-twin reload my-server
|
|
10
|
+
* npx mcp-twin swap my-server
|
|
11
|
+
* npx mcp-twin status
|
|
12
|
+
*
|
|
13
|
+
* Powered by Prax Chat - https://prax.chat
|
|
14
|
+
*/
|
|
15
|
+
export {};
|
|
16
|
+
//# sourceMappingURL=cli.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG"}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
/**
|
|
4
|
+
* MCP Twin CLI - Universal zero-downtime MCP server management
|
|
5
|
+
*
|
|
6
|
+
* Works with ANY MCP client: Claude Desktop, VS Code, Cursor, Cline, etc.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* npx mcp-twin start my-server
|
|
10
|
+
* npx mcp-twin reload my-server
|
|
11
|
+
* npx mcp-twin swap my-server
|
|
12
|
+
* npx mcp-twin status
|
|
13
|
+
*
|
|
14
|
+
* Powered by Prax Chat - https://prax.chat
|
|
15
|
+
*/
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
const twin_manager_1 = require("./twin-manager");
|
|
18
|
+
const config_detector_1 = require("./config-detector");
|
|
19
|
+
const PRAX_FOOTER = '\n─────────────────────────────────────────\n⚡ Powered by Prax Chat | prax.chat/mcp-twin';
|
|
20
|
+
const VERSION = '1.2.0';
|
|
21
|
+
async function main() {
|
|
22
|
+
const args = process.argv.slice(2);
|
|
23
|
+
const command = args[0]?.toLowerCase();
|
|
24
|
+
const serverName = args[1];
|
|
25
|
+
const manager = (0, twin_manager_1.getTwinManager)();
|
|
26
|
+
switch (command) {
|
|
27
|
+
case 'start': {
|
|
28
|
+
if (!serverName) {
|
|
29
|
+
const status = await manager.status();
|
|
30
|
+
console.log('Available servers:');
|
|
31
|
+
(status.available || []).forEach((s) => console.log(` - ${s}`));
|
|
32
|
+
console.log('\nUsage: mcp-twin start <server-name>');
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
const result = await manager.startTwins(serverName);
|
|
36
|
+
if (result.ok) {
|
|
37
|
+
console.log(`✓ Started twins for ${serverName}`);
|
|
38
|
+
console.log(` Server A: port ${result.portA} (${result.statusA})`);
|
|
39
|
+
console.log(` Server B: port ${result.portB} (${result.statusB})`);
|
|
40
|
+
console.log(` Active: A`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
console.error(`✗ Failed: ${result.error}`);
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
console.log(PRAX_FOOTER);
|
|
47
|
+
break;
|
|
48
|
+
}
|
|
49
|
+
case 'stop': {
|
|
50
|
+
if (!serverName) {
|
|
51
|
+
console.log('Usage: mcp-twin stop <server-name>');
|
|
52
|
+
console.log(' mcp-twin stop --all');
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
if (serverName === '--all') {
|
|
56
|
+
const status = await manager.status();
|
|
57
|
+
for (const name of Object.keys(status.twins || {})) {
|
|
58
|
+
await manager.stopTwins(name);
|
|
59
|
+
console.log(`✓ Stopped ${name}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
else {
|
|
63
|
+
const result = await manager.stopTwins(serverName);
|
|
64
|
+
if (result.ok) {
|
|
65
|
+
console.log(`✓ Stopped ${serverName} twins`);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
console.error(`✗ Failed: ${result.error}`);
|
|
69
|
+
process.exit(1);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case 'reload': {
|
|
75
|
+
if (!serverName) {
|
|
76
|
+
console.log('Usage: mcp-twin reload <server-name>');
|
|
77
|
+
break;
|
|
78
|
+
}
|
|
79
|
+
console.log(`Reloading ${serverName} standby...`);
|
|
80
|
+
const result = await manager.reloadStandby(serverName);
|
|
81
|
+
if (result.ok) {
|
|
82
|
+
console.log(`✓ Reloaded ${result.reloaded}`);
|
|
83
|
+
console.log(` Health: ${result.healthy ? 'passing ●' : 'FAILED ○'}`);
|
|
84
|
+
console.log(` Reload count: ${result.reloadCount}`);
|
|
85
|
+
if (result.healthy) {
|
|
86
|
+
console.log(`\nReady to swap: mcp-twin swap ${serverName}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
console.error(`✗ Failed: ${result.error}`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
console.log(PRAX_FOOTER);
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
case 'swap': {
|
|
97
|
+
if (!serverName) {
|
|
98
|
+
console.log('Usage: mcp-twin swap <server-name>');
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
const result = await manager.swapActive(serverName);
|
|
102
|
+
if (result.ok) {
|
|
103
|
+
console.log(`✓ Swapped ${serverName}`);
|
|
104
|
+
console.log(` Previous: ${result.previousActive}`);
|
|
105
|
+
console.log(` Now active: ${result.newActive}`);
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
console.error(`✗ Failed: ${result.error}`);
|
|
109
|
+
if (result.hint)
|
|
110
|
+
console.log(` Hint: ${result.hint}`);
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
console.log(PRAX_FOOTER);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
case 'status': {
|
|
117
|
+
const result = await manager.status(serverName);
|
|
118
|
+
if (serverName && result.ok) {
|
|
119
|
+
console.log(`${serverName} Twin Status`);
|
|
120
|
+
console.log('═'.repeat(40));
|
|
121
|
+
console.log(`\nServer A (port ${result.serverA.port})`);
|
|
122
|
+
console.log(` State: ${result.serverA.state}`);
|
|
123
|
+
console.log(` Health: ${result.serverA.healthy ? 'healthy ●' : 'unhealthy ○'}`);
|
|
124
|
+
console.log(` ${result.serverA.isActive ? '← ACTIVE' : ' standby'}`);
|
|
125
|
+
console.log(`\nServer B (port ${result.serverB.port})`);
|
|
126
|
+
console.log(` State: ${result.serverB.state}`);
|
|
127
|
+
console.log(` Health: ${result.serverB.healthy ? 'healthy ●' : 'unhealthy ○'}`);
|
|
128
|
+
console.log(` ${result.serverB.isActive ? '← ACTIVE' : ' standby'}`);
|
|
129
|
+
console.log(`\nReloads: ${result.reloadCount}`);
|
|
130
|
+
}
|
|
131
|
+
else if (result.ok) {
|
|
132
|
+
console.log('MCP Twin Status');
|
|
133
|
+
console.log('═'.repeat(40));
|
|
134
|
+
const twins = result.twins || {};
|
|
135
|
+
if (Object.keys(twins).length === 0) {
|
|
136
|
+
console.log('\nNo twins running.');
|
|
137
|
+
console.log('\nAvailable servers:');
|
|
138
|
+
(result.available || []).forEach((s) => console.log(` - ${s}`));
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
for (const [name, info] of Object.entries(twins)) {
|
|
142
|
+
const activePort = info.ports[info.active === 'a' ? 0 : 1];
|
|
143
|
+
console.log(`\n${name}`);
|
|
144
|
+
console.log(` Active: ${info.active.toUpperCase()} (port ${activePort}) ${info.healthy[info.active === 'a' ? 0 : 1] ? '●' : '○'}`);
|
|
145
|
+
console.log(` Reloads: ${info.reloadCount}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
case 'detect': {
|
|
152
|
+
console.log('Detecting MCP servers...');
|
|
153
|
+
const result = (0, config_detector_1.autoConfigureTwins)();
|
|
154
|
+
if (result.detected === 0) {
|
|
155
|
+
console.log('No MCP servers found.');
|
|
156
|
+
console.log('\nSearched:');
|
|
157
|
+
console.log(' - Claude Desktop: ~/Library/Application Support/Claude/');
|
|
158
|
+
console.log(' - Claude Code: ~/.claude/');
|
|
159
|
+
console.log(' - VS Code: .vscode/mcp.json');
|
|
160
|
+
console.log(' - Cursor: ~/.cursor/mcp.json');
|
|
161
|
+
}
|
|
162
|
+
else {
|
|
163
|
+
console.log(`✓ Detected ${result.detected} MCP servers:`);
|
|
164
|
+
result.servers.forEach((s) => console.log(` - ${s}`));
|
|
165
|
+
console.log(`\n${result.configured} configured for twins.`);
|
|
166
|
+
}
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
case 'version':
|
|
170
|
+
case '-v':
|
|
171
|
+
case '--version': {
|
|
172
|
+
console.log(`MCP Twin v${VERSION}`);
|
|
173
|
+
console.log('Powered by Prax Chat - https://prax.chat');
|
|
174
|
+
break;
|
|
175
|
+
}
|
|
176
|
+
case 'help':
|
|
177
|
+
case '-h':
|
|
178
|
+
case '--help':
|
|
179
|
+
default: {
|
|
180
|
+
console.log(`
|
|
181
|
+
MCP Twin v${VERSION} - Zero-Downtime MCP Server Updates
|
|
182
|
+
${'═'.repeat(50)}
|
|
183
|
+
|
|
184
|
+
Works with: Claude Desktop, VS Code, Cursor, Cline, Windsurf
|
|
185
|
+
|
|
186
|
+
Commands:
|
|
187
|
+
mcp-twin start <server> Start twin servers
|
|
188
|
+
mcp-twin stop <server> Stop twin servers (--all for all)
|
|
189
|
+
mcp-twin reload <server> Reload standby with new code
|
|
190
|
+
mcp-twin swap <server> Switch traffic to standby
|
|
191
|
+
mcp-twin status [server] Show twin status
|
|
192
|
+
mcp-twin detect Auto-detect MCP servers
|
|
193
|
+
mcp-twin help Show this help
|
|
194
|
+
|
|
195
|
+
Workflow:
|
|
196
|
+
1. mcp-twin start my-server → Start A (active) + B (standby)
|
|
197
|
+
2. Edit your server code
|
|
198
|
+
3. mcp-twin reload my-server → Update standby B
|
|
199
|
+
4. mcp-twin swap my-server → Switch to B (zero downtime!)
|
|
200
|
+
|
|
201
|
+
Config: ~/.mcp-twin/config.json
|
|
202
|
+
Logs: ~/.mcp-twin/logs/
|
|
203
|
+
${PRAX_FOOTER}
|
|
204
|
+
|
|
205
|
+
Pro features: prax.chat/mcp-twin/pro
|
|
206
|
+
`);
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
main().catch(err => {
|
|
212
|
+
console.error('Error:', err.message);
|
|
213
|
+
process.exit(1);
|
|
214
|
+
});
|
|
215
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config Detector - Auto-detect MCP servers from multiple MCP clients
|
|
3
|
+
*
|
|
4
|
+
* Supports:
|
|
5
|
+
* - Claude Desktop
|
|
6
|
+
* - Claude Code
|
|
7
|
+
* - VS Code (GitHub Copilot)
|
|
8
|
+
* - Cursor
|
|
9
|
+
* - Cline
|
|
10
|
+
* - Windsurf
|
|
11
|
+
*
|
|
12
|
+
* Powered by Prax Chat - https://prax.chat
|
|
13
|
+
*/
|
|
14
|
+
export interface DetectedServer {
|
|
15
|
+
name: string;
|
|
16
|
+
command: string;
|
|
17
|
+
args: string[];
|
|
18
|
+
scriptPath: string | null;
|
|
19
|
+
language: 'python' | 'node' | 'unknown';
|
|
20
|
+
source: string;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Assign deterministic ports based on server name
|
|
24
|
+
*/
|
|
25
|
+
export declare function assignPorts(serverName: string): [number, number];
|
|
26
|
+
/**
|
|
27
|
+
* Detect MCP servers from all supported MCP clients
|
|
28
|
+
*/
|
|
29
|
+
export declare function detectMCPServers(): DetectedServer[];
|
|
30
|
+
/**
|
|
31
|
+
* Detect MCP servers from Claude config only (legacy)
|
|
32
|
+
*/
|
|
33
|
+
export declare function detectMCPServersFromClaude(): DetectedServer[];
|
|
34
|
+
/**
|
|
35
|
+
* Generate twin config from detected servers
|
|
36
|
+
*/
|
|
37
|
+
export declare function generateTwinConfig(servers: DetectedServer[]): Record<string, any>;
|
|
38
|
+
/**
|
|
39
|
+
* Auto-detect and update twin config
|
|
40
|
+
*/
|
|
41
|
+
export declare function autoConfigureTwins(): {
|
|
42
|
+
detected: number;
|
|
43
|
+
configured: number;
|
|
44
|
+
servers: string[];
|
|
45
|
+
};
|
|
46
|
+
declare const _default: {
|
|
47
|
+
detectMCPServers: typeof detectMCPServers;
|
|
48
|
+
generateTwinConfig: typeof generateTwinConfig;
|
|
49
|
+
autoConfigureTwins: typeof autoConfigureTwins;
|
|
50
|
+
assignPorts: typeof assignPorts;
|
|
51
|
+
};
|
|
52
|
+
export default _default;
|
|
53
|
+
//# sourceMappingURL=config-detector.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-detector.d.ts","sourceRoot":"","sources":["../src/config-detector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAiBH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;IACf,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,QAAQ,GAAG,MAAM,GAAG,SAAS,CAAC;IACxC,MAAM,EAAE,MAAM,CAAC;CAChB;AAsID;;GAEG;AACH,wBAAgB,WAAW,CAAC,UAAU,EAAE,MAAM,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAYhE;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,cAAc,EAAE,CAyCnD;AAED;;GAEG;AACH,wBAAgB,0BAA0B,IAAI,cAAc,EAAE,CAkC7D;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,cAAc,EAAE,GAAG,MAAM,CAAC,MAAM,EAAE,GAAG,CAAC,CAqBjF;AAED;;GAEG;AACH,wBAAgB,kBAAkB,IAAI;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAShG;;;;;;;AAwBD,wBAKE"}
|