@j0hanz/superfetch 2.2.1 → 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 (75) hide show
  1. package/README.md +243 -494
  2. package/dist/cache.d.ts +2 -3
  3. package/dist/cache.js +51 -241
  4. package/dist/config.d.ts +6 -1
  5. package/dist/config.js +29 -34
  6. package/dist/crypto.d.ts +0 -1
  7. package/dist/crypto.js +0 -1
  8. package/dist/dom-noise-removal.d.ts +5 -0
  9. package/dist/dom-noise-removal.js +485 -0
  10. package/dist/errors.d.ts +0 -1
  11. package/dist/errors.js +8 -6
  12. package/dist/fetch.d.ts +0 -1
  13. package/dist/fetch.js +71 -61
  14. package/dist/host-normalization.d.ts +1 -0
  15. package/dist/host-normalization.js +47 -0
  16. package/dist/http-native.d.ts +5 -0
  17. package/dist/http-native.js +693 -0
  18. package/dist/index.d.ts +0 -1
  19. package/dist/index.js +1 -2
  20. package/dist/instructions.md +22 -20
  21. package/dist/json.d.ts +1 -0
  22. package/dist/json.js +29 -0
  23. package/dist/language-detection.d.ts +12 -0
  24. package/dist/language-detection.js +291 -0
  25. package/dist/markdown-cleanup.d.ts +18 -0
  26. package/dist/markdown-cleanup.js +283 -0
  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 +1 -1
  32. package/dist/observability.js +15 -3
  33. package/dist/server-tuning.d.ts +9 -0
  34. package/dist/server-tuning.js +30 -0
  35. package/dist/session.d.ts +36 -0
  36. package/dist/session.js +159 -0
  37. package/dist/tools.d.ts +0 -1
  38. package/dist/tools.js +23 -33
  39. package/dist/transform-types.d.ts +80 -0
  40. package/dist/transform-types.js +5 -0
  41. package/dist/transform.d.ts +7 -53
  42. package/dist/transform.js +434 -856
  43. package/dist/type-guards.d.ts +1 -2
  44. package/dist/type-guards.js +1 -2
  45. package/dist/workers/transform-worker.d.ts +0 -1
  46. package/dist/workers/transform-worker.js +52 -43
  47. package/package.json +11 -12
  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/errors.d.ts.map +0 -1
  55. package/dist/errors.js.map +0 -1
  56. package/dist/fetch.d.ts.map +0 -1
  57. package/dist/fetch.js.map +0 -1
  58. package/dist/http.d.ts +0 -90
  59. package/dist/http.d.ts.map +0 -1
  60. package/dist/http.js +0 -1576
  61. package/dist/http.js.map +0 -1
  62. package/dist/index.d.ts.map +0 -1
  63. package/dist/index.js.map +0 -1
  64. package/dist/mcp.d.ts.map +0 -1
  65. package/dist/mcp.js.map +0 -1
  66. package/dist/observability.d.ts.map +0 -1
  67. package/dist/observability.js.map +0 -1
  68. package/dist/tools.d.ts.map +0 -1
  69. package/dist/tools.js.map +0 -1
  70. package/dist/transform.d.ts.map +0 -1
  71. package/dist/transform.js.map +0 -1
  72. package/dist/type-guards.d.ts.map +0 -1
  73. package/dist/type-guards.js.map +0 -1
  74. package/dist/workers/transform-worker.d.ts.map +0 -1
  75. package/dist/workers/transform-worker.js.map +0 -1
