@daeda/mcp-pro 0.1.19 → 0.1.21
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/dist/index.js +150 -27
- package/dist/supervisor.js +195 -0
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -3320,6 +3320,8 @@ if (!LICENSE_KEY) {
|
|
|
3320
3320
|
process.exit(1);
|
|
3321
3321
|
}
|
|
3322
3322
|
var WS_URL = process.env.MCP_CLIENT_WEBSOCKET_URL ?? process.env.websocket_url ?? "wss://mcp-ws.daeda.tech";
|
|
3323
|
+
var PING_INTERVAL_MS = 15e3;
|
|
3324
|
+
var PONG_TIMEOUT_MS = 1e4;
|
|
3323
3325
|
var WebSocketLive = Layer5.effect(
|
|
3324
3326
|
WebSocketService,
|
|
3325
3327
|
Effect40.gen(function* () {
|
|
@@ -3333,6 +3335,9 @@ var WebSocketLive = Layer5.effect(
|
|
|
3333
3335
|
const pendingPlanListRef = yield* Ref14.make(null);
|
|
3334
3336
|
const handlersRef = yield* Ref14.make(null);
|
|
3335
3337
|
const encryptionKeyRef = yield* Ref14.make(null);
|
|
3338
|
+
let pingInterval = null;
|
|
3339
|
+
let pongTimeout = null;
|
|
3340
|
+
let awaitingPong = false;
|
|
3336
3341
|
const requireWs = pipe33(
|
|
3337
3342
|
Ref14.get(wsRef),
|
|
3338
3343
|
Effect40.flatMap(
|
|
@@ -3366,26 +3371,95 @@ var WebSocketLive = Layer5.effect(
|
|
|
3366
3371
|
Match.when({ type: "plan:list:response" }, (m) => handlePlanListResponse(m.payload, ctx)),
|
|
3367
3372
|
Match.orElse(() => Effect40.void)
|
|
3368
3373
|
);
|
|
3374
|
+
const stopPing = () => {
|
|
3375
|
+
if (pingInterval) {
|
|
3376
|
+
clearInterval(pingInterval);
|
|
3377
|
+
pingInterval = null;
|
|
3378
|
+
}
|
|
3379
|
+
if (pongTimeout) {
|
|
3380
|
+
clearTimeout(pongTimeout);
|
|
3381
|
+
pongTimeout = null;
|
|
3382
|
+
}
|
|
3383
|
+
awaitingPong = false;
|
|
3384
|
+
};
|
|
3385
|
+
const startPing = (ws) => {
|
|
3386
|
+
stopPing();
|
|
3387
|
+
pingInterval = setInterval(() => {
|
|
3388
|
+
if (awaitingPong) {
|
|
3389
|
+
console.error("[ws:ping] Pong not received in time \u2014 closing stale connection");
|
|
3390
|
+
stopPing();
|
|
3391
|
+
try {
|
|
3392
|
+
ws.close();
|
|
3393
|
+
} catch {
|
|
3394
|
+
}
|
|
3395
|
+
return;
|
|
3396
|
+
}
|
|
3397
|
+
try {
|
|
3398
|
+
ws.send(JSON.stringify({ type: "ping", payload: { timestamp: Date.now() } }));
|
|
3399
|
+
awaitingPong = true;
|
|
3400
|
+
pongTimeout = setTimeout(() => {
|
|
3401
|
+
if (awaitingPong) {
|
|
3402
|
+
console.error("[ws:ping] Pong timeout \u2014 closing stale connection");
|
|
3403
|
+
stopPing();
|
|
3404
|
+
try {
|
|
3405
|
+
ws.close();
|
|
3406
|
+
} catch {
|
|
3407
|
+
}
|
|
3408
|
+
}
|
|
3409
|
+
}, PONG_TIMEOUT_MS);
|
|
3410
|
+
} catch {
|
|
3411
|
+
console.error("[ws:ping] Failed to send ping \u2014 connection may be dead");
|
|
3412
|
+
stopPing();
|
|
3413
|
+
try {
|
|
3414
|
+
ws.close();
|
|
3415
|
+
} catch {
|
|
3416
|
+
}
|
|
3417
|
+
}
|
|
3418
|
+
}, PING_INTERVAL_MS);
|
|
3419
|
+
};
|
|
3369
3420
|
const wireMessageHandler = (ws) => Effect40.sync(() => {
|
|
3370
3421
|
ws.onmessage = (event) => {
|
|
3422
|
+
let parsed;
|
|
3423
|
+
try {
|
|
3424
|
+
parsed = JSON.parse(event.data);
|
|
3425
|
+
} catch (err) {
|
|
3426
|
+
console.error("[ws:message] Failed to parse message:", err);
|
|
3427
|
+
return;
|
|
3428
|
+
}
|
|
3429
|
+
if (parsed.type === "pong") {
|
|
3430
|
+
awaitingPong = false;
|
|
3431
|
+
if (pongTimeout) {
|
|
3432
|
+
clearTimeout(pongTimeout);
|
|
3433
|
+
pongTimeout = null;
|
|
3434
|
+
}
|
|
3435
|
+
return;
|
|
3436
|
+
}
|
|
3371
3437
|
console.error("Received:", event.data);
|
|
3372
3438
|
pipe33(
|
|
3373
|
-
Effect40.
|
|
3374
|
-
try: () => JSON.parse(event.data),
|
|
3375
|
-
catch: (error) => new WebSocketError({ url: WS_URL, cause: error })
|
|
3376
|
-
}),
|
|
3439
|
+
Effect40.succeed(parsed),
|
|
3377
3440
|
Effect40.flatMap(routeMessage),
|
|
3378
3441
|
Effect40.catchAll((err) => Effect40.sync(() => console.error("[ws:message] Error handling message:", err))),
|
|
3379
3442
|
Effect40.runFork
|
|
3380
3443
|
);
|
|
3381
3444
|
};
|
|
3382
3445
|
});
|
|
3446
|
+
const rejectAllPending = () => {
|
|
3447
|
+
const closeError = new WebSocketError({ url: WS_URL, cause: new Error("WebSocket closed") });
|
|
3448
|
+
const pendingArtifact = Effect40.runSync(Ref14.getAndSet(pendingArtifactRef, null));
|
|
3449
|
+
if (pendingArtifact) pendingArtifact.reject(closeError);
|
|
3450
|
+
const pendingPlanCreate = Effect40.runSync(Ref14.getAndSet(pendingPlanCreateRef, null));
|
|
3451
|
+
if (pendingPlanCreate) pendingPlanCreate.reject(closeError);
|
|
3452
|
+
const pendingPlanList = Effect40.runSync(Ref14.getAndSet(pendingPlanListRef, null));
|
|
3453
|
+
if (pendingPlanList) pendingPlanList.reject(closeError);
|
|
3454
|
+
const pendingPlugins = Effect40.runSync(Ref14.getAndSet(pendingPluginRequestsRef, /* @__PURE__ */ new Map()));
|
|
3455
|
+
for (const [, pending] of pendingPlugins) {
|
|
3456
|
+
pending.reject(closeError);
|
|
3457
|
+
}
|
|
3458
|
+
};
|
|
3383
3459
|
const wireCloseHandler = (ws) => Effect40.sync(() => {
|
|
3384
3460
|
ws.onclose = () => {
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
pending.reject(new WebSocketError({ url: WS_URL, cause: new Error("WebSocket closed") }));
|
|
3388
|
-
}
|
|
3461
|
+
stopPing();
|
|
3462
|
+
rejectAllPending();
|
|
3389
3463
|
pipe33(
|
|
3390
3464
|
Ref14.set(stateRef, "disconnected"),
|
|
3391
3465
|
Effect40.flatMap(() => Ref14.set(wsRef, null)),
|
|
@@ -3403,6 +3477,7 @@ var WebSocketLive = Layer5.effect(
|
|
|
3403
3477
|
(ws) => Effect40.async((resume) => {
|
|
3404
3478
|
ws.onopen = () => {
|
|
3405
3479
|
console.error(`[ws] Connected to ${WS_URL}, sending register`);
|
|
3480
|
+
startPing(ws);
|
|
3406
3481
|
pipe33(
|
|
3407
3482
|
Ref14.set(stateRef, "connected"),
|
|
3408
3483
|
Effect40.flatMap(() => Ref14.set(isReconnectingRef, false)),
|
|
@@ -3478,7 +3553,12 @@ var WebSocketLive = Layer5.effect(
|
|
|
3478
3553
|
reject: (error) => resume(Effect40.fail(error))
|
|
3479
3554
|
};
|
|
3480
3555
|
Effect40.runSync(Ref14.set(pendingArtifactRef, pending));
|
|
3481
|
-
|
|
3556
|
+
try {
|
|
3557
|
+
ws.send(JSON.stringify({ type: "sync:artifact", payload: { artifact_id: artifactId } }));
|
|
3558
|
+
} catch (err) {
|
|
3559
|
+
Effect40.runSync(Ref14.set(pendingArtifactRef, null));
|
|
3560
|
+
resume(Effect40.fail(new WebSocketError({ url: WS_URL, cause: err })));
|
|
3561
|
+
}
|
|
3482
3562
|
})
|
|
3483
3563
|
),
|
|
3484
3564
|
Effect40.timeoutFail({
|
|
@@ -3500,14 +3580,21 @@ var WebSocketLive = Layer5.effect(
|
|
|
3500
3580
|
const next = new Map(pendingMap);
|
|
3501
3581
|
next.set(requestId, pending);
|
|
3502
3582
|
Effect40.runSync(Ref14.set(pendingPluginRequestsRef, next));
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3506
|
-
|
|
3507
|
-
|
|
3508
|
-
|
|
3509
|
-
|
|
3510
|
-
|
|
3583
|
+
try {
|
|
3584
|
+
ws.send(JSON.stringify({
|
|
3585
|
+
type: "sync:plugin-data",
|
|
3586
|
+
payload: {
|
|
3587
|
+
target_portal: portalId,
|
|
3588
|
+
plugin_name: pluginName,
|
|
3589
|
+
request_id: requestId
|
|
3590
|
+
}
|
|
3591
|
+
}));
|
|
3592
|
+
} catch (err) {
|
|
3593
|
+
const cleanup = new Map(Effect40.runSync(Ref14.get(pendingPluginRequestsRef)));
|
|
3594
|
+
cleanup.delete(requestId);
|
|
3595
|
+
Effect40.runSync(Ref14.set(pendingPluginRequestsRef, cleanup));
|
|
3596
|
+
resume(Effect40.fail(new WebSocketError({ url: WS_URL, cause: err })));
|
|
3597
|
+
}
|
|
3511
3598
|
}),
|
|
3512
3599
|
Effect40.timeoutFail({
|
|
3513
3600
|
duration: Duration.seconds(timeoutSeconds ?? 30),
|
|
@@ -3535,11 +3622,16 @@ var WebSocketLive = Layer5.effect(
|
|
|
3535
3622
|
reject: (error) => resume(Effect40.fail(error))
|
|
3536
3623
|
};
|
|
3537
3624
|
Effect40.runSync(Ref14.set(pendingPlanCreateRef, pending));
|
|
3538
|
-
|
|
3625
|
+
try {
|
|
3626
|
+
ws.send(JSON.stringify({ type: "plan:create", payload: { target_portal: portalId, ...plan.dry_run ? { dry_run: true } : {}, plan } }));
|
|
3627
|
+
} catch (err) {
|
|
3628
|
+
Effect40.runSync(Ref14.set(pendingPlanCreateRef, null));
|
|
3629
|
+
resume(Effect40.fail(new WebSocketError({ url: WS_URL, cause: err })));
|
|
3630
|
+
}
|
|
3539
3631
|
})
|
|
3540
3632
|
),
|
|
3541
3633
|
Effect40.timeoutFail({
|
|
3542
|
-
duration: Duration.seconds(
|
|
3634
|
+
duration: Duration.seconds(120),
|
|
3543
3635
|
onTimeout: () => new WebSocketError({ url: WS_URL, cause: new Error("Plan create request timed out") })
|
|
3544
3636
|
})
|
|
3545
3637
|
),
|
|
@@ -3552,7 +3644,12 @@ var WebSocketLive = Layer5.effect(
|
|
|
3552
3644
|
reject: (error) => resume(Effect40.fail(error))
|
|
3553
3645
|
};
|
|
3554
3646
|
Effect40.runSync(Ref14.set(pendingPlanListRef, pending));
|
|
3555
|
-
|
|
3647
|
+
try {
|
|
3648
|
+
ws.send(JSON.stringify({ type: "plan:list", payload: { target_portal: portalId, ...status && { status } } }));
|
|
3649
|
+
} catch (err) {
|
|
3650
|
+
Effect40.runSync(Ref14.set(pendingPlanListRef, null));
|
|
3651
|
+
resume(Effect40.fail(new WebSocketError({ url: WS_URL, cause: err })));
|
|
3652
|
+
}
|
|
3556
3653
|
})
|
|
3557
3654
|
),
|
|
3558
3655
|
Effect40.timeoutFail({
|
|
@@ -13326,16 +13423,36 @@ var buildDispatcher = (handlers) => {
|
|
|
13326
13423
|
return h;
|
|
13327
13424
|
};
|
|
13328
13425
|
const validateOperation2 = (op, index) => getHandler(op.type).validate(op, index);
|
|
13329
|
-
const
|
|
13330
|
-
|
|
13331
|
-
|
|
13332
|
-
|
|
13426
|
+
const getHandlerSafe = (type, index) => {
|
|
13427
|
+
const h = map.get(type);
|
|
13428
|
+
return h ? Effect96.succeed(h) : Effect96.fail([{ severity: "error", operation_index: index, code: "UNKNOWN_OPERATION_TYPE", message: `No handler registered for operation type: ${type}` }]);
|
|
13429
|
+
};
|
|
13430
|
+
const validateOperationEffectful2 = (op, index) => pipe83(
|
|
13431
|
+
getHandlerSafe(op.type, index),
|
|
13432
|
+
Effect96.flatMap((h) => h.validateEffectful(op, index)),
|
|
13433
|
+
Effect96.catchAll((issues) => Effect96.succeed(issues))
|
|
13434
|
+
);
|
|
13435
|
+
const preCheckOperation2 = (op, index) => pipe83(
|
|
13436
|
+
getHandlerSafe(op.type, index),
|
|
13437
|
+
Effect96.flatMap((h) => h.preCheck(op, index)),
|
|
13438
|
+
Effect96.catchAll((issues) => Effect96.succeed(issues))
|
|
13439
|
+
);
|
|
13440
|
+
const executeOperation2 = (op, index) => pipe83(
|
|
13441
|
+
getHandlerSafe(op.type, index),
|
|
13442
|
+
Effect96.flatMap((h) => h.execute(op, index)),
|
|
13443
|
+
Effect96.catchAll(() => Effect96.succeed({ index, type: op.type, status: "failed", records_affected: 0, error: `No handler registered for operation type: ${op.type}` }))
|
|
13444
|
+
);
|
|
13445
|
+
const postCheckOperation2 = (op, index, result) => pipe83(
|
|
13446
|
+
getHandlerSafe(op.type, index),
|
|
13447
|
+
Effect96.flatMap((h) => h.postCheck(op, index, result)),
|
|
13448
|
+
Effect96.catchAll((issues) => Effect96.succeed(issues))
|
|
13449
|
+
);
|
|
13333
13450
|
const validatePlan2 = (operations) => operations.flatMap((op, i) => validateOperation2(op, i));
|
|
13334
13451
|
const validatePlanEffectful2 = (operations) => pipe83(
|
|
13335
13452
|
Effect96.forEach(
|
|
13336
13453
|
operations,
|
|
13337
13454
|
(op, i) => validateOperationEffectful2(op, i),
|
|
13338
|
-
{ concurrency:
|
|
13455
|
+
{ concurrency: 5 }
|
|
13339
13456
|
),
|
|
13340
13457
|
Effect96.map((results) => results.flat())
|
|
13341
13458
|
);
|
|
@@ -13343,7 +13460,7 @@ var buildDispatcher = (handlers) => {
|
|
|
13343
13460
|
Effect96.forEach(
|
|
13344
13461
|
operations,
|
|
13345
13462
|
(op, i) => preCheckOperation2(op, i),
|
|
13346
|
-
{ concurrency:
|
|
13463
|
+
{ concurrency: 5 }
|
|
13347
13464
|
),
|
|
13348
13465
|
Effect96.map((results) => results.flat())
|
|
13349
13466
|
);
|
|
@@ -14300,7 +14417,7 @@ No plans found${filterMsg}.` }]
|
|
|
14300
14417
|
}
|
|
14301
14418
|
|
|
14302
14419
|
// src/pure/plugin-sync-gate.ts
|
|
14303
|
-
var PLUGIN_SYNC_DEBOUNCE_MS =
|
|
14420
|
+
var PLUGIN_SYNC_DEBOUNCE_MS = 10 * 60 * 1e3;
|
|
14304
14421
|
var LAST_PLUGIN_SYNC_ALL_KEY = "last_plugin_sync_all";
|
|
14305
14422
|
var shouldRunPluginSync = (lastSyncAllRaw) => {
|
|
14306
14423
|
if (!lastSyncAllRaw) return true;
|
|
@@ -14439,6 +14556,12 @@ var makeEnsureFresh = (deps, options2) => createEnsureFresh(
|
|
|
14439
14556
|
);
|
|
14440
14557
|
|
|
14441
14558
|
// src/index.ts
|
|
14559
|
+
process.on("uncaughtException", (err) => {
|
|
14560
|
+
console.error("[fatal:uncaughtException]", err);
|
|
14561
|
+
});
|
|
14562
|
+
process.on("unhandledRejection", (reason) => {
|
|
14563
|
+
console.error("[fatal:unhandledRejection]", reason);
|
|
14564
|
+
});
|
|
14442
14565
|
var server = new McpServer({
|
|
14443
14566
|
name: "mcp-pro-client",
|
|
14444
14567
|
version: "1.0.0"
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
#!/usr/bin/env node
|
|
3
|
+
|
|
4
|
+
// src/supervisor.ts
|
|
5
|
+
import { spawn } from "child_process";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
7
|
+
import { dirname, resolve } from "path";
|
|
8
|
+
var RESTART_DELAY_MS = 500;
|
|
9
|
+
var MAX_RESTART_COUNT = 10;
|
|
10
|
+
var RESTART_WINDOW_MS = 6e4;
|
|
11
|
+
var isRequest = (msg) => "method" in msg && "id" in msg;
|
|
12
|
+
var isResponse = (msg) => "id" in msg && !("method" in msg);
|
|
13
|
+
var isNotification = (msg) => "method" in msg && !("id" in msg);
|
|
14
|
+
var serialize = (msg) => JSON.stringify(msg) + "\n";
|
|
15
|
+
var makeErrorResponse = (id, message) => ({
|
|
16
|
+
jsonrpc: "2.0",
|
|
17
|
+
id,
|
|
18
|
+
error: { code: -32603, message }
|
|
19
|
+
});
|
|
20
|
+
var LineBuffer = class {
|
|
21
|
+
buf = "";
|
|
22
|
+
push(chunk) {
|
|
23
|
+
this.buf += chunk;
|
|
24
|
+
const messages = [];
|
|
25
|
+
let idx;
|
|
26
|
+
while ((idx = this.buf.indexOf("\n")) !== -1) {
|
|
27
|
+
const line = this.buf.slice(0, idx).replace(/\r$/, "");
|
|
28
|
+
this.buf = this.buf.slice(idx + 1);
|
|
29
|
+
if (line.length === 0) continue;
|
|
30
|
+
try {
|
|
31
|
+
messages.push(JSON.parse(line));
|
|
32
|
+
} catch {
|
|
33
|
+
process.stderr.write(`[supervisor] Failed to parse JSON-RPC line: ${line.slice(0, 200)}
|
|
34
|
+
`);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return messages;
|
|
38
|
+
}
|
|
39
|
+
clear() {
|
|
40
|
+
this.buf = "";
|
|
41
|
+
}
|
|
42
|
+
};
|
|
43
|
+
var selfDir = dirname(fileURLToPath(import.meta.url));
|
|
44
|
+
var childScript = resolve(selfDir, "index.js");
|
|
45
|
+
var child = null;
|
|
46
|
+
var pendingRequests = /* @__PURE__ */ new Map();
|
|
47
|
+
var savedInitializeRequest = null;
|
|
48
|
+
var childReady = false;
|
|
49
|
+
var restarting = false;
|
|
50
|
+
var stdinBuffer = [];
|
|
51
|
+
var recentRestarts = [];
|
|
52
|
+
var childStdoutBuf = new LineBuffer();
|
|
53
|
+
var stdinBuf = new LineBuffer();
|
|
54
|
+
process.stdin.setEncoding("utf-8");
|
|
55
|
+
process.stdin.on("data", (chunk) => {
|
|
56
|
+
const messages = stdinBuf.push(chunk);
|
|
57
|
+
for (const msg of messages) {
|
|
58
|
+
handleUpstreamMessage(msg);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
function handleUpstreamMessage(msg) {
|
|
62
|
+
if (isRequest(msg)) {
|
|
63
|
+
if (msg.method === "initialize") {
|
|
64
|
+
savedInitializeRequest = msg;
|
|
65
|
+
}
|
|
66
|
+
pendingRequests.set(msg.id, msg);
|
|
67
|
+
}
|
|
68
|
+
if (!childReady || !child) {
|
|
69
|
+
stdinBuffer.push(msg);
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
forwardToChild(msg);
|
|
73
|
+
}
|
|
74
|
+
function forwardToChild(msg) {
|
|
75
|
+
try {
|
|
76
|
+
child?.stdin?.write(serialize(msg));
|
|
77
|
+
} catch {
|
|
78
|
+
process.stderr.write("[supervisor] Failed to write to child stdin\n");
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function handleChildStdout(chunk) {
|
|
82
|
+
const messages = childStdoutBuf.push(chunk);
|
|
83
|
+
for (const msg of messages) {
|
|
84
|
+
handleDownstreamMessage(msg);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function handleDownstreamMessage(msg) {
|
|
88
|
+
if (isResponse(msg)) {
|
|
89
|
+
pendingRequests.delete(msg.id);
|
|
90
|
+
if (restarting && savedInitializeRequest && msg.id === savedInitializeRequest.id) {
|
|
91
|
+
process.stderr.write("[supervisor] Swallowed re-initialize response from child\n");
|
|
92
|
+
finishRestart();
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
forwardToCodex(msg);
|
|
97
|
+
}
|
|
98
|
+
function forwardToCodex(msg) {
|
|
99
|
+
try {
|
|
100
|
+
process.stdout.write(serialize(msg));
|
|
101
|
+
} catch {
|
|
102
|
+
process.stderr.write("[supervisor] Failed to write to stdout (Codex pipe broken)\n");
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function spawnChild() {
|
|
107
|
+
const now = Date.now();
|
|
108
|
+
recentRestarts.push(now);
|
|
109
|
+
while (recentRestarts.length > 0 && now - recentRestarts[0] > RESTART_WINDOW_MS) {
|
|
110
|
+
recentRestarts.shift();
|
|
111
|
+
}
|
|
112
|
+
if (recentRestarts.length > MAX_RESTART_COUNT) {
|
|
113
|
+
process.stderr.write(
|
|
114
|
+
`[supervisor] Too many restarts (${recentRestarts.length} in ${RESTART_WINDOW_MS / 1e3}s) \u2014 exiting
|
|
115
|
+
`
|
|
116
|
+
);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
process.stderr.write(`[supervisor] Spawning MCP client: ${childScript}
|
|
120
|
+
`);
|
|
121
|
+
child = spawn(process.execPath, [childScript], {
|
|
122
|
+
stdio: ["pipe", "pipe", "inherit"],
|
|
123
|
+
env: process.env
|
|
124
|
+
});
|
|
125
|
+
child.stdout.setEncoding("utf-8");
|
|
126
|
+
child.stdout.on("data", handleChildStdout);
|
|
127
|
+
child.on("exit", (code, signal) => {
|
|
128
|
+
process.stderr.write(
|
|
129
|
+
`[supervisor] Child exited: code=${code} signal=${signal ?? "none"}
|
|
130
|
+
`
|
|
131
|
+
);
|
|
132
|
+
child = null;
|
|
133
|
+
childReady = false;
|
|
134
|
+
childStdoutBuf.clear();
|
|
135
|
+
handleChildExit();
|
|
136
|
+
});
|
|
137
|
+
child.on("error", (err) => {
|
|
138
|
+
process.stderr.write(`[supervisor] Child error: ${err.message}
|
|
139
|
+
`);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
function handleChildExit() {
|
|
143
|
+
failPendingRequests("MCP server restarted \u2014 please retry this request");
|
|
144
|
+
setTimeout(() => {
|
|
145
|
+
process.stderr.write("[supervisor] Restarting child...\n");
|
|
146
|
+
spawnChild();
|
|
147
|
+
if (savedInitializeRequest) {
|
|
148
|
+
restarting = true;
|
|
149
|
+
forwardToChild({ ...savedInitializeRequest });
|
|
150
|
+
} else {
|
|
151
|
+
childReady = true;
|
|
152
|
+
restarting = false;
|
|
153
|
+
drainStdinBuffer();
|
|
154
|
+
}
|
|
155
|
+
}, RESTART_DELAY_MS);
|
|
156
|
+
}
|
|
157
|
+
function finishRestart() {
|
|
158
|
+
if (savedInitializeRequest) {
|
|
159
|
+
const initialized = {
|
|
160
|
+
jsonrpc: "2.0",
|
|
161
|
+
method: "notifications/initialized"
|
|
162
|
+
};
|
|
163
|
+
forwardToChild(initialized);
|
|
164
|
+
}
|
|
165
|
+
childReady = true;
|
|
166
|
+
restarting = false;
|
|
167
|
+
process.stderr.write("[supervisor] Child re-initialized \u2014 resuming proxy\n");
|
|
168
|
+
drainStdinBuffer();
|
|
169
|
+
}
|
|
170
|
+
function drainStdinBuffer() {
|
|
171
|
+
const buffered = stdinBuffer;
|
|
172
|
+
stdinBuffer = [];
|
|
173
|
+
for (const msg of buffered) {
|
|
174
|
+
if (isRequest(msg) && msg.method === "initialize") continue;
|
|
175
|
+
if (isNotification(msg) && msg.method === "notifications/initialized") continue;
|
|
176
|
+
forwardToChild(msg);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function failPendingRequests(message) {
|
|
180
|
+
for (const [id] of pendingRequests) {
|
|
181
|
+
forwardToCodex(makeErrorResponse(id, message));
|
|
182
|
+
}
|
|
183
|
+
pendingRequests.clear();
|
|
184
|
+
}
|
|
185
|
+
process.stderr.write("[supervisor] Starting MCP supervisor\n");
|
|
186
|
+
spawnChild();
|
|
187
|
+
childReady = true;
|
|
188
|
+
process.on("SIGINT", () => {
|
|
189
|
+
child?.kill("SIGINT");
|
|
190
|
+
process.exit(0);
|
|
191
|
+
});
|
|
192
|
+
process.on("SIGTERM", () => {
|
|
193
|
+
child?.kill("SIGTERM");
|
|
194
|
+
process.exit(0);
|
|
195
|
+
});
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@daeda/mcp-pro",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.21",
|
|
4
4
|
"description": "MCP server for HubSpot CRM — sync, query, and manage your portal data",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"mcp-pro": "dist/index.js"
|
|
7
|
+
"mcp-pro": "dist/index.js",
|
|
8
|
+
"mcp-pro-supervised": "dist/supervisor.js"
|
|
8
9
|
},
|
|
9
10
|
"main": "./dist/index.js",
|
|
10
11
|
"exports": {
|