@daeda/mcp-pro 0.1.20 → 0.1.22
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 +153 -31
- 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,7 +3622,12 @@ 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({
|
|
@@ -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({
|
|
@@ -11781,7 +11878,7 @@ var BUILT_IN_ACTION_TYPES = {
|
|
|
11781
11878
|
description: "Set, edit, copy, or clear property values for enrolled or associated records",
|
|
11782
11879
|
connectionType: "SINGLE_CONNECTION",
|
|
11783
11880
|
fields: [
|
|
11784
|
-
{ name: "
|
|
11881
|
+
{ name: "property", type: "string", required: true, description: "Internal property name to set" },
|
|
11785
11882
|
{ name: "value", type: "object", required: true, description: "MUST include type: 'STATIC_VALUE' when setting a value, e.g. { type: 'STATIC_VALUE', staticValue: 'value' }. Omitting type causes HTTP 500." },
|
|
11786
11883
|
{ name: "association", type: "object", required: false, description: "Association spec if setting on an associated record: { associationCategory: 'HUBSPOT_DEFINED', associationTypeId: <number> }" }
|
|
11787
11884
|
]
|
|
@@ -12347,7 +12444,7 @@ var collectWorkflowAssociatedPropertyActionRefs = (actions) => actions.flatMap((
|
|
|
12347
12444
|
if (actionTypeId !== "0-5") return [];
|
|
12348
12445
|
const fields48 = isRecord(action.fields) ? action.fields : void 0;
|
|
12349
12446
|
const association = isRecord(fields48?.association) ? fields48.association : void 0;
|
|
12350
|
-
const propertyName = typeof fields48?.property_name === "string" ? fields48.property_name : void 0;
|
|
12447
|
+
const propertyName = typeof fields48?.property === "string" ? fields48.property : typeof fields48?.property_name === "string" ? fields48.property_name : void 0;
|
|
12351
12448
|
const associationTypeId = typeof association?.associationTypeId === "number" ? association.associationTypeId : void 0;
|
|
12352
12449
|
if (!propertyName || associationTypeId === void 0) return [];
|
|
12353
12450
|
return [{
|
|
@@ -12621,10 +12718,9 @@ var normalizeWorkflowActionValues = (actions) => actions.map((action) => {
|
|
|
12621
12718
|
if (!fields48) return { ...action };
|
|
12622
12719
|
const actionTypeId = typeof action.actionTypeId === "string" ? action.actionTypeId : "";
|
|
12623
12720
|
if (actionTypeId === "0-5") {
|
|
12624
|
-
|
|
12625
|
-
|
|
12626
|
-
|
|
12627
|
-
};
|
|
12721
|
+
const { property_name, ...restFields } = fields48;
|
|
12722
|
+
const normalizedFields = property_name && !restFields.property ? { ...restFields, property: property_name, value: injectStaticValueType(restFields.value) } : { ...restFields, value: injectStaticValueType(restFields.value) };
|
|
12723
|
+
return { ...action, fields: normalizedFields };
|
|
12628
12724
|
}
|
|
12629
12725
|
if (actionTypeId === "0-14" && Array.isArray(fields48.properties)) {
|
|
12630
12726
|
return {
|
|
@@ -12656,7 +12752,7 @@ var collectWorkflowPropertyActionRefs = (actions) => actions.flatMap((action) =>
|
|
|
12656
12752
|
const fields48 = typeof action.fields === "object" && action.fields !== null ? action.fields : void 0;
|
|
12657
12753
|
const association = typeof fields48?.association === "object" && fields48.association !== null ? fields48.association : void 0;
|
|
12658
12754
|
const value = typeof fields48?.value === "object" && fields48.value !== null ? fields48.value : void 0;
|
|
12659
|
-
const propertyName = typeof fields48?.property_name === "string" ? fields48.property_name : void 0;
|
|
12755
|
+
const propertyName = typeof fields48?.property === "string" ? fields48.property : typeof fields48?.property_name === "string" ? fields48.property_name : void 0;
|
|
12660
12756
|
if (!propertyName) return [];
|
|
12661
12757
|
return [{
|
|
12662
12758
|
actionId: typeof action.actionId === "string" ? action.actionId : "<missing>",
|
|
@@ -13326,10 +13422,30 @@ var buildDispatcher = (handlers) => {
|
|
|
13326
13422
|
return h;
|
|
13327
13423
|
};
|
|
13328
13424
|
const validateOperation2 = (op, index) => getHandler(op.type).validate(op, index);
|
|
13329
|
-
const
|
|
13330
|
-
|
|
13331
|
-
|
|
13332
|
-
|
|
13425
|
+
const getHandlerSafe = (type, index) => {
|
|
13426
|
+
const h = map.get(type);
|
|
13427
|
+
return h ? Effect96.succeed(h) : Effect96.fail([{ severity: "error", operation_index: index, code: "UNKNOWN_OPERATION_TYPE", message: `No handler registered for operation type: ${type}` }]);
|
|
13428
|
+
};
|
|
13429
|
+
const validateOperationEffectful2 = (op, index) => pipe83(
|
|
13430
|
+
getHandlerSafe(op.type, index),
|
|
13431
|
+
Effect96.flatMap((h) => h.validateEffectful(op, index)),
|
|
13432
|
+
Effect96.catchAll((issues) => Effect96.succeed(issues))
|
|
13433
|
+
);
|
|
13434
|
+
const preCheckOperation2 = (op, index) => pipe83(
|
|
13435
|
+
getHandlerSafe(op.type, index),
|
|
13436
|
+
Effect96.flatMap((h) => h.preCheck(op, index)),
|
|
13437
|
+
Effect96.catchAll((issues) => Effect96.succeed(issues))
|
|
13438
|
+
);
|
|
13439
|
+
const executeOperation2 = (op, index) => pipe83(
|
|
13440
|
+
getHandlerSafe(op.type, index),
|
|
13441
|
+
Effect96.flatMap((h) => h.execute(op, index)),
|
|
13442
|
+
Effect96.catchAll(() => Effect96.succeed({ index, type: op.type, status: "failed", records_affected: 0, error: `No handler registered for operation type: ${op.type}` }))
|
|
13443
|
+
);
|
|
13444
|
+
const postCheckOperation2 = (op, index, result) => pipe83(
|
|
13445
|
+
getHandlerSafe(op.type, index),
|
|
13446
|
+
Effect96.flatMap((h) => h.postCheck(op, index, result)),
|
|
13447
|
+
Effect96.catchAll((issues) => Effect96.succeed(issues))
|
|
13448
|
+
);
|
|
13333
13449
|
const validatePlan2 = (operations) => operations.flatMap((op, i) => validateOperation2(op, i));
|
|
13334
13450
|
const validatePlanEffectful2 = (operations) => pipe83(
|
|
13335
13451
|
Effect96.forEach(
|
|
@@ -14300,7 +14416,7 @@ No plans found${filterMsg}.` }]
|
|
|
14300
14416
|
}
|
|
14301
14417
|
|
|
14302
14418
|
// src/pure/plugin-sync-gate.ts
|
|
14303
|
-
var PLUGIN_SYNC_DEBOUNCE_MS =
|
|
14419
|
+
var PLUGIN_SYNC_DEBOUNCE_MS = 10 * 60 * 1e3;
|
|
14304
14420
|
var LAST_PLUGIN_SYNC_ALL_KEY = "last_plugin_sync_all";
|
|
14305
14421
|
var shouldRunPluginSync = (lastSyncAllRaw) => {
|
|
14306
14422
|
if (!lastSyncAllRaw) return true;
|
|
@@ -14439,6 +14555,12 @@ var makeEnsureFresh = (deps, options2) => createEnsureFresh(
|
|
|
14439
14555
|
);
|
|
14440
14556
|
|
|
14441
14557
|
// src/index.ts
|
|
14558
|
+
process.on("uncaughtException", (err) => {
|
|
14559
|
+
console.error("[fatal:uncaughtException]", err);
|
|
14560
|
+
});
|
|
14561
|
+
process.on("unhandledRejection", (reason) => {
|
|
14562
|
+
console.error("[fatal:unhandledRejection]", reason);
|
|
14563
|
+
});
|
|
14442
14564
|
var server = new McpServer({
|
|
14443
14565
|
name: "mcp-pro-client",
|
|
14444
14566
|
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.22",
|
|
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": {
|