@j0hanz/superfetch 2.2.2 → 2.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/README.md +363 -363
  2. package/dist/cache.d.ts +0 -1
  3. package/dist/cache.js +13 -25
  4. package/dist/config.d.ts +0 -1
  5. package/dist/config.js +9 -7
  6. package/dist/crypto.d.ts +0 -1
  7. package/dist/crypto.js +0 -1
  8. package/dist/dom-noise-removal.d.ts +0 -1
  9. package/dist/dom-noise-removal.js +35 -32
  10. package/dist/errors.d.ts +0 -1
  11. package/dist/errors.js +0 -1
  12. package/dist/fetch.d.ts +0 -1
  13. package/dist/fetch.js +45 -29
  14. package/dist/host-normalization.d.ts +1 -0
  15. package/dist/host-normalization.js +47 -0
  16. package/dist/http-native.d.ts +0 -1
  17. package/dist/http-native.js +73 -25
  18. package/dist/index.d.ts +0 -1
  19. package/dist/index.js +0 -1
  20. package/dist/instructions.md +41 -41
  21. package/dist/json.d.ts +0 -1
  22. package/dist/json.js +0 -1
  23. package/dist/language-detection.d.ts +0 -1
  24. package/dist/language-detection.js +10 -2
  25. package/dist/markdown-cleanup.d.ts +0 -1
  26. package/dist/markdown-cleanup.js +10 -10
  27. package/dist/mcp-validator.d.ts +14 -0
  28. package/dist/mcp-validator.js +22 -0
  29. package/dist/mcp.d.ts +0 -1
  30. package/dist/mcp.js +0 -1
  31. package/dist/observability.d.ts +0 -1
  32. package/dist/observability.js +5 -3
  33. package/dist/server-tuning.d.ts +9 -0
  34. package/dist/server-tuning.js +30 -0
  35. package/dist/{http-utils.d.ts → session.d.ts} +0 -25
  36. package/dist/{http-utils.js → session.js} +11 -104
  37. package/dist/tools.d.ts +0 -1
  38. package/dist/tools.js +19 -29
  39. package/dist/transform-types.d.ts +0 -1
  40. package/dist/transform-types.js +0 -1
  41. package/dist/transform.d.ts +0 -1
  42. package/dist/transform.js +85 -79
  43. package/dist/type-guards.d.ts +0 -1
  44. package/dist/type-guards.js +0 -1
  45. package/dist/workers/transform-worker.d.ts +0 -1
  46. package/dist/workers/transform-worker.js +29 -19
  47. package/package.json +85 -85
  48. package/dist/cache.d.ts.map +0 -1
  49. package/dist/cache.js.map +0 -1
  50. package/dist/config.d.ts.map +0 -1
  51. package/dist/config.js.map +0 -1
  52. package/dist/crypto.d.ts.map +0 -1
  53. package/dist/crypto.js.map +0 -1
  54. package/dist/dom-noise-removal.d.ts.map +0 -1
  55. package/dist/dom-noise-removal.js.map +0 -1
  56. package/dist/errors.d.ts.map +0 -1
  57. package/dist/errors.js.map +0 -1
  58. package/dist/fetch.d.ts.map +0 -1
  59. package/dist/fetch.js.map +0 -1
  60. package/dist/http-native.d.ts.map +0 -1
  61. package/dist/http-native.js.map +0 -1
  62. package/dist/http-utils.d.ts.map +0 -1
  63. package/dist/http-utils.js.map +0 -1
  64. package/dist/index.d.ts.map +0 -1
  65. package/dist/index.js.map +0 -1
  66. package/dist/json.d.ts.map +0 -1
  67. package/dist/json.js.map +0 -1
  68. package/dist/language-detection.d.ts.map +0 -1
  69. package/dist/language-detection.js.map +0 -1
  70. package/dist/markdown-cleanup.d.ts.map +0 -1
  71. package/dist/markdown-cleanup.js.map +0 -1
  72. package/dist/mcp.d.ts.map +0 -1
  73. package/dist/mcp.js.map +0 -1
  74. package/dist/observability.d.ts.map +0 -1
  75. package/dist/observability.js.map +0 -1
  76. package/dist/tools.d.ts.map +0 -1
  77. package/dist/tools.js.map +0 -1
  78. package/dist/transform-types.d.ts.map +0 -1
  79. package/dist/transform-types.js.map +0 -1
  80. package/dist/transform.d.ts.map +0 -1
  81. package/dist/transform.js.map +0 -1
  82. package/dist/type-guards.d.ts.map +0 -1
  83. package/dist/type-guards.js.map +0 -1
  84. package/dist/workers/transform-worker.d.ts.map +0 -1
  85. package/dist/workers/transform-worker.js.map +0 -1
