diff --git a/src/server/streamableHttp.ts b/src/server/streamableHttp.ts index d57e75cd7..7d551d97a 100644 --- a/src/server/streamableHttp.ts +++ b/src/server/streamableHttp.ts @@ -17,6 +17,7 @@ import getRawBody from 'raw-body'; import contentType from 'content-type'; import { randomUUID } from 'node:crypto'; import { AuthInfo } from './auth/types.js'; +import { consoleLogger, Logger } from '../shared/logger.js'; const MAXIMUM_MESSAGE_SIZE = '4mb'; @@ -108,6 +109,11 @@ export interface StreamableHTTPServerTransportOptions { * Default is false for backwards compatibility. */ enableDnsRebindingProtection?: boolean; + + /** + * A custom logger to use for SDK internal logs. + */ + logger?: Logger; } /** @@ -160,6 +166,7 @@ export class StreamableHTTPServerTransport implements Transport { private _allowedHosts?: string[]; private _allowedOrigins?: string[]; private _enableDnsRebindingProtection: boolean; + private _logger: Logger; sessionId?: string; onclose?: () => void; @@ -175,6 +182,7 @@ export class StreamableHTTPServerTransport implements Transport { this._allowedHosts = options.allowedHosts; this._allowedOrigins = options.allowedOrigins; this._enableDnsRebindingProtection = options.enableDnsRebindingProtection ?? false; + this._logger = options.logger ?? consoleLogger; } /** @@ -727,6 +735,7 @@ export class StreamableHTTPServerTransport implements Transport { const standaloneSse = this._streamMapping.get(this._standaloneSseStreamId); if (standaloneSse === undefined) { // The spec says the server MAY send messages on the stream, so it's ok to discard if no stream + this._logger.warning(`No standalone SSE stream found, discarding message: ${message.method}`); return; } diff --git a/src/shared/logger.ts b/src/shared/logger.ts new file mode 100644 index 000000000..306c0b40e --- /dev/null +++ b/src/shared/logger.ts @@ -0,0 +1,73 @@ +/** + * LogLevel - SysLog RFC5424 compliant log levels + * + * @see RFC5424: https://tools.ietf.org/html/rfc5424 + */ +export interface LogLevels { + emerg: number; + alert: number; + crit: number; + error: number; + warning: number; + notice: number; + info: number; + debug: number; +} + +export const LogLevels: LogLevels = { + emerg: 0, + alert: 1, + crit: 2, + error: 3, + warning: 4, + notice: 5, + info: 6, + debug: 7 +}; + +/** + * Logger - SysLog RFC5424 compliant logger type + * + * @see RFC5424: https://tools.ietf.org/html/rfc5424 + */ +export type Logger = { + [Level in keyof LogLevels]: (message: string, extra?: Record) => void; +}; + +/** + * Console logger implementation of the Logger interface, to be used by default if no custom logger is provided. + * + * @remarks + * The console logger will log to the console. + * + * The console logger will log at the following levels: + * - log (alias for console.debug) + * - info + * - error + */ +export const consoleLogger: Logger = { + debug: (message, extra) => { + console.log(message, extra); + }, + info: (message, extra) => { + console.info(message, extra); + }, + notice: (message, extra) => { + console.info(message, extra); + }, + warning: (message, extra) => { + console.warn(message, extra); + }, + error: (message, extra) => { + console.error(message, extra); + }, + crit: (message, extra) => { + console.error(message, extra); + }, + alert: (message, extra) => { + console.error(message, extra); + }, + emerg: (message, extra) => { + console.error(message, extra); + } +}; diff --git a/src/shared/protocol.ts b/src/shared/protocol.ts index 5cb969418..e9f73f318 100644 --- a/src/shared/protocol.ts +++ b/src/shared/protocol.ts @@ -27,6 +27,7 @@ import { } from '../types.js'; import { Transport, TransportSendOptions } from './transport.js'; import { AuthInfo } from '../server/auth/types.js'; +import { consoleLogger, Logger } from './logger.js'; /** * Callback for progress notifications. @@ -52,6 +53,11 @@ export type ProtocolOptions = { * e.g., ['notifications/tools/list_changed'] */ debouncedNotificationMethods?: string[]; + + /** + * A custom logger to use for SDK internal logs. + */ + logger?: Logger; }; /** @@ -184,6 +190,7 @@ export abstract class Protocol = new Map(); private _timeoutInfo: Map = new Map(); private _pendingDebouncedNotifications = new Set(); + private readonly _logger: Logger; /** * Callback for when the connection is closed for any reason. @@ -210,7 +217,9 @@ export abstract class Protocol Promise; constructor(private _options?: ProtocolOptions) { + this._logger = _options?.logger ?? consoleLogger; this.setNotificationHandler(CancelledNotificationSchema, notification => { + this._logger.debug('Received a cancelled notification', { notification }); const controller = this._requestHandlerAbortControllers.get(notification.params.requestId); controller?.abort(notification.params.reason); });