@j0hanz/fetch-url-mcp 1.12.8 → 1.12.10

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 (65) hide show
  1. package/README.md +2 -0
  2. package/dist/http/auth.d.ts +2 -2
  3. package/dist/http/auth.d.ts.map +1 -1
  4. package/dist/http/auth.js +42 -24
  5. package/dist/http/index.d.ts +0 -2
  6. package/dist/http/index.d.ts.map +1 -1
  7. package/dist/http/index.js +2 -2
  8. package/dist/http/native.d.ts +4 -1
  9. package/dist/http/native.d.ts.map +1 -1
  10. package/dist/http/native.js +171 -98
  11. package/dist/http/rate-limit.d.ts +11 -3
  12. package/dist/http/rate-limit.d.ts.map +1 -1
  13. package/dist/http/rate-limit.js +19 -10
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +2 -1
  16. package/dist/lib/config.d.ts +1 -0
  17. package/dist/lib/config.d.ts.map +1 -1
  18. package/dist/lib/config.js +25 -3
  19. package/dist/lib/core.d.ts +12 -2
  20. package/dist/lib/core.d.ts.map +1 -1
  21. package/dist/lib/core.js +166 -87
  22. package/dist/lib/error/classes.d.ts.map +1 -1
  23. package/dist/lib/error/classes.js +4 -2
  24. package/dist/lib/error/classify.d.ts.map +1 -1
  25. package/dist/lib/error/classify.js +20 -16
  26. package/dist/lib/error/index.d.ts.map +1 -1
  27. package/dist/lib/error/index.js +2 -0
  28. package/dist/lib/mcp-interop.d.ts +12 -0
  29. package/dist/lib/mcp-interop.d.ts.map +1 -1
  30. package/dist/lib/mcp-interop.js +20 -2
  31. package/dist/lib/net/http.js +2 -1
  32. package/dist/lib/net/index.d.ts.map +1 -1
  33. package/dist/lib/net/index.js +2 -0
  34. package/dist/lib/net/pipeline.d.ts.map +1 -1
  35. package/dist/lib/net/pipeline.js +10 -9
  36. package/dist/lib/net/url.d.ts +1 -2
  37. package/dist/lib/net/url.d.ts.map +1 -1
  38. package/dist/lib/net/url.js +9 -6
  39. package/dist/lib/utils.d.ts +0 -16
  40. package/dist/lib/utils.d.ts.map +1 -1
  41. package/dist/lib/utils.js +3 -51
  42. package/dist/resources/index.d.ts.map +1 -1
  43. package/dist/resources/index.js +49 -22
  44. package/dist/schemas.d.ts +9 -1
  45. package/dist/schemas.d.ts.map +1 -1
  46. package/dist/schemas.js +1 -1
  47. package/dist/server.d.ts.map +1 -1
  48. package/dist/server.js +3 -1
  49. package/dist/tasks/index.d.ts.map +1 -1
  50. package/dist/tasks/index.js +2 -0
  51. package/dist/tasks/manager.d.ts +9 -106
  52. package/dist/tasks/manager.d.ts.map +1 -1
  53. package/dist/tasks/manager.js +200 -553
  54. package/dist/tasks/store.d.ts +104 -0
  55. package/dist/tasks/store.d.ts.map +1 -0
  56. package/dist/tasks/store.js +480 -0
  57. package/dist/tools/index.d.ts.map +1 -1
  58. package/dist/tools/index.js +5 -4
  59. package/dist/transform/index.d.ts +1 -85
  60. package/dist/transform/index.d.ts.map +1 -1
  61. package/dist/transform/index.js +312 -942
  62. package/dist/transform/worker-pool.d.ts +19 -0
  63. package/dist/transform/worker-pool.d.ts.map +1 -0
  64. package/dist/transform/worker-pool.js +713 -0
  65. package/package.json +3 -1
@@ -1,5 +1,5 @@
1
1
  import { StreamableHTTPServerTransport } from '@modelcontextprotocol/sdk/server/streamableHttp.js';
2
- import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
2
+ import { ErrorCode, isInitializeRequest, } from '@modelcontextprotocol/sdk/types.js';
3
3
  import { randomUUID } from 'node:crypto';
4
4
  import { once } from 'node:events';
5
5
  import { readFileSync } from 'node:fs';
@@ -10,16 +10,21 @@ import { monitorEventLoopDelay, performance } from 'node:perf_hooks';
10
10
  import process from 'node:process';
11
11
  import { Writable } from 'node:stream';
12
12
  import { pipeline } from 'node:stream/promises';
13
- import { composeCloseHandlers, config, createSessionStore, createSlotTracker, enableHttpMode, ensureSessionCapacity, logDebug, logError, Loggers, logInfo, logWarn, registerMcpSessionOwnerKey, registerMcpSessionServer, reserveSessionSlot, resolveMcpSessionIdByServer, runWithRequestContext, serverVersion, startSessionCleanupLoop, unregisterMcpSessionServer, unregisterMcpSessionServerByServer, } from '../lib/core.js';
13
+ import { config, enableHttpMode, serverVersion } from '../lib/config.js';
14
+ import { composeCloseHandlers, createSessionStore, createSlotTracker, ensureSessionCapacity, logDebug, logError, Loggers, logInfo, logWarn, registerMcpSessionOwnerKey, registerMcpSessionServer, reserveSessionSlot, resolveMcpSessionIdByServer, runWithRequestContext, runWithTraceContext, startSessionCleanupLoop, unregisterMcpSessionServer, unregisterMcpSessionServerByServer, } from '../lib/core.js';
14
15
  import { getErrorMessage, toError } from '../lib/error/index.js';
