@wrongstack/mcp 0.309.1 → 0.310.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/client.d.ts CHANGED
@@ -24,34 +24,14 @@ export interface MCPClientOptions {
24
24
  */
25
25
  passthroughEnv?: string[] | undefined;
26
26
  }
27
- export type ConnectionState = 'idle' | 'connecting' | 'connected' | 'disconnected' | 'reconnecting' | 'failed'
28
- /** Lazy server: registered from a cached manifest, process not spawned. */
29
- | 'dormant';
30
- export interface MCPTool {
31
- name: string;
32
- description?: string | undefined;
33
- inputSchema: Record<string, unknown>;
34
- }
35
- export interface ToolCallResult {
36
- content: unknown;
37
- isError: boolean;
38
- }
27
+ import type { ConnectionState, JsonRpcResponse, MCPTool, ToolCallResult } from './contracts.js';
28
+ export type { ConnectionState, JsonRpcResponse, MCPTool, ToolCallResult };
39
29
  export interface MCPRequestOptions {
40
30
  signal?: AbortSignal | undefined;
41
31
  }
42
32
  export interface MCPPageOptions extends MCPRequestOptions {
43
33
  cursor?: string | undefined;
44
34
  }
45
- export interface JsonRpcResponse {
46
- jsonrpc: '2.0';
47
- id: number;
48
- result?: unknown | undefined;
49
- error?: {
50
- code: number | undefined;
51
- message: string;
52
- data?: unknown | undefined;
53
- } | undefined;
54
- }
55
35
  type ExitListener = (name: string, code: number | null, signal: string | null) => void;
56
36
  type ToolsChangedListener = (name: string, tools: MCPTool[]) => void;
57
37
  export type MCPListChangedListener = (name: string) => void;
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Leaf contract types for the @wrongstack/mcp public surface.
3
+ *
4
+ * Lives separately from `client.ts` so callers (transport implementations,
5
+ * schema normalisers, registry helpers) can depend on the wire-level types
6
+ * without pulling in the MCP client implementation. Breaks the long-standing
7
+ * type-level SCC ARCH-CYCLE-TYPE-17 where `client.ts` both homed the shared
8
+ * contracts and was imported back by every transport/schema module.
9
+ *
10
+ * This module MUST contain no runtime imports.
11
+ */
12
+ /** Connection lifecycle states for an MCP client. */
13
+ export type ConnectionState = 'idle' | 'connecting' | 'connected' | 'disconnected' | 'reconnecting' | 'failed'
14
+ /** Lazy server: registered from a cached manifest, process not spawned. */
15
+ | 'dormant';
16
+ /** Minimal MCP tool descriptor returned by `tools/list`. */
17
+ export interface MCPTool {
18
+ name: string;
19
+ description?: string | undefined;
20
+ inputSchema: Record<string, unknown>;
21
+ }
22
+ /** Result envelope returned by an MCP `tools/call`. */
23
+ export interface ToolCallResult {
24
+ content: unknown;
25
+ isError: boolean;
26
+ }
27
+ /** JSON-RPC 2.0 response shape (success or error). */
28
+ export interface JsonRpcResponse {
29
+ jsonrpc: '2.0';
30
+ id: number;
31
+ result?: unknown | undefined;
32
+ error?: {
33
+ code: number | undefined;
34
+ message: string;
35
+ data?: unknown | undefined;
36
+ } | undefined;
37
+ }
38
+ //# sourceMappingURL=contracts.d.ts.map
package/dist/index.d.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  export { authorizationHeaderForToken, authorizationServerMetadataUrls, canonicalMcpResource, createMcpAuthorizationRequest, discoverMcpAuthorization, exchangeMcpAuthorizationCode, type MCPAccessToken, type MCPAuthorizationChallenge, type MCPAuthorizationContext, type MCPAuthorizationDiscoveryOptions, type MCPAuthorizationDiscoveryResult, type MCPAuthorizationJsonFetcher, type MCPAuthorizationProvider, type MCPAuthorizationRequestOptions, type MCPAuthorizationServerMetadata, type MCPAuthorizationSession, type MCPProtectedResourceMetadata, type MCPTokenExchangeOptions, type MCPTokenRefreshOptions, type MCPTokenSet, parseAuthorizationServerMetadata, parseMcpAuthorizationCallback, parseMcpBearerChallenge, parseProtectedResourceMetadata, protectedResourceMetadataUrls, refreshMcpAccessToken, validateMcpAuthorizationServerMetadata, } from './authorization.js';
2
2
  export { type MCPAuthorizationCompleteInput, MCPAuthorizationManager, type MCPAuthorizationManagerOptions, type MCPAuthorizationStartInput, type MCPAuthorizationStartResult, type MCPAuthorizationStatus, } from './authorization-manager.js';
