import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { type Tool as McpTool, type Prompt, type GetPromptResult } from '@modelcontextprotocol/sdk/types.js'; import '@utcp/http'; import '@utcp/mcp'; import '@utcp/cli'; import { type CallTemplate } from '@utcp/sdk'; import { CodeModeUtcpClient } from '@utcp/code-mode'; import { type ProxiedTool } from '@bevel-software/platform-mcp-core'; import type { HexisMcpConfig } from './config.js'; /** * How long `shutdown()` waits for background discovery to come to rest * before closing anyway. * * Discovery stops at its phase boundaries, but a phase already in flight is * not cancellable: a deployment fetch runs to its own timeout, a credential * swap drains for up to 15s. Shorter than teardown.ts's SHUTDOWN_GRACE_MS on * purpose — the CLI's watchdog is the last resort for a hang, and this cap * has to expire INSIDE it to be the thing that acts first. */ export declare const DISCOVERY_SHUTDOWN_GRACE_MS = 5000; /** * Register every manual, then flatten what was discovered. * * The deployment's own manual failing is fatal: without it there is no core * toolset and the client would come up looking empty for no stated reason. A * local manual failing is isolated and logged — one unreachable localhost * server must not cost the caller everything else. * * The deployment's copies of the code-mode meta-tools are removed from the * REGISTRY, not merely hidden from the MCP listing: `list_tools` and * `call_tool_chain` here reflect over the client's tool repository, so a copy * left registered would still be advertised to — and callable from — a chain, * which would run it against the remote registry that cannot see a local-only * tool. This process serves its own trio instead, over the merged registry. * Exported for the catalog tests. */ export declare function discoverTools(client: CodeModeUtcpClient, remote: CallTemplate, local: CallTemplate[]): Promise; /** * Belt to `discoverTools`'s registry removal: whatever a repository * implementation declined to remove must still never reach the MCP listing, * where a remote `list_tools` would shadow — or duplicate — the local trio, * and where a stale deployment's retired tool would be listed as callable. */ export declare function withoutRemoteMetaTools(tools: ProxiedTool[]): ProxiedTool[]; /** * Validate + dedupe the discovered tools into MCP listing entries. * * Remote tools are added first and win a name collision, because a local `.tool` * shadowing `read_file` would silently redirect the core toolset. Every drop is * logged with its reason: a tool going missing is otherwise invisible, since a * client rejects the whole listing over one bad entry rather than telling * anyone which one. */ export declare function listedTools(tools: ProxiedTool[], /** * The views the deployment's tools carry, by tool name — `McpAppManifest.tools`. * * Joined on HERE because a UTCP manual has nowhere to carry it: a tool * arrives from discovery with a name, a description and a schema, and this * is the one point where it becomes a listing entry a client reads. */ views?: Record, /** * The read-only hint of each platform tool and meta-tool, by name, as the * deployment serves them (`fetchToolHints`). Joined on here for the same * reason as the views: the deployment's tools reach this process through its * MCP endpoint, and the UTCP MCP protocol keeps no annotation on the way in. * A tool from a local manual is never in it, and lists no hint. */ hints?: Readonly>): McpTool[]; /** The live server plus its teardown — see `createHexisMcpServer`. */ export interface HexisMcpHandle { server: Server; /** * Settles when background tool discovery has finished, successfully or not. * It NEVER rejects — a failure is reported through the tool surface and on * stderr, not by throwing at whoever happens to be holding this. * * The server does not need it: every request handler awaits it already. It * is here for an embedding host that wants to know when the catalog is * real, and for the tests that pin exactly that ordering. */ ready: Promise; /** * Close the UTCP client — and with it every communication protocol it * holds, including @utcp/mcp's stdio transports, whose close() is what * actually terminates the spawned local server processes. Idempotent and * never throws: teardown runs on the way out, where nothing can act on an * error anyway. */ shutdown: () => Promise; } /** * The name of the single tool `tools/list` serves when discovery failed. * * A tool rather than an empty list, because an empty list is what a correctly * configured but tool-less workspace looks like: indistinguishable, from * inside a client, from a server that could not reach its deployment. The * reason travels in the description, which is the one string every client * shows next to a tool. */ export declare const DISCOVERY_NOTICE_TOOL = "hexis_unavailable"; /** The one-tool listing that carries a discovery failure to the client. */ export declare function discoveryNoticeTool(reason: string): McpTool; /** * Stand up the local MCP server — ANSWER FIRST, DISCOVER AFTER. * * What comes back is a server that can be connected and can answer * `initialize` right away. Everything expensive — fetching the manuals, * materializing every plugin, spawning their stdio servers, building the UTCP * client, discovering tools — runs in the BACKGROUND behind `ready`, and every * request handler awaits it before answering. This ordering is the whole point * of the change: a cold `npx` start downloads a dependency tree and then one * archive per plugin, all of which used to happen BEFORE the transport was * connected, so the client timed out (Claude Code allows 30s) on a server that * was working perfectly and had simply not said hello yet. * * Only the two small deployment reads stay in front of the handshake: * `/api/config`, which names the MCP endpoint, and the agent instructions — * because `instructions` travels in the `initialize` RESULT and is the one * thing that genuinely cannot be sent later. Both are single JSON GETs against * the workspace, not the downloads this reordering exists to move out of the * way. They are also where a rejected connection key still surfaces as a * startup failure with its own sentence, as before. * * A failure AFTER that point no longer kills startup. The process stays up and * says what happened, on stderr and through the tools themselves: `tools/list` * answers with one notice tool carrying the reason, `tools/call` with an MCP * error. Exiting there would look, from inside a client, exactly like a server * that was never configured. * * Returned WITH its `shutdown`, because the spawned stdio servers are held by * the UTCP client, not the SDK `Server` — a caller that lets this process * exit without closing the client is relying on the stdin-EOF cascade to end * its grandchildren, and that cascade observably leaks (an orphaned server * then holds its plugin root hostage for every later instance). */ export declare function createHexisMcpServer(config: HexisMcpConfig, version: string, options?: { /** * Whether, and how often at most, to check that the deployment's tools * and skills are still the ones this server registered. The check runs on * ACTIVITY — a tool call finishing, a `tools/list` arriving — never on a * timer: an idle connection asks the deployment nothing, so a deployment * with fifty connected laptops nobody is using answers nothing (see * `catalog-watch.ts`). `minIntervalMs` (default * `CATALOG_CHECK_MIN_INTERVAL_MS`) is the least time between two checks. * `false` turns it off, which freezes this server's toolset at what * discovery found (what it did before the check existed). Here for tests * and embedding hosts — the CLI does not expose it, because the default * is the contract the knowledge base's guide states. */ catalogCheck?: false | { minIntervalMs?: number; }; }): Promise; /** * The prompt list, rebuilt from the deployment's `list_skills`. An upstream * failure PROPAGATES: a dead key or unreachable deployment must surface as the * request's error, not as a workspace that "has no prompts". Exported for the * catalog tests. */ export declare function listSkillPrompts(config: HexisMcpConfig): Promise<{ prompts: Prompt[]; }>; /** * One skill as a prompt. `Unknown skill` (InvalidParams — the caller's mistake) * is reserved for a lookup that SUCCEEDED and found nothing; an upstream * failure propagates instead of masquerading as it. Exported for the catalog * tests. */ export declare function getSkillPrompt(config: HexisMcpConfig, name: string): Promise; //# sourceMappingURL=server.d.ts.map