@j0hanz/fetch-url-mcp 1.1.1 → 1.1.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/cache.js +5 -2
- package/dist/cli.js +12 -5
- package/dist/config.js +19 -18
- package/dist/crypto.js +7 -4
- package/dist/dom-noise-removal.js +14 -11
- package/dist/errors.js +5 -2
- package/dist/fetch.js +5 -2
- package/dist/host-normalization.js +7 -6
- package/dist/http-native.js +6 -4
- package/dist/index.js +7 -4
- package/dist/ip-blocklist.js +3 -3
- package/dist/json.js +3 -2
- package/dist/language-detection.js +1 -5
- package/dist/markdown-cleanup.js +8 -5
- package/dist/mcp-validator.js +7 -8
- package/dist/mcp.js +32 -17
- package/dist/observability.js +9 -8
- package/dist/prompts.js +2 -10
- package/dist/resources.js +6 -4
- package/dist/server-tuning.js +20 -15
- package/dist/server.js +4 -8
- package/dist/session.js +7 -4
- package/dist/tasks.js +4 -4
- package/dist/timer-utils.js +4 -1
- package/dist/tools.js +8 -6
- package/dist/transform.js +6 -3
- package/dist/type-guards.js +3 -3
- package/dist/workers/transform-child.js +8 -4
- package/dist/workers/transform-worker.js +4 -3
- package/package.json +1 -1
package/dist/cache.js
CHANGED
|
@@ -93,13 +93,16 @@ class InMemoryCacheStore {
|
|
|
93
93
|
isEnabled() {
|
|
94
94
|
return config.cache.enabled;
|
|
95
95
|
}
|
|
96
|
+
isExpired(entry, now = Date.now()) {
|
|
97
|
+
return entry.expiresAtMs <= now;
|
|
98
|
+
}
|
|
96
99
|
keys() {
|
|
97
100
|
if (!this.isEnabled())
|
|
98
101
|
return [];
|
|
99
102
|
const now = Date.now();
|
|
100
103
|
const result = [];
|
|
101
104
|
for (const [key, entry] of this.entries) {
|
|
102
|
-
if (entry
|
|
105
|
+
if (!this.isExpired(entry, now))
|
|
103
106
|
result.push(key);
|
|
104
107
|
}
|
|
105
108
|
return result;
|
|
@@ -130,7 +133,7 @@ class InMemoryCacheStore {
|
|
|
130
133
|
if (!entry)
|
|
131
134
|
return undefined;
|
|
132
135
|
const now = Date.now();
|
|
133
|
-
if (entry
|
|
136
|
+
if (this.isExpired(entry, now)) {
|
|
134
137
|
this.delete(cacheKey);
|
|
135
138
|
this.notify(cacheKey, true);
|
|
136
139
|
return undefined;
|
package/dist/cli.js
CHANGED
|
@@ -19,6 +19,17 @@ const optionSchema = {
|
|
|
19
19
|
function toErrorMessage(error) {
|
|
20
20
|
return error instanceof Error ? error.message : String(error);
|
|
21
21
|
}
|
|
22
|
+
function toBoolean(value) {
|
|
23
|
+
return value === true;
|
|
24
|
+
}
|
|
25
|
+
function buildCliValues(values) {
|
|
26
|
+
const { stdio, help, version } = values;
|
|
27
|
+
return {
|
|
28
|
+
stdio: toBoolean(stdio),
|
|
29
|
+
help: toBoolean(help),
|
|
30
|
+
version: toBoolean(version),
|
|
31
|
+
};
|
|
32
|
+
}
|
|
22
33
|
export function renderCliUsage() {
|
|
23
34
|
return `${usageLines.join('\n')}\n`;
|
|
24
35
|
}
|
|
@@ -32,11 +43,7 @@ export function parseCliArgs(args) {
|
|
|
32
43
|
});
|
|
33
44
|
return {
|
|
34
45
|
ok: true,
|
|
35
|
-
values:
|
|
36
|
-
stdio: values.stdio,
|
|
37
|
-
help: values.help,
|
|
38
|
-
version: values.version,
|
|
39
|
-
},
|
|
46
|
+
values: buildCliValues(values),
|
|
40
47
|
};
|
|
41
48
|
}
|
|
42
49
|
catch (error) {
|
package/dist/config.js
CHANGED
|
@@ -233,24 +233,25 @@ const RESOLVED_TASKS_MAX_PER_OWNER = Math.min(DEFAULT_TASKS_MAX_PER_OWNER, DEFAU
|
|
|
233
233
|
function resolveWorkerResourceLimits() {
|
|
234
234
|
const limits = {};
|
|
235
235
|
let hasAny = false;
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
236
|
+
const entries = [
|
|
237
|
+
[
|
|
238
|
+
'maxOldGenerationSizeMb',
|
|
239
|
+
parseOptionalInteger(env['TRANSFORM_WORKER_MAX_OLD_GENERATION_MB'], 1),
|
|
240
|
+
],
|
|
241
|
+
[
|
|
242
|
+
'maxYoungGenerationSizeMb',
|
|
243
|
+
parseOptionalInteger(env['TRANSFORM_WORKER_MAX_YOUNG_GENERATION_MB'], 1),
|
|
244
|
+
],
|
|
245
|
+
[
|
|
246
|
+
'codeRangeSizeMb',
|
|
247
|
+
parseOptionalInteger(env['TRANSFORM_WORKER_CODE_RANGE_MB'], 1),
|
|
248
|
+
],
|
|
249
|
+
['stackSizeMb', parseOptionalInteger(env['TRANSFORM_WORKER_STACK_MB'], 1)],
|
|
250
|
+
];
|
|
251
|
+
for (const [key, value] of entries) {
|
|
252
|
+
if (value === undefined)
|
|
253
|
+
continue;
|
|
254
|
+
limits[key] = value;
|
|
254
255
|
hasAny = true;
|
|
255
256
|
}
|
|
256
257
|
return hasAny ? limits : undefined;
|
package/dist/crypto.js
CHANGED
|
@@ -18,6 +18,11 @@ function assertAllowedAlgorithm(algorithm) {
|
|
|
18
18
|
throw new Error(`Hash algorithm not allowed: ${algorithm}`);
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
|
+
function padBuffer(buffer, length) {
|
|
22
|
+
const padded = Buffer.alloc(length);
|
|
23
|
+
buffer.copy(padded);
|
|
24
|
+
return padded;
|
|
25
|
+
}
|
|
21
26
|
export function timingSafeEqualUtf8(a, b) {
|
|
22
27
|
const aBuffer = Buffer.from(a, 'utf8');
|
|
23
28
|
const bBuffer = Buffer.from(b, 'utf8');
|
|
@@ -26,10 +31,8 @@ export function timingSafeEqualUtf8(a, b) {
|
|
|
26
31
|
}
|
|
27
32
|
// Avoid early return timing differences on length mismatch.
|
|
28
33
|
const maxLength = Math.max(aBuffer.length, bBuffer.length);
|
|
29
|
-
const paddedA =
|
|
30
|
-
const paddedB =
|
|
31
|
-
aBuffer.copy(paddedA);
|
|
32
|
-
bBuffer.copy(paddedB);
|
|
34
|
+
const paddedA = padBuffer(aBuffer, maxLength);
|
|
35
|
+
const paddedB = padBuffer(bBuffer, maxLength);
|
|
33
36
|
return timingSafeEqual(paddedA, paddedB) && aBuffer.length === bBuffer.length;
|
|
34
37
|
}
|
|
35
38
|
function hashHex(algorithm, input) {
|
|
@@ -103,22 +103,25 @@ function buildTokenRegex(tokens) {
|
|
|
103
103
|
return NO_MATCH_REGEX;
|
|
104
104
|
return new RegExp(`(?:^|[^a-z0-9])(?:${[...tokens].map(escapeRegexLiteral).join('|')})(?:$|[^a-z0-9])`, 'i');
|
|
105
105
|
}
|
|
106
|
+
function addTokens(target, tokens) {
|
|
107
|
+
for (const token of tokens)
|
|
108
|
+
target.add(token);
|
|
109
|
+
}
|
|
106
110
|
function getPromoMatchers(currentConfig, flags) {
|
|
107
111
|
const baseTokens = new Set(PROMO_TOKENS_ALWAYS);
|
|
108
112
|
const aggressiveTokens = new Set();
|
|
109
113
|
if (currentConfig.aggressiveMode) {
|
|
110
|
-
|
|
111
|
-
|
|
114
|
+
addTokens(aggressiveTokens, PROMO_TOKENS_AGGRESSIVE);
|
|
115
|
+
}
|
|
116
|
+
if (flags.cookieBanners) {
|
|
117
|
+
addTokens(baseTokens, PROMO_TOKENS_BY_CATEGORY['cookie-banners']);
|
|
118
|
+
}
|
|
119
|
+
if (flags.newsletters) {
|
|
120
|
+
addTokens(baseTokens, PROMO_TOKENS_BY_CATEGORY['newsletters']);
|
|
121
|
+
}
|
|
122
|
+
if (flags.socialShare) {
|
|
123
|
+
addTokens(baseTokens, PROMO_TOKENS_BY_CATEGORY['social-share']);
|
|
112
124
|
}
|
|
113
|
-
if (flags.cookieBanners)
|
|
114
|
-
for (const t of PROMO_TOKENS_BY_CATEGORY['cookie-banners'])
|
|
115
|
-
baseTokens.add(t);
|
|
116
|
-
if (flags.newsletters)
|
|
117
|
-
for (const t of PROMO_TOKENS_BY_CATEGORY['newsletters'])
|
|
118
|
-
baseTokens.add(t);
|
|
119
|
-
if (flags.socialShare)
|
|
120
|
-
for (const t of PROMO_TOKENS_BY_CATEGORY['social-share'])
|
|
121
|
-
baseTokens.add(t);
|
|
122
125
|
for (const t of currentConfig.extraTokens) {
|
|
123
126
|
const n = t.toLowerCase().trim();
|
|
124
127
|
if (n)
|
package/dist/errors.js
CHANGED
|
@@ -19,17 +19,20 @@ export class FetchError extends Error {
|
|
|
19
19
|
export function getErrorMessage(error) {
|
|
20
20
|
if (isError(error))
|
|
21
21
|
return error.message;
|
|
22
|
-
if (
|
|
22
|
+
if (isNonEmptyString(error))
|
|
23
23
|
return error;
|
|
24
24
|
if (isErrorWithMessage(error))
|
|
25
25
|
return error.message;
|
|
26
26
|
return formatUnknownError(error);
|
|
27
27
|
}
|
|
28
|
+
function isNonEmptyString(value) {
|
|
29
|
+
return typeof value === 'string' && value.length > 0;
|
|
30
|
+
}
|
|
28
31
|
function isErrorWithMessage(error) {
|
|
29
32
|
if (!isObject(error))
|
|
30
33
|
return false;
|
|
31
34
|
const { message } = error;
|
|
32
|
-
return
|
|
35
|
+
return isNonEmptyString(message);
|
|
33
36
|
}
|
|
34
37
|
function formatUnknownError(error) {
|
|
35
38
|
if (error === null || error === undefined)
|
package/dist/fetch.js
CHANGED
|
@@ -569,8 +569,11 @@ function createTooManyRedirectsFetchError(url) {
|
|
|
569
569
|
function createMissingRedirectLocationFetchError(url) {
|
|
570
570
|
return new FetchError('Redirect response missing Location header', url);
|
|
571
571
|
}
|
|
572
|
+
function buildNetworkErrorMessage(url) {
|
|
573
|
+
return `Network error: Could not reach ${url}`;
|
|
574
|
+
}
|
|
572
575
|
function createNetworkFetchError(url, message) {
|
|
573
|
-
return new FetchError(
|
|
576
|
+
return new FetchError(buildNetworkErrorMessage(url), url, undefined, message ? { message } : {});
|
|
574
577
|
}
|
|
575
578
|
function createUnknownFetchError(url, message) {
|
|
576
579
|
return new FetchError(message, url);
|
|
@@ -619,7 +622,7 @@ function mapFetchError(error, fallbackUrl, timeoutMs) {
|
|
|
619
622
|
code === 'EINVAL') {
|
|
620
623
|
return new FetchError(error.message, url, 400, { code });
|
|
621
624
|
}
|
|
622
|
-
return new FetchError(
|
|
625
|
+
return new FetchError(buildNetworkErrorMessage(url), url, undefined, {
|
|
623
626
|
code,
|
|
624
627
|
message: error.message,
|
|
625
628
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { isIP, SocketAddress } from 'node:net';
|
|
2
2
|
import { domainToASCII } from 'node:url';
|
|
3
3
|
export function normalizeHost(value) {
|
|
4
|
-
const trimmedLower = value
|
|
4
|
+
const trimmedLower = trimToNull(value)?.toLowerCase();
|
|
5
5
|
if (!trimmedLower)
|
|
6
6
|
return null;
|
|
7
7
|
const first = takeFirstHostValue(trimmedLower);
|
|
@@ -25,10 +25,7 @@ function takeFirstHostValue(value) {
|
|
|
25
25
|
// Faster than split(',') for large forwarded headers; preserves behavior.
|
|
26
26
|
const commaIndex = value.indexOf(',');
|
|
27
27
|
const first = commaIndex === -1 ? value : value.slice(0, commaIndex);
|
|
28
|
-
|
|
29
|
-
return null;
|
|
30
|
-
const trimmed = first.trim();
|
|
31
|
-
return trimmed ? trimmed : null;
|
|
28
|
+
return first ? trimToNull(first) : null;
|
|
32
29
|
}
|
|
33
30
|
function stripIpv6Brackets(value) {
|
|
34
31
|
if (!value.startsWith('['))
|
|
@@ -48,7 +45,7 @@ function isIpV6Literal(value) {
|
|
|
48
45
|
return isIP(value) === 6;
|
|
49
46
|
}
|
|
50
47
|
function normalizeHostname(value) {
|
|
51
|
-
const trimmed = value
|
|
48
|
+
const trimmed = trimToNull(value)?.toLowerCase();
|
|
52
49
|
if (!trimmed)
|
|
53
50
|
return null;
|
|
54
51
|
if (isIP(trimmed))
|
|
@@ -68,6 +65,10 @@ function parseHostWithUrl(value) {
|
|
|
68
65
|
return null;
|
|
69
66
|
}
|
|
70
67
|
}
|
|
68
|
+
function trimToNull(value) {
|
|
69
|
+
const trimmed = value.trim();
|
|
70
|
+
return trimmed ? trimmed : null;
|
|
71
|
+
}
|
|
71
72
|
function stripTrailingDots(value) {
|
|
72
73
|
// Keep loop (rather than regex) to preserve exact behavior and avoid hidden allocations.
|
|
73
74
|
let result = value;
|
package/dist/http-native.js
CHANGED
|
@@ -63,15 +63,13 @@ function createTransportAdapter(transportImpl) {
|
|
|
63
63
|
function sendJson(res, status, body) {
|
|
64
64
|
res.statusCode = status;
|
|
65
65
|
res.setHeader('Content-Type', 'application/json; charset=utf-8');
|
|
66
|
-
res
|
|
67
|
-
res.setHeader('Cache-Control', 'no-store');
|
|
66
|
+
setNoStoreHeaders(res);
|
|
68
67
|
res.end(JSON.stringify(body));
|
|
69
68
|
}
|
|
70
69
|
function sendText(res, status, body) {
|
|
71
70
|
res.statusCode = status;
|
|
72
71
|
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
|
|
73
|
-
res
|
|
74
|
-
res.setHeader('Cache-Control', 'no-store');
|
|
72
|
+
setNoStoreHeaders(res);
|
|
75
73
|
res.end(body);
|
|
76
74
|
}
|
|
77
75
|
function sendEmpty(res, status) {
|
|
@@ -79,6 +77,10 @@ function sendEmpty(res, status) {
|
|
|
79
77
|
res.setHeader('Content-Length', '0');
|
|
80
78
|
res.end();
|
|
81
79
|
}
|
|
80
|
+
function setNoStoreHeaders(res) {
|
|
81
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
82
|
+
res.setHeader('Cache-Control', 'no-store');
|
|
83
|
+
}
|
|
82
84
|
function drainRequest(req) {
|
|
83
85
|
if (req.readableEnded)
|
|
84
86
|
return;
|
package/dist/index.js
CHANGED
|
@@ -7,6 +7,9 @@ import { logError } from './observability.js';
|
|
|
7
7
|
import { startStdioServer } from './server.js';
|
|
8
8
|
const FORCE_EXIT_TIMEOUT_MS = 10_000;
|
|
9
9
|
let forcedExitTimer;
|
|
10
|
+
function toError(value) {
|
|
11
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
12
|
+
}
|
|
10
13
|
function scheduleForcedExit(reason) {
|
|
11
14
|
if (forcedExitTimer)
|
|
12
15
|
return;
|
|
@@ -69,8 +72,7 @@ process.on('uncaughtException', (error) => {
|
|
|
69
72
|
handleFatalError('Uncaught exception', error, 'UNCAUGHT_EXCEPTION');
|
|
70
73
|
});
|
|
71
74
|
process.on('unhandledRejection', (reason) => {
|
|
72
|
-
|
|
73
|
-
handleFatalError('Unhandled rejection', error, 'UNHANDLED_REJECTION');
|
|
75
|
+
handleFatalError('Unhandled rejection', toError(reason), 'UNHANDLED_REJECTION');
|
|
74
76
|
});
|
|
75
77
|
try {
|
|
76
78
|
if (isStdioMode) {
|
|
@@ -83,8 +85,9 @@ try {
|
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
catch (error) {
|
|
86
|
-
|
|
87
|
-
|
|
88
|
+
const resolvedError = toError(error);
|
|
89
|
+
logError('Failed to start server', resolvedError);
|
|
90
|
+
const { message } = resolvedError;
|
|
88
91
|
process.stderr.write(`Failed to start server: ${message}\n`);
|
|
89
92
|
process.exitCode = 1;
|
|
90
93
|
scheduleForcedExit('Startup failure');
|
package/dist/ip-blocklist.js
CHANGED
|
@@ -14,6 +14,7 @@ const IPV6_2002 = buildIpv6(['2002', 0, 0, 0, 0, 0, 0, 0]);
|
|
|
14
14
|
const IPV6_FC00 = buildIpv6(['fc00', 0, 0, 0, 0, 0, 0, 0]);
|
|
15
15
|
const IPV6_FE80 = buildIpv6(['fe80', 0, 0, 0, 0, 0, 0, 0]);
|
|
16
16
|
const IPV6_FF00 = buildIpv6(['ff00', 0, 0, 0, 0, 0, 0, 0]);
|
|
17
|
+
const IPV6_MAPPED_PREFIX = '::ffff:';
|
|
17
18
|
const BLOCKED_SUBNETS = [
|
|
18
19
|
{ subnet: buildIpv4([0, 0, 0, 0]), prefix: 8, family: 'ipv4' },
|
|
19
20
|
{ subnet: buildIpv4([10, 0, 0, 0]), prefix: 8, family: 'ipv4' },
|
|
@@ -42,10 +43,9 @@ export function createDefaultBlockList() {
|
|
|
42
43
|
return list;
|
|
43
44
|
}
|
|
44
45
|
function extractMappedIpv4(ip) {
|
|
45
|
-
|
|
46
|
-
if (!ip.startsWith(prefix))
|
|
46
|
+
if (!ip.startsWith(IPV6_MAPPED_PREFIX))
|
|
47
47
|
return null;
|
|
48
|
-
const mapped = ip.slice(
|
|
48
|
+
const mapped = ip.slice(IPV6_MAPPED_PREFIX.length);
|
|
49
49
|
return isIP(mapped) === 4 ? mapped : null;
|
|
50
50
|
}
|
|
51
51
|
function stripIpv6ZoneId(ip) {
|
package/dist/json.js
CHANGED
|
@@ -13,14 +13,15 @@ function processValue(obj, depth, seen) {
|
|
|
13
13
|
}
|
|
14
14
|
seen.add(obj);
|
|
15
15
|
try {
|
|
16
|
+
const processChild = (value) => processValue(value, depth + 1, seen);
|
|
16
17
|
if (Array.isArray(obj)) {
|
|
17
|
-
return obj.map((item) =>
|
|
18
|
+
return obj.map((item) => processChild(item));
|
|
18
19
|
}
|
|
19
20
|
const keys = Object.keys(obj).sort((a, b) => a.localeCompare(b));
|
|
20
21
|
const record = obj;
|
|
21
22
|
const sortedObj = {};
|
|
22
23
|
for (const key of keys) {
|
|
23
|
-
sortedObj[key] =
|
|
24
|
+
sortedObj[key] = processChild(record[key]);
|
|
24
25
|
}
|
|
25
26
|
return sortedObj;
|
|
26
27
|
}
|
|
@@ -118,11 +118,7 @@ function isBashLine(line) {
|
|
|
118
118
|
return false;
|
|
119
119
|
}
|
|
120
120
|
function detectBashIndicators(lines) {
|
|
121
|
-
|
|
122
|
-
if (isBashLine(line))
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
return false;
|
|
121
|
+
return lines.some((line) => isBashLine(line));
|
|
126
122
|
}
|
|
127
123
|
function detectCssStructure(lines) {
|
|
128
124
|
for (const line of lines) {
|
package/dist/markdown-cleanup.js
CHANGED
|
@@ -50,12 +50,14 @@ function throwIfAborted(signal, url, stage) {
|
|
|
50
50
|
function getLineEnding(content) {
|
|
51
51
|
return content.includes('\r\n') ? '\r\n' : '\n';
|
|
52
52
|
}
|
|
53
|
+
function isBlank(line) {
|
|
54
|
+
return line === undefined || line.trim().length === 0;
|
|
55
|
+
}
|
|
53
56
|
function hasFollowingContent(lines, startIndex) {
|
|
54
57
|
// Optimization: Bound lookahead to avoid checking too many lines in huge files
|
|
55
58
|
const max = Math.min(lines.length, startIndex + 50);
|
|
56
59
|
for (let i = startIndex + 1; i < max; i++) {
|
|
57
|
-
|
|
58
|
-
if (line && line.trim().length > 0)
|
|
60
|
+
if (!isBlank(lines[i]))
|
|
59
61
|
return true;
|
|
60
62
|
}
|
|
61
63
|
return false;
|
|
@@ -145,11 +147,12 @@ function getTocBlockStats(lines, headingIndex) {
|
|
|
145
147
|
function skipTocLines(lines, startIndex) {
|
|
146
148
|
for (let i = startIndex; i < lines.length; i++) {
|
|
147
149
|
const line = lines[i];
|
|
148
|
-
if (
|
|
150
|
+
if (line === undefined)
|
|
149
151
|
continue;
|
|
150
|
-
|
|
152
|
+
const trimmed = line.trim();
|
|
153
|
+
if (!trimmed)
|
|
151
154
|
continue;
|
|
152
|
-
if (!REGEX.TOC_LINK.test(
|
|
155
|
+
if (!REGEX.TOC_LINK.test(trimmed))
|
|
153
156
|
return i;
|
|
154
157
|
}
|
|
155
158
|
return lines.length;
|
package/dist/mcp-validator.js
CHANGED
|
@@ -13,17 +13,16 @@ export function isJsonRpcBatchRequest(body) {
|
|
|
13
13
|
export function isMcpRequestBody(body) {
|
|
14
14
|
return mcpRequestSchema.safeParse(body).success;
|
|
15
15
|
}
|
|
16
|
-
|
|
16
|
+
function parseAcceptHeader(header) {
|
|
17
17
|
if (!header)
|
|
18
|
-
return
|
|
19
|
-
return header
|
|
20
|
-
|
|
21
|
-
|
|
18
|
+
return [];
|
|
19
|
+
return header.split(',').map((value) => value.trim());
|
|
20
|
+
}
|
|
21
|
+
export function acceptsEventStream(header) {
|
|
22
|
+
return parseAcceptHeader(header).some((value) => value.trim().toLowerCase().startsWith('text/event-stream'));
|
|
22
23
|
}
|
|
23
24
|
function hasAcceptedMediaType(header, exact, wildcardPrefix) {
|
|
24
|
-
|
|
25
|
-
return false;
|
|
26
|
-
return header.split(',').some((rawPart) => {
|
|
25
|
+
return parseAcceptHeader(header).some((rawPart) => {
|
|
27
26
|
const mediaType = rawPart.trim().split(';', 1)[0]?.trim().toLowerCase();
|
|
28
27
|
if (!mediaType)
|
|
29
28
|
return false;
|
package/dist/mcp.js
CHANGED
|
@@ -8,26 +8,35 @@ import { isObject } from './type-guards.js';
|
|
|
8
8
|
/* -------------------------------------------------------------------------------------------------
|
|
9
9
|
* Tasks API schemas
|
|
10
10
|
* ------------------------------------------------------------------------------------------------- */
|
|
11
|
-
const TaskGetSchema = z
|
|
11
|
+
const TaskGetSchema = z
|
|
12
|
+
.object({
|
|
12
13
|
method: z.literal('tasks/get'),
|
|
13
|
-
params: z.
|
|
14
|
-
})
|
|
15
|
-
|
|
14
|
+
params: z.object({ taskId: z.string() }).loose(),
|
|
15
|
+
})
|
|
16
|
+
.loose();
|
|
17
|
+
const TaskListSchema = z
|
|
18
|
+
.object({
|
|
16
19
|
method: z.literal('tasks/list'),
|
|
17
20
|
params: z
|
|
18
|
-
.
|
|
21
|
+
.object({
|
|
19
22
|
cursor: z.string().optional(),
|
|
20
23
|
})
|
|
24
|
+
.loose()
|
|
21
25
|
.optional(),
|
|
22
|
-
})
|
|
23
|
-
|
|
26
|
+
})
|
|
27
|
+
.loose();
|
|
28
|
+
const TaskCancelSchema = z
|
|
29
|
+
.object({
|
|
24
30
|
method: z.literal('tasks/cancel'),
|
|
25
|
-
params: z.
|
|
26
|
-
})
|
|
27
|
-
|
|
31
|
+
params: z.object({ taskId: z.string() }).loose(),
|
|
32
|
+
})
|
|
33
|
+
.loose();
|
|
34
|
+
const TaskResultSchema = z
|
|
35
|
+
.object({
|
|
28
36
|
method: z.literal('tasks/result'),
|
|
29
|
-
params: z.
|
|
30
|
-
})
|
|
37
|
+
params: z.object({ taskId: z.string() }).loose(),
|
|
38
|
+
})
|
|
39
|
+
.loose();
|
|
31
40
|
const MIN_TASK_TTL_MS = 1_000;
|
|
32
41
|
const MAX_TASK_TTL_MS = 86_400_000;
|
|
33
42
|
const ExtendedCallToolRequestSchema = z
|
|
@@ -71,6 +80,14 @@ function parseExtendedCallToolRequest(request) {
|
|
|
71
80
|
function isRecord(value) {
|
|
72
81
|
return isObject(value);
|
|
73
82
|
}
|
|
83
|
+
function normalizeSendNotification(sendNotification) {
|
|
84
|
+
if (typeof sendNotification !== 'function')
|
|
85
|
+
return undefined;
|
|
86
|
+
const notify = sendNotification;
|
|
87
|
+
return async (notification) => {
|
|
88
|
+
await Promise.resolve(notify(notification));
|
|
89
|
+
};
|
|
90
|
+
}
|
|
74
91
|
function parseHandlerExtra(extra) {
|
|
75
92
|
if (!isObject(extra))
|
|
76
93
|
return undefined;
|
|
@@ -93,11 +110,9 @@ function parseHandlerExtra(extra) {
|
|
|
93
110
|
if (typeof requestId === 'string' || typeof requestId === 'number') {
|
|
94
111
|
parsed.requestId = requestId;
|
|
95
112
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
parsed.sendNotification =
|
|
99
|
-
await Promise.resolve(notify(notification));
|
|
100
|
-
};
|
|
113
|
+
const normalizedSendNotification = normalizeSendNotification(sendNotification);
|
|
114
|
+
if (normalizedSendNotification) {
|
|
115
|
+
parsed.sendNotification = normalizedSendNotification;
|
|
101
116
|
}
|
|
102
117
|
return parsed;
|
|
103
118
|
}
|
package/dist/observability.js
CHANGED
|
@@ -128,6 +128,13 @@ function mapToMcpLevel(level) {
|
|
|
128
128
|
return 'info';
|
|
129
129
|
}
|
|
130
130
|
}
|
|
131
|
+
function resolveErrorText(err) {
|
|
132
|
+
if (err instanceof Error)
|
|
133
|
+
return err.message;
|
|
134
|
+
if (typeof err === 'string')
|
|
135
|
+
return err;
|
|
136
|
+
return 'unknown error';
|
|
137
|
+
}
|
|
131
138
|
function safeWriteStderr(line) {
|
|
132
139
|
if (!stderrAvailable)
|
|
133
140
|
return;
|
|
@@ -164,20 +171,14 @@ function writeLog(level, message, meta) {
|
|
|
164
171
|
.catch((err) => {
|
|
165
172
|
if (!isDebugEnabled())
|
|
166
173
|
return;
|
|
167
|
-
|
|
168
|
-
if (err instanceof Error) {
|
|
169
|
-
errorText = err.message;
|
|
170
|
-
}
|
|
171
|
-
else if (typeof err === 'string') {
|
|
172
|
-
errorText = err;
|
|
173
|
-
}
|
|
174
|
+
const errorText = resolveErrorText(err);
|
|
174
175
|
safeWriteStderr(`[${createTimestamp()}] WARN: Failed to forward log to MCP${sessionId ? ` (sessionId=${sessionId})` : ''}: ${errorText}\n`);
|
|
175
176
|
});
|
|
176
177
|
}
|
|
177
178
|
catch (err) {
|
|
178
179
|
if (!isDebugEnabled())
|
|
179
180
|
return;
|
|
180
|
-
const errorText = err
|
|
181
|
+
const errorText = resolveErrorText(err);
|
|
181
182
|
safeWriteStderr(`[${createTimestamp()}] WARN: Failed to forward log to MCP (sync error): ${errorText}\n`);
|
|
182
183
|
}
|
|
183
184
|
}
|
package/dist/prompts.js
CHANGED
|
@@ -1,18 +1,10 @@
|
|
|
1
1
|
export function registerGetHelpPrompt(server, instructions, iconInfo) {
|
|
2
2
|
const description = 'Return the Fetch URL usage instructions.';
|
|
3
|
+
const iconConfig = iconInfo ? { icons: [{ ...iconInfo }] } : {};
|
|
3
4
|
server.registerPrompt('get-help', {
|
|
4
5
|
title: 'Get Help',
|
|
5
6
|
description,
|
|
6
|
-
...
|
|
7
|
-
? {
|
|
8
|
-
icons: [
|
|
9
|
-
{
|
|
10
|
-
src: iconInfo.src,
|
|
11
|
-
mimeType: iconInfo.mimeType,
|
|
12
|
-
},
|
|
13
|
-
],
|
|
14
|
-
}
|
|
15
|
-
: {}),
|
|
7
|
+
...iconConfig,
|
|
16
8
|
}, () => ({
|
|
17
9
|
description,
|
|
18
10
|
messages: [
|
package/dist/resources.js
CHANGED
|
@@ -21,17 +21,19 @@ function decodeSegment(value) {
|
|
|
21
21
|
return value;
|
|
22
22
|
}
|
|
23
23
|
}
|
|
24
|
+
function trimToValue(value) {
|
|
25
|
+
const trimmed = value.trim();
|
|
26
|
+
return trimmed.length > 0 ? trimmed : undefined;
|
|
27
|
+
}
|
|
24
28
|
function firstVariableValue(value) {
|
|
25
29
|
if (typeof value === 'string') {
|
|
26
|
-
|
|
27
|
-
return trimmed.length > 0 ? trimmed : undefined;
|
|
30
|
+
return trimToValue(value);
|
|
28
31
|
}
|
|
29
32
|
if (Array.isArray(value)) {
|
|
30
33
|
const first = value[0];
|
|
31
34
|
if (typeof first !== 'string')
|
|
32
35
|
return undefined;
|
|
33
|
-
|
|
34
|
-
return trimmed.length > 0 ? trimmed : undefined;
|
|
36
|
+
return trimToValue(first);
|
|
35
37
|
}
|
|
36
38
|
return undefined;
|
|
37
39
|
}
|
package/dist/server-tuning.js
CHANGED
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
import { config } from './config.js';
|
|
2
2
|
import { logDebug, logWarn } from './observability.js';
|
|
3
3
|
const DROP_LOG_INTERVAL_MS = 10_000;
|
|
4
|
+
function setIfDefined(value, setter) {
|
|
5
|
+
if (value === undefined)
|
|
6
|
+
return;
|
|
7
|
+
setter(value);
|
|
8
|
+
}
|
|
4
9
|
export function applyHttpServerTuning(server) {
|
|
5
10
|
const { headersTimeoutMs, requestTimeoutMs, keepAliveTimeoutMs, keepAliveTimeoutBufferMs, maxHeadersCount, maxConnections, } = config.server.http;
|
|
6
|
-
|
|
7
|
-
server.headersTimeout =
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
server.requestTimeout =
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
server.keepAliveTimeout =
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
server.keepAliveTimeoutBuffer =
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
server.maxHeadersCount =
|
|
20
|
-
}
|
|
11
|
+
setIfDefined(headersTimeoutMs, (value) => {
|
|
12
|
+
server.headersTimeout = value;
|
|
13
|
+
});
|
|
14
|
+
setIfDefined(requestTimeoutMs, (value) => {
|
|
15
|
+
server.requestTimeout = value;
|
|
16
|
+
});
|
|
17
|
+
setIfDefined(keepAliveTimeoutMs, (value) => {
|
|
18
|
+
server.keepAliveTimeout = value;
|
|
19
|
+
});
|
|
20
|
+
setIfDefined(keepAliveTimeoutBufferMs, (value) => {
|
|
21
|
+
server.keepAliveTimeoutBuffer = value;
|
|
22
|
+
});
|
|
23
|
+
setIfDefined(maxHeadersCount, (value) => {
|
|
24
|
+
server.maxHeadersCount = value;
|
|
25
|
+
});
|
|
21
26
|
if (typeof maxConnections === 'number' && maxConnections > 0) {
|
|
22
27
|
server.maxConnections = maxConnections;
|
|
23
28
|
if (typeof server.on === 'function') {
|
package/dist/server.js
CHANGED
|
@@ -70,6 +70,9 @@ function createServerInfo(icons) {
|
|
|
70
70
|
...(icons ? { icons } : {}),
|
|
71
71
|
};
|
|
72
72
|
}
|
|
73
|
+
function toIconList(icon) {
|
|
74
|
+
return icon ? [icon] : undefined;
|
|
75
|
+
}
|
|
73
76
|
/* -------------------------------------------------------------------------------------------------
|
|
74
77
|
* Server lifecycle
|
|
75
78
|
* ------------------------------------------------------------------------------------------------- */
|
|
@@ -88,14 +91,7 @@ async function createMcpServerWithOptions(options) {
|
|
|
88
91
|
if (serverInstructions) {
|
|
89
92
|
serverConfig.instructions = serverInstructions;
|
|
90
93
|
}
|
|
91
|
-
const serverInfo = createServerInfo(localIcon
|
|
92
|
-
? [
|
|
93
|
-
{
|
|
94
|
-
src: localIcon.src,
|
|
95
|
-
mimeType: localIcon.mimeType,
|
|
96
|
-
},
|
|
97
|
-
]
|
|
98
|
-
: undefined);
|
|
94
|
+
const serverInfo = createServerInfo(toIconList(localIcon));
|
|
99
95
|
const server = new McpServer(serverInfo, serverConfig);
|
|
100
96
|
if (options?.registerObservabilityServer ?? true) {
|
|
101
97
|
setMcpServer(server);
|
package/dist/session.js
CHANGED
|
@@ -96,6 +96,9 @@ function moveSessionToEnd(sessions, sessionId, session) {
|
|
|
96
96
|
sessions.delete(sessionId);
|
|
97
97
|
sessions.set(sessionId, session);
|
|
98
98
|
}
|
|
99
|
+
function isBlankSessionId(sessionId) {
|
|
100
|
+
return sessionId.length === 0;
|
|
101
|
+
}
|
|
99
102
|
class InMemorySessionStore {
|
|
100
103
|
sessionTtlMs;
|
|
101
104
|
sessions = new Map();
|
|
@@ -104,12 +107,12 @@ class InMemorySessionStore {
|
|
|
104
107
|
this.sessionTtlMs = sessionTtlMs;
|
|
105
108
|
}
|
|
106
109
|
get(sessionId) {
|
|
107
|
-
if (
|
|
110
|
+
if (isBlankSessionId(sessionId))
|
|
108
111
|
return undefined;
|
|
109
112
|
return this.sessions.get(sessionId);
|
|
110
113
|
}
|
|
111
114
|
touch(sessionId) {
|
|
112
|
-
if (
|
|
115
|
+
if (isBlankSessionId(sessionId))
|
|
113
116
|
return;
|
|
114
117
|
const session = this.sessions.get(sessionId);
|
|
115
118
|
if (!session)
|
|
@@ -118,12 +121,12 @@ class InMemorySessionStore {
|
|
|
118
121
|
moveSessionToEnd(this.sessions, sessionId, session);
|
|
119
122
|
}
|
|
120
123
|
set(sessionId, entry) {
|
|
121
|
-
if (
|
|
124
|
+
if (isBlankSessionId(sessionId))
|
|
122
125
|
return;
|
|
123
126
|
moveSessionToEnd(this.sessions, sessionId, entry);
|
|
124
127
|
}
|
|
125
128
|
remove(sessionId) {
|
|
126
|
-
if (
|
|
129
|
+
if (isBlankSessionId(sessionId))
|
|
127
130
|
return undefined;
|
|
128
131
|
const session = this.sessions.get(sessionId);
|
|
129
132
|
this.sessions.delete(sessionId);
|
package/dist/tasks.js
CHANGED
|
@@ -41,7 +41,7 @@ class TaskManager {
|
|
|
41
41
|
removeExpiredTasks() {
|
|
42
42
|
const now = Date.now();
|
|
43
43
|
for (const [id, task] of this.tasks) {
|
|
44
|
-
if (
|
|
44
|
+
if (this.isExpired(task, now)) {
|
|
45
45
|
this.tasks.delete(id);
|
|
46
46
|
}
|
|
47
47
|
}
|
|
@@ -146,7 +146,7 @@ class TaskManager {
|
|
|
146
146
|
for (const task of this.tasks.values()) {
|
|
147
147
|
if (task.ownerKey !== ownerKey)
|
|
148
148
|
continue;
|
|
149
|
-
if (
|
|
149
|
+
if (this.isExpired(task, now)) {
|
|
150
150
|
this.tasks.delete(task.taskId);
|
|
151
151
|
continue;
|
|
152
152
|
}
|
|
@@ -284,8 +284,8 @@ class TaskManager {
|
|
|
284
284
|
for (const waiter of waiters)
|
|
285
285
|
waiter(task);
|
|
286
286
|
}
|
|
287
|
-
isExpired(task) {
|
|
288
|
-
return
|
|
287
|
+
isExpired(task, now = Date.now()) {
|
|
288
|
+
return now - task._createdAtMs > task.ttl;
|
|
289
289
|
}
|
|
290
290
|
encodeCursor(index) {
|
|
291
291
|
return Buffer.from(String(index), 'utf8').toString('base64url');
|
package/dist/timer-utils.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { setTimeout as setTimeoutPromise } from 'node:timers/promises';
|
|
2
2
|
import { isError } from './type-guards.js';
|
|
3
|
+
function isAbortError(error) {
|
|
4
|
+
return isError(error) && error.name === 'AbortError';
|
|
5
|
+
}
|
|
3
6
|
export function createUnrefTimeout(timeoutMs, value) {
|
|
4
7
|
const controller = new AbortController();
|
|
5
8
|
const promise = setTimeoutPromise(timeoutMs, value, {
|
|
6
9
|
ref: false,
|
|
7
10
|
signal: controller.signal,
|
|
8
11
|
}).catch((err) => {
|
|
9
|
-
if (
|
|
12
|
+
if (isAbortError(err)) {
|
|
10
13
|
return new Promise(() => { });
|
|
11
14
|
}
|
|
12
15
|
throw err;
|
package/dist/tools.js
CHANGED
|
@@ -708,10 +708,7 @@ function serializeMarkdownResult(result) {
|
|
|
708
708
|
function buildStructuredContent(pipeline, inlineResult, inputUrl) {
|
|
709
709
|
const cacheResourceUri = resolveCacheResourceUri(pipeline.cacheKey);
|
|
710
710
|
const truncated = inlineResult.truncated ?? pipeline.data.truncated;
|
|
711
|
-
|
|
712
|
-
if (pipeline.data.truncated && typeof markdown === 'string') {
|
|
713
|
-
markdown = appendTruncationMarker(markdown, TRUNCATION_MARKER);
|
|
714
|
-
}
|
|
711
|
+
const markdown = applyTruncationMarker(inlineResult.content, pipeline.data.truncated);
|
|
715
712
|
const { metadata } = pipeline.data;
|
|
716
713
|
return {
|
|
717
714
|
url: pipeline.originalUrl ?? pipeline.url,
|
|
@@ -728,6 +725,11 @@ function buildStructuredContent(pipeline, inlineResult, inputUrl) {
|
|
|
728
725
|
...(truncated ? { truncated: true } : {}),
|
|
729
726
|
};
|
|
730
727
|
}
|
|
728
|
+
function applyTruncationMarker(content, truncated) {
|
|
729
|
+
if (!truncated || typeof content !== 'string')
|
|
730
|
+
return content;
|
|
731
|
+
return appendTruncationMarker(content, TRUNCATION_MARKER);
|
|
732
|
+
}
|
|
731
733
|
function resolveCacheResourceUri(cacheKey) {
|
|
732
734
|
if (!cacheKey)
|
|
733
735
|
return undefined;
|
|
@@ -864,14 +866,14 @@ function resolveSessionIdFromExtra(extra) {
|
|
|
864
866
|
export function registerTools(server) {
|
|
865
867
|
if (!config.tools.enabled.includes(FETCH_URL_TOOL_NAME))
|
|
866
868
|
return;
|
|
867
|
-
server.registerTool(TOOL_DEFINITION.name, {
|
|
869
|
+
const registeredTool = server.registerTool(TOOL_DEFINITION.name, {
|
|
868
870
|
title: TOOL_DEFINITION.title,
|
|
869
871
|
description: TOOL_DEFINITION.description,
|
|
870
872
|
inputSchema: TOOL_DEFINITION.inputSchema,
|
|
871
873
|
outputSchema: TOOL_DEFINITION.outputSchema,
|
|
872
874
|
annotations: TOOL_DEFINITION.annotations,
|
|
873
875
|
execution: TOOL_DEFINITION.execution,
|
|
874
|
-
// Use specific tool icon here
|
|
875
876
|
icons: [TOOL_ICON],
|
|
876
877
|
}, withRequestContextIfMissing(TOOL_DEFINITION.handler));
|
|
878
|
+
registeredTool.execution = TOOL_DEFINITION.execution;
|
|
877
879
|
}
|
package/dist/transform.js
CHANGED
|
@@ -42,6 +42,9 @@ function getTagName(node) {
|
|
|
42
42
|
const raw = node.tagName;
|
|
43
43
|
return typeof raw === 'string' ? raw.toUpperCase() : '';
|
|
44
44
|
}
|
|
45
|
+
function asError(value) {
|
|
46
|
+
return value instanceof Error ? value : undefined;
|
|
47
|
+
}
|
|
45
48
|
function getAbortReason(signal) {
|
|
46
49
|
const record = isObject(signal) ? signal : null;
|
|
47
50
|
return record && 'reason' in record ? record['reason'] : undefined;
|
|
@@ -513,7 +516,7 @@ function extractArticle(document, url, signal) {
|
|
|
513
516
|
};
|
|
514
517
|
}
|
|
515
518
|
catch (error) {
|
|
516
|
-
logError('Failed to extract article with Readability', error
|
|
519
|
+
logError('Failed to extract article with Readability', asError(error));
|
|
517
520
|
return null;
|
|
518
521
|
}
|
|
519
522
|
}
|
|
@@ -573,7 +576,7 @@ function extractContentContext(html, url, options) {
|
|
|
573
576
|
if (error instanceof FetchError)
|
|
574
577
|
throw error;
|
|
575
578
|
abortPolicy.throwIfAborted(options.signal, url, 'extract:error');
|
|
576
|
-
logError('Failed to extract content', error
|
|
579
|
+
logError('Failed to extract content', asError(error));
|
|
577
580
|
const { document } = parseHTML('<html></html>');
|
|
578
581
|
return { article: null, metadata: {}, document };
|
|
579
582
|
}
|
|
@@ -1177,7 +1180,7 @@ export function htmlToMarkdown(html, metadata, options) {
|
|
|
1177
1180
|
catch (error) {
|
|
1178
1181
|
if (error instanceof FetchError)
|
|
1179
1182
|
throw error;
|
|
1180
|
-
logError('Failed to convert HTML to markdown', error
|
|
1183
|
+
logError('Failed to convert HTML to markdown', asError(error));
|
|
1181
1184
|
throw new FetchError('Failed to convert HTML to markdown', url, 500, {
|
|
1182
1185
|
reason: 'markdown_convert_failed',
|
|
1183
1186
|
});
|
package/dist/type-guards.js
CHANGED
|
@@ -2,9 +2,9 @@ export function isObject(value) {
|
|
|
2
2
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
3
3
|
}
|
|
4
4
|
export function isError(value) {
|
|
5
|
-
const
|
|
6
|
-
if (typeof
|
|
7
|
-
return
|
|
5
|
+
const { isError: isErrorFn } = Error;
|
|
6
|
+
if (typeof isErrorFn === 'function') {
|
|
7
|
+
return isErrorFn(value);
|
|
8
8
|
}
|
|
9
9
|
return value instanceof Error;
|
|
10
10
|
}
|
|
@@ -4,11 +4,15 @@ import { transformHtmlToMarkdownInProcess } from '../transform.js';
|
|
|
4
4
|
const send = process.send?.bind(process);
|
|
5
5
|
if (!send)
|
|
6
6
|
throw new Error('transform-child started without IPC channel');
|
|
7
|
+
const sendMessage = send;
|
|
8
|
+
function postMessage(message) {
|
|
9
|
+
sendMessage(message);
|
|
10
|
+
}
|
|
7
11
|
const controllersById = new Map();
|
|
8
12
|
const decoder = new TextDecoder('utf-8');
|
|
9
13
|
function postError(id, url, error) {
|
|
10
14
|
if (error instanceof FetchError) {
|
|
11
|
-
|
|
15
|
+
postMessage({
|
|
12
16
|
type: 'error',
|
|
13
17
|
id,
|
|
14
18
|
error: {
|
|
@@ -21,7 +25,7 @@ function postError(id, url, error) {
|
|
|
21
25
|
});
|
|
22
26
|
return;
|
|
23
27
|
}
|
|
24
|
-
|
|
28
|
+
postMessage({
|
|
25
29
|
type: 'error',
|
|
26
30
|
id,
|
|
27
31
|
error: {
|
|
@@ -52,7 +56,7 @@ function isValidMessage(msg) {
|
|
|
52
56
|
return true;
|
|
53
57
|
}
|
|
54
58
|
function postValidationError(id, url, message) {
|
|
55
|
-
|
|
59
|
+
postMessage({
|
|
56
60
|
type: 'error',
|
|
57
61
|
id,
|
|
58
62
|
error: { name: 'ValidationError', message, url },
|
|
@@ -94,7 +98,7 @@ function handleTransform(msg) {
|
|
|
94
98
|
...(inputTruncated ? { inputTruncated: true } : {}),
|
|
95
99
|
});
|
|
96
100
|
const { markdown, metadata, title, truncated } = result;
|
|
97
|
-
|
|
101
|
+
postMessage({
|
|
98
102
|
type: 'result',
|
|
99
103
|
id,
|
|
100
104
|
result: title === undefined
|
|
@@ -61,6 +61,9 @@ function decodeHtmlBuffer(htmlBuffer, encoding) {
|
|
|
61
61
|
return decoder.decode(htmlBuffer);
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
|
+
function resolveHtmlContent(html, htmlBuffer, encoding) {
|
|
65
|
+
return htmlBuffer ? decodeHtmlBuffer(htmlBuffer, encoding) : (html ?? '');
|
|
66
|
+
}
|
|
64
67
|
function handleTransform(msg) {
|
|
65
68
|
if (!isValidMessage(msg))
|
|
66
69
|
return;
|
|
@@ -76,9 +79,7 @@ function handleTransform(msg) {
|
|
|
76
79
|
const controller = new AbortController();
|
|
77
80
|
controllersById.set(id, controller);
|
|
78
81
|
try {
|
|
79
|
-
const content = htmlBuffer
|
|
80
|
-
? decodeHtmlBuffer(htmlBuffer, encoding)
|
|
81
|
-
: (html ?? '');
|
|
82
|
+
const content = resolveHtmlContent(html, htmlBuffer, encoding);
|
|
82
83
|
const result = transformHtmlToMarkdownInProcess(content, url, {
|
|
83
84
|
includeMetadata,
|
|
84
85
|
signal: controller.signal,
|
package/package.json
CHANGED