import { J as JsonSchema, a as ToolAnnotations, G as GeneratedTool, D as DispatchConfig, b as DispatchResult, R as RequestAuth, T as ToolManifest } from './generate-BCDqBUjZ.js'; export { A as AuthResolver, c as GenerateOptions, O as OpenApiDocument, d as OpenApiOperation, e as OpenApiParameter, f as OpenApiRequestBody, g as OpenApiResponse, P as ParameterLocation, h as ToolParameter, i as generateTools } from './generate-BCDqBUjZ.js'; export { b as AiCapability, e as AiConnectPromptCopy, A as AiConnectPromptSpec, f as AiHostBrand, g as AiHostConfigureStage, a as AiHostGuide, h as AiHostLink, d as AiProvider, i as aiConnectPrompt, p as providerForHostId } from './guide-CrzdsdNf.js'; import { z } from 'zod'; export { A as AI_CAPABILITIES, a as AI_CONNECT_PROMPT, b as AI_HOST_GUIDES, c as AI_PERMISSION_MODEL, E as EN_US_AI_CAPABILITIES, d as EN_US_AI_CONNECT_PROMPT, e as EN_US_AI_HOST_GUIDES, f as EN_US_AI_PERMISSION_MODEL, P as PT_BR_AI_CAPABILITIES, g as PT_BR_AI_CONNECT_PROMPT, h as PT_BR_AI_HOST_GUIDES, i as PT_BR_AI_PERMISSION_MODEL } from './locales-Cv0Pecvu.js'; /** * Raised when a JSON Schema cannot be turned into a flat, self-contained tool * input — an unresolvable `$ref`, an unsupported pointer, or a recursive schema. * The MCP tool surface is deliberately finite and flat (it is handed to an LLM * and committed to the drift manifest), so recursion is rejected rather than * silently truncated. */ declare class UnsupportedSchemaError extends Error { constructor(message: string); } /** * Inline every local `$ref` in a JSON Schema and drop the now-empty `$defs`/ * `definitions` containers, yielding a flat, self-contained schema. Diamond reuse * (the same definition referenced by sibling branches) is fine; only a true cycle * — a definition that references itself up the resolution stack — is rejected. * * A schema with no `$ref`/definitions is returned structurally unchanged, so * inlining an already-flat spec is a no-op (the drift gate stays a stable diff). */ declare function inlineSchemaRefs(schema: JsonSchema): JsonSchema; /** * How a route is DECLARED, one step before it becomes an OpenAPI operation and * two before it becomes a tool. * * This package already owns everything downstream of an OpenAPI document — * `generateTools` turns operations into tools, `dispatchTool` proxies a call, * `redactResponseSchema`/`redactResponseBody` narrow both halves. What it did * not own was the shape a consumer writes its routes down in, so every consumer * declared its own. That is fine for one app and wrong for several: a monorepo * where the shift routes, the lifecycle routes and the audit routes are each * packaged separately needs those packages to produce endpoint lists the HOST * can concatenate, which they can only do if they all mean the same thing by * "an endpoint". * * Deliberately zod-shaped rather than JSON-Schema-shaped. A route validates its * input with zod at runtime; describing it a second time in JSON Schema is a * copy that drifts, and the drift is invisible — the manifest keeps advertising * the shape the route stopped accepting. Converting zod → JSON Schema at * generate time makes the validator the single source of truth. * * zod is a PEER dependency: it is referenced here as a type only, so this * package pulls no copy of its own and cannot end up type-checking against a * different one than the consumer declares its schemas with. */ /** The methods an MCP-exposed route may use. */ type HttpMethod = "get" | "post" | "put" | "patch" | "delete"; /** * What a PACKAGE can say about how its own tool behaves. * * The host still owns the final `ToolAnnotations` — every field required, and * `mcp:lint` unchanged in demanding that each tool ends classified. What * changes is who supplies the DEFAULT. A package declaring * `getSupplierVersions` knows perfectly well that it reads and does not * destroy; a host cannot know that without reading the package's source, so * today it restates the classification by hand — 48 lines of policy hints for * one package's eight-endpoint factory, growing with every collection plugged * in, and wrong the moment the package changes a verb. * * Every field is OPTIONAL here, which is the whole difference from * `ToolAnnotations`: this is a suggestion the host merges under its own table, * so a package that knows two of the four says two and stays silent on the * rest. Deliberately spelled without the `Hint` suffix and as a structural * twin of `@12-apps/wiring`'s `WireMcpAnnotations`, so an `McpEndpoint` still * satisfies `WireMcpTool` — restated rather than imported because this package * takes no dependency on the wiring contract. */ interface McpAnnotationDefaults { /** Human title override; hosts may re-derive from the operation id. */ title?: string; /** The tool only reads — never mutates host state. */ readOnly?: boolean; /** A destructive write (delete/purge), as opposed to an additive one. */ destructive?: boolean; /** The tool reaches beyond the host's own data (external services). */ openWorld?: boolean; } interface McpEndpointBase { /** Stable tool id — this becomes the MCP tool name, so renaming it is a * breaking change for every agent that has learned the old one. */ operationId: string; method: HttpMethod; /** OpenAPI path template, e.g. `/api/products/{id}`. */ path: string; /** What the tool is FOR, in the words an agent reads when choosing it. */ summary: string; tags?: string[]; /** Object schema whose properties become query parameters. */ query?: z.ZodType; /** Object schema whose properties become path parameters. */ params?: z.ZodType; /** Request body schema (writes only). */ body?: z.ZodType; /** * Behavior the package can assert about its own tool. Optional, and merged * UNDER the host's table — see {@link McpAnnotationDefaults}. */ annotations?: McpAnnotationDefaults; } /** * A declared endpoint either answers 200 with a schema'd JSON body (the * default) or 204 No Content (fire-and-forget writes). * * The union is what makes the two mutually exclusive: a 204 entry cannot carry * a response schema, so a manifest can never advertise a body its route will * not send — a mismatch an agent experiences as a tool that returns nothing * where its own schema promised an object. */ type McpEndpoint = McpEndpointBase & ({ /** Success status (defaults to 200 with a JSON body). */ status?: 200; /** Success (200) response schema. */ response: z.ZodType; } | { /** 204 No Content — no response schema. */ status: 204; response?: never; }); /** * Composing a tool's behavior classification out of two sources. * * The rule the host's gate enforces is unchanged: every served tool ends with * a COMPLETE `ToolAnnotations` — a title and all three hints — and a tool that * ends unclassified fails `mcp:lint`. ChatGPT App review treats a missing hint * as a blocker, and the Anthropic connector directory derives auto-permissions * from `readOnlyHint`/`destructiveHint`, so there is no defensible default for * "we did not say". * * What this adds is where the answer may COME FROM. A package that declares * `getSupplierVersions` knows it reads and does not destroy; the host cannot * know that without reading the package's source, so it restated the * classification by hand — one line per tool, per collection, wrong the moment * the package changed a verb. Now the package can assert what it knows and the * host's table becomes what it should always have been: OVERRIDES, plus the * tools the host itself owns. * * ## Precedence, and why it runs this way * * The HOST wins every field it states. A package's claim is a default, not a * fact about the host's deployment: the same endpoint can be read-only in one * app and reach an external service in another (a host that proxies its * catalog reads through a vendor), and the host is the only party that knows. * Inverting this would make a package version bump silently re-classify a tool * an operator had already audited — the exact thing an audited classification * exists to prevent. * * ## What it refuses * * A field neither side supplies. `resolveToolAnnotations` throws naming the * tool and the missing fields, which keeps the completeness property a * REFUSAL rather than a lint pass over a table that quietly grew a gap. The * host's own gate can keep its message; this one fires first and says the same * thing in the same terms. */ /** The host's half — whatever it chose to state, per tool. */ type ToolAnnotationOverrides = Partial; /** * Merge a package's declared defaults under a host's overrides. * * The four fields are resolved into one record and checked generically rather * than branch by branch — which keeps the "host wins, and `false` is an * answer" rule stated exactly once per field instead of once per field per * check. * * `??` and not `||` throughout, and that is the trap the whole merge turns on: * `false` is a real classification — "this tool does not destroy" — and must * not fall through to the package's answer. * * @param name the tool id, for the refusal message * @param defaults what the package asserted (`McpEndpoint.annotations`) * @param overrides what the host's own table says; wins every field it states */ declare function resolveToolAnnotations(name: string, defaults: McpAnnotationDefaults | undefined, overrides: ToolAnnotationOverrides | undefined): ToolAnnotations; /** Raised when tool arguments cannot be routed onto the HTTP request. */ declare class DispatchInputError extends Error { constructor(message: string); } /** * Execute one generated tool by proxying to its HTTP endpoint, forwarding the * caller's bearer verbatim. This function performs NO authorization — the * endpoint does, exactly as it would for a first-party request. That is the whole * point of the passthrough: the agent can do precisely what the user can. */ declare function dispatchTool(tool: GeneratedTool, args: Record, config: DispatchConfig): Promise; /** * Return `schema` without the listed dotted paths — the advertised half. * * THROWS when a path names nothing, rather than returning quietly: a typo'd or * stale redaction would otherwise protect nothing at all, and it would do so * invisibly, which is the one outcome a redaction list must never have. Failing * here turns it into a generator error naming the offending path. * * The input is cloned, not narrowed in place: a caller may hold the converted * schema for other uses (a shared `$defs` component, a schema reused across two * operations), and mutating it would redact those too. * * `operationId` only shapes the error message — pass it so the failure names * which tool declared the bad path. */ declare function redactResponseSchema(schema: JsonSchema, paths: readonly string[], operationId?: string): JsonSchema; /** * Return `body` without the listed dotted paths. * * The input is deep-cloned first: dispatch results are handed straight to the * JSON-RPC encoder AND reused as `structuredContent`, so mutating in place * could leak a half-redacted object into one of the two surfaces. */ declare function redactResponseBody(body: unknown, paths: readonly string[] | undefined): unknown; /** * The registry is the transport-agnostic seam between the generated tools and the * MCP SDK. The consuming app owns the HTTP/JSON-RPC transport (mounting it at * `/api/mcp`) and, per request, resolves {@link RequestAuth} and calls * {@link ToolRegistry.listTools} / {@link ToolRegistry.callTool}. Keeping the * SDK out of this package means the core stays testable and portable. */ /** An MCP tool descriptor as advertised to clients (subset of the MCP schema). */ interface McpToolDescriptor { name: string; description: string; inputSchema: JsonSchema; outputSchema?: JsonSchema; annotations: ToolAnnotations; } /** * `_meta` key carrying the upstream HTTP status of a dispatched call. * * `isError` is one bit, and it collapses answers that mean opposite things: a * 404 for a record that does not exist, a 403 a guard correctly refused, a * domain refusal ("this store does not use comandas"), and a 500 where the route * threw all arrive identical. Callers that need to tell "correctly refused" from * "actually broken" — `mcp:smoke` above all — cannot, because the status is * known at dispatch and then dropped. Publishing it under a namespaced `_meta` * key (permitted by the MCP result schema) keeps `isError` as the agent-facing * signal while making the distinction recoverable. */ declare const HTTP_STATUS_META_KEY = "dispatch/httpStatus"; /** An MCP tool-call result (subset of the MCP schema). */ interface McpToolResult { content: Array<{ type: "text"; text: string; }>; isError: boolean; /** Machine-readable output matching the advertised outputSchema. */ structuredContent?: Record; /** Out-of-band metadata; carries {@link HTTP_STATUS_META_KEY} when dispatched. */ _meta?: Record; } interface ToolRegistry { listTools(auth?: RequestAuth): McpToolDescriptor[]; callTool(name: string, args: Record, auth: RequestAuth): Promise; } interface RegistryOptions { tools: GeneratedTool[]; /** Origin the tools proxy to (usually the app's own public URL). */ baseUrl: string; fetchImpl?: typeof fetch; /** * Optional visibility filter — e.g. hide mutating tools, or tools whose * required scope the caller lacks. Authorization is still enforced upstream; * this only shapes what the agent is shown. */ isVisible?: (tool: GeneratedTool, auth?: RequestAuth) => boolean; } declare function createToolRegistry(options: RegistryOptions): ToolRegistry; /** * What an unauthorized MCP call is TOLD, as opposed to what it is refused with. * * ## The failure this module exists to remove * * A resource server has three RFC 6750 challenge codes and a boolean's worth of * expressiveness, so every way a bearer can fail arrives at the agent as one * opaque refusal. `Authentication required` is what a lapsed connection, a token * minted for a different origin, a surface an operator never switched on, and a * missing scope all look like — identical, and none of them actionable. * * The cost is not cosmetic. An agent that cannot tell those apart cannot tell the * user anything useful either: every tool call fails, the connector still reports * itself connected, and the one thing that would fix the common case — reconnect * it — is the one thing nobody is told to do. Reported from a live deployment as * "every tool call failing, even the ones that read nothing". * * ## The shape of the answer * * Each reason resolves to three things, and they are deliberately separate: * * - `challenge` — the RFC 6750 code for the `WWW-Authenticate` header. Only * ever one of the two the spec defines for this situation, because a host's * OAuth machinery keys off it; * - `action` — what would actually fix it, for a client that automates; * - `message` — one sentence an agent can relay to a person. English, like * everything else a developer or a model reads here; a host that wants its * own wording supplies it. * * Nothing here narrows `unverified`. Signature, issuer and audience stay * collapsed into one answer on purpose — see `../oauth/access-token.ts` for why * expiry is the single documented exception. */ /** Why a call was refused, across both the transport and the token verifier. */ type McpAuthFailureReason = /** No `Authorization` header at all — the client has not connected yet. */ "no_token" /** The token was fine until its `exp` passed. The common one, and recoverable. */ | "expired" /** Signature, issuer or audience did not hold. Deliberately not narrowed. */ | "unverified" /** Verified, but carrying no usable identity. */ | "incomplete" /** The operator has not provisioned signing material, so nothing can verify. */ | "not_provisioned" /** The whole MCP surface is switched off for this deployment. */ | "surface_disabled" /** A valid token that lacks the scope this particular call needs. */ | "insufficient_scope"; /** What a client should do about it. */ type McpAuthRecovery = /** Exchange the refresh token for a new access token, then retry. */ "refresh" /** Re-run the authorization flow — a human has to approve it again. */ | "reconnect" /** Nothing the client can do; the deployment has to change. */ | "contact_operator"; /** The resolved answer for one refusal. */ interface McpAuthFailure { reason: McpAuthFailureReason; /** The RFC 6750 code for the `WWW-Authenticate` challenge. */ challenge: "invalid_token" | "insufficient_scope"; action: McpAuthRecovery; /** One sentence, written to be relayed to a person by an agent. */ message: string; } /** Resolve a reason to its challenge code, recovery and human-relayable message. */ declare function describeAuthFailure(reason: McpAuthFailureReason): McpAuthFailure; /** * The machine-readable half, carried in the JSON-RPC error's `data` member. * * A model reads `message`; a host that automates its connection lifecycle reads * this. Both travel together so neither has to be inferred from the other. */ interface McpAuthFailureData { reason: McpAuthFailureReason; action: McpAuthRecovery; /** * Whether presenting a NEW token could succeed. `false` means the deployment * itself is the problem, so a client that retries forever is wasting its time * and the user's — and should say so rather than loop. */ recoverable: boolean; } /** Build the `data` payload for a refusal. */ declare function authFailureData(failure: McpAuthFailure): McpAuthFailureData; /** * The MCP JSON-RPC 2.0 request half of the Streamable HTTP transport. * * Implemented directly rather than via the MCP SDK because the SDK's transport is * Node-`http` oriented, and a host serving Web `Request`/`Response` (a Next route * handler, a Hono route) has no `http.IncomingMessage` to hand it. It covers the * methods a client needs to discover and call tools: `initialize`, `tools/list`, * `tools/call`, plus `ping`. * * WHAT IS MECHANISM AND LIVES HERE: the envelope, the method table, the error * codes, the well-formedness rule, and the notification convention. None of it * varies per host — it is JSON-RPC 2.0 and the MCP specification. * * WHAT IS VOCABULARY AND STAYS WITH THE HOST: the server's NAME, its version, and * the `instructions` string an agent reads on connect. Those describe one * particular product's tool surface, so they arrive as {@link McpJsonRpcOptions} * rather than being written here. */ /** The MCP protocol revision this transport implements. */ declare const MCP_PROTOCOL_VERSION = "2025-06-18"; /** * JSON-RPC error code for "authentication required", returned by `tools/call` * when the request carried no valid bearer. Outside the reserved -32768..-32000 * band's *defined* codes on purpose: it is an implementation-defined server * error, and a host maps it to HTTP 401. */ declare const UNAUTHORIZED_CODE = -32001; interface JsonRpcRequest { jsonrpc: "2.0"; id?: string | number | null; method: string; params?: unknown; } interface JsonRpcResponse { jsonrpc: "2.0"; id: string | number | null; result?: unknown; /** * `data` is the JSON-RPC 2.0 optional member, and it is what carries the * machine-readable half of a refusal ({@link McpAuthFailureData}) while * `message` carries the half a model relays to a person. */ error?: { code: number; message: string; data?: unknown; }; } /** What a client is told it connected to, in `initialize`'s `serverInfo`. */ interface McpServerInfo { /** The server's name, as a connected host displays it. */ name: string; /** * The advertised surface version. * * This is the ONLY signal a client gets that the tool surface changed: the * transport is request/response only, so `notifications/tools/list_changed` * can never be sent, and a host that cached `tools/list` at the handshake has * no other reason to ask again. See `server/surface-lock.ts` for the guard * that makes forgetting to move it a build error instead of a comment. */ version: string; } interface McpJsonRpcOptions { /** The host's identity, returned verbatim in `initialize`. */ serverInfo: McpServerInfo; /** * Server-level guidance surfaced to the model on `initialize` (the MCP spec's * optional `instructions` field). Omitted from the result when absent, rather * than sent empty — a blank string is a claim that there is guidance. */ instructions?: string; /** * Override the advertised protocol revision. Defaults to * {@link MCP_PROTOCOL_VERSION}; a host should not normally set it. */ protocolVersion?: string; } /** * Handle one MCP JSON-RPC request. * * Returns `null` for notifications (no id, no reply expected). `auth` is the * verified caller identity, or `null` when the request carried no valid bearer — * `tools/call` then returns {@link UNAUTHORIZED_CODE}, which the host surfaces as * HTTP 401. Discovery (`initialize`, `ping`, `tools/list`) stays open, so a client * can read the surface before it has a token. * * `failure` is WHY `auth` is null, which only the host's verifier knows. It is * optional so an existing caller keeps compiling, and passing it is what turns a * refusal an agent can only report into one it can act on — see * `./auth-failure.ts`. */ declare function handleMcpJsonRpc(request: JsonRpcRequest, registry: ToolRegistry, auth: RequestAuth | null, options: McpJsonRpcOptions, failure?: McpAuthFailureReason): Promise; /** * The manifest is the committed source-of-truth artifact the CI drift gate * (`mcp:check` → `12-apps/ci` `mcp-contract.yml`) diffs against a fresh * regeneration. If an endpoint's schema changes without the manifest being * regenerated, the diff fails the build — that is how the served MCP surface is * kept in lockstep with the endpoint surface. */ interface BuildManifestOptions { /** Bumped intentionally on any tool-shape change (mirrors the golden catalog). */ version: number; /** Human label for the spec, e.g. "acme web @ openapi.json". */ source: string; } declare function buildManifest(tools: GeneratedTool[], options: BuildManifestOptions): ToolManifest; /** * Canonical JSON for a manifest — deep-key-sorted and trailing-newline'd, so the * committed artifact and a regeneration diff cleanly (no key-order or whitespace * churn). `mcp:check` regenerates, serializes with this, and `git diff --exit-code`s. */ declare function serializeManifest(manifest: ToolManifest): string; /** * Making a server's advertised version impossible to leave behind. * * THE PROBLEM, which every MCP server on this transport has. `tools/list` is * answered on request and there is no server→client stream, so a server cannot * push `notifications/tools/list_changed` — and one that declares * `capabilities.tools.listChanged` without being able to send it is worse than * one that is honest, because the host then stops checking for itself. What is * left is `serverInfo.version` from the `initialize` handshake. A host caches the * tool list against it, so a version that never moves gives it no reason to ever * ask again: a tool that shipped stays invisible to every ALREADY CONNECTED * client for as long as that connection lives. * * That is not a hypothetical. In the origin host a new tool reached production, * answered on its route, and did not appear in a live connector — behind a * `serverInfo.version` frozen at its initial value while ~280 tools were added * underneath it. Nothing was broken; the only thing asking anyone to bump it was * a comment, and a rule enforced by a comment is not enforced. * * THE MECHANISM. An app commits a lock recording WHICH surface its current * version stands for. Its generator recomputes the digest and refuses to write * the artifacts when the digest moved while the version did not, naming the * value to set. Because the same generator run under `--check` is what the * contract gate already diffs, the failure lands in CI and in a pre-push hook * without a new job, without git history, and without any event-sensitivity. * * WHY A DIGEST OF THE SURFACE, NOT A PATHS FILTER. A `paths:` list over the * server's own directory is wrong in both directions: it fires on edits no * client can see (a comment in an auth helper) and misses real ones that enter * from outside it (a schema whose ceiling is imported from a storage module). * Hashing what the tools ARE — the canonical manifest serialization — has * neither failure mode: it is exactly the bytes `tools/list` would return. */ /** The committed record: which surface an app's current version stands for. */ interface SurfaceLock { /** The app's surface version at the time `digest` was recorded. */ version: number; /** Digest of the served tool surface (see {@link surfaceDigest}). */ digest: string; } /** Everything {@link surfaceLockProblem} needs to judge one generation. */ interface SurfaceLockCheck { /** The lock as committed, or `null` when there is none to contradict. */ previous: SurfaceLock | null; /** The surface version the app currently declares. */ version: number; /** Digest of the surface being generated now. */ digest: string; /** * Where the app's version constant lives, repo-relative — quoted in the * failure so the fix is a path and a value rather than a hunt. */ versionLocation: string; /** The constant's name, if the app does not use the default. */ versionName?: string; } /** * Digest of a served tool surface — every tool's name, description, annotations * and input/output schemas, in the manifest's own canonical (deep-key-sorted) * serialization. Stable across unrelated reordering, and identical for two * surfaces that a client could not tell apart. */ declare function surfaceDigest(tools: GeneratedTool[], source: string): string; /** Canonical JSON for a committed lock (trailing newline, like the manifest). */ declare function serializeSurfaceLock(lock: SurfaceLock): string; /** * Decide whether an app's current version may stand for its current surface. * * Returns the problem as a sentence ready to print, or `null` when the pair is * consistent. Four outcomes, and three of them pass: * * - surface unchanged → fine, whatever the version did (a release bump with no * surface change is legitimate and must not be blocked); * - surface changed AND version moved → fine, that is the whole contract; * - no lock to contradict (first run, or the file was deleted) → fine, it is * simply recorded; * - surface changed and version did not → the failure this exists for. */ declare function surfaceLockProblem(check: SurfaceLockCheck): string | null; /** * OAuth 2.0 Protected Resource Metadata (RFC 9728), as required by the MCP * authorization spec: the MCP endpoint is an OAuth *resource server*. Agent hosts * (Claude.ai / ChatGPT connectors) discover where to obtain a token by reading * `/.well-known/oauth-protected-resource`, and on a 401 the resource server points * them at that document via a `WWW-Authenticate` challenge. * * This module only builds the discovery documents/headers — validating the * resulting access token is the app's job (the {@link import("../types").AuthResolver}), * because it depends on the app's authorization server and key material. */ interface ProtectedResourceMetadataInput { /** Canonical resource identifier — the MCP endpoint URL (the token audience). */ resource: string; /** Authorization server issuer URLs that can mint tokens for this resource. */ authorizationServers: string[]; /** Scopes the resource server understands (advertised to clients). */ scopesSupported?: string[]; /** Human-facing docs URL for the protected resource, if any. */ resourceDocumentation?: string; } /** The RFC 9728 metadata document served at `/.well-known/oauth-protected-resource`. */ interface ProtectedResourceMetadata { resource: string; authorization_servers: string[]; bearer_methods_supported: string[]; scopes_supported?: string[]; resource_documentation?: string; } declare function buildProtectedResourceMetadata(input: ProtectedResourceMetadataInput): ProtectedResourceMetadata; /** * Build the `WWW-Authenticate` value for an unauthorized MCP response, pointing * the client at the protected-resource metadata so it can start the OAuth flow. * Per RFC 9728 §5.1 the challenge carries a `resource_metadata` parameter. */ declare function bearerChallenge(params: { resourceMetadataUrl: string; error?: "invalid_token" | "insufficient_scope"; errorDescription?: string; }): string; /** Standard path for the protected-resource metadata document. */ declare const PROTECTED_RESOURCE_METADATA_PATH = "/.well-known/oauth-protected-resource"; /** * OAuth 2.0 Authorization Server Metadata (RFC 8414), the discovery half that * complements the RFC 9728 protected-resource metadata in `resource-metadata.ts`. * Agent hosts (Claude.ai / ChatGPT connectors) read * `/.well-known/oauth-authorization-server` to learn where to start the OAuth * 2.1 Authorization Code + PKCE flow. * * This builder is a pure function of `(issuer, scopes)` with no Next.js/request * coupling, so it can move verbatim into the future `@12-apps/mcp` extraction. * It derives every endpoint from the same issuer origin the resource metadata * advertises, so the two discovery documents cannot drift. */ interface AuthorizationServerMetadataInput { /** Authorization server issuer URL (origin) — also the resource issuer. */ issuer: string; /** Scopes the authorization server advertises (from the shared scope source). */ scopesSupported: string[]; /** * Client authentication methods the token endpoint accepts. Defaults to * public PKCE clients (`none`) plus HTTP Basic client-secret auth. */ tokenEndpointAuthMethods?: string[]; /** * Where the endpoints are actually mounted, if not at the defaults below. A * host that moves an endpoint MUST move it here too: this document is the only * thing a connector reads before its first request, so a path that lies here is * a flow that fails at the first hop (12-23 — `createApiMcpOauth` passes its * resolved paths, so the two cannot disagree). */ paths?: Partial; } /** The endpoint paths this document advertises, relative to the issuer origin. */ interface AuthorizationServerPaths { authorize: string; token: string; register: string; jwks: string; } /** The RFC 8414 document served at `/.well-known/oauth-authorization-server`. */ interface AuthorizationServerMetadata { issuer: string; authorization_endpoint: string; token_endpoint: string; registration_endpoint: string; jwks_uri: string; scopes_supported: string[]; response_types_supported: string[]; grant_types_supported: string[]; code_challenge_methods_supported: string[]; token_endpoint_auth_methods_supported: string[]; } /** * Build the RFC 8414 authorization-server metadata document from an issuer * origin and the supported scopes. Endpoints are derived from `issuer`; the * OAuth 2.1 + PKCE contract fixes `response_types_supported`, * `grant_types_supported`, and `code_challenge_methods_supported`. */ declare function buildAuthorizationServerMetadata(input: AuthorizationServerMetadataInput): AuthorizationServerMetadata; export { type AuthorizationServerMetadata, type AuthorizationServerMetadataInput, type AuthorizationServerPaths, type BuildManifestOptions, DispatchConfig, DispatchInputError, DispatchResult, GeneratedTool, HTTP_STATUS_META_KEY, type HttpMethod, type JsonRpcRequest, type JsonRpcResponse, JsonSchema, MCP_PROTOCOL_VERSION, type McpAnnotationDefaults, type McpAuthFailure, type McpAuthFailureData, type McpAuthFailureReason, type McpAuthRecovery, type McpEndpoint, type McpJsonRpcOptions, type McpServerInfo, type McpToolDescriptor, type McpToolResult, PROTECTED_RESOURCE_METADATA_PATH, type ProtectedResourceMetadata, type ProtectedResourceMetadataInput, type RegistryOptions, RequestAuth, type SurfaceLock, type SurfaceLockCheck, type ToolAnnotationOverrides, ToolAnnotations, ToolManifest, type ToolRegistry, UNAUTHORIZED_CODE, UnsupportedSchemaError, authFailureData, bearerChallenge, buildAuthorizationServerMetadata, buildManifest, buildProtectedResourceMetadata, createToolRegistry, describeAuthFailure, dispatchTool, handleMcpJsonRpc, inlineSchemaRefs, redactResponseBody, redactResponseSchema, resolveToolAnnotations, serializeManifest, serializeSurfaceLock, surfaceDigest, surfaceLockProblem };