/** * The MCP tools registry (DES-MCP-TOOLS-001 §5, §8; slice S2): register upstream MCP servers, * keep their tool lists honest, and hold their secrets out of every response. * * - **Preview, then save.** `preview` probes the server and answers its tools with their classes * and a `previewHash` over the exact config and tool schemas it saw. `save` takes ONLY that hash * and registers what the preview showed. A save without a hash, or with one this daemon did not * issue (or issued more than {@link PREVIEW_TTL_MS} ago), is refused, so nothing is registered * that the operator did not see. * - **Tool diff.** `test` probes a registered server again and records the difference. A tool * whose schema hash changed, and a tool the server added, is `unregistered` (D-5 denies it) * until it is previewed and saved again. A tool the server dropped is `gone`: kept, never * deleted, so old call records still resolve. Health turns `failing` after 3 consecutive * failed probes. * - **Secrets.** Only a reference is stored. A value is resolved just for the probe and scrubbed * from its result (`probe.ts`). */ import type { McpDiscoveredServer, McpPreviewResponse, McpRestEgress, McpRestMapping, McpServer, McpServersResponse, McpServerTestResponse, McpTool, McpToolAnnotations, McpToolClass, McpToolDiff, McpToolStatus, McpUpstreamKind } from '../core/types.js'; import { type HashedRestTarget } from './classify.js'; import type { McpUpstreamConfig, Prober } from './probe.js'; import type { McpRegistryStore, McpToolRecord } from './registry-store.js'; import { type SecretStore } from './secrets.js'; /** How long a preview can be saved. */ export declare const PREVIEW_TTL_MS: number; /** Consecutive failed probes before a server's health reads `failing`. */ export declare const HEALTH_FAILING_AFTER = 3; /** A refusal the routes map to a status code. */ export declare class McpRegistryError extends Error { readonly status: 400 | 403 | 404 | 409 | 500 | 501 | 502 | 503; readonly code: string; constructor(status: 400 | 403 | 404 | 409 | 500 | 501 | 502 | 503, code: string, message: string); } /** What the registry holds for one brokered call's subject. */ export interface McpCallTarget { config: McpUpstreamConfig; /** A `rest` tool's request mapping (slice S5a); `null` for an MCP server's tool. */ rest: McpRestMapping | null; annotations: McpToolAnnotations | null; classOverride: McpToolClass | null; registered: boolean; /** * (G2) The tool is a `rest` tool saved before its hash covered the server's target (or whose * target was changed outside a save): refused until the operator previews and saves it again. */ resaveRequired: boolean; } /** A registered, enabled tool, as the tool list judges it (`callableTools`). */ export interface McpCallableTool { server: string; tool: string; kind: McpUpstreamKind; annotations: McpToolAnnotations | null; classOverride: McpToolClass | null; description: string | null; inputSchema: Record | null; } export interface McpRegistryDeps { store: McpRegistryStore; secrets: SecretStore; probe: Prober; discover: (managed: ReadonlySet) => McpDiscoveredServer[]; now?: () => number; env?: NodeJS.ProcessEnv; } export declare class McpRegistry { private readonly deps; private readonly previews; private readonly now; private readonly env; constructor(deps: McpRegistryDeps); get secretStoreAvailable(): boolean; list(): Promise; get(name: string): Promise; /** * Probe `config` and hold exactly what it showed, for `save` (crew#719: with `stagedSecret`, the * secret this server's own keychain reference will hold — probed with now, written only when the * save commits, so the OS store and the registry row land together or not at all). */ preview(config: McpUpstreamConfig, stagedSecret?: string | null): Promise; /** * Register exactly what a preview showed. Saving over an existing name re-registers it: every * tool of the preview becomes `registered`, a tool it no longer lists becomes `gone`, and a class * override survives only while the schema is unchanged. * * (G1, DES-data-connectors §3) A tool's `enabled` switch is {@link enabledOnSave}: a `write` or * `destructive` tool that is new, or whose schema changed, is registered DISABLED. `enabledWrites` * is the operator's per-tool grant (the preview's write checkbox): each name must be a write or * destructive tool of this preview, and is registered enabled. The route allows a grant from a * human operator only and audits it. A grant enables a tool; `MCP-FIRST-USE` still asks on its * first call. * * (G2) `allowOrigin` is the operator naming the exact origin a `rest` server's calls go to. It is * required when the preview's `egress.confirmRequired` (see {@link checkEgress}) and remembered * on the record, so a re-save over the same origin does not ask again. */ save(previewHash: string | undefined, enabledWrites?: readonly string[], allowOrigin?: string | null): Promise; /** * Run `commit`; if it throws and this save had written the OS secret store, put the entry back * exactly as it was — deleted when there was no `prior` value, restored to `prior` when there * was (crew#719, review of PR #724). Either way the registry row and the credential it * references end up as they were before the save. * * A rollback that ALSO fails is reported rather than swallowed: the operator is told what was * left behind and under which account, because nothing else can find a keychain entry for them. */ private restoreSecretOnFailure; /** * (G1) Refuse an `enabledWrites` list that does not fit the held preview: a name it does not list * (400 `unknown_tool`), or a tool that would not be a write once saved (400 `not_a_write_tool`) — * by its annotations, or by a class override that survives the save (schema unchanged). The route * calls this before it withdraws any approval, so a refused grant changes nothing. An unknown or * expired preview is left for `save` to refuse. */ checkWriteGrants(previewHash: string, enabledWrites: readonly string[]): Promise; /** * (G2, DES-data-connectors §3) Refuse a save whose egress the operator has not confirmed: when * the held preview's `egress.confirmRequired` (the base URL matches none of the spec's `servers`, * or its host is a loopback/private address), `allowOrigin` must be exactly `egress.origin` * (409 `egress_unconfirmed`). An `allowOrigin` that names another origin is refused the same way * even when no confirmation is needed, and one for a server that is not `rest` is a 400 * `egress_not_rest`. Like {@link checkWriteGrants} it changes nothing, so the route calls it * before any approval is withdrawn; `save` judges it again inside its write. An unknown or * expired preview is left for `save` to refuse. */ checkEgress(previewHash: string, allowOrigin: string | null): Promise; /** * (G2) Whether every saved tool of the `rest` server `name` has a hash that covers the server's * target. `false` for a registration saved before G2 (or whose url or auth header changed outside * a save): its approvals never covered the target, so a re-save withdraws them all. `true` for an * unknown server and for an MCP server. */ targetBound(name: string): Promise; /** What a held preview would register: its server name and each tool's schema hash (S6). */ peekPreview(previewHash: string): { name: string; tools: Array<{ name: string; schemaHash: string; }>; egress: McpRestEgress | null; target: HashedRestTarget | null; } | null; setServerEnabled(name: string, enabled: boolean): Promise; /** Remove a server, and the keychain secret it owns (`keychain:wicked-mcp/`). */ remove(name: string): Promise; /** * Set a tool's switch and/or class override. (G1) A patch that leaves a tool ENABLED and a WRITE * (by its own annotations or by its class after the patch) when it was not both before is a write * grant: turning a write on, or re-classing an enabled read as a write. It is refused (403 * `write_grant_human_only`) unless `opts.mayGrantWrite`, which the route sets for a human operator * only. Turning a tool off, and enabling a read, need no grant. `grantedWrite` says whether this * patch was one, so the route audits exactly the grants. */ patchTool(subject: string, patch: { enabled?: boolean | undefined; classOverride?: McpToolClass | null | undefined; }, opts?: { mayGrantWrite?: boolean; }): Promise<{ tool: McpTool; grantedWrite: boolean; }>; /** Probe a registered server again: record its health and its tool diff. */ test(name: string): Promise; /** Write a server's secret to the OS store; answers the reference to put in its `auth.ref`. */ setSecret(name: string, value: string): Promise; /** * The broker's resolution of one subject (§6 step 2). `null` = no server of that name. A server * that is disabled, a tool that is disabled, `gone`, never saved, or whose schema changed since it * was saved reads `registered: false`, which the engine denies (D-5). */ resolveCall(server: string, tool: string): Promise; /** * Every tool a call could reach right now (slice S4, the shim's `list`): enabled servers, and * their enabled tools whose saved schema is the one last seen. Anything else is D-5 and would be * refused by the broker, so it is never offered. */ callableTools(): Promise; /** * The MCP inspector's resolution of a registered server (DES-MCP-INSPECTOR-001 §3): its config, * enabled or not (an operator debugs a server it switched off), or `null` for no such name. */ inspectTarget(name: string): Promise; /** * The secret a call to `config` is made with, resolved at the moment of the call and nowhere * else. `missing` = the server names a secret that resolves to nothing: the call is not made * unauthenticated. */ callSecret(config: McpUpstreamConfig): Promise<{ secret: string | null; missing: boolean; }>; private find; private hold; /** * Probe with the server's secret. A server whose `auth` names a secret that resolves to nothing * is NOT probed unauthenticated (that would register what an anonymous caller sees): it fails * without connecting. */ private probe; private authState; private view; } /** * (G1, DES-data-connectors §3) The switch a save gives a tool when the operator grants nothing: * - the schema the operator last saved, unchanged: its switch as they left it; * - otherwise a `write` or `destructive` tool (by its own annotations; an override does not survive * a schema change) is DISABLED until the operator grants it, so a grant covers one schema; * - otherwise a `read` tool keeps its switch, and a new one is enabled. */ export declare function enabledOnSave(was: Pick | undefined, next: { annotations: McpToolAnnotations | null; schemaHash: string; }): boolean; /** (G1) Whether enabling a tool with these annotations and class override grants a write. */ export declare function isWriteGrant(annotations: McpToolAnnotations | null, classOverride: McpToolClass | null): boolean; export declare function toolStatus(t: Pick): McpToolStatus; /** The difference between the registered tools and a probe's (by name, then schema hash). */ export declare function diffTools(registered: ReadonlyArray>, probed: ReadonlyArray<{ name: string; schemaHash: string; }>): McpToolDiff; //# sourceMappingURL=registry.d.ts.map