tracelattice 1.3.2 → 1.3.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/README.md +25 -25
  2. package/dist/ServerConfig.d.ts +16 -23
  3. package/dist/ServerConfig.d.ts.map +1 -1
  4. package/dist/ServerConfig.js +12 -1
  5. package/dist/ServerConfig.js.map +1 -1
  6. package/dist/__tests__/core/HistoryManager.ownership.test.d.ts +2 -0
  7. package/dist/__tests__/core/HistoryManager.ownership.test.d.ts.map +1 -0
  8. package/dist/__tests__/core/SessionLock.test.d.ts +6 -0
  9. package/dist/__tests__/core/SessionLock.test.d.ts.map +1 -0
  10. package/dist/__tests__/core/SessionManager.test.d.ts +8 -0
  11. package/dist/__tests__/core/SessionManager.test.d.ts.map +1 -0
  12. package/dist/__tests__/core/ThoughtProcessor.toolAllowlist.test.d.ts +2 -0
  13. package/dist/__tests__/core/ThoughtProcessor.toolAllowlist.test.d.ts.map +1 -0
  14. package/dist/__tests__/eval/fixtures/scenarios.d.ts.map +1 -1
  15. package/dist/__tests__/helpers/factories.d.ts +20 -1
  16. package/dist/__tests__/helpers/factories.d.ts.map +1 -1
  17. package/dist/__tests__/sanitize.enforceJsonShape.test.d.ts +2 -0
  18. package/dist/__tests__/sanitize.enforceJsonShape.test.d.ts.map +1 -0
  19. package/dist/__tests__/transport-owner-context.test.d.ts +8 -0
  20. package/dist/__tests__/transport-owner-context.test.d.ts.map +1 -0
  21. package/dist/cache/DiscoveryCache.d.ts +1 -1
  22. package/dist/cache/DiscoveryCache.d.ts.map +1 -1
  23. package/dist/cache/DiscoveryCache.js.map +1 -1
  24. package/dist/cli.js +3602 -8
  25. package/dist/config/ConfigLoader.d.ts +9 -2
  26. package/dist/config/ConfigLoader.d.ts.map +1 -1
  27. package/dist/config/ConfigLoader.js +12 -5
  28. package/dist/config/ConfigLoader.js.map +1 -1
  29. package/dist/context/RequestContext.d.ts +26 -0
  30. package/dist/context/RequestContext.d.ts.map +1 -1
  31. package/dist/context/RequestContext.js +7 -1
  32. package/dist/context/RequestContext.js.map +1 -1
  33. package/dist/contracts/PersistenceBackend.d.ts.map +1 -0
  34. package/dist/contracts/features.d.ts +39 -0
  35. package/dist/contracts/features.d.ts.map +1 -0
  36. package/dist/contracts/features.js +15 -0
  37. package/dist/contracts/features.js.map +1 -0
  38. package/dist/contracts/ids.d.ts +58 -0
  39. package/dist/contracts/ids.d.ts.map +1 -0
  40. package/dist/contracts/ids.js +31 -0
  41. package/dist/contracts/ids.js.map +1 -0
  42. package/dist/contracts/interfaces.d.ts +48 -3
  43. package/dist/contracts/interfaces.d.ts.map +1 -1
  44. package/dist/contracts/strategy.d.ts +2 -2
  45. package/dist/contracts/strategy.d.ts.map +1 -1
  46. package/dist/contracts/suspension.d.ts +3 -2
  47. package/dist/contracts/suspension.d.ts.map +1 -1
  48. package/dist/contracts/transport.d.ts +25 -0
  49. package/dist/contracts/transport.d.ts.map +1 -0
  50. package/dist/core/HistoryManager.d.ts +15 -4
  51. package/dist/core/HistoryManager.d.ts.map +1 -1
  52. package/dist/core/HistoryManager.js +25 -14
  53. package/dist/core/HistoryManager.js.map +1 -1
  54. package/dist/core/IHistoryManager.d.ts +10 -0
  55. package/dist/core/IHistoryManager.d.ts.map +1 -1
  56. package/dist/core/IThoughtFormatter.d.ts +51 -0
  57. package/dist/core/IThoughtFormatter.d.ts.map +1 -0
  58. package/dist/core/IThoughtFormatter.js +1 -0
  59. package/dist/core/InputNormalizer.d.ts.map +1 -1
  60. package/dist/core/InputNormalizer.js +9 -5
  61. package/dist/core/InputNormalizer.js.map +1 -1
  62. package/dist/core/PersistenceBuffer.d.ts +1 -1
  63. package/dist/core/PersistenceBuffer.d.ts.map +1 -1
  64. package/dist/core/PersistenceBuffer.js.map +1 -1
  65. package/dist/core/SessionLock.d.ts +56 -0
  66. package/dist/core/SessionLock.d.ts.map +1 -0
  67. package/dist/core/SessionLock.js +43 -0
  68. package/dist/core/SessionLock.js.map +1 -0
  69. package/dist/core/SessionManager.d.ts +18 -3
  70. package/dist/core/SessionManager.d.ts.map +1 -1
  71. package/dist/core/SessionManager.js +34 -1
  72. package/dist/core/SessionManager.js.map +1 -1
  73. package/dist/core/ThoughtFormatter.d.ts +2 -1
  74. package/dist/core/ThoughtFormatter.d.ts.map +1 -1
  75. package/dist/core/ThoughtFormatter.js +3 -0
  76. package/dist/core/ThoughtFormatter.js.map +1 -1
  77. package/dist/core/ThoughtProcessor.d.ts +22 -3
  78. package/dist/core/ThoughtProcessor.d.ts.map +1 -1
  79. package/dist/core/ThoughtProcessor.js +41 -16
  80. package/dist/core/ThoughtProcessor.js.map +1 -1
  81. package/dist/core/compression/CompressionService.js +3 -3
  82. package/dist/core/compression/CompressionService.js.map +1 -1
  83. package/dist/core/compression/Summary.d.ts +4 -3
  84. package/dist/core/compression/Summary.d.ts.map +1 -1
  85. package/dist/core/graph/Edge.d.ts +11 -4
  86. package/dist/core/graph/Edge.d.ts.map +1 -1
  87. package/dist/core/graph/EdgeEmitter.js +5 -5
  88. package/dist/core/graph/EdgeEmitter.js.map +1 -1
  89. package/dist/core/reasoning/strategies/StrategyFactory.d.ts +1 -1
  90. package/dist/core/reasoning/strategies/StrategyFactory.d.ts.map +1 -1
  91. package/dist/core/reasoning/strategies/StrategyFactory.js.map +1 -1
  92. package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.d.ts.map +1 -1
  93. package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.js +5 -0
  94. package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.js.map +1 -1
  95. package/dist/core/reasoning.d.ts +8 -1
  96. package/dist/core/reasoning.d.ts.map +1 -1
  97. package/dist/core/step.d.ts +5 -0
  98. package/dist/core/step.d.ts.map +1 -1
  99. package/dist/core/thought.d.ts +4 -3
  100. package/dist/core/thought.d.ts.map +1 -1
  101. package/dist/core/tools/InMemorySuspensionStore.d.ts +3 -1
  102. package/dist/core/tools/InMemorySuspensionStore.d.ts.map +1 -1
  103. package/dist/core/tools/InMemorySuspensionStore.js +2 -2
  104. package/dist/core/tools/InMemorySuspensionStore.js.map +1 -1
  105. package/dist/di/Container.d.ts +6 -3
  106. package/dist/di/Container.d.ts.map +1 -1
  107. package/dist/di/Container.js.map +1 -1
  108. package/dist/di/ServiceRegistry.d.ts +6 -6
  109. package/dist/di/ServiceRegistry.d.ts.map +1 -1
  110. package/dist/errors.d.ts +84 -2
  111. package/dist/errors.d.ts.map +1 -1
  112. package/dist/errors.js +85 -22
  113. package/dist/errors.js.map +1 -1
  114. package/dist/health/HealthChecker.d.ts +1 -1
  115. package/dist/health/HealthChecker.d.ts.map +1 -1
  116. package/dist/health/HealthChecker.js.map +1 -1
  117. package/dist/lib.d.ts +60 -2
  118. package/dist/lib.d.ts.map +1 -1
  119. package/dist/lib.js +9 -3
  120. package/dist/lib.js.map +1 -1
  121. package/dist/persistence/FilePersistence.d.ts +2 -2
  122. package/dist/persistence/FilePersistence.d.ts.map +1 -1
  123. package/dist/persistence/FilePersistence.js.map +1 -1
  124. package/dist/persistence/MemoryPersistence.d.ts +1 -1
  125. package/dist/persistence/MemoryPersistence.d.ts.map +1 -1
  126. package/dist/persistence/MemoryPersistence.js.map +1 -1
  127. package/dist/persistence/PersistenceFactory.d.ts +1 -1
  128. package/dist/persistence/PersistenceFactory.d.ts.map +1 -1
  129. package/dist/persistence/PersistenceFactory.js.map +1 -1
  130. package/dist/persistence/SqlitePersistence.d.ts +1 -1
  131. package/dist/persistence/SqlitePersistence.d.ts.map +1 -1
  132. package/dist/persistence/SqlitePersistence.js.map +1 -1
  133. package/dist/pool/ConnectionPool.d.ts +11 -13
  134. package/dist/pool/ConnectionPool.d.ts.map +1 -1
  135. package/dist/pool/ConnectionPool.js.map +1 -1
  136. package/dist/pool/IConnectionPool.d.ts +100 -0
  137. package/dist/pool/IConnectionPool.d.ts.map +1 -0
  138. package/dist/pool/IConnectionPool.js +1 -0
  139. package/dist/registry/BaseRegistry.d.ts +1 -1
  140. package/dist/registry/BaseRegistry.d.ts.map +1 -1
  141. package/dist/registry/BaseRegistry.js.map +1 -1
  142. package/dist/registry/ToolRegistry.d.ts +1 -0
  143. package/dist/registry/ToolRegistry.d.ts.map +1 -1
  144. package/dist/registry/ToolRegistry.js +3 -0
  145. package/dist/registry/ToolRegistry.js.map +1 -1
  146. package/dist/sanitize.d.ts +70 -0
  147. package/dist/sanitize.d.ts.map +1 -1
  148. package/dist/sanitize.js +77 -1
  149. package/dist/sanitize.js.map +1 -1
  150. package/dist/schema.d.ts +35 -35
  151. package/dist/schema.d.ts.map +1 -1
  152. package/dist/schema.js +15 -5
  153. package/dist/schema.js.map +1 -1
  154. package/dist/transport/BaseTransport.d.ts +3 -2
  155. package/dist/transport/BaseTransport.d.ts.map +1 -1
  156. package/dist/transport/BaseTransport.js +1 -1
  157. package/dist/transport/BaseTransport.js.map +1 -1
  158. package/dist/transport/HttpTransport.d.ts +4 -2
  159. package/dist/transport/HttpTransport.d.ts.map +1 -1
  160. package/dist/transport/HttpTransport.js +13 -4
  161. package/dist/transport/HttpTransport.js.map +1 -1
  162. package/dist/transport/SseTransport.d.ts +4 -2
  163. package/dist/transport/SseTransport.d.ts.map +1 -1
  164. package/dist/transport/SseTransport.js +13 -3
  165. package/dist/transport/SseTransport.js.map +1 -1
  166. package/dist/transport/StreamableHttpTransport.d.ts +4 -2
  167. package/dist/transport/StreamableHttpTransport.d.ts.map +1 -1
  168. package/dist/transport/StreamableHttpTransport.js +12 -4
  169. package/dist/transport/StreamableHttpTransport.js.map +1 -1
  170. package/dist/types/skill.d.ts +5 -0
  171. package/dist/types/skill.d.ts.map +1 -1
  172. package/dist/types/tool.d.ts +6 -1
  173. package/dist/types/tool.d.ts.map +1 -1
  174. package/package.json +12 -11
  175. package/dist/__tests__/helpers/index.d.ts +0 -3
  176. package/dist/__tests__/helpers/index.d.ts.map +0 -1
  177. package/dist/contracts/index.d.ts +0 -14
  178. package/dist/contracts/index.d.ts.map +0 -1
  179. package/dist/index.d.ts +0 -2
  180. package/dist/index.d.ts.map +0 -1
  181. package/dist/index.js +0 -1
  182. package/dist/persistence/PersistenceBackend.d.ts.map +0 -1
  183. /package/dist/{persistence → contracts}/PersistenceBackend.d.ts +0 -0
  184. /package/dist/{persistence → contracts}/PersistenceBackend.js +0 -0
  185. /package/dist/contracts/{index.js → transport.js} +0 -0
