import { type Server } from 'node:http'; import { type ProtectedResourceMetadata } from '@nacre.work/core'; import { type LimitPolicy, type RateLimiter, type Resource, type VerifyOptions } from '@nacre.work/api'; import type { AdminRunner } from './admin-services.js'; import { type Layers, type McpMetrics, type ToolRunner, type SkillSource } from './factory.js'; export { DISCOVER_TTL_MS, LEGACY_PROTOCOL_VERSIONS, PROTOCOL_VERSION, PROTOCOL_VERSIONS, TOOLS_TTL_MS, } from './results.js'; export type { Layers, McpMetrics, ToolRunner } from './factory.js'; /** * Streamable HTTP, one endpoint, no session. * * The protocol is `@modelcontextprotocol/server`'s: it classifies a request * into the 2026-07-28 era or the legacy one, negotiates `initialize` for a * legacy client, answers `server/discover` and `tools/list`, validates tool * arguments against their schema, stamps `resultType` and the cache hints, * and refuses a mirrored header that disagrees with the body (`-32020`) or a * framing revision it cannot read (`-32022`). What this module adds is * everything the SDK has no opinion on and this deployment does — which is * everything that happens **before** a JSON-RPC envelope is read: * * - `/metrics`, on the same terms as the API's; * - `Origin`, validated and then admitted (the two halves of CORS); * - the RFC 9728 document this transport's own `401` names; * - `405` on the verbs the sessionless revision removed, and a plain HTTP * `404` — never the RPC envelope — on every path that is not `/mcp`; * - authentication, per request, with the `401` that starts the OAuth walk; * - the tenant-override refusal, before anything dispatches; * - the rate limit, on the tools that spend a budget. * * There is no `Mcp-Session-Id` and nothing kept between requests, which is * what lets any replica behind a round-robin balancer serve any request: the * `McpServer` is built per request by `factory.ts`, for the caller the token * names, and discarded with the response. */ export interface McpOptions { /** * `serviceKeys` is required here, unlike on the REST surface. * * This transport exists for agents, and an agent authenticates with a service * account key. Leaving the resolver out is not a smaller deployment — it is a * server that 401s every `nacre_sk_` token while the same key works over REST * and STDIO, which reads as a revoked credential rather than a missing wire. * Requiring it makes that a compile error instead of a support ticket. */ readonly verify: VerifyOptions & { readonly serviceKeys: NonNullable; }; readonly layers: Layers; readonly tools: ToolRunner; /** The base skill `instructions` carry. Absent, the built-in text alone. */ readonly skills?: SkillSource; /** Where a 401 points the client for discovery, per RFC 9728. */ readonly resourceMetadataUrl: string; /** The document that URL resolves to. Built once, in main, and shared with the API. */ readonly resourceMetadata: ProtectedResourceMetadata; /** * What `initialize` reports as `serverInfo.version`. * * Informational, and passed in rather than read from a manifest here: this * module is imported by tests and by the STDIO entry point as well as by the * server, and a file read at import time is the shape that threw ENOENT from * the built package once already. */ readonly serverVersion?: string; /** The API's canonical origin, for the MCP App views. See `ServerBuild.apiOrigin`. */ readonly apiOrigin?: string; /** * Build the discovery document from the origin the client actually reached, * rather than from one baked in at startup. * * Set only when the deployment has **not** pinned `NACRE_MCP_CANONICAL_URL`. * RFC 9728 has the client compare the `resource` identifier against the URL * it used, so a document naming anything else is refused before a token is * ever sent — and the Compose default named `http://localhost:8081`, which is * wrong for every client that is not on the server's own machine. A default * that quietly points at localhost is the failure `loadConfig` refuses for * every other URL in this product, and it had been introduced here. * * Deriving from `Host` is not a trust decision: the identifier is not an * authorization input. A token is still checked against `NACRE_JWT_AUDIENCE` * and `NACRE_JWT_ISSUER`, neither of which comes from the request, so the * worst a forged `Host` achieves is a document naming a resource whose tokens * this server will not accept. */ readonly resourceFromRequest?: (origin: string) => ProtectedResourceMetadata; /** * Browser origins this transport answers, from `NACRE_MCP_ALLOWED_ORIGINS`. * * Validating `Origin` is a MUST in the specification and the attack it names * is DNS rebinding: a page in somebody's browser reaching an MCP server on * their network. Absent means no browser origin is allowed, which is the * right default for a transport built for agents — an agent sends no * `Origin` and is unaffected. */ readonly allowedOrigins?: readonly string[]; /** * The same limiter and the same policies the REST surface uses. * * Absent means unlimited, which is what this transport was: `NACRE_RATE_*` * applied to REST only, so a client that had run out of search budget could * point at port 8081 and carry on. Two doors into one authorization service, * one of them with a lock on it. * * Counted per organization on the same keys, deliberately — a shared bucket * rather than one bucket per surface. Splitting them would give a caller * twice the documented allowance for holding two clients, which is the same * hole one level up. */ readonly limits?: RateLimiter; readonly limitPolicies?: Readonly>; /** Rendered at `/metrics`. Absent means the endpoint answers 404. */ readonly metrics?: { render(): Promise; }; /** A bearer token required on `/metrics`. Absent leaves it open. */ readonly metricsToken?: string; /** Where the tool path writes what it measured. */ readonly observe?: McpMetrics; /** * The administrative MCP at `/mcp/admin`. docs/mcp-admin.md. * * Absent, the path is not served at all — a plain `404` like any other, and * no discovery document for it — which is what a deployment that has not * enabled it gets. Present, it is a resource of its own: its discovery * document names `…/mcp/admin`, its tokens carry the administrative audience * and nothing else is accepted, and its `instructions` carry no skill. */ readonly admin?: { readonly tools: AdminRunner; }; } export declare function createMcpServer(options: McpOptions): Server; //# sourceMappingURL=server.d.ts.map