/** * HTTP Streamable Transport for MCP * * Implements MCP Specification 2025-03-26 * https://modelcontextprotocol.io/specification/2025-03-26/basic/transports * * Why: Enables remote MCP server access with SSE streaming, session management, * and resumability for reliable communication over HTTP. */ import type { Logger } from '../core/logger.js'; import type { HttpTransportConfig, HttpProfileContext } from '../types/http-transport.js'; import { MetricsCollector } from '../core/metrics.js'; import type { AuthInterceptor, OAuthConfig } from '../types/profile.js'; import type { SessionToolFilter, SessionToolFilterRequest } from '../types/http-transport.js'; import type { ListedProfileDetails } from '../profile/profile-resolver.js'; export declare class HttpTransport { private app; private server; private config; private logger; private metrics; private cleanupInterval; private messageHandler; private profileContextProvider; private profileStates; private oauthRedirectHostCache; private warnedMissingOAuthRedirectEnvVars; private profileHintsByClient; private static readonly PROFILE_HINT_TTL_MS; private profileIndexProvider; private ssrfValidator; private rawTenantConfig; constructor(config: HttpTransportConfig, logger: Logger); getMetricsCollector(): MetricsCollector | null; setProfileIndexProvider(provider: (() => Promise) | null): void; /** * Setup Express middleware * * Why: Security (Origin validation, rate limiting), JSON parsing, session extraction, metrics */ private setupMiddleware; setProfileContextProvider(provider: (profileId: string) => Promise): void; private getDefaultProfileId; private buildDefaultProfileContext; private getProfileState; private getProfileIdForRequest; private getProfileStateForRequest; private hasWarnedAboutBinding; /** * Check if origin is allowed * * Why: Prevent DNS rebinding attacks * * Supports: * - Exact hostname: 'example.com', 'api.example.com' * - Wildcard subdomain: '*.example.com' * - IPv4 CIDR: '192.168.1.0/24', '10.0.0.0/8' * - IPv4 exact: '192.168.1.100' */ private isAllowedOrigin; private getOAuthRedirectHostPatterns; private extractRedirectHostPatterns; private resolveRedirectUriFromEnv; private resolveProfileIdFromPath; private getClientHintKey; private storeProfileHint; private resolveProfileIdFromHint; private resolveProfileIdForOriginCheck; private primeOAuthRedirectHosts; private isAllowedOriginForRequest; /** * Match hostname against allowed origin pattern * * Supports: * - Exact match: 'example.com' === 'example.com' * - Wildcard: '*.example.com' matches 'api.example.com', 'web.example.com' * - CIDR: '192.168.1.0/24' matches '192.168.1.1' through '192.168.1.254' */ private matchOrigin; /** * Check if IP address is within CIDR range (IPv4 or IPv6) * * Example: '192.168.1.50' matches '192.168.1.0/24' * '2001:db8::1' matches '2001:db8::/32' */ private matchCIDR; /** * Convert IPv4 address to 32-bit integer * * Example: '192.168.1.1' -> 3232235777 */ private ipv4ToInt; /** * Convert IPv6 address to 128-bit BigInt */ private ipv6ToBigInt; private ipv6Mask; private stripIpv6Brackets; /** * Create configured rate limiter or a passthrough handler when disabled * * Why: Both MCP and metrics endpoints share the same rate limiting setup logic. * Centralizing it keeps behaviour consistent and avoids drifting configuration. */ private createRateLimiter; private formatRateLimitMessage; private getProfilePrefix; private buildProfilePath; private getServerOrigin; private buildProfileUrl; private normalizeResourcePath; private resolveProfileInfoFromResourceUrl; private resolveProfileIdFromResourceUrl; getOAuthProtectedResourceUrl(profileId?: string): string; private respondProfileNotFound; /** * Setup MCP endpoint routes * * Why: Single endpoint for POST (client→server) and GET (SSE stream) */ private setupRoutes; private getProfileIssuerUrl; private getRequestOrigin; private handleProfileIndex; private enrichProfilesForIndexWithTenants; private buildProfileIndexTenantSummary; private handleOAuthProtectedResource; private handleOAuthAuthorize; private handleOAuthToken; private compareSecretsConstantTime; private isProxyCompatibilityClient; private resolveOAuthClientForRequest; private validateOAuthClientCredentials; private handleOAuthCallback; private handleOAuthAuthorizationServerMetadata; private handleOAuthRegister; /** * Handle metrics endpoint * * Why: Prometheus scraping endpoint */ private handleMetrics; /** * Validate authentication token by making a probe request to the API * * Supports all auth types: bearer, query, custom-header * Returns true if token is valid, false otherwise */ /** * Builds a URL by intelligently combining base URL and endpoint * Handles absolute URLs, absolute paths, and relative paths correctly */ private buildUrl; private validateAuthToken; private isAllowedValidationHost; /** * Validate token format and length * * Why centralized: Single source of truth for token validation rules * * Relaxed validation: Allow common API token characters including colons, * to support various token formats (GitLab glpat-, YouTrack perm:, etc.) */ private validateToken; private hasServerEnvAuthToken; private resolveEffectiveAuthContext; private getTenantIndex; private getTenantOAuthProviderCache; /** * Extract and validate auth token from request headers * * Supports: * - Authorization: Bearer * - X-API-Token: * - OAuth session (via mcp-session-id header) * * Why strict validation: Prevents header injection attacks * * Returns: { type: 'bearer' | 'oauth' | 'api-token', token: string, sessionId?: string } */ private extractAuthToken; /** * Lazy initialization of ToolFilterService */ private getToolFilterService; /** * Handle POST requests - Client sending messages to server * * MCP Spec: POST can contain requests, notifications, or responses */ private handlePost; /** * Handle GET requests - Client opening SSE stream for server messages * * MCP Spec: GET opens SSE stream for server-initiated requests/notifications */ private handleGet; /** * Handle DELETE requests - Client terminating session * * MCP Spec: DELETE explicitly terminates session */ private handleDelete; /** * Start SSE response for a POST request * * Why: Returns response via SSE stream, allows server-initiated messages */ private startSSEResponse; /** * Start SSE stream for GET request * * Why: Allows server to send requests/notifications to client */ private startSSEStream; /** * Replay messages after Last-Event-ID * * Why: Resumability - client can reconnect and receive missed messages */ private replayMessages; /** * Send message to client via SSE * * Why: Server-initiated requests/notifications */ sendToClient(profileId: string, sessionId: string, message: unknown): void; /** * Determine message type (request, notification, response) */ private getMessageType; private getFilteringHeaderValue; private getToolFilterHeaderValue; private validateTenantHeaderValue; private getTenantIdHeaderValue; private getTenantBaseUrlHeaderValue; /** * Create new session * * Why: Stateful sessions for MCP protocol */ private createSession; /** * Update session activity timestamp */ private updateSessionActivity; /** * Destroy session and cleanup resources * * Why: Free memory, close streams */ private destroySession; private attachOAuthClientSession; private detachOAuthClientSession; /** * Session destruction listeners for cleanup in other components */ private sessionDestroyedListeners; /** * Register listener for session destruction events * * Why: Allows MCPServer to cleanup per-session HTTP clients */ onSessionDestroyed(listener: (profileId: string, sessionId: string) => void): void; /** * Notify all listeners about session destruction */ private notifySessionDestroyed; /** * Store OAuth tokens in internal map for later session initialization * * Why: Bridge between /oauth/token endpoint (where we see OAuthTokens) * and session initialization (where we only see access token in Authorization header) */ private storeOAuthTokens; /** * Cleanup expired sessions * * Why: Prevent memory leaks, enforce session timeout * * OAuth sessions with refresh tokens have extended or unlimited timeout * to avoid forcing users to re-authenticate after periods of inactivity */ private cleanupExpiredSessions; /** * Get auth token from session * * Why public: Allows MCPServer to securely access session tokens without breaking encapsulation */ getSessionToken(profileId: string, sessionId: string): string | undefined; getSessionFiltering(profileId: string, sessionId: string): Record | undefined; getSessionFilteringHeader(profileId: string, sessionId: string): string | undefined; getSessionToolFilterRequest(profileId: string, sessionId: string): SessionToolFilterRequest | undefined; getSessionToolFilter(profileId: string, sessionId: string): SessionToolFilter | undefined; getSessionToolFilterHeader(profileId: string, sessionId: string): string | undefined; getSessionTenantContext(profileId: string, sessionId: string): { tenantId?: string; tenantBaseUrl?: string; tenantAuthMode?: 'oauth' | 'token'; tenantOAuthConfig?: OAuthConfig; tenantAuthConfigs?: AuthInterceptor[]; } | undefined; setSessionToolFilter(profileId: string, sessionId: string, toolFilter: SessionToolFilter): void; recordGlobalToolFilterMetrics(summary: { originalCount: number; allowedCount: number; removedCount: number; patternCounts: Record; }): void; recordSessionToolFilterMetrics(sessionId: string, allowedCount: number, request: SessionToolFilterRequest): void; recordToolFilterRejection(tool: string, source: 'env' | 'session'): void; private resolveMetricsContext; /** * Ensure session has a valid access token, refreshing if necessary * * Why: Transparently refresh expired OAuth tokens before making API calls * Returns true if token is valid (or was successfully refreshed), false otherwise */ ensureValidSessionToken(profileId: string, sessionId: string): Promise; private getOAuthProviderForSession; /** * Refresh access token using refresh token * * Why: Automatically renew expired OAuth access tokens without user intervention * Returns true on success, false on failure */ private refreshAccessToken; /** * Set message handler for processing incoming JSON-RPC messages */ setMessageHandler(handler: (message: unknown, sessionId?: string, profileId?: string) => Promise): void; /** * Check if OAuth provider is configured */ hasOAuthProvider(profileId?: string): boolean; /** * Get server URL */ getServerUrl(profileId?: string): string; /** * Get OAuth authorization URL */ getOAuthAuthorizationUrl(profileId?: string, sessionId?: string): string; /** * Get OAuth scopes */ getOAuthScopes(profileId?: string): string[]; /** * Start HTTP server */ start(): Promise; /** * Stop HTTP server */ stop(): Promise; } //# sourceMappingURL=http-transport.d.ts.map