package/dist/cache.d.ts CHANGED
@@ -40,4 +40,3 @@ export declare function registerCachedContentResource(server: McpServer): void;
40
40
  export declare function generateSafeFilename(url: string, title?: string, hashFallback?: string, extension?: string): string;
41
41
  export declare function handleDownload(res: ServerResponse, namespace: string, hash: string): void;
42
42
  export {};
43
- //# sourceMappingURL=cache.d.ts.map
package/dist/cache.js CHANGED
@@ -396,43 +396,32 @@ function buildMarkdownContentResponse(uri, content) {
396
396
  ],
397
397
  };
398
398
  }
399
- function isSingleParam(value) {
400
- return typeof value === 'string';
401
- }
402
399
  function parseDownloadParams(namespace, hash) {
403
- if (!isSingleParam(namespace) || !isSingleParam(hash))
404
- return null;
405
- if (!namespace || !hash)
400
+ const resolvedNamespace = resolveStringParam(namespace);
401
+ const resolvedHash = resolveStringParam(hash);
402
+ if (!resolvedNamespace || !resolvedHash)
406
403
  return null;
407
- if (!isValidNamespace(namespace))
404
+ if (!isValidNamespace(resolvedNamespace))
408
405
  return null;
409
- if (!isValidHash(hash))
406
+ if (!isValidHash(resolvedHash))
410
407
  return null;
411
- return { namespace, hash };
408
+ return { namespace: resolvedNamespace, hash: resolvedHash };
412
409
  }
413
410
  function buildCacheKeyFromParams(params) {
414
411
  return `${params.namespace}:${params.hash}`;
415
412
  }
413
+ function sendJsonError(res, status, error, code) {
414
+ res.writeHead(status, { 'Content-Type': 'application/json' });
415
+ res.end(JSON.stringify({ error, code }));
416
+ }
416
417
  function respondBadRequest(res, message) {
417
- res.writeHead(400, { 'Content-Type': 'application/json' });
418
- res.end(JSON.stringify({
419
- error: message,
420
- code: 'BAD_REQUEST',
421
- }));
418
+ sendJsonError(res, 400, message, 'BAD_REQUEST');
422
419
  }
423
420
  function respondNotFound(res) {
424
- res.writeHead(404, { 'Content-Type': 'application/json' });
425
- res.end(JSON.stringify({
426
- error: 'Content not found or expired',
427
- code: 'NOT_FOUND',
428
- }));
421
+ sendJsonError(res, 404, 'Content not found or expired', 'NOT_FOUND');
429
422
  }
430
423
  function respondServiceUnavailable(res) {
431
- res.writeHead(503, { 'Content-Type': 'application/json' });
432
- res.end(JSON.stringify({
433
- error: 'Download service is disabled',
434
- code: 'SERVICE_UNAVAILABLE',
435
- }));
424
+ sendJsonError(res, 503, 'Download service is disabled', 'SERVICE_UNAVAILABLE');
436
425
  }
437
426
  export function generateSafeFilename(url, title, hashFallback, extension = '.md') {
438
427
  const fromUrl = extractFilenameFromUrl(url);
@@ -567,4 +556,3 @@ export function handleDownload(res, namespace, hash) {
567
556
  logDebug('Serving download', { cacheKey, fileName: payload.fileName });
568
557
  sendDownloadPayload(res, payload);
569
558
  }
570
- //# sourceMappingURL=cache.js.map
package/dist/config.d.ts CHANGED
@@ -89,4 +89,3 @@ export declare const config: {
89
89
  };
90
90
  export declare function enableHttpMode(): void;
91
91
  export {};
92
- //# sourceMappingURL=config.d.ts.map
package/dist/config.js CHANGED
@@ -73,6 +73,9 @@ function parseUrlEnv(value, name) {
73
73
  }
74
74
  return new URL(value);
75
75
  }
