import type { StoredSession } from '@frontmcp/auth'; import type { ServerResponse, TransportPersistenceConfigInput } from '../common'; import type { Scope } from '../scope'; import type { TransportBus, Transporter, TransportType } from './transport.types'; export declare class TransportService { readonly ready: Promise; private readonly byType; private readonly distributed; private readonly bus?; private readonly scope; /** * Session history cache for tracking if sessions were ever created. * Used to differentiate between "session never initialized" (HTTP 400) and * "session expired/terminated" (HTTP 404) per MCP Spec 2025-11-25. * * Key: JSON-encoded {type, tokenHash, sessionId}, Value: creation timestamp * Note: We use JSON instead of colon-delimiter because sessionId can contain colons. */ private readonly sessionHistory; private readonly MAX_SESSION_HISTORY; /** * Session store for transport persistence (Redis or Vercel KV) * Used to persist session metadata across server restarts */ private sessionStore?; /** * Transport persistence configuration * - `false`: Explicitly disabled * - `object`: Enabled with config (redis, defaultTtlMs) * - `undefined`: Not configured */ private persistenceConfig?; /** * Pending store configuration for async initialization. Either a Redis/Vercel KV * shape or a `{ sqlite: ... }` shape — discriminated in `createSessionStore`. */ private pendingStoreConfig?; /** Set when the configured backend is SQLite. Used for telemetry-only branching. */ private backendKind; /** * Stable key prefix for transport session keys, persisted from config at construction time. * Unlike pendingStoreConfig (cleared after init), this remains available for HA takeover lookups. */ private transportKeyPrefix; /** * Whether a session store backend was configured (regardless of current connection state). * Set once during constructor when persistence config has redis or sqlite. * Used by pingSessionStore() to distinguish "not configured" from "configured but unavailable". */ private sessionStoreConfigured; /** * Mutex map for preventing concurrent transport creation for the same key. * Key: JSON-encoded {t: type, h: tokenHash, s: sessionId}, Value: Promise that resolves when creation completes */ private readonly creationMutex; /** * Get the default TTL for session persistence. * Returns undefined if persistence is disabled or not configured. */ private getDefaultTtlMs; constructor(scope: Scope, persistenceConfig?: false | TransportPersistenceConfigInput, bus?: TransportBus); private initialize; destroy(): Promise; /** * Drain the configured session store and clear the reference. Probes * for `disconnect` (Redis / VercelKV) first, then `close` (SQLite, * which owns a `better-sqlite3` Database handle and MUST be closed * to release the file descriptor). Both the normal `destroy()` and * the startup ping-failure path route through here so SQLite stores * never leak FDs. */ private teardownSessionStore; /** * Ping the session store to check connectivity. * * Returns: * - `true` if no persistence backend was configured (in-memory only) * - `false` if a backend was configured but is unavailable (creation failed or disconnected) * - the result of `store.ping()` if the backend is present and reachable */ pingSessionStore(): Promise; /** True when a backend (Redis / Vercel KV / SQLite) was wired at construction. * Note: this reflects construction-time intent, not live connection state. * A `false` here means no backend was configured at all; a `true` does NOT * guarantee the store is currently reachable — use `pingSessionStore()` for * that signal. */ isSessionStoreConfigured(): boolean; /** The backend kind selected at construction (`undefined` if none). Used by * the orphan-sqlite WARN guard in scope.instance.ts and by error telemetry. */ getBackendKind(): 'redis' | 'vercel-kv' | 'sqlite' | undefined; getTransporter(type: TransportType, token: string, sessionId: string): Promise; /** * Get stored session from Redis (without creating a transport). * Used by flows to check if session exists and can be recreated. * * @param type - Transport type * @param token - Authorization token * @param sessionId - Session ID * @param options - Optional validation options * @param options.clientFingerprint - Client fingerprint for additional validation * @param options.warnOnFingerprintMismatch - If true, log warning on mismatch but still return session * @returns Stored session data if exists and token matches, undefined otherwise */ getStoredSession(type: TransportType, token: string, sessionId: string, options?: { clientFingerprint?: string; warnOnFingerprintMismatch?: boolean; }): Promise; /** * Recreate a transport from stored session data. * Must be called with a valid response object to create the actual transport. * * @param type - Transport type * @param token - Authorization token * @param sessionId - Session ID * @param storedSession - Previously stored session data * @param res - Server response object for the new transport * @returns The recreated transport */ recreateTransporter(type: TransportType, token: string, sessionId: string, storedSession: StoredSession, res: ServerResponse): Promise; /** * Internal method to actually recreate the transport (called with mutex protection) */ private doRecreateTransporter; createTransporter(type: TransportType, token: string, sessionId: string, res: ServerResponse): Promise; /** * Internal method to actually create the transport (called with mutex protection) */ private doCreateTransporter; destroyTransporter(type: TransportType, token: string, sessionId: string, reason?: string): Promise; /** * Update the stored session in Redis with client capabilities from the initialize handshake. * This ensures capabilities survive session recreation (e.g., after server restart or transport eviction). * * Best-effort: failures are logged but do not throw. * No-op when sessionStore is not configured. */ updateStoredSessionCapabilities(sessionId: string, clientCapabilities: Record): Promise; /** * Get or create a shared singleton transport for anonymous stateless requests. * All anonymous requests share the same transport instance. */ getOrCreateAnonymousStatelessTransport(type: TransportType, res: ServerResponse): Promise; /** * Get or create a singleton transport for authenticated stateless requests. * Each unique token gets its own singleton transport. */ getOrCreateAuthenticatedStatelessTransport(type: TransportType, token: string, res: ServerResponse): Promise; /** * Check if a session was ever created (even if it's been terminated/evicted). * Used to differentiate between "session never initialized" (HTTP 400) and * "session expired/terminated" (HTTP 404) per MCP Spec 2025-11-25. * * Note: This is synchronous and only checks local history. For async Redis check, * use wasSessionCreatedAsync. * * @param type - Transport type (e.g., 'streamable-http', 'sse') * @param token - The authorization token * @param sessionId - The session ID to check * @returns true if session was ever created locally, false otherwise */ wasSessionCreated(type: TransportType, token: string, sessionId: string): boolean; /** * Async version that also checks Redis for session existence. * Used when we need to check if session was ever created across server restarts. */ wasSessionCreatedAsync(type: TransportType, token: string, sessionId: string): Promise; private sha256; /** * Create a history key from components. * Uses JSON encoding to handle sessionIds that contain special characters. */ private makeHistoryKey; /** * Parse a history key back into components. * Returns undefined if the key is malformed. */ private parseHistoryKey; private keyOf; private ensureTypeBucket; private ensureTokenBucket; private lookupLocal; private insertLocal; private evictLocal; } //# sourceMappingURL=transport.registry.d.ts.map