@frontmcp/sdk 0.5.0 → 0.6.0

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 (226) hide show
  1. package/README.md +3 -3
  2. package/package.json +8 -19
  3. package/src/adapter/adapter.instance.js +5 -0
  4. package/src/adapter/adapter.instance.js.map +1 -1
  5. package/src/auth/authorization/authorization.class.d.ts +1 -4
  6. package/src/auth/authorization/authorization.class.js +6 -13
  7. package/src/auth/authorization/authorization.class.js.map +1 -1
  8. package/src/auth/flows/session.verify.flow.d.ts +1 -0
  9. package/src/auth/flows/session.verify.flow.js +11 -1
  10. package/src/auth/flows/session.verify.flow.js.map +1 -1
  11. package/src/auth/flows/well-known.jwks.flow.js +2 -2
  12. package/src/auth/flows/well-known.jwks.flow.js.map +1 -1
  13. package/src/auth/jwks/dev-key-persistence.d.ts +63 -0
  14. package/src/auth/jwks/dev-key-persistence.js +219 -0
  15. package/src/auth/jwks/dev-key-persistence.js.map +1 -0
  16. package/src/auth/jwks/index.d.ts +1 -0
  17. package/src/auth/jwks/index.js +1 -0
  18. package/src/auth/jwks/index.js.map +1 -1
  19. package/src/auth/jwks/jwks.service.d.ts +7 -4
  20. package/src/auth/jwks/jwks.service.js +81 -12
  21. package/src/auth/jwks/jwks.service.js.map +1 -1
  22. package/src/auth/jwks/jwks.types.d.ts +7 -0
  23. package/src/auth/jwks/jwks.types.js.map +1 -1
  24. package/src/auth/machine-id.d.ts +5 -0
  25. package/src/auth/machine-id.js +32 -0
  26. package/src/auth/machine-id.js.map +1 -0
  27. package/src/auth/session/index.d.ts +1 -0
  28. package/src/auth/session/index.js +3 -1
  29. package/src/auth/session/index.js.map +1 -1
  30. package/src/auth/session/record/session.base.js +5 -3
  31. package/src/auth/session/record/session.base.js.map +1 -1
  32. package/src/auth/session/record/session.stateless.d.ts +2 -2
  33. package/src/auth/session/record/session.stateless.js +5 -3
  34. package/src/auth/session/record/session.stateless.js.map +1 -1
  35. package/src/auth/session/redis-session.store.d.ts +64 -0
  36. package/src/auth/session/redis-session.store.js +204 -0
  37. package/src/auth/session/redis-session.store.js.map +1 -0
  38. package/src/auth/session/session.service.d.ts +0 -2
  39. package/src/auth/session/session.service.js +1 -7
  40. package/src/auth/session/session.service.js.map +1 -1
  41. package/src/auth/session/transport-session.manager.js +3 -5
  42. package/src/auth/session/transport-session.manager.js.map +1 -1
  43. package/src/auth/session/transport-session.types.d.ts +4 -0
  44. package/src/auth/session/transport-session.types.js +4 -3
  45. package/src/auth/session/transport-session.types.js.map +1 -1
  46. package/src/auth/session/utils/session-id.utils.d.ts +12 -1
  47. package/src/auth/session/utils/session-id.utils.js +48 -9
  48. package/src/auth/session/utils/session-id.utils.js.map +1 -1
  49. package/src/auth/ui/base-layout.d.ts +0 -8
  50. package/src/auth/ui/base-layout.js +1 -14
  51. package/src/auth/ui/base-layout.js.map +1 -1
  52. package/src/auth/ui/index.d.ts +3 -4
  53. package/src/auth/ui/index.js +10 -11
  54. package/src/auth/ui/index.js.map +1 -1
  55. package/src/auth/ui/{htmx-templates.d.ts → templates.d.ts} +5 -6
  56. package/src/auth/ui/{htmx-templates.js → templates.js} +8 -15
  57. package/src/auth/ui/templates.js.map +1 -0
  58. package/src/common/decorators/decorator-utils.js.map +1 -1
  59. package/src/common/decorators/front-mcp.decorator.js +28 -2
  60. package/src/common/decorators/front-mcp.decorator.js.map +1 -1
  61. package/src/common/index.d.ts +0 -1
  62. package/src/common/index.js +0 -1
  63. package/src/common/index.js.map +1 -1
  64. package/src/common/interfaces/adapter.interface.d.ts +6 -0
  65. package/src/common/interfaces/adapter.interface.js.map +1 -1
  66. package/src/common/interfaces/execution-context.interface.d.ts +52 -3
  67. package/src/common/interfaces/execution-context.interface.js +88 -3
  68. package/src/common/interfaces/execution-context.interface.js.map +1 -1
  69. package/src/common/interfaces/flow.interface.d.ts +13 -0
  70. package/src/common/interfaces/flow.interface.js +24 -0
  71. package/src/common/interfaces/flow.interface.js.map +1 -1
  72. package/src/common/interfaces/server.interface.d.ts +9 -0
  73. package/src/common/interfaces/server.interface.js.map +1 -1
  74. package/src/common/metadata/app.metadata.d.ts +108 -0
  75. package/src/common/metadata/front-mcp.metadata.d.ts +659 -2
  76. package/src/common/metadata/front-mcp.metadata.js +3 -1
  77. package/src/common/metadata/front-mcp.metadata.js.map +1 -1
  78. package/src/common/metadata/provider.metadata.d.ts +14 -0
  79. package/src/common/metadata/provider.metadata.js +18 -2
  80. package/src/common/metadata/provider.metadata.js.map +1 -1
  81. package/src/common/metadata/tool.metadata.d.ts +33 -1
  82. package/src/common/metadata/tool.metadata.js.map +1 -1
  83. package/src/common/migrate/auth-transport.migrate.d.ts +62 -0
  84. package/src/common/migrate/auth-transport.migrate.js +140 -0
  85. package/src/common/migrate/auth-transport.migrate.js.map +1 -0
  86. package/src/common/migrate/index.d.ts +1 -0
  87. package/src/common/migrate/index.js +6 -0
  88. package/src/common/migrate/index.js.map +1 -0
  89. package/src/common/schemas/http-output.schema.d.ts +10 -2
  90. package/src/common/schemas/index.d.ts +1 -0
  91. package/src/common/schemas/index.js +1 -0
  92. package/src/common/schemas/index.js.map +1 -1
  93. package/src/common/schemas/session-header.schema.d.ts +16 -0
  94. package/src/common/schemas/session-header.schema.js +42 -0
  95. package/src/common/schemas/session-header.schema.js.map +1 -0
  96. package/src/common/tokens/front-mcp.tokens.js +3 -1
  97. package/src/common/tokens/front-mcp.tokens.js.map +1 -1
  98. package/src/common/types/options/auth.options.d.ts +233 -3
  99. package/src/common/types/options/auth.options.js +29 -40
  100. package/src/common/types/options/auth.options.js.map +1 -1
  101. package/src/common/types/options/index.d.ts +2 -0
  102. package/src/common/types/options/index.js +2 -0
  103. package/src/common/types/options/index.js.map +1 -1
  104. package/src/common/types/options/redis.options.d.ts +22 -0
  105. package/src/common/types/options/redis.options.js +45 -0
  106. package/src/common/types/options/redis.options.js.map +1 -0
  107. package/src/common/types/options/transport.options.d.ts +84 -0
  108. package/src/common/types/options/transport.options.js +121 -0
  109. package/src/common/types/options/transport.options.js.map +1 -0
  110. package/src/completion/flows/complete.flow.d.ts +17 -2
  111. package/src/context/frontmcp-context-storage.d.ts +94 -0
  112. package/src/context/frontmcp-context-storage.js +183 -0
  113. package/src/context/frontmcp-context-storage.js.map +1 -0
  114. package/src/context/frontmcp-context.d.ts +269 -0
  115. package/src/context/frontmcp-context.js +360 -0
  116. package/src/context/frontmcp-context.js.map +1 -0
  117. package/src/context/frontmcp-context.provider.d.ts +43 -0
  118. package/src/context/frontmcp-context.provider.js +61 -0
  119. package/src/context/frontmcp-context.provider.js.map +1 -0
  120. package/src/context/index.d.ts +34 -0
  121. package/src/context/index.js +64 -0
  122. package/src/context/index.js.map +1 -0
  123. package/src/context/request-context-storage.d.ts +89 -0
  124. package/src/context/request-context-storage.js +183 -0
  125. package/src/context/request-context-storage.js.map +1 -0
  126. package/src/context/request-context.d.ts +184 -0
  127. package/src/context/request-context.js +209 -0
  128. package/src/context/request-context.js.map +1 -0
  129. package/src/context/request-context.provider.d.ts +37 -0
  130. package/src/context/request-context.provider.js +51 -0
  131. package/src/context/request-context.provider.js.map +1 -0
  132. package/src/context/session-key.provider.d.ts +45 -0
  133. package/src/context/session-key.provider.js +65 -0
  134. package/src/context/session-key.provider.js.map +1 -0
  135. package/src/context/trace-context.d.ts +43 -0
  136. package/src/context/trace-context.js +142 -0
  137. package/src/context/trace-context.js.map +1 -0
  138. package/src/errors/index.d.ts +1 -1
  139. package/src/errors/index.js +3 -1
  140. package/src/errors/index.js.map +1 -1
  141. package/src/errors/mcp.error.d.ts +7 -0
  142. package/src/errors/mcp.error.js +11 -1
  143. package/src/errors/mcp.error.js.map +1 -1
  144. package/src/flows/flow.instance.d.ts +16 -0
  145. package/src/flows/flow.instance.js +166 -80
  146. package/src/flows/flow.instance.js.map +1 -1
  147. package/src/flows/flow.registry.d.ts +5 -0
  148. package/src/flows/flow.registry.js +45 -3
  149. package/src/flows/flow.registry.js.map +1 -1
  150. package/src/front-mcp/front-mcp.d.ts +12 -0
  151. package/src/front-mcp/front-mcp.js +22 -3
  152. package/src/front-mcp/front-mcp.js.map +1 -1
  153. package/src/front-mcp/front-mcp.providers.d.ts +266 -1
  154. package/src/front-mcp/front-mcp.providers.js +2 -1
  155. package/src/front-mcp/front-mcp.providers.js.map +1 -1
  156. package/src/front-mcp/serverless-handler.d.ts +28 -0
  157. package/src/front-mcp/serverless-handler.js +61 -0
  158. package/src/front-mcp/serverless-handler.js.map +1 -0
  159. package/src/hooks/hooks.utils.d.ts +1 -1
  160. package/src/hooks/hooks.utils.js +10 -3
  161. package/src/hooks/hooks.utils.js.map +1 -1
  162. package/src/index.d.ts +8 -4
  163. package/src/index.js +20 -1
  164. package/src/index.js.map +1 -1
  165. package/src/logger/instances/instance.logger.js +0 -1
  166. package/src/logger/instances/instance.logger.js.map +1 -1
  167. package/src/logging/flows/set-level.flow.d.ts +17 -2
  168. package/src/notification/notification.service.js +5 -1
  169. package/src/notification/notification.service.js.map +1 -1
  170. package/src/prompt/flows/get-prompt.flow.d.ts +97 -2
  171. package/src/prompt/flows/prompts-list.flow.d.ts +12 -1
  172. package/src/provider/provider.registry.d.ts +97 -5
  173. package/src/provider/provider.registry.js +306 -9
  174. package/src/provider/provider.registry.js.map +1 -1
  175. package/src/provider/provider.types.d.ts +21 -3
  176. package/src/provider/provider.types.js.map +1 -1
  177. package/src/resource/flows/read-resource.flow.d.ts +22 -3
  178. package/src/resource/flows/resource-templates-list.flow.d.ts +20 -1
  179. package/src/resource/flows/resources-list.flow.d.ts +20 -1
  180. package/src/resource/flows/subscribe-resource.flow.d.ts +17 -2
  181. package/src/resource/flows/unsubscribe-resource.flow.d.ts +17 -2
  182. package/src/scope/flows/http.request.flow.js +43 -7
  183. package/src/scope/flows/http.request.flow.js.map +1 -1
  184. package/src/scope/scope.instance.js +12 -5
  185. package/src/scope/scope.instance.js.map +1 -1
  186. package/src/server/adapters/base.host.adapter.d.ts +9 -0
  187. package/src/server/adapters/base.host.adapter.js.map +1 -1
  188. package/src/server/adapters/express.host.adapter.d.ts +12 -0
  189. package/src/server/adapters/express.host.adapter.js +21 -1
  190. package/src/server/adapters/express.host.adapter.js.map +1 -1
  191. package/src/server/server.instance.d.ts +3 -0
  192. package/src/server/server.instance.js +14 -7
  193. package/src/server/server.instance.js.map +1 -1
  194. package/src/tool/flows/call-tool.flow.d.ts +118 -13
  195. package/src/tool/flows/call-tool.flow.js +240 -194
  196. package/src/tool/flows/call-tool.flow.js.map +1 -1
  197. package/src/tool/flows/tools-list.flow.d.ts +25 -11
  198. package/src/tool/flows/tools-list.flow.js +82 -31
  199. package/src/tool/flows/tools-list.flow.js.map +1 -1
  200. package/src/tool/tool.instance.d.ts +1 -4
  201. package/src/transport/adapters/transport.streamable-http.adapter.js +1 -0
  202. package/src/transport/adapters/transport.streamable-http.adapter.js.map +1 -1
  203. package/src/transport/flows/handle.sse.flow.js +9 -2
  204. package/src/transport/flows/handle.sse.flow.js.map +1 -1
  205. package/src/transport/flows/handle.streamable-http.flow.js +63 -6
  206. package/src/transport/flows/handle.streamable-http.flow.js.map +1 -1
  207. package/src/transport/mcp-handlers/complete-request.handler.d.ts +27 -1
  208. package/src/transport/mcp-handlers/get-prompt-request.handler.d.ts +52 -1
  209. package/src/transport/mcp-handlers/index.d.ts +413 -7
  210. package/src/transport/mcp-handlers/initialize-request.handler.js +12 -2
  211. package/src/transport/mcp-handlers/initialize-request.handler.js.map +1 -1
  212. package/src/transport/mcp-handlers/list-prompts-request.handler.d.ts +27 -1
  213. package/src/transport/mcp-handlers/list-resource-templates-request.handler.d.ts +32 -1
  214. package/src/transport/mcp-handlers/list-resources-request.handler.d.ts +32 -1
  215. package/src/transport/mcp-handlers/list-tools-request.handler.d.ts +30 -1
  216. package/src/transport/mcp-handlers/logging-set-level-request.handler.d.ts +20 -0
  217. package/src/transport/mcp-handlers/read-resource-request.handler.d.ts +27 -1
  218. package/src/transport/mcp-handlers/subscribe-request.handler.d.ts +20 -0
  219. package/src/transport/mcp-handlers/unsubscribe-request.handler.d.ts +20 -0
  220. package/src/transport/transport.registry.d.ts +68 -4
  221. package/src/transport/transport.registry.js +313 -11
  222. package/src/transport/transport.registry.js.map +1 -1
  223. package/src/auth/ui/htmx-templates.js.map +0 -1
  224. package/src/common/providers/session.provider.d.ts +0 -13
  225. package/src/common/providers/session.provider.js +0 -27
  226. package/src/common/providers/session.provider.js.map +0 -1