3
- export { type ConnectionState, MCPClient, type MCPClientOptions, type MCPListChangedListener, type MCPPageOptions, type MCPRequestOptions, type MCPTool, type ToolCallResult, type Transport, } from './client.js';
3
+ export { MCPClient, type MCPClientOptions, type MCPListChangedListener, type MCPPageOptions, type MCPRequestOptions, type Transport, } from './client.js';
4
4
  export { MCP_CONSTANTS } from './constants.js';
5
5
  export { DEFAULT_MCP_INSERTION_MAX_BYTES, DEFAULT_MCP_RESOURCE_SCHEMES, type MCPContentProvenance, type MCPInsertionPolicy, type MCPPromptInsertion, type MCPResourceInsertion, preparePromptInsertion, prepareResourceInsertion, } from './content-selection.js';
6
+ export type { ConnectionState, JsonRpcResponse, MCPTool, ToolCallResult, } from './contracts.js';
6
7
  export { addMcp, disableMcp, discoverMcp, enableMcp, listMcp, MCP_ENV_MASK, type McpManageDeps, type McpOpResult, type McpServerInfo, type McpServerInput, removeMcp, restartMcp, updateMcp, } from './manage.js';
7
8
  export { type MCPCapabilityManifest, manifestConfigHash, readCapabilityManifest, readManifest, writeCapabilityManifest, writeManifest, } from './manifest-cache.js';
8
9
  export { MCP_OPERATION_LIMITS, type MCPFailureKind, type MCPHealthState, type MCPLatencySummary, type MCPOperationEvent, type MCPOperationKind, type MCPServerOperationalHealth, } from './operations.js';
