import { z } from 'zod'; import type { MCPClient } from './client.js'; /** * Keeps an MCP connection alive across a transport that drops. * * `MCPClient.connect()` is called exactly once, by whoever built the client. * `transport.onClose` sets `status = 'disconnected'`, emits the lifecycle * event and rejects everything pending — and nothing in the module schedules * another attempt. So one blip, one server restart, one laptop sleep, and the * plugin's tools are gone for the rest of the process while the plugin itself * still reports as enabled: the failure is silent at the layer a host looks at. * * This is a supervisor rather than a retry inside `connect()` on purpose. A * caller awaiting `connect()` wants to know whether the first attempt worked; * burying an unbounded retry in it would turn a fast, actionable failure at * startup into a hang. Recovery after a connection that once succeeded is a * different question, and it belongs to a different object. */ /** How the supervisor backs off. Every field has a default. */ export interface MCPReconnectOptions { /** Defaults to true. `false` makes this object inert rather than absent. */ readonly enabled?: boolean; /** First wait, in ms. Defaults to 500. */ readonly initialDelayMs?: number; /** Ceiling for the exponential wait, in ms. Defaults to 30_000. */ readonly maxDelayMs?: number; /** Attempts before giving up. Defaults to 6. */ readonly maxAttempts?: number; /** * Called after a reconnect succeeds. * * A reconnected client is not the same as one that never dropped: its * server may have restarted with a different tool list, and every * subscription the host made through `onNotification` was made against a * transport that no longer exists. The supervisor cannot know what a host * needs to redo, so it says when rather than guessing what. */ readonly onReconnected?: () => void | Promise; /** Called when the attempts are exhausted. */ readonly onGaveUp?: (attempts: number) => void; } /** * Watches one client and reconnects it when its transport drops. * * ## Stop it before you disconnect deliberately * * `MCPClient.disconnect()` emits the same `mcp_client_disconnected` event the * transport does when it dies, and the event carries nothing that tells the * two apart. So a supervisor that is still attached when a host tears its * client down will read the teardown as a fault and reconnect the thing the * host just closed. * * `stop()` is therefore part of the teardown sequence, not an optimisation: * call it BEFORE `disconnect()`. The ordering is the contract because the * event cannot be, and widening the event to carry a cause would change a * published union for every consumer. */ /** * Where the policy comes from, read fresh. * * A function rather than a value, and that is the difference between a * configuration seam that is live and one that merely exists: an operator * raising `maxAttempts` during an outage wants the retry that is happening * NOW to take it, not the next process. `ConfigRegistry` supplies * `() => scope.get()`; a caller with a fixed policy supplies a constant. */ export type MCPReconnectPolicySource = () => MCPReconnectOptions; export declare class MCPReconnectSupervisor { private readonly client; private readonly readPolicy; private unsubscribe?; private stopped; private inFlight; private timer?; constructor(client: MCPClient, options?: MCPReconnectOptions | MCPReconnectPolicySource); /** * The policy as of right now. * * Called at each decision point rather than cached in a field. Caching it * in the constructor is what made this a declaration nothing drove: the * registry could be updated and the supervisor would go on using the * numbers it read at start-up. */ private policy; /** Begin watching. Idempotent. */ start(): void; /** * Stop watching and cancel any pending attempt. * * Safe to call more than once, and safe to call from inside a reconnect — * the loop checks `stopped` between waits, so a teardown during a backoff * does not have to wait it out. */ stop(): void; private recover; private wait; } /** * The policy as a schema, so a `ConfigRegistry` can validate an operator's * patch against it. * * The callbacks are absent from it on purpose: `onReconnected` and * `onGaveUp` are functions a host wires up in code, and neither survives * JSON or belongs in a file an operator edits. What is here is exactly the * part that is a NUMBER somebody wants to change during an outage. */ export declare const MCPReconnectOptionsSchema: z.ZodObject<{ enabled: z.ZodDefault; initialDelayMs: z.ZodDefault; maxDelayMs: z.ZodDefault; maxAttempts: z.ZodDefault; }, "strip", z.ZodTypeAny, { enabled: boolean; initialDelayMs: number; maxDelayMs: number; maxAttempts: number; }, { enabled?: boolean | undefined; initialDelayMs?: number | undefined; maxDelayMs?: number | undefined; maxAttempts?: number | undefined; }>; //# sourceMappingURL=reconnect.d.ts.map