@iworkforces/tracelattice 2.0.6 → 2.0.8

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 (67) hide show
  1. package/README.md +28 -1
  2. package/dist/cli.js +296 -196
  3. package/dist/contracts/summary.d.ts +0 -1
  4. package/dist/contracts/summary.d.ts.map +1 -1
  5. package/dist/core/PersistenceRestore.d.ts +1 -1
  6. package/dist/core/PersistenceRestore.d.ts.map +1 -1
  7. package/dist/core/PersistenceRestore.js.map +1 -1
  8. package/dist/core/ThoughtEvaluator.d.ts.map +1 -1
  9. package/dist/core/ThoughtEvaluator.js.map +1 -1
  10. package/dist/core/ThoughtFormatter.js.map +1 -1
  11. package/dist/core/ThoughtProcessor.d.ts.map +1 -1
  12. package/dist/core/ThoughtProcessor.js.map +1 -1
  13. package/dist/core/compression/CompressionService.d.ts +6 -4
  14. package/dist/core/compression/CompressionService.d.ts.map +1 -1
  15. package/dist/core/compression/CompressionService.js +19 -10
  16. package/dist/core/compression/CompressionService.js.map +1 -1
  17. package/dist/core/compression/DehydrationPolicy.d.ts +4 -4
  18. package/dist/core/compression/DehydrationPolicy.d.ts.map +1 -1
  19. package/dist/core/compression/DehydrationPolicy.js +3 -6
  20. package/dist/core/compression/DehydrationPolicy.js.map +1 -1
  21. package/dist/core/compression/InMemorySummaryStore.d.ts +2 -1
  22. package/dist/core/compression/InMemorySummaryStore.d.ts.map +1 -1
  23. package/dist/core/compression/InMemorySummaryStore.js.map +1 -1
  24. package/dist/core/evaluator/Calibrator.d.ts +1 -1
  25. package/dist/core/evaluator/Calibrator.d.ts.map +1 -1
  26. package/dist/core/evaluator/Calibrator.js.map +1 -1
  27. package/dist/core/evaluator/SignalComputer.d.ts +2 -1
  28. package/dist/core/evaluator/SignalComputer.d.ts.map +1 -1
  29. package/dist/core/evaluator/SignalComputer.js.map +1 -1
  30. package/dist/core/evaluator/internals.d.ts +1 -1
  31. package/dist/core/evaluator/internals.d.ts.map +1 -1
  32. package/dist/core/evaluator/internals.js.map +1 -1
  33. package/dist/core/graph/GraphView.d.ts +13 -1
  34. package/dist/core/graph/GraphView.d.ts.map +1 -1
  35. package/dist/core/graph/GraphView.js +34 -0
  36. package/dist/core/graph/GraphView.js.map +1 -1
  37. package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.d.ts +6 -6
  38. package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.d.ts.map +1 -1
  39. package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.js +16 -2
  40. package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.js.map +1 -1
  41. package/dist/core/reasoning/strategies/totScoring.d.ts.map +1 -1
  42. package/dist/core/reasoning/strategies/totScoring.js.map +1 -1
  43. package/dist/core/reasoning.d.ts +1 -3
  44. package/dist/core/reasoning.d.ts.map +1 -1
  45. package/dist/core/thought.d.ts +1 -1
  46. package/dist/core/thought.d.ts.map +1 -1
  47. package/dist/lib.d.ts +20 -6
  48. package/dist/lib.d.ts.map +1 -1
  49. package/dist/lib.js +52 -3
  50. package/dist/lib.js.map +1 -1
  51. package/dist/transport/HttpHelpers.d.ts +4 -1
  52. package/dist/transport/HttpHelpers.d.ts.map +1 -1
  53. package/dist/transport/HttpHelpers.js +61 -11
  54. package/dist/transport/HttpHelpers.js.map +1 -1
  55. package/dist/transport/HttpRequestLifecycle.d.ts +15 -1
  56. package/dist/transport/HttpRequestLifecycle.d.ts.map +1 -1
  57. package/dist/transport/HttpRequestLifecycle.js +66 -4
  58. package/dist/transport/HttpRequestLifecycle.js.map +1 -1
  59. package/dist/transport/HttpTransport.d.ts +2 -0
  60. package/dist/transport/HttpTransport.d.ts.map +1 -1
  61. package/dist/transport/HttpTransport.js +75 -38
  62. package/dist/transport/HttpTransport.js.map +1 -1
  63. package/dist/transport/StreamableHttpTransport.d.ts +17 -29
  64. package/dist/transport/StreamableHttpTransport.d.ts.map +1 -1
  65. package/dist/transport/StreamableHttpTransport.js +153 -124
  66. package/dist/transport/StreamableHttpTransport.js.map +1 -1
  67. package/package.json +4 -4
package/dist/cli.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import * as __rspack_external_valibot from "valibot";
3
3
  import * as __rspack_external_node_crypto_2e7c4b46 from "node:crypto";
