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
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Config Detector - Auto-detect MCP servers from multiple MCP clients
|
|
4
|
+
*
|
|
5
|
+
* Supports:
|
|
6
|
+
* - Claude Desktop
|
|
7
|
+
* - Claude Code
|
|
8
|
+
* - VS Code (GitHub Copilot)
|
|
9
|
+
* - Cursor
|
|
10
|
+
* - Cline
|
|
11
|
+
* - Windsurf
|
|
12
|
+
*
|
|
13
|
+
* Powered by Prax Chat - https://prax.chat
|
|
14
|
+
*/
|
|
15
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
18
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
19
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
20
|
+
}
|
|
21
|
+
Object.defineProperty(o, k2, desc);
|
|
22
|
+
}) : (function(o, m, k, k2) {
|
|
23
|
+
if (k2 === undefined) k2 = k;
|
|
24
|
+
o[k2] = m[k];
|
|
25
|
+
}));
|
|
26
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
27
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
28
|
+
}) : function(o, v) {
|
|
29
|
+
o["default"] = v;
|
|
30
|
+
});
|
|
31
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
32
|
+
var ownKeys = function(o) {
|
|
33
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
34
|
+
var ar = [];
|
|
35
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
36
|
+
return ar;
|
|
37
|
+
};
|
|
38
|
+
return ownKeys(o);
|
|
39
|
+
};
|
|
40
|
+
return function (mod) {
|
|
41
|
+
if (mod && mod.__esModule) return mod;
|
|
42
|
+
var result = {};
|
|
43
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
44
|
+
__setModuleDefault(result, mod);
|
|
45
|
+
return result;
|
|
46
|
+
};
|
|
47
|
+
})();
|
|
48
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
49
|
+
exports.assignPorts = assignPorts;
|
|
50
|
+
exports.detectMCPServers = detectMCPServers;
|
|
51
|
+
exports.detectMCPServersFromClaude = detectMCPServersFromClaude;
|
|
52
|
+
exports.generateTwinConfig = generateTwinConfig;
|
|
53
|
+
exports.autoConfigureTwins = autoConfigureTwins;
|
|
54
|
+
const fs = __importStar(require("fs"));
|
|
55
|
+
const path = __importStar(require("path"));
|
|
56
|
+
const os = __importStar(require("os"));
|
|
57
|
+
/**
|
|
58
|
+
* Get all possible MCP config paths for different clients
|
|
59
|
+
*/
|
|
60
|
+
function getAllConfigPaths() {
|
|
61
|
+
const platform = os.platform();
|
|
62
|
+
const home = os.homedir();
|
|
63
|
+
const cwd = process.cwd();
|
|
64
|
+
const paths = [];
|
|
65
|
+
// Claude Desktop
|
|
66
|
+
if (platform === 'darwin') {
|
|
67
|
+
paths.push({
|
|
68
|
+
client: 'Claude Desktop',
|
|
69
|
+
path: path.join(home, 'Library/Application Support/Claude/claude_desktop_config.json')
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
else if (platform === 'win32') {
|
|
73
|
+
paths.push({
|
|
74
|
+
client: 'Claude Desktop',
|
|
75
|
+
path: path.join(home, 'AppData/Roaming/Claude/claude_desktop_config.json')
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
else {
|
|
79
|
+
paths.push({
|
|
80
|
+
client: 'Claude Desktop',
|
|
81
|
+
path: path.join(home, '.config/claude/claude_desktop_config.json')
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
// Claude Code
|
|
85
|
+
paths.push({
|
|
86
|
+
client: 'Claude Code',
|
|
87
|
+
path: path.join(home, '.claude/claude_desktop_config.json')
|
|
88
|
+
});
|
|
89
|
+
// VS Code - workspace
|
|
90
|
+
paths.push({
|
|
91
|
+
client: 'VS Code (workspace)',
|
|
92
|
+
path: path.join(cwd, '.vscode/mcp.json')
|
|
93
|
+
});
|
|
94
|
+
// VS Code - user settings (macOS)
|
|
95
|
+
if (platform === 'darwin') {
|
|
96
|
+
paths.push({
|
|
97
|
+
client: 'VS Code (user)',
|
|
98
|
+
path: path.join(home, 'Library/Application Support/Code/User/settings.json')
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
// Cursor - workspace
|
|
102
|
+
paths.push({
|
|
103
|
+
client: 'Cursor (workspace)',
|
|
104
|
+
path: path.join(cwd, '.cursor/mcp.json')
|
|
105
|
+
});
|
|
106
|
+
// Cursor - global
|
|
107
|
+
paths.push({
|
|
108
|
+
client: 'Cursor (global)',
|
|
109
|
+
path: path.join(home, '.cursor/mcp.json')
|
|
110
|
+
});
|
|
111
|
+
// Windsurf
|
|
112
|
+
paths.push({
|
|
113
|
+
client: 'Windsurf',
|
|
114
|
+
path: path.join(home, '.windsurf/mcp.json')
|
|
115
|
+
});
|
|
116
|
+
// Cline (VS Code extension, uses VS Code settings)
|
|
117
|
+
// Already covered by VS Code paths
|
|
118
|
+
return paths;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Get path to Claude desktop config (legacy, for backwards compatibility)
|
|
122
|
+
*/
|
|
123
|
+
function getClaudeConfigPath() {
|
|
124
|
+
const platform = os.platform();
|
|
125
|
+
if (platform === 'darwin') {
|
|
126
|
+
return path.join(os.homedir(), 'Library/Application Support/Claude/claude_desktop_config.json');
|
|
127
|
+
}
|
|
128
|
+
else if (platform === 'win32') {
|
|
129
|
+
return path.join(os.homedir(), 'AppData/Roaming/Claude/claude_desktop_config.json');
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// Linux
|
|
133
|
+
return path.join(os.homedir(), '.config/claude/claude_desktop_config.json');
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Extract script path from command args
|
|
138
|
+
*/
|
|
139
|
+
function extractScriptPath(command, args) {
|
|
140
|
+
// Common patterns:
|
|
141
|
+
// python3 /path/to/server.py
|
|
142
|
+
// node /path/to/server.js
|
|
143
|
+
// npx ts-node /path/to/server.ts
|
|
144
|
+
for (const arg of args) {
|
|
145
|
+
// Skip flags
|
|
146
|
+
if (arg.startsWith('-'))
|
|
147
|
+
continue;
|
|
148
|
+
// Check if it's a file path
|
|
149
|
+
const expanded = arg.replace('~', os.homedir());
|
|
150
|
+
if (fs.existsSync(expanded)) {
|
|
151
|
+
return expanded;
|
|
152
|
+
}
|
|
153
|
+
// Check common extensions
|
|
154
|
+
if (arg.endsWith('.py') || arg.endsWith('.js') || arg.endsWith('.ts')) {
|
|
155
|
+
return arg.replace('~', os.homedir());
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Detect language from command
|
|
162
|
+
*/
|
|
163
|
+
function detectLanguage(command) {
|
|
164
|
+
const cmd = command.toLowerCase();
|
|
165
|
+
if (cmd.includes('python') || cmd.includes('python3')) {
|
|
166
|
+
return 'python';
|
|
167
|
+
}
|
|
168
|
+
if (cmd.includes('node') || cmd.includes('npx') || cmd.includes('tsx')) {
|
|
169
|
+
return 'node';
|
|
170
|
+
}
|
|
171
|
+
return 'unknown';
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Assign deterministic ports based on server name
|
|
175
|
+
*/
|
|
176
|
+
function assignPorts(serverName) {
|
|
177
|
+
// Simple hash function
|
|
178
|
+
let hash = 0;
|
|
179
|
+
for (let i = 0; i < serverName.length; i++) {
|
|
180
|
+
const char = serverName.charCodeAt(i);
|
|
181
|
+
hash = ((hash << 5) - hash) + char;
|
|
182
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
183
|
+
}
|
|
184
|
+
// Map to port range 8100-8298 (99 server pairs)
|
|
185
|
+
const basePort = 8100 + (Math.abs(hash) % 100) * 2;
|
|
186
|
+
return [basePort, basePort + 1];
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Detect MCP servers from all supported MCP clients
|
|
190
|
+
*/
|
|
191
|
+
function detectMCPServers() {
|
|
192
|
+
const allPaths = getAllConfigPaths();
|
|
193
|
+
const servers = [];
|
|
194
|
+
const seen = new Set(); // Dedupe by server name
|
|
195
|
+
for (const { client, path: configPath } of allPaths) {
|
|
196
|
+
if (!fs.existsSync(configPath)) {
|
|
197
|
+
continue;
|
|
198
|
+
}
|
|
199
|
+
try {
|
|
200
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
201
|
+
// Support both mcpServers (Claude) and servers (VS Code/Cursor) formats
|
|
202
|
+
const mcpServers = config.mcpServers || config.servers || {};
|
|
203
|
+
for (const [name, entry] of Object.entries(mcpServers)) {
|
|
204
|
+
// Skip if already seen (first config wins)
|
|
205
|
+
if (seen.has(name))
|
|
206
|
+
continue;
|
|
207
|
+
seen.add(name);
|
|
208
|
+
const args = entry.args || [];
|
|
209
|
+
const scriptPath = extractScriptPath(entry.command, args);
|
|
210
|
+
const language = detectLanguage(entry.command);
|
|
211
|
+
servers.push({
|
|
212
|
+
name,
|
|
213
|
+
command: entry.command,
|
|
214
|
+
args,
|
|
215
|
+
scriptPath,
|
|
216
|
+
language,
|
|
217
|
+
source: client
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
catch (err) {
|
|
222
|
+
// Skip invalid configs silently
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
return servers;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Detect MCP servers from Claude config only (legacy)
|
|
230
|
+
*/
|
|
231
|
+
function detectMCPServersFromClaude() {
|
|
232
|
+
const configPath = getClaudeConfigPath();
|
|
233
|
+
if (!fs.existsSync(configPath)) {
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
try {
|
|
237
|
+
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
238
|
+
const servers = [];
|
|
239
|
+
if (!config.mcpServers) {
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
for (const [name, entry] of Object.entries(config.mcpServers)) {
|
|
243
|
+
const args = entry.args || [];
|
|
244
|
+
const scriptPath = extractScriptPath(entry.command, args);
|
|
245
|
+
const language = detectLanguage(entry.command);
|
|
246
|
+
servers.push({
|
|
247
|
+
name,
|
|
248
|
+
command: entry.command,
|
|
249
|
+
args,
|
|
250
|
+
scriptPath,
|
|
251
|
+
language,
|
|
252
|
+
source: 'Claude Desktop'
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
return servers;
|
|
256
|
+
}
|
|
257
|
+
catch (err) {
|
|
258
|
+
return [];
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
/**
|
|
262
|
+
* Generate twin config from detected servers
|
|
263
|
+
*/
|
|
264
|
+
function generateTwinConfig(servers) {
|
|
265
|
+
const config = {};
|
|
266
|
+
for (const server of servers) {
|
|
267
|
+
if (!server.scriptPath) {
|
|
268
|
+
console.log(`[ConfigDetector] Skipping ${server.name}: no script path found`);
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
const [portA, portB] = assignPorts(server.name);
|
|
272
|
+
config[server.name] = {
|
|
273
|
+
script: server.scriptPath,
|
|
274
|
+
ports: [portA, portB],
|
|
275
|
+
healthEndpoint: '/health',
|
|
276
|
+
startupTimeout: 10,
|
|
277
|
+
python: server.language === 'python' ? server.command : 'python3'
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
return config;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Auto-detect and update twin config
|
|
284
|
+
*/
|
|
285
|
+
function autoConfigureTwins() {
|
|
286
|
+
const detected = detectMCPServers();
|
|
287
|
+
const twinConfig = generateTwinConfig(detected);
|
|
288
|
+
return {
|
|
289
|
+
detected: detected.length,
|
|
290
|
+
configured: Object.keys(twinConfig).length,
|
|
291
|
+
servers: Object.keys(twinConfig)
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
// CLI test
|
|
295
|
+
if (require.main === module) {
|
|
296
|
+
console.log('Detecting MCP servers...\n');
|
|
297
|
+
const servers = detectMCPServers();
|
|
298
|
+
if (servers.length === 0) {
|
|
299
|
+
console.log('No MCP servers found in Claude config.');
|
|
300
|
+
}
|
|
301
|
+
else {
|
|
302
|
+
console.log(`Found ${servers.length} servers:\n`);
|
|
303
|
+
for (const server of servers) {
|
|
304
|
+
console.log(` ${server.name}`);
|
|
305
|
+
console.log(` Command: ${server.command}`);
|
|
306
|
+
console.log(` Script: ${server.scriptPath || '(not found)'}`);
|
|
307
|
+
console.log(` Language: ${server.language}`);
|
|
308
|
+
console.log(` Ports: ${assignPorts(server.name).join(', ')}`);
|
|
309
|
+
console.log();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
exports.default = {
|
|
314
|
+
detectMCPServers,
|
|
315
|
+
generateTwinConfig,
|
|
316
|
+
autoConfigureTwins,
|
|
317
|
+
assignPorts
|
|
318
|
+
};
|
|
319
|
+
//# sourceMappingURL=config-detector.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config-detector.js","sourceRoot":"","sources":["../src/config-detector.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;GAYG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiKH,kCAYC;AAKD,4CAyCC;AAKD,gEAkCC;AAKD,gDAqBC;AAKD,gDASC;AAxSD,uCAAyB;AACzB,2CAA6B;AAC7B,uCAAyB;AAsBzB;;GAEG;AACH,SAAS,iBAAiB;IACxB,MAAM,QAAQ,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;IAC/B,MAAM,IAAI,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC;IAC1B,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;IAE1B,MAAM,KAAK,GAAuC,EAAE,CAAC;IAErD,iBAAiB;IACjB,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,KAAK,CAAC,IAAI,CAAC;YACT,MAAM,EAAE,gBAAgB;YACxB,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,+DAA+D,CAAC;SACvF,CAAC,CAAC;IACL,CAAC;SAAM,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QAChC,KAAK,CAAC,IAAI,CAAC;YACT,MAAM,EAAE,gBAAgB;YACxB,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,mDAAmD,CAAC;SAC3E,CAAC,CAAC;IACL,CAAC;SAAM,CAAC;QACN,KAAK,CAAC,IAAI,CAAC;YACT,MAAM,EAAE,gBAAgB;YACxB,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,2CAA2C,CAAC;SACnE,CAAC,CAAC;IACL,CAAC;IAED,cAAc;IACd,KAAK,CAAC,IAAI,CAAC;QACT,MAAM,EAAE,aAAa;QACrB,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,oCAAoC,CAAC;KAC5D,CAAC,CAAC;IAEH,sBAAsB;IACtB,KAAK,CAAC,IAAI,CAAC;QACT,MAAM,EAAE,qBAAqB;QAC7B,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,kBAAkB,CAAC;KACzC,CAAC,CAAC;IAEH,kCAAkC;IAClC,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,KAAK,CAAC,IAAI,CAAC;YACT,MAAM,EAAE,gBAAgB;YACxB,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,qDAAqD,CAAC;SAC7E,CAAC,CAAC;IACL,CAAC;IAED,qBAAqB;IACrB,KAAK,CAAC,IAAI,CAAC;QACT,MAAM,EAAE,oBAAoB;QAC5B,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,kBAAkB,CAAC;KACzC,CAAC,CAAC;IAEH,kBAAkB;IAClB,KAAK,CAAC,IAAI,CAAC;QACT,MAAM,EAAE,iBAAiB;QACzB,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,kBAAkB,CAAC;KAC1C,CAAC,CAAC;IAEH,WAAW;IACX,KAAK,CAAC,IAAI,CAAC;QACT,MAAM,EAAE,UAAU;QAClB,IAAI,EAAE,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,oBAAoB,CAAC;KAC5C,CAAC,CAAC;IAEH,mDAAmD;IACnD,mCAAmC;IAEnC,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;GAEG;AACH,SAAS,mBAAmB;IAC1B,MAAM,QAAQ,GAAG,EAAE,CAAC,QAAQ,EAAE,CAAC;IAE/B,IAAI,QAAQ,KAAK,QAAQ,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,+DAA+D,CAAC,CAAC;IAClG,CAAC;SAAM,IAAI,QAAQ,KAAK,OAAO,EAAE,CAAC;QAChC,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,mDAAmD,CAAC,CAAC;IACtF,CAAC;SAAM,CAAC;QACN,QAAQ;QACR,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,2CAA2C,CAAC,CAAC;IAC9E,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAS,iBAAiB,CAAC,OAAe,EAAE,IAAc;IACxD,mBAAmB;IACnB,6BAA6B;IAC7B,0BAA0B;IAC1B,iCAAiC;IAEjC,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;QACvB,aAAa;QACb,IAAI,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAElC,4BAA4B;QAC5B,MAAM,QAAQ,GAAG,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QAChD,IAAI,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC5B,OAAO,QAAQ,CAAC;QAClB,CAAC;QAED,0BAA0B;QAC1B,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;YACtE,OAAO,GAAG,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QACxC,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;GAEG;AACH,SAAS,cAAc,CAAC,OAAe;IACrC,MAAM,GAAG,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAElC,IAAI,GAAG,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,SAAS,CAAC,EAAE,CAAC;QACtD,OAAO,QAAQ,CAAC;IAClB,CAAC;IACD,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;QACvE,OAAO,MAAM,CAAC;IAChB,CAAC;IAED,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;GAEG;AACH,SAAgB,WAAW,CAAC,UAAkB;IAC5C,uBAAuB;IACvB,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,UAAU,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QACtC,IAAI,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,IAAI,CAAC;QACnC,IAAI,GAAG,IAAI,GAAG,IAAI,CAAC,CAAC,2BAA2B;IACjD,CAAC;IAED,gDAAgD;IAChD,MAAM,QAAQ,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,GAAG,CAAC,CAAC;IACnD,OAAO,CAAC,QAAQ,EAAE,QAAQ,GAAG,CAAC,CAAC,CAAC;AAClC,CAAC;AAED;;GAEG;AACH,SAAgB,gBAAgB;IAC9B,MAAM,QAAQ,GAAG,iBAAiB,EAAE,CAAC;IACrC,MAAM,OAAO,GAAqB,EAAE,CAAC;IACrC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC,CAAE,wBAAwB;IAEzD,KAAK,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,QAAQ,EAAE,CAAC;QACpD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC/B,SAAS;QACX,CAAC;QAED,IAAI,CAAC;YACH,MAAM,MAAM,GAAc,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;YAE1E,wEAAwE;YACxE,MAAM,UAAU,GAAG,MAAM,CAAC,UAAU,IAAI,MAAM,CAAC,OAAO,IAAI,EAAE,CAAC;YAE7D,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,CAAC;gBACvD,2CAA2C;gBAC3C,IAAI,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC;oBAAE,SAAS;gBAC7B,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;gBAEf,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;gBAC9B,MAAM,UAAU,GAAG,iBAAiB,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;gBAC1D,MAAM,QAAQ,GAAG,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;gBAE/C,OAAO,CAAC,IAAI,CAAC;oBACX,IAAI;oBACJ,OAAO,EAAE,KAAK,CAAC,OAAO;oBACtB,IAAI;oBACJ,UAAU;oBACV,QAAQ;oBACR,MAAM,EAAE,MAAM;iBACf,CAAC,CAAC;YACL,CAAC;QACH,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,gCAAgC;YAChC,SAAS;QACX,CAAC;IACH,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;GAEG;AACH,SAAgB,0BAA0B;IACxC,MAAM,UAAU,GAAG,mBAAmB,EAAE,CAAC;IAEzC,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAc,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;QAC1E,MAAM,OAAO,GAAqB,EAAE,CAAC;QAErC,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YACvB,OAAO,EAAE,CAAC;QACZ,CAAC;QAED,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,UAAU,CAAC,EAAE,CAAC;YAC9D,MAAM,IAAI,GAAG,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;YAC9B,MAAM,UAAU,GAAG,iBAAiB,CAAC,KAAK,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAC1D,MAAM,QAAQ,GAAG,cAAc,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAE/C,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI;gBACJ,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,IAAI;gBACJ,UAAU;gBACV,QAAQ;gBACR,MAAM,EAAE,gBAAgB;aACzB,CAAC,CAAC;QACL,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,SAAgB,kBAAkB,CAAC,OAAyB;IAC1D,MAAM,MAAM,GAAwB,EAAE,CAAC;IAEvC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,UAAU,EAAE,CAAC;YACvB,OAAO,CAAC,GAAG,CAAC,6BAA6B,MAAM,CAAC,IAAI,wBAAwB,CAAC,CAAC;YAC9E,SAAS;QACX,CAAC;QAED,MAAM,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;QAEhD,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG;YACpB,MAAM,EAAE,MAAM,CAAC,UAAU;YACzB,KAAK,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC;YACrB,cAAc,EAAE,SAAS;YACzB,cAAc,EAAE,EAAE;YAClB,MAAM,EAAE,MAAM,CAAC,QAAQ,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,SAAS;SAClE,CAAC;IACJ,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;GAEG;AACH,SAAgB,kBAAkB;IAChC,MAAM,QAAQ,GAAG,gBAAgB,EAAE,CAAC;IACpC,MAAM,UAAU,GAAG,kBAAkB,CAAC,QAAQ,CAAC,CAAC;IAEhD,OAAO;QACL,QAAQ,EAAE,QAAQ,CAAC,MAAM;QACzB,UAAU,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM;QAC1C,OAAO,EAAE,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC;KACjC,CAAC;AACJ,CAAC;AAED,WAAW;AACX,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE,CAAC;IAC5B,OAAO,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;IAE1C,MAAM,OAAO,GAAG,gBAAgB,EAAE,CAAC;IAEnC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,wCAAwC,CAAC,CAAC;IACxD,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,GAAG,CAAC,SAAS,OAAO,CAAC,MAAM,aAAa,CAAC,CAAC;QAElD,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,OAAO,CAAC,GAAG,CAAC,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;YAChC,OAAO,CAAC,GAAG,CAAC,gBAAgB,MAAM,CAAC,OAAO,EAAE,CAAC,CAAC;YAC9C,OAAO,CAAC,GAAG,CAAC,eAAe,MAAM,CAAC,UAAU,IAAI,aAAa,EAAE,CAAC,CAAC;YACjE,OAAO,CAAC,GAAG,CAAC,iBAAiB,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;YAChD,OAAO,CAAC,GAAG,CAAC,cAAc,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACjE,OAAO,CAAC,GAAG,EAAE,CAAC;QAChB,CAAC;IACH,CAAC;AACH,CAAC;AAED,kBAAe;IACb,gBAAgB;IAChB,kBAAkB;IAClB,kBAAkB;IAClB,WAAW;CACZ,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MCP Twin Plugin - Entry Point
|
|
3
|
+
* Zero-downtime MCP server updates for Claude Code
|
|
4
|
+
*
|
|
5
|
+
* Powered by Prax - https://prax.chat
|
|
6
|
+
*/
|
|
7
|
+
import { getTwinManager, MCPTwinManager } from './twin-manager';
|
|
8
|
+
import { detectMCPServers, generateTwinConfig, autoConfigureTwins } from './config-detector';
|
|
9
|
+
export { MCPTwinManager, getTwinManager };
|
|
10
|
+
export { detectMCPServers, generateTwinConfig, autoConfigureTwins };
|
|
11
|
+
/**
|
|
12
|
+
* Plugin Commands
|
|
13
|
+
*/
|
|
14
|
+
export declare const commands: {
|
|
15
|
+
/**
|
|
16
|
+
* /twin start [server]
|
|
17
|
+
*/
|
|
18
|
+
start(serverName?: string): Promise<string>;
|
|
19
|
+
/**
|
|
20
|
+
* /twin stop [server]
|
|
21
|
+
*/
|
|
22
|
+
stop(serverName?: string): Promise<string>;
|
|
23
|
+
/**
|
|
24
|
+
* /twin reload <server>
|
|
25
|
+
*/
|
|
26
|
+
reload(serverName: string): Promise<string>;
|
|
27
|
+
/**
|
|
28
|
+
* /twin swap <server>
|
|
29
|
+
*/
|
|
30
|
+
swap(serverName: string): Promise<string>;
|
|
31
|
+
/**
|
|
32
|
+
* /twin status [server]
|
|
33
|
+
*/
|
|
34
|
+
status(serverName?: string): Promise<string>;
|
|
35
|
+
/**
|
|
36
|
+
* /twin detect - Auto-detect MCP servers
|
|
37
|
+
*/
|
|
38
|
+
detect(): Promise<string>;
|
|
39
|
+
/**
|
|
40
|
+
* /twin help
|
|
41
|
+
*/
|
|
42
|
+
help(): string;
|
|
43
|
+
};
|
|
44
|
+
/**
|
|
45
|
+
* Main command router
|
|
46
|
+
*/
|
|
47
|
+
export declare function handleCommand(args: string[]): Promise<string>;
|
|
48
|
+
declare const _default: {
|
|
49
|
+
commands: {
|
|
50
|
+
/**
|
|
51
|
+
* /twin start [server]
|
|
52
|
+
*/
|
|
53
|
+
start(serverName?: string): Promise<string>;
|
|
54
|
+
/**
|
|
55
|
+
* /twin stop [server]
|
|
56
|
+
*/
|
|
57
|
+
stop(serverName?: string): Promise<string>;
|
|
58
|
+
/**
|
|
59
|
+
* /twin reload <server>
|
|
60
|
+
*/
|
|
61
|
+
reload(serverName: string): Promise<string>;
|
|
62
|
+
/**
|
|
63
|
+
* /twin swap <server>
|
|
64
|
+
*/
|
|
65
|
+
swap(serverName: string): Promise<string>;
|
|
66
|
+
/**
|
|
67
|
+
* /twin status [server]
|
|
68
|
+
*/
|
|
69
|
+
status(serverName?: string): Promise<string>;
|
|
70
|
+
/**
|
|
71
|
+
* /twin detect - Auto-detect MCP servers
|
|
72
|
+
*/
|
|
73
|
+
detect(): Promise<string>;
|
|
74
|
+
/**
|
|
75
|
+
* /twin help
|
|
76
|
+
*/
|
|
77
|
+
help(): string;
|
|
78
|
+
};
|
|
79
|
+
handleCommand: typeof handleCommand;
|
|
80
|
+
getTwinManager: typeof getTwinManager;
|
|
81
|
+
detectMCPServers: typeof detectMCPServers;
|
|
82
|
+
};
|
|
83
|
+
export default _default;
|
|
84
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChE,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAE7F,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,CAAC;AAC1C,OAAO,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,CAAC;AAKpE;;GAEG;AACH,eAAO,MAAM,QAAQ;IACnB;;OAEG;uBACsB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAwCjD;;OAEG;sBACqB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAsChD;;OAEG;uBACsB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAuBjD;;OAEG;qBACoB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAsB/C;;OAEG;wBACuB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAoElD;;OAEG;cACa,OAAO,CAAC,MAAM,CAAC;IAmB/B;;OAEG;YACK,MAAM;CAuBf,CAAC;AAEF;;GAEG;AACH,wBAAsB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAsBnE;;;QAzRC;;WAEG;2BACsB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAwCjD;;WAEG;0BACqB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAsChD;;WAEG;2BACsB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAuBjD;;WAEG;yBACoB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAsB/C;;WAEG;4BACuB,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAoElD;;WAEG;kBACa,OAAO,CAAC,MAAM,CAAC;QAmB/B;;WAEG;gBACK,MAAM;;;;;;AAoDhB,wBAKE"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* MCP Twin Plugin - Entry Point
|
|
4
|
+
* Zero-downtime MCP server updates for Claude Code
|
|
5
|
+
*
|
|
6
|
+
* Powered by Prax - https://prax.chat
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.commands = exports.autoConfigureTwins = exports.generateTwinConfig = exports.detectMCPServers = exports.getTwinManager = exports.MCPTwinManager = void 0;
|
|
10
|
+
exports.handleCommand = handleCommand;
|
|
11
|
+
const twin_manager_1 = require("./twin-manager");
|
|
12
|
+
Object.defineProperty(exports, "getTwinManager", { enumerable: true, get: function () { return twin_manager_1.getTwinManager; } });
|
|
13
|
+
Object.defineProperty(exports, "MCPTwinManager", { enumerable: true, get: function () { return twin_manager_1.MCPTwinManager; } });
|
|
14
|
+
const config_detector_1 = require("./config-detector");
|
|
15
|
+
Object.defineProperty(exports, "detectMCPServers", { enumerable: true, get: function () { return config_detector_1.detectMCPServers; } });
|
|
16
|
+
Object.defineProperty(exports, "generateTwinConfig", { enumerable: true, get: function () { return config_detector_1.generateTwinConfig; } });
|
|
17
|
+
Object.defineProperty(exports, "autoConfigureTwins", { enumerable: true, get: function () { return config_detector_1.autoConfigureTwins; } });
|
|
18
|
+
// Branding
|
|
19
|
+
const PRAX_FOOTER = '\n─────────────────────────────────────────\n⚡ Powered by Prax Chat | prax.chat/mcp-twin';
|
|
20
|
+
/**
|
|
21
|
+
* Plugin Commands
|
|
22
|
+
*/
|
|
23
|
+
exports.commands = {
|
|
24
|
+
/**
|
|
25
|
+
* /twin start [server]
|
|
26
|
+
*/
|
|
27
|
+
async start(serverName) {
|
|
28
|
+
const manager = (0, twin_manager_1.getTwinManager)();
|
|
29
|
+
if (!serverName) {
|
|
30
|
+
// Show available servers
|
|
31
|
+
const status = await manager.status();
|
|
32
|
+
const available = status.available || [];
|
|
33
|
+
if (available.length === 0) {
|
|
34
|
+
return `No MCP servers configured.
|
|
35
|
+
|
|
36
|
+
Run auto-detect to find servers:
|
|
37
|
+
/twin detect
|
|
38
|
+
|
|
39
|
+
Or add manually:
|
|
40
|
+
/twin add <name> <script-path>`;
|
|
41
|
+
}
|
|
42
|
+
return `Available servers:
|
|
43
|
+
${available.map((s) => ` - ${s}`).join('\n')}
|
|
44
|
+
|
|
45
|
+
Start twins:
|
|
46
|
+
/twin start <server-name>`;
|
|
47
|
+
}
|
|
48
|
+
const result = await manager.startTwins(serverName);
|
|
49
|
+
if (!result.ok) {
|
|
50
|
+
return `Failed to start twins: ${result.error}`;
|
|
51
|
+
}
|
|
52
|
+
return `Started twins for ${serverName}
|
|
53
|
+
|
|
54
|
+
Server A: port ${result.pidA ? result.statusA : 'failed'} ${result.statusA === 'running' ? '●' : '○'}
|
|
55
|
+
Server B: port ${result.pidB ? result.statusB : 'failed'} ${result.statusB === 'running' ? '●' : '○'}
|
|
56
|
+
Active: A
|
|
57
|
+
|
|
58
|
+
${result.hint}${PRAX_FOOTER}`;
|
|
59
|
+
},
|
|
60
|
+
/**
|
|
61
|
+
* /twin stop [server]
|
|
62
|
+
*/
|
|
63
|
+
async stop(serverName) {
|
|
64
|
+
const manager = (0, twin_manager_1.getTwinManager)();
|
|
65
|
+
if (!serverName) {
|
|
66
|
+
return `Usage: /twin stop <server-name>
|
|
67
|
+
|
|
68
|
+
Or stop all: /twin stop --all`;
|
|
69
|
+
}
|
|
70
|
+
if (serverName === '--all') {
|
|
71
|
+
const status = await manager.status();
|
|
72
|
+
const running = Object.keys(status.twins || {});
|
|
73
|
+
if (running.length === 0) {
|
|
74
|
+
return 'No twins running.';
|
|
75
|
+
}
|
|
76
|
+
const results = [];
|
|
77
|
+
for (const name of running) {
|
|
78
|
+
const result = await manager.stopTwins(name);
|
|
79
|
+
results.push(` ${name}: ${result.ok ? 'stopped' : 'failed'}`);
|
|
80
|
+
}
|
|
81
|
+
return `Stopped all twins:\n${results.join('\n')}`;
|
|
82
|
+
}
|
|
83
|
+
const result = await manager.stopTwins(serverName);
|
|
84
|
+
if (!result.ok) {
|
|
85
|
+
return `Failed to stop: ${result.error}`;
|
|
86
|
+
}
|
|
87
|
+
return `Stopped ${serverName} twins
|
|
88
|
+
|
|
89
|
+
Server A: stopped
|
|
90
|
+
Server B: stopped`;
|
|
91
|
+
},
|
|
92
|
+
/**
|
|
93
|
+
* /twin reload <server>
|
|
94
|
+
*/
|
|
95
|
+
async reload(serverName) {
|
|
96
|
+
if (!serverName) {
|
|
97
|
+
return 'Usage: /twin reload <server-name>';
|
|
98
|
+
}
|
|
99
|
+
const manager = (0, twin_manager_1.getTwinManager)();
|
|
100
|
+
const result = await manager.reloadStandby(serverName);
|
|
101
|
+
if (!result.ok) {
|
|
102
|
+
return `Reload failed: ${result.error}
|
|
103
|
+
|
|
104
|
+
${result.hint || ''}`;
|
|
105
|
+
}
|
|
106
|
+
return `Reloaded ${serverName} standby
|
|
107
|
+
|
|
108
|
+
${result.reloaded}
|
|
109
|
+
Health: ${result.healthy ? 'passing ●' : 'FAILED ○'}
|
|
110
|
+
Reload count: ${result.reloadCount}
|
|
111
|
+
|
|
112
|
+
${result.hint}`;
|
|
113
|
+
},
|
|
114
|
+
/**
|
|
115
|
+
* /twin swap <server>
|
|
116
|
+
*/
|
|
117
|
+
async swap(serverName) {
|
|
118
|
+
if (!serverName) {
|
|
119
|
+
return 'Usage: /twin swap <server-name>';
|
|
120
|
+
}
|
|
121
|
+
const manager = (0, twin_manager_1.getTwinManager)();
|
|
122
|
+
const result = await manager.swapActive(serverName);
|
|
123
|
+
if (!result.ok) {
|
|
124
|
+
return `Swap failed: ${result.error}
|
|
125
|
+
|
|
126
|
+
${result.hint || ''}`;
|
|
127
|
+
}
|
|
128
|
+
return `Swapped ${serverName}
|
|
129
|
+
|
|
130
|
+
Previous: ${result.previousActive}
|
|
131
|
+
Now active: ${result.newActive}
|
|
132
|
+
|
|
133
|
+
${result.hint}${PRAX_FOOTER}`;
|
|
134
|
+
},
|
|
135
|
+
/**
|
|
136
|
+
* /twin status [server]
|
|
137
|
+
*/
|
|
138
|
+
async status(serverName) {
|
|
139
|
+
const manager = (0, twin_manager_1.getTwinManager)();
|
|
140
|
+
const result = await manager.status(serverName);
|
|
141
|
+
if (!result.ok) {
|
|
142
|
+
return `Error: ${result.error}`;
|
|
143
|
+
}
|
|
144
|
+
if (serverName) {
|
|
145
|
+
// Detailed single server status
|
|
146
|
+
const { serverA, serverB, active, reloadCount, lastSwap } = result;
|
|
147
|
+
return `${serverName} Twin Status
|
|
148
|
+
${'═'.repeat(40)}
|
|
149
|
+
|
|
150
|
+
Server A (port ${serverA.port})
|
|
151
|
+
State: ${serverA.state}
|
|
152
|
+
Health: ${serverA.healthy ? 'healthy ●' : 'unhealthy ○'}
|
|
153
|
+
${serverA.isActive ? '← ACTIVE' : ' standby'}
|
|
154
|
+
|
|
155
|
+
Server B (port ${serverB.port})
|
|
156
|
+
State: ${serverB.state}
|
|
157
|
+
Health: ${serverB.healthy ? 'healthy ●' : 'unhealthy ○'}
|
|
158
|
+
${serverB.isActive ? '← ACTIVE' : ' standby'}
|
|
159
|
+
|
|
160
|
+
Reloads: ${reloadCount}
|
|
161
|
+
Last swap: ${lastSwap ? new Date(lastSwap).toLocaleTimeString() : 'never'}`;
|
|
162
|
+
}
|
|
163
|
+
// All twins summary
|
|
164
|
+
const twins = result.twins || {};
|
|
165
|
+
const twinNames = Object.keys(twins);
|
|
166
|
+
if (twinNames.length === 0) {
|
|
167
|
+
const available = result.available || [];
|
|
168
|
+
return `No twins running.
|
|
169
|
+
|
|
170
|
+
Available servers:
|
|
171
|
+
${available.map((s) => ` - ${s}`).join('\n')}
|
|
172
|
+
|
|
173
|
+
Start with: /twin start <server>`;
|
|
174
|
+
}
|
|
175
|
+
let output = `MCP Twin Status
|
|
176
|
+
${'═'.repeat(40)}
|
|
177
|
+
|
|
178
|
+
`;
|
|
179
|
+
for (const [name, info] of Object.entries(twins)) {
|
|
180
|
+
const activeLabel = info.active.toUpperCase();
|
|
181
|
+
const [healthA, healthB] = info.healthy;
|
|
182
|
+
output += `${name}
|
|
183
|
+
Active: ${activeLabel} (${info.ports[info.active === 'a' ? 0 : 1]}) ${healthA || healthB ? '●' : '○'}
|
|
184
|
+
Standby: ${info.active === 'a' ? 'B' : 'A'} (${info.ports[info.active === 'a' ? 1 : 0]}) ${(info.active === 'a' ? healthB : healthA) ? '●' : '○'}
|
|
185
|
+
Reloads: ${info.reloadCount}
|
|
186
|
+
|
|
187
|
+
`;
|
|
188
|
+
}
|
|
189
|
+
const notRunning = (result.available || []).filter((s) => !twins[s]);
|
|
190
|
+
if (notRunning.length > 0) {
|
|
191
|
+
output += `Not running: ${notRunning.join(', ')}`;
|
|
192
|
+
}
|
|
193
|
+
return output;
|
|
194
|
+
},
|
|
195
|
+
/**
|
|
196
|
+
* /twin detect - Auto-detect MCP servers
|
|
197
|
+
*/
|
|
198
|
+
async detect() {
|
|
199
|
+
const result = (0, config_detector_1.autoConfigureTwins)();
|
|
200
|
+
if (result.detected === 0) {
|
|
201
|
+
return `No MCP servers found in Claude config.
|
|
202
|
+
|
|
203
|
+
Add servers manually:
|
|
204
|
+
/twin add <name> <script-path> <port1> <port2>`;
|
|
205
|
+
}
|
|
206
|
+
return `Detected ${result.detected} MCP servers:
|
|
207
|
+
${result.servers.map(s => ` - ${s}`).join('\n')}
|
|
208
|
+
|
|
209
|
+
${result.configured} configured for twins.
|
|
210
|
+
|
|
211
|
+
Start twins:
|
|
212
|
+
/twin start <server-name>`;
|
|
213
|
+
},
|
|
214
|
+
/**
|
|
215
|
+
* /twin help
|
|
216
|
+
*/
|
|
217
|
+
help() {
|
|
218
|
+
return `MCP Twin - Zero-Downtime Server Updates
|
|
219
|
+
${'═'.repeat(44)}
|
|
220
|
+
|
|
221
|
+
Commands:
|
|
222
|
+
/twin start [server] Start twin servers
|
|
223
|
+
/twin stop [server] Stop twin servers
|
|
224
|
+
/twin reload <server> Reload standby with new code
|
|
225
|
+
/twin swap <server> Switch traffic to standby
|
|
226
|
+
/twin status [server] Show twin status
|
|
227
|
+
/twin detect Auto-detect MCP servers
|
|
228
|
+
/twin help Show this help
|
|
229
|
+
|
|
230
|
+
Workflow:
|
|
231
|
+
1. /twin start my-server → Start A (active) + B (standby)
|
|
232
|
+
2. Edit your server code
|
|
233
|
+
3. /twin reload my-server → Update standby B
|
|
234
|
+
4. /twin swap my-server → Switch to B
|
|
235
|
+
5. Keep coding - no restart needed!
|
|
236
|
+
${PRAX_FOOTER}
|
|
237
|
+
|
|
238
|
+
Pro features coming soon: prax.chat/mcp-twin/pro`;
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
/**
|
|
242
|
+
* Main command router
|
|
243
|
+
*/
|
|
244
|
+
async function handleCommand(args) {
|
|
245
|
+
const [subcommand, ...rest] = args;
|
|
246
|
+
switch (subcommand?.toLowerCase()) {
|
|
247
|
+
case 'start':
|
|
248
|
+
return exports.commands.start(rest[0]);
|
|
249
|
+
case 'stop':
|
|
250
|
+
return exports.commands.stop(rest[0]);
|
|
251
|
+
case 'reload':
|
|
252
|
+
return exports.commands.reload(rest[0]);
|
|
253
|
+
case 'swap':
|
|
254
|
+
return exports.commands.swap(rest[0]);
|
|
255
|
+
case 'status':
|
|
256
|
+
return exports.commands.status(rest[0]);
|
|
257
|
+
case 'detect':
|
|
258
|
+
return exports.commands.detect();
|
|
259
|
+
case 'help':
|
|
260
|
+
case undefined:
|
|
261
|
+
return exports.commands.help();
|
|
262
|
+
default:
|
|
263
|
+
return `Unknown command: ${subcommand}\n\nRun /twin help for usage.`;
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
exports.default = {
|
|
267
|
+
commands: exports.commands,
|
|
268
|
+
handleCommand,
|
|
269
|
+
getTwinManager: twin_manager_1.getTwinManager,
|
|
270
|
+
detectMCPServers: config_detector_1.detectMCPServers
|
|
271
|
+
};
|
|
272
|
+
//# sourceMappingURL=index.js.map
|