76
+ function readUrlEnv(name) {
77
+ return parseUrlEnv(process.env[name], name);
78
+ }
76
79
  function parseAllowedHosts(envValue) {
77
80
  const hosts = new Set();
78
81
  for (const entry of parseList(envValue)) {
@@ -108,16 +111,16 @@ const DEFAULT_TOOL_TIMEOUT_MS = TIMEOUT.DEFAULT_FETCH_TIMEOUT_MS +
108
111
  5000;
109
112
  function readCoreOAuthUrls() {
110
113
  return {
111
- issuerUrl: parseUrlEnv(process.env.OAUTH_ISSUER_URL, 'OAUTH_ISSUER_URL'),
112
- authorizationUrl: parseUrlEnv(process.env.OAUTH_AUTHORIZATION_URL, 'OAUTH_AUTHORIZATION_URL'),
113
- tokenUrl: parseUrlEnv(process.env.OAUTH_TOKEN_URL, 'OAUTH_TOKEN_URL'),
114
+ issuerUrl: readUrlEnv('OAUTH_ISSUER_URL'),
115
+ authorizationUrl: readUrlEnv('OAUTH_AUTHORIZATION_URL'),
116
+ tokenUrl: readUrlEnv('OAUTH_TOKEN_URL'),
114
117
  };
115
118
  }
116
119
  function readOptionalOAuthUrls(baseUrl) {
117
120
  return {
118
- revocationUrl: parseUrlEnv(process.env.OAUTH_REVOCATION_URL, 'OAUTH_REVOCATION_URL'),
119
- registrationUrl: parseUrlEnv(process.env.OAUTH_REGISTRATION_URL, 'OAUTH_REGISTRATION_URL'),
120
- introspectionUrl: parseUrlEnv(process.env.OAUTH_INTROSPECTION_URL, 'OAUTH_INTROSPECTION_URL'),
121
+ revocationUrl: readUrlEnv('OAUTH_REVOCATION_URL'),
122
+ registrationUrl: readUrlEnv('OAUTH_REGISTRATION_URL'),
123
+ introspectionUrl: readUrlEnv('OAUTH_INTROSPECTION_URL'),
121
124
  resourceUrl: parseUrlEnv(process.env.OAUTH_RESOURCE_URL, 'OAUTH_RESOURCE_URL') ??
122
125
  new URL('/mcp', baseUrl),
123
126
  };
@@ -271,4 +274,3 @@ export const config = {
271
274
  export function enableHttpMode() {
272
275
  runtimeState.httpMode = true;
273
276
  }
274
- //# sourceMappingURL=config.js.map
package/dist/crypto.d.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  export declare function timingSafeEqualUtf8(a: string, b: string): boolean;
2
2
  export declare function sha256Hex(input: string | Uint8Array): string;
3
- //# sourceMappingURL=crypto.d.ts.map
package/dist/crypto.js CHANGED
@@ -30,4 +30,3 @@ function hashHex(algorithm, input) {
30
30
  export function sha256Hex(input) {
31
31
  return hashHex('sha256', input);
32
32
  }
33
- //# sourceMappingURL=crypto.js.map
@@ -3,4 +3,3 @@
3
3
  * Used as a preprocessing step before markdown conversion.
4
4
  */
5
5
  export declare function removeNoiseFromHtml(html: string, document?: Document, baseUrl?: string): string;
6
- //# sourceMappingURL=dom-noise-removal.d.ts.map
@@ -56,6 +56,37 @@ const STRUCTURAL_TAGS = new Set([
56
56
  'canvas',
57
57
  ]);
58
58
  const ALWAYS_NOISE_TAGS = new Set(['nav', 'footer']);
59
+ const BASE_NOISE_SELECTORS = [
60
+ 'nav',
61
+ 'footer',
62
+ 'header[class*="site"]',
63
+ 'header[class*="nav"]',
64
+ 'header[class*="menu"]',
65
+ '[role="banner"]',
66
+ '[role="navigation"]',
67
+ '[role="dialog"]',
68
+ '[style*="display: none"]',
69
+ '[style*="display:none"]',
70
+ '[hidden]',
71
+ '[aria-hidden="true"]',
72
+ ];
73
+ const BASE_NOISE_SELECTOR = BASE_NOISE_SELECTORS.join(',');
74
+ const CANDIDATE_NOISE_SELECTOR = [
75
+ ...STRUCTURAL_TAGS,
76
+ ...ALWAYS_NOISE_TAGS,
77
+ 'aside',
78
+ 'header',
79
+ '[class]',
80
+ '[id]',
81
+ '[role]',
82
+ '[style]',
83
+ ].join(',');
84
+ function buildNoiseSelector(extraSelectors) {
85
+ const extra = extraSelectors.filter((selector) => selector.trim().length > 0);
86
+ if (extra.length === 0)
87
+ return BASE_NOISE_SELECTOR;
88
+ return `${BASE_NOISE_SELECTOR},${extra.join(',')}`;
89
+ }
59
90
  const NAVIGATION_ROLES = new Set([
60
91
  'navigation',
61
92
  'banner',
@@ -309,39 +340,12 @@ function removeNoiseNodes(nodes, shouldCheckNoise = true) {
309
340
  function stripNoiseNodes(document) {
310
341
  // Pass 1: Trusted selectors (Common noise)
311
342
  // We trust these selectors match actual noise, so we skip the expensive isNoiseElement check
312
- const baseSelectors = [
313
- 'nav',
314
- 'footer',
315
- 'header[class*="site"]',
316
- 'header[class*="nav"]',
317
- 'header[class*="menu"]',
318
- '[role="banner"]',
319
- '[role="navigation"]',
320
- '[role="dialog"]',
321
- '[style*="display: none"]',
322
- '[style*="display:none"]',
323
- '[hidden]',
324
- '[aria-hidden="true"]',
325
- ];
326
343
  // Add user-configured extra selectors
327
- const extraSelectors = config.noiseRemoval.extraSelectors.filter((s) => s.trim().length > 0);
328
- const targetSelectors = [...baseSelectors, ...extraSelectors].join(',');
329
- if (targetSelectors) {
330
- const potentialNoiseNodes = document.querySelectorAll(targetSelectors);
331
- removeNoiseNodes(potentialNoiseNodes, false);
332
- }
344
+ const targetSelectors = buildNoiseSelector(config.noiseRemoval.extraSelectors);
345
+ const potentialNoiseNodes = document.querySelectorAll(targetSelectors);
346
+ removeNoiseNodes(potentialNoiseNodes, false);
333
347
  // Second pass: check remaining elements for noise patterns (promo, fixed positioning, etc.)
334
- const candidateSelectors = [
335
- ...STRUCTURAL_TAGS,
336
- ...ALWAYS_NOISE_TAGS,
337
- 'aside',
338
- 'header',
339
- '[class]',
340
- '[id]',
341
- '[role]',
342
- '[style]',
343
- ].join(',');
344
- const allElements = document.querySelectorAll(candidateSelectors);
348
+ const allElements = document.querySelectorAll(CANDIDATE_NOISE_SELECTOR);
345
349
  removeNoiseNodes(allElements, true);
346
350
  }
347
351
  // ─────────────────────────────────────────────────────────────────────────────
@@ -479,4 +483,3 @@ export function removeNoiseFromHtml(html, document, baseUrl) {
479
483
  return html;
480
484
  }
481
485
  }
482
- //# sourceMappingURL=dom-noise-removal.js.map
package/dist/errors.d.ts CHANGED
@@ -8,4 +8,3 @@ export declare class FetchError extends Error {
8
8
  export declare function getErrorMessage(error: unknown): string;
9
9
  export declare function createErrorWithCode(message: string, code: string): NodeJS.ErrnoException;
10
10
  export declare function isSystemError(error: unknown): error is NodeJS.ErrnoException;
11
- //# sourceMappingURL=errors.d.ts.map
package/dist/errors.js CHANGED
@@ -42,4 +42,3 @@ export function isSystemError(error) {
42
42
  const { code } = error;
43
43
  return typeof code === 'string';
44
44
  }
45
- //# sourceMappingURL=errors.js.map
package/dist/fetch.d.ts CHANGED
@@ -38,4 +38,3 @@ export declare function readResponseText(response: Response, url: string, maxByt
38
38
  }>;
39
39
  export declare function fetchNormalizedUrl(normalizedUrl: string, options?: FetchOptions): Promise<string>;
40
40
  export {};
41
- //# sourceMappingURL=fetch.d.ts.map
package/dist/fetch.js CHANGED
@@ -503,6 +503,12 @@ function createRateLimitError(url, headerValue) {
503
503
  function createHttpError(url, status, statusText) {
504
504
  return new FetchError(`HTTP ${status}: ${statusText}`, url, status);
505
505
  }
506
+ function createTooManyRedirectsError(url) {
507
+ return new FetchError('Too many redirects', url);
508
+ }
509
+ function createMissingRedirectLocationError(url) {
510
+ return new FetchError('Redirect response missing Location header', url);
511
+ }
506
512
  function createSizeLimitError(url, maxBytes) {
507
513
  return new FetchError(`Response exceeds maximum size of ${maxBytes} bytes`, url);
508
514
  }
@@ -534,21 +540,29 @@ function resolveErrorUrl(error, fallback) {
534
540
  return requestUrl;
535
541
  return fallback;
536
542
  }
537
- function mapFetchError(error, fallbackUrl, timeoutMs) {
538
- if (error instanceof FetchError)
539
- return error;
540
- const url = resolveErrorUrl(error, fallbackUrl);
541
- if (isAbortError(error)) {
542
- if (isTimeoutError(error)) {
543
- return createTimeoutError(url, timeoutMs);
544
- }
545
- return createCanceledError(url);
543
+ function resolveAbortFetchError(error, url, timeoutMs) {
544
+ if (!isAbortError(error))
545
+ return null;
546
+ if (isTimeoutError(error)) {
547
+ return createTimeoutError(url, timeoutMs);
546
548
  }
549
+ return createCanceledError(url);
550
+ }
551
+ function resolveUnexpectedFetchError(error, url) {
547
552
  if (error instanceof Error) {
548
553
  return createNetworkError(url, error.message);
549
554
  }
550
555
  return createUnknownError(url, 'Unexpected error');
551
556
  }
557
+ function mapFetchError(error, fallbackUrl, timeoutMs) {
558
+ if (error instanceof FetchError)
559
+ return error;
560
+ const url = resolveErrorUrl(error, fallbackUrl);
561
+ const abortError = resolveAbortFetchError(error, url, timeoutMs);
562
+ if (abortError)
563
+ return abortError;
564
+ return resolveUnexpectedFetchError(error, url);
565
+ }
552
566
  const fetchChannel = diagnosticsChannel.channel('superfetch.fetch');
553
567
  function publishFetchEvent(event) {
554
568
  if (!fetchChannel.hasSubscribers)
@@ -713,14 +727,14 @@ function assertRedirectWithinLimit(response, currentUrl, redirectLimit, redirect
713
727
  if (redirectCount < redirectLimit)
714
728
  return;
715
729
  cancelResponseBody(response);
716
- throw new FetchError('Too many redirects', currentUrl);
730
+ throw createTooManyRedirectsError(currentUrl);
717
731
  }
718
732
  function getRedirectLocation(response, currentUrl) {
719
733
  const location = response.headers.get('location');
720
734
  if (location)
721
735
  return location;
722
736
  cancelResponseBody(response);
723
- throw new FetchError('Redirect response missing Location header', currentUrl);
737
+ throw createMissingRedirectLocationError(currentUrl);
724
738
  }
725
739
  function annotateRedirectError(error, url) {
726
740
  if (!isObject(error))
@@ -737,26 +751,26 @@ function resolveRedirectTarget(baseUrl, location) {
737
751
  }
738
752
  return validateAndNormalizeUrl(resolved.href);
739
753
  }
754
+ async function withRedirectErrorContext(url, fn) {
755
+ try {
756
+ return await fn();
757
+ }
758
+ catch (error) {
759
+ annotateRedirectError(error, url);
760
+ throw error;
761
+ }
762
+ }
740
763
  export async function fetchWithRedirects(url, init, maxRedirects) {
741
764
  let currentUrl = url;
742
765
  const redirectLimit = Math.max(0, maxRedirects);
743
766
  for (let redirectCount = 0; redirectCount <= redirectLimit; redirectCount += 1) {
744
- const { response, nextUrl } = await performFetchCycleSafely(currentUrl, init, redirectLimit, redirectCount);
767
+ const { response, nextUrl } = await withRedirectErrorContext(currentUrl, () => performFetchCycle(currentUrl, init, redirectLimit, redirectCount));
745
768
  if (!nextUrl) {
746
769
  return { response, url: currentUrl };
747
770
  }
748
771
  currentUrl = nextUrl;
749
772
  }
750
- throw new FetchError('Too many redirects', currentUrl);
751
- }
752
- async function performFetchCycleSafely(currentUrl, init, redirectLimit, redirectCount) {
753
- try {
754
- return await performFetchCycle(currentUrl, init, redirectLimit, redirectCount);
755
- }
756
- catch (error) {
757
- annotateRedirectError(error, currentUrl);
758
- throw error;
759
- }
773
+ throw createTooManyRedirectsError(currentUrl);
760
774
  }
761
775
  function assertContentLengthWithinLimit(response, url, maxBytes) {
762
776
  const contentLengthHeader = response.headers.get('content-length');
@@ -841,15 +855,18 @@ async function readStreamWithLimit(stream, url, maxBytes, signal) {
841
855
  finalizeRead(state);
842
856
  return { text: state.parts.join(''), size: state.total };
843
857
  }
858
+ async function readResponseTextFallback(response, url, maxBytes) {
859
+ const text = await response.text();
860
+ const size = Buffer.byteLength(text);
861
+ if (size > maxBytes) {
862
+ throw createSizeLimitError(url, maxBytes);
863
+ }
864
+ return { text, size };
865
+ }
844
866
  export async function readResponseText(response, url, maxBytes, signal) {
845
867
  assertContentLengthWithinLimit(response, url, maxBytes);
846
868
  if (!response.body) {
847
- const text = await response.text();
848
- const size = Buffer.byteLength(text);
849
- if (size > maxBytes) {
850
- throw createSizeLimitError(url, maxBytes);
851
- }
852
- return { text, size };
869
+ return readResponseTextFallback(response, url, maxBytes);
853
870
  }
854
871
  return readStreamWithLimit(response.body, url, maxBytes, signal);
855
872
  }
@@ -925,4 +942,3 @@ export async function fetchNormalizedUrl(normalizedUrl, options) {
925
942
  const requestInit = buildRequestInit(headers, signal);
926
943
  return fetchWithTelemetry(normalizedUrl, requestInit, timeoutMs);
927
944
  }
928
- //# sourceMappingURL=fetch.js.map
@@ -0,0 +1 @@
1
+ export declare function normalizeHost(value: string): string | null;
@@ -0,0 +1,47 @@
1
+ import { isIP } from 'node:net';
2
+ export function normalizeHost(value) {
3
+ const trimmed = value.trim().toLowerCase();
4
+ if (!trimmed)
5
+ return null;
6
+ const first = takeFirstHostValue(trimmed);
7
+ if (!first)
8
+ return null;
9
+ const ipv6 = stripIpv6Brackets(first);
10
+ if (ipv6)
11
+ return stripTrailingDots(ipv6);
12
+ if (isIpV6Literal(first)) {
13
+ return stripTrailingDots(first);
14
+ }
15
+ return stripTrailingDots(stripPortIfPresent(first));
16
+ }
17
+ function takeFirstHostValue(value) {
18
+ const first = value.split(',')[0];
19
+ if (!first)
20
+ return null;
21
+ const trimmed = first.trim();
22
+ return trimmed ? trimmed : null;
23
+ }
24
+ function stripIpv6Brackets(value) {
25
+ if (!value.startsWith('['))
26
+ return null;
27
+ const end = value.indexOf(']');
28
+ if (end === -1)
29
+ return null;
30
+ return value.slice(1, end);
31
+ }
32
+ function stripPortIfPresent(value) {
33
+ const colonIndex = value.indexOf(':');
34
+ if (colonIndex === -1)
35
+ return value;
36
+ return value.slice(0, colonIndex);
37
+ }
38
+ function isIpV6Literal(value) {
39
+ return isIP(value) === 6;
40
+ }
41
+ function stripTrailingDots(value) {
42
+ let result = value;
43
+ while (result.endsWith('.')) {
44
+ result = result.slice(0, -1);
45
+ }
46
+ return result;
47
+ }
@@ -3,4 +3,3 @@ export declare function startHttpServer(): Promise<{
3
3
  port: number;
4
4
  host: string;
5
5
  }>;
6
- //# sourceMappingURL=http-native.d.ts.map
@@ -8,10 +8,47 @@ import { isInitializeRequest } from '@modelcontextprotocol/sdk/types.js';
8
8
  import { handleDownload } from './cache.js';
9
9
  import { config, enableHttpMode } from './config.js';
10
10
  import { timingSafeEqualUtf8 } from './crypto.js';
11
- import { acceptsEventStream, applyHttpServerTuning, composeCloseHandlers, createSessionStore, createSlotTracker, drainConnectionsOnShutdown, ensureSessionCapacity, isJsonRpcBatchRequest, isMcpRequestBody, normalizeHost, reserveSessionSlot, startSessionCleanupLoop, } from './http-utils.js';
11
+ import { normalizeHost } from './host-normalization.js';
12
+ import { acceptsEventStream, isJsonRpcBatchRequest, isMcpRequestBody, } from './mcp-validator.js';
12
13
  import { createMcpServer } from './mcp.js';
13
14
  import { logError, logInfo, logWarn } from './observability.js';
15
+ import { applyHttpServerTuning, drainConnectionsOnShutdown, } from './server-tuning.js';
16
+ import { composeCloseHandlers, createSessionStore, createSlotTracker, ensureSessionCapacity, reserveSessionSlot, startSessionCleanupLoop, } from './session.js';
14
17
  import { isObject } from './type-guards.js';
18
+ function createTransportAdapter(transportImpl) {
19
+ const noopOnClose = () => { };
20
+ const noopOnError = () => { };
21
+ const noopOnMessage = () => { };
22
+ let oncloseHandler = noopOnClose;
23
+ let onerrorHandler = noopOnError;
24
+ let onmessageHandler = noopOnMessage;
25
+ return {
26
+ start: () => transportImpl.start(),
27
+ send: (message, options) => transportImpl.send(message, options),
28
+ close: () => transportImpl.close(),
29
+ get onclose() {
30
+ return oncloseHandler;
31
+ },
32
+ set onclose(handler) {
33
+ oncloseHandler = handler;
34
+ transportImpl.onclose = handler;
35
+ },
36
+ get onerror() {
37
+ return onerrorHandler;
38
+ },
39
+ set onerror(handler) {
40
+ onerrorHandler = handler;
41
+ transportImpl.onerror = handler;
42
+ },
43
+ get onmessage() {
44
+ return onmessageHandler;
45
+ },
46
+ set onmessage(handler) {
47
+ onmessageHandler = handler;
48
+ transportImpl.onmessage = handler;
49
+ },
50
+ };
51
+ }
15
52
  function shimResponse(res) {
16
53
  const shim = res;
17
54
  shim.status = function (code) {
@@ -144,26 +181,26 @@ function resolveOriginHost(origin) {
144
181
  return null;
145
182
  }
146
183
  }
184
+ function rejectHostRequest(res, status, message) {
185
+ res.status(status).json({ error: message });
186
+ return false;
187
+ }
147
188
  function validateHostAndOrigin(req, res) {
148
189
  const host = resolveHostHeader(req);
149
190
  if (!host) {
150
- res.status(400).json({ error: 'Missing or invalid Host header' });
151
- return false;
191
+ return rejectHostRequest(res, 400, 'Missing or invalid Host header');
152
192
  }
153
193
  if (!ALLOWED_HOSTS.has(host)) {
154
- res.status(403).json({ error: 'Host not allowed' });
155
- return false;
194
+ return rejectHostRequest(res, 403, 'Host not allowed');
156
195
  }
157
196
  const originHeader = getHeaderValue(req, 'origin');
158
197
  if (originHeader) {
159
198
  const originHost = resolveOriginHost(originHeader);
160
199
  if (!originHost) {
161
- res.status(403).json({ error: 'Invalid Origin header' });
162
- return false;
200
+ return rejectHostRequest(res, 403, 'Invalid Origin header');
163
201
  }
164
202
  if (!ALLOWED_HOSTS.has(originHost)) {
165
- res.status(403).json({ error: 'Origin not allowed' });
166
- return false;
203
+ return rejectHostRequest(res, 403, 'Origin not allowed');
167
204
  }
168
205
  }
169
206
  return true;
@@ -318,24 +355,35 @@ async function verifyWithIntrospection(token) {
318
355
  throw new InvalidTokenError('Token is inactive');
319
356
  return buildIntrospectionAuthInfo(token, payload);
320
357
  }
358
+ function resolveBearerToken(authHeader) {
359
+ const [type, token] = authHeader.split(' ');
360
+ if (type !== 'Bearer' || !token) {
361
+ throw new InvalidTokenError('Invalid Authorization header format');
362
+ }
363
+ return token;
364
+ }
365
+ function authenticateWithToken(token) {
366
+ return config.auth.mode === 'oauth'
367
+ ? verifyWithIntrospection(token)
368
+ : Promise.resolve(verifyStaticToken(token));
369
+ }
370
+ function authenticateWithApiKey(req) {
371
+ const apiKey = getHeaderValue(req, 'x-api-key');
372
+ if (apiKey && config.auth.mode === 'static') {
373
+ return verifyStaticToken(apiKey);
374
+ }
375
+ if (apiKey && config.auth.mode === 'oauth') {
376
+ throw new InvalidTokenError('X-API-Key not supported for OAuth');
377
+ }
378
+ throw new InvalidTokenError('Missing Authorization header');
379
+ }
321
380
  async function authenticate(req) {
322
381
  const authHeader = req.headers.authorization;
323
382
  if (!authHeader) {
324
- const apiKey = getHeaderValue(req, 'x-api-key');
325
- if (apiKey && config.auth.mode === 'static') {
326
- return verifyStaticToken(apiKey);
327
- }
328
- if (apiKey && config.auth.mode === 'oauth') {
329
- throw new InvalidTokenError('X-API-Key not supported for OAuth');
330
- }
331
- throw new InvalidTokenError('Missing Authorization header');
383
+ return authenticateWithApiKey(req);
332
384
  }
333
- const [type, token] = authHeader.split(' ');
334
- if (type !== 'Bearer' || !token)
335
- throw new InvalidTokenError('Invalid Authorization header format');
336
- if (config.auth.mode === 'oauth')
337
- return verifyWithIntrospection(token);
338
- return verifyStaticToken(token);
385
+ const token = resolveBearerToken(authHeader);
386
+ return authenticateWithToken(token);
339
387
  }
340
388
  // --- MCP Routes ---
341
389
  function sendError(res, code, message, status = 400, id = null) {
@@ -394,7 +442,8 @@ async function createNewSession(store, mcpServer, res, requestId) {
394
442
  tracker.releaseSlot();
395
443
  };
396
444
  try {
397
- await mcpServer.connect(transportImpl);
445
+ const transport = createTransportAdapter(transportImpl);
446
+ await mcpServer.connect(transport);
398
447
  }
399
448
  catch (err) {
400
449
  clearTimeout(initTimeout);
@@ -642,4 +691,3 @@ async function handleRequest(rawReq, rawRes, rateLimiter, ctx) {
642
691
  // 5. Routing
643
692
  await dispatchRequest(req, res, url, ctx);
644
693
  }
645
- //# sourceMappingURL=http-native.js.map
package/dist/index.d.ts CHANGED
@@ -1,3 +1,2 @@
1
1
  #!/usr/bin/env node
2
2
  export {};
3
- //# sourceMappingURL=index.d.ts.map
package/dist/index.js CHANGED
@@ -52,4 +52,3 @@ catch (error) {
52
52
  process.stderr.write(`Failed to start server: ${message}\n`);
53
53
  process.exit(1);
54
54
  }
55
- //# sourceMappingURL=index.js.map