@juspay/neurolink 13.8.4 → 13.8.5

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.
@@ -8,7 +8,7 @@
8
8
  */
9
9
  import { EventEmitter } from "events";
10
10
  import { mcpLogger } from "../utils/logger.js";
11
- import { MCPClientFactory, terminateStartingStdioServers, } from "./mcpClientFactory.js";
11
+ import { MCPClientFactory, terminateLiveStdioServers, } from "./mcpClientFactory.js";
12
12
  import { ToolDiscoveryService } from "./toolDiscoveryService.js";
13
13
  import { toolRegistry } from "./toolRegistry.js";
14
14
  import { HITLUserRejectedError, HITLTimeoutError } from "../hitl/hitlErrors.js";
@@ -372,9 +372,9 @@ const shutdownOnSignal = (signal) => {
372
372
  // re-raise and nowhere else.
373
373
  if (!hadOtherListeners) {
374
374
  process.removeListener(signal, shutdownOnSignal);
375
- // Dying of a signal runs no `exit` handler, and the cleanup above cannot
376
- // reach a server whose handshake is still pending.
377
- terminateStartingStdioServers();
375
+ // Dying of a signal runs no `exit` handler, and the cleanup above reaches
376
+ // only the servers a manager has recorded and that close when asked.
377
+ terminateLiveStdioServers();
378
378
  process.kill(process.pid, signal);
379
379
  }
380
380
  });
@@ -14,7 +14,7 @@ import type { MCPTransportType, MCPServerInfo, MCPClientResult } from "../types/
14
14
  * `exit` event, and SIGTERM is delivered by the kernel even though this
15
15
  * process is already on its way out.
16
16
  */
17
- export declare const terminateStartingStdioServers: () => void;
17
+ export declare const terminateLiveStdioServers: () => void;
18
18
  /**
19
19
  * MCPClientFactory
20
20
  * Factory class for creating MCP clients with different transports
@@ -63,25 +63,29 @@ class StderrTail {
63
63
  }
64
64
  const stderrTails = new WeakMap();
65
65
  /**
66
- * Stdio servers that have been spawned but have not yet answered `initialize`.
66
+ * Stdio servers this process has spawned and has not yet seen close.
67
67
  *
68
- * Nothing that cleans up at exit can reach one: the manager learns of a client
69
- * and its transport only when `connect()` returns, and `process.exit()` runs
70
- * no async handler anyway. It is also the state a server is least likely to
71
- * notice its host going away, because `npx -y <pkg>` does not read its stdin
72
- * until the package has downloaded. Once a server has answered it is an
73
- * ordinary MCP server again and ends when its stdin closes, so it leaves this
74
- * set and the shutdown paths that already exist keep ownership of it.
68
+ * A transport is tracked from creation to close, not only until the server
69
+ * answers `initialize`, because two things leave a server running with nothing
70
+ * to stop it. Until the manager records the client, which happens only after
71
+ * `connect()` and the handshake, no shutdown path knows the server exists, and
72
+ * `npx -y <pkg>` does not read its stdin until the package has downloaded.
73
+ * After that, a server holding a timer, a connection pool or a file watcher
74
+ * ignores its stdin closing, and `process.exit()` runs neither `beforeExit`
75
+ * nor the manager's async shutdown.
76
+ *
77
+ * `transport.close()` takes a transport out through `onclose` once the child
78
+ * has exited, so a server the manager closed is not signalled again.
75
79
  */
76
- const startingStdioTransports = new Set();
77
- let startingStdioExitHookInstalled = false;
80
+ const liveStdioTransports = new Set();
81
+ let stdioExitHookInstalled = false;
78
82
  /**
79
83
  * Synchronous on purpose: it is the one thing that can run inside the
80
84
  * `exit` event, and SIGTERM is delivered by the kernel even though this
81
85
  * process is already on its way out.
82
86
  */
83
- export const terminateStartingStdioServers = () => {
84
- for (const transport of startingStdioTransports) {
87
+ export const terminateLiveStdioServers = () => {
88
+ for (const transport of liveStdioTransports) {
85
89
  if ("pid" in transport && typeof transport.pid === "number") {
86
90
  try {
87
91
  process.kill(transport.pid, "SIGTERM");
@@ -92,16 +96,18 @@ export const terminateStartingStdioServers = () => {
92
96
  }
93
97
  }
94
98
  };
95
- const trackStartingStdioTransport = (transport) => {
96
- if (!startingStdioExitHookInstalled) {
97
- startingStdioExitHookInstalled = true;
98
- process.on("exit", terminateStartingStdioServers);
99
+ const trackStdioTransport = (transport) => {
100
+ if (!stdioExitHookInstalled) {
101
+ stdioExitHookInstalled = true;
102
+ process.on("exit", terminateLiveStdioServers);
99
103
  }
100
- startingStdioTransports.add(transport);
104
+ liveStdioTransports.add(transport);
101
105
  // Client.connect() chains an onclose that is already set rather than
102
- // replacing it, and a spawn that fails still closes the transport.
106
+ // replacing it, and the manager listens on client.onclose, so this one
107
+ // survives for the life of the connection. A spawn that fails still closes
108
+ // the transport.
103
109
  transport.onclose = () => {
104
- startingStdioTransports.delete(transport);
110
+ liveStdioTransports.delete(transport);
105
111
  };
106
112
  };
107
113
  /**
@@ -265,7 +271,6 @@ export class MCPClientFactory {
265
271
  }
266
272
  // Connect with timeout
267
273
  await this.raceWithTimeout(client.connect(transport, { timeout }), timeout, `Client connection timeout for ${config.id}`);
268
- startingStdioTransports.delete(transport);
269
274
  // Perform handshake to get server capabilities
270
275
  const serverCapabilities = await this.performHandshake(client, timeout);
271
276
  mcpLogger.debug(`[MCPClientFactory] Handshake completed for ${config.id}`, {
@@ -361,7 +366,7 @@ export class MCPClientFactory {
361
366
  cwd: config.cwd,
362
367
  stderr: "pipe",
363
368
  });
364
- trackStartingStdioTransport(transport);
369
+ trackStdioTransport(transport);
365
370
  const tail = new StderrTail();
366
371
  stderrTails.set(transport, tail);
367
372
  transport.stderr?.on("data", (chunk) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@juspay/neurolink",
3
- "version": "13.8.4",
3
+ "version": "13.8.5",
4
4
  "packageManager": "pnpm@10.15.1",
5
5
  "description": "The pipe layer of an AI nervous system: one interface connecting provider neurons to your application, across three inference types — generate, stream and decide. `decide` returns typed, calibrated judgements from a non-generative model (~400ms, ~$0.00002/call) for routing, tool selection and context budgeting. MCP-native (4 transports), voice TTS/STT/realtime, RAG, agents, memory, compaction, 9 observability exporters. OpenAI · Anthropic · Gemini · Bedrock · Azure · Ollama · TypeSafe Jev and more.",
6
6
  "author": {