@j0hanz/superfetch 2.5.0 → 2.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/fetch.d.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { Dispatcher } from 'undici';
2
1
  export interface FetchOptions {
3
2
  signal?: AbortSignal;
4
3
  }
@@ -18,8 +17,6 @@ export declare function validateAndNormalizeUrl(urlString: string): string;
18
17
  /** Backwards-compatible exports */
19
18
  export declare function transformToRawUrl(url: string): TransformResult;
20
19
  export declare function isRawTextContentUrl(url: string): boolean;
21
- export declare const dispatcher: Dispatcher;
22
- export declare function destroyAgents(): void;
23
20
  export interface FetchTelemetryContext {
24
21
  requestId: string;
25
22
  startTime: number;
package/dist/fetch.js CHANGED
@@ -2,9 +2,7 @@ import { randomUUID } from 'node:crypto';
2
2
  import diagnosticsChannel from 'node:diagnostics_channel';
3
3
  import dns from 'node:dns';
4
4
  import { BlockList, isIP } from 'node:net';
5
- import os from 'node:os';
6
5
  import { performance } from 'node:perf_hooks';
7
- import { Agent } from 'undici';
8
6
  import { config } from './config.js';
9
7
  import { createErrorWithCode, FetchError, isSystemError } from './errors.js';
10
8
  import { getOperationId, getRequestId, logDebug, logError, logWarn, redactUrl, } from './observability.js';
@@ -435,21 +433,16 @@ class SafeDnsLookup {
435
433
  }
436
434
  }
437
435
  const safeDns = new SafeDnsLookup();
