/** * Local MCP server entry point combining OAuth 2.1 AS + Streamable HTTP transport. * * Composes: * 1. OAuth 2.1 AS (credential form + token exchange) -- serves /authorize, * /token, /otp, /setup-status, /.well-known/* * 2. MCP Streamable HTTP transport -- serves /mcp with optional Bearer auth * 3. /health endpoint -- liveness probe * * For servers without credential input (e.g. godot) ``relaySchema`` may be * omitted: only /mcp (unauthenticated) and /health are served. * * This is a TypeScript port of ``core-py``'s ``local_server.py``. Route layout, * Bearer enforcement, and lifecycle semantics are kept identical. */ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'; import type { JWTPayload } from 'jose'; import type { RelayConfigSchema } from '../auth/credential-form.js'; import { type DelegatedOAuthAppOptions } from '../auth/delegated-oauth-app.js'; import { type CredentialsCallback, type StepCallback } from '../auth/local-oauth-app.js'; /** Decoded JWT claims returned by JWTIssuer.verifyAccessToken. */ export type JWTClaims = JWTPayload & { anonymous?: boolean; }; export interface RunHttpServerOptions { /** Identifier used for JWT iss/aud and credential storage. */ serverName: string; /** If undefined, server has NO auth (e.g., godot). */ relaySchema?: RelayConfigSchema; /** * Mutually exclusive with `relaySchema`. When set, the OAuth app is the * delegated provider (upstream redirect or device_code) instead of the local * credential form. The `serverName` and `jwtIssuer` are supplied by this * function; callers provide only `flow`, `upstream`, and `onTokenReceived`. */ delegatedOAuth?: Omit; /** 0 = auto-find a free port. Default: 0. */ port?: number; /** Host to bind. Default '127.0.0.1'. */ host?: string; /** Optional callback invoked with credentials after POST /authorize. */ onCredentialsSaved?: CredentialsCallback; /** Optional callback invoked with step data after POST /otp. */ onStepSubmitted?: StepCallback; /** * Called after server ready so callers can wire background tasks (e.g. * GDrive device code poll) to the form's ``/setup-status`` endpoint. * * Accepts either arity for backward compatibility: * - Legacy 1-arg: ``hook(markComplete)`` -- success-only (older consumers). * - New 2-arg: ``hook(markComplete, markFailed)`` -- surfaces upstream * errors (``invalid_grant`` / ``expired_token`` / ``access_denied``) * to the browser form so it stops polling and shows the error. * * Prefer the 2-arg form for new code. Arity is detected via * ``Function.prototype.length``. */ setupCompleteHook?: ((markComplete: (key?: string) => void) => void) | ((markComplete: (key?: string) => void, markFailed: (key?: string, error?: string) => void) => void); /** * Optional renderer used in place of the default credential form on GET * /authorize. Passed through to ``createLocalOAuthApp``. */ customCredentialFormHtml?: (schema: RelayConfigSchema, options: { submitUrl: string; prefill?: Record; }) => string; /** Forwarded to the local OAuth app; see LocalOAuthAppOptions.stableSubEnabled. */ stableSubEnabled?: boolean; /** * Optional middleware invoked after JWT verification and before the MCP * transport handles the request. Called with verified claims and a ``next`` * function that invokes the MCP transport. Consumers use this to wrap the * request in AsyncLocalStorage (e.g., for per-user token lookup). */ authScope?: (claims: JWTClaims, next: () => Promise) => Promise; /** * When `true`, skip Bearer token validation on the MCP endpoint and treat * the caller as anonymous. Intended for deployments behind an external * auth boundary (reverse proxy, API gateway like agentgateway/Zitadel) that * already enforces authentication. The deployer is responsible for ensuring * the network in front of this server is locked down — anyone who can * reach `/mcp` directly will get tool access. * * Wire via env var: `authDisabled: process.env.MCP_AUTH_DISABLE === '1'`. * * When set, `authScope` (if provided) receives anonymous claims * `{ sub: 'anonymous', anonymous: true }`. */ authDisabled?: boolean; } export interface HttpServerHandle { /** Actual TCP port bound. Non-zero even when ``options.port`` was 0. */ port: number; /** Host bound. */ host: string; /** Cleanly close transport + http server. */ close: () => Promise; } /** * Start an HTTP server with optional local OAuth AS + MCP Streamable HTTP transport. * * Behavior: * - If ``relaySchema`` is provided, serves OAuth routes (/authorize, /token, * /otp, /setup-status, /.well-known/*) AND /mcp with Bearer auth. * - If ``relaySchema`` is undefined (e.g., godot), serves ONLY /mcp without * auth (plus /health). * - Binds to ``host:port``. Port 0 auto-assigns via the OS. * - Returns a handle for lifecycle management; the server runs in the * background until ``close()`` is called. */ export declare function runHttpServer(serverFactory: () => McpServer, options: RunHttpServerOptions): Promise; //# sourceMappingURL=local-server.d.ts.map