@@ -1,6 +1,7 @@
1
1
  import { Transporter, TransportType } from './transport.types';
2
- import { ServerResponse } from '../common';
2
+ import { ServerResponse, TransportPersistenceConfigInput } from '../common';
3
3
  import { Scope } from '../scope';
4
+ import { StoredSession } from '../auth/session';
4
5
  export declare class TransportService {
5
6
  readonly ready: Promise<void>;
6
7
  private readonly byType;
@@ -12,15 +13,60 @@ export declare class TransportService {
12
13
  * Used to differentiate between "session never initialized" (HTTP 400) and
13
14
  * "session expired/terminated" (HTTP 404) per MCP Spec 2025-11-25.
14
15
  *
15
- * Key: "type:tokenHash:sessionId", Value: creation timestamp
16
+ * Key: JSON-encoded {type, tokenHash, sessionId}, Value: creation timestamp
17
+ * Note: We use JSON instead of colon-delimiter because sessionId can contain colons.
16
18
  */
17
19
  private readonly sessionHistory;
18
20
  private readonly MAX_SESSION_HISTORY;
19
- constructor(scope: Scope);
21
+ /**
22
+ * Redis session store for transport persistence
23
+ * Used to persist session metadata across server restarts
24
+ */
25
+ private sessionStore?;
26
+ /**
27
+ * Transport persistence configuration
28
+ */
29
+ private persistenceConfig?;
30
+ /**
31
+ * Mutex map for preventing concurrent transport creation for the same key.
32
+ * Key: JSON-encoded {t: type, h: tokenHash, s: sessionId}, Value: Promise that resolves when creation completes
33
+ */
34
+ private readonly creationMutex;
35
+ constructor(scope: Scope, persistenceConfig?: TransportPersistenceConfigInput);
20
36
  private initialize;
21
37
  destroy(): Promise<void>;
22
38
  getTransporter(type: TransportType, token: string, sessionId: string): Promise<Transporter | undefined>;
39
+ /**
40
+ * Get stored session from Redis (without creating a transport).
41
+ * Used by flows to check if session exists and can be recreated.
42
+ *
43
+ * @param type - Transport type
44
+ * @param token - Authorization token
45
+ * @param sessionId - Session ID
46
+ * @returns Stored session data if exists and token matches, undefined otherwise
47
+ */
48
+ getStoredSession(type: TransportType, token: string, sessionId: string): Promise<StoredSession | undefined>;
49
+ /**
50
+ * Recreate a transport from stored session data.
51
+ * Must be called with a valid response object to create the actual transport.
52
+ *
53
+ * @param type - Transport type
54
+ * @param token - Authorization token
55
+ * @param sessionId - Session ID
56
+ * @param storedSession - Previously stored session data
57
+ * @param res - Server response object for the new transport
58
+ * @returns The recreated transport
59
+ */
60
+ recreateTransporter(type: TransportType, token: string, sessionId: string, storedSession: StoredSession, res: ServerResponse): Promise<Transporter>;
61
+ /**
62
+ * Internal method to actually recreate the transport (called with mutex protection)
63
+ */
64
+ private doRecreateTransporter;
23
65
  createTransporter(type: TransportType, token: string, sessionId: string, res: ServerResponse): Promise<Transporter>;
66
+ /**
67
+ * Internal method to actually create the transport (called with mutex protection)
68
+ */
69
+ private doCreateTransporter;
24
70
  destroyTransporter(type: TransportType, token: string, sessionId: string, reason?: string): Promise<void>;
25
71
  /**
26
72
  * Get or create a shared singleton transport for anonymous stateless requests.
@@ -37,13 +83,31 @@ export declare class TransportService {
37
83
  * Used to differentiate between "session never initialized" (HTTP 400) and
38
84
  * "session expired/terminated" (HTTP 404) per MCP Spec 2025-11-25.
39
85
  *
86
+ * Note: This is synchronous and only checks local history. For async Redis check,
87
+ * use wasSessionCreatedAsync.
88
+ *
40
89
  * @param type - Transport type (e.g., 'streamable-http', 'sse')
41
90
  * @param token - The authorization token
42
91
  * @param sessionId - The session ID to check
43
- * @returns true if session was ever created, false otherwise
92
+ * @returns true if session was ever created locally, false otherwise
44
93
  */
45
94
  wasSessionCreated(type: TransportType, token: string, sessionId: string): boolean;
95
+ /**
96
+ * Async version that also checks Redis for session existence.
97
+ * Used when we need to check if session was ever created across server restarts.
98
+ */
99
+ wasSessionCreatedAsync(type: TransportType, token: string, sessionId: string): Promise<boolean>;
46
100
  private sha256;
101
+ /**
102
+ * Create a history key from components.
103
+ * Uses JSON encoding to handle sessionIds that contain special characters.
104
+ */
105
+ private makeHistoryKey;
106
+ /**
107
+ * Parse a history key back into components.
108
+ * Returns undefined if the key is malformed.
109
+ */
110
+ private parseHistoryKey;
47
111
  private keyOf;
48
112
  private ensureTypeBucket;
49
113
  private ensureTokenBucket;
@@ -9,6 +9,8 @@ const transport_local_1 = require("./transport.local");
9
9
  const handle_streamable_http_flow_1 = tslib_1.__importDefault(require("./flows/handle.streamable-http.flow"));
10
10
  const handle_sse_flow_1 = tslib_1.__importDefault(require("./flows/handle.sse.flow"));
11
11
  const handle_stateless_http_flow_1 = tslib_1.__importDefault(require("./flows/handle.stateless-http.flow"));
12
+ const session_1 = require("../auth/session");
13
+ const authorization_class_1 = require("../auth/authorization/authorization.class");
12
14
  class TransportService {
13
15
  ready;
14
16
  byType = new Map();
@@ -20,52 +22,280 @@ class TransportService {
20
22
  * Used to differentiate between "session never initialized" (HTTP 400) and
21
23
  * "session expired/terminated" (HTTP 404) per MCP Spec 2025-11-25.
22
24
  *
23
- * Key: "type:tokenHash:sessionId", Value: creation timestamp
25
+ * Key: JSON-encoded {type, tokenHash, sessionId}, Value: creation timestamp
26
+ * Note: We use JSON instead of colon-delimiter because sessionId can contain colons.
24
27
  */
25
28
  sessionHistory = new Map();
26
29
  MAX_SESSION_HISTORY = 10000;
27
- constructor(scope) {
30
+ /**
31
+ * Redis session store for transport persistence
32
+ * Used to persist session metadata across server restarts
33
+ */
34
+ sessionStore;
35
+ /**
36
+ * Transport persistence configuration
37
+ */
38
+ persistenceConfig;
39
+ /**
40
+ * Mutex map for preventing concurrent transport creation for the same key.
41
+ * Key: JSON-encoded {t: type, h: tokenHash, s: sessionId}, Value: Promise that resolves when creation completes
42
+ */
43
+ creationMutex = new Map();
44
+ constructor(scope, persistenceConfig) {
28
45
  this.scope = scope;
46
+ this.persistenceConfig = persistenceConfig;
29
47
  this.distributed = false; // get from scope metadata
30
48
  this.bus = undefined; // get from scope metadata
31
49
  if (this.distributed && !this.bus) {
32
50
  throw new Error('TransportRegistry: distributed=true requires a TransportBus implementation.');
33
51
  }
52
+ // Initialize Redis session store if persistence is enabled
53
+ if (persistenceConfig?.enabled && persistenceConfig.redis) {
54
+ this.sessionStore = new session_1.RedisSessionStore({
55
+ host: persistenceConfig.redis.host,
56
+ port: persistenceConfig.redis.port,
57
+ password: persistenceConfig.redis.password,
58
+ db: persistenceConfig.redis.db,
59
+ tls: persistenceConfig.redis.tls,
60
+ // Note: Uses 'mcp:transport:' prefix (not 'mcp:session:') to separate transport
61
+ // persistence data from authentication session data in the auth module
62
+ keyPrefix: persistenceConfig.redis.keyPrefix ?? 'mcp:transport:',
63
+ defaultTtlMs: persistenceConfig.defaultTtlMs ?? 3600000, // 1 hour default
64
+ }, this.scope.logger.child('RedisSessionStore'));
65
+ this.scope.logger.info('[TransportService] Redis session store initialized for transport persistence');
66
+ }
34
67
  this.ready = this.initialize();
35
68
  }
36
69
  async initialize() {
70
+ // Validate Redis connection if session store is configured
71
+ if (this.sessionStore) {
72
+ const isConnected = await this.sessionStore.ping();
73
+ if (!isConnected) {
74
+ this.scope.logger.error('[TransportService] Failed to connect to Redis - session persistence disabled');
75
+ // Nullify sessionStore to prevent silent failures on all subsequent operations
76
+ // This ensures clean graceful degradation - sessions will only persist in memory
77
+ await this.sessionStore.disconnect().catch(() => void 0);
78
+ this.sessionStore = undefined;
79
+ }
80
+ else {
81
+ this.scope.logger.info('[TransportService] Redis connection validated successfully');
82
+ }
83
+ }
37
84
  await this.scope.registryFlows(handle_streamable_http_flow_1.default, handle_sse_flow_1.default, handle_stateless_http_flow_1.default);
38
85
  }
39
86
  async destroy() {
40
- /* empty */
87
+ // Close Redis connection if it was created
88
+ if (this.sessionStore) {
89
+ await this.sessionStore.disconnect();
90
+ this.scope.logger.info('[TransportService] Redis session store disconnected');
91
+ }
41
92
  }
42
93
  async getTransporter(type, token, sessionId) {
43
94
  const key = this.keyOf(type, token, sessionId);
95
+ // 1. Check local in-memory cache first
44
96
  const local = this.lookupLocal(key);
45
97
  if (local)
46
98
  return local;
99
+ // 2. Check distributed bus (if enabled)
47
100
  if (this.distributed && this.bus) {
48
101
  const location = await this.bus.lookup(key);
49
102
  if (location) {
50
103
  return new transport_remote_1.RemoteTransporter(key, this.bus);
51
104
  }
52
105
  }
106
+ // Note: Redis-stored sessions require recreation via recreateTransporter()
107
+ // Flows should use getStoredSession() to check if session exists in Redis,
108
+ // then call recreateTransporter() with the response object.
53
109
  return undefined;
54
110
  }
111
+ /**
112
+ * Get stored session from Redis (without creating a transport).
113
+ * Used by flows to check if session exists and can be recreated.
114
+ *
115
+ * @param type - Transport type
116
+ * @param token - Authorization token
117
+ * @param sessionId - Session ID
118
+ * @returns Stored session data if exists and token matches, undefined otherwise
119
+ */
120
+ async getStoredSession(type, token, sessionId) {
121
+ if (!this.sessionStore || type !== 'streamable-http')
122
+ return undefined;
123
+ const tokenHash = this.sha256(token);
124
+ const stored = await this.sessionStore.get(sessionId);
125
+ if (!stored)
126
+ return undefined;
127
+ // Verify the token hash matches
128
+ if (stored.authorizationId !== tokenHash) {
129
+ this.scope.logger.warn('[TransportService] Session token mismatch during lookup', {
130
+ sessionId: sessionId.slice(0, 20),
131
+ storedTokenHash: stored.authorizationId.slice(0, 8),
132
+ requestTokenHash: tokenHash.slice(0, 8),
133
+ });
134
+ return undefined;
135
+ }
136
+ return stored;
137
+ }
138
+ /**
139
+ * Recreate a transport from stored session data.
140
+ * Must be called with a valid response object to create the actual transport.
141
+ *
142
+ * @param type - Transport type
143
+ * @param token - Authorization token
144
+ * @param sessionId - Session ID
145
+ * @param storedSession - Previously stored session data
146
+ * @param res - Server response object for the new transport
147
+ * @returns The recreated transport
148
+ */
149
+ async recreateTransporter(type, token, sessionId, storedSession, res) {
150
+ const key = this.keyOf(type, token, sessionId);
151
+ // Check if already recreated in memory
152
+ const existing = this.lookupLocal(key);
153
+ if (existing)
154
+ return existing;
155
+ // Use mutex to prevent concurrent recreation of the same transport
156
+ // Use JSON encoding for mutex key (consistent with history key format, handles colons in sessionId)
157
+ const mutexKey = JSON.stringify({ t: type, h: key.tokenHash, s: sessionId });
158
+ const pendingCreation = this.creationMutex.get(mutexKey);
159
+ if (pendingCreation) {
160
+ // Another request is already recreating this transport - wait for it
161
+ return pendingCreation;
162
+ }
163
+ // Recreate the transport with mutex protection
164
+ const recreationPromise = this.doRecreateTransporter(key, sessionId, storedSession, res);
165
+ this.creationMutex.set(mutexKey, recreationPromise);
166
+ try {
167
+ return await recreationPromise;
168
+ }
169
+ catch (error) {
170
+ // Log recreation errors for debugging
171
+ this.scope.logger.error('[TransportService] Failed to recreate transport from stored session', {
172
+ sessionId: sessionId.slice(0, 20),
173
+ error: error instanceof Error ? { name: error.name, message: error.message } : String(error),
174
+ });
175
+ throw error;
176
+ }
177
+ finally {
178
+ this.creationMutex.delete(mutexKey);
179
+ }
180
+ }
181
+ /**
182
+ * Internal method to actually recreate the transport (called with mutex protection)
183
+ */
184
+ async doRecreateTransporter(key, sessionId, storedSession, res) {
185
+ // Double-check in case another request completed while we were waiting
186
+ const existing = this.lookupLocal(key);
187
+ if (existing)
188
+ return existing;
189
+ this.scope.logger.info('[TransportService] Recreating transport from stored session', {
190
+ sessionId: sessionId.slice(0, 20),
191
+ protocol: storedSession.session.protocol,
192
+ createdAt: storedSession.createdAt,
193
+ });
194
+ // Mark session as recreated in history
195
+ const historyKey = this.makeHistoryKey(key.type, key.tokenHash, sessionId);
196
+ this.sessionHistory.set(historyKey, storedSession.createdAt);
197
+ const sessionStore = this.sessionStore;
198
+ const persistenceConfig = this.persistenceConfig;
199
+ // Create new transport
200
+ const transporter = new transport_local_1.LocalTransporter(this.scope, key, res, () => {
201
+ key.sessionId = sessionId;
202
+ this.evictLocal(key);
203
+ if (this.distributed && this.bus) {
204
+ this.bus.revoke(key).catch(() => void 0);
205
+ }
206
+ // Remove from Redis on dispose
207
+ if (sessionStore) {
208
+ sessionStore.delete(sessionId).catch(() => void 0);
209
+ }
210
+ });
211
+ await transporter.ready();
212
+ this.insertLocal(key, transporter);
213
+ // Update session access time in Redis
214
+ if (sessionStore) {
215
+ const updatedSession = {
216
+ ...storedSession,
217
+ lastAccessedAt: Date.now(),
218
+ };
219
+ sessionStore.set(sessionId, updatedSession, persistenceConfig?.defaultTtlMs).catch((err) => {
220
+ this.scope.logger.warn('[TransportService] Failed to update session in Redis', {
221
+ sessionId: sessionId.slice(0, 20),
222
+ error: err instanceof Error ? err.message : String(err),
223
+ });
224
+ });
225
+ }
226
+ if (this.distributed && this.bus) {
227
+ await this.bus.advertise(key);
228
+ }
229
+ return transporter;
230
+ }
55
231
  async createTransporter(type, token, sessionId, res) {
56
232
  const key = this.keyOf(type, token, sessionId);
233
+ // Check if already exists
234
+ const existing = this.lookupLocal(key);
235
+ if (existing)
236
+ return existing;
237
+ // Use mutex to prevent concurrent creation of the same transport
238
+ // Use JSON encoding for mutex key (consistent with history key format, handles colons in sessionId)
239
+ const mutexKey = JSON.stringify({ t: type, h: key.tokenHash, s: sessionId });
240
+ const pendingCreation = this.creationMutex.get(mutexKey);
241
+ if (pendingCreation) {
242
+ // Another request is already creating this transport - wait for it
243
+ return pendingCreation;
244
+ }
245
+ // Create the transport with mutex protection
246
+ const creationPromise = this.doCreateTransporter(key, sessionId, res, type);
247
+ this.creationMutex.set(mutexKey, creationPromise);
248
+ try {
249
+ return await creationPromise;
250
+ }
251
+ finally {
252
+ this.creationMutex.delete(mutexKey);
253
+ }
254
+ }
255
+ /**
256
+ * Internal method to actually create the transport (called with mutex protection)
257
+ */
258
+ async doCreateTransporter(key, sessionId, res, type) {
259
+ // Double-check in case another request completed while we were waiting
57
260
  const existing = this.lookupLocal(key);
58
261
  if (existing)
59
262
  return existing;
263
+ const sessionStore = this.sessionStore;
264
+ const persistenceConfig = this.persistenceConfig;
60
265
  const transporter = new transport_local_1.LocalTransporter(this.scope, key, res, () => {
61
266
  key.sessionId = sessionId;
62
267
  this.evictLocal(key);
63
268
  if (this.distributed && this.bus) {
64
269
  this.bus.revoke(key).catch(() => void 0);
65
270
  }
271
+ // Remove from Redis on dispose
272
+ if (sessionStore) {
273
+ sessionStore.delete(sessionId).catch(() => void 0);
274
+ }
66
275
  });
67
276
  await transporter.ready();
68
277
  this.insertLocal(key, transporter);
278
+ // Persist session to Redis (streamable-http only for now)
279
+ if (sessionStore && type === 'streamable-http') {
280
+ const storedSession = {
281
+ session: {
282
+ id: sessionId,
283
+ authorizationId: key.tokenHash,
284
+ protocol: 'streamable-http',
285
+ createdAt: Date.now(),
286
+ nodeId: (0, authorization_class_1.getMachineId)(),
287
+ },
288
+ authorizationId: key.tokenHash,
289
+ createdAt: Date.now(),
290
+ lastAccessedAt: Date.now(),
291
+ };
292
+ sessionStore.set(sessionId, storedSession, persistenceConfig?.defaultTtlMs).catch((err) => {
293
+ this.scope.logger.warn('[TransportService] Failed to persist session to Redis', {
294
+ sessionId: sessionId.slice(0, 20),
295
+ error: err instanceof Error ? err.message : String(err),
296
+ });
297
+ });
298
+ }
69
299
  if (this.distributed && this.bus) {
70
300
  await this.bus.advertise(key);
71
301
  }
@@ -138,20 +368,63 @@ class TransportService {
138
368
  * Used to differentiate between "session never initialized" (HTTP 400) and
139
369
  * "session expired/terminated" (HTTP 404) per MCP Spec 2025-11-25.
140
370
  *
371
+ * Note: This is synchronous and only checks local history. For async Redis check,
372
+ * use wasSessionCreatedAsync.
373
+ *
141
374
  * @param type - Transport type (e.g., 'streamable-http', 'sse')
142
375
  * @param token - The authorization token
143
376
  * @param sessionId - The session ID to check
144
- * @returns true if session was ever created, false otherwise
377
+ * @returns true if session was ever created locally, false otherwise
145
378
  */
146
379
  wasSessionCreated(type, token, sessionId) {
147
380
  const tokenHash = this.sha256(token);
148
- const historyKey = `${type}:${tokenHash}:${sessionId}`;
381
+ const historyKey = this.makeHistoryKey(type, tokenHash, sessionId);
149
382
  return this.sessionHistory.has(historyKey);
150
383
  }
384
+ /**
385
+ * Async version that also checks Redis for session existence.
386
+ * Used when we need to check if session was ever created across server restarts.
387
+ */
388
+ async wasSessionCreatedAsync(type, token, sessionId) {
389
+ // Check local history first (fast path)
390
+ if (this.wasSessionCreated(type, token, sessionId)) {
391
+ return true;
392
+ }
393
+ // Check Redis if available - use getStoredSession() to verify token hash
394
+ // (sessionStore.exists() would leak session existence to unauthorized callers)
395
+ if (this.sessionStore && type === 'streamable-http') {
396
+ const stored = await this.getStoredSession(type, token, sessionId);
397
+ return stored !== undefined;
398
+ }
399
+ return false;
400
+ }
151
401
  /* --------------------------------- internals -------------------------------- */
152
402
  sha256(value) {
153
403
  return (0, crypto_1.createHash)('sha256').update(value, 'utf8').digest('hex');
154
404
  }
405
+ /**
406
+ * Create a history key from components.
407
+ * Uses JSON encoding to handle sessionIds that contain special characters.
408
+ */
409
+ makeHistoryKey(type, tokenHash, sessionId) {
410
+ return JSON.stringify({ t: type, h: tokenHash, s: sessionId });
411
+ }
412
+ /**
413
+ * Parse a history key back into components.
414
+ * Returns undefined if the key is malformed.
415
+ */
416
+ parseHistoryKey(key) {
417
+ try {
418
+ const parsed = JSON.parse(key);
419
+ if (typeof parsed.t === 'string' && typeof parsed.h === 'string' && typeof parsed.s === 'string') {
420
+ return { type: parsed.t, tokenHash: parsed.h, sessionId: parsed.s };
421
+ }
422
+ return undefined;
423
+ }
424
+ catch {
425
+ return undefined;
426
+ }
427
+ }
155
428
  keyOf(type, token, sessionId, sessionIdSse) {
156
429
  return {
157
430
  type,
@@ -191,15 +464,44 @@ class TransportService {
191
464
  const tokenBucket = this.ensureTokenBucket(typeBucket, key.tokenHash);
192
465
  tokenBucket.set(key.sessionId, t);
193
466
  // Record session creation in history for HTTP 404 detection
194
- const historyKey = `${key.type}:${key.tokenHash}:${key.sessionId}`;
467
+ const historyKey = this.makeHistoryKey(key.type, key.tokenHash, key.sessionId);
195
468
  this.sessionHistory.set(historyKey, Date.now());
196
- // Evict oldest entries if cache exceeds max size (LRU-like eviction)
469
+ // Evict oldest entries if cache exceeds max size
470
+ // Only evict entries that don't have active transports (to avoid inconsistent state)
197
471
  if (this.sessionHistory.size > this.MAX_SESSION_HISTORY) {
198
472
  const entries = [...this.sessionHistory.entries()].sort((a, b) => a[1] - b[1]);
199
- // Remove oldest 10% of entries
200
- const toEvict = Math.ceil(this.MAX_SESSION_HISTORY * 0.1);
201
- for (let i = 0; i < toEvict && i < entries.length; i++) {
202
- this.sessionHistory.delete(entries[i][0]);
473
+ // Try to remove oldest 10% of entries (skip those with active transports)
474
+ const targetEvictions = Math.ceil(this.MAX_SESSION_HISTORY * 0.1);
475
+ let evicted = 0;
476
+ for (const [histKey] of entries) {
477
+ if (evicted >= targetEvictions)
478
+ break;
479
+ // Parse history key to check if transport still exists
480
+ const parsed = this.parseHistoryKey(histKey);
481
+ if (!parsed) {
482
+ // Invalid key format - safe to evict
483
+ this.sessionHistory.delete(histKey);
484
+ evicted++;
485
+ continue;
486
+ }
487
+ const { type, tokenHash, sessionId } = parsed;
488
+ const typeBucket = this.byType.get(type);
489
+ const tokenBucket = typeBucket?.get(tokenHash);
490
+ const hasActiveTransport = tokenBucket?.has(sessionId) ?? false;
491
+ // Only evict if there's no active transport for this session
492
+ if (!hasActiveTransport) {
493
+ this.sessionHistory.delete(histKey);
494
+ evicted++;
495
+ }
496
+ }
497
+ // Log warning if we couldn't evict enough entries (all have active transports)
498
+ if (evicted < targetEvictions) {
499
+ this.scope.logger.warn('[TransportService] Session history eviction: unable to free target memory', {
500
+ targetEvictions,
501
+ actualEvictions: evicted,
502
+ currentSize: this.sessionHistory.size,
503
+ maxSize: this.MAX_SESSION_HISTORY,
504
+ });
203
505
  }
204
506
  }
205
507
  }
@@ -1 +1 @@
1
- {"version":3,"file":"transport.registry.js","sourceRoot":"","sources":["../../../src/transport/transport.registry.ts"],"names":[],"mappings":"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","sourcesContent":["// server/transport/transport.registry.ts\nimport { createHash } from 'crypto';\nimport {\n TransportBus,\n Transporter,\n TransportKey,\n TransportRegistryBucket,\n TransportTokenBucket,\n TransportType,\n TransportTypeBucket,\n} from './transport.types';\nimport { RemoteTransporter } from './transport.remote';\nimport { LocalTransporter } from './transport.local';\nimport { ServerResponse } from '../common';\nimport { Scope } from '../scope';\nimport HandleStreamableHttpFlow from './flows/handle.streamable-http.flow';\nimport HandleSseFlow from './flows/handle.sse.flow';\nimport HandleStatelessHttpFlow from './flows/handle.stateless-http.flow';\n\nexport class TransportService {\n readonly ready: Promise<void>;\n private readonly byType: TransportRegistryBucket = new Map();\n private readonly distributed: boolean;\n private readonly bus?: TransportBus;\n private readonly scope: Scope;\n\n /**\n * Session history cache for tracking if sessions were ever created.\n * Used to differentiate between \"session never initialized\" (HTTP 400) and\n * \"session expired/terminated\" (HTTP 404) per MCP Spec 2025-11-25.\n *\n * Key: \"type:tokenHash:sessionId\", Value: creation timestamp\n */\n private readonly sessionHistory: Map<string, number> = new Map();\n private readonly MAX_SESSION_HISTORY = 10000;\n\n constructor(scope: Scope) {\n this.scope = scope;\n this.distributed = false; // get from scope metadata\n this.bus = undefined; // get from scope metadata\n if (this.distributed && !this.bus) {\n throw new Error('TransportRegistry: distributed=true requires a TransportBus implementation.');\n }\n\n this.ready = this.initialize();\n }\n\n private async initialize() {\n await this.scope.registryFlows(HandleStreamableHttpFlow, HandleSseFlow, HandleStatelessHttpFlow);\n }\n\n async destroy() {\n /* empty */\n }\n\n async getTransporter(type: TransportType, token: string, sessionId: string): Promise<Transporter | undefined> {\n const key = this.keyOf(type, token, sessionId);\n\n const local = this.lookupLocal(key);\n if (local) return local;\n\n if (this.distributed && this.bus) {\n const location = await this.bus.lookup(key);\n if (location) {\n return new RemoteTransporter(key, this.bus);\n }\n }\n\n return undefined;\n }\n\n async createTransporter(\n type: TransportType,\n token: string,\n sessionId: string,\n res: ServerResponse,\n ): Promise<Transporter> {\n const key = this.keyOf(type, token, sessionId);\n const existing = this.lookupLocal(key);\n if (existing) return existing;\n\n const transporter = new LocalTransporter(this.scope, key, res, () => {\n key.sessionId = sessionId;\n this.evictLocal(key);\n if (this.distributed && this.bus) {\n this.bus.revoke(key).catch(() => void 0);\n }\n });\n\n await transporter.ready();\n\n this.insertLocal(key, transporter);\n\n if (this.distributed && this.bus) {\n await this.bus.advertise(key);\n }\n\n return transporter;\n }\n\n async destroyTransporter(type: TransportType, token: string, sessionId: string, reason?: string): Promise<void> {\n const key = this.keyOf(type, token, sessionId);\n\n const local = this.lookupLocal(key);\n if (local) {\n await local.destroy(reason);\n return;\n }\n\n if (this.distributed && this.bus) {\n const location = await this.bus.lookup(key);\n if (location) {\n await this.bus.destroyRemote(key, reason);\n return;\n }\n }\n\n throw new Error('Invalid session: cannot destroy non-existent transporter.');\n }\n\n /**\n * Get or create a shared singleton transport for anonymous stateless requests.\n * All anonymous requests share the same transport instance.\n */\n async getOrCreateAnonymousStatelessTransport(type: TransportType, res: ServerResponse): Promise<Transporter> {\n const key = this.keyOf(type, '__anonymous__', '__stateless__');\n const existing = this.lookupLocal(key);\n if (existing) return existing;\n\n // Create shared transport for all anonymous requests\n const transporter = new LocalTransporter(this.scope, key, res, () => {\n this.evictLocal(key);\n if (this.distributed && this.bus) {\n this.bus.revoke(key).catch(() => void 0);\n }\n });\n\n await transporter.ready();\n this.insertLocal(key, transporter);\n\n if (this.distributed && this.bus) {\n await this.bus.advertise(key);\n }\n\n return transporter;\n }\n\n /**\n * Get or create a singleton transport for authenticated stateless requests.\n * Each unique token gets its own singleton transport.\n */\n async getOrCreateAuthenticatedStatelessTransport(\n type: TransportType,\n token: string,\n res: ServerResponse,\n ): Promise<Transporter> {\n const key = this.keyOf(type, token, '__stateless__');\n const existing = this.lookupLocal(key);\n if (existing) return existing;\n\n // Create singleton transport for this token\n const transporter = new LocalTransporter(this.scope, key, res, () => {\n this.evictLocal(key);\n if (this.distributed && this.bus) {\n this.bus.revoke(key).catch(() => void 0);\n }\n });\n\n await transporter.ready();\n this.insertLocal(key, transporter);\n\n if (this.distributed && this.bus) {\n await this.bus.advertise(key);\n }\n\n return transporter;\n }\n\n /**\n * Check if a session was ever created (even if it's been terminated/evicted).\n * Used to differentiate between \"session never initialized\" (HTTP 400) and\n * \"session expired/terminated\" (HTTP 404) per MCP Spec 2025-11-25.\n *\n * @param type - Transport type (e.g., 'streamable-http', 'sse')\n * @param token - The authorization token\n * @param sessionId - The session ID to check\n * @returns true if session was ever created, false otherwise\n */\n wasSessionCreated(type: TransportType, token: string, sessionId: string): boolean {\n const tokenHash = this.sha256(token);\n const historyKey = `${type}:${tokenHash}:${sessionId}`;\n return this.sessionHistory.has(historyKey);\n }\n\n /* --------------------------------- internals -------------------------------- */\n\n private sha256(value: string): string {\n return createHash('sha256').update(value, 'utf8').digest('hex');\n }\n\n private keyOf(type: TransportType, token: string, sessionId: string, sessionIdSse?: string): TransportKey {\n return {\n type,\n token,\n tokenHash: this.sha256(token),\n sessionId,\n sessionIdSse,\n };\n }\n\n private ensureTypeBucket(type: TransportType): TransportTypeBucket {\n let bucket = this.byType.get(type);\n if (!bucket) {\n bucket = new Map<string, TransportTokenBucket>();\n this.byType.set(type, bucket);\n }\n return bucket;\n }\n\n private ensureTokenBucket(typeBucket: TransportTypeBucket, tokenHash: string): TransportTokenBucket {\n let bucket = typeBucket.get(tokenHash);\n if (!bucket) {\n bucket = new Map<string, Transporter>();\n typeBucket.set(tokenHash, bucket);\n }\n return bucket;\n }\n\n private lookupLocal(key: TransportKey): Transporter | undefined {\n const typeBucket = this.byType.get(key.type);\n if (!typeBucket) return undefined;\n const tokenBucket = typeBucket.get(key.tokenHash);\n if (!tokenBucket) return undefined;\n return tokenBucket.get(key.sessionId);\n }\n\n private insertLocal(key: TransportKey, t: Transporter): void {\n const typeBucket = this.ensureTypeBucket(key.type);\n const tokenBucket = this.ensureTokenBucket(typeBucket, key.tokenHash);\n tokenBucket.set(key.sessionId, t);\n\n // Record session creation in history for HTTP 404 detection\n const historyKey = `${key.type}:${key.tokenHash}:${key.sessionId}`;\n this.sessionHistory.set(historyKey, Date.now());\n\n // Evict oldest entries if cache exceeds max size (LRU-like eviction)\n if (this.sessionHistory.size > this.MAX_SESSION_HISTORY) {\n const entries = [...this.sessionHistory.entries()].sort((a, b) => a[1] - b[1]);\n // Remove oldest 10% of entries\n const toEvict = Math.ceil(this.MAX_SESSION_HISTORY * 0.1);\n for (let i = 0; i < toEvict && i < entries.length; i++) {\n this.sessionHistory.delete(entries[i][0]);\n }\n }\n }\n\n private evictLocal(key: TransportKey): void {\n const typeBucket = this.byType.get(key.type);\n if (!typeBucket) return;\n const tokenBucket = typeBucket.get(key.tokenHash);\n if (!tokenBucket) return;\n tokenBucket.delete(key.sessionId);\n if (tokenBucket.size === 0) typeBucket.delete(key.tokenHash);\n if (typeBucket.size === 0) this.byType.delete(key.type);\n }\n}\n"]}
1
+ {"version":3,"file":"transport.registry.js","sourceRoot":"","sources":["../../../src/transport/transport.registry.ts"],"names":[],"mappings":"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","sourcesContent":["// server/transport/transport.registry.ts\nimport { createHash } from 'crypto';\nimport {\n TransportBus,\n Transporter,\n TransportKey,\n TransportRegistryBucket,\n TransportTokenBucket,\n TransportType,\n TransportTypeBucket,\n} from './transport.types';\nimport { RemoteTransporter } from './transport.remote';\nimport { LocalTransporter } from './transport.local';\nimport { ServerResponse, TransportPersistenceConfigInput } from '../common';\nimport { Scope } from '../scope';\nimport HandleStreamableHttpFlow from './flows/handle.streamable-http.flow';\nimport HandleSseFlow from './flows/handle.sse.flow';\nimport HandleStatelessHttpFlow from './flows/handle.stateless-http.flow';\nimport { RedisSessionStore, StoredSession } from '../auth/session';\nimport { getMachineId } from '../auth/authorization/authorization.class';\n\nexport class TransportService {\n readonly ready: Promise<void>;\n private readonly byType: TransportRegistryBucket = new Map();\n private readonly distributed: boolean;\n private readonly bus?: TransportBus;\n private readonly scope: Scope;\n\n /**\n * Session history cache for tracking if sessions were ever created.\n * Used to differentiate between \"session never initialized\" (HTTP 400) and\n * \"session expired/terminated\" (HTTP 404) per MCP Spec 2025-11-25.\n *\n * Key: JSON-encoded {type, tokenHash, sessionId}, Value: creation timestamp\n * Note: We use JSON instead of colon-delimiter because sessionId can contain colons.\n */\n private readonly sessionHistory: Map<string, number> = new Map();\n private readonly MAX_SESSION_HISTORY = 10000;\n\n /**\n * Redis session store for transport persistence\n * Used to persist session metadata across server restarts\n */\n private sessionStore?: RedisSessionStore;\n\n /**\n * Transport persistence configuration\n */\n private persistenceConfig?: TransportPersistenceConfigInput;\n\n /**\n * Mutex map for preventing concurrent transport creation for the same key.\n * Key: JSON-encoded {t: type, h: tokenHash, s: sessionId}, Value: Promise that resolves when creation completes\n */\n private readonly creationMutex: Map<string, Promise<Transporter>> = new Map();\n\n constructor(scope: Scope, persistenceConfig?: TransportPersistenceConfigInput) {\n this.scope = scope;\n this.persistenceConfig = persistenceConfig;\n this.distributed = false; // get from scope metadata\n this.bus = undefined; // get from scope metadata\n if (this.distributed && !this.bus) {\n throw new Error('TransportRegistry: distributed=true requires a TransportBus implementation.');\n }\n\n // Initialize Redis session store if persistence is enabled\n if (persistenceConfig?.enabled && persistenceConfig.redis) {\n this.sessionStore = new RedisSessionStore(\n {\n host: persistenceConfig.redis.host,\n port: persistenceConfig.redis.port,\n password: persistenceConfig.redis.password,\n db: persistenceConfig.redis.db,\n tls: persistenceConfig.redis.tls,\n // Note: Uses 'mcp:transport:' prefix (not 'mcp:session:') to separate transport\n // persistence data from authentication session data in the auth module\n keyPrefix: persistenceConfig.redis.keyPrefix ?? 'mcp:transport:',\n defaultTtlMs: persistenceConfig.defaultTtlMs ?? 3600000, // 1 hour default\n },\n this.scope.logger.child('RedisSessionStore'),\n );\n this.scope.logger.info('[TransportService] Redis session store initialized for transport persistence');\n }\n\n this.ready = this.initialize();\n }\n\n private async initialize() {\n // Validate Redis connection if session store is configured\n if (this.sessionStore) {\n const isConnected = await this.sessionStore.ping();\n if (!isConnected) {\n this.scope.logger.error('[TransportService] Failed to connect to Redis - session persistence disabled');\n // Nullify sessionStore to prevent silent failures on all subsequent operations\n // This ensures clean graceful degradation - sessions will only persist in memory\n await this.sessionStore.disconnect().catch(() => void 0);\n this.sessionStore = undefined;\n } else {\n this.scope.logger.info('[TransportService] Redis connection validated successfully');\n }\n }\n\n await this.scope.registryFlows(HandleStreamableHttpFlow, HandleSseFlow, HandleStatelessHttpFlow);\n }\n\n async destroy() {\n // Close Redis connection if it was created\n if (this.sessionStore) {\n await this.sessionStore.disconnect();\n this.scope.logger.info('[TransportService] Redis session store disconnected');\n }\n }\n\n async getTransporter(type: TransportType, token: string, sessionId: string): Promise<Transporter | undefined> {\n const key = this.keyOf(type, token, sessionId);\n\n // 1. Check local in-memory cache first\n const local = this.lookupLocal(key);\n if (local) return local;\n\n // 2. Check distributed bus (if enabled)\n if (this.distributed && this.bus) {\n const location = await this.bus.lookup(key);\n if (location) {\n return new RemoteTransporter(key, this.bus);\n }\n }\n\n // Note: Redis-stored sessions require recreation via recreateTransporter()\n // Flows should use getStoredSession() to check if session exists in Redis,\n // then call recreateTransporter() with the response object.\n\n return undefined;\n }\n\n /**\n * Get stored session from Redis (without creating a transport).\n * Used by flows to check if session exists and can be recreated.\n *\n * @param type - Transport type\n * @param token - Authorization token\n * @param sessionId - Session ID\n * @returns Stored session data if exists and token matches, undefined otherwise\n */\n async getStoredSession(type: TransportType, token: string, sessionId: string): Promise<StoredSession | undefined> {\n if (!this.sessionStore || type !== 'streamable-http') return undefined;\n\n const tokenHash = this.sha256(token);\n const stored = await this.sessionStore.get(sessionId);\n if (!stored) return undefined;\n\n // Verify the token hash matches\n if (stored.authorizationId !== tokenHash) {\n this.scope.logger.warn('[TransportService] Session token mismatch during lookup', {\n sessionId: sessionId.slice(0, 20),\n storedTokenHash: stored.authorizationId.slice(0, 8),\n requestTokenHash: tokenHash.slice(0, 8),\n });\n return undefined;\n }\n\n return stored;\n }\n\n /**\n * Recreate a transport from stored session data.\n * Must be called with a valid response object to create the actual transport.\n *\n * @param type - Transport type\n * @param token - Authorization token\n * @param sessionId - Session ID\n * @param storedSession - Previously stored session data\n * @param res - Server response object for the new transport\n * @returns The recreated transport\n */\n async recreateTransporter(\n type: TransportType,\n token: string,\n sessionId: string,\n storedSession: StoredSession,\n res: ServerResponse,\n ): Promise<Transporter> {\n const key = this.keyOf(type, token, sessionId);\n\n // Check if already recreated in memory\n const existing = this.lookupLocal(key);\n if (existing) return existing;\n\n // Use mutex to prevent concurrent recreation of the same transport\n // Use JSON encoding for mutex key (consistent with history key format, handles colons in sessionId)\n const mutexKey = JSON.stringify({ t: type, h: key.tokenHash, s: sessionId });\n const pendingCreation = this.creationMutex.get(mutexKey);\n if (pendingCreation) {\n // Another request is already recreating this transport - wait for it\n return pendingCreation;\n }\n\n // Recreate the transport with mutex protection\n const recreationPromise = this.doRecreateTransporter(key, sessionId, storedSession, res);\n this.creationMutex.set(mutexKey, recreationPromise);\n\n try {\n return await recreationPromise;\n } catch (error) {\n // Log recreation errors for debugging\n this.scope.logger.error('[TransportService] Failed to recreate transport from stored session', {\n sessionId: sessionId.slice(0, 20),\n error: error instanceof Error ? { name: error.name, message: error.message } : String(error),\n });\n throw error;\n } finally {\n this.creationMutex.delete(mutexKey);\n }\n }\n\n /**\n * Internal method to actually recreate the transport (called with mutex protection)\n */\n private async doRecreateTransporter(\n key: TransportKey,\n sessionId: string,\n storedSession: StoredSession,\n res: ServerResponse,\n ): Promise<Transporter> {\n // Double-check in case another request completed while we were waiting\n const existing = this.lookupLocal(key);\n if (existing) return existing;\n\n this.scope.logger.info('[TransportService] Recreating transport from stored session', {\n sessionId: sessionId.slice(0, 20),\n protocol: storedSession.session.protocol,\n createdAt: storedSession.createdAt,\n });\n\n // Mark session as recreated in history\n const historyKey = this.makeHistoryKey(key.type, key.tokenHash, sessionId);\n this.sessionHistory.set(historyKey, storedSession.createdAt);\n\n const sessionStore = this.sessionStore;\n const persistenceConfig = this.persistenceConfig;\n\n // Create new transport\n const transporter = new LocalTransporter(this.scope, key, res, () => {\n key.sessionId = sessionId;\n this.evictLocal(key);\n if (this.distributed && this.bus) {\n this.bus.revoke(key).catch(() => void 0);\n }\n // Remove from Redis on dispose\n if (sessionStore) {\n sessionStore.delete(sessionId).catch(() => void 0);\n }\n });\n\n await transporter.ready();\n this.insertLocal(key, transporter);\n\n // Update session access time in Redis\n if (sessionStore) {\n const updatedSession: StoredSession = {\n ...storedSession,\n lastAccessedAt: Date.now(),\n };\n sessionStore.set(sessionId, updatedSession, persistenceConfig?.defaultTtlMs).catch((err) => {\n this.scope.logger.warn('[TransportService] Failed to update session in Redis', {\n sessionId: sessionId.slice(0, 20),\n error: err instanceof Error ? err.message : String(err),\n });\n });\n }\n\n if (this.distributed && this.bus) {\n await this.bus.advertise(key);\n }\n\n return transporter;\n }\n\n async createTransporter(\n type: TransportType,\n token: string,\n sessionId: string,\n res: ServerResponse,\n ): Promise<Transporter> {\n const key = this.keyOf(type, token, sessionId);\n\n // Check if already exists\n const existing = this.lookupLocal(key);\n if (existing) return existing;\n\n // Use mutex to prevent concurrent creation of the same transport\n // Use JSON encoding for mutex key (consistent with history key format, handles colons in sessionId)\n const mutexKey = JSON.stringify({ t: type, h: key.tokenHash, s: sessionId });\n const pendingCreation = this.creationMutex.get(mutexKey);\n if (pendingCreation) {\n // Another request is already creating this transport - wait for it\n return pendingCreation;\n }\n\n // Create the transport with mutex protection\n const creationPromise = this.doCreateTransporter(key, sessionId, res, type);\n this.creationMutex.set(mutexKey, creationPromise);\n\n try {\n return await creationPromise;\n } finally {\n this.creationMutex.delete(mutexKey);\n }\n }\n\n /**\n * Internal method to actually create the transport (called with mutex protection)\n */\n private async doCreateTransporter(\n key: TransportKey,\n sessionId: string,\n res: ServerResponse,\n type: TransportType,\n ): Promise<Transporter> {\n // Double-check in case another request completed while we were waiting\n const existing = this.lookupLocal(key);\n if (existing) return existing;\n\n const sessionStore = this.sessionStore;\n const persistenceConfig = this.persistenceConfig;\n\n const transporter = new LocalTransporter(this.scope, key, res, () => {\n key.sessionId = sessionId;\n this.evictLocal(key);\n if (this.distributed && this.bus) {\n this.bus.revoke(key).catch(() => void 0);\n }\n // Remove from Redis on dispose\n if (sessionStore) {\n sessionStore.delete(sessionId).catch(() => void 0);\n }\n });\n\n await transporter.ready();\n\n this.insertLocal(key, transporter);\n\n // Persist session to Redis (streamable-http only for now)\n if (sessionStore && type === 'streamable-http') {\n const storedSession: StoredSession = {\n session: {\n id: sessionId,\n authorizationId: key.tokenHash,\n protocol: 'streamable-http',\n createdAt: Date.now(),\n nodeId: getMachineId(),\n },\n authorizationId: key.tokenHash,\n createdAt: Date.now(),\n lastAccessedAt: Date.now(),\n };\n sessionStore.set(sessionId, storedSession, persistenceConfig?.defaultTtlMs).catch((err) => {\n this.scope.logger.warn('[TransportService] Failed to persist session to Redis', {\n sessionId: sessionId.slice(0, 20),\n error: err instanceof Error ? err.message : String(err),\n });\n });\n }\n\n if (this.distributed && this.bus) {\n await this.bus.advertise(key);\n }\n\n return transporter;\n }\n\n async destroyTransporter(type: TransportType, token: string, sessionId: string, reason?: string): Promise<void> {\n const key = this.keyOf(type, token, sessionId);\n\n const local = this.lookupLocal(key);\n if (local) {\n await local.destroy(reason);\n return;\n }\n\n if (this.distributed && this.bus) {\n const location = await this.bus.lookup(key);\n if (location) {\n await this.bus.destroyRemote(key, reason);\n return;\n }\n }\n\n throw new Error('Invalid session: cannot destroy non-existent transporter.');\n }\n\n /**\n * Get or create a shared singleton transport for anonymous stateless requests.\n * All anonymous requests share the same transport instance.\n */\n async getOrCreateAnonymousStatelessTransport(type: TransportType, res: ServerResponse): Promise<Transporter> {\n const key = this.keyOf(type, '__anonymous__', '__stateless__');\n const existing = this.lookupLocal(key);\n if (existing) return existing;\n\n // Create shared transport for all anonymous requests\n const transporter = new LocalTransporter(this.scope, key, res, () => {\n this.evictLocal(key);\n if (this.distributed && this.bus) {\n this.bus.revoke(key).catch(() => void 0);\n }\n });\n\n await transporter.ready();\n this.insertLocal(key, transporter);\n\n if (this.distributed && this.bus) {\n await this.bus.advertise(key);\n }\n\n return transporter;\n }\n\n /**\n * Get or create a singleton transport for authenticated stateless requests.\n * Each unique token gets its own singleton transport.\n */\n async getOrCreateAuthenticatedStatelessTransport(\n type: TransportType,\n token: string,\n res: ServerResponse,\n ): Promise<Transporter> {\n const key = this.keyOf(type, token, '__stateless__');\n const existing = this.lookupLocal(key);\n if (existing) return existing;\n\n // Create singleton transport for this token\n const transporter = new LocalTransporter(this.scope, key, res, () => {\n this.evictLocal(key);\n if (this.distributed && this.bus) {\n this.bus.revoke(key).catch(() => void 0);\n }\n });\n\n await transporter.ready();\n this.insertLocal(key, transporter);\n\n if (this.distributed && this.bus) {\n await this.bus.advertise(key);\n }\n\n return transporter;\n }\n\n /**\n * Check if a session was ever created (even if it's been terminated/evicted).\n * Used to differentiate between \"session never initialized\" (HTTP 400) and\n * \"session expired/terminated\" (HTTP 404) per MCP Spec 2025-11-25.\n *\n * Note: This is synchronous and only checks local history. For async Redis check,\n * use wasSessionCreatedAsync.\n *\n * @param type - Transport type (e.g., 'streamable-http', 'sse')\n * @param token - The authorization token\n * @param sessionId - The session ID to check\n * @returns true if session was ever created locally, false otherwise\n */\n wasSessionCreated(type: TransportType, token: string, sessionId: string): boolean {\n const tokenHash = this.sha256(token);\n const historyKey = this.makeHistoryKey(type, tokenHash, sessionId);\n return this.sessionHistory.has(historyKey);\n }\n\n /**\n * Async version that also checks Redis for session existence.\n * Used when we need to check if session was ever created across server restarts.\n */\n async wasSessionCreatedAsync(type: TransportType, token: string, sessionId: string): Promise<boolean> {\n // Check local history first (fast path)\n if (this.wasSessionCreated(type, token, sessionId)) {\n return true;\n }\n\n // Check Redis if available - use getStoredSession() to verify token hash\n // (sessionStore.exists() would leak session existence to unauthorized callers)\n if (this.sessionStore && type === 'streamable-http') {\n const stored = await this.getStoredSession(type, token, sessionId);\n return stored !== undefined;\n }\n\n return false;\n }\n\n /* --------------------------------- internals -------------------------------- */\n\n private sha256(value: string): string {\n return createHash('sha256').update(value, 'utf8').digest('hex');\n }\n\n /**\n * Create a history key from components.\n * Uses JSON encoding to handle sessionIds that contain special characters.\n */\n private makeHistoryKey(type: string, tokenHash: string, sessionId: string): string {\n return JSON.stringify({ t: type, h: tokenHash, s: sessionId });\n }\n\n /**\n * Parse a history key back into components.\n * Returns undefined if the key is malformed.\n */\n private parseHistoryKey(key: string): { type: string; tokenHash: string; sessionId: string } | undefined {\n try {\n const parsed = JSON.parse(key);\n if (typeof parsed.t === 'string' && typeof parsed.h === 'string' && typeof parsed.s === 'string') {\n return { type: parsed.t, tokenHash: parsed.h, sessionId: parsed.s };\n }\n return undefined;\n } catch {\n return undefined;\n }\n }\n\n private keyOf(type: TransportType, token: string, sessionId: string, sessionIdSse?: string): TransportKey {\n return {\n type,\n token,\n tokenHash: this.sha256(token),\n sessionId,\n sessionIdSse,\n };\n }\n\n private ensureTypeBucket(type: TransportType): TransportTypeBucket {\n let bucket = this.byType.get(type);\n if (!bucket) {\n bucket = new Map<string, TransportTokenBucket>();\n this.byType.set(type, bucket);\n }\n return bucket;\n }\n\n private ensureTokenBucket(typeBucket: TransportTypeBucket, tokenHash: string): TransportTokenBucket {\n let bucket = typeBucket.get(tokenHash);\n if (!bucket) {\n bucket = new Map<string, Transporter>();\n typeBucket.set(tokenHash, bucket);\n }\n return bucket;\n }\n\n private lookupLocal(key: TransportKey): Transporter | undefined {\n const typeBucket = this.byType.get(key.type);\n if (!typeBucket) return undefined;\n const tokenBucket = typeBucket.get(key.tokenHash);\n if (!tokenBucket) return undefined;\n return tokenBucket.get(key.sessionId);\n }\n\n private insertLocal(key: TransportKey, t: Transporter): void {\n const typeBucket = this.ensureTypeBucket(key.type);\n const tokenBucket = this.ensureTokenBucket(typeBucket, key.tokenHash);\n tokenBucket.set(key.sessionId, t);\n\n // Record session creation in history for HTTP 404 detection\n const historyKey = this.makeHistoryKey(key.type, key.tokenHash, key.sessionId);\n this.sessionHistory.set(historyKey, Date.now());\n\n // Evict oldest entries if cache exceeds max size\n // Only evict entries that don't have active transports (to avoid inconsistent state)\n if (this.sessionHistory.size > this.MAX_SESSION_HISTORY) {\n const entries = [...this.sessionHistory.entries()].sort((a, b) => a[1] - b[1]);\n // Try to remove oldest 10% of entries (skip those with active transports)\n const targetEvictions = Math.ceil(this.MAX_SESSION_HISTORY * 0.1);\n let evicted = 0;\n\n for (const [histKey] of entries) {\n if (evicted >= targetEvictions) break;\n\n // Parse history key to check if transport still exists\n const parsed = this.parseHistoryKey(histKey);\n if (!parsed) {\n // Invalid key format - safe to evict\n this.sessionHistory.delete(histKey);\n evicted++;\n continue;\n }\n\n const { type, tokenHash, sessionId } = parsed;\n const typeBucket = this.byType.get(type as TransportType);\n const tokenBucket = typeBucket?.get(tokenHash);\n const hasActiveTransport = tokenBucket?.has(sessionId) ?? false;\n\n // Only evict if there's no active transport for this session\n if (!hasActiveTransport) {\n this.sessionHistory.delete(histKey);\n evicted++;\n }\n }\n\n // Log warning if we couldn't evict enough entries (all have active transports)\n if (evicted < targetEvictions) {\n this.scope.logger.warn('[TransportService] Session history eviction: unable to free target memory', {\n targetEvictions,\n actualEvictions: evicted,\n currentSize: this.sessionHistory.size,\n maxSize: this.MAX_SESSION_HISTORY,\n });\n }\n }\n }\n\n private evictLocal(key: TransportKey): void {\n const typeBucket = this.byType.get(key.type);\n if (!typeBucket) return;\n const tokenBucket = typeBucket.get(key.tokenHash);\n if (!tokenBucket) return;\n tokenBucket.delete(key.sessionId);\n if (tokenBucket.size === 0) typeBucket.delete(key.tokenHash);\n if (typeBucket.size === 0) this.byType.delete(key.type);\n }\n}\n"]}