package/dist/cache.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Express } from 'express';
1
+ import type { ServerResponse } from 'node:http';
2
2
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  export interface CacheEntry {
4
4
  url: string;
@@ -38,6 +38,5 @@ export declare function keys(): readonly string[];
38
38
  export declare function isEnabled(): boolean;
39
39
  export declare function registerCachedContentResource(server: McpServer): void;
40
40
  export declare function generateSafeFilename(url: string, title?: string, hashFallback?: string, extension?: string): string;
41
- export declare function registerDownloadRoutes(app: Express): void;
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
@@ -1,11 +1,12 @@
1
- import { setInterval as setIntervalPromise } from 'node:timers/promises';
1
+ import { LRUCache } from 'lru-cache';
2
2
  import { ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js';
3
3
  import { ErrorCode, McpError, SubscribeRequestSchema, UnsubscribeRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
4
4
  import { config } from './config.js';
5
5
  import { sha256Hex } from './crypto.js';
6
6
  import { getErrorMessage } from './errors.js';
7
+ import { stableStringify as stableJsonStringify } from './json.js';
7
8
  import { logDebug, logWarn } from './observability.js';
8
- import { isRecord } from './type-guards.js';
9
+ import { isObject } from './type-guards.js';
9
10
  export function parseCachedPayload(raw) {
10
11
  try {
11
12
  const parsed = JSON.parse(raw);
@@ -31,7 +32,7 @@ function hasOptionalStringProperty(value, key) {
31
32
  return typeof prop === 'string';
32
33
  }
33
34
  function isCachedPayload(value) {
34
- if (!isRecord(value))
35
+ if (!isObject(value))
35
36
  return false;
36
37
  if (!hasOptionalStringProperty(value, 'content'))
37
38
  return false;
@@ -42,124 +43,16 @@ function isCachedPayload(value) {
42
43
  return true;
43
44
  }
44
45
  const CACHE_HASH = {
45
- URL_HASH_LENGTH: 16,
46
- VARY_HASH_LENGTH: 12,
46
+ URL_HASH_LENGTH: 32,
47
+ VARY_HASH_LENGTH: 16,
47
48
  };
48
- const CACHE_VARY_LIMITS = {
49
- MAX_STRING_LENGTH: 4096,
50
- MAX_KEYS: 64,
51
- MAX_ARRAY_LENGTH: 64,
52
- MAX_DEPTH: 6,
53
- MAX_NODES: 512,
54
- };
55
- function bumpStableStringifyNodeCount(state) {
56
- state.nodes += 1;
57
- return state.nodes <= CACHE_VARY_LIMITS.MAX_NODES;
58
- }
59
- function stableStringifyPrimitive(value) {
60
- if (value === null || value === undefined) {
61
- return '';
62
- }
63
- const json = JSON.stringify(value);
64
- return typeof json === 'string' ? json : '';
65
- }
66
- function stableStringifyArray(value, state) {
67
- if (value.length > CACHE_VARY_LIMITS.MAX_ARRAY_LENGTH) {
68
- return null;
69
- }
70
- const parts = ['['];
71
- let length = 1;
72
- for (let index = 0; index < value.length; index += 1) {
73
- if (index > 0) {
74
- parts.push(',');
75
- length += 1;
76
- if (length > CACHE_VARY_LIMITS.MAX_STRING_LENGTH)
77
- return null;
78
- }
79
- const entry = stableStringifyInner(value[index], state);
80
- if (entry === null)
81
- return null;
82
- parts.push(entry);
83
- length += entry.length;
84
- if (length > CACHE_VARY_LIMITS.MAX_STRING_LENGTH)
85
- return null;
86
- }
87
- parts.push(']');
88
- length += 1;
89
- return length > CACHE_VARY_LIMITS.MAX_STRING_LENGTH ? null : parts.join('');
90
- }
91
- function stableStringifyRecord(value, state) {
92
- const keys = Object.keys(value);
93
- if (keys.length > CACHE_VARY_LIMITS.MAX_KEYS) {
94
- return null;
95
- }
96
- keys.sort((a, b) => a.localeCompare(b));
97
- const parts = ['{'];
98
- let length = 1;
99
- let isFirst = true;
100
- for (const key of keys) {
101
- const entryValue = value[key];
102
- if (entryValue === undefined)
103
- continue;
104
- const encodedValue = stableStringifyInner(entryValue, state);
105
- if (encodedValue === null)
106
- return null;
107
- const entry = `${JSON.stringify(key)}:${encodedValue}`;
108
- if (!isFirst) {
109
- parts.push(',');
110
- length += 1;
111
- if (length > CACHE_VARY_LIMITS.MAX_STRING_LENGTH)
112
- return null;
113
- }
114
- parts.push(entry);
115
- length += entry.length;
116
- if (length > CACHE_VARY_LIMITS.MAX_STRING_LENGTH)
117
- return null;
118
- isFirst = false;
119
- }
120
- parts.push('}');
121
- length += 1;
122
- return length > CACHE_VARY_LIMITS.MAX_STRING_LENGTH ? null : parts.join('');
123
- }
124
- function stableStringifyObject(value, state) {
125
- if (state.stack.has(value)) {
126
- return null;
127
- }
128
- if (state.depth >= CACHE_VARY_LIMITS.MAX_DEPTH) {
129
- return null;
130
- }
131
- state.stack.add(value);
132
- state.depth += 1;
49
+ function stableStringify(value) {
133
50
  try {
134
- if (Array.isArray(value)) {
135
- return stableStringifyArray(value, state);
136
- }
137
- return isRecord(value) ? stableStringifyRecord(value, state) : null;
51
+ return stableJsonStringify(value);
138
52
  }
139
- finally {
140
- state.depth -= 1;
141
- state.stack.delete(value);
142
- }
143
- }
144
- function stableStringifyInner(value, state) {
145
- if (!bumpStableStringifyNodeCount(state)) {
53
+ catch {
146
54
  return null;
147
55
  }
148
- if (value === null || value === undefined) {
149
- return 'null';
150
- }
151
- if (typeof value !== 'object') {
152
- return stableStringifyPrimitive(value);
153
- }
154
- return stableStringifyObject(value, state);
155
- }
156
- function stableStringify(value) {
157
- const state = {
158
- depth: 0,
159
- nodes: 0,
160
- stack: new WeakSet(),
161
- };
162
- return stableStringifyInner(value, state);
163
56
  }
164
57
  function createHashFragment(input, length) {
165
58
  return sha256Hex(input).substring(0, length);
@@ -174,8 +67,7 @@ function getVaryHash(vary) {
174
67
  return undefined;
175
68
  let varyString;
176
69
  if (typeof vary === 'string') {
177
- varyString =
178
- vary.length > CACHE_VARY_LIMITS.MAX_STRING_LENGTH ? null : vary;
70
+ varyString = vary;
179
71
  }
180
72
  else {
181
73
  varyString = stableStringify(vary);
@@ -204,15 +96,20 @@ export function parseCacheKey(cacheKey) {
204
96
  return null;
205
97
  return { namespace, urlHash };
206
98
  }
99
+ function buildCacheResourceUri(namespace, urlHash) {
100
+ return `superfetch://cache/${namespace}/${urlHash}`;
101
+ }
207
102
  export function toResourceUri(cacheKey) {
208
103
  const parts = parseCacheKey(cacheKey);
209
104
  if (!parts)
210
105
  return null;
211
- return `superfetch://cache/${parts.namespace}/${parts.urlHash}`;
106
+ return buildCacheResourceUri(parts.namespace, parts.urlHash);
212
107
  }
213
- const contentCache = new Map();
214
- let cleanupController = null;
215
- let nextExpiryAt = null;
108
+ const contentCache = new LRUCache({
109
+ max: config.cache.maxKeys,
110
+ ttl: config.cache.ttl * 1000,
111
+ updateAgeOnGet: false,
112
+ });
216
113
  const updateListeners = new Set();
217
114
  export function onCacheUpdate(listener) {
218
115
  updateListeners.add(listener);
@@ -231,47 +128,10 @@ function notifyCacheUpdate(cacheKey) {
231
128
  listener(event);
232
129
  }
233
130
  }
234
- function startCleanupLoop() {
235
- if (cleanupController)
236
- return;
237
- cleanupController = new AbortController();
238
- void runCleanupLoop(cleanupController.signal).catch((error) => {
239
- if (error instanceof Error && error.name !== 'AbortError') {
240
- logWarn('Cache cleanup loop failed', { error: getErrorMessage(error) });
241
- }
242
- });
243
- }
244
- async function runCleanupLoop(signal) {
245
- const intervalMs = Math.floor(config.cache.ttl * 1000);
246
- for await (const getNow of setIntervalPromise(intervalMs, Date.now, {
247
- signal,
248
- ref: false,
249
- })) {
250
- enforceCacheLimits(getNow());
251
- }
252
- }
253
- function enforceCacheLimits(now) {
254
- if (nextExpiryAt !== null && now < nextExpiryAt) {
255
- trimCacheToMaxKeys();
256
- return;
257
- }
258
- let nextExpiry = null;
259
- for (const [key, item] of contentCache.entries()) {
260
- if (now > item.expiresAt) {
261
- contentCache.delete(key);
262
- continue;
263
- }
264
- if (nextExpiry === null || item.expiresAt < nextExpiry) {
265
- nextExpiry = item.expiresAt;
266
- }
267
- }
268
- nextExpiryAt = nextExpiry;
269
- trimCacheToMaxKeys();
270
- }
271
131
  export function get(cacheKey) {
272
132
  if (!isCacheReadable(cacheKey))
273
133
  return undefined;
274
- return runCacheOperation(cacheKey, 'Cache get error', () => readCacheEntry(cacheKey));
134
+ return runCacheOperation(cacheKey, 'Cache get error', () => contentCache.get(cacheKey));
275
135
  }
276
136
  function isCacheReadable(cacheKey) {
277
137
  return config.cache.enabled && Boolean(cacheKey);
@@ -288,28 +148,10 @@ function runCacheOperation(cacheKey, message, operation) {
288
148
  return undefined;
289
149
  }
290
150
  }
291
- function readCacheEntry(cacheKey) {
292
- const now = Date.now();
293
- return readCacheItem(cacheKey, now)?.entry;
294
- }
295
- function isExpired(item, now) {
296
- return now > item.expiresAt;
297
- }
298
- function readCacheItem(cacheKey, now) {
299
- const item = contentCache.get(cacheKey);
300
- if (!item)
301
- return undefined;
302
- if (isExpired(item, now)) {
303
- contentCache.delete(cacheKey);
304
- return undefined;
305
- }
306
- return item;
307
- }
308
151
  export function set(cacheKey, content, metadata) {
309
152
  if (!isCacheWritable(cacheKey, content))
310
153
  return;
311
154
  runCacheOperation(cacheKey, 'Cache set error', () => {
312
- startCleanupLoop();
313
155
  const now = Date.now();
314
156
  const expiresAtMs = now + config.cache.ttl * 1000;
315
157
  const entry = buildCacheEntry({
@@ -318,11 +160,11 @@ export function set(cacheKey, content, metadata) {
318
160
  fetchedAtMs: now,
319
161
  expiresAtMs,
320
162
  });
321
- persistCacheEntry(cacheKey, entry, expiresAtMs);
163
+ persistCacheEntry(cacheKey, entry);
322
164
  });
323
165
  }
324
166
  export function keys() {
325
- return Array.from(contentCache.keys());
167
+ return [...contentCache.keys()];
326
168
  }
327
169
  export function isEnabled() {
328
170
  return config.cache.enabled;
@@ -336,33 +178,10 @@ function buildCacheEntry({ content, metadata, fetchedAtMs, expiresAtMs, }) {
336
178
  ...(metadata.title === undefined ? {} : { title: metadata.title }),
337
179
  };
338
180
  }
339
- function persistCacheEntry(cacheKey, entry, expiresAtMs) {
340
- contentCache.set(cacheKey, { entry, expiresAt: expiresAtMs });
341
- if (nextExpiryAt === null || expiresAtMs < nextExpiryAt) {
342
- nextExpiryAt = expiresAtMs;
343
- }
344
- trimCacheToMaxKeys();
181
+ function persistCacheEntry(cacheKey, entry) {
182
+ contentCache.set(cacheKey, entry);
345
183
  notifyCacheUpdate(cacheKey);
346
184
  }
347
- function trimCacheToMaxKeys() {
348
- if (contentCache.size <= config.cache.maxKeys)
349
- return;
350
- removeOldestEntries(contentCache.size - config.cache.maxKeys);
351
- }
352
- function removeOldestEntries(count) {
353
- const iterator = contentCache.keys();
354
- for (let removed = 0; removed < count; removed += 1) {
355
- const next = iterator.next();
356
- if (next.done)
357
- break;
358
- const removedKey = next.value;
359
- const removedItem = contentCache.get(removedKey);
360
- contentCache.delete(removedKey);
361
- if (nextExpiryAt !== null && removedItem?.expiresAt === nextExpiryAt) {
362
- nextExpiryAt = null;
363
- }
364
- }
365
- }
366
185
  function logCacheError(message, cacheKey, error) {
367
186
  logWarn(message, {
368
187
  key: cacheKey.length > 100 ? cacheKey.slice(0, 100) : cacheKey,
@@ -371,18 +190,22 @@ function logCacheError(message, cacheKey, error) {
371
190
  }
372
191
  const CACHE_NAMESPACE = 'markdown';
373
192
  const HASH_PATTERN = /^[a-f0-9.]+$/i;
193
+ const INVALID_CACHE_PARAMS_MESSAGE = 'Invalid cache resource parameters';
194
+ function throwInvalidCacheParams() {
195
+ throw new McpError(ErrorCode.InvalidParams, INVALID_CACHE_PARAMS_MESSAGE);
196
+ }
374
197
  function resolveCacheParams(params) {
375
198
  const parsed = requireRecordParams(params);
376
199
  const namespace = requireParamString(parsed, 'namespace');
377
200
  const urlHash = requireParamString(parsed, 'urlHash');
378
201
  if (!isValidNamespace(namespace) || !isValidHash(urlHash)) {
379
- throw new McpError(ErrorCode.InvalidParams, 'Invalid cache resource parameters');
202
+ throwInvalidCacheParams();
380
203
  }
381
204
  return { namespace, urlHash };
382
205
  }
383
206
  function requireRecordParams(value) {
384
- if (!isRecord(value)) {
385
- throw new McpError(ErrorCode.InvalidParams, 'Invalid cache resource parameters');
207
+ if (!isObject(value)) {
208
+ throwInvalidCacheParams();
386
209
  }
387
210
  return value;
388
211
  }
@@ -428,7 +251,7 @@ function resolveStringParam(value) {
428
251
  function buildResourceEntry(namespace, urlHash) {
429
252
  return {
430
253
  name: `${namespace}:${urlHash}`,
431
- uri: `superfetch://cache/${namespace}/${urlHash}`,
254
+ uri: buildCacheResourceUri(namespace, urlHash),
432
255
  description: `Cached content entry for ${namespace}`,
433
256
  mimeType: 'text/markdown',
434
257
  };
@@ -462,11 +285,11 @@ function attachInitializedGate(server) {
462
285
  }
463
286
  function getClientResourceCapabilities(server) {
464
287
  const caps = server.server.getClientCapabilities();
465
- if (!caps || !isRecord(caps)) {
288
+ if (!caps || !isObject(caps)) {
466
289
  return { listChanged: false, subscribe: false };
467
290
  }
468
291
  const { resources } = caps;
469
- if (!isRecord(resources)) {
292
+ if (!isObject(resources)) {
470
293
  return { listChanged: false, subscribe: false };
471
294
  }
472
295
  const { listChanged, subscribe } = resources;
@@ -573,41 +396,32 @@ function buildMarkdownContentResponse(uri, content) {
573
396
  ],
574
397
  };
575
398
  }
576
- function isSingleParam(value) {
577
- return typeof value === 'string';
578
- }
579
- function parseDownloadParams(req) {
580
- const { namespace, hash } = req.params;
581
- if (!isSingleParam(namespace) || !isSingleParam(hash))
399
+ function parseDownloadParams(namespace, hash) {
400
+ const resolvedNamespace = resolveStringParam(namespace);
401
+ const resolvedHash = resolveStringParam(hash);
402
+ if (!resolvedNamespace || !resolvedHash)
582
403
  return null;
583
- if (!namespace || !hash)
404
+ if (!isValidNamespace(resolvedNamespace))
584
405
  return null;
585
- if (!isValidNamespace(namespace))
406
+ if (!isValidHash(resolvedHash))
586
407
  return null;
587
- if (!isValidHash(hash))
588
- return null;
589
- return { namespace, hash };
408
+ return { namespace: resolvedNamespace, hash: resolvedHash };
590
409
  }
591
410
  function buildCacheKeyFromParams(params) {
592
411
  return `${params.namespace}:${params.hash}`;
593
412
  }
413
+ function sendJsonError(res, status, error, code) {
414
+ res.writeHead(status, { 'Content-Type': 'application/json' });
415
+ res.end(JSON.stringify({ error, code }));
416
+ }
594
417
  function respondBadRequest(res, message) {
595
- res.status(400).json({
596
- error: message,
597
- code: 'BAD_REQUEST',
598
- });
418
+ sendJsonError(res, 400, message, 'BAD_REQUEST');
599
419
  }
600
420
  function respondNotFound(res) {
601
- res.status(404).json({
602
- error: 'Content not found or expired',
603
- code: 'NOT_FOUND',
604
- });
421
+ sendJsonError(res, 404, 'Content not found or expired', 'NOT_FOUND');
605
422
  }
606
423
  function respondServiceUnavailable(res) {
607
- res.status(503).json({
608
- error: 'Download service is disabled',
609
- code: 'SERVICE_UNAVAILABLE',
610
- });
424
+ sendJsonError(res, 503, 'Download service is disabled', 'SERVICE_UNAVAILABLE');
611
425
  }
612
426
  export function generateSafeFilename(url, title, hashFallback, extension = '.md') {
613
427
  const fromUrl = extractFilenameFromUrl(url);
@@ -714,14 +528,14 @@ function sendDownloadPayload(res, payload) {
714
528
  res.setHeader('Content-Disposition', disposition);
715
529
  res.setHeader('Cache-Control', `private, max-age=${config.cache.ttl}`);
716
530
  res.setHeader('X-Content-Type-Options', 'nosniff');
717
- res.send(payload.content);
531
+ res.end(payload.content);
718
532
  }
719
- function handleDownload(req, res) {
533
+ export function handleDownload(res, namespace, hash) {
720
534
  if (!config.cache.enabled) {
721
535
  respondServiceUnavailable(res);
722
536
  return;
723
537
  }
724
- const params = parseDownloadParams(req);
538
+ const params = parseDownloadParams(namespace, hash);
725
539
  if (!params) {
726
540
  respondBadRequest(res, 'Invalid namespace or hash format');
727
541
  return;
@@ -742,7 +556,3 @@ function handleDownload(req, res) {
742
556
  logDebug('Serving download', { cacheKey, fileName: payload.fileName });
743
557
  sendDownloadPayload(res, payload);
744
558
  }
745
- export function registerDownloadRoutes(app) {
746
- app.get('/mcp/downloads/:namespace/:hash', handleDownload);
747
- }
748
- //# sourceMappingURL=cache.js.map
package/dist/config.d.ts CHANGED
@@ -57,6 +57,10 @@ export declare const config: {
57
57
  maxBlockLength: number;
58
58
  minParagraphLength: number;
59
59
  };
60
+ noiseRemoval: {
61
+ extraTokens: string[];
62
+ extraSelectors: string[];
63
+ };
60
64
  logging: {
61
65
  level: LogLevel;
62
66
  };
@@ -68,6 +72,8 @@ export declare const config: {
68
72
  security: {
69
73
  blockedHosts: Set<string>;
70
74
  blockedIpPatterns: readonly [RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp, RegExp];
75
+ blockedIpPattern: RegExp;
76
+ blockedIpv4MappedPattern: RegExp;
71
77
  allowedHosts: Set<string>;
72
78
  apiKey: string | undefined;
73
79
  allowRemote: boolean;
@@ -83,4 +89,3 @@ export declare const config: {
83
89
  };
84
90
  export declare function enableHttpMode(): void;
85
91
  export {};
86
- //# sourceMappingURL=config.d.ts.map
package/dist/config.js CHANGED
@@ -33,39 +33,24 @@ const ALLOWED_LOG_LEVELS = new Set([
33
33
  function isLogLevel(value) {
34
34
  return ALLOWED_LOG_LEVELS.has(value);
35
35
  }
36
- function isBelowMin(value, min) {
37
- if (min === undefined)
38
- return false;
39
- return value < min;
36
+ function isOutsideRange(value, min, max) {
37
+ return ((min !== undefined && value < min) || (max !== undefined && value > max));
40
38
  }
41
- function isAboveMax(value, max) {
42
- if (max === undefined)
43
- return false;
44
- return value > max;
45
- }
46
- function parseInteger(envValue, defaultValue, min, max) {
39
+ function parseIntegerValue(envValue, min, max) {
47
40
  if (!envValue)
48
- return defaultValue;
41
+ return null;
49
42
  const parsed = parseInt(envValue, 10);
50
43
  if (Number.isNaN(parsed))
51
- return defaultValue;
52
- if (isBelowMin(parsed, min))
53
- return defaultValue;
54
- if (isAboveMax(parsed, max))
55
- return defaultValue;
44
+ return null;
45
+ if (isOutsideRange(parsed, min, max))
46
+ return null;
56
47
  return parsed;
57
48
  }
49
+ function parseInteger(envValue, defaultValue, min, max) {
50
+ return parseIntegerValue(envValue, min, max) ?? defaultValue;
51
+ }
58
52
  function parseOptionalInteger(envValue, min, max) {
59
- if (!envValue)
60
- return undefined;
61
- const parsed = parseInt(envValue, 10);
62
- if (Number.isNaN(parsed))
63
- return undefined;
64
- if (isBelowMin(parsed, min))
65
- return undefined;
66
- if (isAboveMax(parsed, max))
67
- return undefined;
68
- return parsed;
53
+ return parseIntegerValue(envValue, min, max) ?? undefined;
69
54
  }
70
55
  function parseBoolean(envValue, defaultValue) {
71
56
  if (!envValue)
@@ -88,6 +73,9 @@ function parseUrlEnv(value, name) {
88
73
  }
89
74
  return new URL(value);
90
75
  }
76
+ function readUrlEnv(name) {
77
+ return parseUrlEnv(process.env[name], name);
78
+ }
91
79
  function parseAllowedHosts(envValue) {
92
80
  const hosts = new Set();
93
81
  for (const entry of parseList(envValue)) {
@@ -123,16 +111,16 @@ const DEFAULT_TOOL_TIMEOUT_MS = TIMEOUT.DEFAULT_FETCH_TIMEOUT_MS +
123
111
  5000;
124
112
  function readCoreOAuthUrls() {
125
113
  return {
126
- issuerUrl: parseUrlEnv(process.env.OAUTH_ISSUER_URL, 'OAUTH_ISSUER_URL'),
127
- authorizationUrl: parseUrlEnv(process.env.OAUTH_AUTHORIZATION_URL, 'OAUTH_AUTHORIZATION_URL'),
128
- 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'),
129
117
  };
130
118
  }
131
119
  function readOptionalOAuthUrls(baseUrl) {
132
120
  return {
133
- revocationUrl: parseUrlEnv(process.env.OAUTH_REVOCATION_URL, 'OAUTH_REVOCATION_URL'),
134
- registrationUrl: parseUrlEnv(process.env.OAUTH_REGISTRATION_URL, 'OAUTH_REGISTRATION_URL'),
135
- 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'),
136
124
  resourceUrl: parseUrlEnv(process.env.OAUTH_RESOURCE_URL, 'OAUTH_RESOURCE_URL') ??
137
125
  new URL('/mcp', baseUrl),
138
126
  };
@@ -158,7 +146,7 @@ function collectStaticTokens() {
158
146
  if (process.env.API_KEY) {
159
147
  staticTokens.add(process.env.API_KEY);
160
148
  }
161
- return Array.from(staticTokens);
149
+ return [...staticTokens];
162
150
  }
163
151
  function buildAuthConfig(baseUrl) {
164
152
  const urls = readOAuthUrls(baseUrl);
@@ -225,6 +213,10 @@ export const config = {
225
213
  maxBlockLength: 5000,
226
214
  minParagraphLength: 10,
227
215
  },
216
+ noiseRemoval: {
217
+ extraTokens: parseList(process.env.SUPERFETCH_EXTRA_NOISE_TOKENS),
218
+ extraSelectors: parseList(process.env.SUPERFETCH_EXTRA_NOISE_SELECTORS),
219
+ },
228
220
  logging: {
229
221
  level: parseLogLevel(process.env.LOG_LEVEL),
230
222
  },
@@ -262,6 +254,10 @@ export const config = {
262
254
  /^::ffff:192\.168\./,
263
255
  /^::ffff:169\.254\./,
264
256
  ],
257
+ // Combined regex patterns for fast IP blocking (used in fetch.ts)
258
+ // Split into two patterns to reduce complexity while maintaining performance
259
+ blockedIpPattern: /^(?:10\.|172\.(?:1[6-9]|2\d|3[01])\.|192\.168\.|127\.|0\.|169\.254\.|100\.64\.|fc00:|fd00:|fe80:)/i,
260
+ blockedIpv4MappedPattern: /^::ffff:(?:127\.|10\.|172\.(?:1[6-9]|2\d|3[01])\.|192\.168\.|169\.254\.)/i,
265
261
  allowedHosts: parseAllowedHosts(process.env.ALLOWED_HOSTS),
266
262
  apiKey: process.env.API_KEY,
267
263
  allowRemote,
@@ -278,4 +274,3 @@ export const config = {
278
274
  export function enableHttpMode() {
279
275
  runtimeState.httpMode = true;
280
276
  }
281
- //# 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
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Remove noise elements from HTML and resolve relative URLs.
3
+ * Used as a preprocessing step before markdown conversion.
4
+ */
5
+ export declare function removeNoiseFromHtml(html: string, document?: Document, baseUrl?: string): string;