@@ -1 +1 @@
1
- {"version":3,"file":"transport/BaseTransport.js","sources":["../../src/transport/BaseTransport.ts"],"sourcesContent":["/**\n * Base transport implementation.\n *\n * This class provides shared functionality for all transport implementations,\n * including session validation, rate limiting, CORS handling, and IP extraction.\n *\n * @remarks\n * **Security Features:**\n * - Session ID validation (alphanumeric, max 64 chars)\n * - Query parameter sanitization (whitelist allowed keys)\n * - Rate limiting per IP (configurable, default 100 req/min)\n * - CORS origin validation\n *\n * **Rate Limiting:**\n * - Tracks requests per IP address within a time window\n * - Returns 429 Too Many Requests when limit exceeded\n * - Can be disabled via `enableRateLimit: false`\n */\n\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { URL } from 'node:url';\nimport type { HealthChecker } from '../health/HealthChecker.js';\nimport type { Logger, LogLevel } from '../logger/StructuredLogger.js';\nimport { SESSION_ID_PATTERN, MAX_SESSION_ID_LENGTH } from '../core/ids.js';\n\n/**\n * No-op logger that does nothing. Used when no logger is provided.\n */\nclass NoopLogger implements Logger {\n\tprivate _level: LogLevel = 'info';\n\n\tinfo(_message: string, _meta?: Record<string, unknown>): void {}\n\twarn(_message: string, _meta?: Record<string, unknown>): void {}\n\terror(_message: string, _meta?: Record<string, unknown>): void {}\n\tdebug(_message: string, _meta?: Record<string, unknown>): void {}\n\tsetLevel(level: LogLevel): void {\n\t\tthis._level = level;\n\t}\n\tgetLevel(): LogLevel {\n\t\treturn this._level;\n\t}\n}\n\n/**\n * Allowed query parameter names (whitelist for security).\n */\nconst ALLOWED_QUERY_PARAMS = new Set(['session', 'sessionId', 'client', 'clientId']);\n\n\n\n/**\n * Rate limit settings (requests per minute per IP).\n */\nconst RATE_LIMIT_REQUESTS = 100;\nconst RATE_LIMIT_WINDOW_MS = 60 * 1000; // 1 minute\n\nexport interface TransportOptions {\n\tport?: number;\n\thost?: string;\n\tallowedHosts?: string[];\n\tcorsOrigin?: string;\n\tenableCors?: boolean;\n\tenableRateLimit?: boolean;\n\tmaxRequestsPerMinute?: number;\n\tlogger?: Logger;\n\thealthChecker?: HealthChecker;\n}\n\nexport abstract class BaseTransport {\n\tprotected _port: number;\n\tprotected _host: string;\n\tprotected _corsOrigin: string;\n\tprotected _enableCors: boolean;\n\tprotected _rateLimitEnabled: boolean;\n\tprotected _maxRequestsPerMinute: number;\n\tprotected _allowedHosts: Set<string>;\n\tprotected _rateLimitMap: Map<string, { count: number; resetTime: number }> = new Map();\n\tprotected _rateLimitCleanupIntervalId: NodeJS.Timeout | null = null;\n\tprotected _wasHostExplicitlySet: boolean;\n\t/** Shutdown state for graceful shutdown. */\n\tprotected _isShuttingDown: boolean = false;\n\tprivate _logger: Logger | NoopLogger;\n\tprotected _healthChecker: HealthChecker | null;\n\n\tconstructor(options: TransportOptions = {}) {\n\t\tthis._port = options.port ?? 9108;\n\t\tthis._host = options.host ?? '127.0.0.1';\n\t\tthis._wasHostExplicitlySet = options.host !== undefined;\n\t\tthis._corsOrigin = options.corsOrigin ?? '*';\n\t\tthis._enableCors = options.enableCors ?? true;\n\t\tthis._rateLimitEnabled = options.enableRateLimit ?? true;\n\t\tthis._maxRequestsPerMinute = options.maxRequestsPerMinute ?? RATE_LIMIT_REQUESTS;\n\t\tthis._allowedHosts = this._buildAllowedHosts(options.allowedHosts);\n\t\tthis._isShuttingDown = false;\n\t\tthis._logger = options.logger ?? new NoopLogger();\n\t\tthis._healthChecker = options.healthChecker ?? null;\n\n\t\tif (this._rateLimitEnabled) {\n\t\t\tthis._startRateLimitCleanup();\n\t\t}\n\t}\n\n\t/**\n\t * Get the server URL with localhost substitution for default host.\n\t */\n\tget serverUrl(): string {\n\t\tconst host =\n\t\t\t!this._wasHostExplicitlySet && this._host === '127.0.0.1' ? 'localhost' : this._host;\n\t\treturn `http://${host}:${this._port}`;\n\t}\n\n\t/**\n\t * Validate session ID format.\n\t *\n\t * @param sessionId - The session ID to validate\n\t * @returns true if valid, false otherwise\n\t */\n\tprotected validateSessionId(sessionId: string): boolean {\n\t\tif (sessionId.length > MAX_SESSION_ID_LENGTH) {\n\t\t\treturn false;\n\t\t}\n\t\treturn SESSION_ID_PATTERN.test(sessionId);\n\t}\n\n\t/**\n\t * Sanitize query parameters by removing any not in whitelist.\n\t *\n\t * @param url - The URL object containing query parameters\n\t * @returns A sanitized record of allowed query parameters\n\t */\n\tprotected sanitizeQueryParams(url: URL): Record<string, string> {\n\t\tconst sanitized: Record<string, string> = {};\n\n\t\tfor (const [key, value] of url.searchParams.entries()) {\n\t\t\tif (ALLOWED_QUERY_PARAMS.has(key)) {\n\t\t\t\tsanitized[key] = value;\n\t\t\t}\n\t\t}\n\n\t\treturn sanitized;\n\t}\n\n\t/**\n\t * Check rate limit for a given IP address.\n\t *\n\t * @param ip - The IP address to check\n\t * @returns true if rate limit exceeded, false otherwise\n\t */\n\tprotected checkRateLimit(ip: string): boolean {\n\t\tif (!this._rateLimitEnabled) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst now = Date.now();\n\t\tthis._cleanupExpiredRateLimitEntries(now);\n\t\tconst record = this._rateLimitMap.get(ip);\n\n\t\tif (!record || now > record.resetTime) {\n\t\t\tthis._rateLimitMap.set(ip, {\n\t\t\t\tcount: 1,\n\t\t\t\tresetTime: now + RATE_LIMIT_WINDOW_MS,\n\t\t\t});\n\t\t\treturn false;\n\t\t}\n\n\t\tif (record.count >= this._maxRequestsPerMinute) {\n\t\t\treturn true; // Rate limit exceeded\n\t\t}\n\n\t\trecord.count++;\n\t\treturn false;\n\t}\n\n\tprotected _cleanupExpiredRateLimitEntries(now = Date.now()): void {\n\t\tfor (const [ip, record] of this._rateLimitMap.entries()) {\n\t\t\tif (record.resetTime <= now) {\n\t\t\t\tthis._rateLimitMap.delete(ip);\n\t\t\t}\n\t\t}\n\t}\n\n\tprotected _startRateLimitCleanup(): void {\n\t\tif (this._rateLimitCleanupIntervalId !== null) {\n\t\t\tclearInterval(this._rateLimitCleanupIntervalId);\n\t\t}\n\n\t\tthis._rateLimitCleanupIntervalId = setInterval(() => {\n\t\t\tthis._cleanupExpiredRateLimitEntries();\n\t\t}, RATE_LIMIT_WINDOW_MS);\n\t}\n\n\tprotected _stopRateLimitCleanup(): void {\n\t\tif (this._rateLimitCleanupIntervalId !== null) {\n\t\t\tclearInterval(this._rateLimitCleanupIntervalId);\n\t\t\tthis._rateLimitCleanupIntervalId = null;\n\t\t}\n\t}\n\n\t/**\n\t * Get client IP address from request.\n\t *\n\t * @param req - The incoming request\n\t * @returns The client IP address\n\t */\n\tprotected getClientIp(req: IncomingMessage): string {\n\t\tconst forwardedFor = req.headers['x-forwarded-for'];\n\t\tif (forwardedFor && typeof forwardedFor === 'string') {\n\t\t\treturn forwardedFor.split(',')[0]!.trim();\n\t\t}\n\t\tconst remoteAddress = req.socket.remoteAddress;\n\t\treturn remoteAddress || 'unknown';\n\t}\n\n\t/**\n\t * Validate CORS origin from request headers.\n\t *\n\t * @param req - The incoming request\n\t * @returns true if origin is valid, false otherwise\n\t */\n\tprotected validateCorsOrigin(req: IncomingMessage): boolean {\n\t\tif (this._corsOrigin === '*') {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst origin = req.headers.origin;\n\t\tif (!origin) {\n\t\t\treturn true; // No origin header is acceptable\n\t\t}\n\n\t\t// Exact match\n\t\tif (this._corsOrigin === origin) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// Check if configured origin is a wildcard pattern\n\t\tif (this._corsOrigin.includes('*')) {\n\t\t\t// Escape all regex metacharacters EXCEPT *,\n\t\t\t// then replace * with a hostname-safe pattern (alphanumeric, hyphens, dots)\n\t\t\tconst escaped = this._corsOrigin\n\t\t\t\t.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&') // escape metacharacters (not *)\n\t\t\t\t.replace(/\\*/g, '[a-zA-Z0-9.-]*'); // * matches valid hostname chars only\n\t\t\tconst regex = new RegExp(`^${escaped}$`);\n\t\t\treturn regex.test(origin);\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Set CORS headers on response.\n\t *\n\t * @param res - The server response\n\t */\n\tprotected setCorsHeaders(res: ServerResponse): void {\n\t\tif (this._enableCors) {\n\t\t\tres.setHeader('Access-Control-Allow-Origin', this._corsOrigin);\n\t\t\tres.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n\t\t\tres.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n\t\t}\n\t}\n\n\tprotected validateHostHeader(req: IncomingMessage): boolean {\n\t\tconst rawHost = req.headers.host;\n\t\tif (!rawHost) {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst hostWithoutPort = rawHost.split(':')[0]!.trim().toLowerCase();\n\t\tif (!hostWithoutPort) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (this._allowedHosts.size === 0) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn this._allowedHosts.has(hostWithoutPort);\n\t}\n\n\tprivate _buildAllowedHosts(configuredHosts?: string[]): Set<string> {\n\t\tif (configuredHosts && configuredHosts.length > 0) {\n\t\t\treturn new Set(configuredHosts.map((host) => host.toLowerCase().trim()).filter(Boolean));\n\t\t}\n\n\t\tconst boundHost = this._host.toLowerCase();\n\t\tconst localHosts = ['localhost', '127.0.0.1', '::1'];\n\n\t\tif (localHosts.includes(boundHost)) {\n\t\t\treturn new Set(localHosts);\n\t\t}\n\n\t\tif (boundHost === '0.0.0.0' || boundHost === '::') {\n\t\t\treturn new Set(localHosts);\n\t\t}\n\n\t\treturn new Set([boundHost]);\n\t}\n\n\t/**\n\t * Log a message using the configured logger.\n\t *\n\t * @param level - Log level\n\t * @param message - Message to log\n\t * @param meta - Optional metadata\n\t */\n\tprotected log(\n\t\tlevel: 'info' | 'warn' | 'error',\n\t\tmessage: string,\n\t\tmeta?: Record<string, unknown>\n\t): void {\n\t\tif (level === 'info') {\n\t\t\tthis._logger.info(message, meta);\n\t\t} else if (level === 'warn') {\n\t\t\tthis._logger.warn(message, meta);\n\t\t} else {\n\t\t\tthis._logger.error(message, meta);\n\t\t}\n\t}\n\n\t/**\n\t * Check if transport is shutting down.\n\t * @returns true if in shutdown phase\n\t */\n\tprotected isShuttingDown(): boolean {\n\t\treturn this._isShuttingDown;\n\t}\n\n\t/**\n\t * Handle GET /health endpoint — liveness check.\n\t *\n\t * Builds a standard health response with optional liveness data from the health checker.\n\t * Transports can pass extra data (e.g. client counts, session info).\n\t *\n\t * @param res - The server response\n\t * @param extraData - Optional additional health metadata\n\t */\n\tprotected handleHealthEndpoint(res: ServerResponse, extraData?: Record<string, unknown>): void {\n\t\tconst healthData: Record<string, unknown> = { status: 'healthy', ...extraData };\n\t\tif (this._healthChecker) {\n\t\t\tconst liveness = this._healthChecker.checkLiveness();\n\t\t\thealthData.liveness = liveness;\n\t\t}\n\t\tres.writeHead(200, { 'Content-Type': 'application/json' });\n\t\tres.end(JSON.stringify(healthData));\n\t}\n\n\t/**\n\t * Handle GET /ready endpoint — readiness check.\n\t *\n\t * Delegates to the health checker if available, otherwise returns a default OK response.\n\t *\n\t * @param res - The server response\n\t */\n\tprotected async handleReadinessEndpoint(res: ServerResponse): Promise<void> {\n\t\tif (this._healthChecker) {\n\t\t\tconst readiness = await this._healthChecker.checkReadiness();\n\t\t\tconst statusCode = readiness.status === 'ok' ? 200 : 503;\n\t\t\tres.writeHead(statusCode, { 'Content-Type': 'application/json' });\n\t\t\tres.end(JSON.stringify(readiness));\n\t\t} else {\n\t\t\tres.writeHead(200, { 'Content-Type': 'application/json' });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({ status: 'ok', timestamp: new Date().toISOString(), components: {} })\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Handle GET /metrics endpoint — Prometheus metrics.\n\t *\n\t * Returns 404 if no metrics provider is configured.\n\t *\n\t * @param res - The server response\n\t * @param metricsProvider - Function that returns Prometheus-format metrics text\n\t */\n\tprotected handleMetricsEndpoint(res: ServerResponse, metricsProvider: (() => string) | null): void {\n\t\tif (!metricsProvider) {\n\t\t\tres.writeHead(404, { 'Content-Type': 'text/plain' });\n\t\t\tres.end('Not Found');\n\t\t\treturn;\n\t\t}\n\t\tres.writeHead(200, { 'Content-Type': 'text/plain; version=0.0.4; charset=utf-8' });\n\t\tres.end(metricsProvider());\n\t}\n\n\t/**\n\t * Connect to MCP server.\n\t */\n\tabstract connect(mcpServer: unknown): Promise<void>;\n\n\t/**\n\t * Stop transport server with graceful shutdown.\n\t *\n\t * This method should:\n\t * 1. Set shutdown flag to prevent new connections\n\t * 2. Wait for in-flight requests to complete (configurable timeout)\n\t * 3. Close server connections\n\t * 4. Release resources\n\t *\n\t * @param timeout - Maximum time to wait for requests to drain (default: 30 seconds)\n\t * @returns Promise that resolves when shutdown is complete\n\t */\n\tabstract stop(timeout?: number): Promise<void>;\n\n\t/**\n\t * Get number of clients connected.\n\t */\n\tabstract get clientCount(): number;\n\n}\n"],"names":["NoopLogger","_message","_meta","level","ALLOWED_QUERY_PARAMS","Set","RATE_LIMIT_REQUESTS","RATE_LIMIT_WINDOW_MS","BaseTransport","Map","options","undefined","host","sessionId","MAX_SESSION_ID_LENGTH","SESSION_ID_PATTERN","url","sanitized","key","value","ip","now","Date","record","clearInterval","setInterval","req","forwardedFor","remoteAddress","origin","escaped","regex","RegExp","res","rawHost","hostWithoutPort","configuredHosts","Boolean","boundHost","localHosts","message","meta","extraData","healthData","liveness","JSON","readiness","statusCode","metricsProvider"],"mappings":"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"}
1
+ {"version":3,"file":"transport/BaseTransport.js","sources":["../../src/transport/BaseTransport.ts"],"sourcesContent":["/**\n * Base transport implementation.\n *\n * This class provides shared functionality for all transport implementations,\n * including session validation, rate limiting, CORS handling, and IP extraction.\n *\n * @remarks\n * **Security Features:**\n * - Session ID validation (alphanumeric, max 64 chars)\n * - Query parameter sanitization (whitelist allowed keys)\n * - Rate limiting per IP (configurable, default 100 req/min)\n * - CORS origin validation\n *\n * **Rate Limiting:**\n * - Tracks requests per IP address within a time window\n * - Returns 429 Too Many Requests when limit exceeded\n * - Can be disabled via `enableRateLimit: false`\n */\n\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { URL } from 'node:url';\nimport type { HealthChecker } from '../health/HealthChecker.js';\nimport type { Logger, LogLevel } from '../logger/StructuredLogger.js';\nimport { SESSION_ID_PATTERN, MAX_SESSION_ID_LENGTH } from '../core/ids.js';\nimport type { McpServer } from 'tmcp';\n\n/**\n * No-op logger that does nothing. Used when no logger is provided.\n */\nclass NoopLogger implements Logger {\n\tprivate _level: LogLevel = 'info';\n\n\tinfo(_message: string, _meta?: Record<string, unknown>): void {}\n\twarn(_message: string, _meta?: Record<string, unknown>): void {}\n\terror(_message: string, _meta?: Record<string, unknown>): void {}\n\tdebug(_message: string, _meta?: Record<string, unknown>): void {}\n\tsetLevel(level: LogLevel): void {\n\t\tthis._level = level;\n\t}\n\tgetLevel(): LogLevel {\n\t\treturn this._level;\n\t}\n}\n\n/**\n * Allowed query parameter names (whitelist for security).\n */\nconst ALLOWED_QUERY_PARAMS = new Set(['session', 'sessionId', 'client', 'clientId']);\n\n\n\n/**\n * Rate limit settings (requests per minute per IP).\n */\nconst RATE_LIMIT_REQUESTS = 100;\nconst RATE_LIMIT_WINDOW_MS = 60 * 1000; // 1 minute\n\nexport interface TransportOptions {\n\tport?: number;\n\thost?: string;\n\tallowedHosts?: string[];\n\tcorsOrigin?: string;\n\tenableCors?: boolean;\n\tenableRateLimit?: boolean;\n\tmaxRequestsPerMinute?: number;\n\tlogger?: Logger;\n\thealthChecker?: HealthChecker;\n}\n\nexport abstract class BaseTransport {\n\tprotected _port: number;\n\tprotected _host: string;\n\tprotected _corsOrigin: string;\n\tprotected _enableCors: boolean;\n\tprotected _rateLimitEnabled: boolean;\n\tprotected _maxRequestsPerMinute: number;\n\tprotected _allowedHosts: Set<string>;\n\tprotected _rateLimitMap: Map<string, { count: number; resetTime: number }> = new Map();\n\tprotected _rateLimitCleanupIntervalId: NodeJS.Timeout | null = null;\n\tprotected _wasHostExplicitlySet: boolean;\n\t/** Shutdown state for graceful shutdown. */\n\tprotected _isShuttingDown: boolean = false;\n\tprivate _logger: Logger | NoopLogger;\n\tprotected _healthChecker: HealthChecker | null;\n\n\tconstructor(options: TransportOptions = {}) {\n\t\tthis._port = options.port ?? 9108;\n\t\tthis._host = options.host ?? '127.0.0.1';\n\t\tthis._wasHostExplicitlySet = options.host !== undefined;\n\t\tthis._corsOrigin = options.corsOrigin ?? '*';\n\t\tthis._enableCors = options.enableCors ?? true;\n\t\tthis._rateLimitEnabled = options.enableRateLimit ?? true;\n\t\tthis._maxRequestsPerMinute = options.maxRequestsPerMinute ?? RATE_LIMIT_REQUESTS;\n\t\tthis._allowedHosts = this._buildAllowedHosts(options.allowedHosts);\n\t\tthis._isShuttingDown = false;\n\t\tthis._logger = options.logger ?? new NoopLogger();\n\t\tthis._healthChecker = options.healthChecker ?? null;\n\n\t\tif (this._rateLimitEnabled) {\n\t\t\tthis._startRateLimitCleanup();\n\t\t}\n\t}\n\n\t/**\n\t * Get the server URL with localhost substitution for default host.\n\t */\n\tget serverUrl(): string {\n\t\tconst host =\n\t\t\t!this._wasHostExplicitlySet && this._host === '127.0.0.1' ? 'localhost' : this._host;\n\t\treturn `http://${host}:${this._port}`;\n\t}\n\n\t/**\n\t * Validate session ID format.\n\t *\n\t * @param sessionId - The session ID to validate\n\t * @returns true if valid, false otherwise\n\t */\n\tprotected validateSessionId(sessionId: string): boolean {\n\t\tif (sessionId.length > MAX_SESSION_ID_LENGTH) {\n\t\t\treturn false;\n\t\t}\n\t\treturn SESSION_ID_PATTERN.test(sessionId);\n\t}\n\n\t/**\n\t * Sanitize query parameters by removing any not in whitelist.\n\t *\n\t * @param url - The URL object containing query parameters\n\t * @returns A sanitized record of allowed query parameters\n\t */\n\tprotected sanitizeQueryParams(url: URL): Record<string, string> {\n\t\tconst sanitized: Record<string, string> = {};\n\n\t\tfor (const [key, value] of url.searchParams.entries()) {\n\t\t\tif (ALLOWED_QUERY_PARAMS.has(key)) {\n\t\t\t\tsanitized[key] = value;\n\t\t\t}\n\t\t}\n\n\t\treturn sanitized;\n\t}\n\n\t/**\n\t * Check rate limit for a given IP address.\n\t *\n\t * @param ip - The IP address to check\n\t * @returns true if rate limit exceeded, false otherwise\n\t */\n\tprotected checkRateLimit(ip: string): boolean {\n\t\tif (!this._rateLimitEnabled) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst now = Date.now();\n\t\tthis._cleanupExpiredRateLimitEntries(now);\n\t\tconst record = this._rateLimitMap.get(ip);\n\n\t\tif (!record || now > record.resetTime) {\n\t\t\tthis._rateLimitMap.set(ip, {\n\t\t\t\tcount: 1,\n\t\t\t\tresetTime: now + RATE_LIMIT_WINDOW_MS,\n\t\t\t});\n\t\t\treturn false;\n\t\t}\n\n\t\tif (record.count >= this._maxRequestsPerMinute) {\n\t\t\treturn true; // Rate limit exceeded\n\t\t}\n\n\t\trecord.count++;\n\t\treturn false;\n\t}\n\n\tprotected _cleanupExpiredRateLimitEntries(now = Date.now()): void {\n\t\tfor (const [ip, record] of this._rateLimitMap.entries()) {\n\t\t\tif (record.resetTime <= now) {\n\t\t\t\tthis._rateLimitMap.delete(ip);\n\t\t\t}\n\t\t}\n\t}\n\n\tprotected _startRateLimitCleanup(): void {\n\t\tif (this._rateLimitCleanupIntervalId !== null) {\n\t\t\tclearInterval(this._rateLimitCleanupIntervalId);\n\t\t}\n\n\t\tthis._rateLimitCleanupIntervalId = setInterval(() => {\n\t\t\tthis._cleanupExpiredRateLimitEntries();\n\t\t}, RATE_LIMIT_WINDOW_MS);\n\t}\n\n\tprotected _stopRateLimitCleanup(): void {\n\t\tif (this._rateLimitCleanupIntervalId !== null) {\n\t\t\tclearInterval(this._rateLimitCleanupIntervalId);\n\t\t\tthis._rateLimitCleanupIntervalId = null;\n\t\t}\n\t}\n\n\t/**\n\t * Get client IP address from request.\n\t *\n\t * @param req - The incoming request\n\t * @returns The client IP address\n\t */\n\tprotected getClientIp(req: IncomingMessage): string {\n\t\tconst forwardedFor = req.headers['x-forwarded-for'];\n\t\tif (forwardedFor && typeof forwardedFor === 'string') {\n\t\t\treturn forwardedFor.split(',')[0]!.trim();\n\t\t}\n\t\tconst remoteAddress = req.socket.remoteAddress;\n\t\treturn remoteAddress || 'unknown';\n\t}\n\n\t/**\n\t * Validate CORS origin from request headers.\n\t *\n\t * @param req - The incoming request\n\t * @returns true if origin is valid, false otherwise\n\t */\n\tprotected validateCorsOrigin(req: IncomingMessage): boolean {\n\t\tif (this._corsOrigin === '*') {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst origin = req.headers.origin;\n\t\tif (!origin) {\n\t\t\treturn true; // No origin header is acceptable\n\t\t}\n\n\t\t// Exact match\n\t\tif (this._corsOrigin === origin) {\n\t\t\treturn true;\n\t\t}\n\n\t\t// Check if configured origin is a wildcard pattern\n\t\tif (this._corsOrigin.includes('*')) {\n\t\t\t// Escape all regex metacharacters EXCEPT *,\n\t\t\t// then replace * with a hostname-safe pattern (alphanumeric, hyphens, dots)\n\t\t\tconst escaped = this._corsOrigin\n\t\t\t\t.replace(/[.+?^${}()|[\\]\\\\]/g, '\\\\$&') // escape metacharacters (not *)\n\t\t\t\t.replace(/\\*/g, '[a-zA-Z0-9.-]*'); // * matches valid hostname chars only\n\t\t\tconst regex = new RegExp(`^${escaped}$`);\n\t\t\treturn regex.test(origin);\n\t\t}\n\n\t\treturn false;\n\t}\n\n\t/**\n\t * Set CORS headers on response.\n\t *\n\t * @param res - The server response\n\t */\n\tprotected setCorsHeaders(res: ServerResponse): void {\n\t\tif (this._enableCors) {\n\t\t\tres.setHeader('Access-Control-Allow-Origin', this._corsOrigin);\n\t\t\tres.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');\n\t\t\tres.setHeader('Access-Control-Allow-Headers', 'Content-Type');\n\t\t}\n\t}\n\n\tprotected validateHostHeader(req: IncomingMessage): boolean {\n\t\tconst rawHost = req.headers.host;\n\t\tif (!rawHost) {\n\t\t\treturn true;\n\t\t}\n\n\t\tconst hostWithoutPort = rawHost.split(':')[0]!.trim().toLowerCase();\n\t\tif (!hostWithoutPort) {\n\t\t\treturn false;\n\t\t}\n\n\t\tif (this._allowedHosts.size === 0) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn this._allowedHosts.has(hostWithoutPort);\n\t}\n\n\tprivate _buildAllowedHosts(configuredHosts?: string[]): Set<string> {\n\t\tif (configuredHosts && configuredHosts.length > 0) {\n\t\t\treturn new Set(configuredHosts.map((host) => host.toLowerCase().trim()).filter(Boolean));\n\t\t}\n\n\t\tconst boundHost = this._host.toLowerCase();\n\t\tconst localHosts = ['localhost', '127.0.0.1', '::1'];\n\n\t\tif (localHosts.includes(boundHost)) {\n\t\t\treturn new Set(localHosts);\n\t\t}\n\n\t\tif (boundHost === '0.0.0.0' || boundHost === '::') {\n\t\t\treturn new Set(localHosts);\n\t\t}\n\n\t\treturn new Set([boundHost]);\n\t}\n\n\t/**\n\t * Log a message using the configured logger.\n\t *\n\t * @param level - Log level\n\t * @param message - Message to log\n\t * @param meta - Optional metadata\n\t */\n\tprotected log(\n\t\tlevel: 'info' | 'warn' | 'error',\n\t\tmessage: string,\n\t\tmeta?: Record<string, unknown>\n\t): void {\n\t\tif (level === 'info') {\n\t\t\tthis._logger.info(message, meta);\n\t\t} else if (level === 'warn') {\n\t\t\tthis._logger.warn(message, meta);\n\t\t} else {\n\t\t\tthis._logger.error(message, meta);\n\t\t}\n\t}\n\n\t/**\n\t * Check if transport is shutting down.\n\t * @returns true if in shutdown phase\n\t */\n\tpublic get isShuttingDown(): boolean {\n\t\treturn this._isShuttingDown;\n\t}\n\n\t/**\n\t * Handle GET /health endpoint — liveness check.\n\t *\n\t * Builds a standard health response with optional liveness data from the health checker.\n\t * Transports can pass extra data (e.g. client counts, session info).\n\t *\n\t * @param res - The server response\n\t * @param extraData - Optional additional health metadata\n\t */\n\tprotected handleHealthEndpoint(res: ServerResponse, extraData?: Record<string, unknown>): void {\n\t\tconst healthData: Record<string, unknown> = { status: 'healthy', ...extraData };\n\t\tif (this._healthChecker) {\n\t\t\tconst liveness = this._healthChecker.checkLiveness();\n\t\t\thealthData.liveness = liveness;\n\t\t}\n\t\tres.writeHead(200, { 'Content-Type': 'application/json' });\n\t\tres.end(JSON.stringify(healthData));\n\t}\n\n\t/**\n\t * Handle GET /ready endpoint — readiness check.\n\t *\n\t * Delegates to the health checker if available, otherwise returns a default OK response.\n\t *\n\t * @param res - The server response\n\t */\n\tprotected async handleReadinessEndpoint(res: ServerResponse): Promise<void> {\n\t\tif (this._healthChecker) {\n\t\t\tconst readiness = await this._healthChecker.checkReadiness();\n\t\t\tconst statusCode = readiness.status === 'ok' ? 200 : 503;\n\t\t\tres.writeHead(statusCode, { 'Content-Type': 'application/json' });\n\t\t\tres.end(JSON.stringify(readiness));\n\t\t} else {\n\t\t\tres.writeHead(200, { 'Content-Type': 'application/json' });\n\t\t\tres.end(\n\t\t\t\tJSON.stringify({ status: 'ok', timestamp: new Date().toISOString(), components: {} })\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Handle GET /metrics endpoint — Prometheus metrics.\n\t *\n\t * Returns 404 if no metrics provider is configured.\n\t *\n\t * @param res - The server response\n\t * @param metricsProvider - Function that returns Prometheus-format metrics text\n\t */\n\tprotected handleMetricsEndpoint(res: ServerResponse, metricsProvider: (() => string) | null): void {\n\t\tif (!metricsProvider) {\n\t\t\tres.writeHead(404, { 'Content-Type': 'text/plain' });\n\t\t\tres.end('Not Found');\n\t\t\treturn;\n\t\t}\n\t\tres.writeHead(200, { 'Content-Type': 'text/plain; version=0.0.4; charset=utf-8' });\n\t\tres.end(metricsProvider());\n\t}\n\n\t/**\n\t * Connect to MCP server.\n\t */\n\tabstract connect(mcpServer: McpServer): Promise<void>;\n\n\t/**\n\t * Stop transport server with graceful shutdown.\n\t *\n\t * This method should:\n\t * 1. Set shutdown flag to prevent new connections\n\t * 2. Wait for in-flight requests to complete (configurable timeout)\n\t * 3. Close server connections\n\t * 4. Release resources\n\t *\n\t * @param timeout - Maximum time to wait for requests to drain (default: 30 seconds)\n\t * @returns Promise that resolves when shutdown is complete\n\t */\n\tabstract stop(timeout?: number): Promise<void>;\n\n\t/**\n\t * Get number of clients connected.\n\t */\n\tabstract get clientCount(): number;\n\n}\n"],"names":["NoopLogger","_message","_meta","level","ALLOWED_QUERY_PARAMS","Set","RATE_LIMIT_REQUESTS","RATE_LIMIT_WINDOW_MS","BaseTransport","Map","options","undefined","host","sessionId","MAX_SESSION_ID_LENGTH","SESSION_ID_PATTERN","url","sanitized","key","value","ip","now","Date","record","clearInterval","setInterval","req","forwardedFor","remoteAddress","origin","escaped","regex","RegExp","res","rawHost","hostWithoutPort","configuredHosts","Boolean","boundHost","localHosts","message","meta","extraData","healthData","liveness","JSON","readiness","statusCode","metricsProvider"],"mappings":"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"}
@@ -14,8 +14,9 @@
14
14
  * ```
15
15
  */
16
16
  import type { McpServer } from 'tmcp';
17
- import type { IMetrics } from '../contracts/index.js';
17
+ import type { IMetrics } from '../contracts/interfaces.js';
18
18
  import { BaseTransport, type TransportOptions } from './BaseTransport.js';
19
+ import type { ITransport, TransportKind } from '../contracts/transport.js';
19
20
  export interface HttpTransportOptions extends TransportOptions {
20
21
  /**
21
22
  * Path for messages endpoint
@@ -71,7 +72,8 @@ export interface HttpTransportOptions extends TransportOptions {
71
72
  * - 500: Internal Server Error
72
73
  * - 503: Server Not Ready
73
74
  */
74
- export declare class HttpTransport extends BaseTransport {
75
+ export declare class HttpTransport extends BaseTransport implements ITransport {
76
+ get kind(): TransportKind;
75
77
  private _server;
76
78
  private _mcpServer;
77
79
  private _requestTimeout;
@@ -1 +1 @@
1
- {"version":3,"file":"HttpTransport.d.ts","sourceRoot":"","sources":["../../src/transport/HttpTransport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEtC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGtD,OAAO,EAAE,aAAa,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAQ1E,MAAM,WAAW,oBAAqB,SAAQ,gBAAgB;IAC7D;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,MAAM,CAAC;IAE/B;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAAa,aAAc,SAAQ,aAAa;IAC/C,OAAO,CAAC,OAAO,CAAkC;IACjD,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,qBAAqB,CAAU;IACvC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,eAAe,CAAa;IACpC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,QAAQ,CAAC,CAAW;IAC5B,OAAO,CAAC,gBAAgB,CAAwB;gBAEpC,OAAO,GAAE,oBAAyB;IAY9C;;OAEG;IACH,IAAI,WAAW,IAAI,MAAM,CAExB;IAED;;OAEG;IACG,OAAO,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAUlD;;OAEG;IACH,OAAO,CAAC,WAAW;IASnB;;;;;OAKG;YACW,cAAc;IAiE5B;;;;;OAKG;YACW,kBAAkB;IAqFhC;;OAEG;IACH,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAW3B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,oBAAyB,GAAG,aAAa,CAErF"}
1
+ {"version":3,"file":"HttpTransport.d.ts","sourceRoot":"","sources":["../../src/transport/HttpTransport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEtC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAG3D,OAAO,EAAE,aAAa,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAS3E,MAAM,WAAW,oBAAqB,SAAQ,gBAAgB;IAC7D;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,MAAM,CAAC;IAE/B;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,qBAAa,aAAc,SAAQ,aAAc,YAAW,UAAU;IACrE,IAAI,IAAI,IAAI,aAAa,CAAmB;IAC5C,OAAO,CAAC,OAAO,CAAkC;IACjD,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,qBAAqB,CAAU;IACvC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,eAAe,CAAa;IACpC,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,QAAQ,CAAC,CAAW;IAC5B,OAAO,CAAC,gBAAgB,CAAwB;gBAEpC,OAAO,GAAE,oBAAyB;IAY9C;;OAEG;IACH,IAAI,WAAW,IAAI,MAAM,CAExB;IAED;;OAEG;IACG,OAAO,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAUlD;;OAEG;IACH,OAAO,CAAC,WAAW;IASnB;;;;;OAKG;YACW,cAAc;IAiE5B;;;;;OAKG;YACW,kBAAkB;IAyFhC;;OAEG;IACH,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;CAW3B;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,GAAE,oBAAyB,GAAG,aAAa,CAErF"}
@@ -1,10 +1,15 @@
1
1
  import { createServer } from "node:http";
2
+ import { randomUUID } from "node:crypto";
2
3
  import { safeParse } from "valibot";
3
4
  import { getErrorMessage } from "../errors.js";
4
5
  import { JsonRpcRequestSchema } from "../schema.js";
5
6
  import { BaseTransport } from "./BaseTransport.js";
6
7
  import { readRequestBody, sendCorsPreflight, sendJsonRpcError, sendJsonRpcResponse } from "./HttpHelpers.js";
8
+ import { runWithContext } from "../context/RequestContext.js";
7
9
  class HttpTransport extends BaseTransport {
10
+ get kind() {
11
+ return 'http';
12
+ }
8
13
  _server;
9
14
  _mcpServer = null;
10
15
  _requestTimeout;
@@ -66,7 +71,7 @@ class HttpTransport extends BaseTransport {
66
71
  sendJsonRpcError(res, 403, -32000, 'Forbidden - invalid host header');
67
72
  return;
68
73
  }
69
- if (this.isShuttingDown()) {
74
+ if (this.isShuttingDown) {
70
75
  this._trackError('shutting_down');
71
76
  sendJsonRpcError(res, 503, -32603, 'Server is shutting down');
72
77
  return;
@@ -137,9 +142,13 @@ class HttpTransport extends BaseTransport {
137
142
  sendJsonRpcError(res, 200, -32603, 'Server not ready', jsonRpcRequest?.id ?? null);
138
143
  return;
139
144
  }
140
- const response = await this._mcpServer.receive(jsonRpcRequest, {
141
- sessionInfo: {}
142
- });
145
+ const owner = randomUUID();
146
+ const response = await runWithContext({
147
+ requestId: randomUUID(),
148
+ owner
149
+ }, ()=>this._mcpServer.receive(jsonRpcRequest, {
150
+ sessionInfo: {}
151
+ }));
143
152
  clearTimeout(timeout);
144
153
  this._activeRequests--;
145
154
  if (response) sendJsonRpcResponse(res, response);
@@ -1 +1 @@
1
- {"version":3,"file":"transport/HttpTransport.js","sources":["../../src/transport/HttpTransport.ts"],"sourcesContent":["/**\n * HTTP Transport implementation.\n *\n * This transport provides a stateless, REST-like API interface for MCP tool invocations\n * using standard HTTP request-response patterns.\n *\n * @example\n * ```typescript\n * const transport = new HttpTransport({\n * port: 3000,\n * host: 'localhost'\n * });\n * await transport.connect(server);\n * ```\n */\n\nimport { createServer, IncomingMessage, ServerResponse } from 'node:http';\nimport type { McpServer } from 'tmcp';\nimport { safeParse } from 'valibot';\nimport type { IMetrics } from '../contracts/index.js';\nimport { getErrorMessage } from '../errors.js';\nimport { JsonRpcRequestSchema } from '../schema.js';\nimport { BaseTransport, type TransportOptions } from './BaseTransport.js';\nimport {\n\treadRequestBody,\n\tsendCorsPreflight,\n\tsendJsonRpcError,\n\tsendJsonRpcResponse,\n} from './HttpHelpers.js';\n\nexport interface HttpTransportOptions extends TransportOptions {\n\t/**\n\t * Path for messages endpoint\n\t * @default '/messages'\n\t */\n\tpath?: string;\n\tmetrics?: IMetrics;\n\tmetricsProvider?: () => string;\n\n\t/**\n\t * Enable request body size limit\n\t * @default true\n\t */\n\tenableBodySizeLimit?: boolean;\n\n\t/**\n\t * Maximum request body size in bytes\n\t * @default 10485760 (10MB)\n\t */\n\tmaxBodySize?: number;\n\n\t/**\n\t * Request timeout in milliseconds\n\t * @default 30000 (30 seconds)\n\t */\n\trequestTimeout?: number;\n}\n\n/**\n * HTTP Transport for MCP server.\n *\n * This transport provides a stateless, REST-like API interface for MCP tool invocations\n * using standard HTTP request-response patterns.\n *\n * @remarks\n * **Security Features:**\n * - Session ID validation (alphanumeric, max 64 chars)\n * - Query parameter sanitization (whitelist allowed keys)\n * - Rate limiting per IP (configurable, default 100 req/min)\n * - CORS origin validation\n * - Request body size limits (configurable, default 10MB)\n * - Request timeout (configurable, default 30s)\n *\n * **Rate Limiting:**\n * - Tracks requests per IP address within a time window\n * - Returns 429 Too Many Requests when limit exceeded\n * - Can be disabled via `enableRateLimit: false`\n *\n * **HTTP Status Code Mapping:**\n * - 200: Success (JSON-RPC response)\n * - 204: CORS Preflight (empty body)\n * - 400: Bad Request\n * - 403: Forbidden (invalid CORS)\n * - 404: Not Found\n * - 413: Payload Too Large\n * - 429: Too Many Requests\n * - 500: Internal Server Error\n * - 503: Server Not Ready\n */\nexport class HttpTransport extends BaseTransport {\n\tprivate _server: ReturnType<typeof createServer>;\n\tprivate _mcpServer: McpServer | null = null;\n\tprivate _requestTimeout: number;\n\tprivate _bodySizeLimitEnabled: boolean;\n\tprivate _maxBodySize: number;\n\tprivate _requestCount: number = 0;\n\tprivate _activeRequests: number = 0;\n\tprivate _path: string;\n\tprivate _metrics?: IMetrics;\n\tprivate _metricsProvider: (() => string) | null;\n\n\tconstructor(options: HttpTransportOptions = {}) {\n\t\tsuper(options);\n\n\t\tthis._requestTimeout = options.requestTimeout ?? 30000;\n\t\tthis._bodySizeLimitEnabled = options.enableBodySizeLimit ?? true;\n\t\tthis._maxBodySize = options.maxBodySize ?? 10 * 1024 * 1024;\n\t\tthis._path = options.path ?? '/messages';\n\t\tthis._metrics = options.metrics;\n\t\tthis._metricsProvider = options.metricsProvider ?? null;\n\t\tthis._server = createServer((req, res) => this._handleRequest(req, res));\n\t}\n\n\t/**\n\t * Get number of active HTTP connections.\n\t */\n\tget clientCount(): number {\n\t\treturn this._activeRequests;\n\t}\n\n\t/**\n\t * Connects MCP server to this transport.\n\t */\n\tasync connect(mcpServer: McpServer): Promise<void> {\n\t\tthis._mcpServer = mcpServer;\n\t\treturn new Promise((resolve) => {\n\t\t\tthis._server.listen(this._port, this._host, () => {\n\t\t\t\tthis.log('info', `HTTP transport listening on http://${this._host}:${this._port}`);\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Track an error in metrics.\n\t */\n\tprivate _trackError(errorType: string): void {\n\t\tthis._metrics?.counter(\n\t\t\t'http_request_errors_total',\n\t\t\t1,\n\t\t\t{ transport: 'http', error_type: errorType },\n\t\t\t'Total HTTP request errors'\n\t\t);\n\t}\n\n\t/**\n\t * Route and handle incoming HTTP requests.\n\t *\n\t * Performs security checks (host, shutdown, rate limit, CORS) then\n\t * dispatches to the appropriate endpoint handler.\n\t */\n\tprivate async _handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\tconst startTime = Date.now();\n\t\tconst requestPath = req.url || '/';\n\t\tconst requestMethod = req.method || 'GET';\n\t\tthis._metrics?.counter('http_requests_total', 1, {}, 'Total HTTP transport requests');\n\t\tthis._metrics?.counter(\n\t\t\t'http_transport_requests_total',\n\t\t\t1,\n\t\t\t{ transport: 'http', method: requestMethod, path: requestPath },\n\t\t\t'Total HTTP requests by transport'\n\t\t);\n\t\tres.once('finish', () => {\n\t\t\tconst durationSeconds = (Date.now() - startTime) / 1000;\n\t\t\tthis._metrics?.histogram('http_request_duration_seconds', durationSeconds, {});\n\t\t\tthis._metrics?.histogram('http_transport_request_duration_seconds', durationSeconds, {\n\t\t\t\ttransport: 'http',\n\t\t\t\tpath: requestPath,\n\t\t\t});\n\t\t});\n\n\t\t// Security middleware chain\n\t\tif (!this.validateHostHeader(req)) {\n\t\t\tthis._trackError('forbidden');\n\t\t\tsendJsonRpcError(res, 403, -32000, 'Forbidden - invalid host header');\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.isShuttingDown()) {\n\t\t\tthis._trackError('shutting_down');\n\t\t\tsendJsonRpcError(res, 503, -32603, 'Server is shutting down');\n\t\t\treturn;\n\t\t}\n\n\t\tconst clientIp = this.getClientIp(req);\n\t\tif (this.checkRateLimit(clientIp)) {\n\t\t\tthis._trackError('rate_limit');\n\t\t\tres.setHeader('Retry-After', '60');\n\t\t\tsendJsonRpcError(res, 429, -32000, 'Too many requests');\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.validateCorsOrigin(req)) {\n\t\t\tthis._trackError('forbidden');\n\t\t\tsendJsonRpcError(res, 403, -32000, 'Forbidden - invalid origin');\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setCorsHeaders(res);\n\n\t\t// Static endpoints\n\t\tif (req.method === 'GET' && req.url === '/metrics')\n\t\t\treturn this.handleMetricsEndpoint(res, this._metricsProvider);\n\t\tif (req.method === 'OPTIONS') return sendCorsPreflight(res);\n\t\tif (req.method === 'GET' && req.url === '/health')\n\t\t\treturn this.handleHealthEndpoint(res, { requests: this._requestCount });\n\t\tif (req.method === 'GET' && req.url === '/ready') return this.handleReadinessEndpoint(res);\n\n\t\t// MCP endpoint\n\t\tif (req.method === 'POST' && req.url === this._path) return this._handlePostRequest(req, res);\n\n\t\t// 404\n\t\tthis._trackError('not_found');\n\t\tsendJsonRpcError(res, 404, -32601, 'Not Found');\n\t}\n\n\t/**\n\t * Handle POST to the MCP messages endpoint.\n\t *\n\t * Reads the request body, validates JSON-RPC format, and delegates\n\t * processing to the MCP server.\n\t */\n\tprivate async _handlePostRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\tthis._requestCount++;\n\t\tthis._activeRequests++;\n\n\t\tconst timeout = setTimeout(() => {\n\t\t\tthis._activeRequests--;\n\t\t\tthis._trackError('timeout');\n\t\t\tsendJsonRpcError(res, 500, -32603, 'Request timeout');\n\t\t}, this._requestTimeout);\n\n\t\ttry {\n\t\t\tconst maxBodySize = this._bodySizeLimitEnabled ? this._maxBodySize : 0;\n\t\t\tconst body = await readRequestBody(req, maxBodySize);\n\n\t\t\tif (body === null) {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tthis._activeRequests--;\n\t\t\t\tthis._trackError('payload_too_large');\n\t\t\t\tsendJsonRpcError(res, 413, -32000, 'Request body too large');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet jsonRpcRequest;\n\t\t\ttry {\n\t\t\t\tjsonRpcRequest = JSON.parse(body);\n\t\t\t} catch {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tthis._activeRequests--;\n\t\t\t\tthis._trackError('parse_error');\n\t\t\t\tsendJsonRpcError(res, 200, -32700, 'Parse error');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst parseResult = safeParse(JsonRpcRequestSchema, jsonRpcRequest);\n\t\t\tif (!parseResult.success) {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tthis._activeRequests--;\n\t\t\t\tthis._trackError('validation');\n\t\t\t\tsendJsonRpcError(\n\t\t\t\t\tres,\n\t\t\t\t\t200,\n\t\t\t\t\t-32600,\n\t\t\t\t\t'Invalid Request',\n\t\t\t\t\tjsonRpcRequest?.id ?? null,\n\t\t\t\t\tparseResult.issues\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (!this._mcpServer) {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tthis._activeRequests--;\n\t\t\t\tthis._trackError('server_not_ready');\n\t\t\t\tsendJsonRpcError(res, 200, -32603, 'Server not ready', jsonRpcRequest?.id ?? null);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst response = await this._mcpServer.receive(jsonRpcRequest, {\n\t\t\t\tsessionInfo: {},\n\t\t\t});\n\n\t\t\tclearTimeout(timeout);\n\t\t\tthis._activeRequests--;\n\n\t\t\tif (response) {\n\t\t\t\tsendJsonRpcResponse(res, response);\n\t\t\t} else {\n\t\t\t\tres.writeHead(204);\n\t\t\t\tres.end();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tclearTimeout(timeout);\n\t\t\tthis._activeRequests--;\n\t\t\tthis._trackError('internal_error');\n\t\t\tsendJsonRpcError(\n\t\t\t\tres,\n\t\t\t\t200,\n\t\t\t\t-32603,\n\t\t\t\t'Internal error',\n\t\t\t\tnull,\n\t\t\t\tgetErrorMessage(error)\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Returns number of requests handled.\n\t */\n\tget requestCount(): number {\n\t\treturn this._requestCount;\n\t}\n\n\t/**\n\t * Stops transport server.\n\t */\n\tasync stop(): Promise<void> {\n\t\tthis._isShuttingDown = true;\n\t\tthis._stopRateLimitCleanup();\n\n\t\treturn new Promise((resolve) => {\n\t\t\tthis._server.close(() => {\n\t\t\t\tthis.log('info', 'HTTP transport stopped');\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\t}\n}\n\n/**\n * Create an HTTP transport with given options.\n *\n * @param options - Transport configuration\n * @returns A configured HTTP transport\n *\n * @example\n * ```typescript\n * const transport = new HttpTransport({ port: 3000 });\n * await transport.connect(server);\n * ```\n */\nexport function createHttpTransport(options: HttpTransportOptions = {}): HttpTransport {\n\treturn new HttpTransport(options);\n}\n"],"names":["HttpTransport","BaseTransport","options","createServer","req","res","mcpServer","Promise","resolve","errorType","startTime","Date","requestPath","requestMethod","durationSeconds","sendJsonRpcError","clientIp","sendCorsPreflight","timeout","setTimeout","maxBodySize","body","readRequestBody","clearTimeout","jsonRpcRequest","JSON","parseResult","safeParse","JsonRpcRequestSchema","response","sendJsonRpcResponse","error","getErrorMessage","createHttpTransport"],"mappings":"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"}
1
+ {"version":3,"file":"transport/HttpTransport.js","sources":["../../src/transport/HttpTransport.ts"],"sourcesContent":["/**\n * HTTP Transport implementation.\n *\n * This transport provides a stateless, REST-like API interface for MCP tool invocations\n * using standard HTTP request-response patterns.\n *\n * @example\n * ```typescript\n * const transport = new HttpTransport({\n * port: 3000,\n * host: 'localhost'\n * });\n * await transport.connect(server);\n * ```\n */\n\nimport { createServer, IncomingMessage, ServerResponse } from 'node:http';\nimport { randomUUID } from 'node:crypto';\nimport type { McpServer } from 'tmcp';\nimport { safeParse } from 'valibot';\nimport type { IMetrics } from '../contracts/interfaces.js';\nimport { getErrorMessage } from '../errors.js';\nimport { JsonRpcRequestSchema } from '../schema.js';\nimport { BaseTransport, type TransportOptions } from './BaseTransport.js';\nimport type { ITransport, TransportKind } from '../contracts/transport.js';\nimport {\n\treadRequestBody,\n\tsendCorsPreflight,\n\tsendJsonRpcError,\n\tsendJsonRpcResponse,\n} from './HttpHelpers.js';\nimport { runWithContext } from '../context/RequestContext.js';\n\nexport interface HttpTransportOptions extends TransportOptions {\n\t/**\n\t * Path for messages endpoint\n\t * @default '/messages'\n\t */\n\tpath?: string;\n\tmetrics?: IMetrics;\n\tmetricsProvider?: () => string;\n\n\t/**\n\t * Enable request body size limit\n\t * @default true\n\t */\n\tenableBodySizeLimit?: boolean;\n\n\t/**\n\t * Maximum request body size in bytes\n\t * @default 10485760 (10MB)\n\t */\n\tmaxBodySize?: number;\n\n\t/**\n\t * Request timeout in milliseconds\n\t * @default 30000 (30 seconds)\n\t */\n\trequestTimeout?: number;\n}\n\n/**\n * HTTP Transport for MCP server.\n *\n * This transport provides a stateless, REST-like API interface for MCP tool invocations\n * using standard HTTP request-response patterns.\n *\n * @remarks\n * **Security Features:**\n * - Session ID validation (alphanumeric, max 64 chars)\n * - Query parameter sanitization (whitelist allowed keys)\n * - Rate limiting per IP (configurable, default 100 req/min)\n * - CORS origin validation\n * - Request body size limits (configurable, default 10MB)\n * - Request timeout (configurable, default 30s)\n *\n * **Rate Limiting:**\n * - Tracks requests per IP address within a time window\n * - Returns 429 Too Many Requests when limit exceeded\n * - Can be disabled via `enableRateLimit: false`\n *\n * **HTTP Status Code Mapping:**\n * - 200: Success (JSON-RPC response)\n * - 204: CORS Preflight (empty body)\n * - 400: Bad Request\n * - 403: Forbidden (invalid CORS)\n * - 404: Not Found\n * - 413: Payload Too Large\n * - 429: Too Many Requests\n * - 500: Internal Server Error\n * - 503: Server Not Ready\n */\nexport class HttpTransport extends BaseTransport implements ITransport {\n\tget kind(): TransportKind { return 'http'; }\n\tprivate _server: ReturnType<typeof createServer>;\n\tprivate _mcpServer: McpServer | null = null;\n\tprivate _requestTimeout: number;\n\tprivate _bodySizeLimitEnabled: boolean;\n\tprivate _maxBodySize: number;\n\tprivate _requestCount: number = 0;\n\tprivate _activeRequests: number = 0;\n\tprivate _path: string;\n\tprivate _metrics?: IMetrics;\n\tprivate _metricsProvider: (() => string) | null;\n\n\tconstructor(options: HttpTransportOptions = {}) {\n\t\tsuper(options);\n\n\t\tthis._requestTimeout = options.requestTimeout ?? 30000;\n\t\tthis._bodySizeLimitEnabled = options.enableBodySizeLimit ?? true;\n\t\tthis._maxBodySize = options.maxBodySize ?? 10 * 1024 * 1024;\n\t\tthis._path = options.path ?? '/messages';\n\t\tthis._metrics = options.metrics;\n\t\tthis._metricsProvider = options.metricsProvider ?? null;\n\t\tthis._server = createServer((req, res) => this._handleRequest(req, res));\n\t}\n\n\t/**\n\t * Get number of active HTTP connections.\n\t */\n\tget clientCount(): number {\n\t\treturn this._activeRequests;\n\t}\n\n\t/**\n\t * Connects MCP server to this transport.\n\t */\n\tasync connect(mcpServer: McpServer): Promise<void> {\n\t\tthis._mcpServer = mcpServer;\n\t\treturn new Promise((resolve) => {\n\t\t\tthis._server.listen(this._port, this._host, () => {\n\t\t\t\tthis.log('info', `HTTP transport listening on http://${this._host}:${this._port}`);\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * Track an error in metrics.\n\t */\n\tprivate _trackError(errorType: string): void {\n\t\tthis._metrics?.counter(\n\t\t\t'http_request_errors_total',\n\t\t\t1,\n\t\t\t{ transport: 'http', error_type: errorType },\n\t\t\t'Total HTTP request errors'\n\t\t);\n\t}\n\n\t/**\n\t * Route and handle incoming HTTP requests.\n\t *\n\t * Performs security checks (host, shutdown, rate limit, CORS) then\n\t * dispatches to the appropriate endpoint handler.\n\t */\n\tprivate async _handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\tconst startTime = Date.now();\n\t\tconst requestPath = req.url || '/';\n\t\tconst requestMethod = req.method || 'GET';\n\t\tthis._metrics?.counter('http_requests_total', 1, {}, 'Total HTTP transport requests');\n\t\tthis._metrics?.counter(\n\t\t\t'http_transport_requests_total',\n\t\t\t1,\n\t\t\t{ transport: 'http', method: requestMethod, path: requestPath },\n\t\t\t'Total HTTP requests by transport'\n\t\t);\n\t\tres.once('finish', () => {\n\t\t\tconst durationSeconds = (Date.now() - startTime) / 1000;\n\t\t\tthis._metrics?.histogram('http_request_duration_seconds', durationSeconds, {});\n\t\t\tthis._metrics?.histogram('http_transport_request_duration_seconds', durationSeconds, {\n\t\t\t\ttransport: 'http',\n\t\t\t\tpath: requestPath,\n\t\t\t});\n\t\t});\n\n\t\t// Security middleware chain\n\t\tif (!this.validateHostHeader(req)) {\n\t\t\tthis._trackError('forbidden');\n\t\t\tsendJsonRpcError(res, 403, -32000, 'Forbidden - invalid host header');\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.isShuttingDown) {\n\t\t\tthis._trackError('shutting_down');\n\t\t\tsendJsonRpcError(res, 503, -32603, 'Server is shutting down');\n\t\t\treturn;\n\t\t}\n\n\t\tconst clientIp = this.getClientIp(req);\n\t\tif (this.checkRateLimit(clientIp)) {\n\t\t\tthis._trackError('rate_limit');\n\t\t\tres.setHeader('Retry-After', '60');\n\t\t\tsendJsonRpcError(res, 429, -32000, 'Too many requests');\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.validateCorsOrigin(req)) {\n\t\t\tthis._trackError('forbidden');\n\t\t\tsendJsonRpcError(res, 403, -32000, 'Forbidden - invalid origin');\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setCorsHeaders(res);\n\n\t\t// Static endpoints\n\t\tif (req.method === 'GET' && req.url === '/metrics')\n\t\t\treturn this.handleMetricsEndpoint(res, this._metricsProvider);\n\t\tif (req.method === 'OPTIONS') return sendCorsPreflight(res);\n\t\tif (req.method === 'GET' && req.url === '/health')\n\t\t\treturn this.handleHealthEndpoint(res, { requests: this._requestCount });\n\t\tif (req.method === 'GET' && req.url === '/ready') return this.handleReadinessEndpoint(res);\n\n\t\t// MCP endpoint\n\t\tif (req.method === 'POST' && req.url === this._path) return this._handlePostRequest(req, res);\n\n\t\t// 404\n\t\tthis._trackError('not_found');\n\t\tsendJsonRpcError(res, 404, -32601, 'Not Found');\n\t}\n\n\t/**\n\t * Handle POST to the MCP messages endpoint.\n\t *\n\t * Reads the request body, validates JSON-RPC format, and delegates\n\t * processing to the MCP server.\n\t */\n\tprivate async _handlePostRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\tthis._requestCount++;\n\t\tthis._activeRequests++;\n\n\t\tconst timeout = setTimeout(() => {\n\t\t\tthis._activeRequests--;\n\t\t\tthis._trackError('timeout');\n\t\t\tsendJsonRpcError(res, 500, -32603, 'Request timeout');\n\t\t}, this._requestTimeout);\n\n\t\ttry {\n\t\t\tconst maxBodySize = this._bodySizeLimitEnabled ? this._maxBodySize : 0;\n\t\t\tconst body = await readRequestBody(req, maxBodySize);\n\n\t\t\tif (body === null) {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tthis._activeRequests--;\n\t\t\t\tthis._trackError('payload_too_large');\n\t\t\t\tsendJsonRpcError(res, 413, -32000, 'Request body too large');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet jsonRpcRequest;\n\t\t\ttry {\n\t\t\t\tjsonRpcRequest = JSON.parse(body);\n\t\t\t} catch {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tthis._activeRequests--;\n\t\t\t\tthis._trackError('parse_error');\n\t\t\t\tsendJsonRpcError(res, 200, -32700, 'Parse error');\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst parseResult = safeParse(JsonRpcRequestSchema, jsonRpcRequest);\n\t\t\tif (!parseResult.success) {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tthis._activeRequests--;\n\t\t\t\tthis._trackError('validation');\n\t\t\t\tsendJsonRpcError(\n\t\t\t\t\tres,\n\t\t\t\t\t200,\n\t\t\t\t\t-32600,\n\t\t\t\t\t'Invalid Request',\n\t\t\t\t\tjsonRpcRequest?.id ?? null,\n\t\t\t\t\tparseResult.issues\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (!this._mcpServer) {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tthis._activeRequests--;\n\t\t\t\tthis._trackError('server_not_ready');\n\t\t\t\tsendJsonRpcError(res, 200, -32603, 'Server not ready', jsonRpcRequest?.id ?? null);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst owner = randomUUID();\n\t\t\tconst response = await runWithContext(\n\t\t\t\t{ requestId: randomUUID(), owner },\n\t\t\t\t() => this._mcpServer!.receive(jsonRpcRequest, {\n\t\t\t\t\tsessionInfo: {},\n\t\t\t\t})\n\t\t\t);\n\n\t\t\tclearTimeout(timeout);\n\t\t\tthis._activeRequests--;\n\n\t\t\tif (response) {\n\t\t\t\tsendJsonRpcResponse(res, response);\n\t\t\t} else {\n\t\t\t\tres.writeHead(204);\n\t\t\t\tres.end();\n\t\t\t}\n\t\t} catch (error) {\n\t\t\tclearTimeout(timeout);\n\t\t\tthis._activeRequests--;\n\t\t\tthis._trackError('internal_error');\n\t\t\tsendJsonRpcError(\n\t\t\t\tres,\n\t\t\t\t200,\n\t\t\t\t-32603,\n\t\t\t\t'Internal error',\n\t\t\t\tnull,\n\t\t\t\tgetErrorMessage(error)\n\t\t\t);\n\t\t}\n\t}\n\n\t/**\n\t * Returns number of requests handled.\n\t */\n\tget requestCount(): number {\n\t\treturn this._requestCount;\n\t}\n\n\t/**\n\t * Stops transport server.\n\t */\n\tasync stop(): Promise<void> {\n\t\tthis._isShuttingDown = true;\n\t\tthis._stopRateLimitCleanup();\n\n\t\treturn new Promise((resolve) => {\n\t\t\tthis._server.close(() => {\n\t\t\t\tthis.log('info', 'HTTP transport stopped');\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\t}\n}\n\n/**\n * Create an HTTP transport with given options.\n *\n * @param options - Transport configuration\n * @returns A configured HTTP transport\n *\n * @example\n * ```typescript\n * const transport = new HttpTransport({ port: 3000 });\n * await transport.connect(server);\n * ```\n */\nexport function createHttpTransport(options: HttpTransportOptions = {}): HttpTransport {\n\treturn new HttpTransport(options);\n}\n"],"names":["HttpTransport","BaseTransport","options","createServer","req","res","mcpServer","Promise","resolve","errorType","startTime","Date","requestPath","requestMethod","durationSeconds","sendJsonRpcError","clientIp","sendCorsPreflight","timeout","setTimeout","maxBodySize","body","readRequestBody","clearTimeout","jsonRpcRequest","JSON","parseResult","safeParse","JsonRpcRequestSchema","owner","randomUUID","response","runWithContext","sendJsonRpcResponse","error","getErrorMessage","createHttpTransport"],"mappings":"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"}
@@ -17,9 +17,10 @@
17
17
  * ```
18
18
  */
19
19
  import type { McpServer } from 'tmcp';
20
- import type { IMetrics } from '../contracts/index.js';
20
+ import type { IMetrics } from '../contracts/interfaces.js';
21
21
  import type { ConnectionPool } from '../pool/ConnectionPool.js';
22
22
  import { BaseTransport, type TransportOptions } from './BaseTransport.js';
23
+ import type { ITransport, TransportKind } from '../contracts/transport.js';
23
24
  /**
24
25
  * SSE-specific transport options extending base TransportOptions.
25
26
  */
@@ -51,7 +52,8 @@ export interface SseTransportOptions extends TransportOptions {
51
52
  * - Returns 429 Too Many Requests when limit exceeded
52
53
  * - Can be disabled via `enableRateLimit: false`
53
54
  */
54
- export declare class SseTransport extends BaseTransport {
55
+ export declare class SseTransport extends BaseTransport implements ITransport {
56
+ get kind(): TransportKind;
55
57
  private _server;
56
58
  private _path;
57
59
  private _clients;
@@ -1 +1 @@
1
- {"version":3,"file":"SseTransport.d.ts","sourceRoot":"","sources":["../../src/transport/SseTransport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEtC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEhE,OAAO,EAAE,aAAa,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC1E;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,gBAAgB;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB;;;;OAIG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,YAAa,SAAQ,aAAa;IAC9C,OAAO,CAAC,OAAO,CAAkC;IACjD,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,iBAAiB,CAA0C;IACnE,OAAO,CAAC,aAAa,CAAqC;IAC1D,OAAO,CAAC,QAAQ,CAAC,CAAW;IAC5B,OAAO,CAAC,eAAe,CAAC,CAAiB;gBAE7B,OAAO,GAAE,mBAAwB;IAU7C;;;;OAIG;IACG,OAAO,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAWlD,OAAO,CAAC,UAAU,CAA0B;IAE5C;;OAEG;YACW,cAAc;IA2F5B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAc1B;;OAEG;YACW,qBAAqB;IAYnC;;OAEG;YACW,oBAAoB;IA6DlC;;OAEG;YACW,cAAc;IAyD5B;;OAEG;IACH,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,8BAA8B;IAStC,OAAO,CAAC,kBAAkB;IAyB1B;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI;IAM7C;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;OAEG;IACH,IAAI,WAAW,IAAI,MAAM,CAExB;IAED;;OAEG;IACH,IAAI,cAAc,IAAI,cAAc,GAAG,SAAS,CAE/C;IAED;;;;;OAKG;IACG,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CA6B5C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,YAAY,CAElF"}
1
+ {"version":3,"file":"SseTransport.d.ts","sourceRoot":"","sources":["../../src/transport/SseTransport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAKH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEtC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,2BAA2B,CAAC;AAEhE,OAAO,EAAE,aAAa,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAE3E;;GAEG;AACH,MAAM,WAAW,mBAAoB,SAAQ,gBAAgB;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,QAAQ,CAAC;IACnB;;;;OAIG;IACH,cAAc,CAAC,EAAE,cAAc,CAAC;CAChC;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,qBAAa,YAAa,SAAQ,aAAc,YAAW,UAAU;IACpE,IAAI,IAAI,IAAI,aAAa,CAAkB;IAC3C,OAAO,CAAC,OAAO,CAAkC;IACjD,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,QAAQ,CAAkC;IAClD,OAAO,CAAC,iBAAiB,CAA0C;IACnE,OAAO,CAAC,aAAa,CAAqC;IAC1D,OAAO,CAAC,QAAQ,CAAC,CAAW;IAC5B,OAAO,CAAC,eAAe,CAAC,CAAiB;gBAE7B,OAAO,GAAE,mBAAwB;IAU7C;;;;OAIG;IACG,OAAO,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAWlD,OAAO,CAAC,UAAU,CAA0B;IAE5C;;OAEG;YACW,cAAc;IA2F5B;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAc1B;;OAEG;YACW,qBAAqB;IAYnC;;OAEG;YACW,oBAAoB;IA6DlC;;OAEG;YACW,cAAc;IA8D5B;;OAEG;IACH,OAAO,CAAC,aAAa;IAWrB,OAAO,CAAC,8BAA8B;IAStC,OAAO,CAAC,kBAAkB;IAyB1B;;OAEG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI;IAM7C;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAIzB;;OAEG;IACH,IAAI,WAAW,IAAI,MAAM,CAExB;IAED;;OAEG;IACH,IAAI,cAAc,IAAI,cAAc,GAAG,SAAS,CAE/C;IAED;;;;;OAKG;IACG,IAAI,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CA6B5C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,GAAE,mBAAwB,GAAG,YAAY,CAElF"}
@@ -1,9 +1,14 @@
1
1
  import { createServer } from "node:http";
2
+ import { randomUUID } from "node:crypto";
2
3
  import { URL } from "node:url";
3
4
  import { safeParse } from "valibot";
4
5
  import { JsonRpcRequestSchema } from "../schema.js";
5
6
  import { BaseTransport } from "./BaseTransport.js";
7
+ import { runWithContext } from "../context/RequestContext.js";
6
8
  class SseTransport extends BaseTransport {
9
+ get kind() {
10
+ return 'sse';
11
+ }
7
12
  _server;
8
13
  _path;
9
14
  _clients = new Set();
@@ -224,9 +229,14 @@ class SseTransport extends BaseTransport {
224
229
  return;
225
230
  }
226
231
  if (this._mcpServer) {
227
- const response = await this._mcpServer.receive(jsonRpcRequest, {
228
- sessionInfo: {}
229
- });
232
+ const sessionId = this._clientSessionMap.get(res);
233
+ const owner = sessionId ?? `sse-${randomUUID()}`;
234
+ const response = await runWithContext({
235
+ requestId: randomUUID(),
236
+ owner
237
+ }, ()=>this._mcpServer.receive(jsonRpcRequest, {
238
+ sessionInfo: {}
239
+ }));
230
240
  res.writeHead(200, {
231
241
  'Content-Type': 'application/json'
232
242
  });
@@ -1 +1 @@
1
- {"version":3,"file":"transport/SseTransport.js","sources":["../../src/transport/SseTransport.ts"],"sourcesContent":["/**\n * SSE (Server-Sent Events) Transport implementation.\n *\n * This transport allows multiple concurrent connections over HTTP using Server-Sent Events,\n * enabling multi-user scenarios and horizontal scaling.\n *\n * When a ConnectionPool is provided, each SSE client gets an isolated session with its own\n * thought history. Without a pool, all clients share a single server instance (backward compatible).\n *\n * @example\n * ```typescript\n * const transport = new SseTransport({\n * port: 3000,\n * host: 'localhost'\n * });\n * await transport.connect(server);\n * ```\n */\n\nimport { createServer, IncomingMessage, ServerResponse } from 'node:http';\nimport { URL } from 'node:url';\nimport type { McpServer } from 'tmcp';\nimport { safeParse } from 'valibot';\nimport type { IMetrics } from '../contracts/index.js';\nimport type { ConnectionPool } from '../pool/ConnectionPool.js';\nimport { JsonRpcRequestSchema } from '../schema.js';\nimport { BaseTransport, type TransportOptions } from './BaseTransport.js';\n/**\n * SSE-specific transport options extending base TransportOptions.\n */\nexport interface SseTransportOptions extends TransportOptions {\n\tpath?: string;\n\tmetrics?: IMetrics;\n\t/**\n\t * Optional connection pool for per-session state isolation.\n\t * When provided, each SSE client gets an isolated thought history.\n\t * When omitted, all clients share a single server instance (backward compatible).\n\t */\n\tconnectionPool?: ConnectionPool;\n}\n\n/**\n * SSE Transport for MCP server over HTTP.\n *\n * This transport uses Server-Sent Events (SSE) to communicate with clients,\n * allowing multiple concurrent connections and web-based clients.\n *\n * @remarks\n * **Security Features:**\n * - Session ID validation (alphanumeric, max 64 chars)\n * - Query parameter sanitization (whitelist allowed keys)\n * - Rate limiting per IP (configurable, default 100 req/min)\n * - CORS origin validation\n *\n * **Rate Limiting:**\n * - Tracks requests per IP address within a time window\n * - Returns 429 Too Many Requests when limit exceeded\n * - Can be disabled via `enableRateLimit: false`\n */\nexport class SseTransport extends BaseTransport {\n\tprivate _server: ReturnType<typeof createServer>;\n\tprivate _path: string;\n\tprivate _clients: Set<ServerResponse> = new Set();\n\tprivate _clientSessionMap: Map<ServerResponse, string> = new Map();\n\tprivate _messageQueue: Map<string, unknown[]> = new Map();\n\tprivate _metrics?: IMetrics;\n\tprivate _connectionPool?: ConnectionPool;\n\n\tconstructor(options: SseTransportOptions = {}) {\n\t\tsuper(options);\n\t\tthis._path = options.path ?? '/sse';\n\t\tthis._metrics = options.metrics;\n\t\tthis._connectionPool = options.connectionPool;\n\t\tthis._updateActiveConnectionsMetric();\n\n\t\tthis._server = createServer((req, res) => this._handleRequest(req, res));\n\t}\n\n\t/**\n\t * Connect MCP server to this transport.\n\t *\n\t * @param mcpServer - The MCP server instance\n\t */\n\tasync connect(mcpServer: McpServer): Promise<void> {\n\t\tthis._mcpServer = mcpServer;\n\n\t\treturn new Promise((resolve) => {\n\t\t\tthis._server.listen(this._port, this._host, () => {\n\t\t\t\tthis.log('info', `SSE transport listening on http://${this._host}:${this._port}`);\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\t}\n\n\tprivate _mcpServer: McpServer | null = null;\n\n\t/**\n\t * Handle incoming HTTP requests\n\t */\n\tprivate async _handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\tconst startTime = Date.now();\n\t\tconst requestPath = req.url || '/';\n\t\tconst requestMethod = req.method || 'GET';\n\t\tthis._metrics?.counter('http_requests_total', 1, { transport: 'sse', method: requestMethod, path: requestPath }, 'Total HTTP requests');\n\t\tres.once('finish', () => {\n\t\t\tconst durationSeconds = (Date.now() - startTime) / 1000;\n\t\t\tthis._metrics?.histogram('http_request_duration_seconds', durationSeconds, { transport: 'sse', path: requestPath });\n\t\t});\n\t\tif (!this.validateHostHeader(req)) {\n\t\t\tthis._metrics?.counter('http_request_errors_total', 1, { transport: 'sse', error_type: 'forbidden' }, 'Total HTTP request errors');\n\t\t\tres.writeHead(403, { 'Content-Type': 'application/json' });\n\t\t\tres.end(JSON.stringify({ error: 'Forbidden - invalid host header' }));\n\t\t\treturn;\n\t\t}\n\n\t\tconst url = new URL(req.url || '', `http://${req.headers.host}`);\n\n\t\t// Check rate limit first\n\t\tconst clientIp = this.getClientIp(req);\n\t\tif (this.checkRateLimit(clientIp)) {\n\t\t\tthis._metrics?.counter('http_request_errors_total', 1, { transport: 'sse', error_type: 'rate_limit' }, 'Total HTTP request errors');\n\t\t\tres.writeHead(429, {\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t'Retry-After': '60',\n\t\t\t});\n\t\t\tres.end(JSON.stringify({ error: 'Too many requests' }));\n\t\t\treturn;\n\t\t}\n\n\t\t// Validate CORS origin\n\t\tif (!this.validateCorsOrigin(req)) {\n\t\t\tthis._metrics?.counter('http_request_errors_total', 1, { transport: 'sse', error_type: 'forbidden' }, 'Total HTTP request errors');\n\t\t\tres.writeHead(403, { 'Content-Type': 'application/json' });\n\t\t\tres.end(JSON.stringify({ error: 'Forbidden - invalid origin' }));\n\t\t\treturn;\n\t\t}\n\n\t\t// Set CORS headers\n\t\tthis.setCorsHeaders(res);\n\n\t\t// Sanitize query parameters\n\t\tconst sanitizedParams = this.sanitizeQueryParams(url);\n\n\t\t// Validate session ID if present\n\t\tif (sanitizedParams.session || sanitizedParams.sessionId) {\n\t\t\tconst sessionId = (sanitizedParams.session ?? sanitizedParams.sessionId)!;\n\t\t\tif (!this.validateSessionId(sessionId)) {\n\t\t\t\tthis._metrics?.counter('http_request_errors_total', 1, { transport: 'sse', error_type: 'validation' }, 'Total HTTP request errors');\n\t\t\t\tres.writeHead(400, { 'Content-Type': 'application/json' });\n\t\t\t\tres.end(JSON.stringify({ error: 'Invalid session ID format' }));\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\t// Handle CORS preflight\n\t\tif (this._enableCors && req.method === 'OPTIONS') {\n\t\t\tres.writeHead(204);\n\t\t\tres.end();\n\t\t\treturn;\n\t\t}\n\n\t\t// Handle SSE endpoint\n\t\tif (url.pathname === this._path && req.method === 'GET') {\n\t\t\tawait this._handleSseConnection(req, res, sanitizedParams);\n\t\t\treturn;\n\t\t}\n\n\t\t// Handle message endpoint (for receiving messages from clients)\n\t\tif (url.pathname === `${this._path}/message` && req.method === 'POST') {\n\t\t\tawait this._handleMessage(req, res, sanitizedParams);\n\t\t\treturn;\n\t\t}\n\n\t\t// Handle health check (liveness)\n\t\tif (url.pathname === '/health') {\n\t\t\tthis._handleHealthCheck(res);\n\t\t\treturn;\n\t\t}\n\n\t\t// Handle readiness check\n\t\tif (url.pathname === '/ready') {\n\t\t\tawait this._handleReadinessCheck(res);\n\t\t\treturn;\n\t\t}\n\n\n\t\t// 404 for unknown paths\n\t\tres.writeHead(404, { 'Content-Type': 'text/plain' });\n\t\tres.end('Not Found');\n\t}\n\n\t/**\n\t * Handle health check (liveness) endpoint\n\t */\n\tprivate _handleHealthCheck(res: ServerResponse): void {\n\t\tconst healthData: Record<string, unknown> = { status: 'healthy', clients: this._clients.size };\n\t\tif (this._connectionPool) {\n\t\t\tconst poolStats = this._connectionPool.getStats();\n\t\t\thealthData.pool = poolStats;\n\t\t}\n\t\tif (this._healthChecker) {\n\t\t\tconst liveness = this._healthChecker.checkLiveness();\n\t\t\thealthData.liveness = liveness;\n\t\t}\n\t\tres.writeHead(200, { 'Content-Type': 'application/json' });\n\t\tres.end(JSON.stringify(healthData));\n\t}\n\n\t/**\n\t * Handle readiness check endpoint\n\t */\n\tprivate async _handleReadinessCheck(res: ServerResponse): Promise<void> {\n\t\tif (this._healthChecker) {\n\t\t\tconst readiness = await this._healthChecker.checkReadiness();\n\t\t\tconst statusCode = readiness.status === 'ok' ? 200 : 503;\n\t\t\tres.writeHead(statusCode, { 'Content-Type': 'application/json' });\n\t\t\tres.end(JSON.stringify(readiness));\n\t\t} else {\n\t\t\tres.writeHead(200, { 'Content-Type': 'application/json' });\n\t\t\tres.end(JSON.stringify({ status: 'ok', timestamp: new Date().toISOString(), components: {} }));\n\t\t}\n\t}\n\n\t/**\n\t * Handle new SSE connection\n\t */\n\tprivate async _handleSseConnection(\n\t\treq: IncomingMessage,\n\t\tres: ServerResponse,\n\t\tparams: Record<string, string>\n\t): Promise<void> {\n\t\t// Set SSE headers\n\t\tres.writeHead(200, {\n\t\t\t'Content-Type': 'text/event-stream',\n\t\t\t'Cache-Control': 'no-cache',\n\t\t\tConnection: 'keep-alive',\n\t\t});\n\n\t\t// Resolve session ID when pool is active\n\t\tlet sessionId: string | undefined;\n\t\tif (this._connectionPool) {\n\t\t\tconst requestedSession = params.session ?? params.sessionId;\n\t\t\tif (requestedSession && this._connectionPool.getSessionInfo(requestedSession)) {\n\t\t\t\tsessionId = requestedSession;\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\tsessionId = await this._connectionPool.createSession();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tres.write(`event: error\\n`);\n\t\t\t\t\tres.write(`data: ${JSON.stringify({ error: error instanceof Error ? error.message : 'Failed to create session' })}\\n\\n`);\n\t\t\t\t\tres.end();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis._clientSessionMap.set(res, sessionId);\n\t\t\tthis._updatePoolMetrics();\n\t\t}\n\n\t\t// Send initial connection event\n\t\tconst connectedPayload: Record<string, unknown> = { timestamp: Date.now() };\n\t\tif (sessionId) {\n\t\t\tconnectedPayload.sessionId = sessionId;\n\t\t}\n\t\tthis._sendSseEvent(res, 'connected', connectedPayload);\n\n\t\t// Add to clients\n\t\tthis._clients.add(res);\n\t\tthis._updateActiveConnectionsMetric();\n\n\t\t// Handle client disconnect\n\t\treq.on('close', () => {\n\t\t\tthis._clients.delete(res);\n\t\t\tthis._clientSessionMap.delete(res);\n\t\t\tthis._updateActiveConnectionsMetric();\n\t\t});\n\n\t\t// Send any queued messages\n\t\tconst clientId = this._generateClientId();\n\t\tconst queued = this._messageQueue.get(clientId);\n\t\tif (queued) {\n\t\t\tfor (const message of queued) {\n\t\t\t\tthis._sendSseEvent(res, 'message', message);\n\t\t\t}\n\t\t\tthis._messageQueue.delete(clientId);\n\t\t}\n\t}\n\n\t/**\n\t * Handle incoming message from client\n\t */\n\tprivate async _handleMessage(\n\t\treq: IncomingMessage,\n\t\tres: ServerResponse,\n\t\t_params: Record<string, string>\n\t): Promise<void> {\n\t\tlet body = '';\n\n\t\tfor await (const chunk of req) {\n\t\t\tbody += chunk.toString();\n\t\t}\n\n\t\ttry {\n\t\t\tconst jsonRpcRequest = JSON.parse(body);\n\t\t\tconst parseResult = safeParse(JsonRpcRequestSchema, jsonRpcRequest);\n\t\t\tif (!parseResult.success) {\n\t\t\t\tthis._metrics?.counter('http_request_errors_total', 1, { transport: 'sse', error_type: 'validation' }, 'Total HTTP request errors');\n\t\t\t\tres.writeHead(200, { 'Content-Type': 'application/json' });\n\t\t\t\tres.end(\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\tjsonrpc: '2.0',\n\t\t\t\t\t\tid: jsonRpcRequest?.id ?? null,\n\t\t\t\t\t\terror: {\n\t\t\t\t\t\t\tcode: -32600,\n\t\t\t\t\t\t\tmessage: 'Invalid Request',\n\t\t\t\t\t\t\tdata: parseResult.issues,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Process message through MCP server\n\t\t\tif (this._mcpServer) {\n\t\t\t\tconst response = await this._mcpServer.receive(jsonRpcRequest, {\n\t\t\t\t\tsessionInfo: {},\n\t\t\t\t});\n\t\t\t\tres.writeHead(200, {\n\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t});\n\n\t\t\t\tif (response) {\n\t\t\t\t\tres.end(JSON.stringify(response));\n\t\t\t\t} else {\n\t\t\t\t\tres.end(JSON.stringify({ jsonrpc: '2.0', id: jsonRpcRequest?.id ?? null, result: null }));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis._metrics?.counter('http_request_errors_total', 1, { transport: 'sse', error_type: 'server_not_ready' }, 'Total HTTP request errors');\n\t\t\t\tres.writeHead(503, { 'Content-Type': 'application/json' });\n\t\t\t\tres.end(JSON.stringify({ error: 'Server not ready' }));\n\t\t\t}\n\t\t} catch {\n\t\t\tthis._metrics?.counter('http_request_errors_total', 1, { transport: 'sse', error_type: 'parse_error' }, 'Total HTTP request errors');\n\t\t\tres.writeHead(400, { 'Content-Type': 'application/json' });\n\t\t\tres.end(JSON.stringify({ error: 'Invalid JSON' }));\n\t\t}\n\t}\n\n\t/**\n\t * Send an SSE event to a specific client\n\t */\n\tprivate _sendSseEvent(res: ServerResponse, event: string, data: unknown): void {\n\t\ttry {\n\t\t\tres.write(`event: ${event}\\n`);\n\t\t\tres.write(`data: ${JSON.stringify(data)}\\n\\n`);\n\t\t} catch {\n\t\t\t// Client disconnected\n\t\t\tthis._clients.delete(res);\n\t\t\tthis._updateActiveConnectionsMetric();\n\t\t}\n\t}\n\n\tprivate _updateActiveConnectionsMetric(): void {\n\t\tthis._metrics?.gauge(\n\t\t\t'sse_active_connections',\n\t\t\tthis._clients.size,\n\t\t\t{},\n\t\t\t'Current active SSE connections'\n\t\t);\n\t}\n\n\tprivate _updatePoolMetrics(): void {\n\t\tif (!this._connectionPool || !this._metrics) {\n\t\t\treturn;\n\t\t}\n\t\tconst stats = this._connectionPool.getStats();\n\t\tthis._metrics.gauge(\n\t\t\t'sse_pool_active_sessions',\n\t\t\tstats.activeSessions,\n\t\t\t{},\n\t\t\t'Active sessions in connection pool'\n\t\t);\n\t\tthis._metrics.gauge(\n\t\t\t'sse_pool_total_sessions',\n\t\t\tstats.totalSessions,\n\t\t\t{},\n\t\t\t'Total sessions in connection pool'\n\t\t);\n\t\tthis._metrics.gauge(\n\t\t\t'sse_pool_max_sessions',\n\t\t\tstats.maxSessions,\n\t\t\t{},\n\t\t\t'Maximum sessions in connection pool'\n\t\t);\n\t}\n\n\t/**\n\t * Broadcast a message to all connected clients\n\t */\n\tbroadcast(event: string, data: unknown): void {\n\t\tfor (const client of this._clients) {\n\t\t\tthis._sendSseEvent(client, event, data);\n\t\t}\n\t}\n\n\t/**\n\t * Generate a unique client ID\n\t */\n\tprivate _generateClientId(): string {\n\t\treturn `client_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;\n\t}\n\n\t/**\n\t * Get number of connected clients\n\t */\n\tget clientCount(): number {\n\t\treturn this._clients.size;\n\t}\n\n\t/**\n\t * Get the connection pool, if one was configured.\n\t */\n\tget connectionPool(): ConnectionPool | undefined {\n\t\treturn this._connectionPool;\n\t}\n\n\t/**\n\t * Stop the transport server with graceful shutdown.\n\t *\n\t * @param timeout - Maximum time to wait for requests to drain (not used for SSE)\n\t * @returns Promise that resolves when shutdown is complete\n\t */\n\tasync stop(_timeout?: number): Promise<void> {\n\t\tthis._isShuttingDown = true;\n\t\tthis._stopRateLimitCleanup();\n\n\t\t// Terminate connection pool if present\n\t\tif (this._connectionPool) {\n\t\t\tawait this._connectionPool.terminate();\n\t\t}\n\n\t\treturn new Promise((resolve) => {\n\t\t\t// Close all client connections\n\t\t\tfor (const client of this._clients) {\n\t\t\t\ttry {\n\t\t\t\t\tclient.end();\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore errors\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis._clients.clear();\n\t\t\tthis._clientSessionMap.clear();\n\t\t\tthis._updateActiveConnectionsMetric();\n\n\t\t\t// Close server\n\t\t\tthis._server.close(() => {\n\t\t\t\tthis.log('info', 'SSE transport stopped');\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\t}\n}\n\n/**\n * Create an SSE transport with given options.\n *\n * @param options - Transport configuration\n * @returns A configured SSE transport\n *\n * @example\n * ```typescript\n * const transport = createSseTransport({ port: 3000 });\n * await transport.connect(mcpServer);\n * ```\n */\nexport function createSseTransport(options: SseTransportOptions = {}): SseTransport {\n\treturn new SseTransport(options);\n}\n"],"names":["SseTransport","BaseTransport","Set","Map","options","createServer","req","res","mcpServer","Promise","resolve","startTime","Date","requestPath","requestMethod","durationSeconds","JSON","url","URL","clientIp","sanitizedParams","sessionId","healthData","poolStats","liveness","readiness","statusCode","params","requestedSession","error","Error","connectedPayload","clientId","queued","message","_params","body","chunk","jsonRpcRequest","parseResult","safeParse","JsonRpcRequestSchema","response","event","data","stats","client","Math","_timeout","createSseTransport"],"mappings":"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"}
1
+ {"version":3,"file":"transport/SseTransport.js","sources":["../../src/transport/SseTransport.ts"],"sourcesContent":["/**\n * SSE (Server-Sent Events) Transport implementation.\n *\n * This transport allows multiple concurrent connections over HTTP using Server-Sent Events,\n * enabling multi-user scenarios and horizontal scaling.\n *\n * When a ConnectionPool is provided, each SSE client gets an isolated session with its own\n * thought history. Without a pool, all clients share a single server instance (backward compatible).\n *\n * @example\n * ```typescript\n * const transport = new SseTransport({\n * port: 3000,\n * host: 'localhost'\n * });\n * await transport.connect(server);\n * ```\n */\n\nimport { createServer, IncomingMessage, ServerResponse } from 'node:http';\nimport { randomUUID } from 'node:crypto';\nimport { URL } from 'node:url';\nimport type { McpServer } from 'tmcp';\nimport { safeParse } from 'valibot';\nimport type { IMetrics } from '../contracts/interfaces.js';\nimport type { ConnectionPool } from '../pool/ConnectionPool.js';\nimport { JsonRpcRequestSchema } from '../schema.js';\nimport { BaseTransport, type TransportOptions } from './BaseTransport.js';\nimport type { ITransport, TransportKind } from '../contracts/transport.js';\nimport { runWithContext } from '../context/RequestContext.js';\n/**\n * SSE-specific transport options extending base TransportOptions.\n */\nexport interface SseTransportOptions extends TransportOptions {\n\tpath?: string;\n\tmetrics?: IMetrics;\n\t/**\n\t * Optional connection pool for per-session state isolation.\n\t * When provided, each SSE client gets an isolated thought history.\n\t * When omitted, all clients share a single server instance (backward compatible).\n\t */\n\tconnectionPool?: ConnectionPool;\n}\n\n/**\n * SSE Transport for MCP server over HTTP.\n *\n * This transport uses Server-Sent Events (SSE) to communicate with clients,\n * allowing multiple concurrent connections and web-based clients.\n *\n * @remarks\n * **Security Features:**\n * - Session ID validation (alphanumeric, max 64 chars)\n * - Query parameter sanitization (whitelist allowed keys)\n * - Rate limiting per IP (configurable, default 100 req/min)\n * - CORS origin validation\n *\n * **Rate Limiting:**\n * - Tracks requests per IP address within a time window\n * - Returns 429 Too Many Requests when limit exceeded\n * - Can be disabled via `enableRateLimit: false`\n */\nexport class SseTransport extends BaseTransport implements ITransport {\n\tget kind(): TransportKind { return 'sse'; }\n\tprivate _server: ReturnType<typeof createServer>;\n\tprivate _path: string;\n\tprivate _clients: Set<ServerResponse> = new Set();\n\tprivate _clientSessionMap: Map<ServerResponse, string> = new Map();\n\tprivate _messageQueue: Map<string, unknown[]> = new Map();\n\tprivate _metrics?: IMetrics;\n\tprivate _connectionPool?: ConnectionPool;\n\n\tconstructor(options: SseTransportOptions = {}) {\n\t\tsuper(options);\n\t\tthis._path = options.path ?? '/sse';\n\t\tthis._metrics = options.metrics;\n\t\tthis._connectionPool = options.connectionPool;\n\t\tthis._updateActiveConnectionsMetric();\n\n\t\tthis._server = createServer((req, res) => this._handleRequest(req, res));\n\t}\n\n\t/**\n\t * Connect MCP server to this transport.\n\t *\n\t * @param mcpServer - The MCP server instance\n\t */\n\tasync connect(mcpServer: McpServer): Promise<void> {\n\t\tthis._mcpServer = mcpServer;\n\n\t\treturn new Promise((resolve) => {\n\t\t\tthis._server.listen(this._port, this._host, () => {\n\t\t\t\tthis.log('info', `SSE transport listening on http://${this._host}:${this._port}`);\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\t}\n\n\tprivate _mcpServer: McpServer | null = null;\n\n\t/**\n\t * Handle incoming HTTP requests\n\t */\n\tprivate async _handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\tconst startTime = Date.now();\n\t\tconst requestPath = req.url || '/';\n\t\tconst requestMethod = req.method || 'GET';\n\t\tthis._metrics?.counter('http_requests_total', 1, { transport: 'sse', method: requestMethod, path: requestPath }, 'Total HTTP requests');\n\t\tres.once('finish', () => {\n\t\t\tconst durationSeconds = (Date.now() - startTime) / 1000;\n\t\t\tthis._metrics?.histogram('http_request_duration_seconds', durationSeconds, { transport: 'sse', path: requestPath });\n\t\t});\n\t\tif (!this.validateHostHeader(req)) {\n\t\t\tthis._metrics?.counter('http_request_errors_total', 1, { transport: 'sse', error_type: 'forbidden' }, 'Total HTTP request errors');\n\t\t\tres.writeHead(403, { 'Content-Type': 'application/json' });\n\t\t\tres.end(JSON.stringify({ error: 'Forbidden - invalid host header' }));\n\t\t\treturn;\n\t\t}\n\n\t\tconst url = new URL(req.url || '', `http://${req.headers.host}`);\n\n\t\t// Check rate limit first\n\t\tconst clientIp = this.getClientIp(req);\n\t\tif (this.checkRateLimit(clientIp)) {\n\t\t\tthis._metrics?.counter('http_request_errors_total', 1, { transport: 'sse', error_type: 'rate_limit' }, 'Total HTTP request errors');\n\t\t\tres.writeHead(429, {\n\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t'Retry-After': '60',\n\t\t\t});\n\t\t\tres.end(JSON.stringify({ error: 'Too many requests' }));\n\t\t\treturn;\n\t\t}\n\n\t\t// Validate CORS origin\n\t\tif (!this.validateCorsOrigin(req)) {\n\t\t\tthis._metrics?.counter('http_request_errors_total', 1, { transport: 'sse', error_type: 'forbidden' }, 'Total HTTP request errors');\n\t\t\tres.writeHead(403, { 'Content-Type': 'application/json' });\n\t\t\tres.end(JSON.stringify({ error: 'Forbidden - invalid origin' }));\n\t\t\treturn;\n\t\t}\n\n\t\t// Set CORS headers\n\t\tthis.setCorsHeaders(res);\n\n\t\t// Sanitize query parameters\n\t\tconst sanitizedParams = this.sanitizeQueryParams(url);\n\n\t\t// Validate session ID if present\n\t\tif (sanitizedParams.session || sanitizedParams.sessionId) {\n\t\t\tconst sessionId = (sanitizedParams.session ?? sanitizedParams.sessionId)!;\n\t\t\tif (!this.validateSessionId(sessionId)) {\n\t\t\t\tthis._metrics?.counter('http_request_errors_total', 1, { transport: 'sse', error_type: 'validation' }, 'Total HTTP request errors');\n\t\t\t\tres.writeHead(400, { 'Content-Type': 'application/json' });\n\t\t\t\tres.end(JSON.stringify({ error: 'Invalid session ID format' }));\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t\t// Handle CORS preflight\n\t\tif (this._enableCors && req.method === 'OPTIONS') {\n\t\t\tres.writeHead(204);\n\t\t\tres.end();\n\t\t\treturn;\n\t\t}\n\n\t\t// Handle SSE endpoint\n\t\tif (url.pathname === this._path && req.method === 'GET') {\n\t\t\tawait this._handleSseConnection(req, res, sanitizedParams);\n\t\t\treturn;\n\t\t}\n\n\t\t// Handle message endpoint (for receiving messages from clients)\n\t\tif (url.pathname === `${this._path}/message` && req.method === 'POST') {\n\t\t\tawait this._handleMessage(req, res, sanitizedParams);\n\t\t\treturn;\n\t\t}\n\n\t\t// Handle health check (liveness)\n\t\tif (url.pathname === '/health') {\n\t\t\tthis._handleHealthCheck(res);\n\t\t\treturn;\n\t\t}\n\n\t\t// Handle readiness check\n\t\tif (url.pathname === '/ready') {\n\t\t\tawait this._handleReadinessCheck(res);\n\t\t\treturn;\n\t\t}\n\n\n\t\t// 404 for unknown paths\n\t\tres.writeHead(404, { 'Content-Type': 'text/plain' });\n\t\tres.end('Not Found');\n\t}\n\n\t/**\n\t * Handle health check (liveness) endpoint\n\t */\n\tprivate _handleHealthCheck(res: ServerResponse): void {\n\t\tconst healthData: Record<string, unknown> = { status: 'healthy', clients: this._clients.size };\n\t\tif (this._connectionPool) {\n\t\t\tconst poolStats = this._connectionPool.getStats();\n\t\t\thealthData.pool = poolStats;\n\t\t}\n\t\tif (this._healthChecker) {\n\t\t\tconst liveness = this._healthChecker.checkLiveness();\n\t\t\thealthData.liveness = liveness;\n\t\t}\n\t\tres.writeHead(200, { 'Content-Type': 'application/json' });\n\t\tres.end(JSON.stringify(healthData));\n\t}\n\n\t/**\n\t * Handle readiness check endpoint\n\t */\n\tprivate async _handleReadinessCheck(res: ServerResponse): Promise<void> {\n\t\tif (this._healthChecker) {\n\t\t\tconst readiness = await this._healthChecker.checkReadiness();\n\t\t\tconst statusCode = readiness.status === 'ok' ? 200 : 503;\n\t\t\tres.writeHead(statusCode, { 'Content-Type': 'application/json' });\n\t\t\tres.end(JSON.stringify(readiness));\n\t\t} else {\n\t\t\tres.writeHead(200, { 'Content-Type': 'application/json' });\n\t\t\tres.end(JSON.stringify({ status: 'ok', timestamp: new Date().toISOString(), components: {} }));\n\t\t}\n\t}\n\n\t/**\n\t * Handle new SSE connection\n\t */\n\tprivate async _handleSseConnection(\n\t\treq: IncomingMessage,\n\t\tres: ServerResponse,\n\t\tparams: Record<string, string>\n\t): Promise<void> {\n\t\t// Set SSE headers\n\t\tres.writeHead(200, {\n\t\t\t'Content-Type': 'text/event-stream',\n\t\t\t'Cache-Control': 'no-cache',\n\t\t\tConnection: 'keep-alive',\n\t\t});\n\n\t\t// Resolve session ID when pool is active\n\t\tlet sessionId: string | undefined;\n\t\tif (this._connectionPool) {\n\t\t\tconst requestedSession = params.session ?? params.sessionId;\n\t\t\tif (requestedSession && this._connectionPool.getSessionInfo(requestedSession)) {\n\t\t\t\tsessionId = requestedSession;\n\t\t\t} else {\n\t\t\t\ttry {\n\t\t\t\t\tsessionId = await this._connectionPool.createSession();\n\t\t\t\t} catch (error) {\n\t\t\t\t\tres.write(`event: error\\n`);\n\t\t\t\t\tres.write(`data: ${JSON.stringify({ error: error instanceof Error ? error.message : 'Failed to create session' })}\\n\\n`);\n\t\t\t\t\tres.end();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis._clientSessionMap.set(res, sessionId);\n\t\t\tthis._updatePoolMetrics();\n\t\t}\n\n\t\t// Send initial connection event\n\t\tconst connectedPayload: Record<string, unknown> = { timestamp: Date.now() };\n\t\tif (sessionId) {\n\t\t\tconnectedPayload.sessionId = sessionId;\n\t\t}\n\t\tthis._sendSseEvent(res, 'connected', connectedPayload);\n\n\t\t// Add to clients\n\t\tthis._clients.add(res);\n\t\tthis._updateActiveConnectionsMetric();\n\n\t\t// Handle client disconnect\n\t\treq.on('close', () => {\n\t\t\tthis._clients.delete(res);\n\t\t\tthis._clientSessionMap.delete(res);\n\t\t\tthis._updateActiveConnectionsMetric();\n\t\t});\n\n\t\t// Send any queued messages\n\t\tconst clientId = this._generateClientId();\n\t\tconst queued = this._messageQueue.get(clientId);\n\t\tif (queued) {\n\t\t\tfor (const message of queued) {\n\t\t\t\tthis._sendSseEvent(res, 'message', message);\n\t\t\t}\n\t\t\tthis._messageQueue.delete(clientId);\n\t\t}\n\t}\n\n\t/**\n\t * Handle incoming message from client\n\t */\n\tprivate async _handleMessage(\n\t\treq: IncomingMessage,\n\t\tres: ServerResponse,\n\t\t_params: Record<string, string>\n\t): Promise<void> {\n\t\tlet body = '';\n\n\t\tfor await (const chunk of req) {\n\t\t\tbody += chunk.toString();\n\t\t}\n\n\t\ttry {\n\t\t\tconst jsonRpcRequest = JSON.parse(body);\n\t\t\tconst parseResult = safeParse(JsonRpcRequestSchema, jsonRpcRequest);\n\t\t\tif (!parseResult.success) {\n\t\t\t\tthis._metrics?.counter('http_request_errors_total', 1, { transport: 'sse', error_type: 'validation' }, 'Total HTTP request errors');\n\t\t\t\tres.writeHead(200, { 'Content-Type': 'application/json' });\n\t\t\t\tres.end(\n\t\t\t\t\tJSON.stringify({\n\t\t\t\t\t\tjsonrpc: '2.0',\n\t\t\t\t\t\tid: jsonRpcRequest?.id ?? null,\n\t\t\t\t\t\terror: {\n\t\t\t\t\t\t\tcode: -32600,\n\t\t\t\t\t\t\tmessage: 'Invalid Request',\n\t\t\t\t\t\t\tdata: parseResult.issues,\n\t\t\t\t\t\t},\n\t\t\t\t\t})\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// Process message through MCP server with owner context\n\t\t\tif (this._mcpServer) {\n\t\t\t\tconst sessionId = this._clientSessionMap.get(res);\n\t\t\t\tconst owner = sessionId ?? `sse-${randomUUID()}`;\n\t\t\t\tconst response = await runWithContext(\n\t\t\t\t\t{ requestId: randomUUID(), owner },\n\t\t\t\t\t() => this._mcpServer!.receive(jsonRpcRequest, {\n\t\t\t\t\t\tsessionInfo: {},\n\t\t\t\t\t})\n\t\t\t\t);\n\t\t\t\tres.writeHead(200, {\n\t\t\t\t\t'Content-Type': 'application/json',\n\t\t\t\t});\n\n\t\t\t\tif (response) {\n\t\t\t\t\tres.end(JSON.stringify(response));\n\t\t\t\t} else {\n\t\t\t\t\tres.end(JSON.stringify({ jsonrpc: '2.0', id: jsonRpcRequest?.id ?? null, result: null }));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis._metrics?.counter('http_request_errors_total', 1, { transport: 'sse', error_type: 'server_not_ready' }, 'Total HTTP request errors');\n\t\t\t\tres.writeHead(503, { 'Content-Type': 'application/json' });\n\t\t\t\tres.end(JSON.stringify({ error: 'Server not ready' }));\n\t\t\t}\n\t\t} catch {\n\t\t\tthis._metrics?.counter('http_request_errors_total', 1, { transport: 'sse', error_type: 'parse_error' }, 'Total HTTP request errors');\n\t\t\tres.writeHead(400, { 'Content-Type': 'application/json' });\n\t\t\tres.end(JSON.stringify({ error: 'Invalid JSON' }));\n\t\t}\n\t}\n\n\t/**\n\t * Send an SSE event to a specific client\n\t */\n\tprivate _sendSseEvent(res: ServerResponse, event: string, data: unknown): void {\n\t\ttry {\n\t\t\tres.write(`event: ${event}\\n`);\n\t\t\tres.write(`data: ${JSON.stringify(data)}\\n\\n`);\n\t\t} catch {\n\t\t\t// Client disconnected\n\t\t\tthis._clients.delete(res);\n\t\t\tthis._updateActiveConnectionsMetric();\n\t\t}\n\t}\n\n\tprivate _updateActiveConnectionsMetric(): void {\n\t\tthis._metrics?.gauge(\n\t\t\t'sse_active_connections',\n\t\t\tthis._clients.size,\n\t\t\t{},\n\t\t\t'Current active SSE connections'\n\t\t);\n\t}\n\n\tprivate _updatePoolMetrics(): void {\n\t\tif (!this._connectionPool || !this._metrics) {\n\t\t\treturn;\n\t\t}\n\t\tconst stats = this._connectionPool.getStats();\n\t\tthis._metrics.gauge(\n\t\t\t'sse_pool_active_sessions',\n\t\t\tstats.activeSessions,\n\t\t\t{},\n\t\t\t'Active sessions in connection pool'\n\t\t);\n\t\tthis._metrics.gauge(\n\t\t\t'sse_pool_total_sessions',\n\t\t\tstats.totalSessions,\n\t\t\t{},\n\t\t\t'Total sessions in connection pool'\n\t\t);\n\t\tthis._metrics.gauge(\n\t\t\t'sse_pool_max_sessions',\n\t\t\tstats.maxSessions,\n\t\t\t{},\n\t\t\t'Maximum sessions in connection pool'\n\t\t);\n\t}\n\n\t/**\n\t * Broadcast a message to all connected clients\n\t */\n\tbroadcast(event: string, data: unknown): void {\n\t\tfor (const client of this._clients) {\n\t\t\tthis._sendSseEvent(client, event, data);\n\t\t}\n\t}\n\n\t/**\n\t * Generate a unique client ID\n\t */\n\tprivate _generateClientId(): string {\n\t\treturn `client_${Date.now()}_${Math.random().toString(36).substring(2, 11)}`;\n\t}\n\n\t/**\n\t * Get number of connected clients\n\t */\n\tget clientCount(): number {\n\t\treturn this._clients.size;\n\t}\n\n\t/**\n\t * Get the connection pool, if one was configured.\n\t */\n\tget connectionPool(): ConnectionPool | undefined {\n\t\treturn this._connectionPool;\n\t}\n\n\t/**\n\t * Stop the transport server with graceful shutdown.\n\t *\n\t * @param timeout - Maximum time to wait for requests to drain (not used for SSE)\n\t * @returns Promise that resolves when shutdown is complete\n\t */\n\tasync stop(_timeout?: number): Promise<void> {\n\t\tthis._isShuttingDown = true;\n\t\tthis._stopRateLimitCleanup();\n\n\t\t// Terminate connection pool if present\n\t\tif (this._connectionPool) {\n\t\t\tawait this._connectionPool.terminate();\n\t\t}\n\n\t\treturn new Promise((resolve) => {\n\t\t\t// Close all client connections\n\t\t\tfor (const client of this._clients) {\n\t\t\t\ttry {\n\t\t\t\t\tclient.end();\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore errors\n\t\t\t\t}\n\t\t\t}\n\t\t\tthis._clients.clear();\n\t\t\tthis._clientSessionMap.clear();\n\t\t\tthis._updateActiveConnectionsMetric();\n\n\t\t\t// Close server\n\t\t\tthis._server.close(() => {\n\t\t\t\tthis.log('info', 'SSE transport stopped');\n\t\t\t\tresolve();\n\t\t\t});\n\t\t});\n\t}\n}\n\n/**\n * Create an SSE transport with given options.\n *\n * @param options - Transport configuration\n * @returns A configured SSE transport\n *\n * @example\n * ```typescript\n * const transport = createSseTransport({ port: 3000 });\n * await transport.connect(mcpServer);\n * ```\n */\nexport function createSseTransport(options: SseTransportOptions = {}): SseTransport {\n\treturn new SseTransport(options);\n}\n"],"names":["SseTransport","BaseTransport","Set","Map","options","createServer","req","res","mcpServer","Promise","resolve","startTime","Date","requestPath","requestMethod","durationSeconds","JSON","url","URL","clientIp","sanitizedParams","sessionId","healthData","poolStats","liveness","readiness","statusCode","params","requestedSession","error","Error","connectedPayload","clientId","queued","message","_params","body","chunk","jsonRpcRequest","parseResult","safeParse","JsonRpcRequestSchema","owner","randomUUID","response","runWithContext","event","data","stats","client","Math","_timeout","createSseTransport"],"mappings":"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"}
@@ -22,8 +22,9 @@
22
22
  * ```
23
23
  */
24
24
  import type { McpServer } from 'tmcp';
25
- import type { IMetrics } from '../contracts/index.js';
25
+ import type { IMetrics } from '../contracts/interfaces.js';
26
26
  import { BaseTransport, type TransportOptions } from './BaseTransport.js';
27
+ import type { ITransport, TransportKind } from '../contracts/transport.js';
27
28
  /**
28
29
  * MCP Streamable HTTP transport options extending base TransportOptions.
29
30
  */
@@ -104,7 +105,8 @@ export interface StreamableHttpTransportOptions extends TransportOptions {
104
105
  * - 500: Internal Server Error
105
106
  * - 503: Server Not Ready / Shutting Down
106
107
  */
107
- export declare class StreamableHttpTransport extends BaseTransport {
108
+ export declare class StreamableHttpTransport extends BaseTransport implements ITransport {
109
+ get kind(): TransportKind;
108
110
  private _server;
109
111
  private _mcpServer;
110
112
  private _path;
@@ -1 +1 @@
1
- {"version":3,"file":"StreamableHttpTransport.d.ts","sourceRoot":"","sources":["../../src/transport/StreamableHttpTransport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEtC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAGtD,OAAO,EAAE,aAAa,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAG1E;;GAEG;AACH,MAAM,WAAW,8BAA+B,SAAQ,gBAAgB;IACvE;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,OAAO,CAAC,EAAE,QAAQ,CAAC;IAEnB;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,MAAM,CAAC;IAE/B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,MAAM,CAAC;IAElC;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAgBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,qBAAa,uBAAwB,SAAQ,aAAa;IACzD,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,mBAAmB,CAAe;IAC1C,OAAO,CAAC,SAAS,CAAwC;IACzD,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,eAAe,CAAa;IACpC,OAAO,CAAC,qBAAqB,CAAU;IACvC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,CAAW;IAC5B,OAAO,CAAC,gBAAgB,CAAwB;gBAEpC,OAAO,GAAE,8BAAmC;IAaxD;;OAEG;IACH,IAAI,WAAW,IAAI,MAAM,CAExB;IAED;;OAEG;IACH,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;OAEG;IACG,OAAO,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAelD;;OAEG;YACW,cAAc;IAoF5B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IASzB;;;;;;;;OAQG;YACW,cAAc;IAsF5B;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;IAsErB;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAmDvB;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAS/B;;OAEG;IACH,OAAO,CAAC,aAAa;IASrB;;;;;;OAMG;IACH,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI;IAWzE;;OAEG;IACH,OAAO,CAAC,cAAc;IAWtB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAe1B;;OAEG;YACW,qBAAqB;IAkBnC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAoB7B;;;;OAIG;IACG,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAuC3C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,6BAA6B,CAC5C,OAAO,GAAE,8BAAmC,GAC1C,uBAAuB,CAEzB"}
1
+ {"version":3,"file":"StreamableHttpTransport.d.ts","sourceRoot":"","sources":["../../src/transport/StreamableHttpTransport.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;GAsBG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AAEtC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAG3D,OAAO,EAAE,aAAa,EAAE,KAAK,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAI3E;;GAEG;AACH,MAAM,WAAW,8BAA+B,SAAQ,gBAAgB;IACvE;;;OAGG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IAEd;;OAEG;IACH,OAAO,CAAC,EAAE,QAAQ,CAAC;IAEnB;;OAEG;IACH,eAAe,CAAC,EAAE,MAAM,MAAM,CAAC;IAE/B;;;;;OAKG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IAEnB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,MAAM,CAAC;IAElC;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAE9B;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;IAErB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CACxB;AAgBD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AACH,qBAAa,uBAAwB,SAAQ,aAAc,YAAW,UAAU;IAC/E,IAAI,IAAI,IAAI,aAAa,CAA8B;IACvD,OAAO,CAAC,OAAO,CAAuB;IACtC,OAAO,CAAC,UAAU,CAA0B;IAC5C,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,SAAS,CAAU;IAC3B,OAAO,CAAC,mBAAmB,CAAe;IAC1C,OAAO,CAAC,SAAS,CAAwC;IACzD,OAAO,CAAC,aAAa,CAAa;IAClC,OAAO,CAAC,eAAe,CAAa;IACpC,OAAO,CAAC,qBAAqB,CAAU;IACvC,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,eAAe,CAAS;IAChC,OAAO,CAAC,QAAQ,CAAC,CAAW;IAC5B,OAAO,CAAC,gBAAgB,CAAwB;gBAEpC,OAAO,GAAE,8BAAmC;IAaxD;;OAEG;IACH,IAAI,WAAW,IAAI,MAAM,CAExB;IAED;;OAEG;IACH,IAAI,YAAY,IAAI,MAAM,CAEzB;IAED;;OAEG;IACG,OAAO,CAAC,SAAS,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC;IAelD;;OAEG;YACW,cAAc;IAoF5B;;OAEG;IACH,OAAO,CAAC,iBAAiB;IASzB;;;;;;;;OAQG;YACW,cAAc;IA2F5B;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;IAsErB;;;;OAIG;IACH,OAAO,CAAC,eAAe;IAmDvB;;OAEG;IACH,OAAO,CAAC,uBAAuB;IAS/B;;OAEG;IACH,OAAO,CAAC,aAAa;IASrB;;;;;;OAMG;IACH,kBAAkB,CAAC,SAAS,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI;IAWzE;;OAEG;IACH,OAAO,CAAC,cAAc;IAWtB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAe1B;;OAEG;YACW,qBAAqB;IAkBnC;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAoB7B;;;;OAIG;IACG,IAAI,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAuC3C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,6BAA6B,CAC5C,OAAO,GAAE,8BAAmC,GAC1C,uBAAuB,CAEzB"}
@@ -5,7 +5,11 @@ import { getErrorMessage } from "../errors.js";
5
5
  import { JsonRpcRequestSchema } from "../schema.js";
6
6
  import { BaseTransport } from "./BaseTransport.js";
7
7
  import { readRequestBody } from "./HttpHelpers.js";
8
+ import { runWithContext } from "../context/RequestContext.js";
8
9
  class StreamableHttpTransport extends BaseTransport {
10
+ get kind() {
11
+ return 'streamable-http';
12
+ }
9
13
  _server = null;
10
14
  _mcpServer = null;
11
15
  _path;
@@ -54,7 +58,7 @@ class StreamableHttpTransport extends BaseTransport {
54
58
  this._metrics?.histogram('streamable_http_request_duration_seconds', durationSeconds, {});
55
59
  });
56
60
  if (!this.validateHostHeader(req)) return void this._sendJsonRpcError(res, 403, -32000, 'Forbidden - invalid host header');
57
- if (this.isShuttingDown()) return void this._sendJsonRpcError(res, 503, -32603, 'Server is shutting down');
61
+ if (this.isShuttingDown) return void this._sendJsonRpcError(res, 503, -32603, 'Server is shutting down');
58
62
  const clientIp = this.getClientIp(req);
59
63
  if (this.checkRateLimit(clientIp)) {
60
64
  res.setHeader('Retry-After', '60');
@@ -156,9 +160,13 @@ class StreamableHttpTransport extends BaseTransport {
156
160
  this._sendJsonRpcError(res, 503, -32603, 'Server not ready', jsonRpcRequest?.id ?? null);
157
161
  return;
158
162
  }
159
- const response = await this._mcpServer.receive(jsonRpcRequest, {
160
- sessionInfo: {}
161
- });
163
+ const owner = this._stateful ? sessionId ?? randomUUID() : randomUUID();
164
+ const response = await runWithContext({
165
+ requestId: randomUUID(),
166
+ owner
167
+ }, ()=>this._mcpServer.receive(jsonRpcRequest, {
168
+ sessionInfo: {}
169
+ }));
162
170
  clearTimeout(timeout);
163
171
  this._activeRequests--;
164
172
  const responseHeaders = {