438
- /* -------------------------------------------------------------------------------------------------
439
- * Dispatcher / Agent lifecycle
440
- * ------------------------------------------------------------------------------------------------- */
441
- function getAgentOptions() {
442
- const cpuCount = os.availableParallelism();
443
- return {
444
- keepAliveTimeout: 60000,
445
- connections: Math.max(cpuCount * 2, 25),
446
- pipelining: 1,
447
- connect: { lookup: safeDns.lookup.bind(safeDns) },
448
- };
449
- }
450
- export const dispatcher = new Agent(getAgentOptions());
451
- export function destroyAgents() {
452
- void dispatcher.close();
436
+ async function assertSafeDnsLookup(hostname) {
437
+ await new Promise((resolve, reject) => {
438
+ safeDns.lookup(hostname, { all: true }, (err) => {
439
+ if (err) {
440
+ reject(err);
441
+ return;
442
+ }
443
+ resolve();
444
+ });
445
+ });
453
446
  }
454
447
  /* -------------------------------------------------------------------------------------------------
455
448
  * Fetch error mapping (request-level)
@@ -834,7 +827,7 @@ function buildRequestSignal(timeoutMs, external) {
834
827
  return external ? AbortSignal.any([external, timeoutSignal]) : timeoutSignal;
835
828
  }
836
829
  function buildRequestInit(headers, signal) {
837
- return { method: 'GET', headers, signal, dispatcher };
830
+ return { method: 'GET', headers, signal };
838
831
  }
839
832
  function resolveResponseError(response, finalUrl) {
840
833
  if (response.status === 429) {
@@ -856,6 +849,8 @@ async function handleFetchResponse(response, finalUrl, ctx, signal) {
856
849
  }
857
850
  class HttpFetcher {
858
851
  async fetchNormalizedUrl(normalizedUrl, options) {
852
+ const { hostname } = new URL(normalizedUrl);
853
+ await assertSafeDnsLookup(hostname);
859
854
  const timeoutMs = config.fetcher.timeout;
860
855
  const headers = buildHeaders();
861
856
  const signal = buildRequestSignal(timeoutMs, options?.signal);
package/dist/mcp.js CHANGED
@@ -5,7 +5,6 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
5
5
  import { CallToolRequestSchema, ErrorCode, McpError, } from '@modelcontextprotocol/sdk/types.js';
6
6
  import { registerCachedContentResource } from './cache.js';
7
7
  import { config } from './config.js';
8
- import { destroyAgents } from './fetch.js';
9
8
  import { logError, logInfo, setMcpServer } from './observability.js';
10
9
  import { registerConfigResource } from './resources.js';
11
10
  import { taskManager } from './tasks.js';
@@ -415,7 +414,6 @@ function handleShutdownSignal(server, signal) {
415
414
  process.stderr.write(`\n${signal} received, shutting down superFetch MCP server...\n`);
416
415
  Promise.resolve()
417
416
  .then(async () => {
418
- destroyAgents();
419
417
  await shutdownTransformWorkerPool();
420
418
  await server.close();
421
419
  })
package/dist/tools.js CHANGED
@@ -95,29 +95,33 @@ class ToolProgressReporter {
95
95
  }
96
96
  async report(progress, message) {
97
97
  try {
98
- await Promise.race([
99
- this.sendNotification({
100
- method: 'notifications/progress',
101
- params: {
102
- progressToken: this.token,
103
- progress,
104
- total: FETCH_PROGRESS_TOTAL,
105
- message,
106
- ...(this.relatedTaskMeta
107
- ? {
108
- _meta: {
109
- 'io.modelcontextprotocol/related-task': this.relatedTaskMeta,
110
- },
111
- }
112
- : {}),
113
- },
114
- }),
115
- new Promise((_, reject) => {
116
- setTimeout(() => {
117
- reject(new Error('Progress notification timeout'));
118
- }, PROGRESS_NOTIFICATION_TIMEOUT_MS);
119
- }),
120
- ]);
98
+ let timeoutId;
99
+ const timeoutPromise = new Promise((_, reject) => {
100
+ timeoutId = setTimeout(() => {
101
+ reject(new Error('Progress notification timeout'));
102
+ }, PROGRESS_NOTIFICATION_TIMEOUT_MS);
103
+ timeoutId.unref();
104
+ });
105
+ const sendPromise = this.sendNotification({
106
+ method: 'notifications/progress',
107
+ params: {
108
+ progressToken: this.token,
109
+ progress,
110
+ total: FETCH_PROGRESS_TOTAL,
111
+ message,
112
+ ...(this.relatedTaskMeta
113
+ ? {
114
+ _meta: {
115
+ 'io.modelcontextprotocol/related-task': this.relatedTaskMeta,
116
+ },
117
+ }
118
+ : {}),
119
+ },
120
+ }).finally(() => {
121
+ if (timeoutId)
122
+ clearTimeout(timeoutId);
123
+ });
124
+ await Promise.race([sendPromise, timeoutPromise]);
121
125
  }
122
126
  catch (error) {
123
127
  const isTimeout = error instanceof Error &&
package/dist/transform.js CHANGED
@@ -630,6 +630,10 @@ function translateHtmlToMarkdown(params) {
630
630
  }
631
631
  function appendMetadataFooter(content, metadata, url) {
632
632
  const footer = buildMetadataFooter(metadata, url);
633
+ if (!content.trim() && footer) {
634
+ const note = '> **Note:** This page contains no readable content. It may require JavaScript to render.\n\n';
635
+ return `${note}${footer}`;
636
+ }
633
637
  return footer ? `${content}\n\n${footer}` : content;
634
638
  }
635
639
  export function htmlToMarkdown(html, metadata, options) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@j0hanz/superfetch",
3
- "version": "2.5.0",
3
+ "version": "2.5.2",
4
4
  "mcpName": "io.github.j0hanz/superfetch",
5
5
  "description": "Intelligent web content fetcher MCP server that converts HTML to clean, AI-readable Markdown",
6
6
  "type": "module",
@@ -60,20 +60,19 @@
60
60
  "@mozilla/readability": "^0.6.0",
61
61
  "linkedom": "^0.18.12",
62
62
  "node-html-markdown": "^2.0.0",
63
- "undici": "^7.19.2",
64
63
  "zod": "^4.3.6"
65
64
  },
66
65
  "devDependencies": {
67
66
  "@eslint/js": "^9.39.2",
68
67
  "@trivago/prettier-plugin-sort-imports": "^6.0.2",
69
- "@types/node": "^22.19.7",
68
+ "@types/node": "^22.19.8",
70
69
  "eslint": "^9.23.2",
71
70
  "eslint-config-prettier": "^10.1.8",
72
71
  "eslint-plugin-de-morgan": "^2.0.0",
73
72
  "eslint-plugin-depend": "^1.4.0",
74
73
  "eslint-plugin-sonarjs": "^3.0.6",
75
74
  "eslint-plugin-unused-imports": "^4.3.0",
76
- "knip": "^5.82.1",
75
+ "knip": "^5.83.0",
77
76
  "prettier": "^3.8.1",
78
77
  "typescript": "^5.9.3",
79
78
  "typescript-eslint": "^8.54.0"