package/dist/index.js CHANGED
@@ -3471,94 +3471,6 @@ function percentile(sorted, ratio) {
3471
3471
  // src/registry.ts
3472
3472
  import { expectDefined as expectDefined2 } from "@wrongstack/core/utils";
3473
3473
 
3474
- // src/registry-catalog.ts
3475
- var MAX_CATALOG_PAGES = 100;
3476
- var MAX_CATALOG_ITEMS = 1e4;
3477
- async function collectCatalogPages(load, select) {
3478
- const items = [];
3479
- const seenCursors = /* @__PURE__ */ new Set();
3480
- let cursor;
3481
- for (let pageNumber = 0; pageNumber < MAX_CATALOG_PAGES; pageNumber++) {
3482
- const page = await load(cursor);
3483
- items.push(...select(page));
3484
- if (items.length > MAX_CATALOG_ITEMS) {
3485
- throw new Error(`MCP catalog exceeds ${MAX_CATALOG_ITEMS} items`);
3486
- }
3487
- const next = page.nextCursor;
3488
- if (!next) return items;
3489
- if (seenCursors.has(next)) throw new Error(`MCP catalog repeated cursor "${next}"`);
3490
- seenCursors.add(next);
3491
- cursor = next;
3492
- }
3493
- throw new Error(`MCP catalog exceeds ${MAX_CATALOG_PAGES} pages`);
3494
- }
3495
- function cloneCatalogRecords(records) {
3496
- return structuredClone(records);
3497
- }
3498
- function registryCatalogSnapshot(slot) {
3499
- return {
3500
- name: slot.cfg.name,
3501
- state: slot.state,
3502
- serverMetadata: slot.serverMetadata ? structuredClone(slot.serverMetadata) : void 0,
3503
- resources: slot.resources ? cloneCatalogRecords(slot.resources) : void 0,
3504
- resourceTemplates: slot.resourceTemplates ? cloneCatalogRecords(slot.resourceTemplates) : void 0,
3505
- prompts: slot.prompts ? cloneCatalogRecords(slot.prompts) : void 0
3506
- };
3507
- }
3508
-
3509
- // src/registry-health.ts
3510
- function buildRegistryOperationalHealth(servers, disabledServers) {
3511
- const active = Array.from(servers).map((slot) => {
3512
- const op = slot.operations;
3513
- const baseHealth = healthStateFor(slot.state, op, slot.cfg.enabled !== false);
3514
- const checks = evaluateHealthThresholds(op, slot.cfg.health?.thresholds);
3515
- return {
3516
- name: slot.cfg.name,
3517
- connectionState: slot.state,
3518
- healthState: applyHealthThresholds(baseHealth, checks),
3519
- lastSuccessAt: op.lastSuccessAt,
3520
- lastFailureAt: op.lastFailureAt,
3521
- lastFailureKind: op.lastFailureKind,
3522
- lastReason: op.lastReason,
3523
- consecutiveFailures: op.consecutiveFailures,
3524
- failures: { ...op.failures },
3525
- reconnectCount: op.reconnectCount,
3526
- wakeCount: op.wakeCount,
3527
- sleepCount: op.sleepCount,
3528
- restartCount: op.restartCount,
3529
- connectionLatency: summarizeLatency(op.connectionSamples),
3530
- discoveryLatency: summarizeLatency(op.discoverySamples),
3531
- callLatency: summarizeLatency(op.callSamples),
3532
- inFlightCalls: op.inFlightCalls,
3533
- peakInFlightCalls: op.peakInFlightCalls,
3534
- recentEvents: op.recentEvents.map((event) => ({ ...event })),
3535
- healthChecks: checks
3536
- };
3537
- });
3538
- const disabled = Array.from(disabledServers).map((cfg) => {
3539
- const operations = createMCPServerOperationState();
3540
- return {
3541
- name: cfg.name,
3542
- connectionState: "idle",
3543
- healthState: "disabled",
3544
- consecutiveFailures: 0,
3545
- failures: { ...operations.failures },
3546
- reconnectCount: 0,
3547
- wakeCount: 0,
3548
- sleepCount: 0,
3549
- restartCount: 0,
3550
- connectionLatency: summarizeLatency([]),
3551
- discoveryLatency: summarizeLatency([]),
3552
- callLatency: summarizeLatency([]),
3553
- inFlightCalls: 0,
3554
- peakInFlightCalls: 0,
3555
- recentEvents: [],
3556
- healthChecks: []
3557
- };
3558
- });
3559
- return [...active, ...disabled];
3560
- }
3561
-
3562
3474
  // src/registry-authorization.ts
3563
3475
  function requireAuthorizationManager(manager) {
3564
3476
  if (!manager) {
@@ -3590,109 +3502,39 @@ async function completeRegistryAuthorization(manager, cfg, name, callbackUrl, si
3590
3502
  });
3591
3503
  }
3592
3504
 
3593
- // src/registry-operations.ts
3594
- function operationsForSlot(slot) {
3595
- if (!slot.operations) slot.operations = createMCPServerOperationState();
3596
- return slot.operations;
3597
- }
3598
- function recordRegistrySuccess(slot, resetFailures = true) {
3599
- const operations = operationsForSlot(slot);
3600
- operations.lastSuccessAt = Date.now();
3601
- if (resetFailures) operations.consecutiveFailures = 0;
3602
- }
3603
- function recordRegistryFailure(slot, listeners, failureKind, reason, durationMs) {
3604
- const operations = operationsForSlot(slot);
3605
- const safeReason = safeOperationReason(reason);
3606
- operations.lastFailureAt = Date.now();
3607
- operations.lastFailureKind = failureKind;
3608
- operations.lastReason = safeReason;
3609
- operations.consecutiveFailures++;
3610
- operations.failures[failureKind]++;
3611
- recordRegistryOperation(slot, listeners, "failure", safeReason, failureKind, durationMs);
3612
- }
3613
- function recordRegistryOperation(slot, listeners, kind, reason, failureKind, durationMs, retain = true) {
3614
- const operations = operationsForSlot(slot);
3615
- const baseHealth = healthStateFor(slot.state, operations, slot.cfg.enabled !== false);
3616
- const checks = evaluateHealthThresholds(operations, slot.cfg.health?.thresholds);
3617
- const event = {
3618
- serverName: slot.cfg.name,
3619
- kind,
3620
- at: Date.now(),
3621
- connectionState: slot.state,
3622
- healthState: applyHealthThresholds(baseHealth, checks)
3623
- };
3624
- if (reason !== void 0) event.reason = safeOperationReason(reason);
3625
- if (failureKind !== void 0) event.failureKind = failureKind;
3626
- if (durationMs !== void 0) event.durationMs = Math.max(0, Math.round(durationMs));
3627
- if (retain) pushBounded(operations.recentEvents, event, MCP_OPERATION_LIMITS.RECENT_EVENTS);
3628
- for (const listener of listeners) {
3629
- try {
3630
- listener({ ...event });
3631
- } catch {
3505
+ // src/registry-catalog.ts
3506
+ var MAX_CATALOG_PAGES = 100;
3507
+ var MAX_CATALOG_ITEMS = 1e4;
3508
+ async function collectCatalogPages(load, select) {
3509
+ const items = [];
3510
+ const seenCursors = /* @__PURE__ */ new Set();
3511
+ let cursor;
3512
+ for (let pageNumber = 0; pageNumber < MAX_CATALOG_PAGES; pageNumber++) {
3513
+ const page = await load(cursor);
3514
+ items.push(...select(page));
3515
+ if (items.length > MAX_CATALOG_ITEMS) {
3516
+ throw new Error(`MCP catalog exceeds ${MAX_CATALOG_ITEMS} items`);
3632
3517
  }
3518
+ const next = page.nextCursor;
3519
+ if (!next) return items;
3520
+ if (seenCursors.has(next)) throw new Error(`MCP catalog repeated cursor "${next}"`);
3521
+ seenCursors.add(next);
3522
+ cursor = next;
3633
3523
  }
3524
+ throw new Error(`MCP catalog exceeds ${MAX_CATALOG_PAGES} pages`);
3634
3525
  }
3635
-
3636
- // src/registry-disconnect.ts
3637
- function resetDisconnectedSlotTools(slot, toolRegistry) {
3638
- for (const t of slot.toolNames) {
3639
- try {
3640
- toolRegistry.unregister(t);
3641
- } catch {
3642
- }
3643
- }
3644
- slot.toolNames = [];
3645
- slot.lazyTools = [];
3646
- slot.serverMetadata = void 0;
3647
- slot.resources = void 0;
3648
- slot.resourceTemplates = void 0;
3649
- slot.prompts = void 0;
3526
+ function cloneCatalogRecords(records) {
3527
+ return structuredClone(records);
3650
3528
  }
3651
- function markLazySlotDormant(slot, events, reason) {
3652
- slot.client = void 0;
3653
- slot.state = "dormant";
3654
- events.emit("mcp.server.disconnected", {
3529
+ function registryCatalogSnapshot(slot) {
3530
+ return {
3655
3531
  name: slot.cfg.name,
3656
- reason: `${reason} (dormant)`
3657
- });
3658
- }
3659
-
3660
- // src/registry-reconnect.ts
3661
- function scheduleRegistryReconnect({
3662
- slot,
3663
- events,
3664
- log,
3665
- maxReconnectCycles,
3666
- baseReconnectDelayMs,
3667
- maxReconnectDelayMs,
3668
- recordReconnectExhausted,
3669
- attemptReconnect
3670
- }) {
3671
- if (slot.reconnectPending) return;
3672
- if (slot.reconnectCycles >= maxReconnectCycles) {
3673
- slot.state = "failed";
3674
- recordReconnectExhausted(slot);
3675
- log.error(
3676
- `MCP server "${slot.cfg.name}" giving up after ${slot.reconnectCycles} reconnect cycles. Use \`/mcp restart ${slot.cfg.name}\` to retry.`
3677
- );
3678
- events.emit("mcp.server.disconnected", {
3679
- name: slot.cfg.name,
3680
- reason: `reconnect-exhausted:${slot.reconnectCycles}`
3681
- });
3682
- return;
3683
- }
3684
- slot.reconnectPending = true;
3685
- if (slot.reconnectTimer) {
3686
- clearTimeout(slot.reconnectTimer);
3687
- slot.reconnectTimer = void 0;
3688
- }
3689
- const base = Math.min(baseReconnectDelayMs * 2 ** slot.reconnectCycles, maxReconnectDelayMs);
3690
- const jitter = base * MCP_CONSTANTS.RECONNECT.JITTER_FACTOR * (Math.random() * 2 - 1);
3691
- const delay = Math.max(100, Math.round(base + jitter));
3692
- slot.reconnectTimer = setTimeout(() => {
3693
- slot.reconnectTimer = void 0;
3694
- void attemptReconnect(slot);
3695
- }, delay);
3532
+ state: slot.state,
3533
+ serverMetadata: slot.serverMetadata ? structuredClone(slot.serverMetadata) : void 0,
3534
+ resources: slot.resources ? cloneCatalogRecords(slot.resources) : void 0,
3535
+ resourceTemplates: slot.resourceTemplates ? cloneCatalogRecords(slot.resourceTemplates) : void 0,
3536
+ prompts: slot.prompts ? cloneCatalogRecords(slot.prompts) : void 0
3537
+ };
3696
3538
  }
3697
3539
 
3698
3540
  // src/registry-connect-loop.ts
@@ -3782,11 +3624,7 @@ function applySlotTools(ctx, slot, tools, client) {
3782
3624
  },
3783
3625
  onFinish: ({ durationMs, ok }) => {
3784
3626
  slot.operations.inFlightCalls = Math.max(0, slot.operations.inFlightCalls - 1);
3785
- pushBounded(
3786
- slot.operations.callSamples,
3787
- durationMs,
3788
- MCP_OPERATION_LIMITS.LATENCY_SAMPLES
3789
- );
3627
+ pushBounded(slot.operations.callSamples, durationMs, MCP_OPERATION_LIMITS.LATENCY_SAMPLES);
3790
3628
  if (ok) {
3791
3629
  ctx.recordSuccess(slot);
3792
3630
  ctx.recordOperation(slot, "call", "ok", void 0, durationMs, false);
@@ -3997,6 +3835,83 @@ async function attemptConnectSlot(ctx, slot) {
3997
3835
  }
3998
3836
  }
3999
3837
 
3838
+ // src/registry-disconnect.ts
3839
+ function resetDisconnectedSlotTools(slot, toolRegistry) {
3840
+ for (const t of slot.toolNames) {
3841
+ try {
3842
+ toolRegistry.unregister(t);
3843
+ } catch {
3844
+ }
3845
+ }
3846
+ slot.toolNames = [];
3847
+ slot.lazyTools = [];
3848
+ slot.serverMetadata = void 0;
3849
+ slot.resources = void 0;
3850
+ slot.resourceTemplates = void 0;
3851
+ slot.prompts = void 0;
3852
+ }
3853
+ function markLazySlotDormant(slot, events, reason) {
3854
+ slot.client = void 0;
3855
+ slot.state = "dormant";
3856
+ events.emit("mcp.server.disconnected", {
3857
+ name: slot.cfg.name,
3858
+ reason: `${reason} (dormant)`
3859
+ });
3860
+ }
3861
+
3862
+ // src/registry-health.ts
3863
+ function buildRegistryOperationalHealth(servers, disabledServers) {
3864
+ const active = Array.from(servers).map((slot) => {
3865
+ const op = slot.operations;
3866
+ const baseHealth = healthStateFor(slot.state, op, slot.cfg.enabled !== false);
3867
+ const checks = evaluateHealthThresholds(op, slot.cfg.health?.thresholds);
3868
+ return {
3869
+ name: slot.cfg.name,
3870
+ connectionState: slot.state,
3871
+ healthState: applyHealthThresholds(baseHealth, checks),
3872
+ lastSuccessAt: op.lastSuccessAt,
3873
+ lastFailureAt: op.lastFailureAt,
3874
+ lastFailureKind: op.lastFailureKind,
3875
+ lastReason: op.lastReason,
3876
+ consecutiveFailures: op.consecutiveFailures,
3877
+ failures: { ...op.failures },
3878
+ reconnectCount: op.reconnectCount,
3879
+ wakeCount: op.wakeCount,
3880
+ sleepCount: op.sleepCount,
3881
+ restartCount: op.restartCount,
3882
+ connectionLatency: summarizeLatency(op.connectionSamples),
3883
+ discoveryLatency: summarizeLatency(op.discoverySamples),
3884
+ callLatency: summarizeLatency(op.callSamples),
3885
+ inFlightCalls: op.inFlightCalls,
3886
+ peakInFlightCalls: op.peakInFlightCalls,
3887
+ recentEvents: op.recentEvents.map((event) => ({ ...event })),
3888
+ healthChecks: checks
3889
+ };
3890
+ });
3891
+ const disabled = Array.from(disabledServers).map((cfg) => {
3892
+ const operations = createMCPServerOperationState();
3893
+ return {
3894
+ name: cfg.name,
3895
+ connectionState: "idle",
3896
+ healthState: "disabled",
3897
+ consecutiveFailures: 0,
3898
+ failures: { ...operations.failures },
3899
+ reconnectCount: 0,
3900
+ wakeCount: 0,
3901
+ sleepCount: 0,
3902
+ restartCount: 0,
3903
+ connectionLatency: summarizeLatency([]),
3904
+ discoveryLatency: summarizeLatency([]),
3905
+ callLatency: summarizeLatency([]),
3906
+ inFlightCalls: 0,
3907
+ peakInFlightCalls: 0,
3908
+ recentEvents: [],
3909
+ healthChecks: []
3910
+ };
3911
+ });
3912
+ return [...active, ...disabled];
3913
+ }
3914
+
4000
3915
  // src/registry-idle.ts
4001
3916
  async function sleepIdleSlot(ctx, slot) {
4002
3917
  slot.reconnectPending = false;
@@ -4032,6 +3947,87 @@ async function sweepIdleSlots(ctx) {
4032
3947
  );
4033
3948
  }
4034
3949
 
3950
+ // src/registry-operations.ts
3951
+ function operationsForSlot(slot) {
3952
+ if (!slot.operations) slot.operations = createMCPServerOperationState();
3953
+ return slot.operations;
3954
+ }
3955
+ function recordRegistrySuccess(slot, resetFailures = true) {
3956
+ const operations = operationsForSlot(slot);
3957
+ operations.lastSuccessAt = Date.now();
3958
+ if (resetFailures) operations.consecutiveFailures = 0;
3959
+ }
3960
+ function recordRegistryFailure(slot, listeners, failureKind, reason, durationMs) {
3961
+ const operations = operationsForSlot(slot);
3962
+ const safeReason = safeOperationReason(reason);
3963
+ operations.lastFailureAt = Date.now();
3964
+ operations.lastFailureKind = failureKind;
3965
+ operations.lastReason = safeReason;
3966
+ operations.consecutiveFailures++;
3967
+ operations.failures[failureKind]++;
3968
+ recordRegistryOperation(slot, listeners, "failure", safeReason, failureKind, durationMs);
3969
+ }
3970
+ function recordRegistryOperation(slot, listeners, kind, reason, failureKind, durationMs, retain = true) {
3971
+ const operations = operationsForSlot(slot);
3972
+ const baseHealth = healthStateFor(slot.state, operations, slot.cfg.enabled !== false);
3973
+ const checks = evaluateHealthThresholds(operations, slot.cfg.health?.thresholds);
3974
+ const event = {
3975
+ serverName: slot.cfg.name,
3976
+ kind,
3977
+ at: Date.now(),
3978
+ connectionState: slot.state,
3979
+ healthState: applyHealthThresholds(baseHealth, checks)
3980
+ };
3981
+ if (reason !== void 0) event.reason = safeOperationReason(reason);
3982
+ if (failureKind !== void 0) event.failureKind = failureKind;
3983
+ if (durationMs !== void 0) event.durationMs = Math.max(0, Math.round(durationMs));
3984
+ if (retain) pushBounded(operations.recentEvents, event, MCP_OPERATION_LIMITS.RECENT_EVENTS);
3985
+ for (const listener of listeners) {
3986
+ try {
3987
+ listener({ ...event });
3988
+ } catch {
3989
+ }
3990
+ }
3991
+ }
3992
+
3993
+ // src/registry-reconnect.ts
3994
+ function scheduleRegistryReconnect({
3995
+ slot,
3996
+ events,
3997
+ log,
3998
+ maxReconnectCycles,
3999
+ baseReconnectDelayMs,
4000
+ maxReconnectDelayMs,
4001
+ recordReconnectExhausted,
4002
+ attemptReconnect
4003
+ }) {
4004
+ if (slot.reconnectPending) return;
4005
+ if (slot.reconnectCycles >= maxReconnectCycles) {
4006
+ slot.state = "failed";
4007
+ recordReconnectExhausted(slot);
4008
+ log.error(
4009
+ `MCP server "${slot.cfg.name}" giving up after ${slot.reconnectCycles} reconnect cycles. Use \`/mcp restart ${slot.cfg.name}\` to retry.`
4010
+ );
4011
+ events.emit("mcp.server.disconnected", {
4012
+ name: slot.cfg.name,
4013
+ reason: `reconnect-exhausted:${slot.reconnectCycles}`
4014
+ });
4015
+ return;
4016
+ }
4017
+ slot.reconnectPending = true;
4018
+ if (slot.reconnectTimer) {
4019
+ clearTimeout(slot.reconnectTimer);
4020
+ slot.reconnectTimer = void 0;
4021
+ }
4022
+ const base = Math.min(baseReconnectDelayMs * 2 ** slot.reconnectCycles, maxReconnectDelayMs);
4023
+ const jitter = base * MCP_CONSTANTS.RECONNECT.JITTER_FACTOR * (Math.random() * 2 - 1);
4024
+ const delay = Math.max(100, Math.round(base + jitter));
4025
+ slot.reconnectTimer = setTimeout(() => {
4026
+ slot.reconnectTimer = void 0;
4027
+ void attemptReconnect(slot);
4028
+ }, delay);
4029
+ }
4030
+
4035
4031
  // src/registry.ts
4036
4032
  var MCPRegistry = class _MCPRegistry {
4037
4033
  servers = /* @__PURE__ */ new Map();
@@ -4069,13 +4065,7 @@ var MCPRegistry = class _MCPRegistry {
4069
4065
  }
4070
4066
  async completeAuthorization(name, callbackUrl, signal) {
4071
4067
  const cfg = this.requireHttpServerConfig(name);
4072
- return completeRegistryAuthorization(
4073
- this.authorizationManager,
4074
- cfg,
4075
- name,
4076
- callbackUrl,
4077
- signal
4078
- );
4068
+ return completeRegistryAuthorization(this.authorizationManager, cfg, name, callbackUrl, signal);
4079
4069
  }
4080
4070
  async authorizationStatus(name) {
4081
4071
  const manager = requireAuthorizationManager(this.authorizationManager);
@@ -1,4 +1,4 @@
1
- import type { MCPTool } from './client.js';
1
+ import type { MCPTool } from './contracts.js';
2
2
  import { type MCPPrompt, type MCPResource, type MCPResourceTemplate, type MCPServerMetadata } from './protocol.js';
3
3
  export interface MCPCapabilityManifest {
4
4
  tools: MCPTool[];
@@ -1,5 +1,5 @@
1
1
  import type { MCPHealthThresholds } from '@wrongstack/core/types';
2
- import type { ConnectionState } from './client.js';
2
+ import type { ConnectionState } from './contracts.js';
3
3
  /** Operator-facing health state. Intentionally separate from transport lifecycle state. */
4
4
  export type MCPHealthState = 'disabled' | 'dormant' | 'connecting' | 'healthy' | 'degraded' | 'failed';
5
5
  export type MCPFailureKind = 'transport' | 'protocol' | 'tool';
@@ -1,4 +1,4 @@
1
- import type { ConnectionState } from './client.js';
1
+ import type { ConnectionState } from './contracts.js';
2
2
  import type { MCPPrompt, MCPResource, MCPResourceTemplate, MCPServerMetadata } from './protocol.js';
3
3
  import type { MCPRegistryCatalog } from './registry-types.js';
4
4
  export declare function collectCatalogPages<Page extends {
@@ -1,7 +1,8 @@
1
1
  import type { EventBus } from '@wrongstack/core/kernel';
2
2
  import type { ToolRegistry } from '@wrongstack/core/registry';
3
3
  import type { Logger } from '@wrongstack/core/types';
4
- import { MCPClient, type MCPTool } from './client.js';
4
+ import { MCPClient } from './client.js';
5
+ import type { MCPTool } from './contracts.js';
5
6
  import { type MCPFailureKind, type MCPOperationKind, type MCPOperationListener } from './operations.js';
6
7
  import type { ServerSlot } from './registry-slots.js';
7
8
  import type { MCPRegistryOptions } from './registry-types.js';
@@ -1,7 +1,8 @@
1
1
  import type { MCPServerConfig, Tool } from '@wrongstack/core/types';
2
- import type { ConnectionState, MCPClient } from './client.js';
3
- import type { MCPPrompt, MCPResource, MCPResourceTemplate, MCPServerMetadata } from './protocol.js';
2
+ import type { MCPClient } from './client.js';
3
+ import type { ConnectionState } from './contracts.js';
4
4
  import type { MCPServerOperationState } from './operations.js';
5
+ import type { MCPPrompt, MCPResource, MCPResourceTemplate, MCPServerMetadata } from './protocol.js';
5
6
  export interface ServerSlot {
6
7
  cfg: MCPServerConfig;
7
8
  client?: MCPClient | undefined;
@@ -3,7 +3,7 @@ import type { ToolRegistry } from '@wrongstack/core/registry';
3
3
  import type { Logger, MCPServerConfig } from '@wrongstack/core/types';
4
4
  import type { MCPAuthorizationProvider } from './authorization.js';
5
5
  import type { MCPAuthorizationManager } from './authorization-manager.js';
6
- import type { ConnectionState } from './client.js';
6
+ import type { ConnectionState } from './contracts.js';
7
7
  import type { MCPPrompt, MCPResource, MCPResourceTemplate, MCPServerMetadata } from './protocol.js';
8
8
  export interface MCPRegistryOptions {
9
9
  toolRegistry: ToolRegistry;
@@ -1,10 +1,11 @@
1
1
  import type { MCPServerConfig } from '@wrongstack/core/types';
2
2
  import type { MCPAuthorizationStartResult, MCPAuthorizationStatus } from './authorization-manager.js';
3
- import type { ConnectionState, MCPClient } from './client.js';
3
+ import type { MCPClient } from './client.js';
4
4
  import { type MCPInsertionPolicy, type MCPPromptInsertion, type MCPResourceInsertion } from './content-selection.js';
5
+ import type { ConnectionState } from './contracts.js';
5
6
  import { type MCPOperationListener, type MCPServerOperationalHealth } from './operations.js';
6
- import type { MCPRegistryCatalog, MCPRegistryOptions } from './registry-types.js';
7
7
  import type { MCPGetPromptResult, MCPPrompt, MCPReadResourceResult, MCPResource, MCPResourceTemplate } from './protocol.js';
8
+ import type { MCPRegistryCatalog, MCPRegistryOptions } from './registry-types.js';
8
9
  export type { MCPRegistryCatalog, MCPRegistryOptions } from './registry-types.js';
9
10
  export declare class MCPRegistry {
10
11
  private readonly servers;
@@ -1,4 +1,4 @@
1
- import type { MCPTool } from '../client.js';
1
+ import type { MCPTool } from '../contracts.js';
2
2
  export interface MockToolResponse {
3
3
  content: unknown;
4
4
  isError?: boolean | undefined;
@@ -1,3 +1,3 @@
1
- import type { MCPTool } from './client.js';
1
+ import type { MCPTool } from './contracts.js';
2
2
  export declare function normalizeMCPTools(value: unknown): MCPTool[];
3
3
  //# sourceMappingURL=tool-schema.d.ts.map
@@ -1,6 +1,6 @@
1
1
  import * as https from 'node:https';
2
2
  import { type MCPAuthorizationProvider } from './authorization.js';
3
- import type { ConnectionState, MCPTool } from './client.js';
3
+ import type { ConnectionState, MCPTool } from './contracts.js';
4
4
  import type { MCPServerMetadata } from './protocol.js';
5
5
  export interface HttpTransportOptions {
6
6
  name: string;
@@ -1,4 +1,4 @@
1
- import type { JsonRpcResponse, ToolCallResult } from './client.js';
1
+ import type { JsonRpcResponse, ToolCallResult } from './contracts.js';
2
2
  import { BaseHTTPTransport, type HttpTransportOptions } from './transport-base.js';
3
3
  /**
4
4
  * SSE transport for MCP over HTTP.
@@ -1,4 +1,4 @@
1
- import type { JsonRpcResponse, ToolCallResult } from './client.js';
1
+ import type { JsonRpcResponse, ToolCallResult } from './contracts.js';
2
2
  import { BaseHTTPTransport, type HttpTransportOptions } from './transport-base.js';
3
3
  /**
4
4
  * Streamable HTTP transport for MCP.
@@ -1,5 +1,6 @@
1
1
  import type { Permission, Tool } from '@wrongstack/core/types';
2
- import type { MCPClient, MCPTool } from './client.js';
2
+ import type { MCPClient } from './client.js';
3
+ import type { MCPTool } from './contracts.js';
3
4
  /**
4
5
  * Resolves the live client for a tool call. A plain {@link MCPClient} for eager
5
6
  * servers, or a thunk that connects-on-demand for lazy/dormant servers (the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/mcp",
3
- "version": "0.309.1",
3
+ "version": "0.310.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.309.1"
29
+ "@wrongstack/core": "0.310.0"
30
30
  },
31
31
  "devDependencies": {
32
32
  "@types/node": "^26.2.0",