@wrongstack/mcp 0.298.3 → 0.299.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/dist/index.js CHANGED
@@ -3769,7 +3769,7 @@ var MCPRegistry = class _MCPRegistry {
3769
3769
  if (lazy) {
3770
3770
  await this.startLazy(slot);
3771
3771
  } else {
3772
- await this.attemptConnect(slot);
3772
+ await this.singleFlightConnect(slot);
3773
3773
  }
3774
3774
  }
3775
3775
  /** Record an intentionally disabled configuration without opening a transport. */
@@ -3805,7 +3805,21 @@ var MCPRegistry = class _MCPRegistry {
3805
3805
  );
3806
3806
  return;
3807
3807
  }
3808
- await this.attemptConnect(slot);
3808
+ await this.singleFlightConnect(slot);
3809
+ }
3810
+ singleFlightConnect(slot) {
3811
+ if (slot.client && slot.state === "connected") return Promise.resolve(slot.client);
3812
+ if (slot.connecting) return slot.connecting;
3813
+ const promise = (async () => {
3814
+ try {
3815
+ await this.attemptConnect(slot);
3816
+ return slot.client;
3817
+ } finally {
3818
+ slot.connecting = void 0;
3819
+ }
3820
+ })();
3821
+ slot.connecting = promise;
3822
+ return promise;
3809
3823
  }
3810
3824
  /**
3811
3825
  * Ensure a lazy server is connected, spawning it on demand. Single-flight:
@@ -3816,28 +3830,20 @@ var MCPRegistry = class _MCPRegistry {
3816
3830
  if (!slot) throw new Error(`MCP server "${name}" not registered`);
3817
3831
  slot.lastUsed = Date.now();
3818
3832
  if (slot.client && slot.state === "connected") return slot.client;
3819
- if (slot.connecting) return slot.connecting;
3820
3833
  const waking = slot.state === "dormant";
3821
3834
  if (waking) {
3822
3835
  slot.operations.wakeCount++;
3823
3836
  this.recordOperation(slot, "wake", "lazy-demand");
3837
+ slot.attempts = 0;
3838
+ slot.reconnectCycles = 0;
3824
3839
  }
3825
- slot.connecting = (async () => {
3826
- try {
3827
- slot.attempts = 0;
3828
- slot.reconnectCycles = 0;
3829
- await this.attemptConnect(slot);
3830
- if (!slot.client) {
3831
- throw new Error(`MCP server "${name}" failed to connect on demand`);
3832
- }
3833
- slot.lastUsed = Date.now();
3834
- this.ensureIdleSweep();
3835
- return slot.client;
3836
- } finally {
3837
- slot.connecting = void 0;
3838
- }
3839
- })();
3840
- return slot.connecting;
3840
+ const client = await this.singleFlightConnect(slot);
3841
+ if (!client) {
3842
+ throw new Error(`MCP server "${name}" failed to connect on demand`);
3843
+ }
3844
+ slot.lastUsed = Date.now();
3845
+ this.ensureIdleSweep();
3846
+ return client;
3841
3847
  }
3842
3848
  /**
3843
3849
  * Register all cached tools for a given server into the tool registry.
@@ -3898,6 +3904,7 @@ var MCPRegistry = class _MCPRegistry {
3898
3904
  clearTimeout(slot.reconnectTimer);
3899
3905
  slot.reconnectTimer = void 0;
3900
3906
  }
3907
+ slot.state = "disconnected";
3901
3908
  if (slot.client) {
3902
3909
  slot.client.removeExitListener(this.onChildExit);
3903
3910
  if (slot.onDisconnect) slot.client.removeDisconnectListener(slot.onDisconnect);
@@ -3916,7 +3923,6 @@ var MCPRegistry = class _MCPRegistry {
3916
3923
  slot.resourceTemplates = void 0;
3917
3924
  slot.prompts = void 0;
3918
3925
  slot.registeredLazy = false;
3919
- slot.state = "disconnected";
3920
3926
  this.recordOperation(slot, "stop", "manual");
3921
3927
  this.events.emit("mcp.server.disconnected", { name, reason: "stop" });
3922
3928
  }
@@ -3928,7 +3934,11 @@ var MCPRegistry = class _MCPRegistry {
3928
3934
  await this.stop(name);
3929
3935
  slot.attempts = 0;
3930
3936
  slot.reconnectCycles = 0;
3931
- await this.attemptConnect(slot);
3937
+ if (slot.lazy) {
3938
+ await this.startLazy(slot);
3939
+ } else {
3940
+ await this.singleFlightConnect(slot);
3941
+ }
3932
3942
  }
3933
3943
  list() {
3934
3944
  return Array.from(this.servers.values()).map((s) => {
@@ -4356,6 +4366,9 @@ var MCPRegistry = class _MCPRegistry {
4356
4366
  const MAX_ATTEMPTS = MCP_CONSTANTS.RECONNECT.MAX_ATTEMPTS;
4357
4367
  let attempt = 0;
4358
4368
  while (attempt < MAX_ATTEMPTS) {
4369
+ if (this.servers.has(slot.cfg.name) && this.servers.get(slot.cfg.name) !== slot) {
4370
+ return;
4371
+ }
4359
4372
  attempt++;
4360
4373
  const startedAt = Date.now();
4361
4374
  slot.state = attempt === 1 ? "connecting" : "reconnecting";
@@ -4385,6 +4398,15 @@ var MCPRegistry = class _MCPRegistry {
4385
4398
  client.addToolsChangedListener(this.onToolsChanged);
4386
4399
  this.addCatalogListeners(client);
4387
4400
  await client.connect();
4401
+ if (slot.state === "disconnected" || this.servers.has(slot.cfg.name) && this.servers.get(slot.cfg.name) !== slot) {
4402
+ client.removeExitListener(this.onChildExit);
4403
+ if (boundDisconnect) client.removeDisconnectListener(boundDisconnect);
4404
+ client.removeToolsChangedListener(this.onToolsChanged);
4405
+ this.removeCatalogListeners(client);
4406
+ await client.close().catch(() => {
4407
+ });
4408
+ return;
4409
+ }
4388
4410
  if (slot.client && slot.client !== client) {
4389
4411
  const prior = slot.client;
4390
4412
  const priorDisconnect = slot.onDisconnect;
@@ -4457,6 +4479,9 @@ var MCPRegistry = class _MCPRegistry {
4457
4479
  }
4458
4480
  const delay = 500 * 2 ** attempt;
4459
4481
  await new Promise((r) => setTimeout(r, delay));
4482
+ if (slot.state === "disconnected" || this.servers.has(slot.cfg.name) && this.servers.get(slot.cfg.name) !== slot) {
4483
+ return;
4484
+ }
4460
4485
  }
4461
4486
  }
4462
4487
  }
@@ -26,7 +26,7 @@ export interface ServerSlot {
26
26
  /** Epoch ms of the last tool call — drives idle auto-sleep. */
