import type { ElicitResult } from "@modelcontextprotocol/client"; import type { AgentTool } from "@kenkaiiii/gg-agent"; import type { MCPServerConfig } from "./types.js"; import { McpCatalogCache } from "./catalog-cache.js"; import { type SharedMcpPool } from "./shared-pool.js"; /** Per-server connection outcome for the dashboard / non-interactive list. */ export interface MCPConnectResult { name: string; ok: boolean; toolCount: number; tools: AgentTool[]; error?: string; /** True when the server returned 401/Unauthorized and an OAuth login is * required before it can connect. The UI surfaces this as "requires login". */ requiresAuth?: boolean; } /** Outcome of an interactive remote-MCP OAuth login. */ export interface MCPLoginResult { ok: boolean; toolCount: number; error?: string; } /** Terminal state of one server's connection attempt, awaited by `whenConnected`. */ type ConnectionOutcome = { ok: true; } | { ok: false; error: string; }; /** * A server-initiated request for user input, mid tool call. The host resolves * it by showing the user a form built from `requestedSchema` and returning * their answer — or `decline` / `cancel`. */ export interface MCPElicitation { /** Which configured server is asking. */ server: string; /** Human-readable prompt from the server. */ message: string; /** JSON Schema object describing the fields to collect. */ requestedSchema: Record; } export type MCPElicitHandler = (request: MCPElicitation) => Promise; export declare class MCPClientManager { private servers; /** * Per-server connection settlement, so a caller holding a cached-only tool * can wait for the live client instead of failing or hanging forever. */ private connections; /** * In-flight session rebuilds, keyed by server name. N tool calls to the same * server all 404 at once when its session expires; without coalescing each * would spawn its own reconnect and they would race to replace each other in * `this.servers`. Sharing one promise means exactly one reconnect per outage. */ private reconnecting; /** * Claims on process-wide shared connections, keyed by server name. Held so * `dispose` can hand them back: the pooled child only exits once the LAST * manager holding it releases, so leaking a claim here would pin the process * for the daemon's lifetime — the exact bug sharing is meant to fix. */ private sharedHandles; private readonly catalogCache; /** * Opt into the 2026-07-28 revision. Off by default: `mode: "auto"` probes with * `server/discover` before falling back to `initialize`, and a legacy stdio * server that ignores pre-`initialize` requests pays the full probe timeout. */ private readonly modernProtocol; /** * Host callback for server-initiated `elicitation/create`. Absent means we * declare no elicitation capability at all — which is protocol-legal and is * how the CLI runs, since it has nowhere to render the form. */ private readonly onElicit?; /** * Pool backing `shared: true` servers. Defaults to the daemon-wide singleton; * injectable so tests can exercise sharing without touching global state. */ private readonly pool; /** * Fired when a connected server's transport closes on its own — a crashed or * exited child, not our own `dispose`. The pool uses it to drop a dead * connection so the next session rebuilds instead of inheriting a corpse. */ private readonly onServerClosed?; /** Set while `dispose` runs, so our own teardown is not reported as a death. */ private disposing; constructor(opts?: { catalogCache?: McpCatalogCache; modernProtocol?: boolean; onElicit?: MCPElicitHandler; sharedPool?: SharedMcpPool; onServerClosed?: (name: string) => void; }); /** * Build a `Client` for one server, declaring elicitation support and wiring * the handler when the host can actually prompt the user. * * Form mode only. URL mode would have us open a browser tab in the middle of * a tool call with no user gesture behind it — the SDK rejects URL-mode * requests for us when the `url` capability is undeclared. */ private createClient; /** * Version-negotiation options for a real (non-probe) connect. `probe()` is * spawn-per-invocation and deliberately never negotiates: a legacy stdio * server that ignores the discovery request would stall it for the whole * probe timeout, turning "validate this server" into a 30s hang. * * The probe timeout is transport-aware because the SDK's timeout VERDICT is: * * - **stdio** — silence on a local pipe means a legacy server, and the SDK * falls back to `initialize`. The probe runs on a disposable sibling * process, so the fallback is clean. Waiting the full connect timeout to * reach a conclusion we can draw in seconds is pure dead time, so cap it * short. * - **HTTP** — silence means an outage, and the SDK REJECTS the connect * rather than falling back. A short timeout would therefore turn a slow * cold start into a hard connection failure, so inherit the full connect * timeout and let the normal timeout handling deal with a real outage. */ private negotiationOptions; /** * Claim the pooled connection for a shared server and adopt its tools. * * Shaped like `connectServer` (resolve tools / throw on failure) so the * caller's `allSettled` handling is identical for both kinds. The pool * reports failure in-band, so it is rethrown here with the message the pool * already formatted. */ private connectShared; /** Get-or-create the settlement record for one server name. */ private connectionSlot; /** * Resolve once a server's connection attempt has settled. Returns `ok:false` * with the failure reason when that server could not connect, and times out * rather than hanging when no attempt is ever made (e.g. a server that was * removed from the config since the catalog cache was written). */ whenConnected(name: string, timeoutMs?: number): Promise; connectAll(configs: MCPServerConfig[]): Promise; /** * Connect every enabled server and return one result per server (success → * ok + toolCount; failure → ok:false with a human-readable error string). * Keeps successfully connected servers in `this.servers`. * * Shareable servers — stdio, unless opted out with `shared: false` — are not * connected here at all: they are claimed from the process-wide pool, so every * session in the daemon multiplexes over one child process instead of spawning * its own. The split is invisible to callers — results and tools come back in * the same shape, `whenConnected` settles for both kinds, and `dispose` * releases the pooled claims — so sharing applies wherever a manager is used * (sessions, CLI, subagents) without each call site opting in. */ connectAllDetailed(configs: MCPServerConfig[]): Promise; /** * Connect a single server, list its tools, then close that client so the * probe connection doesn't accumulate in `this.servers`. Used to validate a * server before persisting it. */ probe(config: MCPServerConfig): Promise; /** * Run the interactive OAuth login for one remote MCP server end-to-end: * start a loopback callback server, let the SDK open the browser via * `onAuthorizationUrl`, capture the redirect, exchange the code, then verify * the authorized connection by listing tools. Tokens are persisted by the * provider so later (non-interactive) connects succeed silently. * * `onAuthorizationUrl` is invoked with the authorize URL so the host can open * it (the gg-app broadcasts it to the webview, which opens the system * browser; the CLI prints it). Never throws — returns `{ ok:false, error }`. */ login(config: MCPServerConfig, onAuthorizationUrl: (url: string) => void, timeoutMs?: number): Promise; private connectServer; /** * Is this failure a recoverable expired HTTP session? * * Stdio servers are excluded: they have no HTTP session to expire, so a 404 * from one means something else entirely and respawning the child process * mid-call would be a surprise. An already-aborted call is excluded too — * reconnecting to replay work the user cancelled resurrects it. */ private canRecoverSession; /** A local "the transport went away mid-request" failure, as the SDK reports it. */ private isConnectionClosed; /** * Rebuild one server's connection from its stored config, replacing the dead * entry in `this.servers`. Concurrent callers share a single rebuild. * * Rejects if the reconnect itself fails, so the caller reports a real error * rather than retrying against a client that was never replaced. */ private reconnectServer; /** * Connect a single HTTP (Streamable HTTP or SSE) server and return the live * client + transport. Transport selection: * - `transport === "sse"` → legacy SSE directly (Playwright MCP `--port`). * - otherwise → Streamable HTTP first, SSE fallback for older servers. * * An OAuth provider is attached only for REMOTE servers — localhost never * needs OAuth and attaching it there is dead weight that can misdiagnose a * protocol mismatch as a login requirement. */ private connectHttp; /** * Protocol era actually negotiated with a server, as the SDK reports it. * Undefined before a connect completes; with negotiation off the SDK always * settles on `legacy` (the 2025 `initialize` handshake). */ private negotiatedEra; /** Previously negotiated era for this exact server config, if still cached. */ private cachedEra; dispose(): Promise; } export {}; //# sourceMappingURL=client.d.ts.map