/** * The `/api/v1/mcp/*` registry routes (DES-MCP-TOOLS-001 §8; slice S2). * * GET /mcp/servers upstreams, their tools (class, subject, status), health, * auth state, and the servers discovered in CLI homes (names only) * POST /mcp/servers/preview probe (45 s budget, hardened env): tools, classes and a previewHash * (`kind: "rest"`, slice S5a: the tools of an OpenAPI 3 document, * each pinned to the base `url`; see `mcp/rest.ts`) * POST /mcp/servers save: `{previewHash, enabledWrites?, allowOrigin?}`; missing, unknown or expired → 409. * (G1) a write/destructive tool that is new or whose schema changed * is registered DISABLED; `enabledWrites` names the ones the operator * grants (human operator only → else 403), each audited * `mcp.tool.write_granted`. * (G2) a `rest` base URL that matches none of the spec's `servers`, or a * loopback/private host, needs `allowOrigin` = exactly that origin (409 * `egress_unconfirmed`; human operator only → else 403), audited * `mcp.server.egress_confirmed`. A REST tool's hash covers the target, so * a changed target re-registers its tools and withdraws their approvals * PATCH /mcp/servers/:name `{enabled}` * DELETE /mcp/servers/:name remove the server and the keychain secret it owns * POST /mcp/servers/:name/test probe again: health plus the tool diff * PATCH /mcp/tools/:subject `{enabled?, classOverride?}`; `:subject` is `mcp:/`, URL-encoded. * (G1) leaving a tool enabled AND a write when it was not both (turning * a write on, or re-classing an enabled read) is a grant: human * operator only (403), audited `mcp.tool.write_granted` * PUT /mcp/servers/:name/secret `{value}` → the OS keychain; answers `{ref, set: true}`, never the value. * RE-KEY only (crew#719): the server must already reference * `keychain:wicked-mcp/`. A new server's secret rides * `secret` on the PREVIEW body and is written by the save. * POST /mcp/call the broker (§6, slice S3): `{token, subject, args?}` from the garden * shim; judged, budgeted, invoked, scrubbed, output-judged, recorded * POST /mcp/tools `{token}` from the garden shim's `list` (slice S4): the tools the * token's unit may try, judged with no arguments, recorded nowhere * * Slice S6 (the policy preview and the approvals, `mcp/policies.ts`): * * POST /mcp/policies/preview `{subject?, server?, phaseRole?, seat?, mode?, phaseId?}` → each * tool's decision per role × seat × mode; nothing is recorded * GET /mcp/approvals the approved subjects, the tools that would ask, the two ledgers * POST /mcp/approvals `{subject}`: a server joins `MCP-FIRST-USE` excludes; a tool joins * that and `MCP-POSTURE-WRITE` (audited rule upserts) * DELETE /mcp/approvals/:subject removes it from both (`:subject` URL-encoded) * * Slice S7 (the usage fold, `mcp/usage.ts`): * * GET /mcp/usage `?days=1-30&subject&seat&decision` → counts, the decision split * (allow / ask / deny / guard_error), error rate, p50/p95/p99, * per-tool, per-server, tool × seat × run, chains, per day * * `POST /mcp/servers/preview` also answers the matrix (`policies`); a save that changes a saved * tool's schema, and a removal, withdraw the approvals that named it, BEFORE the registry changes. * * The secret value is in no response, log, audit entry or file (D-2); tests/mcp-registry.test.ts * scans all of them, a malformed secret body included. */ import type { FastifyInstance, FastifyRequest } from 'fastify'; import { z } from 'zod'; import type { Actor } from '../core/types.js'; import type { McpBroker } from '../mcp/broker.js'; import type { AskServer } from '../mcp/ask-server.js'; import type { McpPolicies } from '../mcp/policies.js'; import { type McpRegistry } from '../mcp/registry.js'; import type { McpCallRecordSource } from '../mcp/call-records.js'; import type { AuditLog } from './audit.js'; export declare const McpServerConfigSchema: z.ZodEffects; command: z.ZodOptional; args: z.ZodOptional>; url: z.ZodOptional; auth: z.ZodOptional; env: z.ZodOptional; header: z.ZodOptional; prefix: z.ZodOptional; }, "strict", z.ZodTypeAny, { ref: string; env?: string | undefined; header?: string | undefined; prefix?: string | undefined; }, { ref: string; env?: string | undefined; header?: string | undefined; prefix?: string | undefined; }>>>; openapiUrl: z.ZodOptional; openapi: z.ZodOptional, Record, Record>>; operations: z.ZodOptional>; /** * (crew#719) The secret VALUE this server's keychain entry will hold. The preview is probed * with it and it is held with the preview; `POST /mcp/servers` writes it to the OS store as * part of the save, so the secret and the registry row commit together. Requires * `auth.ref` = `keychain:wicked-mcp/`. It is in no response, log, audit entry or file. */ secret: z.ZodOptional>; }, "strict", z.ZodTypeAny, { name: string; kind: "rest" | "mcp-stdio" | "mcp-http"; auth?: { ref: string; env?: string | undefined; header?: string | undefined; prefix?: string | undefined; } | null | undefined; url?: string | undefined; command?: string | undefined; operations?: string[] | undefined; secret?: string | undefined; args?: string[] | undefined; openapiUrl?: string | undefined; openapi?: Record | undefined; }, { name: string; kind: "rest" | "mcp-stdio" | "mcp-http"; auth?: { ref: string; env?: string | undefined; header?: string | undefined; prefix?: string | undefined; } | null | undefined; url?: string | undefined; command?: string | undefined; operations?: string[] | undefined; secret?: string | undefined; args?: string[] | undefined; openapiUrl?: string | undefined; openapi?: Record | undefined; }>, { name: string; kind: "rest" | "mcp-stdio" | "mcp-http"; auth?: { ref: string; env?: string | undefined; header?: string | undefined; prefix?: string | undefined; } | null | undefined; url?: string | undefined; command?: string | undefined; operations?: string[] | undefined; secret?: string | undefined; args?: string[] | undefined; openapiUrl?: string | undefined; openapi?: Record | undefined; }, { name: string; kind: "rest" | "mcp-stdio" | "mcp-http"; auth?: { ref: string; env?: string | undefined; header?: string | undefined; prefix?: string | undefined; } | null | undefined; url?: string | undefined; command?: string | undefined; operations?: string[] | undefined; secret?: string | undefined; args?: string[] | undefined; openapiUrl?: string | undefined; openapi?: Record | undefined; }>; export declare const SaveMcpServerSchema: z.ZodObject<{ previewHash: z.ZodOptional; /** (G1) The write/destructive tools of the preview the operator grants: registered enabled. */ enabledWrites: z.ZodOptional>; /** (G2) The exact origin a `rest` server's calls go to, confirmed by a human operator. */ allowOrigin: z.ZodOptional; }, "strict", z.ZodTypeAny, { previewHash?: string | undefined; enabledWrites?: string[] | undefined; allowOrigin?: string | undefined; }, { previewHash?: string | undefined; enabledWrites?: string[] | undefined; allowOrigin?: string | undefined; }>; export declare const PatchMcpServerSchema: z.ZodObject<{ enabled: z.ZodBoolean; }, "strict", z.ZodTypeAny, { enabled: boolean; }, { enabled: boolean; }>; export declare const PatchMcpToolSchema: z.ZodEffects; classOverride: z.ZodOptional>>; }, "strict", z.ZodTypeAny, { enabled?: boolean | undefined; classOverride?: "read" | "write" | "destructive" | null | undefined; }, { enabled?: boolean | undefined; classOverride?: "read" | "write" | "destructive" | null | undefined; }>, { enabled?: boolean | undefined; classOverride?: "read" | "write" | "destructive" | null | undefined; }, { enabled?: boolean | undefined; classOverride?: "read" | "write" | "destructive" | null | undefined; }>; export declare const PutMcpSecretSchema: z.ZodObject<{ value: z.ZodEffects; }, "strict", z.ZodTypeAny, { value: string; }, { value: string; }>; export declare const McpCallSchema: z.ZodObject<{ token: z.ZodString; subject: z.ZodString; args: z.ZodOptional>; }, "strict", z.ZodTypeAny, { subject: string; token: string; args?: Record | undefined; }, { subject: string; token: string; args?: Record | undefined; }>; export declare const McpToolsSchema: z.ZodObject<{ token: z.ZodString; }, "strict", z.ZodTypeAny, { token: string; }, { token: string; }>; export declare const McpPolicyPreviewSchema: z.ZodEffects; server: z.ZodOptional; phaseRole: z.ZodOptional>; seat: z.ZodOptional; mode: z.ZodOptional>; phaseId: z.ZodOptional; }, "strict", z.ZodTypeAny, { mode?: string | undefined; seat?: string | undefined; server?: string | undefined; subject?: string | undefined; phaseId?: string | undefined; phaseRole?: string | undefined; }, { mode?: string | undefined; seat?: string | undefined; server?: string | undefined; subject?: string | undefined; phaseId?: string | undefined; phaseRole?: string | undefined; }>, { mode?: string | undefined; seat?: string | undefined; server?: string | undefined; subject?: string | undefined; phaseId?: string | undefined; phaseRole?: string | undefined; }, { mode?: string | undefined; seat?: string | undefined; server?: string | undefined; subject?: string | undefined; phaseId?: string | undefined; phaseRole?: string | undefined; }>; export declare const McpApprovalSchema: z.ZodObject<{ subject: z.ZodString; }, "strict", z.ZodTypeAny, { subject: string; }, { subject: string; }>; export declare const McpUsageQuerySchema: z.ZodObject<{ days: z.ZodOptional; subject: z.ZodOptional; seat: z.ZodOptional; decision: z.ZodOptional>; }, "strict", z.ZodTypeAny, { seat?: string | undefined; decision?: string | undefined; subject?: string | undefined; days?: number | undefined; }, { seat?: string | undefined; decision?: string | undefined; subject?: string | undefined; days?: number | undefined; }>; export interface McpRouteDeps { askServer?: AskServer; /** Absent = a directly-driven route set with no registry seam → 503. */ registry?: McpRegistry; /** The broker's call path (S3). Absent → `POST /mcp/call` answers 503. */ broker?: McpBroker; /** `Core.listMcpTools` for `POST /mcp/tools` (S4), read per request. Absent → that route answers 503. */ toolLister?: () => ((requestJson: string) => Promise) | null; /** The preview and approvals (S6). Absent → those routes answer 503; the S2 routes are unchanged. */ policies?: McpPolicies; /** The call records the usage fold reads (S7). Absent → `GET /mcp/usage` answers 503. */ usage?: McpCallRecordSource; /** The usage window's end; tests pin it. Default `Date.now`. */ now?: () => number; audit: Pick; actorOf: (req: FastifyRequest) => Actor; } export declare function registerMcpRoutes(app: FastifyInstance, deps: McpRouteDeps): void; //# sourceMappingURL=mcp.d.ts.map