/** * Streamable HTTP mode (#303): web-standard request router wiring the SDK's * protected-resource pieces (createMcpHandler, requireBearerAuth, metadata) * to the OAuth 2.1 authorization server in `oauth.ts`. * * Everything here is fetch-shaped (Request in, Response out) so it runs * unchanged under any web-standard host; `http.ts` provides the thin Node * adapter. No framework, no cookies, no sessions — the authorize form is the * only HTML and carries its whole state in the form body. */ import { OAuthProvider } from './oauth.js'; import type { CoolifyConfig } from '../types/coolify.js'; import type { InstanceRegistry } from './instances.js'; export interface HttpServerConfig { /** The default instance: what tier-2 proof of access validates against. */ coolify: CoolifyConfig; /** The full fleet (#367); omitted means `coolify` alone. */ instances?: InstanceRegistry; /** Public base URL of this container, e.g. https://mcp.example.com */ publicUrl: string; accessTokenTtl: number; refreshTokenTtl: number; stateFile: string; readonly: boolean; } /** * Accept the shapes MCP_PUBLIC_URL actually arrives in and produce one * canonical origin. Coolify's SERVICE_FQDN magic variable has carried both * bare domains and full URLs across versions, and a human typing the value * will produce trailing slashes and stray whitespace — none of which should * be a boot failure. A bare domain is assumed https (the TLS-terminating * proxy is the deployment model); anything unparseable throws. */ export declare function normalizePublicUrl(raw: string): string; /** Where the HTTP listener binds: the argument handed to `server.listen()`. */ export interface ListenOptions { port: number; host?: string; } /** * Resolve the listen address from the environment. `MCP_PORT` (or `PORT`) * picks the port, exactly as before. `MCP_HOST` picks the interface. * * Unset or blank means every interface, which is what a container needs: the * platform's proxy reaches it over the container network. Run the same server * on a workstation and that default puts a process holding a Coolify token on * every network the machine joins, so `MCP_HOST=127.0.0.1` keeps it on * loopback. The `host` key is left out rather than set to `undefined`, so an * unconfigured server binds exactly as it did before this option existed. */ export declare function listenOptionsFromEnv(env: NodeJS.ProcessEnv): ListenOptions; /** * The address for the startup log line. Unset host keeps the historical * `:8080` form that deploy logs and the docs grep for; an IPv6 literal gets * brackets so the port stays readable. */ export declare function describeListen({ port, host }: ListenOptions): string; /** * Tier-2 proof of access: does this Coolify API token belong to someone with * access to the instance this container manages? `GET /teams/current` 401s on * a bad token and returns the token's team on a good one. The token is used * for exactly this one request and then discarded — never stored, never used * to act. */ export declare function validateCoolifyToken(baseUrl: string, presentedToken: string, extraHeaders?: Record): Promise<{ ok: true; teamName: string; } | { ok: false; }>; /** * Fixed-window per-IP rate limiter for the endpoints that take guesses * (token, register, authorize POST). Deliberately simple: one container * serves one team, so the goal is blunting brute force, not fairness. */ export declare class RateLimiter { private readonly limit; private readonly windowMs; private readonly windows; constructor(limit: number, windowMs: number); allow(key: string): boolean; } export declare function createHttpApp(config: HttpServerConfig): { fetch: (request: Request) => Promise; provider: OAuthProvider; };