@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.
- package/README.md +28 -1
- package/dist/cli.js +296 -196
- package/dist/contracts/summary.d.ts +0 -1
- package/dist/contracts/summary.d.ts.map +1 -1
- package/dist/core/PersistenceRestore.d.ts +1 -1
- package/dist/core/PersistenceRestore.d.ts.map +1 -1
- package/dist/core/PersistenceRestore.js.map +1 -1
- package/dist/core/ThoughtEvaluator.d.ts.map +1 -1
- package/dist/core/ThoughtEvaluator.js.map +1 -1
- package/dist/core/ThoughtFormatter.js.map +1 -1
- package/dist/core/ThoughtProcessor.d.ts.map +1 -1
- package/dist/core/ThoughtProcessor.js.map +1 -1
- package/dist/core/compression/CompressionService.d.ts +6 -4
- package/dist/core/compression/CompressionService.d.ts.map +1 -1
- package/dist/core/compression/CompressionService.js +19 -10
- package/dist/core/compression/CompressionService.js.map +1 -1
- package/dist/core/compression/DehydrationPolicy.d.ts +4 -4
- package/dist/core/compression/DehydrationPolicy.d.ts.map +1 -1
- package/dist/core/compression/DehydrationPolicy.js +3 -6
- package/dist/core/compression/DehydrationPolicy.js.map +1 -1
- package/dist/core/compression/InMemorySummaryStore.d.ts +2 -1
- package/dist/core/compression/InMemorySummaryStore.d.ts.map +1 -1
- package/dist/core/compression/InMemorySummaryStore.js.map +1 -1
- package/dist/core/evaluator/Calibrator.d.ts +1 -1
- package/dist/core/evaluator/Calibrator.d.ts.map +1 -1
- package/dist/core/evaluator/Calibrator.js.map +1 -1
- package/dist/core/evaluator/SignalComputer.d.ts +2 -1
- package/dist/core/evaluator/SignalComputer.d.ts.map +1 -1
- package/dist/core/evaluator/SignalComputer.js.map +1 -1
- package/dist/core/evaluator/internals.d.ts +1 -1
- package/dist/core/evaluator/internals.d.ts.map +1 -1
- package/dist/core/evaluator/internals.js.map +1 -1
- package/dist/core/graph/GraphView.d.ts +13 -1
- package/dist/core/graph/GraphView.d.ts.map +1 -1
- package/dist/core/graph/GraphView.js +34 -0
- package/dist/core/graph/GraphView.js.map +1 -1
- package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.d.ts +6 -6
- package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.d.ts.map +1 -1
- package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.js +16 -2
- package/dist/core/reasoning/strategies/TreeOfThoughtStrategy.js.map +1 -1
- package/dist/core/reasoning/strategies/totScoring.d.ts.map +1 -1
- package/dist/core/reasoning/strategies/totScoring.js.map +1 -1
- package/dist/core/reasoning.d.ts +1 -3
- package/dist/core/reasoning.d.ts.map +1 -1
- package/dist/core/thought.d.ts +1 -1
- package/dist/core/thought.d.ts.map +1 -1
- package/dist/lib.d.ts +20 -6
- package/dist/lib.d.ts.map +1 -1
- package/dist/lib.js +52 -3
- package/dist/lib.js.map +1 -1
- package/dist/transport/HttpHelpers.d.ts +4 -1
- package/dist/transport/HttpHelpers.d.ts.map +1 -1
- package/dist/transport/HttpHelpers.js +61 -11
- package/dist/transport/HttpHelpers.js.map +1 -1
- package/dist/transport/HttpRequestLifecycle.d.ts +15 -1
- package/dist/transport/HttpRequestLifecycle.d.ts.map +1 -1
- package/dist/transport/HttpRequestLifecycle.js +66 -4
- package/dist/transport/HttpRequestLifecycle.js.map +1 -1
- package/dist/transport/HttpTransport.d.ts +2 -0
- package/dist/transport/HttpTransport.d.ts.map +1 -1
- package/dist/transport/HttpTransport.js +75 -38
- package/dist/transport/HttpTransport.js.map +1 -1
- package/dist/transport/StreamableHttpTransport.d.ts +17 -29
- package/dist/transport/StreamableHttpTransport.d.ts.map +1 -1
- package/dist/transport/StreamableHttpTransport.js +153 -124
- package/dist/transport/StreamableHttpTransport.js.map +1 -1
- package/package.json +4 -4
|
@@ -6,7 +6,7 @@ import { JsonRpcRequestSchema } from "../schema.js";
|
|
|
6
6
|
import { BaseTransport } from "./BaseTransport.js";
|
|
7
7
|
import { readRequestBody, sendCorsPreflight, sendJsonRpcError, sendJsonRpcResponse } from "./HttpHelpers.js";
|
|
8
8
|
import { runWithContext } from "../context/RequestContext.js";
|
|
9
|
-
import { AcceptedWorkTracker, LifecycleFailureReporter, ResponseFinalizer } from "./HttpRequestLifecycle.js";
|
|
9
|
+
import { AcceptedWorkTracker, LifecycleFailureReporter, PreDispatchTracker, ResponseFinalizer } from "./HttpRequestLifecycle.js";
|
|
10
10
|
class HttpTransport extends BaseTransport {
|
|
11
11
|
get kind() {
|
|
12
12
|
return 'http';
|
|
@@ -23,6 +23,7 @@ class HttpTransport extends BaseTransport {
|
|
|
23
23
|
_metricsProvider;
|
|
24
24
|
_lifecycleFailureReporter;
|
|
25
25
|
_acceptedWork;
|
|
26
|
+
_preDispatch = new PreDispatchTracker();
|
|
26
27
|
_stopPromise = null;
|
|
27
28
|
constructor(options = {}){
|
|
28
29
|
super(options);
|
|
@@ -88,6 +89,28 @@ class HttpTransport extends BaseTransport {
|
|
|
88
89
|
error_type: errorType
|
|
89
90
|
}, 'Total HTTP request errors');
|
|
90
91
|
}
|
|
92
|
+
_parseJsonRpcRequest(body, responseFinalizer) {
|
|
93
|
+
let rawBody;
|
|
94
|
+
try {
|
|
95
|
+
rawBody = JSON.parse(body);
|
|
96
|
+
} catch {
|
|
97
|
+
this._trackError('parse_error');
|
|
98
|
+
responseFinalizer.finalize((response)=>{
|
|
99
|
+
sendJsonRpcError(response, 200, -32700, 'Parse error');
|
|
100
|
+
});
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
const parseResult = safeParse(JsonRpcRequestSchema, rawBody);
|
|
104
|
+
const rawId = rawBody && 'object' == typeof rawBody && 'id' in rawBody ? rawBody.id ?? null : null;
|
|
105
|
+
if (!parseResult.success) {
|
|
106
|
+
this._trackError('validation');
|
|
107
|
+
responseFinalizer.finalize((response)=>{
|
|
108
|
+
sendJsonRpcError(response, 200, -32600, 'Invalid Request', rawId, parseResult.issues);
|
|
109
|
+
});
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
return parseResult.output;
|
|
113
|
+
}
|
|
91
114
|
async _handleRequest(req, res) {
|
|
92
115
|
const startTime = Date.now();
|
|
93
116
|
const requestPath = req.url || '/';
|
|
@@ -135,23 +158,37 @@ class HttpTransport extends BaseTransport {
|
|
|
135
158
|
requests: this._requestCount
|
|
136
159
|
});
|
|
137
160
|
if ('GET' === req.method && '/ready' === req.url) return this.handleReadinessEndpoint(res);
|
|
138
|
-
if ('POST' === req.method && req.url === this._path)
|
|
161
|
+
if ('POST' === req.method && req.url === this._path) {
|
|
162
|
+
const lease = this._preDispatch.acquire();
|
|
163
|
+
if (!lease) {
|
|
164
|
+
const responseFinalizer = new ResponseFinalizer(res, this._lifecycleFailureReporter);
|
|
165
|
+
responseFinalizer.finalize((response)=>{
|
|
166
|
+
sendJsonRpcError(response, 503, -32603, 'Server is shutting down');
|
|
167
|
+
});
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
await this._handlePostRequest(req, res, lease);
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
139
173
|
this._trackError('not_found');
|
|
140
174
|
sendJsonRpcError(res, 404, -32601, 'Not Found');
|
|
141
175
|
}
|
|
142
|
-
async _handlePostRequest(req, res) {
|
|
176
|
+
async _handlePostRequest(req, res, lease) {
|
|
143
177
|
this._requestCount++;
|
|
144
178
|
this._activeRequests++;
|
|
145
179
|
const responseFinalizer = new ResponseFinalizer(res, this._lifecycleFailureReporter);
|
|
180
|
+
const onAborted = ()=>lease.cancel('peer');
|
|
181
|
+
req.once('aborted', onAborted);
|
|
146
182
|
const timeout = setTimeout(()=>{
|
|
147
183
|
this._trackError('timeout');
|
|
148
184
|
responseFinalizer.finalize((response)=>{
|
|
149
185
|
sendJsonRpcError(response, 500, -32603, 'Request timeout');
|
|
150
186
|
});
|
|
187
|
+
lease.cancel('timeout');
|
|
151
188
|
}, this._requestTimeout);
|
|
152
189
|
try {
|
|
153
190
|
const maxBodySize = this._bodySizeLimitEnabled ? this._maxBodySize : 0;
|
|
154
|
-
const body = await readRequestBody(req, maxBodySize);
|
|
191
|
+
const body = await readRequestBody(req, maxBodySize, lease.signal);
|
|
155
192
|
if (null === body) {
|
|
156
193
|
this._trackError('payload_too_large');
|
|
157
194
|
responseFinalizer.finalize((response)=>{
|
|
@@ -159,53 +196,52 @@ class HttpTransport extends BaseTransport {
|
|
|
159
196
|
});
|
|
160
197
|
return;
|
|
161
198
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
rawBody = JSON.parse(body);
|
|
165
|
-
} catch {
|
|
166
|
-
this._trackError('parse_error');
|
|
167
|
-
responseFinalizer.finalize((response)=>{
|
|
168
|
-
sendJsonRpcError(response, 200, -32700, 'Parse error');
|
|
169
|
-
});
|
|
170
|
-
return;
|
|
171
|
-
}
|
|
172
|
-
const parseResult = safeParse(JsonRpcRequestSchema, rawBody);
|
|
173
|
-
const rawId = rawBody && 'object' == typeof rawBody && 'id' in rawBody ? rawBody.id ?? null : null;
|
|
174
|
-
if (!parseResult.success) {
|
|
175
|
-
this._trackError('validation');
|
|
176
|
-
responseFinalizer.finalize((response)=>{
|
|
177
|
-
sendJsonRpcError(response, 200, -32600, 'Invalid Request', rawId, parseResult.issues);
|
|
178
|
-
});
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
const jsonRpcRequest = parseResult.output;
|
|
199
|
+
const jsonRpcRequest = this._parseJsonRpcRequest(body, responseFinalizer);
|
|
200
|
+
if (null === jsonRpcRequest) return;
|
|
182
201
|
if (!this._mcpServer) {
|
|
183
202
|
this._trackError('server_not_ready');
|
|
203
|
+
const requestId = 'id' in jsonRpcRequest ? jsonRpcRequest.id ?? null : null;
|
|
184
204
|
responseFinalizer.finalize((response)=>{
|
|
185
|
-
sendJsonRpcError(response, 200, -32603, 'Server not ready',
|
|
205
|
+
sendJsonRpcError(response, 200, -32603, 'Server not ready', requestId);
|
|
186
206
|
});
|
|
187
207
|
return;
|
|
188
208
|
}
|
|
209
|
+
req.off('aborted', onAborted);
|
|
189
210
|
const owner = randomUUID();
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
211
|
+
const acceptedWork = this._acceptedWork.transfer(lease, ()=>runWithContext({
|
|
212
|
+
requestId: randomUUID(),
|
|
213
|
+
owner
|
|
214
|
+
}, ()=>this._requireMcpServer().receive(jsonRpcRequest, {
|
|
215
|
+
sessionInfo: {}
|
|
216
|
+
})).then((response)=>{
|
|
217
|
+
responseFinalizer.finalize((httpResponse)=>{
|
|
218
|
+
if (response) sendJsonRpcResponse(httpResponse, response);
|
|
219
|
+
else {
|
|
220
|
+
httpResponse.writeHead(204);
|
|
221
|
+
httpResponse.end();
|
|
222
|
+
}
|
|
223
|
+
});
|
|
224
|
+
}).catch((error)=>{
|
|
225
|
+
this._trackError('internal_error');
|
|
226
|
+
responseFinalizer.finalize((response)=>{
|
|
227
|
+
sendJsonRpcError(response, 200, -32603, 'Internal error', null, getErrorMessage(error));
|
|
228
|
+
});
|
|
195
229
|
}));
|
|
196
|
-
|
|
197
|
-
if (response) sendJsonRpcResponse(httpResponse, response);
|
|
198
|
-
else {
|
|
199
|
-
httpResponse.writeHead(204);
|
|
200
|
-
httpResponse.end();
|
|
201
|
-
}
|
|
202
|
-
});
|
|
230
|
+
await acceptedWork;
|
|
203
231
|
} catch (error) {
|
|
232
|
+
if (lease.signal.aborted) {
|
|
233
|
+
if ('shutdown' === lease.cancellationReason) responseFinalizer.finalize((response)=>{
|
|
234
|
+
sendJsonRpcError(response, 503, -32603, 'Server is shutting down');
|
|
235
|
+
});
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
204
238
|
this._trackError('internal_error');
|
|
205
239
|
responseFinalizer.finalize((response)=>{
|
|
206
240
|
sendJsonRpcError(response, 200, -32603, 'Internal error', null, getErrorMessage(error));
|
|
207
241
|
});
|
|
208
242
|
} finally{
|
|
243
|
+
req.off('aborted', onAborted);
|
|
244
|
+
lease.release();
|
|
209
245
|
clearTimeout(timeout);
|
|
210
246
|
this._activeRequests--;
|
|
211
247
|
}
|
|
@@ -216,6 +252,7 @@ class HttpTransport extends BaseTransport {
|
|
|
216
252
|
stop() {
|
|
217
253
|
if (this._stopPromise) return this._stopPromise;
|
|
218
254
|
this._isShuttingDown = true;
|
|
255
|
+
this._preDispatch.closeAdmission('shutdown');
|
|
219
256
|
this._stopRateLimitCleanup();
|
|
220
257
|
const server = this._server;
|
|
221
258
|
const serverClosed = server.listening ? new Promise((resolve, reject)=>{
|
|
@@ -226,7 +263,7 @@ class HttpTransport extends BaseTransport {
|
|
|
226
263
|
}) : Promise.resolve();
|
|
227
264
|
const stopPromise = Promise.all([
|
|
228
265
|
serverClosed,
|
|
229
|
-
this._acceptedWork.join()
|
|
266
|
+
this._preDispatch.join().then(()=>this._acceptedWork.join())
|
|
230
267
|
]).then(()=>{
|
|
231
268
|
this.log('info', 'HTTP transport stopped');
|
|
232
269
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"transport/HttpTransport.js","sources":["../../src/transport/HttpTransport.ts"],"sourcesContent":["/**\n * HTTP Transport implementation.\n *\n * This transport provides a stateless, REST-like API interface for MCP tool invocations\n * using standard HTTP request-response patterns.\n *\n * @example\n * ```typescript\n * const transport = new HttpTransport({\n * port: 3000,\n * host: 'localhost'\n * });\n * await transport.connect(server);\n * ```\n */\n\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { createServer } from 'node:http';\nimport { randomUUID } from 'node:crypto';\nimport type { McpServer } from 'tmcp';\nimport { safeParse } from 'valibot';\nimport type { IMetrics } from '../contracts/interfaces.js';\nimport { getErrorMessage } from '../errors.js';\nimport { JsonRpcRequestSchema } from '../schema.js';\nimport { BaseTransport, type TransportOptions } from './BaseTransport.js';\nimport type { ITransport, TransportKind } from '../contracts/transport.js';\nimport {\n\treadRequestBody,\n\tsendCorsPreflight,\n\tsendJsonRpcError,\n\tsendJsonRpcResponse,\n} from './HttpHelpers.js';\nimport { runWithContext } from '../context/RequestContext.js';\nimport {\n\tAcceptedWorkTracker,\n\tLifecycleFailureReporter,\n\tResponseFinalizer,\n} from './HttpRequestLifecycle.js';\n\nexport interface HttpTransportOptions extends TransportOptions {\n\t/**\n\t * Path for messages endpoint\n\t * @default '/messages'\n\t */\n\tpath?: string;\n\tmetrics?: IMetrics;\n\tmetricsProvider?: () => string;\n\n\t/**\n\t * Enable request body size limit\n\t * @default true\n\t */\n\tenableBodySizeLimit?: boolean;\n\n\t/**\n\t * Maximum request body size in bytes\n\t * @default 10485760 (10MB)\n\t */\n\tmaxBodySize?: number;\n\n\t/**\n\t * Request timeout in milliseconds\n\t * @default 30000 (30 seconds)\n\t */\n\trequestTimeout?: number;\n}\n\n/**\n * HTTP Transport for MCP server.\n *\n * This transport provides a stateless, REST-like API interface for MCP tool invocations\n * using standard HTTP request-response patterns.\n *\n * @remarks\n * **Security Features:**\n * - Session ID validation (alphanumeric, max 64 chars)\n * - Query parameter sanitization (whitelist allowed keys)\n * - Rate limiting per IP (configurable, default 100 req/min)\n * - CORS origin validation\n * - Request body size limits (configurable, default 10MB)\n * - Request timeout (configurable, default 30s)\n *\n * **Rate Limiting:**\n * - Tracks requests per IP address within a time window\n * - Returns 429 Too Many Requests when limit exceeded\n * - Can be disabled via `enableRateLimit: false`\n *\n * **HTTP Status Code Mapping:**\n * - 200: Success (JSON-RPC response)\n * - 204: CORS Preflight (empty body)\n * - 400: Bad Request\n * - 403: Forbidden (invalid CORS)\n * - 404: Not Found\n * - 413: Payload Too Large\n * - 429: Too Many Requests\n * - 500: Internal Server Error\n * - 503: Server Not Ready\n */\nexport class HttpTransport extends BaseTransport implements ITransport {\n\tget kind(): TransportKind {\n\t\treturn 'http';\n\t}\n\tprivate _server: ReturnType<typeof createServer>;\n\tprivate _mcpServer: McpServer | null = null;\n\tprivate _requestTimeout: number;\n\tprivate _bodySizeLimitEnabled: boolean;\n\tprivate _maxBodySize: number;\n\tprivate _requestCount: number = 0;\n\tprivate _activeRequests: number = 0;\n\tprivate _path: string;\n\tprivate _metrics?: IMetrics;\n\tprivate _metricsProvider: (() => string) | null;\n\tprivate readonly _lifecycleFailureReporter: LifecycleFailureReporter;\n\tprivate readonly _acceptedWork: AcceptedWorkTracker;\n\tprivate _stopPromise: Promise<void> | null = null;\n\n\tconstructor(options: HttpTransportOptions = {}) {\n\t\tsuper(options);\n\n\t\tthis._requestTimeout = options.requestTimeout ?? 30000;\n\t\tthis._bodySizeLimitEnabled = options.enableBodySizeLimit ?? true;\n\t\tthis._maxBodySize = options.maxBodySize ?? 10 * 1024 * 1024;\n\t\tthis._path = options.path ?? '/messages';\n\t\tthis._metrics = options.metrics;\n\t\tthis._metricsProvider = options.metricsProvider ?? null;\n\t\tthis._lifecycleFailureReporter = new LifecycleFailureReporter((error) => {\n\t\t\tthis.log('error', 'HTTP request lifecycle failed', { error: getErrorMessage(error) });\n\t\t});\n\t\tthis._acceptedWork = new AcceptedWorkTracker(this._lifecycleFailureReporter);\n\t\tthis._server = createServer((req, res) => {\n\t\t\tconst responseFinalizer = new ResponseFinalizer(res, this._lifecycleFailureReporter);\n\t\t\tvoid this._handleRequest(req, res).catch((error: unknown) => {\n\t\t\t\tthis._lifecycleFailureReporter.report(error);\n\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\tsendJsonRpcError(response, 500, -32603, 'Internal error', null, getErrorMessage(error));\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\t}\n\n\tprivate _requireMcpServer(): McpServer {\n\t\tif (!this._mcpServer) {\n\t\t\tthrow new Error('MCP server not initialized. Did you call connect()?');\n\t\t}\n\t\treturn this._mcpServer;\n\t}\n\n\t/**\n\t * Get number of active HTTP connections.\n\t */\n\tget clientCount(): number {\n\t\treturn this._activeRequests;\n\t}\n\n\t/**\n\t * Connects MCP server to this transport.\n\t */\n\tasync connect(mcpServer: McpServer): Promise<void> {\n\t\tthis._mcpServer = mcpServer;\n\t\tconst server = this._server;\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst cleanup = (): void => {\n\t\t\t\tserver.off('error', onError);\n\t\t\t\tserver.off('listening', onListening);\n\t\t\t};\n\t\t\tconst onError = (error: Error): void => {\n\t\t\t\tcleanup();\n\t\t\t\treject(error);\n\t\t\t};\n\t\t\tconst onListening = (): void => {\n\t\t\t\tcleanup();\n\t\t\t\tthis.log('info', `HTTP transport listening on http://${this._host}:${this._port}`);\n\t\t\t\tresolve();\n\t\t\t};\n\t\t\tserver.once('error', onError);\n\t\t\tserver.once('listening', onListening);\n\t\t\ttry {\n\t\t\t\tserver.listen(this._port, this._host);\n\t\t\t} catch (error) {\n\t\t\t\tcleanup();\n\t\t\t\treject(error);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Track an error in metrics.\n\t */\n\tprivate _trackError(errorType: string): void {\n\t\tthis._metrics?.counter(\n\t\t\t'http_request_errors_total',\n\t\t\t1,\n\t\t\t{ transport: 'http', error_type: errorType },\n\t\t\t'Total HTTP request errors'\n\t\t);\n\t}\n\n\t/**\n\t * Route and handle incoming HTTP requests.\n\t *\n\t * Performs security checks (host, shutdown, rate limit, CORS) then\n\t * dispatches to the appropriate endpoint handler.\n\t */\n\tprivate async _handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\tconst startTime = Date.now();\n\t\tconst requestPath = req.url || '/';\n\t\tconst requestMethod = req.method || 'GET';\n\t\tthis._metrics?.counter('http_requests_total', 1, {}, 'Total HTTP transport requests');\n\t\tthis._metrics?.counter(\n\t\t\t'http_transport_requests_total',\n\t\t\t1,\n\t\t\t{ transport: 'http', method: requestMethod, path: requestPath },\n\t\t\t'Total HTTP requests by transport'\n\t\t);\n\t\tres.once('finish', () => {\n\t\t\tconst durationSeconds = (Date.now() - startTime) / 1000;\n\t\t\tthis._metrics?.histogram('http_request_duration_seconds', durationSeconds, {});\n\t\t\tthis._metrics?.histogram('http_transport_request_duration_seconds', durationSeconds, {\n\t\t\t\ttransport: 'http',\n\t\t\t\tpath: requestPath,\n\t\t\t});\n\t\t});\n\n\t\t// Security middleware chain\n\t\tif (!this.validateHostHeader(req)) {\n\t\t\tthis._trackError('forbidden');\n\t\t\tsendJsonRpcError(res, 403, -32000, 'Forbidden - invalid host header');\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.isShuttingDown) {\n\t\t\tthis._trackError('shutting_down');\n\t\t\tsendJsonRpcError(res, 503, -32603, 'Server is shutting down');\n\t\t\treturn;\n\t\t}\n\n\t\tconst clientIp = this.getClientIp(req);\n\t\tif (this.checkRateLimit(clientIp)) {\n\t\t\tthis._trackError('rate_limit');\n\t\t\tres.setHeader('Retry-After', '60');\n\t\t\tsendJsonRpcError(res, 429, -32000, 'Too many requests');\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.validateCorsOrigin(req)) {\n\t\t\tthis._trackError('forbidden');\n\t\t\tsendJsonRpcError(res, 403, -32000, 'Forbidden - invalid origin');\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setCorsHeaders(res);\n\n\t\t// Static endpoints\n\t\tif (req.method === 'GET' && req.url === '/metrics')\n\t\t\treturn this.handleMetricsEndpoint(res, this._metricsProvider);\n\t\tif (req.method === 'OPTIONS') return sendCorsPreflight(res);\n\t\tif (req.method === 'GET' && req.url === '/health')\n\t\t\treturn this.handleHealthEndpoint(res, { requests: this._requestCount });\n\t\tif (req.method === 'GET' && req.url === '/ready') return this.handleReadinessEndpoint(res);\n\n\t\t// MCP endpoint\n\t\tif (req.method === 'POST' && req.url === this._path) {\n\t\t\tthis._acceptedWork.track(this._handlePostRequest(req, res));\n\t\t\treturn;\n\t\t}\n\n\t\t// 404\n\t\tthis._trackError('not_found');\n\t\tsendJsonRpcError(res, 404, -32601, 'Not Found');\n\t}\n\n\t/**\n\t * Handle POST to the MCP messages endpoint.\n\t *\n\t * Reads the request body, validates JSON-RPC format, and delegates\n\t * processing to the MCP server.\n\t */\n\tprivate async _handlePostRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\tthis._requestCount++;\n\t\tthis._activeRequests++;\n\t\tconst responseFinalizer = new ResponseFinalizer(res, this._lifecycleFailureReporter);\n\n\t\tconst timeout = setTimeout(() => {\n\t\t\tthis._trackError('timeout');\n\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\tsendJsonRpcError(response, 500, -32603, 'Request timeout');\n\t\t\t});\n\t\t}, this._requestTimeout);\n\n\t\ttry {\n\t\t\tconst maxBodySize = this._bodySizeLimitEnabled ? this._maxBodySize : 0;\n\t\t\tconst body = await readRequestBody(req, maxBodySize);\n\n\t\t\tif (body === null) {\n\t\t\t\tthis._trackError('payload_too_large');\n\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\tsendJsonRpcError(response, 413, -32000, 'Request body too large');\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet rawBody: unknown;\n\t\t\ttry {\n\t\t\t\trawBody = JSON.parse(body) as unknown;\n\t\t\t} catch {\n\t\t\t\tthis._trackError('parse_error');\n\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\tsendJsonRpcError(response, 200, -32700, 'Parse error');\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst parseResult = safeParse(JsonRpcRequestSchema, rawBody);\n\t\t\tconst rawId =\n\t\t\t\trawBody && typeof rawBody === 'object' && 'id' in rawBody\n\t\t\t\t\t? ((rawBody as { id?: unknown }).id ?? null)\n\t\t\t\t\t: null;\n\t\t\tif (!parseResult.success) {\n\t\t\t\tthis._trackError('validation');\n\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\tsendJsonRpcError(\n\t\t\t\t\t\tresponse,\n\t\t\t\t\t\t200,\n\t\t\t\t\t\t-32600,\n\t\t\t\t\t\t'Invalid Request',\n\t\t\t\t\t\trawId as string | number | null,\n\t\t\t\t\t\tparseResult.issues\n\t\t\t\t\t);\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tconst jsonRpcRequest = parseResult.output;\n\n\t\t\tif (!this._mcpServer) {\n\t\t\t\tthis._trackError('server_not_ready');\n\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\tsendJsonRpcError(response, 200, -32603, 'Server not ready', jsonRpcRequest.id ?? null);\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst owner = randomUUID();\n\t\t\tconst response = await runWithContext({ requestId: randomUUID(), owner }, () =>\n\t\t\t\tthis._requireMcpServer().receive(jsonRpcRequest as Parameters<McpServer['receive']>[0], {\n\t\t\t\t\tsessionInfo: {},\n\t\t\t\t})\n\t\t\t);\n\n\t\t\tresponseFinalizer.finalize((httpResponse) => {\n\t\t\t\tif (response) {\n\t\t\t\t\tsendJsonRpcResponse(httpResponse, response);\n\t\t\t\t} else {\n\t\t\t\t\thttpResponse.writeHead(204);\n\t\t\t\t\thttpResponse.end();\n\t\t\t\t}\n\t\t\t});\n\t\t} catch (error) {\n\t\t\tthis._trackError('internal_error');\n\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\tsendJsonRpcError(response, 200, -32603, 'Internal error', null, getErrorMessage(error));\n\t\t\t});\n\t\t} finally {\n\t\t\tclearTimeout(timeout);\n\t\t\tthis._activeRequests--;\n\t\t}\n\t}\n\n\t/**\n\t * Returns number of requests handled.\n\t */\n\tget requestCount(): number {\n\t\treturn this._requestCount;\n\t}\n\n\t/**\n\t * Stops transport server.\n\t */\n\tstop(): Promise<void> {\n\t\tif (this._stopPromise) return this._stopPromise;\n\t\tthis._isShuttingDown = true;\n\t\tthis._stopRateLimitCleanup();\n\n\t\tconst server = this._server;\n\t\tconst serverClosed = server.listening\n\t\t\t? new Promise<void>((resolve, reject) => {\n\t\t\t\t\tserver.close((error) => {\n\t\t\t\t\t\tif (error) {\n\t\t\t\t\t\t\treject(error);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresolve();\n\t\t\t\t\t});\n\t\t\t\t})\n\t\t\t: Promise.resolve();\n\t\tconst stopPromise = Promise.all([serverClosed, this._acceptedWork.join()]).then(() => {\n\t\t\tthis.log('info', 'HTTP transport stopped');\n\t\t});\n\t\tthis._stopPromise = stopPromise;\n\t\tvoid stopPromise.catch(() => {\n\t\t\tif (this._stopPromise === stopPromise) this._stopPromise = null;\n\t\t});\n\t\treturn stopPromise;\n\t}\n}\n\n/**\n * Create an HTTP transport with given options.\n *\n * @param options - Transport configuration\n * @returns A configured HTTP transport\n *\n * @example\n * ```typescript\n * const transport = new HttpTransport({ port: 3000 });\n * await transport.connect(server);\n * ```\n */\nexport function createHttpTransport(options: HttpTransportOptions = {}): HttpTransport {\n\treturn new HttpTransport(options);\n}\n"],"names":["HttpTransport","BaseTransport","options","LifecycleFailureReporter","error","getErrorMessage","AcceptedWorkTracker","createServer","req","res","responseFinalizer","ResponseFinalizer","response","sendJsonRpcError","Error","mcpServer","server","Promise","resolve","reject","cleanup","onError","onListening","errorType","startTime","Date","requestPath","requestMethod","durationSeconds","clientIp","sendCorsPreflight","timeout","setTimeout","maxBodySize","body","readRequestBody","rawBody","JSON","parseResult","safeParse","JsonRpcRequestSchema","rawId","jsonRpcRequest","owner","randomUUID","runWithContext","httpResponse","sendJsonRpcResponse","clearTimeout","serverClosed","stopPromise","createHttpTransport"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"transport/HttpTransport.js","sources":["../../src/transport/HttpTransport.ts"],"sourcesContent":["/**\n * HTTP Transport implementation.\n *\n * This transport provides a stateless, REST-like API interface for MCP tool invocations\n * using standard HTTP request-response patterns.\n *\n * @example\n * ```typescript\n * const transport = new HttpTransport({\n * port: 3000,\n * host: 'localhost'\n * });\n * await transport.connect(server);\n * ```\n */\n\nimport type { IncomingMessage, ServerResponse } from 'node:http';\nimport { createServer } from 'node:http';\nimport { randomUUID } from 'node:crypto';\nimport type { McpServer } from 'tmcp';\nimport { safeParse } from 'valibot';\nimport type { IMetrics } from '../contracts/interfaces.js';\nimport { getErrorMessage } from '../errors.js';\nimport { JsonRpcRequestSchema } from '../schema.js';\nimport { BaseTransport, type TransportOptions } from './BaseTransport.js';\nimport type { ITransport, TransportKind } from '../contracts/transport.js';\nimport {\n\treadRequestBody,\n\tsendCorsPreflight,\n\tsendJsonRpcError,\n\tsendJsonRpcResponse,\n} from './HttpHelpers.js';\nimport { runWithContext } from '../context/RequestContext.js';\nimport {\n\tAcceptedWorkTracker,\n\tLifecycleFailureReporter,\n\tPreDispatchTracker,\n\tResponseFinalizer,\n\ttype PreDispatchLease,\n} from './HttpRequestLifecycle.js';\n\nexport interface HttpTransportOptions extends TransportOptions {\n\t/**\n\t * Path for messages endpoint\n\t * @default '/messages'\n\t */\n\tpath?: string;\n\tmetrics?: IMetrics;\n\tmetricsProvider?: () => string;\n\n\t/**\n\t * Enable request body size limit\n\t * @default true\n\t */\n\tenableBodySizeLimit?: boolean;\n\n\t/**\n\t * Maximum request body size in bytes\n\t * @default 10485760 (10MB)\n\t */\n\tmaxBodySize?: number;\n\n\t/**\n\t * Request timeout in milliseconds\n\t * @default 30000 (30 seconds)\n\t */\n\trequestTimeout?: number;\n}\n\n/**\n * HTTP Transport for MCP server.\n *\n * This transport provides a stateless, REST-like API interface for MCP tool invocations\n * using standard HTTP request-response patterns.\n *\n * @remarks\n * **Security Features:**\n * - Session ID validation (alphanumeric, max 64 chars)\n * - Query parameter sanitization (whitelist allowed keys)\n * - Rate limiting per IP (configurable, default 100 req/min)\n * - CORS origin validation\n * - Request body size limits (configurable, default 10MB)\n * - Request timeout (configurable, default 30s)\n *\n * **Rate Limiting:**\n * - Tracks requests per IP address within a time window\n * - Returns 429 Too Many Requests when limit exceeded\n * - Can be disabled via `enableRateLimit: false`\n *\n * **HTTP Status Code Mapping:**\n * - 200: Success (JSON-RPC response)\n * - 204: CORS Preflight (empty body)\n * - 400: Bad Request\n * - 403: Forbidden (invalid CORS)\n * - 404: Not Found\n * - 413: Payload Too Large\n * - 429: Too Many Requests\n * - 500: Internal Server Error\n * - 503: Server Not Ready\n */\nexport class HttpTransport extends BaseTransport implements ITransport {\n\tget kind(): TransportKind {\n\t\treturn 'http';\n\t}\n\tprivate _server: ReturnType<typeof createServer>;\n\tprivate _mcpServer: McpServer | null = null;\n\tprivate _requestTimeout: number;\n\tprivate _bodySizeLimitEnabled: boolean;\n\tprivate _maxBodySize: number;\n\tprivate _requestCount: number = 0;\n\tprivate _activeRequests: number = 0;\n\tprivate _path: string;\n\tprivate _metrics?: IMetrics;\n\tprivate _metricsProvider: (() => string) | null;\n\tprivate readonly _lifecycleFailureReporter: LifecycleFailureReporter;\n\tprivate readonly _acceptedWork: AcceptedWorkTracker;\n\tprivate readonly _preDispatch = new PreDispatchTracker();\n\tprivate _stopPromise: Promise<void> | null = null;\n\n\tconstructor(options: HttpTransportOptions = {}) {\n\t\tsuper(options);\n\n\t\tthis._requestTimeout = options.requestTimeout ?? 30000;\n\t\tthis._bodySizeLimitEnabled = options.enableBodySizeLimit ?? true;\n\t\tthis._maxBodySize = options.maxBodySize ?? 10 * 1024 * 1024;\n\t\tthis._path = options.path ?? '/messages';\n\t\tthis._metrics = options.metrics;\n\t\tthis._metricsProvider = options.metricsProvider ?? null;\n\t\tthis._lifecycleFailureReporter = new LifecycleFailureReporter((error) => {\n\t\t\tthis.log('error', 'HTTP request lifecycle failed', { error: getErrorMessage(error) });\n\t\t});\n\t\tthis._acceptedWork = new AcceptedWorkTracker(this._lifecycleFailureReporter);\n\t\tthis._server = createServer((req, res) => {\n\t\t\tconst responseFinalizer = new ResponseFinalizer(res, this._lifecycleFailureReporter);\n\t\t\tvoid this._handleRequest(req, res).catch((error: unknown) => {\n\t\t\t\tthis._lifecycleFailureReporter.report(error);\n\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\tsendJsonRpcError(response, 500, -32603, 'Internal error', null, getErrorMessage(error));\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\t}\n\n\tprivate _requireMcpServer(): McpServer {\n\t\tif (!this._mcpServer) {\n\t\t\tthrow new Error('MCP server not initialized. Did you call connect()?');\n\t\t}\n\t\treturn this._mcpServer;\n\t}\n\n\t/**\n\t * Get number of active HTTP connections.\n\t */\n\tget clientCount(): number {\n\t\treturn this._activeRequests;\n\t}\n\n\t/**\n\t * Connects MCP server to this transport.\n\t */\n\tasync connect(mcpServer: McpServer): Promise<void> {\n\t\tthis._mcpServer = mcpServer;\n\t\tconst server = this._server;\n\t\treturn new Promise((resolve, reject) => {\n\t\t\tconst cleanup = (): void => {\n\t\t\t\tserver.off('error', onError);\n\t\t\t\tserver.off('listening', onListening);\n\t\t\t};\n\t\t\tconst onError = (error: Error): void => {\n\t\t\t\tcleanup();\n\t\t\t\treject(error);\n\t\t\t};\n\t\t\tconst onListening = (): void => {\n\t\t\t\tcleanup();\n\t\t\t\tthis.log('info', `HTTP transport listening on http://${this._host}:${this._port}`);\n\t\t\t\tresolve();\n\t\t\t};\n\t\t\tserver.once('error', onError);\n\t\t\tserver.once('listening', onListening);\n\t\t\ttry {\n\t\t\t\tserver.listen(this._port, this._host);\n\t\t\t} catch (error) {\n\t\t\t\tcleanup();\n\t\t\t\treject(error);\n\t\t\t}\n\t\t});\n\t}\n\n\t/**\n\t * Track an error in metrics.\n\t */\n\tprivate _trackError(errorType: string): void {\n\t\tthis._metrics?.counter(\n\t\t\t'http_request_errors_total',\n\t\t\t1,\n\t\t\t{ transport: 'http', error_type: errorType },\n\t\t\t'Total HTTP request errors'\n\t\t);\n\t}\n\n\tprivate _parseJsonRpcRequest(\n\t\tbody: string,\n\t\tresponseFinalizer: ResponseFinalizer\n\t): Parameters<McpServer['receive']>[0] | null {\n\t\tlet rawBody: unknown;\n\t\ttry {\n\t\t\trawBody = JSON.parse(body) as unknown;\n\t\t} catch {\n\t\t\tthis._trackError('parse_error');\n\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\tsendJsonRpcError(response, 200, -32700, 'Parse error');\n\t\t\t});\n\t\t\treturn null;\n\t\t}\n\n\t\tconst parseResult = safeParse(JsonRpcRequestSchema, rawBody);\n\t\tconst rawId =\n\t\t\trawBody && typeof rawBody === 'object' && 'id' in rawBody\n\t\t\t\t? ((rawBody as { id?: unknown }).id ?? null)\n\t\t\t\t: null;\n\t\tif (!parseResult.success) {\n\t\t\tthis._trackError('validation');\n\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\tsendJsonRpcError(\n\t\t\t\t\tresponse,\n\t\t\t\t\t200,\n\t\t\t\t\t-32600,\n\t\t\t\t\t'Invalid Request',\n\t\t\t\t\trawId as string | number | null,\n\t\t\t\t\tparseResult.issues\n\t\t\t\t);\n\t\t\t});\n\t\t\treturn null;\n\t\t}\n\t\treturn parseResult.output as Parameters<McpServer['receive']>[0];\n\t}\n\n\t/**\n\t * Route and handle incoming HTTP requests.\n\t *\n\t * Performs security checks (host, shutdown, rate limit, CORS) then\n\t * dispatches to the appropriate endpoint handler.\n\t */\n\tprivate async _handleRequest(req: IncomingMessage, res: ServerResponse): Promise<void> {\n\t\tconst startTime = Date.now();\n\t\tconst requestPath = req.url || '/';\n\t\tconst requestMethod = req.method || 'GET';\n\t\tthis._metrics?.counter('http_requests_total', 1, {}, 'Total HTTP transport requests');\n\t\tthis._metrics?.counter(\n\t\t\t'http_transport_requests_total',\n\t\t\t1,\n\t\t\t{ transport: 'http', method: requestMethod, path: requestPath },\n\t\t\t'Total HTTP requests by transport'\n\t\t);\n\t\tres.once('finish', () => {\n\t\t\tconst durationSeconds = (Date.now() - startTime) / 1000;\n\t\t\tthis._metrics?.histogram('http_request_duration_seconds', durationSeconds, {});\n\t\t\tthis._metrics?.histogram('http_transport_request_duration_seconds', durationSeconds, {\n\t\t\t\ttransport: 'http',\n\t\t\t\tpath: requestPath,\n\t\t\t});\n\t\t});\n\n\t\t// Security middleware chain\n\t\tif (!this.validateHostHeader(req)) {\n\t\t\tthis._trackError('forbidden');\n\t\t\tsendJsonRpcError(res, 403, -32000, 'Forbidden - invalid host header');\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.isShuttingDown) {\n\t\t\tthis._trackError('shutting_down');\n\t\t\tsendJsonRpcError(res, 503, -32603, 'Server is shutting down');\n\t\t\treturn;\n\t\t}\n\n\t\tconst clientIp = this.getClientIp(req);\n\t\tif (this.checkRateLimit(clientIp)) {\n\t\t\tthis._trackError('rate_limit');\n\t\t\tres.setHeader('Retry-After', '60');\n\t\t\tsendJsonRpcError(res, 429, -32000, 'Too many requests');\n\t\t\treturn;\n\t\t}\n\n\t\tif (!this.validateCorsOrigin(req)) {\n\t\t\tthis._trackError('forbidden');\n\t\t\tsendJsonRpcError(res, 403, -32000, 'Forbidden - invalid origin');\n\t\t\treturn;\n\t\t}\n\n\t\tthis.setCorsHeaders(res);\n\n\t\t// Static endpoints\n\t\tif (req.method === 'GET' && req.url === '/metrics')\n\t\t\treturn this.handleMetricsEndpoint(res, this._metricsProvider);\n\t\tif (req.method === 'OPTIONS') return sendCorsPreflight(res);\n\t\tif (req.method === 'GET' && req.url === '/health')\n\t\t\treturn this.handleHealthEndpoint(res, { requests: this._requestCount });\n\t\tif (req.method === 'GET' && req.url === '/ready') return this.handleReadinessEndpoint(res);\n\n\t\t// MCP endpoint\n\t\tif (req.method === 'POST' && req.url === this._path) {\n\t\t\tconst lease = this._preDispatch.acquire();\n\t\t\tif (!lease) {\n\t\t\t\tconst responseFinalizer = new ResponseFinalizer(res, this._lifecycleFailureReporter);\n\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\tsendJsonRpcError(response, 503, -32603, 'Server is shutting down');\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tawait this._handlePostRequest(req, res, lease);\n\t\t\treturn;\n\t\t}\n\n\t\t// 404\n\t\tthis._trackError('not_found');\n\t\tsendJsonRpcError(res, 404, -32601, 'Not Found');\n\t}\n\n\t/**\n\t * Handle POST to the MCP messages endpoint.\n\t *\n\t * Reads the request body, validates JSON-RPC format, and delegates\n\t * processing to the MCP server.\n\t */\n\tprivate async _handlePostRequest(\n\t\treq: IncomingMessage,\n\t\tres: ServerResponse,\n\t\tlease: PreDispatchLease\n\t): Promise<void> {\n\t\tthis._requestCount++;\n\t\tthis._activeRequests++;\n\t\tconst responseFinalizer = new ResponseFinalizer(res, this._lifecycleFailureReporter);\n\t\tconst onAborted = (): void => lease.cancel('peer');\n\t\treq.once('aborted', onAborted);\n\n\t\tconst timeout = setTimeout(() => {\n\t\t\tthis._trackError('timeout');\n\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\tsendJsonRpcError(response, 500, -32603, 'Request timeout');\n\t\t\t});\n\t\t\tlease.cancel('timeout');\n\t\t}, this._requestTimeout);\n\n\t\ttry {\n\t\t\tconst maxBodySize = this._bodySizeLimitEnabled ? this._maxBodySize : 0;\n\t\t\tconst body = await readRequestBody(req, maxBodySize, lease.signal);\n\n\t\t\tif (body === null) {\n\t\t\t\tthis._trackError('payload_too_large');\n\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\tsendJsonRpcError(response, 413, -32000, 'Request body too large');\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tconst jsonRpcRequest = this._parseJsonRpcRequest(body, responseFinalizer);\n\t\t\tif (jsonRpcRequest === null) return;\n\n\t\t\tif (!this._mcpServer) {\n\t\t\t\tthis._trackError('server_not_ready');\n\t\t\t\tconst requestId = 'id' in jsonRpcRequest ? (jsonRpcRequest.id ?? null) : null;\n\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\tsendJsonRpcError(response, 200, -32603, 'Server not ready', requestId);\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\treq.off('aborted', onAborted);\n\t\t\tconst owner = randomUUID();\n\t\t\tconst acceptedWork = this._acceptedWork.transfer(lease, () =>\n\t\t\t\trunWithContext({ requestId: randomUUID(), owner }, () =>\n\t\t\t\t\tthis._requireMcpServer().receive(jsonRpcRequest, {\n\t\t\t\t\t\tsessionInfo: {},\n\t\t\t\t\t})\n\t\t\t\t)\n\t\t\t\t\t.then((response: Awaited<ReturnType<McpServer['receive']>>) => {\n\t\t\t\t\t\tresponseFinalizer.finalize((httpResponse) => {\n\t\t\t\t\t\t\tif (response) {\n\t\t\t\t\t\t\t\tsendJsonRpcResponse(httpResponse, response);\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\thttpResponse.writeHead(204);\n\t\t\t\t\t\t\t\thttpResponse.end();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t})\n\t\t\t\t\t.catch((error: unknown) => {\n\t\t\t\t\t\tthis._trackError('internal_error');\n\t\t\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\t\t\tsendJsonRpcError(\n\t\t\t\t\t\t\t\tresponse,\n\t\t\t\t\t\t\t\t200,\n\t\t\t\t\t\t\t\t-32603,\n\t\t\t\t\t\t\t\t'Internal error',\n\t\t\t\t\t\t\t\tnull,\n\t\t\t\t\t\t\t\tgetErrorMessage(error)\n\t\t\t\t\t\t\t);\n\t\t\t\t\t\t});\n\t\t\t\t\t})\n\t\t\t);\n\t\t\tawait acceptedWork;\n\t\t} catch (error) {\n\t\t\tif (lease.signal.aborted) {\n\t\t\t\tif (lease.cancellationReason === 'shutdown') {\n\t\t\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\t\t\tsendJsonRpcError(response, 503, -32603, 'Server is shutting down');\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t\tthis._trackError('internal_error');\n\t\t\tresponseFinalizer.finalize((response) => {\n\t\t\t\tsendJsonRpcError(response, 200, -32603, 'Internal error', null, getErrorMessage(error));\n\t\t\t});\n\t\t} finally {\n\t\t\treq.off('aborted', onAborted);\n\t\t\tlease.release();\n\t\t\tclearTimeout(timeout);\n\t\t\tthis._activeRequests--;\n\t\t}\n\t}\n\n\t/**\n\t * Returns number of requests handled.\n\t */\n\tget requestCount(): number {\n\t\treturn this._requestCount;\n\t}\n\n\t/**\n\t * Stops transport server.\n\t */\n\tstop(): Promise<void> {\n\t\tif (this._stopPromise) return this._stopPromise;\n\t\tthis._isShuttingDown = true;\n\t\tthis._preDispatch.closeAdmission('shutdown');\n\t\tthis._stopRateLimitCleanup();\n\n\t\tconst server = this._server;\n\t\tconst serverClosed = server.listening\n\t\t\t? new Promise<void>((resolve, reject) => {\n\t\t\t\t\tserver.close((error) => {\n\t\t\t\t\t\tif (error) {\n\t\t\t\t\t\t\treject(error);\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t\tresolve();\n\t\t\t\t\t});\n\t\t\t\t})\n\t\t\t: Promise.resolve();\n\t\tconst stopPromise = Promise.all([\n\t\t\tserverClosed,\n\t\t\tthis._preDispatch.join().then(() => this._acceptedWork.join()),\n\t\t]).then(() => {\n\t\t\tthis.log('info', 'HTTP transport stopped');\n\t\t});\n\t\tthis._stopPromise = stopPromise;\n\t\tvoid stopPromise.catch(() => {\n\t\t\tif (this._stopPromise === stopPromise) this._stopPromise = null;\n\t\t});\n\t\treturn stopPromise;\n\t}\n}\n\n/**\n * Create an HTTP transport with given options.\n *\n * @param options - Transport configuration\n * @returns A configured HTTP transport\n *\n * @example\n * ```typescript\n * const transport = new HttpTransport({ port: 3000 });\n * await transport.connect(server);\n * ```\n */\nexport function createHttpTransport(options: HttpTransportOptions = {}): HttpTransport {\n\treturn new HttpTransport(options);\n}\n"],"names":["HttpTransport","BaseTransport","PreDispatchTracker","options","LifecycleFailureReporter","error","getErrorMessage","AcceptedWorkTracker","createServer","req","res","responseFinalizer","ResponseFinalizer","response","sendJsonRpcError","Error","mcpServer","server","Promise","resolve","reject","cleanup","onError","onListening","errorType","body","rawBody","JSON","parseResult","safeParse","JsonRpcRequestSchema","rawId","startTime","Date","requestPath","requestMethod","durationSeconds","clientIp","sendCorsPreflight","lease","onAborted","timeout","setTimeout","maxBodySize","readRequestBody","jsonRpcRequest","requestId","owner","randomUUID","acceptedWork","runWithContext","httpResponse","sendJsonRpcResponse","clearTimeout","serverClosed","stopPromise","createHttpTransport"],"mappings":"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"}
|
|
@@ -1,12 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Streamable HTTP Transport implementation (MCP spec recommended transport).
|
|
3
3
|
*
|
|
4
|
-
* This transport implements the MCP Streamable HTTP specification, which replaces
|
|
5
|
-
* the deprecated SSE transport as the recommended HTTP-based transport since March 2025.
|
|
6
|
-
*
|
|
7
4
|
* Key features:
|
|
8
5
|
* - POST /mcp for JSON-RPC requests (main MCP endpoint)
|
|
9
|
-
* - GET /mcp for optional SSE server-to-client notifications
|
|
10
6
|
* - Session management via Mcp-Session-Id header
|
|
11
7
|
* - Supports both stateful (session-based) and stateless (per-request) modes
|
|
12
8
|
* - Health endpoints (/health, /ready)
|
|
@@ -69,13 +65,18 @@ export interface StreamableHttpTransportOptions extends TransportOptions {
|
|
|
69
65
|
* @default 30000 (30 seconds)
|
|
70
66
|
*/
|
|
71
67
|
requestTimeout?: number;
|
|
68
|
+
/** Maximum retained sessions when idle expiry is enabled. */
|
|
69
|
+
maxSessions?: number;
|
|
70
|
+
/** Idle duration before an unpinned session expires. */
|
|
71
|
+
sessionIdleTimeoutMs?: number;
|
|
72
|
+
/** Interval between idle-session sweeps. */
|
|
73
|
+
sessionSweepIntervalMs?: number;
|
|
72
74
|
}
|
|
73
75
|
/**
|
|
74
76
|
* Streamable HTTP Transport for MCP server.
|
|
75
77
|
*
|
|
76
78
|
* This transport implements the MCP Streamable HTTP specification,
|
|
77
|
-
* providing JSON-RPC over HTTP with optional session management
|
|
78
|
-
* and server-to-client SSE notification streams.
|
|
79
|
+
* providing JSON-RPC over HTTP with optional session management.
|
|
79
80
|
*
|
|
80
81
|
* @remarks
|
|
81
82
|
* **Security Features (inherited from BaseTransport):**
|
|
@@ -87,13 +88,11 @@ export interface StreamableHttpTransportOptions extends TransportOptions {
|
|
|
87
88
|
*
|
|
88
89
|
* **MCP Streamable HTTP Spec Compliance:**
|
|
89
90
|
* - POST /mcp — JSON-RPC method calls
|
|
90
|
-
* - GET /mcp — SSE notification stream (server-to-client)
|
|
91
91
|
* - Mcp-Session-Id header for session management
|
|
92
92
|
* - Content-Type: application/json for JSON-RPC responses
|
|
93
|
-
* - Content-Type: text/event-stream for SSE notification streams
|
|
94
93
|
*
|
|
95
94
|
* **HTTP Status Code Mapping:**
|
|
96
|
-
* - 200: Success (JSON-RPC response
|
|
95
|
+
* - 200: Success (JSON-RPC response)
|
|
97
96
|
* - 202: Accepted (JSON-RPC notification, no response body)
|
|
98
97
|
* - 204: CORS Preflight (empty body)
|
|
99
98
|
* - 400: Bad Request (invalid JSON, invalid session ID)
|
|
@@ -122,6 +121,9 @@ export declare class StreamableHttpTransport extends BaseTransport implements IT
|
|
|
122
121
|
private _metricsProvider;
|
|
123
122
|
private readonly _lifecycleFailureReporter;
|
|
124
123
|
private readonly _acceptedWork;
|
|
124
|
+
private readonly _preDispatch;
|
|
125
|
+
private readonly _sessionRetention;
|
|
126
|
+
private _sessionSweepIntervalId;
|
|
125
127
|
private _stopPromise;
|
|
126
128
|
constructor(options?: StreamableHttpTransportOptions);
|
|
127
129
|
private _requireMcpServer;
|
|
@@ -137,6 +139,7 @@ export declare class StreamableHttpTransport extends BaseTransport implements IT
|
|
|
137
139
|
* Connects MCP server to this transport and starts listening.
|
|
138
140
|
*/
|
|
139
141
|
connect(mcpServer: McpServer): Promise<void>;
|
|
142
|
+
private _recordRequestMetrics;
|
|
140
143
|
/**
|
|
141
144
|
* Route and handle incoming HTTP requests.
|
|
142
145
|
*/
|
|
@@ -145,6 +148,7 @@ export declare class StreamableHttpTransport extends BaseTransport implements IT
|
|
|
145
148
|
* Send a JSON-RPC error response.
|
|
146
149
|
*/
|
|
147
150
|
private _sendJsonRpcError;
|
|
151
|
+
private _parseJsonRpcRequest;
|
|
148
152
|
/**
|
|
149
153
|
* Handle POST /mcp — JSON-RPC method calls.
|
|
150
154
|
*
|
|
@@ -155,37 +159,21 @@ export declare class StreamableHttpTransport extends BaseTransport implements IT
|
|
|
155
159
|
* - Content-Type: application/json for responses
|
|
156
160
|
*/
|
|
157
161
|
private _handleMcpPost;
|
|
162
|
+
private _acceptPostSession;
|
|
158
163
|
private _receiveAndFinalize;
|
|
159
|
-
/**
|
|
160
|
-
* Handle GET /mcp — Optional SSE notification stream.
|
|
161
|
-
*
|
|
162
|
-
* Per the MCP Streamable HTTP spec, clients may open a GET request
|
|
163
|
-
* to receive server-initiated notifications as SSE events.
|
|
164
|
-
* Requires a valid Mcp-Session-Id in stateful mode.
|
|
165
|
-
*/
|
|
166
|
-
private _handleMcpGet;
|
|
167
164
|
/**
|
|
168
165
|
* Resolve or create a session for a stateful request.
|
|
169
166
|
*
|
|
170
167
|
* @returns Session ID string on success, or the response error to finalize.
|
|
171
168
|
*/
|
|
172
169
|
private _resolveSession;
|
|
170
|
+
private _sweepExpiredSessions;
|
|
171
|
+
private _disposeSession;
|
|
172
|
+
private _stopSessionSweep;
|
|
173
173
|
/**
|
|
174
174
|
* Extract Mcp-Session-Id from request headers.
|
|
175
175
|
*/
|
|
176
176
|
private _getSessionIdFromHeader;
|
|
177
|
-
/**
|
|
178
|
-
* Send an SSE event to a specific client response stream.
|
|
179
|
-
*/
|
|
180
|
-
private _sendSseEvent;
|
|
181
|
-
/**
|
|
182
|
-
* Broadcast a notification to all SSE streams in a given session.
|
|
183
|
-
*
|
|
184
|
-
* @param sessionId - Target session ID
|
|
185
|
-
* @param event - SSE event name
|
|
186
|
-
* @param data - Event payload
|
|
187
|
-
*/
|
|
188
|
-
broadcastToSession(sessionId: string, event: string, data: unknown): void;
|
|
189
177
|
/**
|
|
190
178
|
* Handle GET /metrics endpoint.
|
|
191
179
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"StreamableHttpTransport.d.ts","sourceRoot":"","sources":["../../src/transport/StreamableHttpTransport.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"StreamableHttpTransport.d.ts","sourceRoot":"","sources":["../../src/transport/StreamableHttpTransport.ts"],"names":[],"mappings":"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"}
|