@j0hanz/fetch-url-mcp 1.1.0 → 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.d.ts +5 -5
- package/dist/cache.js +5 -2
- package/dist/cli.js +12 -5
- package/dist/config.d.ts +1 -1
- 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.d.ts +4 -3
- 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.d.ts +6 -1
- package/dist/markdown-cleanup.js +92 -30
- package/dist/mcp-validator.d.ts +3 -2
- package/dist/mcp-validator.js +7 -8
- package/dist/mcp.js +32 -17
- package/dist/observability.d.ts +1 -1
- package/dist/observability.js +9 -8
- package/dist/prompts.js +2 -10
- package/dist/resources.js +6 -4
- package/dist/server-tuning.d.ts +2 -1
- package/dist/server-tuning.js +20 -15
- package/dist/server.js +4 -8
- package/dist/session.d.ts +3 -2
- package/dist/session.js +7 -4
- package/dist/tasks.d.ts +1 -1
- package/dist/tasks.js +4 -4
- package/dist/timer-utils.js +4 -1
- package/dist/tools.d.ts +9 -14
- package/dist/tools.js +8 -8
- package/dist/transform-types.d.ts +5 -1
- package/dist/transform.d.ts +2 -2
- package/dist/transform.js +52 -6
- package/dist/type-guards.d.ts +2 -1
- package/dist/type-guards.js +3 -3
- package/dist/workers/transform-child.js +8 -4
- package/dist/workers/transform-worker.js +5 -3
- package/package.json +1 -1
package/dist/markdown-cleanup.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { config } from './config.js';
|
|
2
|
+
import { FetchError } from './errors.js';
|
|
2
3
|
// --- Constants & Regex ---
|
|
3
4
|
const MAX_LINE_LENGTH = 80;
|
|
4
5
|
const REGEX = {
|
|
@@ -24,20 +25,39 @@ const REGEX = {
|
|
|
24
25
|
SPACING_ESCAPES: /\\([[\].])/g,
|
|
25
26
|
SPACING_LIST_NUM_COMBINED: /^((?![-*+] |\d+\. |[ \t]).+)\n((?:[-*+]|\d+\.) )/gm,
|
|
26
27
|
NESTED_LIST_INDENT: /^( +)((?:[-*+])|\d+\.)\s/gm,
|
|
27
|
-
|
|
28
|
+
TYPEDOC_COMMENT: /(`+)(?:(?!\1)[\s\S])*?\1|\s?\/\\?\*[\s\S]*?\\?\*\//g,
|
|
28
29
|
};
|
|
29
30
|
const HEADING_KEYWORDS = new Set(config.markdownCleanup.headingKeywords.map((value) => value.toLocaleLowerCase(config.i18n.locale)));
|
|
30
31
|
const SPECIAL_PREFIXES = /^(?:example|note|tip|warning|important|caution):\s+\S/i;
|
|
32
|
+
const TOC_SCAN_LIMIT = 20;
|
|
33
|
+
const TOC_MAX_NON_EMPTY = 12;
|
|
34
|
+
const TOC_LINK_RATIO_THRESHOLD = 0.8;
|
|
35
|
+
const TYPEDOC_PREFIXES = [
|
|
36
|
+
'Defined in:',
|
|
37
|
+
'Returns:',
|
|
38
|
+
'Since:',
|
|
39
|
+
'See also:',
|
|
40
|
+
];
|
|
41
|
+
function throwIfAborted(signal, url, stage) {
|
|
42
|
+
if (!signal?.aborted)
|
|
43
|
+
return;
|
|
44
|
+
throw new FetchError('Request was canceled', url, 499, {
|
|
45
|
+
reason: 'aborted',
|
|
46
|
+
stage,
|
|
47
|
+
});
|
|
48
|
+
}
|
|
31
49
|
// --- Helper Functions ---
|
|
32
50
|
function getLineEnding(content) {
|
|
33
51
|
return content.includes('\r\n') ? '\r\n' : '\n';
|
|
34
52
|
}
|
|
53
|
+
function isBlank(line) {
|
|
54
|
+
return line === undefined || line.trim().length === 0;
|
|
55
|
+
}
|
|
35
56
|
function hasFollowingContent(lines, startIndex) {
|
|
36
57
|
// Optimization: Bound lookahead to avoid checking too many lines in huge files
|
|
37
58
|
const max = Math.min(lines.length, startIndex + 50);
|
|
38
59
|
for (let i = startIndex + 1; i < max; i++) {
|
|
39
|
-
|
|
40
|
-
if (line && line.trim().length > 0)
|
|
60
|
+
if (!isBlank(lines[i]))
|
|
41
61
|
return true;
|
|
42
62
|
}
|
|
43
63
|
return false;
|
|
@@ -100,29 +120,55 @@ function getHeadingPrefix(trimmed) {
|
|
|
100
120
|
return isTitleCaseOrKeyword(trimmed) ? '## ' : null;
|
|
101
121
|
}
|
|
102
122
|
// Optimized TOC detection
|
|
103
|
-
function
|
|
104
|
-
|
|
123
|
+
function getTocBlockStats(lines, headingIndex) {
|
|
124
|
+
let total = 0;
|
|
125
|
+
let linkCount = 0;
|
|
126
|
+
let nonLinkCount = 0;
|
|
127
|
+
const lookaheadMax = Math.min(lines.length, headingIndex + TOC_SCAN_LIMIT);
|
|
105
128
|
for (let i = headingIndex + 1; i < lookaheadMax; i++) {
|
|
106
129
|
const line = lines[i];
|
|
107
|
-
if (!line
|
|
130
|
+
if (!line)
|
|
108
131
|
continue;
|
|
109
|
-
|
|
110
|
-
|
|
132
|
+
const trimmed = line.trim();
|
|
133
|
+
if (!trimmed)
|
|
134
|
+
continue;
|
|
135
|
+
if (REGEX.HEADING_MARKER.test(trimmed))
|
|
136
|
+
break;
|
|
137
|
+
total += 1;
|
|
138
|
+
if (REGEX.TOC_LINK.test(trimmed))
|
|
139
|
+
linkCount += 1;
|
|
140
|
+
else
|
|
141
|
+
nonLinkCount += 1;
|
|
142
|
+
if (total >= TOC_MAX_NON_EMPTY)
|
|
143
|
+
break;
|
|
111
144
|
}
|
|
112
|
-
return
|
|
145
|
+
return { total, linkCount, nonLinkCount };
|
|
113
146
|
}
|
|
114
147
|
function skipTocLines(lines, startIndex) {
|
|
115
148
|
for (let i = startIndex; i < lines.length; i++) {
|
|
116
149
|
const line = lines[i];
|
|
117
|
-
if (
|
|
150
|
+
if (line === undefined)
|
|
118
151
|
continue;
|
|
119
|
-
|
|
152
|
+
const trimmed = line.trim();
|
|
153
|
+
if (!trimmed)
|
|
120
154
|
continue;
|
|
121
|
-
if (!REGEX.TOC_LINK.test(
|
|
155
|
+
if (!REGEX.TOC_LINK.test(trimmed))
|
|
122
156
|
return i;
|
|
123
157
|
}
|
|
124
158
|
return lines.length;
|
|
125
159
|
}
|
|
160
|
+
function isTypeDocArtifactLine(line) {
|
|
161
|
+
const trimmed = line.trim();
|
|
162
|
+
for (const prefix of TYPEDOC_PREFIXES) {
|
|
163
|
+
if (!trimmed.startsWith(prefix))
|
|
164
|
+
continue;
|
|
165
|
+
const rest = trimmed.slice(prefix.length).trimStart();
|
|
166
|
+
if (!rest.startsWith('**`'))
|
|
167
|
+
return false;
|
|
168
|
+
return rest.includes('`**');
|
|
169
|
+
}
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
126
172
|
// --- Main Processing Logic ---
|
|
127
173
|
function tryPromoteOrphan(lines, i, trimmed) {
|
|
128
174
|
const prevLine = lines[i - 1];
|
|
@@ -137,13 +183,19 @@ function tryPromoteOrphan(lines, i, trimmed) {
|
|
|
137
183
|
return null;
|
|
138
184
|
return `${prefix}${trimmed}`;
|
|
139
185
|
}
|
|
140
|
-
function shouldSkipAsToc(lines, i, trimmed, removeToc) {
|
|
141
|
-
if (removeToc
|
|
142
|
-
return
|
|
143
|
-
}
|
|
144
|
-
|
|
186
|
+
function shouldSkipAsToc(lines, i, trimmed, removeToc, options) {
|
|
187
|
+
if (!removeToc || !REGEX.TOC_HEADING.test(trimmed))
|
|
188
|
+
return null;
|
|
189
|
+
const { total, linkCount, nonLinkCount } = getTocBlockStats(lines, i);
|
|
190
|
+
if (total === 0 || nonLinkCount > 0)
|
|
191
|
+
return null;
|
|
192
|
+
const ratio = linkCount / total;
|
|
193
|
+
if (ratio <= TOC_LINK_RATIO_THRESHOLD)
|
|
194
|
+
return null;
|
|
195
|
+
throwIfAborted(options?.signal, options?.url ?? '', 'markdown:cleanup:toc');
|
|
196
|
+
return skipTocLines(lines, i + 1);
|
|
145
197
|
}
|
|
146
|
-
function preprocessLines(lines) {
|
|
198
|
+
function preprocessLines(lines, options) {
|
|
147
199
|
const processedLines = [];
|
|
148
200
|
const len = lines.length;
|
|
149
201
|
const promote = config.markdownCleanup.promoteOrphanHeadings;
|
|
@@ -158,12 +210,13 @@ function preprocessLines(lines) {
|
|
|
158
210
|
const trimmed = line.trim();
|
|
159
211
|
if (REGEX.EMPTY_HEADING_LINE.test(trimmed))
|
|
160
212
|
continue;
|
|
161
|
-
const tocSkip = shouldSkipAsToc(lines, i, trimmed, removeToc);
|
|
213
|
+
const tocSkip = shouldSkipAsToc(lines, i, trimmed, removeToc, options);
|
|
162
214
|
if (tocSkip !== null) {
|
|
163
215
|
skipUntil = tocSkip;
|
|
164
216
|
continue;
|
|
165
217
|
}
|
|
166
218
|
if (promote && trimmed.length > 0) {
|
|
219
|
+
throwIfAborted(options?.signal, options?.url ?? '', 'markdown:cleanup:promote');
|
|
167
220
|
const promoted = tryPromoteOrphan(lines, i, trimmed);
|
|
168
221
|
if (promoted)
|
|
169
222
|
line = promoted;
|
|
@@ -173,28 +226,35 @@ function preprocessLines(lines) {
|
|
|
173
226
|
return processedLines.join('\n');
|
|
174
227
|
}
|
|
175
228
|
// Process a block of non-fence lines
|
|
176
|
-
function processTextBuffer(lines) {
|
|
229
|
+
function processTextBuffer(lines, options) {
|
|
177
230
|
if (lines.length === 0)
|
|
178
231
|
return '';
|
|
179
|
-
const text = preprocessLines(lines);
|
|
180
|
-
return applyGlobalRegexes(text);
|
|
232
|
+
const text = preprocessLines(lines, options);
|
|
233
|
+
return applyGlobalRegexes(text, options);
|
|
181
234
|
}
|
|
182
|
-
function applyGlobalRegexes(text) {
|
|
235
|
+
function applyGlobalRegexes(text, options) {
|
|
183
236
|
let result = text;
|
|
237
|
+
throwIfAborted(options?.signal, options?.url ?? '', 'markdown:cleanup:headings');
|
|
184
238
|
// fixAndSpaceHeadings
|
|
185
239
|
result = result
|
|
186
240
|
.replace(REGEX.HEADING_SPACING, '$1\n\n$2')
|
|
187
241
|
.replace(REGEX.HEADING_CODE_BLOCK, '$1\n\n```')
|
|
188
242
|
.replace(REGEX.HEADING_CAMEL_CASE, '$1\n\n$2');
|
|
189
|
-
// removeTypeDocComments
|
|
190
243
|
if (config.markdownCleanup.removeTypeDocComments) {
|
|
191
|
-
|
|
244
|
+
throwIfAborted(options?.signal, options?.url ?? '', 'markdown:cleanup:typedoc');
|
|
245
|
+
result = result
|
|
246
|
+
.split('\n')
|
|
247
|
+
.filter((line) => !isTypeDocArtifactLine(line))
|
|
248
|
+
.join('\n');
|
|
249
|
+
result = result.replace(REGEX.TYPEDOC_COMMENT, (match) => match.startsWith('`') ? match : '');
|
|
192
250
|
}
|
|
193
251
|
if (config.markdownCleanup.removeSkipLinks) {
|
|
252
|
+
throwIfAborted(options?.signal, options?.url ?? '', 'markdown:cleanup:skip-links');
|
|
194
253
|
result = result
|
|
195
254
|
.replace(REGEX.ZERO_WIDTH_ANCHOR, '')
|
|
196
255
|
.replace(REGEX.COMBINED_LINE_REMOVALS, '');
|
|
197
256
|
}
|
|
257
|
+
throwIfAborted(options?.signal, options?.url ?? '', 'markdown:cleanup:spacing');
|
|
198
258
|
// normalizeSpacing
|
|
199
259
|
result = result
|
|
200
260
|
.replace(REGEX.SPACING_LINK_FIX, ']($1)\n\n[')
|
|
@@ -204,6 +264,7 @@ function applyGlobalRegexes(text) {
|
|
|
204
264
|
.replace(REGEX.SPACING_LIST_NUM_COMBINED, '$1\n\n$2')
|
|
205
265
|
.replace(REGEX.DOUBLE_NEWLINE_REDUCER, '\n\n');
|
|
206
266
|
result = normalizeNestedListIndentation(result);
|
|
267
|
+
throwIfAborted(options?.signal, options?.url ?? '', 'markdown:cleanup:properties');
|
|
207
268
|
// fixProperties
|
|
208
269
|
for (let k = 0; k < 3; k++) {
|
|
209
270
|
const next = result.replace(REGEX.CONCATENATED_PROPS, '$1$2\n\n$3');
|
|
@@ -251,22 +312,23 @@ function handleFencedLine(line, trimmed, fenceMarker, segments) {
|
|
|
251
312
|
segments.push(line);
|
|
252
313
|
return isFenceClosure(trimmed, fenceMarker) ? null : fenceMarker;
|
|
253
314
|
}
|
|
254
|
-
function handleUnfencedLine(line, segments, buffer) {
|
|
315
|
+
function handleUnfencedLine(line, segments, buffer, options) {
|
|
255
316
|
const newMarker = checkFenceStart(line);
|
|
256
317
|
if (!newMarker) {
|
|
257
318
|
buffer.push(line);
|
|
258
319
|
return { fenceMarker: null, buffer };
|
|
259
320
|
}
|
|
260
321
|
if (buffer.length > 0) {
|
|
261
|
-
segments.push(processTextBuffer(buffer));
|
|
322
|
+
segments.push(processTextBuffer(buffer, options));
|
|
262
323
|
buffer = [];
|
|
263
324
|
}
|
|
264
325
|
segments.push(line);
|
|
265
326
|
return { fenceMarker: newMarker, buffer };
|
|
266
327
|
}
|
|
267
|
-
export function cleanupMarkdownArtifacts(content) {
|
|
328
|
+
export function cleanupMarkdownArtifacts(content, options) {
|
|
268
329
|
if (!content)
|
|
269
330
|
return '';
|
|
331
|
+
throwIfAborted(options?.signal, options?.url ?? '', 'markdown:cleanup:begin');
|
|
270
332
|
const len = content.length;
|
|
271
333
|
let lastIndex = 0;
|
|
272
334
|
let fenceMarker = null;
|
|
@@ -279,12 +341,12 @@ export function cleanupMarkdownArtifacts(content) {
|
|
|
279
341
|
fenceMarker = handleFencedLine(line, trimmed, fenceMarker, segments);
|
|
280
342
|
}
|
|
281
343
|
else {
|
|
282
|
-
({ fenceMarker, buffer } = handleUnfencedLine(line, segments, buffer));
|
|
344
|
+
({ fenceMarker, buffer } = handleUnfencedLine(line, segments, buffer, options));
|
|
283
345
|
}
|
|
284
346
|
lastIndex = nextIndex;
|
|
285
347
|
}
|
|
286
348
|
if (buffer.length > 0) {
|
|
287
|
-
segments.push(processTextBuffer(buffer));
|
|
349
|
+
segments.push(processTextBuffer(buffer, options));
|
|
288
350
|
}
|
|
289
351
|
return segments.join('\n').trim();
|
|
290
352
|
}
|
package/dist/mcp-validator.d.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
export type JsonRpcId = string | number | null;
|
|
2
|
-
|
|
2
|
+
interface McpRequestParams {
|
|
3
3
|
_meta?: Record<string, unknown>;
|
|
4
4
|
[key: string]: unknown;
|
|
5
5
|
}
|
|
6
|
-
|
|
6
|
+
interface McpRequestBody {
|
|
7
7
|
jsonrpc: '2.0';
|
|
8
8
|
method: string;
|
|
9
9
|
id?: JsonRpcId;
|
|
@@ -13,3 +13,4 @@ export declare function isJsonRpcBatchRequest(body: unknown): boolean;
|
|
|
13
13
|
export declare function isMcpRequestBody(body: unknown): body is McpRequestBody;
|
|
14
14
|
export declare function acceptsEventStream(header: string | null | undefined): boolean;
|
|
15
15
|
export declare function acceptsJsonAndEventStream(header: string | null | undefined): boolean;
|
|
16
|
+
export {};
|
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.d.ts
CHANGED
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.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
interface HttpServerTuningTarget {
|
|
2
2
|
headersTimeout?: number;
|
|
3
3
|
requestTimeout?: number;
|
|
4
4
|
keepAliveTimeout?: number;
|
|
@@ -11,3 +11,4 @@ export interface HttpServerTuningTarget {
|
|
|
11
11
|
}
|
|
12
12
|
export declare function applyHttpServerTuning(server: HttpServerTuningTarget): void;
|
|
13
13
|
export declare function drainConnectionsOnShutdown(server: HttpServerTuningTarget): void;
|
|
14
|
+
export {};
|
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.d.ts
CHANGED
|
@@ -21,12 +21,12 @@ export interface SessionStore {
|
|
|
21
21
|
evictExpired: () => SessionEntry[];
|
|
22
22
|
evictOldest: () => SessionEntry | undefined;
|
|
23
23
|
}
|
|
24
|
-
|
|
24
|
+
interface SlotTracker {
|
|
25
25
|
readonly releaseSlot: () => void;
|
|
26
26
|
readonly markInitialized: () => void;
|
|
27
27
|
readonly isInitialized: () => boolean;
|
|
28
28
|
}
|
|
29
|
-
|
|
29
|
+
type CloseHandler = (() => void) | undefined;
|
|
30
30
|
export declare function composeCloseHandlers(first: CloseHandler, second: CloseHandler): CloseHandler;
|
|
31
31
|
export declare function startSessionCleanupLoop(store: SessionStore, sessionTtlMs: number): AbortController;
|
|
32
32
|
export declare function createSessionStore(sessionTtlMs: number): SessionStore;
|
|
@@ -37,3 +37,4 @@ export declare function ensureSessionCapacity({ store, maxSessions, evictOldest,
|
|
|
37
37
|
maxSessions: number;
|
|
38
38
|
evictOldest: (store: SessionStore) => boolean;
|
|
39
39
|
}): boolean;
|
|
40
|
+
export {};
|
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.d.ts
CHANGED
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;
|