import { RunEvent } from '@theokit/sdk'; import { M as McpServersMap } from './types-DdltPCj2.js'; import 'zod'; /** * M82 — per-turn, per-server MCP health. * * ## The gap * * The SDK emits `mcp_server_failed` as a `RunEvent`, and nothing turned that into STATE. So a * consumer's sink read the payload **structurally** — duck-checking `type` and `serverName` — to * avoid pinning an SDK version. A sink doing that is compensating for a typed surface that does not * reach it, and the compensation silently stops working the day a field is renamed. * * ## Two behaviours that are corrections, not taste * * **Clear per turn.** A run failure is about THIS turn. Without the clear, a server that failed once * stays red forever, the operator learns to ignore the indicator, and the indicator becomes worse * than nothing. * * **Deduplicate by server name.** The SDK emits once per failing server per run, but a turn can span * retries — and a list that grows per attempt reports "three broken servers" for one. * * ## One channel, deliberately * * `loadMcpJson`'s `onWarn` drains here too. "server X was ignored" (config time) and "server X failed * to list" (run time) are the same question for whoever is looking: is this server usable? Two * channels means the operator checks one and misses the other. * * The asymmetry is stated rather than hidden: config warnings survive `startTurn`, because they are * about the FILE and stay true until it changes. Clearing them every turn would make a misconfigured * server flicker. */ /** * The `mcp_server_failed` member of the SDK's `RunEvent` union. * * Derived by DISCRIMINANT rather than imported by name: the SDK publishes the union but not the * member interface, and keying on `type` survives a rename of that interface — which importing the * name would not. One less thing that can drift without a red build. */ type McpServerFailedEvent = Extract; /** Where a failure was observed. */ type McpFailureSource = 'run' | 'config'; /** One unusable MCP server, as the UI should show it. */ interface McpFailure { /** The server as named in the MCP configuration, so a consumer can match it to what it lists. */ readonly serverName: string; /** Why — a spawn error, a handshake timeout, an ignored config entry. */ readonly message: string; readonly source: McpFailureSource; } interface McpHealthSink { /** * Feed a `RunEvent`. Anything that is not `mcp_server_failed` is ignored. * * Typed against the SDK's union rather than duck-checked — which is the point of the milestone: * the consumer was reading fields structurally precisely because this type did not reach it. */ sink(event: RunEvent): void; /** Feed a `loadMcpJson` warning. Same list, because it is the same question. */ onWarn(message: string): void; /** Begin a turn: clears run failures, keeps config warnings. */ startTurn(): void; /** The servers currently believed unusable. */ current(): readonly McpFailure[]; } declare function createMcpHealthSink(): McpHealthSink; /** * What the agent's MCP servers are DOING — as far as this package can honestly know * (usetheokit/theokit#192). * * ## The distinction that made this worth building * * `loadMcpJson` reads the FILE. A `/mcp` command built on it shows what is *configured*, and that * is not the question a user opens the command to ask: they open it to find the server that failed * its handshake, or the one they wrote down that never came up. Configuration and state are * different facts, and only one of them is useful when something is wrong. * * ## Why a composition rather than a new read * * The handshake belongs to `@theokit/sdk`; nothing in this package ever connects to a server. But * the SDK already emits `mcp_server_failed` per failing server, and `createMcpHealthSink` already * turns that into state (M82). So the SERVER-level answer is a projection of two facts this package * already holds — `configured − failed = loaded` — rather than a second source of truth to keep in * sync with the first. * * The turn semantics deliberately are NOT re-derived here. A run failure is cleared by * `startTurn()`, and re-implementing that rule would give the ecosystem two copies of it to drift. * This function reads whatever the sink currently believes. * * ## What this does NOT answer, stated rather than implied * * **Which tools each server exposed.** That inventory exists only as the agent loop's resolved tool * table (`@theokit/sdk`, `internal/agent-loop/loop-types.ts`), the run-event union carries no * event for it, and this package cannot derive it: an MCP tool is only observable here once it is * CALLED, by its `mcp__` name, which reports use and not availability. * * Closing that half needs the SDK to emit the inventory — as an EVENT rather than a getter, because * the state is run-scoped: with `mcpLifecycle: 'run'` a server may not exist by the time a caller * asks, so a getter would have to answer about something already gone. */ /** What is known about one MCP server right now. */ type McpServerStatus = /** In the configuration, and nothing has reported it failing. */ 'loaded' /** In the configuration, and the run reported it failing. */ | 'failed' /** Written down by the user, and REFUSED by the loader — so it is in no configuration map. */ | 'ignored'; interface McpServerState { /** The server as named in `.mcp.json`, so a consumer can match it to a row it already shows. */ readonly serverName: string; readonly status: McpServerStatus; /** * Why, when the status is not `loaded`. * * The reason is the actionable half: a red row with no message sends the operator to the logs for * something the framework already knows. */ readonly message?: string; } /** * Project the configured servers and the observed failures into one per-server status list. * * @param configured - what `loadMcpJson` returned. * @param health - the sink fed by `RunEvent`s and by `loadMcpJson`'s `onWarn`. * @returns configured servers in their declared order, then any server the loader refused. */ declare function mcpInventory(configured: McpServersMap, health: McpHealthSink): readonly McpServerState[]; export { type McpFailure, type McpFailureSource, type McpHealthSink, type McpServerFailedEvent, type McpServerState, type McpServerStatus, createMcpHealthSink, mcpInventory };