/** * REST upstreams (DES-MCP-TOOLS-001 §2, §5, invariant I6; slice S5a): a plain HTTP API wrapped as * brokered tools, one tool per chosen OpenAPI 3 operation. * * - **Import.** The OpenAPI document is fetched from `openapiUrl` (bounded in time and size; the * server's auth header rides along only when the document is on the API's own origin) or taken as * pasted. Local `$ref`s are resolved (a cycle becomes `{}`); an external `$ref` is not followed. * Each operation becomes a tool whose input schema is its path, query and header parameters and * its JSON body, and whose class comes from its method: `GET`/`HEAD`/`OPTIONS` read, `POST` * write, `PUT`/`PATCH`/`DELETE` destructive (the broker's classification, §4.2). * - **Mapping, an allowlist.** A tool carries its {@link McpRestMapping}: which argument fills * which path variable, query parameter, header or body key. An argument no map names is DROPPED, * never sent; its name is disclosed in the result's `_meta["wicked/rest"].droppedArgs`. * - **Host pin (I6).** Every request is built on the server's base URL and checked before it is * sent: its scheme, host and port must be the base URL's and its path must stay under the base * path. A request that would leave, and a redirect to another host, are refused with * {@link RestBoundaryError}, which the broker records as a `guard_error`. Redirects are never * followed. The auth header can't be set or overridden by an argument. * * Ideas only from ContextForge (`openapi_service.py`, `tool_service.py`); no code is copied. */ import type { McpRestEgress, McpRestMapping, McpToolAnnotations } from '../core/types.js'; import type { UpstreamToolResult } from './invoke.js'; import type { HashedRestTarget } from './classify.js'; import type { McpUpstreamConfig, ProbedTool, ProbeResult } from './probe.js'; import { UpstreamCallError } from './upstream-error.js'; export declare const REST_SPEC_TIMEOUT_MS = 10000; /** The largest OpenAPI document crew reads (fetched or pasted). */ export declare const REST_SPEC_MAX_BYTES: number; /** The largest response body handed back; the rest is cut and disclosed as `truncated`. */ export declare const REST_RESPONSE_MAX_BYTES: number; export declare const REST_DEFAULT_TIMEOUT_MS = 30000; type Method = McpRestMapping['method']; /** A request that would leave the API's pinned base URL (I6). Never retried; recorded as `guard_error`. */ export declare class RestBoundaryError extends UpstreamCallError { constructor(message: string); } /** A tool probed from an OpenAPI document: an MCP tool shape plus its request mapping. */ export type RestProbedTool = ProbedTool & { rest: McpRestMapping; }; /** Parse a base URL: http(s), no credentials, no query or fragment. `null` = not acceptable. */ export declare function parseBaseUrl(raw: string | null): URL | null; /** * (G2, DES-data-connectors §3) What a `rest` server's calls go to, and so what its tools' hash * covers (`toolSchemaHash`): the base URL's origin and path, and the header its secret rides in * (lowercased; header names are case-insensitive). `null` = no valid base URL (never callable). */ export declare function restTarget(config: Pick): HashedRestTarget | null; /** Whether an IP literal is a loopback/private/link-local/unique-local address. Not an IP → `false`. */ export declare function isPrivateAddress(ip: string): boolean; /** Resolves a hostname to its addresses; `[]` when it does not resolve. Injectable for tests. */ export type RestLookup = (hostname: string) => Promise; export declare const dnsLookup: RestLookup; /** * The spec's `servers`, each resolved to an absolute URL: `{variable}`s take their `default`, and a * relative URL resolves against the document's own URL (OpenAPI 3 §4.8.5). A server that can't be * resolved (a variable with no default, a relative URL in a pasted document) is not checkable and * is left out. No `servers` means `[{url: "/"}]`, as the spec defines. */ export declare function specServers(doc: Record, specUrl: string | null): URL[]; /** What the probe finds of a `rest` server's egress (G2); the registry adds `confirmRequired`. */ export type RestEgress = Omit; /** Check `base` against the spec's `servers`, and whether its host is a private address. */ export declare function restEgress(base: URL, doc: Record, specUrl: string | null, lookupImpl?: RestLookup): Promise; /** * The request URL for `pathTemplate` filled with `values`, pinned to `base`: the same origin, and a * path under the base path. Anything else is a {@link RestBoundaryError}. */ export declare function pinnedUrl(base: URL, pathTemplate: string, values: Record): URL; /** Why `url` is outside `base` (another origin, or a path off the base path); `null` = inside. */ export declare function boundaryEscape(base: URL, url: URL): string | null; type Json = Record; /** * `value` with every local `$ref` replaced by what it points to. A `$ref` already being resolved * on the current path (a cycle), an unresolvable or external one, and anything deeper than * {@link MAX_REF_DEPTH} become `{}` (any value): the schema is a hint to the caller, never a check. */ export declare function derefLocal(doc: Json, value: unknown, seen?: ReadonlyArray, depth?: number): unknown; /** Parse an OpenAPI document's text: JSON, else YAML. */ export declare function parseOpenApiText(text: string): Json; /** The class an HTTP method implies, as annotations (§4.2): the broker decides it, never the carrier. */ export declare function annotationsForMethod(method: Method): McpToolAnnotations; export interface OpenApiImport { serverInfo: { name: string; version: string; } | null; tools: RestProbedTool[]; /** Operations that could not be wrapped, with why (a non-JSON body, a name clash past 512 tools). */ skipped: string[]; } /** * Wrap an OpenAPI 3 document's operations as tools. `operations` (by `operationId` or tool name) * picks which; a name that matches no operation is an error, so a typo never silently wraps less. */ export declare function importOpenApi(doc: Json, opts?: { authHeader?: string | undefined; operations?: ReadonlyArray | null; timeoutMs?: number; }): OpenApiImport; export type RestFetch = typeof fetch; /** * The `rest` probe: read the OpenAPI document and wrap its operations. The auth header goes to the * document's URL only when it is on the base URL's own origin. Every string returned has the * secret scrubbed out. * * The document's URL is NOT pinned to the base URL, by design: it is the operator's own input at * preview time (an API's spec often lives elsewhere, e.g. a docs host), not a worker's call, and it * carries no secret off the API's origin. The host pin (I6) is on the tools' calls. * * (G2) It also answers `egress`: the base URL checked against the document's `servers`, and * whether its host is a private address. The registry refuses to save a mismatch or a private * address unless the operator names that exact origin. */ export declare function probeRestServer(config: McpUpstreamConfig, secret: string | null, fetchImpl?: RestFetch, lookupImpl?: RestLookup): Promise; /** * Build the one HTTP request a `rest` tool call makes, from the ALLOWLISTED arguments only, pinned * to the base URL. Exported so the allowlist and the pin are testable without a network. */ export declare function buildRestRequest(config: McpUpstreamConfig, secret: string | null, mapping: McpRestMapping, args: Record): { url: URL; init: { method: string; headers: Record; body?: string; }; droppedArgs: string[]; }; /** * Call one `rest` tool. Transport failures, 429 and 5xx are retryable {@link UpstreamCallError}s; a * redirect is never followed (one to another host is a {@link RestBoundaryError}); any other * status is handed back as the tool's own result (`isError` on 4xx). The result is RAW: the broker * scrubs it. */ export declare function invokeRestTool(config: McpUpstreamConfig, secret: string | null, mapping: McpRestMapping, args: Record, timeoutMs: number, fetchImpl?: RestFetch): Promise; export {}; //# sourceMappingURL=rest.d.ts.map