27
27
  lastUsed: number;
28
28
  /** Single-flight guard so concurrent first-calls trigger only one connect. */
29
- connecting?: Promise<MCPClient> | undefined;
29
+ connecting?: Promise<MCPClient | undefined> | undefined;
30
30
  /** Whether this lazy server's resolver wrappers are registered (register once). */
31
31
  registeredLazy: boolean;
32
32
  /** Bounded, payload-free operational telemetry for this server. */
@@ -46,6 +46,7 @@ export declare class MCPRegistry {
46
46
  * cache yet, do a one-time cold discovery connect to learn + cache the tools.
47
47
  */
48
48
  private startLazy;
49
+ private singleFlightConnect;
49
50
  /**
50
51
  * Ensure a lazy server is connected, spawning it on demand. Single-flight:
51
52
  * concurrent first-calls share one connect. Resolver wrappers call this.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/mcp",
3
- "version": "0.298.3",
3
+ "version": "0.299.0",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack Model Context Protocol client and registry: stdio, SSE, and streamable HTTP transports.",
6
6
  "repository": {
@@ -26,7 +26,7 @@
26
26
  "!dist/**/*.map"
27
27
  ],
28
28
  "dependencies": {
29
- "@wrongstack/core": "0.298.3"
29
+ "@wrongstack/core": "0.299.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "^26.1.2",