4
4
  import * as __rspack_external_node_http_e245b239 from "node:http";
5
+ import * as __rspack_external_node_string_decoder_69b9bd04 from "node:string_decoder";
5
6
  import * as __rspack_external_node_async_hooks_4d8b1b4a from "node:async_hooks";
6
7
  import * as __rspack_external__tmcp_adapter_valibot_0faddfa6 from "@tmcp/adapter-valibot";
7
8
  import * as __rspack_external__tmcp_transport_stdio_98e5584f from "@tmcp/transport-stdio";
@@ -1326,7 +1327,7 @@ __webpack_require__.d(__webpack_exports__, {
1326
1327
 
1327
1328
 
1328
1329
  },
1329
- "E"(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
1330
+ "q"(__unused_rspack_module, __webpack_exports__, __webpack_require__) {
1330
1331
 
1331
1332
  // EXPORTS
1332
1333
  __webpack_require__.d(__webpack_exports__, {
@@ -1779,6 +1780,8 @@ class BaseTransport {
1779
1780
  * instead of casting `'__global__'` ad-hoc.
1780
1781
  */ const GLOBAL_SESSION_ID = asSessionId('__global__');
1781
1782
 
1783
+ ;// CONCATENATED MODULE: external "node:string_decoder"
1784
+
1782
1785
  ;// CONCATENATED MODULE: ./src/transport/HttpHelpers.ts
1783
1786
  /**
1784
1787
  * Shared HTTP helper utilities for MCP transport implementations.
@@ -1788,7 +1791,11 @@ class BaseTransport {
1788
1791
  * HttpTransport and StreamableHttpTransport.
1789
1792
  *
1790
1793
  * @module transport/HttpHelpers
1791
- */ /**
1794
+ */
1795
+ class RequestBodyAbortedError extends Error {
1796
+ name = 'RequestBodyAbortedError';
1797
+ }
1798
+ /**
1792
1799
  * Send a JSON-RPC 2.0 error response.
1793
1800
  *
1794
1801
  * Standardizes error response formatting across all transport implementations.
@@ -1856,23 +1863,118 @@ class BaseTransport {
1856
1863
  * @param req - The incoming HTTP request
1857
1864
  * @param maxBodySize - Maximum allowed body size in bytes (0 = unlimited)
1858
1865
  * @returns The body string, or `null` if the body exceeded the size limit
1859
- */ async function readRequestBody(req, maxBodySize) {
1860
- let body = '';
1861
- let bodySize = 0;
1862
- for await (const chunk of req){
1863
- const chunkStr = typeof chunk === 'string' ? chunk : chunk.toString();
1864
- bodySize += chunkStr.length;
1865
- if (maxBodySize > 0 && bodySize > maxBodySize) {
1866
- return null;
1867
- }
1868
- body += chunkStr;
1869
- }
1870
- return body;
1866
+ */ async function readRequestBody(req, maxBodySize, signal) {
1867
+ return new Promise((resolve, reject)=>{
1868
+ let body = '';
1869
+ let bodySize = 0;
1870
+ let settled = false;
1871
+ const decoder = new __rspack_external_node_string_decoder_69b9bd04.StringDecoder('utf8');
1872
+ const cleanup = ()=>{
1873
+ req.off('data', onData);
1874
+ req.off('end', onEnd);
1875
+ req.off('aborted', onAborted);
1876
+ req.off('close', onAborted);
1877
+ req.off('error', onError);
1878
+ signal?.removeEventListener('abort', onAborted);
1879
+ };
1880
+ const onData = (chunk)=>{
1881
+ if (settled) return;
1882
+ const chunkSize = typeof chunk === 'string' ? Buffer.byteLength(chunk) : chunk.byteLength;
1883
+ bodySize += chunkSize;
1884
+ if (maxBodySize > 0 && bodySize > maxBodySize) {
1885
+ settled = true;
1886
+ cleanup();
1887
+ req.resume();
1888
+ resolve(null);
1889
+ return;
1890
+ }
1891
+ body += typeof chunk === 'string' ? chunk : decoder.write(chunk);
1892
+ };
1893
+ const onEnd = ()=>{
1894
+ if (settled) return;
1895
+ settled = true;
1896
+ cleanup();
1897
+ resolve(body + decoder.end());
1898
+ };
1899
+ const onAborted = ()=>{
1900
+ if (settled) return;
1901
+ settled = true;
1902
+ cleanup();
1903
+ req.resume();
1904
+ reject(new RequestBodyAbortedError());
1905
+ };
1906
+ const onError = (error)=>{
1907
+ if (settled) return;
1908
+ settled = true;
1909
+ cleanup();
1910
+ reject(error);
1911
+ };
1912
+ req.on('data', onData);
1913
+ req.once('end', onEnd);
1914
+ req.once('aborted', onAborted);
1915
+ req.once('close', onAborted);
1916
+ req.once('error', onError);
1917
+ signal?.addEventListener('abort', onAborted, {
1918
+ once: true
1919
+ });
1920
+ if (signal?.aborted) onAborted();
1921
+ });
1871
1922
  }
1872
1923
 
1873
1924
  // EXTERNAL MODULE: ./src/context/RequestContext.ts + 1 modules
1874
1925
  var RequestContext = __webpack_require__("i");
1875
1926
  ;// CONCATENATED MODULE: ./src/transport/HttpRequestLifecycle.ts
1927
+ class TrackedPreDispatchLease {
1928
+ _onRelease;
1929
+ _controller = new AbortController();
1930
+ _settled = Promise.withResolvers();
1931
+ _active = true;
1932
+ _cancellationReason = null;
1933
+ constructor(_onRelease){
1934
+ this._onRelease = _onRelease;
1935
+ }
1936
+ get signal() {
1937
+ return this._controller.signal;
1938
+ }
1939
+ get cancellationReason() {
1940
+ return this._cancellationReason;
1941
+ }
1942
+ get settled() {
1943
+ return this._settled.promise;
1944
+ }
1945
+ cancel(reason) {
1946
+ if (!this._active || this._controller.signal.aborted) return;
1947
+ this._cancellationReason = reason;
1948
+ this._controller.abort();
1949
+ }
1950
+ release() {
1951
+ if (!this._active) return;
1952
+ this._active = false;
1953
+ this._onRelease(this);
1954
+ this._settled.resolve();
1955
+ }
1956
+ }
1957
+ class PreDispatchTracker {
1958
+ _leases = new Set();
1959
+ _accepting = true;
1960
+ acquire() {
1961
+ if (!this._accepting) return null;
1962
+ const lease = new TrackedPreDispatchLease((released)=>this._leases.delete(released));
1963
+ this._leases.add(lease);
1964
+ return lease;
1965
+ }
1966
+ closeAdmission(reason) {
1967
+ this._accepting = false;
1968
+ for (const lease of this._leases)lease.cancel(reason);
1969
+ }
1970
+ async join() {
1971
+ while(this._leases.size > 0){
1972
+ await Promise.all([
1973
+ ...this._leases
1974
+ ].map((lease)=>lease.settled));
1975
+ }
1976
+ }
1977
+ }
1876
1978
  /** Maximum number of reporting failures retained for diagnostics. */ const LIFECYCLE_REPORTING_FAILURE_CAPACITY = 32;
1877
1979
  class LifecycleFailureReporter {
1878
1980
  _onFailure;
@@ -1944,14 +2046,27 @@ class AcceptedWorkTracker {
1944
2046
  get size() {
1945
2047
  return this._work.size;
1946
2048
  }
1947
- track(work) {
2049
+ transfer(lease, start) {
2050
+ lease.signal.throwIfAborted();
2051
+ const reservation = Promise.withResolvers();
2052
+ this._work.add(reservation.promise);
2053
+ lease.release();
2054
+ let work;
2055
+ try {
2056
+ work = start();
2057
+ } catch (error) {
2058
+ this._work.delete(reservation.promise);
2059
+ reservation.resolve();
2060
+ throw error;
2061
+ }
1948
2062
  const observed = work.catch((error)=>{
1949
2063
  this._failureReporter.report(error);
1950
2064
  });
1951
- this._work.add(observed);
1952
2065
  void observed.then(()=>{
1953
- this._work.delete(observed);
2066
+ this._work.delete(reservation.promise);
2067
+ reservation.resolve();
1954
2068
  });
2069
+ return work;
1955
2070
  }
1956
2071
  async join() {
1957
2072
  while(this._work.size > 0){
@@ -1964,12 +2079,8 @@ class AcceptedWorkTracker {
1964
2079
  /**
1965
2080
  * Streamable HTTP Transport implementation (MCP spec recommended transport).
1966
2081
  *
1967
- * This transport implements the MCP Streamable HTTP specification, which replaces
1968
- * the deprecated SSE transport as the recommended HTTP-based transport since March 2025.
1969
- *
1970
2082
  * Key features:
1971
2083
  * - POST /mcp for JSON-RPC requests (main MCP endpoint)
1972
- * - GET /mcp for optional SSE server-to-client notifications
1973
2084
  * - Session management via Mcp-Session-Id header
1974
2085
  * - Supports both stateful (session-based) and stateless (per-request) modes
1975
2086
  * - Health endpoints (/health, /ready)
@@ -1993,12 +2104,25 @@ class AcceptedWorkTracker {
1993
2104
 
1994
2105
 
1995
2106
 
2107
+ function parseSessionRetentionOptions(options) {
2108
+ const { maxSessions, sessionIdleTimeoutMs, sessionSweepIntervalMs } = options;
2109
+ if (maxSessions === undefined && sessionIdleTimeoutMs === undefined && sessionSweepIntervalMs === undefined) {
2110
+ return null;
2111
+ }
2112
+ if (maxSessions === undefined || !Number.isFinite(maxSessions) || !Number.isInteger(maxSessions) || maxSessions <= 0 || sessionIdleTimeoutMs === undefined || !Number.isFinite(sessionIdleTimeoutMs) || !Number.isInteger(sessionIdleTimeoutMs) || sessionIdleTimeoutMs <= 0 || sessionSweepIntervalMs === undefined || !Number.isFinite(sessionSweepIntervalMs) || !Number.isInteger(sessionSweepIntervalMs) || sessionSweepIntervalMs <= 0) {
2113
+ throw new TypeError('maxSessions, sessionIdleTimeoutMs, and sessionSweepIntervalMs must be provided together as positive integers');
2114
+ }
2115
+ return {
2116
+ maxSessions,
2117
+ sessionIdleTimeoutMs,
2118
+ sessionSweepIntervalMs
2119
+ };
2120
+ }
1996
2121
  /**
1997
2122
  * Streamable HTTP Transport for MCP server.
1998
2123
  *
1999
2124
  * This transport implements the MCP Streamable HTTP specification,
2000
- * providing JSON-RPC over HTTP with optional session management
2001
- * and server-to-client SSE notification streams.
2125
+ * providing JSON-RPC over HTTP with optional session management.
2002
2126
  *
2003
2127
  * @remarks
2004
2128
  * **Security Features (inherited from BaseTransport):**
@@ -2010,13 +2134,11 @@ class AcceptedWorkTracker {
2010
2134
  *
2011
2135
  * **MCP Streamable HTTP Spec Compliance:**
2012
2136
  * - POST /mcp — JSON-RPC method calls
2013
- * - GET /mcp — SSE notification stream (server-to-client)
2014
2137
  * - Mcp-Session-Id header for session management
2015
2138
  * - Content-Type: application/json for JSON-RPC responses
2016
- * - Content-Type: text/event-stream for SSE notification streams
2017
2139
  *
2018
2140
  * **HTTP Status Code Mapping:**
2019
- * - 200: Success (JSON-RPC response or SSE stream)
2141
+ * - 200: Success (JSON-RPC response)
2020
2142
  * - 202: Accepted (JSON-RPC notification, no response body)
2021
2143
  * - 204: CORS Preflight (empty body)
2022
2144
  * - 400: Bad Request (invalid JSON, invalid session ID)
@@ -2046,8 +2168,12 @@ class AcceptedWorkTracker {
2046
2168
  _metricsProvider;
2047
2169
  _lifecycleFailureReporter;
2048
2170
  _acceptedWork;
2171
+ _preDispatch = new PreDispatchTracker();
2172
+ _sessionRetention;
2173
+ _sessionSweepIntervalId = null;
2049
2174
  _stopPromise = null;
2050
2175
  constructor(options = {}){
2176
+ const sessionRetention = parseSessionRetentionOptions(options);
2051
2177
  super(options);
2052
2178
  this._path = options.path ?? '/mcp';
2053
2179
  this._stateful = options.stateful ?? true;
@@ -2057,12 +2183,17 @@ class AcceptedWorkTracker {
2057
2183
  this._requestTimeout = options.requestTimeout ?? 30000;
2058
2184
  this._metrics = options.metrics;
2059
2185
  this._metricsProvider = options.metricsProvider ?? null;
2186
+ this._sessionRetention = sessionRetention;
2060
2187
  this._lifecycleFailureReporter = new LifecycleFailureReporter((error)=>{
2061
2188
  this.log('error', 'Streamable HTTP request lifecycle failed', {
2062
2189
  error: (0,errors/* .getErrorMessage */.u1)(error)
2063
2190
  });
2064
2191
  });
2065
2192
  this._acceptedWork = new AcceptedWorkTracker(this._lifecycleFailureReporter);
2193
+ if (this._stateful && sessionRetention) {
2194
+ this._sessionSweepIntervalId = setInterval(()=>this._sweepExpiredSessions(), sessionRetention.sessionSweepIntervalMs);
2195
+ this._sessionSweepIntervalId.unref();
2196
+ }
2066
2197
  }
2067
2198
  _requireMcpServer() {
2068
2199
  if (!this._mcpServer) {
@@ -2120,15 +2251,18 @@ class AcceptedWorkTracker {
2120
2251
  }
2121
2252
  });
2122
2253
  }
2123
- /**
2124
- * Route and handle incoming HTTP requests.
2125
- */ async _handleRequest(req, res) {
2254
+ _recordRequestMetrics(res) {
2126
2255
  const startTime = Date.now();
2127
2256
  this._metrics?.counter('streamable_http_requests_total', 1, {}, 'Total Streamable HTTP transport requests');
2128
2257
  res.once('finish', ()=>{
2129
2258
  const durationSeconds = (Date.now() - startTime) / 1000;
2130
2259
  this._metrics?.histogram('streamable_http_request_duration_seconds', durationSeconds, {});
2131
2260
  });
2261
+ }
2262
+ /**
2263
+ * Route and handle incoming HTTP requests.
2264
+ */ async _handleRequest(req, res) {
2265
+ this._recordRequestMetrics(res);
2132
2266
  // Host validation
2133
2267
  if (!this.validateHostHeader(req)) {
2134
2268
  this._sendJsonRpcError(res, 403, -32000, 'Forbidden - invalid host header');
@@ -2179,16 +2313,20 @@ class AcceptedWorkTracker {
2179
2313
  // MCP endpoint routing
2180
2314
  if (urlPath === this._path) {
2181
2315
  if (req.method === 'POST') {
2182
- this._acceptedWork.track(this._handleMcpPost(req, res));
2183
- return;
2184
- }
2185
- if (req.method === 'GET') {
2186
- this._handleMcpGet(req, res);
2316
+ const lease = this._preDispatch.acquire();
2317
+ if (!lease) {
2318
+ const responseFinalizer = new ResponseFinalizer(res, this._lifecycleFailureReporter);
2319
+ responseFinalizer.finalize((response)=>{
2320
+ this._sendJsonRpcError(response, 503, -32603, 'Server is shutting down');
2321
+ });
2322
+ return;
2323
+ }
2324
+ await this._handleMcpPost(req, res, lease);
2187
2325
  return;
2188
2326
  }
2189
2327
  res.writeHead(405, {
2190
2328
  'Content-Type': 'application/json',
2191
- Allow: 'GET, POST'
2329
+ Allow: 'POST'
2192
2330
  });
2193
2331
  res.end(JSON.stringify({
2194
2332
  jsonrpc: '2.0',
@@ -2222,6 +2360,28 @@ class AcceptedWorkTracker {
2222
2360
  error
2223
2361
  }));
2224
2362
  }
2363
+ _parseJsonRpcRequest(body, responseFinalizer) {
2364
+ let rawBody;
2365
+ try {
2366
+ rawBody = JSON.parse(body);
2367
+ } catch {
2368
+ responseFinalizer.finalize((response)=>{
2369
+ this._sendJsonRpcError(response, 200, -32700, 'Parse error');
2370
+ });
2371
+ return null;
2372
+ }
2373
+ const parseResult = (0,external_valibot_.safeParse)(schema/* .JsonRpcRequestSchema */.Uv, rawBody);
2374
+ const rawId = rawBody && typeof rawBody === 'object' && 'id' in rawBody ? rawBody.id ?? null : null;
2375
+ if (!parseResult.success) {
2376
+ responseFinalizer.finalize((response)=>{
2377
+ this._sendJsonRpcError(response, 200, -32600, 'Invalid Request', rawId, {
2378
+ data: parseResult.issues
2379
+ });
2380
+ });
2381
+ return null;
2382
+ }
2383
+ return parseResult.output;
2384
+ }
2225
2385
  /**
2226
2386
  * Handle POST /mcp — JSON-RPC method calls.
2227
2387
  *
@@ -2230,77 +2390,96 @@ class AcceptedWorkTracker {
2230
2390
  * - Returns Mcp-Session-Id header for new sessions (stateful mode)
2231
2391
  * - Validates Mcp-Session-Id for existing sessions (stateful mode)
2232
2392
  * - Content-Type: application/json for responses
2233
- */ async _handleMcpPost(req, res) {
2393
+ */ async _handleMcpPost(req, res, lease) {
2234
2394
  this._requestCount++;
2235
2395
  this._activeRequests++;
2236
2396
  const responseFinalizer = new ResponseFinalizer(res, this._lifecycleFailureReporter);
2397
+ let acceptedSession = null;
2398
+ const onAborted = ()=>lease.cancel('peer');
2399
+ req.once('aborted', onAborted);
2237
2400
  const timeout = setTimeout(()=>{
2238
2401
  responseFinalizer.finalize((response)=>{
2239
2402
  this._sendJsonRpcError(response, 500, -32603, 'Request timeout');
2240
2403
  });
2404
+ lease.cancel('timeout');
2241
2405
  }, this._requestTimeout);
2242
2406
  try {
2243
2407
  // Read request body with size limit
2244
- const body = await readRequestBody(req, this._bodySizeLimitEnabled ? this._maxBodySize : 0);
2408
+ const body = await readRequestBody(req, this._bodySizeLimitEnabled ? this._maxBodySize : 0, lease.signal);
2245
2409
  if (body === null) {
2246
2410
  responseFinalizer.finalize((response)=>{
2247
2411
  this._sendJsonRpcError(response, 413, -32000, 'Request body too large');
2248
2412
  });
2249
2413
  return;
2250
2414
  }
2251
- // Parse JSON
2252
- let rawBody;
2253
- try {
2254
- rawBody = JSON.parse(body);
2255
- } catch {
2415
+ const jsonRpcRequest = this._parseJsonRpcRequest(body, responseFinalizer);
2416
+ if (jsonRpcRequest === null) return;
2417
+ const session = this._acceptPostSession(req, responseFinalizer);
2418
+ if (!session) return;
2419
+ acceptedSession = session.state;
2420
+ // Check if MCP server is ready
2421
+ if (!this._mcpServer) {
2422
+ const requestId = 'id' in jsonRpcRequest ? jsonRpcRequest.id ?? null : null;
2256
2423
  responseFinalizer.finalize((response)=>{
2257
- this._sendJsonRpcError(response, 200, -32700, 'Parse error');
2424
+ this._sendJsonRpcError(response, 503, -32603, 'Server not ready', requestId);
2258
2425
  });
2259
2426
  return;
2260
2427
  }
2261
- // Validate JSON-RPC schema
2262
- const parseResult = (0,external_valibot_.safeParse)(schema/* .JsonRpcRequestSchema */.Uv, rawBody);
2263
- const rawId = rawBody && typeof rawBody === 'object' && 'id' in rawBody ? rawBody.id ?? null : null;
2264
- if (!parseResult.success) {
2265
- responseFinalizer.finalize((response)=>{
2266
- this._sendJsonRpcError(response, 200, -32600, 'Invalid Request', rawId, {
2267
- data: parseResult.issues
2428
+ req.off('aborted', onAborted);
2429
+ const acceptedWork = this._acceptedWork.transfer(lease, ()=>this._receiveAndFinalize(jsonRpcRequest, session.id, responseFinalizer).catch((error)=>{
2430
+ responseFinalizer.finalize((response)=>{
2431
+ this._sendJsonRpcError(response, 200, -32603, 'Internal error', null, {
2432
+ data: (0,errors/* .getErrorMessage */.u1)(error)
2433
+ });
2268
2434
  });
2269
- });
2270
- return;
2271
- }
2272
- const jsonRpcRequest = parseResult.output;
2273
- // Session management (stateful mode)
2274
- let sessionId;
2275
- if (this._stateful) {
2276
- const sessionResult = this._resolveSession(req);
2277
- if (typeof sessionResult !== 'string') {
2435
+ }));
2436
+ await acceptedWork;
2437
+ } catch (error) {
2438
+ if (lease.signal.aborted) {
2439
+ if (lease.cancellationReason === 'shutdown') {
2278
2440
  responseFinalizer.finalize((response)=>{
2279
- this._sendJsonRpcError(response, sessionResult.statusCode, sessionResult.code, sessionResult.message);
2441
+ this._sendJsonRpcError(response, 503, -32603, 'Server is shutting down');
2280
2442
  });
2281
- return;
2282
2443
  }
2283
- sessionId = sessionResult;
2284
- }
2285
- // Check if MCP server is ready
2286
- if (!this._mcpServer) {
2287
- responseFinalizer.finalize((response)=>{
2288
- this._sendJsonRpcError(response, 503, -32603, 'Server not ready', jsonRpcRequest?.id ?? null);
2289
- });
2290
2444
  return;
2291
2445
  }
2292
- await this._receiveAndFinalize(jsonRpcRequest, sessionId, responseFinalizer);
2293
- } catch (error) {
2294
2446
  responseFinalizer.finalize((response)=>{
2295
2447
  this._sendJsonRpcError(response, 200, -32603, 'Internal error', null, {
2296
2448
  data: (0,errors/* .getErrorMessage */.u1)(error)
2297
2449
  });
2298
2450
  });
2299
2451
  } finally{
2452
+ req.off('aborted', onAborted);
2453
+ lease.release();
2454
+ if (acceptedSession) acceptedSession.acceptedPostCount--;
2300
2455
  clearTimeout(timeout);
2301
2456
  this._activeRequests--;
2302
2457
  }
2303
2458
  }
2459
+ _acceptPostSession(req, responseFinalizer) {
2460
+ if (!this._stateful) return {
2461
+ id: undefined,
2462
+ state: null
2463
+ };
2464
+ if (this.isShuttingDown) {
2465
+ responseFinalizer.finalize((response)=>{
2466
+ this._sendJsonRpcError(response, 503, -32603, 'Server is shutting down');
2467
+ });
2468
+ return null;
2469
+ }
2470
+ const session = this._resolveSession(req);
2471
+ if (!('state' in session)) {
2472
+ responseFinalizer.finalize((response)=>{
2473
+ this._sendJsonRpcError(response, session.statusCode, session.code, session.message);
2474
+ });
2475
+ return null;
2476
+ }
2477
+ session.state.acceptedPostCount++;
2478
+ return {
2479
+ id: session.id,
2480
+ state: session.state
2481
+ };
2482
+ }
2304
2483
  async _receiveAndFinalize(jsonRpcRequest, sessionId, responseFinalizer) {
2305
2484
  const owner = this._stateful ? sessionId ?? (0,__rspack_external_node_crypto_2e7c4b46.randomUUID)() : (0,__rspack_external_node_crypto_2e7c4b46.randomUUID)();
2306
2485
  const response = await (0,RequestContext/* .runWithContext */.Vo)({
@@ -2324,82 +2503,6 @@ class AcceptedWorkTracker {
2324
2503
  httpResponse.end();
2325
2504
  });
2326
2505
  }
2327
- /**
2328
- * Handle GET /mcp — Optional SSE notification stream.
2329
- *
2330
- * Per the MCP Streamable HTTP spec, clients may open a GET request
2331
- * to receive server-initiated notifications as SSE events.
2332
- * Requires a valid Mcp-Session-Id in stateful mode.
2333
- */ _handleMcpGet(req, res) {
2334
- if (!this._stateful) {
2335
- // SSE notification streams require stateful mode
2336
- res.writeHead(405, {
2337
- 'Content-Type': 'application/json',
2338
- Allow: 'POST'
2339
- });
2340
- res.end(JSON.stringify({
2341
- jsonrpc: '2.0',
2342
- id: null,
2343
- error: {
2344
- code: -32601,
2345
- message: 'GET not supported in stateless mode'
2346
- }
2347
- }));
2348
- return;
2349
- }
2350
- // Require Mcp-Session-Id for GET requests
2351
- const sessionId = this._getSessionIdFromHeader(req);
2352
- if (!sessionId) {
2353
- res.writeHead(400, {
2354
- 'Content-Type': 'application/json'
2355
- });
2356
- res.end(JSON.stringify({
2357
- jsonrpc: '2.0',
2358
- id: null,
2359
- error: {
2360
- code: -32600,
2361
- message: 'Missing Mcp-Session-Id header'
2362
- }
2363
- }));
2364
- return;
2365
- }
2366
- const session = this._sessions.get(asSessionId(sessionId));
2367
- if (!session) {
2368
- res.writeHead(404, {
2369
- 'Content-Type': 'application/json'
2370
- });
2371
- res.end(JSON.stringify({
2372
- jsonrpc: '2.0',
2373
- id: null,
2374
- error: {
2375
- code: -32001,
2376
- message: 'Session not found'
2377
- }
2378
- }));
2379
- return;
2380
- }
2381
- // Set SSE headers
2382
- res.writeHead(200, {
2383
- 'Content-Type': 'text/event-stream',
2384
- 'Cache-Control': 'no-cache',
2385
- Connection: 'keep-alive',
2386
- 'Mcp-Session-Id': sessionId
2387
- });
2388
- // Send initial connected event
2389
- this._sendSseEvent(res, 'connected', {
2390
- sessionId,
2391
- timestamp: Date.now()
2392
- });
2393
- // Track this notification stream
2394
- session.notificationStreams.add(res);
2395
- session.lastActivityAt = Date.now();
2396
- this._updateSessionMetrics();
2397
- // Handle client disconnect
2398
- req.on('close', ()=>{
2399
- session.notificationStreams.delete(res);
2400
- this._updateSessionMetrics();
2401
- });
2402
- }
2403
2506
  /**
2404
2507
  * Resolve or create a session for a stateful request.
2405
2508
  *
@@ -2419,7 +2522,10 @@ class AcceptedWorkTracker {
2419
2522
  const session = this._sessions.get(asSessionId(headerSessionId));
2420
2523
  if (session) {
2421
2524
  session.lastActivityAt = Date.now();
2422
- return headerSessionId;
2525
+ return {
2526
+ id: session.id,
2527
+ state: session
2528
+ };
2423
2529
  }
2424
2530
  // Unknown session ID — per spec, return 404
2425
2531
  return {
@@ -2429,17 +2535,48 @@ class AcceptedWorkTracker {
2429
2535
  };
2430
2536
  }
2431
2537
  // No session header — create new session
2432
- const newSessionId = this._sessionIdGenerator();
2538
+ if (this._sessionRetention && this._sessions.size >= this._sessionRetention.maxSessions) {
2539
+ return {
2540
+ statusCode: 503,
2541
+ code: -32000,
2542
+ message: 'Session capacity reached'
2543
+ };
2544
+ }
2545
+ const newSessionId = asSessionId(this._sessionIdGenerator());
2546
+ const now = Date.now();
2433
2547
  const sessionState = {
2434
2548
  id: newSessionId,
2435
- createdAt: Date.now(),
2436
- lastActivityAt: Date.now(),
2437
- notificationStreams: new Set()
2549
+ createdAt: now,
2550
+ lastActivityAt: now,
2551
+ acceptedPostCount: 0
2438
2552
  };
2439
- this._sessions.set(asSessionId(newSessionId), sessionState);
2553
+ this._sessions.set(newSessionId, sessionState);
2440
2554
  this.log('info', `New session created: ${newSessionId}`);
2441
2555
  this._updateSessionMetrics();
2442
- return newSessionId;
2556
+ return {
2557
+ id: newSessionId,
2558
+ state: sessionState
2559
+ };
2560
+ }
2561
+ _sweepExpiredSessions() {
2562
+ if (!this._sessionRetention) return;
2563
+ const now = Date.now();
2564
+ for (const [sessionId, session] of this._sessions){
2565
+ if (session.acceptedPostCount === 0 && now - session.lastActivityAt >= this._sessionRetention.sessionIdleTimeoutMs) {
2566
+ this._disposeSession(sessionId);
2567
+ }
2568
+ }
2569
+ }
2570
+ _disposeSession(sessionId) {
2571
+ if (!this._sessions.delete(sessionId)) return;
2572
+ this._updateSessionMetrics();
2573
+ }
2574
+ _stopSessionSweep() {
2575
+ const interval = this._sessionSweepIntervalId;
2576
+ if (!interval) return;
2577
+ interval.unref();
2578
+ clearInterval(interval);
2579
+ this._sessionSweepIntervalId = null;
2443
2580
  }
2444
2581
  /**
2445
2582
  * Extract Mcp-Session-Id from request headers.
@@ -2450,31 +2587,6 @@ class AcceptedWorkTracker {
2450
2587
  }
2451
2588
  return undefined;
2452
2589
  }
2453
- /**
2454
- * Send an SSE event to a specific client response stream.
2455
- */ _sendSseEvent(res, event, data) {
2456
- try {
2457
- res.write(`event: ${event}\n`);
2458
- res.write(`data: ${JSON.stringify(data)}\n\n`);
2459
- } catch {
2460
- // Client disconnected — ignore
2461
- }
2462
- }
2463
- /**
2464
- * Broadcast a notification to all SSE streams in a given session.
2465
- *
2466
- * @param sessionId - Target session ID
2467
- * @param event - SSE event name
2468
- * @param data - Event payload
2469
- */ broadcastToSession(sessionId, event, data) {
2470
- const session = this._sessions.get(asSessionId(sessionId));
2471
- if (!session) {
2472
- return;
2473
- }
2474
- for (const stream of session.notificationStreams){
2475
- this._sendSseEvent(stream, event, data);
2476
- }
2477
- }
2478
2590
  /**
2479
2591
  * Handle GET /metrics endpoint.
2480
2592
  */ _handleMetrics(res) {
@@ -2533,11 +2645,6 @@ class AcceptedWorkTracker {
2533
2645
  * Update session-related metrics.
2534
2646
  */ _updateSessionMetrics() {
2535
2647
  this._metrics?.gauge('streamable_http_active_sessions', this._sessions.size, {}, 'Active Streamable HTTP sessions');
2536
- let totalStreams = 0;
2537
- for (const session of this._sessions.values()){
2538
- totalStreams += session.notificationStreams.size;
2539
- }
2540
- this._metrics?.gauge('streamable_http_notification_streams', totalStreams, {}, 'Active SSE notification streams');
2541
2648
  }
2542
2649
  /**
2543
2650
  * Stop transport server with graceful shutdown.
@@ -2546,20 +2653,13 @@ class AcceptedWorkTracker {
2546
2653
  */ stop(timeout) {
2547
2654
  if (this._stopPromise) return this._stopPromise;
2548
2655
  this._isShuttingDown = true;
2656
+ this._preDispatch.closeAdmission('shutdown');
2549
2657
  this._stopRateLimitCleanup();
2658
+ this._stopSessionSweep();
2550
2659
  const shutdownTimeout = timeout ?? 30000;
2551
- // Close all SSE notification streams
2552
- for (const session of this._sessions.values()){
2553
- for (const stream of session.notificationStreams){
2554
- try {
2555
- stream.end();
2556
- } catch {
2557
- // Ignore errors
2558
- }
2559
- }
2560
- session.notificationStreams.clear();
2660
+ for (const sessionId of this._sessions.keys()){
2661
+ this._disposeSession(sessionId);
2561
2662
  }
2562
- this._sessions.clear();
2563
2663
  const server = this._server;
2564
2664
  const serverClosed = new Promise((resolve, reject)=>{
2565
2665
  if (!server?.listening) {
@@ -2589,7 +2689,7 @@ class AcceptedWorkTracker {
2589
2689
  });
2590
2690
  const stopPromise = Promise.all([
2591
2691
  serverClosed,
2592
- this._acceptedWork.join()
2692
+ this._preDispatch.join().then(()=>this._acceptedWork.join())
2593
2693
  ]).then(()=>{
2594
2694
  this.log('info', 'Streamable HTTP transport stopped');
2595
2695
  });
@@ -3103,7 +3203,7 @@ async function main() {
3103
3203
  /**
3104
3204
  * Start Streamable HTTP transport (MCP spec recommended)
3105
3205
  */ async function startStreamableHttpTransport(server, thinkingServer, lifecycle) {
3106
- const { StreamableHttpTransport } = await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, "E"));
3206
+ const { StreamableHttpTransport } = await Promise.resolve(/* import() */).then(__webpack_require__.bind(__webpack_require__, "q"));
3107
3207
  const port = parseInt(process.env.STREAMABLE_HTTP_PORT || '9007', 10);
3108
3208
  const host = process.env.STREAMABLE_HTTP_HOST || 'localhost';
3109
3209
  const transportMetrics = thinkingServer.getContainer().resolve('Metrics');