import { type LoggingLevel, type McpServer, type ProgressToken, type Root } from '@frontmcp/protocol'; import type { AIPlatformType, PlatformDetectionConfig } from '../common'; import type { Scope } from '../scope'; /** * Re-export Root from MCP SDK for convenience. * Per MCP 2025-11-25 specification. */ export type { Root }; /** * Re-export AIPlatformType from session types for backwards compatibility. */ export type { AIPlatformType } from '../common/types/auth/session.types'; /** * Alias for MCP SDK's LoggingLevel for backwards compatibility. * @deprecated Use LoggingLevel from @frontmcp/protocol directly */ export type McpLoggingLevel = LoggingLevel; /** * Client capabilities from the initialize request. * Stored per session to understand what the client supports. */ export interface ClientCapabilities { /** Whether the client supports roots and root change notifications */ roots?: { listChanged?: boolean; }; /** Other capabilities can be added here as needed */ sampling?: Record; /** Experimental capabilities including MCP Apps extension */ experimental?: { /** MCP Apps (ext-apps) extension capability */ 'io.modelcontextprotocol/ui'?: { /** Supported MIME types (e.g., ['text/html;profile=mcp-app']) */ mimeTypes?: string[]; }; [key: string]: unknown; }; /** * Elicitation capability - indicates the client supports interactive user input. * Per MCP 2025-11-25 specification. */ elicitation?: { /** Whether the client supports form-based elicitation */ form?: Record; /** Whether the client supports URL-based elicitation */ url?: Record; }; } /** * Client info from the MCP initialize request. * Contains the name and version of the calling client. */ export interface ClientInfo { /** Client application name (e.g., "claude-desktop", "chatgpt", "cursor") */ name: string; /** Client version string */ version: string; } /** * MCP Apps extension key in capabilities.experimental */ export declare const MCP_APPS_EXTENSION_KEY: "io.modelcontextprotocol/ui"; /** * Check if client capabilities include MCP Apps extension. * @param capabilities - Client capabilities from initialize request * @returns true if the client supports MCP Apps */ export declare function hasMcpAppsExtension(capabilities?: ClientCapabilities): boolean; /** * Detect platform from client capabilities. * Checks for known extension capabilities before falling back to client info. * @param capabilities - Client capabilities from initialize request * @returns Platform type if detected from capabilities, undefined otherwise */ export declare function detectPlatformFromCapabilities(capabilities?: ClientCapabilities): AIPlatformType | undefined; /** * Check if client supports elicitation. * * @param capabilities - Client capabilities from initialize request * @param mode - Optional mode to check ('form' or 'url'). If not provided, checks for any elicitation support. * @returns true if elicitation is supported for the given mode */ export declare function supportsElicitation(capabilities?: ClientCapabilities, mode?: 'form' | 'url'): boolean; /** * Detect platform from user-agent header. * Called during session creation before MCP initialize. * * @param userAgent - The User-Agent header value * @param config - Optional platform detection configuration * @returns The detected platform type */ export declare function detectPlatformFromUserAgent(userAgent?: string, config?: PlatformDetectionConfig): AIPlatformType; /** * Detect the AI platform type from client info. * Supports custom mappings that are checked before default detection. * * @param clientInfo - Client info from MCP initialize request * @param config - Optional platform detection configuration with custom mappings * @returns The detected platform type */ export declare function detectAIPlatform(clientInfo?: ClientInfo, config?: PlatformDetectionConfig): AIPlatformType; /** * Check if client capabilities include Claude Code channels support. * @param capabilities - Client capabilities from initialize request * @returns true if the client supports the claude/channel experimental extension */ export declare function supportsChannels(capabilities?: ClientCapabilities): boolean; /** * MCP logging level priority (lower number = more verbose). * Uses LoggingLevel from MCP SDK for type safety. */ export declare const MCP_LOGGING_LEVEL_PRIORITY: Record; /** * MCP notification method types per the 2025-11-25 specification. * * `notifications/skills/list_changed` is FrontMCP's anticipation of SEP-2076 * (Agent Skills as a First-Class MCP Primitive, working-group draft). Many MCP * clients do not honor list_changed notifications today, so this is best-effort: * dynamically-registered skills also expose `bundleVersion` on their SkillContent * so polling clients can detect change cheaply. */ export type McpNotificationMethod = 'notifications/resources/list_changed' | 'notifications/tools/list_changed' | 'notifications/prompts/list_changed' | 'notifications/skills/list_changed' | 'notifications/resources/updated' | 'notifications/message' | 'notifications/progress' | 'notifications/tasks/status'; /** * Information about a registered MCP server/transport connection. */ export interface RegisteredServer { /** Unique session identifier */ sessionId: string; /** The MCP server instance */ server: McpServer; /** Timestamp when the server was registered */ registeredAt: number; /** Client capabilities from the initialize request */ clientCapabilities?: ClientCapabilities; /** Client info (name/version) from the initialize request */ clientInfo?: ClientInfo; /** Detected AI platform type based on client info */ platformType?: AIPlatformType; /** Cached roots from the client (invalidated on roots/list_changed) */ cachedRoots?: Root[]; /** Timestamp when roots were last fetched */ rootsFetchedAt?: number; } /** * NotificationService manages server→client notifications per MCP 2025-11-25 spec. * * It tracks all active MCP server instances and broadcasts notifications when * registries (resources, tools, prompts) change. * * It also manages resource subscriptions per session, allowing clients to * subscribe to specific resource URIs and receive notifications when they change. * * @example * ```typescript * // In LocalTransportAdapter after server.connect() * scope.notifications.registerServer(sessionId, this.server); * * // On session close * scope.notifications.unregisterServer(sessionId); * * // Resource subscriptions * scope.notifications.subscribeResource(sessionId, 'file://path/to/file'); * scope.notifications.unsubscribeResource(sessionId, 'file://path/to/file'); * ``` */ export declare class NotificationService { private readonly scope; private readonly options; private readonly logger; private readonly servers; private readonly unsubscribers; /** Maps session ID to set of subscribed resource URIs */ private readonly subscriptions; /** Maps session ID to set of subscribed channel names */ private readonly channelSubscriptions; /** Maps session ID to minimum log level for that session */ private readonly logLevels; /** * Set of terminated session IDs (for session invalidation on DELETE). * Uses LRU-style eviction to prevent unbounded memory growth. */ private readonly terminatedSessions; /** Maximum number of terminated sessions to track before eviction */ private static readonly MAX_TERMINATED_SESSIONS; constructor(scope: Scope, options?: { maxTerminatedSessions?: number; }); private get maxTerminatedSessions(); /** * Initialize the notification service and subscribe to registry changes. * Called after all registries are ready. */ initialize(): Promise; /** * Register an MCP server instance for receiving notifications. * Call this when a transport connection is established. * * @param sessionId - Unique session identifier * @param server - The MCP server instance */ registerServer(sessionId: string, server: McpServer): void; /** * Unregister an MCP server instance. * Call this when a transport connection is closed. * Also cleans up any resource subscriptions for this session. * * @param sessionId - The session identifier to unregister * @returns true if the server was registered and is now unregistered, false if not found */ unregisterServer(sessionId: string): boolean; /** * Terminate a session. This adds the session ID to the terminated set, * preventing further use of this session ID. This is called during DELETE * session handling per MCP 2025-11-25 spec. * * Note: For stateless sessions (encrypted JWTs), the session ID cannot be * truly "invalidated" cryptographically, but we track it in memory to reject * future requests with this session ID on this server instance. * * @param sessionId - The session ID to terminate * @returns true if the session was registered and is now terminated */ terminateSession(sessionId: string): boolean; /** * Check if a session has been terminated. * Used during session verification to reject requests with terminated session IDs. * * @param sessionId - The session ID to check * @returns true if the session has been terminated */ isSessionTerminated(sessionId: string): boolean; /** * Remove a session from the terminated set. * Called when a terminated session is being re-initialized with the same session ID. */ unmarkTerminated(sessionId: string): void; /** * Broadcast a notification to all registered servers. * * @param method - The MCP notification method * @param params - Optional notification parameters */ broadcastNotification(method: McpNotificationMethod, params?: Record): void; /** * Send a notification to a specific session. * * @param sessionId - The target session * @param method - The MCP notification method * @param params - Optional notification parameters */ sendNotificationToSession(sessionId: string, method: McpNotificationMethod, params?: Record): void; /** * Subscribe a session to receive notifications when a specific resource changes. * * @param sessionId - The session to subscribe * @param uri - The resource URI to subscribe to * @returns true if this is a new subscription, false if already subscribed */ subscribeResource(sessionId: string, uri: string): boolean; /** * Unsubscribe a session from a specific resource. * * @param sessionId - The session to unsubscribe * @param uri - The resource URI to unsubscribe from * @returns true if the subscription was removed, false if not subscribed */ unsubscribeResource(sessionId: string, uri: string): boolean; /** * Check if a session is subscribed to a specific resource. * * @param sessionId - The session to check * @param uri - The resource URI * @returns true if subscribed */ isSubscribed(sessionId: string, uri: string): boolean; /** * Get all sessions subscribed to a specific resource. * * @param uri - The resource URI * @returns Array of session IDs subscribed to this resource */ getSubscribersForResource(uri: string): string[]; /** * Send a resource update notification to subscribed sessions only. * Per MCP 2025-11-25 spec, only sessions that have subscribed to this * resource via `resources/subscribe` will receive the notification. * * @param uri - The resource URI that was updated */ notifyResourceUpdated(uri: string): void; /** * Subscribe a session to receive notifications from a specific channel. * Only sessions subscribed to a channel will receive its notifications. * * @param sessionId - The session to subscribe * @param channelName - The channel name to subscribe to * @returns true if this is a new subscription */ subscribeChannel(sessionId: string, channelName: string): boolean; /** * Unsubscribe a session from a specific channel. * * @param sessionId - The session to unsubscribe * @param channelName - The channel name * @returns true if the subscription was removed */ unsubscribeChannel(sessionId: string, channelName: string): boolean; /** * Subscribe a session to ALL available channels at once. * Convenience method called during initialization when a session has claude/channel capability. * * @param sessionId - The session to subscribe * @param channelNames - Array of all channel names to subscribe to */ subscribeAllChannels(sessionId: string, channelNames: string[]): void; /** * Check if a session is subscribed to a specific channel. */ isChannelSubscribed(sessionId: string, channelName: string): boolean; /** * Get all sessions subscribed to a specific channel. * * @param channelName - The channel name * @returns Array of session IDs subscribed to this channel */ getSubscribersForChannel(channelName: string): string[]; /** * Get all channel names a session is subscribed to. */ getChannelSubscriptions(sessionId: string): string[]; /** * Get the number of registered servers. */ get serverCount(): number; /** * Set the minimum log level for a session. * Only log messages at or above this level will be sent to the session. * Per MCP spec, the default level when not set is determined by the server. * * @param sessionId - The session to configure * @param level - The minimum log level * @returns true if the session was found and level was set */ setLogLevel(sessionId: string, level: McpLoggingLevel): boolean; /** * Get the current log level for a session. * * @param sessionId - The session to query * @returns The log level, or undefined if not set */ getLogLevel(sessionId: string): McpLoggingLevel | undefined; /** * Send a log message to all sessions that have enabled the given level. * Per MCP 2025-11-25 spec, this sends a 'notifications/message' notification * to sessions whose configured minimum level allows this message through. * * @param level - The log level of this message * @param loggerName - Optional logger name/component identifier * @param data - The log message data (string or structured data) */ sendLogMessage(level: McpLoggingLevel, loggerName: string | undefined, data: unknown): void; /** * Send a log message to a specific session, respecting its log level. * * @param sessionId - The target session * @param level - The log level of this message * @param loggerName - Optional logger name/component identifier * @param data - The log message data * @returns true if the message was sent (session exists and level allows) */ sendLogMessageToSession(sessionId: string, level: McpLoggingLevel, loggerName: string | undefined, data: unknown): boolean; /** * Send a progress notification to a specific session. * Per MCP 2025-11-25 spec, this sends a 'notifications/progress' notification * using the progressToken from the original request. * * @param sessionId - The target session * @param progressToken - The progress token from the original request's _meta * @param progress - Current progress value (should increase monotonically) * @param total - Total progress value (optional) * @param message - Progress message (optional) * @returns true if the notification was sent */ sendProgressNotification(sessionId: string, progressToken: ProgressToken, progress: number, total?: number, message?: string): boolean; /** * Set client capabilities for a session. * Called during initialization to store what the client supports. * * @param sessionId - The session to configure * @param capabilities - The client's capabilities from the initialize request * @returns true if the session was found and capabilities were set */ setClientCapabilities(sessionId: string, capabilities: ClientCapabilities): boolean; /** * Get client capabilities for a session. * * @param sessionId - The session to query * @returns The client's capabilities, or undefined if not set */ getClientCapabilities(sessionId: string): ClientCapabilities | undefined; /** * Set client info for a session and auto-detect the AI platform type. * Called during initialization to store who the client is. * Uses the scope's platform detection configuration for custom mappings. * * @param sessionId - The session to configure * @param clientInfo - The client's info (name/version) from the initialize request * @returns The detected platform type, or undefined if the session was not found */ setClientInfo(sessionId: string, clientInfo: ClientInfo): AIPlatformType | undefined; /** * Get client info for a session. * * @param sessionId - The session to query * @returns The client's info, or undefined if not set */ getClientInfo(sessionId: string): ClientInfo | undefined; /** * Get the detected AI platform type for a session. * This is auto-detected from client info during initialization. * * @param sessionId - The session to query * @returns The detected platform type, or 'unknown' if not detected */ getPlatformType(sessionId: string): AIPlatformType; /** * Check if a session's client supports roots listing. * * @param sessionId - The session to check * @returns true if the client supports roots */ supportsRoots(sessionId: string): boolean; /** * List roots from the client for a session. * This sends a `roots/list` request to the client and returns the roots. * * If the client doesn't support roots, returns an empty array. * Results are cached and invalidated when `notifications/roots/list_changed` is received. * * @param sessionId - The session to request roots from * @param options - Options for the request * @param options.forceRefresh - If true, bypass the cache and fetch fresh roots * @param options.timeout - Timeout in milliseconds (default: 30000) * @returns Array of roots from the client */ listRoots(sessionId: string, options?: { forceRefresh?: boolean; timeout?: number; }): Promise; /** * Invalidate cached roots for a session. * Call this when receiving `notifications/roots/list_changed` from the client. * * @param sessionId - The session whose roots cache should be invalidated * @returns true if the session was found and cache was invalidated */ invalidateRootsCache(sessionId: string): boolean; /** * Get the cached roots for a session without fetching from the client. * * @param sessionId - The session to query * @returns Cached roots, or undefined if not cached */ getCachedRoots(sessionId: string): Root[] | undefined; /** * Clean up subscriptions and resources. */ destroy(): Promise; /** * Send a custom/experimental notification to sessions matching an optional filter. * Used for non-standard notification methods like 'notifications/claude/channel'. * * @param method - The notification method string (can be non-standard) * @param params - Notification parameters * @param filter - Optional filter function to select which sessions receive the notification */ sendCustomNotification(method: string, params: Record, filter?: (session: RegisteredServer) => boolean): void; /** * Get a registered server by session ID. * Used by ChannelNotificationService for targeted sends. * * @param sessionId - The session ID * @returns The registered server info, or undefined if not found */ getRegisteredServer(sessionId: string): RegisteredServer | undefined; private sendNotificationToServer; } //# sourceMappingURL=notification.service.d.ts.map