@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 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.try({
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
- const pending = Effect40.runSync(Ref14.getAndSet(pendingArtifactRef, null));
3386
- if (pending) {
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
- ws.send(JSON.stringify({ type: "sync:artifact", payload: { artifact_id: artifactId } }));
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
- ws.send(JSON.stringify({
3504
- type: "sync:plugin-data",
3505
- payload: {
3506
- target_portal: portalId,
3507
- plugin_name: pluginName,
3508
- request_id: requestId
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
- ws.send(JSON.stringify({ type: "plan:create", payload: { target_portal: portalId, ...plan.dry_run ? { dry_run: true } : {}, plan } }));
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(30),
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
- ws.send(JSON.stringify({ type: "plan:list", payload: { target_portal: portalId, ...status && { status } } }));
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 validateOperationEffectful2 = (op, index) => getHandler(op.type).validateEffectful(op, index);
13330
- const preCheckOperation2 = (op, index) => getHandler(op.type).preCheck(op, index);
13331
- const executeOperation2 = (op, index) => getHandler(op.type).execute(op, index);
13332
- const postCheckOperation2 = (op, index, result) => getHandler(op.type).postCheck(op, index, result);
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: 3 }
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: 3 }
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 = 1 * 60 * 1e3;
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.19",
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": {