15
- import { acceptsEventStream, acceptsJsonAndEventStream, isMcpRequestBody, } from '../lib/mcp-interop.js';
16
+ import { acceptsEventStream, acceptsJsonAndEventStream, isMcpRequestBody, sendJsonRpcError, } from '../lib/mcp-interop.js';
16
17
  import { createDefaultBlockList, normalizeIpForBlockList, } from '../lib/net/index.js';
17
- import { applyHttpServerTuning, drainConnectionsOnShutdown, isObject, } from '../lib/utils.js';
18
+ import { isObject } from '../lib/utils.js';
18
19
  import { createMcpServerForHttpSession } from '../server.js';
19
20
  import { buildAuthenticatedOwnerKey } from '../tasks/index.js';
20
21
  import { getTransformPoolStats } from '../transform/index.js';
21
- import { applyInsufficientScopeAuthHeaders, applyUnauthorizedAuthHeaders, assertHttpModeConfiguration, authService, buildAuthFingerprint, buildProtectedResourceMetadataDocument, corsPolicy, DEFAULT_MCP_PROTOCOL_VERSION, ensureMcpProtocolVersion, hostOriginPolicy, isInsufficientScopeError, isOAuthMetadataEnabled, isProtectedResourceMetadataPath, SUPPORTED_MCP_PROTOCOL_VERSIONS, } from './auth.js';
22
- import { createRateLimitManagerImpl, } from './rate-limit.js';
22
+ import { applyInsufficientScopeAuthHeaders, applyUnauthorizedAuthHeaders, assertHttpModeConfiguration, authService, buildAuthFingerprint, buildProtectedResourceMetadataDocument, corsPolicy, ensureMcpProtocolVersion, hostOriginPolicy, isInsufficientScopeError, isOAuthMetadataEnabled, isProtectedResourceMetadataPath, SUPPORTED_MCP_PROTOCOL_VERSIONS, } from './auth.js';
23
+ import { RateLimiter } from './rate-limit.js';
24
+ const DROP_LOG_INTERVAL_MS = 10_000;
25
+ const MISSING_SESSION_ID_MESSAGE = "We couldn't find a session ID for your request. Please ensure you have an active session.";
26
+ const SESSION_NOT_INITIALIZED_MESSAGE = "Your session hasn't been initialized yet. Please wait a moment and try again.";
27
+ const SESSION_NOT_FOUND_MESSAGE = "We couldn't find your session. It might have expired or been closed.";
23
28
  function abortControllerBestEffort(controller) {
24
29
  if (!controller.signal.aborted)
25
30
  controller.abort();
@@ -32,6 +37,47 @@ function destroyRequestBestEffort(req) {
32
37
  // Best-effort only.
33
38
  }
34
39
  }
40
+ function applyHttpServerTuning(server) {
41
+ const { headersTimeoutMs, requestTimeoutMs, keepAliveTimeoutMs, keepAliveTimeoutBufferMs, maxHeadersCount, maxConnections, } = config.server.http;
42
+ if (headersTimeoutMs !== undefined)
43
+ server.headersTimeout = headersTimeoutMs;
44
+ if (requestTimeoutMs !== undefined)
45
+ server.requestTimeout = requestTimeoutMs;
46
+ if (keepAliveTimeoutMs !== undefined)
47
+ server.keepAliveTimeout = keepAliveTimeoutMs;
48
+ if (keepAliveTimeoutBufferMs !== undefined)
49
+ server.keepAliveTimeoutBuffer = keepAliveTimeoutBufferMs;
50
+ if (maxHeadersCount !== undefined)
51
+ server.maxHeadersCount = maxHeadersCount;
52
+ if (typeof maxConnections === 'number' && maxConnections > 0) {
53
+ server.maxConnections = maxConnections;
54
+ server.dropMaxConnection = true;
55
+ if (typeof server.on === 'function') {
56
+ let lastLoggedAt = 0;
57
+ let droppedSinceLastLog = 0;
58
+ const onDrop = (data) => {
59
+ droppedSinceLastLog += 1;
60
+ const now = Date.now();
61
+ if (now - lastLoggedAt < DROP_LOG_INTERVAL_MS)
62
+ return;
63
+ logWarn('Incoming connection dropped (maxConnections reached)', {
64
+ maxConnections,
65
+ dropped: droppedSinceLastLog,
66
+ data,
67
+ }, Loggers.LOG_HTTP);
68
+ lastLoggedAt = now;
69
+ droppedSinceLastLog = 0;
70
+ };
71
+ server.on('drop', onDrop);
72
+ }
73
+ }
74
+ }
75
+ function drainConnectionsOnShutdown(server) {
76
+ if (typeof server.closeIdleConnections === 'function') {
77
+ server.closeIdleConnections();
78
+ logDebug('Closed idle HTTP connections during shutdown', undefined, Loggers.LOG_HTTP);
79
+ }
80
+ }
35
81
  // ---------------------------------------------------------------------------
36
82
  // Response helpers
37
83
  // ---------------------------------------------------------------------------
@@ -50,10 +96,8 @@ export function sendEmpty(res, status) {
50
96
  res.setHeader('Content-Length', '0');
51
97
  res.end();
52
98
  }
