@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 +0 -3
- package/dist/fetch.js +13 -18
- package/dist/mcp.js +0 -2
- package/dist/tools.js +27 -23
- package/dist/transform.js +4 -0
- package/package.json +3 -4
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
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
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
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
75
|
+
"knip": "^5.83.0",
|
|
77
76
|
"prettier": "^3.8.1",
|
|
78
77
|
"typescript": "^5.9.3",
|
|
79
78
|
"typescript-eslint": "^8.54.0"
|