53
- export function sendError(res, _code, message, status = 400,
54
- // eslint-disable-next-line @typescript-eslint/no-unused-vars -- kept for call-site compat
55
- _id) {
56
- sendJson(res, status, { error: message });
99
+ export function sendError(res, code, message, status = 400, id) {
100
+ sendJsonRpcError(res, status, code, message, id ?? null);
57
101
  }
58
102
  // ---------------------------------------------------------------------------
59
103
  // Request helpers
@@ -262,18 +306,20 @@ function isRequestReadAborted(req) {
262
306
  return req.destroyed && !req.complete;
263
307
  }
264
308
  class JsonBodyReader {
309
+ validateContentLength(req, limit) {
310
+ const contentLengthHeader = getHeaderValue(req, 'content-length');
311
+ if (!contentLengthHeader)
312
+ return;
313
+ const contentLength = Number.parseInt(contentLengthHeader, 10);
314
+ if (Number.isFinite(contentLength) && contentLength > limit) {
315
+ throw new JsonBodyError('payload-too-large', 'Payload too large');
316
+ }
317
+ }
265
318
  async read(req, limit = DEFAULT_BODY_LIMIT_BYTES, signal) {
266
319
  const contentType = getHeaderValue(req, 'content-type');
267
320
  if (!contentType?.includes('application/json'))
268
321
  return undefined;
269
- const contentLengthHeader = getHeaderValue(req, 'content-length');
270
- if (contentLengthHeader) {
271
- const contentLength = Number.parseInt(contentLengthHeader, 10);
272
- if (Number.isFinite(contentLength) && contentLength > limit) {
273
- const error = new JsonBodyError('payload-too-large', 'Payload too large');
274
- throw error;
275
- }
276
- }
322
+ this.validateContentLength(req, limit);
277
323
  if (signal?.aborted || isRequestReadAborted(req)) {
278
324
  const error = new JsonBodyError('read-failed', 'Request aborted');
279
325
  throw error;
@@ -285,24 +331,35 @@ class JsonBodyReader {
285
331
  return JSON.parse(body);
286
332
  }
287
333
  catch (err) {
288
- const error = new JsonBodyError('invalid-json', getErrorMessage(err));
289
- throw error;
334
+ throw new JsonBodyError('invalid-json', getErrorMessage(err));
290
335
  }
291
336
  }
292
- async readBody(req, limit, signal) {
293
- const abortListener = signal != null
294
- ? () => {
295
- destroyRequestBestEffort(req);
296
- }
297
- : null;
298
- if (signal != null && abortListener) {
299
- if (signal.aborted) {
300
- abortListener();
301
- }
302
- else {
303
- signal.addEventListener('abort', abortListener, { once: true });
304
- }
337
+ setupAbortListener(req, signal) {
338
+ if (signal == null)
339
+ return null;
340
+ const listener = () => {
341
+ destroyRequestBestEffort(req);
342
+ };
343
+ if (signal.aborted) {
344
+ listener();
305
345
  }
346
+ else {
347
+ signal.addEventListener('abort', listener, { once: true });
348
+ }
349
+ return listener;
350
+ }
351
+ cleanupAbortListener(signal, listener) {
352
+ if (!signal || !listener)
353
+ return;
354
+ try {
355
+ signal.removeEventListener('abort', listener);
356
+ }
357
+ catch {
358
+ // Best-effort cleanup.
359
+ }
360
+ }
361
+ async readBody(req, limit, signal) {
362
+ const abortListener = this.setupAbortListener(req, signal);
306
363
  try {
307
364
  const { chunks, size } = await this.collectChunks(req, limit, signal);
308
365
  if (chunks.length === 0)
@@ -317,14 +374,7 @@ class JsonBodyReader {
317
374
  return text;
318
375
  }
319
376
  finally {
320
- if (signal && abortListener) {
321
- try {
322
- signal.removeEventListener('abort', abortListener);
323
- }
324
- catch {
325
- // Best-effort cleanup.
326
- }
327
- }
377
+ this.cleanupAbortListener(signal, abortListener);
328
378
  }
329
379
  }
330
380
  async collectChunks(req, limit, signal) {
@@ -386,7 +436,7 @@ function unregisterSessionTaskScope(server) {
386
436
  unregisterMcpSessionServer(sessionId);
387
437
  return sessionId;
388
438
  }
389
- async function closeSessionResources(session, options) {
439
+ function buildCloseTasks(session, options) {
390
440
  const closeTasks = [];
391
441
  if (options.closeTransportReason) {
392
442
  closeTasks.push(closeTransportBestEffort(session.transport, options.closeTransportReason));
@@ -394,6 +444,10 @@ async function closeSessionResources(session, options) {
394
444
  if (options.closeServerReason) {
395
445
  closeTasks.push(closeMcpServerBestEffort(session.server, options.closeServerReason));
396
446
  }
447
+ return closeTasks;
448
+ }
449
+ async function closeSessionResources(session, options) {
450
+ const closeTasks = buildCloseTasks(session, options);
397
451
  if (options.awaitClose && closeTasks.length > 0) {
398
452
  await Promise.all(closeTasks);
399
453
  }
@@ -564,19 +618,15 @@ export function sendHealthRouteResponse(store, ctx, authPresent) {
564
618
  }
565
619
  function resolveRequestedProtocolVersion(body) {
566
620
  if (!isObject(body))
567
- return DEFAULT_MCP_PROTOCOL_VERSION;
621
+ return '';
568
622
  const { params } = body;
569
623
  if (!isObject(params))
570
- return DEFAULT_MCP_PROTOCOL_VERSION;
624
+ return '';
571
625
  const { protocolVersion: value } = params;
572
626
  if (typeof value !== 'string')
573
- return DEFAULT_MCP_PROTOCOL_VERSION;
627
+ return '';
574
628
  const normalized = value.trim();
575
- if (normalized.length === 0)
576
- return DEFAULT_MCP_PROTOCOL_VERSION;
577
- return SUPPORTED_MCP_PROTOCOL_VERSIONS.has(normalized)
578
- ? normalized
579
- : DEFAULT_MCP_PROTOCOL_VERSION;
629
+ return normalized;
580
630
  }
581
631
  function resolveProtocolVersionHeader(req) {
582
632
  const header = getHeaderValue(req, 'mcp-protocol-version');
@@ -588,6 +638,15 @@ function resolveProtocolVersionHeader(req) {
588
638
  function isInitializedNotification(method) {
589
639
  return method === 'notifications/initialized';
590
640
  }
641
+ function getBodyMeta(body) {
642
+ if (!isObject(body))
643
+ return undefined;
644
+ const { params } = body;
645
+ if (!isObject(params))
646
+ return undefined;
647
+ const meta = params['_meta'];
648
+ return isObject(meta) ? meta : undefined;
649
+ }
591
650
  function isPingRequest(method) {
592
651
  return method === 'ping';
593
652
  }
@@ -704,9 +763,7 @@ class McpSessionGateway {
704
763
  status: 406,
705
764
  sessionId,
706
765
  });
707
- sendJson(ctx.res, 406, {
708
- error: 'We need you to use "text/event-stream" for this connection.',
709
- });
766
+ sendError(ctx.res, ErrorCode.InvalidRequest, 'We need you to use "text/event-stream" for this connection.', 406);
710
767
  return;
711
768
  }
712
769
  logDebug('MCP GET received', { sessionId }, Loggers.LOG_HTTP);
@@ -734,9 +791,7 @@ class McpSessionGateway {
734
791
  reason: 'accept_missing_json_or_event_stream',
735
792
  status: 406,
736
793
  });
737
- sendJson(ctx.res, 406, {
738
- error: 'We need the request to accept both "application/json" and "text/event-stream".',
739
- });
794
+ sendError(ctx.res, ErrorCode.InvalidRequest, 'We need the request to accept both "application/json" and "text/event-stream".', 406);
740
795
  return null;
741
796
  }
742
797
  const { body } = ctx;
@@ -800,7 +855,7 @@ class McpSessionGateway {
800
855
  mcpCode: -32600,
801
856
  rpcId: requestId,
802
857
  });
803
- sendError(ctx.res, -32600, "We couldn't find a session ID for your request. Please ensure you have an active session.", 400, requestId);
858
+ sendError(ctx.res, -32600, MISSING_SESSION_ID_MESSAGE, 400, requestId);
804
859
  return false;
805
860
  }
806
861
  return true;
@@ -823,7 +878,7 @@ class McpSessionGateway {
823
878
  sessionId,
824
879
  rpcId: requestId,
825
880
  });
826
- sendError(ctx.res, -32600, "Your session hasn't been initialized yet. Please wait a moment and try again.", 400, requestId);
881
+ sendError(ctx.res, -32600, SESSION_NOT_INITIALIZED_MESSAGE, 400, requestId);
827
882
  return false;
828
883
  }
829
884
  async getOrCreateTransport(ctx, requestId) {
@@ -837,40 +892,39 @@ class McpSessionGateway {
837
892
  return null;
838
893
  return this.createNewSession(ctx, requestId, negotiatedProtocolVersion);
839
894
  }
895
+ rejectInitializeRequest(ctx, requestId, reason, mcpCode, message) {
896
+ logGatewayRejection({
897
+ message: 'Rejected MCP initialize request',
898
+ method: ctx.method,
899
+ path: ctx.url.pathname,
900
+ reason,
901
+ status: 400,
902
+ mcpCode,
903
+ rpcId: requestId,
904
+ });
905
+ sendError(ctx.res, mcpCode, message, 400, requestId);
906
+ return null;
907
+ }
840
908
  getInitializeProtocolVersion(ctx, requestId) {
841
909
  if (!isMcpRequestBody(ctx.body)) {
842
- logGatewayRejection({
843
- message: 'Rejected MCP initialize request',
844
- method: ctx.method,
845
- path: ctx.url.pathname,
846
- reason: 'missing_session_id',
847
- status: 400,
848
- mcpCode: -32600,
849
- rpcId: requestId,
850
- });
851
- sendError(ctx.res, -32600, "We couldn't find a session ID for your request. Please ensure you have an active session.", 400, requestId);
852
- return null;
910
+ return this.rejectInitializeRequest(ctx, requestId, 'missing_session_id', -32600, MISSING_SESSION_ID_MESSAGE);
853
911
  }
854
912
  if (!isInitializeRequest(ctx.body)) {
855
913
  const invalidInitialize = ctx.body.method === 'initialize';
856
- logGatewayRejection({
857
- message: 'Rejected MCP initialize request',
858
- method: ctx.method,
859
- path: ctx.url.pathname,
860
- reason: invalidInitialize
861
- ? 'invalid_initialize_request'
862
- : 'missing_session_id',
863
- status: 400,
864
- mcpCode: invalidInitialize ? -32602 : -32600,
865
- rpcId: requestId,
866
- });
867
- sendError(ctx.res, invalidInitialize ? -32602 : -32600, invalidInitialize
914
+ return this.rejectInitializeRequest(ctx, requestId, invalidInitialize ? 'invalid_initialize_request' : 'missing_session_id', invalidInitialize ? -32602 : -32600, invalidInitialize
868
915
  ? 'The initialize request format is invalid. Please double-check your parameters.'
869
- : "We couldn't find a session ID for your request. Please ensure you have an active session.", 400, requestId);
870
- return null;
916
+ : MISSING_SESSION_ID_MESSAGE);
871
917
  }
872
918
  const negotiatedProtocolVersion = resolveRequestedProtocolVersion(ctx.body);
919
+ if (negotiatedProtocolVersion.length === 0 ||
920
+ !SUPPORTED_MCP_PROTOCOL_VERSIONS.has(negotiatedProtocolVersion)) {
921
+ return this.rejectInitializeRequest(ctx, requestId, 'unsupported_protocol_version', -32600, `The protocol version '${negotiatedProtocolVersion || '(missing)'}' isn't supported right now. Please check and try again.`);
922
+ }
873
923
  const headerProtocolVersion = resolveProtocolVersionHeader(ctx.req);
924
+ if (headerProtocolVersion &&
925
+ !SUPPORTED_MCP_PROTOCOL_VERSIONS.has(headerProtocolVersion)) {
926
+ return this.rejectInitializeRequest(ctx, requestId, 'unsupported_protocol_version_header', -32600, `The protocol version '${headerProtocolVersion}' isn't supported right now. Please check and try again.`);
927
+ }
874
928
  if (headerProtocolVersion &&
875
929
  headerProtocolVersion !== negotiatedProtocolVersion) {
876
930
  logGatewayRejection({
@@ -908,20 +962,30 @@ class McpSessionGateway {
908
962
  return null;
909
963
  return { sessionId, session };
910
964
  }
965
+ sendMissingSessionId(res, requestId = null) {
966
+ sendError(res, -32600, MISSING_SESSION_ID_MESSAGE, 400, requestId);
967
+ return null;
968
+ }
969
+ sendSessionNotInitialized(res, requestId = null) {
970
+ sendError(res, -32600, SESSION_NOT_INITIALIZED_MESSAGE, 400, requestId);
971
+ return null;
972
+ }
973
+ sendSessionUnavailable(res, requestId = null, status = 404) {
974
+ sendError(res, -32600, SESSION_NOT_FOUND_MESSAGE, status, requestId);
975
+ return null;
976
+ }
911
977
  getRequiredAuthenticatedSession(ctx, requestId = null, options) {
912
978
  const state = this.getOptionalAuthenticatedSession(ctx, requestId);
913
979
  if (!state)
914
980
  return null;
915
981
  const { sessionId, session } = state;
916
982
  if (!sessionId || !session) {
917
- sendError(ctx.res, -32600, "We couldn't find a session ID for your request. Please ensure you have an active session.", 400, requestId);
918
- return null;
983
+ return this.sendMissingSessionId(ctx.res, requestId);
919
984
  }
920
985
  if (!this.ensureSessionProtocolVersion(ctx, session))
921
986
  return null;
922
987
  if (options?.requireInitialized && !session.protocolInitialized) {
923
- sendError(ctx.res, -32600, "Your session hasn't been initialized yet. Please wait a moment and try again.", 400, requestId);
924
- return null;
988
+ return this.sendSessionNotInitialized(ctx.res, requestId);
925
989
  }
926
990
  return { sessionId, session };
927
991
  }
@@ -937,7 +1001,7 @@ class McpSessionGateway {
937
1001
  sessionId,
938
1002
  rpcId: requestId,
939
1003
  });
940
- sendError(res, -32600, "We couldn't find your session. It might have expired or been closed.", 404, requestId);
1004
+ sendError(res, -32600, SESSION_NOT_FOUND_MESSAGE, 404, requestId);
941
1005
  return null;
942
1006
  }
943
1007
  if (!authFingerprint || session.authFingerprint !== authFingerprint) {
@@ -950,8 +1014,7 @@ class McpSessionGateway {
950
1014
  sessionId,
951
1015
  rpcId: requestId,
952
1016
  });
953
- sendError(res, -32600, "We couldn't find your session. It might have expired or been closed.", 404, requestId);
954
- return null;
1017
+ return this.sendSessionUnavailable(res, requestId);
955
1018
  }
956
1019
  return session;
957
1020
  }
@@ -1134,6 +1197,13 @@ class HttpDispatcher {
1134
1197
  this.store = store;
1135
1198
  this.mcpGateway = mcpGateway;
1136
1199
  }
1200
+ sendAuthFailure(ctx, status, message) {
1201
+ if (isMcpRoute(ctx.url.pathname)) {
1202
+ sendError(ctx.res, -32000, message, status);
1203
+ return;
1204
+ }
1205
+ sendJson(ctx.res, status, { error: message });
1206
+ }
1137
1207
  async tryHandleHealthRoute(ctx) {
1138
1208
  if (!shouldHandleHealthRoute(ctx))
1139
1209
  return false;
@@ -1155,7 +1225,7 @@ class HttpDispatcher {
1155
1225
  return false;
1156
1226
  if (!isProtectedResourceMetadataPath(ctx.url.pathname))
1157
1227
  return false;
1158
- const document = buildProtectedResourceMetadataDocument(ctx.req);
1228
+ const document = buildProtectedResourceMetadataDocument();
1159
1229
  sendJson(ctx.res, 200, document);
1160
1230
  return true;
1161
1231
  }
@@ -1217,11 +1287,11 @@ class HttpDispatcher {
1217
1287
  logWarn('Authentication failed', { message, method: ctx.method, path: ctx.url.pathname }, Loggers.LOG_AUTH);
1218
1288
  if (isInsufficientScopeError(err)) {
1219
1289
  applyInsufficientScopeAuthHeaders(ctx.req, ctx.res, err.requiredScopes, message);
1220
- sendError(ctx.res, -32000, message, 403);
1290
+ this.sendAuthFailure(ctx, 403, message);
1221
1291
  return null;
1222
1292
  }
1223
1293
  applyUnauthorizedAuthHeaders(ctx.req, ctx.res);
1224
- sendError(ctx.res, -32000, message, 401);
1294
+ this.sendAuthFailure(ctx, 401, message);
1225
1295
  return null;
1226
1296
  }
1227
1297
  }
@@ -1300,7 +1370,7 @@ class HttpRequestPipeline {
1300
1370
  return;
1301
1371
  if (!(await this.populateRequestBody(ctx, rawReq)))
1302
1372
  return;
1303
- await this.dispatcher.dispatch(ctx);
1373
+ await runWithTraceContext(getBodyMeta(ctx.body), () => this.dispatcher.dispatch(ctx));
1304
1374
  });
1305
1375
  }
1306
1376
  finally {
@@ -1342,9 +1412,6 @@ class HttpRequestPipeline {
1342
1412
  return false;
1343
1413
  }
1344
1414
  if (!this.rateLimiter.check(ctx)) {
1345
- sendJson(ctx.res, 429, {
1346
- error: "You're sending requests a bit too quickly. Please slow down and try again.",
1347
- });
1348
1415
  drainRequest(rawReq);
1349
1416
  return false;
1350
1417
  }
@@ -1459,7 +1526,7 @@ export async function startHttpServer() {
1459
1526
  assertHttpModeConfiguration();
1460
1527
  enableHttpMode();
1461
1528
  resetEventLoopMonitoring();
1462
- const rateLimiter = createRateLimitManagerImpl(config.rateLimit);
1529
+ const rateLimiter = new RateLimiter(config.rateLimit);
1463
1530
  const sessionStore = createSessionStore(config.server.sessionTtlMs);
1464
1531
  const sessionCleanup = startSessionCleanupLoop(sessionStore, config.server.sessionTtlMs, {
1465
1532
  onEvictSession: (session) => {
@@ -1479,6 +1546,12 @@ export async function startHttpServer() {
1479
1546
  await listen(server, config.server.host, config.server.port);
1480
1547
  const port = resolveListeningPort(server, config.server.port);
1481
1548
  const protocol = config.server.https.enabled ? 'https' : 'http';
1549
+ if (!config.auth.publicBaseUrl) {
1550
+ const resolvedResourceUrl = new URL(config.auth.resourceUrl);
1551
+ resolvedResourceUrl.port = String(port);
1552
+ resolvedResourceUrl.protocol = `${protocol}:`;
1553
+ config.auth.resourceUrl = resolvedResourceUrl;
1554
+ }
1482
1555
  logInfo(`${protocol.toUpperCase()} server listening on port ${port}`, {
1483
1556
  platform: process.platform,
1484
1557
  arch: process.arch,
@@ -1,14 +1,22 @@
1
- import { type RequestContext } from './native.js';
1
+ import type { RequestContext } from './native.js';
2
2
  interface RateLimitConfig {
3
3
  maxRequests: number;
4
4
  windowMs: number;
5
5
  cleanupIntervalMs: number;
6
6
  enabled: boolean;
7
7
  }
8
- export interface RateLimitManagerImpl {
8
+ export declare class RateLimiter {
9
+ private readonly options;
10
+ private readonly store;
11
+ private readonly cleanup;
12
+ constructor(options: RateLimitConfig);
13
+ private startCleanupLoop;
14
+ private cleanupEntries;
15
+ private resetEntry;
16
+ private incrementEntry;
17
+ private createEntry;
9
18
  check(ctx: RequestContext): boolean;
10
19
  stop(): void;
11
20
  }
12
- export declare function createRateLimitManagerImpl(options: RateLimitConfig): RateLimitManagerImpl;
13
21
  export {};
14
22
  //# sourceMappingURL=rate-limit.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/http/rate-limit.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,KAAK,cAAc,EAAY,MAAM,aAAa,CAAC;AAY5D,UAAU,eAAe;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,oBAAoB;IACnC,KAAK,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC;IACpC,IAAI,IAAI,IAAI,CAAC;CACd;AA+FD,wBAAgB,0BAA0B,CACxC,OAAO,EAAE,eAAe,GACvB,oBAAoB,CAGtB"}
1
+ {"version":3,"file":"rate-limit.d.ts","sourceRoot":"","sources":["../../src/http/rate-limit.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAYlD,UAAU,eAAe;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,OAAO,EAAE,OAAO,CAAC;CAClB;AAcD,qBAAa,WAAW;IAIV,OAAO,CAAC,QAAQ,CAAC,OAAO;IAHpC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqC;IAC3D,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAyB;gBAEpB,OAAO,EAAE,eAAe;IAIrD,OAAO,CAAC,gBAAgB;IAkBxB,OAAO,CAAC,cAAc;IAStB,OAAO,CAAC,UAAU;IAMlB,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,WAAW;IAQnB,KAAK,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO;IAyCnC,IAAI,IAAI,IAAI;CAGb"}
@@ -1,11 +1,19 @@
1
+ import { ErrorCode } from '@modelcontextprotocol/sdk/types.js';
1
2
  import { Loggers, logWarn } from '../lib/core.js';
2
3
  import { isAbortError } from '../lib/error/index.js';
4
+ import { sendJsonRpcError } from '../lib/mcp-interop.js';
3
5
  import { startAbortableIntervalLoop } from '../lib/utils.js';
4
- import { sendJson } from './native.js';
5
- // ---------------------------------------------------------------------------
6
- // Rate limiter
7
- // ---------------------------------------------------------------------------
8
- class RateLimiter {
6
+ function sendJson(res, status, body) {
7
+ res.statusCode = status;
8
+ res.setHeader('Content-Type', 'application/json; charset=utf-8');
9
+ res.setHeader('X-Content-Type-Options', 'nosniff');
10
+ res.setHeader('Cache-Control', 'no-store');
11
+ res.end(JSON.stringify(body));
12
+ }
13
+ function isMcpEndpoint(pathname) {
14
+ return pathname === '/mcp' || pathname === '/mcp/';
15
+ }
16
+ export class RateLimiter {
9
17
  options;
10
18
  store = new Map();
11
19
  cleanup = new AbortController();
@@ -74,7 +82,12 @@ class RateLimiter {
74
82
  logWarn('Rate limit exceeded', { ip: key }, Loggers.LOG_RATE_LIMIT);
75
83
  const retryAfter = Math.max(1, Math.ceil((entry.resetTime - now) / 1000));
76
84
  ctx.res.setHeader('Retry-After', String(retryAfter));
77
- sendJson(ctx.res, 429, { error: 'Rate limit exceeded', retryAfter });
85
+ if (isMcpEndpoint(ctx.url.pathname)) {
86
+ sendJsonRpcError(ctx.res, 429, ErrorCode.InvalidRequest, 'Rate limit exceeded', null, { retryAfter });
87
+ }
88
+ else {
89
+ sendJson(ctx.res, 429, { error: 'Rate limit exceeded', retryAfter });
90
+ }
78
91
  return false;
79
92
  }
80
93
  return true;
@@ -83,7 +96,3 @@ class RateLimiter {
83
96
  this.cleanup.abort();
84
97
  }
85
98
  }
86
- export function createRateLimitManagerImpl(options) {
87
- const limiter = new RateLimiter(options);
88
- return limiter;
89
- }
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAUA,UAAU,SAAS;IACjB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,UAAU,eAAe;IACvB,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;CAC5B;AAED,UAAU,eAAe;IACvB,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,KAAK,cAAc,GAAG,eAAe,GAAG,eAAe,CAAC;AA2CxD,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc,CA2BpE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAWA,UAAU,SAAS;IACjB,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;CAC3B;AAED,UAAU,eAAe;IACvB,QAAQ,CAAC,EAAE,EAAE,IAAI,CAAC;IAClB,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAC;CAC5B;AAED,UAAU,eAAe;IACvB,QAAQ,CAAC,EAAE,EAAE,KAAK,CAAC;IACnB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,KAAK,cAAc,GAAG,eAAe,GAAG,eAAe,CAAC;AA2CxD,wBAAgB,cAAc,IAAI,MAAM,CAEvC;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc,CA2BpE"}
package/dist/index.js CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import process from 'node:process';
3
3
  import { parseArgs } from 'node:util';
4
- import { logError, Loggers, serverVersion } from './lib/core.js';
4
+ import { serverVersion } from './lib/config.js';
5
+ import { logError, Loggers } from './lib/core.js';
5
6
  import { getErrorMessage, toError } from './lib/error/index.js';
6
7
  import { startHttpServer } from './http/index.js';
7
8
  import { startStdioServer } from './server.js';
@@ -16,6 +16,7 @@ interface AuthConfig {
16
16
  revocationUrl: URL | undefined;
17
17
  registrationUrl: URL | undefined;
18
18
  introspectionUrl: URL | undefined;
19
+ publicBaseUrl: URL | undefined;
19
20
  resourceUrl: URL;
20
21
  requiredScopes: string[];
21
22
  clientId: string | undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"AAgDA,eAAO,MAAM,aAAa,EAAE,MAA2C,CAAC;AAIxE,MAAM,MAAM,QAAQ,GAChB,OAAO,GACP,MAAM,GACN,QAAQ,GACR,MAAM,GACN,OAAO,GACP,UAAU,CAAC;AAkCf,KAAK,mBAAmB,GAAG,SAAS,GAAG,SAAS,CAAC;AACjD,KAAK,QAAQ,GAAG,OAAO,GAAG,QAAQ,CAAC;AAgPnC,UAAU,oBAAoB;IAC5B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAyCD,UAAU,UAAU;IAClB,IAAI,EAAE,QAAQ,CAAC;IACf,SAAS,EAAE,GAAG,GAAG,SAAS,CAAC;IAC3B,gBAAgB,EAAE,GAAG,GAAG,SAAS,CAAC;IAClC,QAAQ,EAAE,GAAG,GAAG,SAAS,CAAC;IAC1B,aAAa,EAAE,GAAG,GAAG,SAAS,CAAC;IAC/B,eAAe,EAAE,GAAG,GAAG,SAAS,CAAC;IACjC,gBAAgB,EAAE,GAAG,GAAG,SAAS,CAAC;IAClC,WAAW,EAAE,GAAG,CAAC;IACjB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,YAAY,EAAE,MAAM,GAAG,SAAS,CAAC;IACjC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,UAAU,WAAW;IACnB,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,SAAS,CAAC;IAC5B,QAAQ,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7B,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC;CAC5B;AAqGD,UAAU,YAAY;IACpB,QAAQ,EAAE,OAAO,CAAC;CACnB;AAQD,UAAU,mBAAmB;IAC3B,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,gBAAgB,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,kBAAkB,EAAE,MAAM,GAAG,SAAS,CAAC;IACvC,wBAAwB,EAAE,MAAM,GAAG,SAAS,CAAC;IAC7C,eAAe,EAAE,MAAM,GAAG,SAAS,CAAC;IACpC,cAAc,EAAE,MAAM,CAAC;IACvB,uBAAuB,EAAE,OAAO,CAAC;IACjC,4BAA4B,EAAE,OAAO,CAAC;IACtC,2BAA2B,EAAE,OAAO,CAAC;CACtC;AAED,UAAU,eAAe;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,WAAW,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,oBAAoB,EAAE,MAAM,CAAC;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,mBAAmB,CAAC;CAC3B;AAuCD,UAAU,gBAAgB;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAWD,UAAU,kBAAkB;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,cAAc,EAAE,MAAM,CAAC;IACvB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,UAAU,EAAE,mBAAmB,CAAC;IAChC,oBAAoB,EAAE,oBAAoB,GAAG,SAAS,CAAC;CACxD;AAkBD,UAAU,cAAc;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,uBAAuB,EAAE,OAAO,CAAC;IACjC,mBAAmB,EAAE,OAAO,CAAC;CAC9B;AAmBD,UAAU,qBAAqB;IAC7B,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,iBAAiB,EAAE,MAAM,EAAE,CAAC;IAC5B,KAAK,EAAE,OAAO,CAAC;IACf,cAAc,EAAE,OAAO,CAAC;IACxB,iBAAiB,EAAE,OAAO,CAAC;IAC3B,OAAO,EAAE;QACP,MAAM,EAAE,MAAM,CAAC;QACf,UAAU,EAAE,MAAM,CAAC;QACnB,KAAK,EAAE,MAAM,CAAC;QACd,WAAW,EAAE,MAAM,CAAC;QACpB,SAAS,EAAE,MAAM,CAAC;KACnB,CAAC;CACH;AA2BD,UAAU,wBAAwB;IAChC,qBAAqB,EAAE,OAAO,CAAC;IAC/B,eAAe,EAAE,OAAO,CAAC;IACzB,eAAe,EAAE,OAAO,CAAC;IACzB,qBAAqB,EAAE,OAAO,CAAC;IAC/B,eAAe,EAAE,MAAM,EAAE,CAAC;CAC3B;AAiBD,eAAO,MAAM,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8ClB,CAAC;AAEF,wBAAgB,cAAc,IAAI,IAAI,CAErC"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/lib/config.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,10 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  import { findPackageJSON } from 'node:module';
3
+ import { isIP } from 'node:net';
3
4
  import process from 'node:process';
5
+ import { domainToASCII } from 'node:url';
4
6
  import { z } from 'zod';
5
- import { getErrorMessage } from './error/index.js';
6
- import { buildIpv4, isIP, normalizeHostname, stripTrailingDots, } from './net/url.js';
7
+ import { getErrorMessage } from './error/classes.js';
7
8
  // ── Version ─────────────────────────────────────────────────────────
8
9
  function getObjectProperty(value, key) {
9
10
  return value[key];
@@ -87,6 +88,25 @@ const ENV_BOOLEAN_SCHEMA = z.stringbool({
87
88
  falsy: ['false', '0', 'no', 'off'],
88
89
  });
89
90
  // ── Host parsing helpers ────────────────────────────────────────────
91
+ function buildIpv4(parts) {
92
+ return parts.join('.');
93
+ }
94
+ function stripTrailingDots(value) {
95
+ let result = value;
96
+ while (result.endsWith('.'))
97
+ result = result.slice(0, -1);
98
+ return result;
99
+ }
100
+ function normalizeHostname(value) {
101
+ const trimmed = value.trim();
102
+ if (!trimmed)
103
+ return null;
104
+ const lowered = trimmed.toLowerCase();
105
+ if (isIP(lowered))
106
+ return stripTrailingDots(lowered);
107
+ const ascii = domainToASCII(lowered);
108
+ return ascii ? stripTrailingDots(ascii) : null;
109
+ }
90
110
  function tryParseUrlHost(raw) {
91
111
  if (raw.includes('://')) {
92
112
  const hostname = URL.parse(raw)?.hostname;
@@ -277,7 +297,8 @@ function buildAuthConfig(baseUrl) {
277
297
  const revocationUrl = parseUrl('OAUTH_REVOCATION_URL');
278
298
  const registrationUrl = parseUrl('OAUTH_REGISTRATION_URL');
279
299
  const introspectionUrl = parseUrl('OAUTH_INTROSPECTION_URL');
280
- const resourceUrl = new URL('/mcp', baseUrl);
300
+ const publicBaseUrl = parseUrl('PUBLIC_BASE_URL');
301
+ const resourceUrl = new URL('/mcp', publicBaseUrl ?? baseUrl);
281
302
  const oauthConfigured = issuerUrl !== undefined ||
282
303
  authorizationUrl !== undefined ||
283
304
  tokenUrl !== undefined ||
@@ -293,6 +314,7 @@ function buildAuthConfig(baseUrl) {
293
314
  revocationUrl,
294
315
  registrationUrl,
295
316
  introspectionUrl,
317
+ publicBaseUrl,
296
318
  resourceUrl,
297
319
  requiredScopes: EnvParser.list(env['OAUTH_REQUIRED_SCOPES']),
298
320
  clientId: env['OAUTH_CLIENT_ID'],