@j0hanz/fetch-url-mcp 0.0.1

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 (65) hide show
  1. package/README.md +570 -0
  2. package/dist/AGENTS.md +115 -0
  3. package/dist/assets/logo.svg +24837 -0
  4. package/dist/cache.d.ts +47 -0
  5. package/dist/cache.js +316 -0
  6. package/dist/cli.d.ts +17 -0
  7. package/dist/cli.js +48 -0
  8. package/dist/config.d.ts +142 -0
  9. package/dist/config.js +480 -0
  10. package/dist/crypto.d.ts +3 -0
  11. package/dist/crypto.js +49 -0
  12. package/dist/dom-noise-removal.d.ts +1 -0
  13. package/dist/dom-noise-removal.js +488 -0
  14. package/dist/errors.d.ts +10 -0
  15. package/dist/errors.js +61 -0
  16. package/dist/fetch.d.ts +42 -0
  17. package/dist/fetch.js +1544 -0
  18. package/dist/host-normalization.d.ts +1 -0
  19. package/dist/host-normalization.js +77 -0
  20. package/dist/http-native.d.ts +5 -0
  21. package/dist/http-native.js +1313 -0
  22. package/dist/index.d.ts +2 -0
  23. package/dist/index.js +91 -0
  24. package/dist/instructions.md +57 -0
  25. package/dist/ip-blocklist.d.ts +8 -0
  26. package/dist/ip-blocklist.js +74 -0
  27. package/dist/json.d.ts +1 -0
  28. package/dist/json.js +34 -0
  29. package/dist/language-detection.d.ts +2 -0
  30. package/dist/language-detection.js +364 -0
  31. package/dist/markdown-cleanup.d.ts +6 -0
  32. package/dist/markdown-cleanup.js +474 -0
  33. package/dist/mcp-validator.d.ts +15 -0
  34. package/dist/mcp-validator.js +44 -0
  35. package/dist/mcp.d.ts +4 -0
  36. package/dist/mcp.js +421 -0
  37. package/dist/observability.d.ts +21 -0
  38. package/dist/observability.js +211 -0
  39. package/dist/prompts.d.ts +7 -0
  40. package/dist/prompts.js +28 -0
  41. package/dist/resources.d.ts +8 -0
  42. package/dist/resources.js +216 -0
  43. package/dist/server-tuning.d.ts +13 -0
  44. package/dist/server-tuning.js +47 -0
  45. package/dist/server.d.ts +4 -0
  46. package/dist/server.js +174 -0
  47. package/dist/session.d.ts +39 -0
  48. package/dist/session.js +218 -0
  49. package/dist/tasks.d.ts +63 -0
  50. package/dist/tasks.js +327 -0
  51. package/dist/timer-utils.d.ts +5 -0
  52. package/dist/timer-utils.js +20 -0
  53. package/dist/tools.d.ts +135 -0
  54. package/dist/tools.js +812 -0
  55. package/dist/transform-types.d.ts +126 -0
  56. package/dist/transform-types.js +5 -0
  57. package/dist/transform.d.ts +36 -0
  58. package/dist/transform.js +2341 -0
  59. package/dist/type-guards.d.ts +14 -0
  60. package/dist/type-guards.js +13 -0
  61. package/dist/workers/transform-child.d.ts +1 -0
  62. package/dist/workers/transform-child.js +136 -0
  63. package/dist/workers/transform-worker.d.ts +1 -0
  64. package/dist/workers/transform-worker.js +128 -0
  65. package/package.json +91 -0
@@ -0,0 +1,474 @@
1
+ import { config } from './config.js';
2
+ // --- Constants & Regex ---
3
+ const MAX_LINE_LENGTH = 80;
4
+ const REGEX = {
5
+ HEADING_MARKER: /^#{1,6}\s/m,
6
+ HEADING_STRICT: /^#{1,6}\s+/m,
7
+ EMPTY_HEADING_LINE: /^#{1,6}[ \t\u00A0]*$/,
8
+ FENCE_START: /^\s*(`{3,}|~{3,})/,
9
+ LIST_MARKER: /^(?:[-*+])\s/m,
10
+ TOC_LINK: /^- \[[^\]]+\]\(#[^)]+\)\s*$/,
11
+ TOC_HEADING: /^(?:#{1,6}\s+)?(?:table of contents|contents)\s*$/i,
12
+ HTML_DOC_START: /^(<!doctype|<html)/i,
13
+ COMBINED_LINE_REMOVALS: /^(?:\[Skip to (?:main )?(?:content|navigation)\]\(#[^)]*\)|\[Skip link\]\(#[^)]*\)|Was this page helpful\??)\s*$/gim,
14
+ ZERO_WIDTH_ANCHOR: /\[(?:\s|\u200B)*\]\(#[^)]*\)[ \t]*/g,
15
+ CONCATENATED_PROPS: /([a-z_][a-z0-9_]{0,30}\??:\s+)([\u0022\u201C][^\u0022\u201C\u201D]*[\u0022\u201D])([a-z_][a-z0-9_]{0,30}\??:)/g,
16
+ DOUBLE_NEWLINE_REDUCER: /\n{3,}/g,
17
+ SOURCE_KEY: /^source:\s/im,
18
+ HEADING_SPACING: /(^#{1,6}\s[^\n]*)\n([^\n])/gm,
19
+ HEADING_CODE_BLOCK: /(^#{1,6}\s+\w+)```/gm,
20
+ HEADING_CAMEL_CASE: /(^#{1,6}\s+\w*[A-Z])([A-Z][a-z])/gm,
21
+ SPACING_LINK_FIX: /\]\(([^)]+)\)\[/g,
22
+ SPACING_ADJ_COMBINED: /(?:\]\([^)]+\)|`[^`]+`)(?=[A-Za-z0-9])/g,
23
+ SPACING_CODE_DASH: /(`[^`]+`)\s*\\-\s*/g,
24
+ SPACING_ESCAPES: /\\([[\].])/g,
25
+ SPACING_LIST_NUM_COMBINED: /^((?![-*+] |\d+\. |[ \t]).+)\n((?:[-*+]|\d+\.) )/gm,
26
+ NESTED_LIST_INDENT: /^( +)((?:[-*+])|\d+\.)\s/gm,
27
+ TYPEDOC: /(`+)(?:(?!\1)[\s\S])*?\1|\s?\/\\?\*[\s\S]*?\\?\*\//g,
28
+ };
29
+ const HEADING_KEYWORDS = new Set(config.markdownCleanup.headingKeywords.map((value) => value.toLocaleLowerCase(config.i18n.locale)));
30
+ const SPECIAL_PREFIXES = /^(?:example|note|tip|warning|important|caution):\s+\S/i;
31
+ // --- Helper Functions ---
32
+ function getLineEnding(content) {
33
+ return content.includes('\r\n') ? '\r\n' : '\n';
34
+ }
35
+ function hasFollowingContent(lines, startIndex) {
36
+ // Optimization: Bound lookahead to avoid checking too many lines in huge files
37
+ const max = Math.min(lines.length, startIndex + 50);
38
+ for (let i = startIndex + 1; i < max; i++) {
39
+ const line = lines[i];
40
+ if (line && line.trim().length > 0)
41
+ return true;
42
+ }
43
+ return false;
44
+ }
45
+ // Optimized Heuristics
46
+ function isTitleCaseOrKeyword(trimmed) {
47
+ // Quick check for length to avoid regex on long strings
48
+ if (trimmed.length > MAX_LINE_LENGTH)
49
+ return false;
50
+ // Single word optimization
51
+ if (!trimmed.includes(' ')) {
52
+ if (!/^[A-Z]/.test(trimmed))
53
+ return false;
54
+ return HEADING_KEYWORDS.has(trimmed.toLocaleLowerCase(config.i18n.locale));
55
+ }
56
+ // Split limited number of words
57
+ const words = trimmed.split(/\s+/);
58
+ const len = words.length;
59
+ if (len < 2 || len > 6)
60
+ return false;
61
+ let capitalizedCount = 0;
62
+ for (let i = 0; i < len; i++) {
63
+ const w = words[i];
64
+ if (!w)
65
+ continue;
66
+ const isCap = /^[A-Z][a-z]*$/.test(w);
67
+ if (isCap)
68
+ capitalizedCount++;
69
+ else if (!/^(?:and|or|the|of|in|for|to|a)$/i.test(w))
70
+ return false;
71
+ }
72
+ return capitalizedCount >= 2;
73
+ }
74
+ function getHeadingPrefix(trimmed) {
75
+ if (trimmed.length > MAX_LINE_LENGTH)
76
+ return null;
77
+ // Fast path: Check common markdown markers first
78
+ const firstChar = trimmed.charCodeAt(0);
79
+ // # (35), - (45), * (42), + (43), digit (48-57), [ (91)
80
+ if (firstChar === 35 ||
81
+ firstChar === 45 ||
82
+ firstChar === 42 ||
83
+ firstChar === 43 ||
84
+ firstChar === 91 ||
85
+ (firstChar >= 48 && firstChar <= 57)) {
86
+ if (REGEX.HEADING_MARKER.test(trimmed) ||
87
+ REGEX.LIST_MARKER.test(trimmed) ||
88
+ /^\d+\.\s/.test(trimmed) ||
89
+ /^\[.*\]\(.*\)$/.test(trimmed)) {
90
+ return null;
91
+ }
92
+ }
93
+ if (SPECIAL_PREFIXES.test(trimmed)) {
94
+ return /^example:\s/i.test(trimmed) ? '### ' : '## ';
95
+ }
96
+ const lastChar = trimmed.charCodeAt(trimmed.length - 1);
97
+ // . (46), ! (33), ? (63)
98
+ if (lastChar === 46 || lastChar === 33 || lastChar === 63)
99
+ return null;
100
+ return isTitleCaseOrKeyword(trimmed) ? '## ' : null;
101
+ }
102
+ // Optimized TOC detection
103
+ function hasTocBlock(lines, headingIndex) {
104
+ const lookaheadMax = Math.min(lines.length, headingIndex + 8);
105
+ for (let i = headingIndex + 1; i < lookaheadMax; i++) {
106
+ const line = lines[i];
107
+ if (!line || line.trim().length === 0)
108
+ continue;
109
+ if (REGEX.TOC_LINK.test(line))
110
+ return true;
111
+ }
112
+ return false;
113
+ }
114
+ function skipTocLines(lines, startIndex) {
115
+ for (let i = startIndex; i < lines.length; i++) {
116
+ const line = lines[i];
117
+ if (!line)
118
+ continue;
119
+ if (line.trim().length === 0)
120
+ continue;
121
+ if (!REGEX.TOC_LINK.test(line))
122
+ return i;
123
+ }
124
+ return lines.length;
125
+ }
126
+ // --- Main Processing Logic ---
127
+ function tryPromoteOrphan(lines, i, trimmed) {
128
+ const prevLine = lines[i - 1];
129
+ const isOrphan = i === 0 || !prevLine || prevLine.trim().length === 0;
130
+ if (!isOrphan)
131
+ return null;
132
+ const prefix = getHeadingPrefix(trimmed);
133
+ if (!prefix)
134
+ return null;
135
+ const isSpecialPrefix = SPECIAL_PREFIXES.test(trimmed);
136
+ if (!isSpecialPrefix && !hasFollowingContent(lines, i))
137
+ return null;
138
+ return `${prefix}${trimmed}`;
139
+ }
140
+ function shouldSkipAsToc(lines, i, trimmed, removeToc) {
141
+ if (removeToc && REGEX.TOC_HEADING.test(trimmed) && hasTocBlock(lines, i)) {
142
+ return skipTocLines(lines, i + 1);
143
+ }
144
+ return null;
145
+ }
146
+ function preprocessLines(lines) {
147
+ const processedLines = [];
148
+ const len = lines.length;
149
+ const promote = config.markdownCleanup.promoteOrphanHeadings;
150
+ const removeToc = config.markdownCleanup.removeTocBlocks;
151
+ let skipUntil = -1;
152
+ for (let i = 0; i < len; i++) {
153
+ if (i < skipUntil)
154
+ continue;
155
+ let line = lines[i];
156
+ if (line === undefined)
157
+ continue;
158
+ const trimmed = line.trim();
159
+ if (REGEX.EMPTY_HEADING_LINE.test(trimmed))
160
+ continue;
161
+ const tocSkip = shouldSkipAsToc(lines, i, trimmed, removeToc);
162
+ if (tocSkip !== null) {
163
+ skipUntil = tocSkip;
164
+ continue;
165
+ }
166
+ if (promote && trimmed.length > 0) {
167
+ const promoted = tryPromoteOrphan(lines, i, trimmed);
168
+ if (promoted)
169
+ line = promoted;
170
+ }
171
+ processedLines.push(line);
172
+ }
173
+ return processedLines.join('\n');
174
+ }
175
+ // Process a block of non-fence lines
176
+ function processTextBuffer(lines) {
177
+ if (lines.length === 0)
178
+ return '';
179
+ const text = preprocessLines(lines);
180
+ return applyGlobalRegexes(text);
181
+ }
182
+ function applyGlobalRegexes(text) {
183
+ let result = text;
184
+ // fixAndSpaceHeadings
185
+ result = result
186
+ .replace(REGEX.HEADING_SPACING, '$1\n\n$2')
187
+ .replace(REGEX.HEADING_CODE_BLOCK, '$1\n\n```')
188
+ .replace(REGEX.HEADING_CAMEL_CASE, '$1\n\n$2');
189
+ // removeTypeDocComments
190
+ if (config.markdownCleanup.removeTypeDocComments) {
191
+ result = result.replace(REGEX.TYPEDOC, (match) => match.startsWith('`') ? match : '');
192
+ }
193
+ if (config.markdownCleanup.removeSkipLinks) {
194
+ result = result
195
+ .replace(REGEX.ZERO_WIDTH_ANCHOR, '')
196
+ .replace(REGEX.COMBINED_LINE_REMOVALS, '');
197
+ }
198
+ // normalizeSpacing
199
+ result = result
200
+ .replace(REGEX.SPACING_LINK_FIX, ']($1)\n\n[')
201
+ .replace(REGEX.SPACING_ADJ_COMBINED, '$& ')
202
+ .replace(REGEX.SPACING_CODE_DASH, '$1 - ')
203
+ .replace(REGEX.SPACING_ESCAPES, '$1')
204
+ .replace(REGEX.SPACING_LIST_NUM_COMBINED, '$1\n\n$2')
205
+ .replace(REGEX.DOUBLE_NEWLINE_REDUCER, '\n\n');
206
+ result = normalizeNestedListIndentation(result);
207
+ // fixProperties
208
+ for (let k = 0; k < 3; k++) {
209
+ const next = result.replace(REGEX.CONCATENATED_PROPS, '$1$2\n\n$3');
210
+ if (next === result)
211
+ break;
212
+ result = next;
213
+ }
214
+ return result;
215
+ }
216
+ function normalizeNestedListIndentation(text) {
217
+ return text.replace(REGEX.NESTED_LIST_INDENT, (match, spaces, marker) => {
218
+ const count = spaces.length;
219
+ if (count < 2 || count % 2 !== 0)
220
+ return match;
221
+ const normalized = ' '.repeat((count / 2) * 4);
222
+ return `${normalized}${marker} `;
223
+ });
224
+ }
225
+ function findNextLine(content, lastIndex, len) {
226
+ let nextIndex = content.indexOf('\n', lastIndex);
227
+ let line;
228
+ if (nextIndex === -1) {
229
+ line = content.slice(lastIndex);
230
+ nextIndex = len;
231
+ }
232
+ else {
233
+ if (nextIndex > lastIndex && content.charCodeAt(nextIndex - 1) === 13) {
234
+ line = content.slice(lastIndex, nextIndex - 1);
235
+ }
236
+ else {
237
+ line = content.slice(lastIndex, nextIndex);
238
+ }
239
+ nextIndex++; // Skip \n
240
+ }
241
+ return { line, nextIndex };
242
+ }
243
+ function checkFenceStart(line) {
244
+ const match = REGEX.FENCE_START.exec(line);
245
+ return match ? (match[1] ?? '```') : null;
246
+ }
247
+ function isFenceClosure(trimmed, marker) {
248
+ return (trimmed.startsWith(marker) && trimmed.slice(marker.length).trim() === '');
249
+ }
250
+ function handleFencedLine(line, trimmed, fenceMarker, segments) {
251
+ segments.push(line);
252
+ return isFenceClosure(trimmed, fenceMarker) ? null : fenceMarker;
253
+ }
254
+ function handleUnfencedLine(line, segments, buffer) {
255
+ const newMarker = checkFenceStart(line);
256
+ if (!newMarker) {
257
+ buffer.push(line);
258
+ return { fenceMarker: null, buffer };
259
+ }
260
+ if (buffer.length > 0) {
261
+ segments.push(processTextBuffer(buffer));
262
+ buffer = [];
263
+ }
264
+ segments.push(line);
265
+ return { fenceMarker: newMarker, buffer };
266
+ }
267
+ export function cleanupMarkdownArtifacts(content) {
268
+ if (!content)
269
+ return '';
270
+ const len = content.length;
271
+ let lastIndex = 0;
272
+ let fenceMarker = null;
273
+ const segments = [];
274
+ let buffer = [];
275
+ while (lastIndex < len) {
276
+ const { line, nextIndex } = findNextLine(content, lastIndex, len);
277
+ const trimmed = line.trimStart();
278
+ if (fenceMarker) {
279
+ fenceMarker = handleFencedLine(line, trimmed, fenceMarker, segments);
280
+ }
281
+ else {
282
+ ({ fenceMarker, buffer } = handleUnfencedLine(line, segments, buffer));
283
+ }
284
+ lastIndex = nextIndex;
285
+ }
286
+ if (buffer.length > 0) {
287
+ segments.push(processTextBuffer(buffer));
288
+ }
289
+ return segments.join('\n').trim();
290
+ }
291
+ function detectFrontmatter(content) {
292
+ const len = content.length;
293
+ if (len < 4)
294
+ return null;
295
+ let lineEnding = null;
296
+ let fenceLen = 0;
297
+ if (content.startsWith('---\n')) {
298
+ lineEnding = '\n';
299
+ fenceLen = 4;
300
+ }
301
+ else if (content.startsWith('---\r\n')) {
302
+ lineEnding = '\r\n';
303
+ fenceLen = 5;
304
+ }
305
+ if (!lineEnding)
306
+ return null;
307
+ const fence = `---${lineEnding}`;
308
+ const closeIndex = content.indexOf(fence, fenceLen);
309
+ if (closeIndex === -1)
310
+ return null;
311
+ return {
312
+ start: 0,
313
+ end: closeIndex + fenceLen,
314
+ linesStart: fenceLen,
315
+ linesEnd: closeIndex,
316
+ lineEnding,
317
+ };
318
+ }
319
+ function parseFrontmatterEntry(line) {
320
+ const trimmed = line.trim();
321
+ const idx = trimmed.indexOf(':');
322
+ if (!trimmed || idx <= 0)
323
+ return null;
324
+ return {
325
+ key: trimmed.slice(0, idx).trim().toLowerCase(),
326
+ value: trimmed.slice(idx + 1).trim(),
327
+ };
328
+ }
329
+ function stripFrontmatterQuotes(val) {
330
+ const first = val.charAt(0);
331
+ const last = val.charAt(val.length - 1);
332
+ if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
333
+ return val.slice(1, -1).trim();
334
+ }
335
+ return val;
336
+ }
337
+ function scanFrontmatterForTitle(content, fm) {
338
+ const fmBody = content.slice(fm.linesStart, fm.linesEnd);
339
+ let lastIdx = 0;
340
+ while (lastIdx < fmBody.length) {
341
+ let nextIdx = fmBody.indexOf(fm.lineEnding, lastIdx);
342
+ if (nextIdx === -1)
343
+ nextIdx = fmBody.length;
344
+ const line = fmBody.slice(lastIdx, nextIdx);
345
+ const entry = parseFrontmatterEntry(line);
346
+ if (entry) {
347
+ if (entry.key === 'title' || entry.key === 'name') {
348
+ const cleaned = stripFrontmatterQuotes(entry.value);
349
+ if (cleaned)
350
+ return cleaned;
351
+ }
352
+ }
353
+ lastIdx = nextIdx + fm.lineEnding.length;
354
+ }
355
+ return undefined;
356
+ }
357
+ function scanBodyForTitle(content) {
358
+ const len = content.length;
359
+ let scanIndex = 0;
360
+ const LIMIT = 5000;
361
+ const maxScan = Math.min(len, LIMIT);
362
+ while (scanIndex < maxScan) {
363
+ let nextIndex = content.indexOf('\n', scanIndex);
364
+ if (nextIndex === -1)
365
+ nextIndex = len;
366
+ let line = content.slice(scanIndex, nextIndex);
367
+ if (line.endsWith('\r'))
368
+ line = line.slice(0, -1);
369
+ const trimmed = line.trim();
370
+ if (trimmed) {
371
+ if (REGEX.HEADING_STRICT.test(trimmed)) {
372
+ return trimmed.replace(REGEX.HEADING_MARKER, '').trim() || undefined;
373
+ }
374
+ return undefined;
375
+ }
376
+ scanIndex = nextIndex + 1;
377
+ }
378
+ return undefined;
379
+ }
380
+ export function extractTitleFromRawMarkdown(content) {
381
+ const fm = detectFrontmatter(content);
382
+ if (fm) {
383
+ const title = scanFrontmatterForTitle(content, fm);
384
+ if (title)
385
+ return title;
386
+ }
387
+ return scanBodyForTitle(content);
388
+ }
389
+ export function addSourceToMarkdown(content, url) {
390
+ const fm = detectFrontmatter(content);
391
+ const useMarkdownFormat = config.transform.metadataFormat === 'markdown';
392
+ if (useMarkdownFormat && !fm) {
393
+ if (REGEX.SOURCE_KEY.test(content))
394
+ return content;
395
+ const lineEnding = getLineEnding(content);
396
+ const firstH1Match = REGEX.HEADING_MARKER.exec(content);
397
+ if (firstH1Match) {
398
+ const h1Index = firstH1Match.index;
399
+ const lineEndIndex = content.indexOf(lineEnding, h1Index);
400
+ const insertPos = lineEndIndex === -1 ? content.length : lineEndIndex + lineEnding.length;
401
+ const injection = `${lineEnding}Source: ${url}${lineEnding}`;
402
+ return content.slice(0, insertPos) + injection + content.slice(insertPos);
403
+ }
404
+ return `Source: ${url}${lineEnding}${lineEnding}${content}`;
405
+ }
406
+ if (!fm) {
407
+ const lineEnding = getLineEnding(content);
408
+ const escapedUrl = url.replace(/"/g, '\\"');
409
+ return `---${lineEnding}source: "${escapedUrl}"${lineEnding}---${lineEnding}${lineEnding}${content}`;
410
+ }
411
+ const fmBody = content.slice(fm.linesStart, fm.linesEnd);
412
+ if (REGEX.SOURCE_KEY.test(fmBody))
413
+ return content;
414
+ const escapedUrl = url.replace(/"/g, '\\"');
415
+ const injection = `source: "${escapedUrl}"${fm.lineEnding}`;
416
+ return content.slice(0, fm.linesEnd) + injection + content.slice(fm.linesEnd);
417
+ }
418
+ function countCommonTags(content, limit) {
419
+ if (limit <= 0)
420
+ return 0;
421
+ const regex = /<(html|head|body|div|span|script|style|meta|link)\b/gi;
422
+ let count = 0;
423
+ while (regex.exec(content)) {
424
+ count += 1;
425
+ if (count > limit)
426
+ break;
427
+ }
428
+ return count;
429
+ }
430
+ export function isRawTextContent(content) {
431
+ const trimmed = content.trim();
432
+ if (REGEX.HTML_DOC_START.test(trimmed))
433
+ return false;
434
+ if (detectFrontmatter(trimmed) !== null)
435
+ return true;
436
+ const tagCount = countCommonTags(content, 5);
437
+ if (tagCount > 5)
438
+ return false;
439
+ return (REGEX.HEADING_MARKER.test(content) ||
440
+ REGEX.LIST_MARKER.test(content) ||
441
+ content.includes('```'));
442
+ }
443
+ function formatFetchedAt(value) {
444
+ const date = new Date(value);
445
+ if (Number.isNaN(date.getTime()))
446
+ return value;
447
+ const formatter = new Intl.DateTimeFormat(config.i18n.locale, {
448
+ day: '2-digit',
449
+ month: '2-digit',
450
+ year: 'numeric',
451
+ });
452
+ return formatter.format(date);
453
+ }
454
+ export function buildMetadataFooter(metadata, fallbackUrl) {
455
+ if (!metadata)
456
+ return '';
457
+ const lines = ['---', ''];
458
+ const url = metadata.url || fallbackUrl;
459
+ const parts = [];
460
+ if (metadata.title)
461
+ parts.push(`_${metadata.title}_`);
462
+ if (metadata.author)
463
+ parts.push(`_${metadata.author}_`);
464
+ if (url)
465
+ parts.push(`[_Original Source_](${url})`);
466
+ if (metadata.fetchedAt) {
467
+ parts.push(`_${formatFetchedAt(metadata.fetchedAt)}_`);
468
+ }
469
+ if (parts.length > 0)
470
+ lines.push(` ${parts.join(' | ')}`);
471
+ if (metadata.description)
472
+ lines.push(` <sub>${metadata.description}</sub>`);
473
+ return lines.join('\n');
474
+ }
@@ -0,0 +1,15 @@
1
+ export type JsonRpcId = string | number | null;
2
+ export interface McpRequestParams {
3
+ _meta?: Record<string, unknown>;
4
+ [key: string]: unknown;
5
+ }
6
+ export interface McpRequestBody {
7
+ jsonrpc: '2.0';
8
+ method: string;
9
+ id?: JsonRpcId;
10
+ params?: McpRequestParams;
11
+ }
12
+ export declare function isJsonRpcBatchRequest(body: unknown): boolean;
13
+ export declare function isMcpRequestBody(body: unknown): body is McpRequestBody;
14
+ export declare function acceptsEventStream(header: string | null | undefined): boolean;
15
+ export declare function acceptsJsonAndEventStream(header: string | null | undefined): boolean;
@@ -0,0 +1,44 @@
1
+ import { z } from 'zod';
2
+ // --- Validation ---
3
+ const paramsSchema = z.looseObject({});
4
+ const mcpRequestSchema = z.strictObject({
5
+ jsonrpc: z.literal('2.0'),
6
+ method: z.string().min(1),
7
+ id: z.union([z.string(), z.number()]).optional(),
8
+ params: paramsSchema.optional(),
9
+ });
10
+ export function isJsonRpcBatchRequest(body) {
11
+ return Array.isArray(body);
12
+ }
13
+ export function isMcpRequestBody(body) {
14
+ return mcpRequestSchema.safeParse(body).success;
15
+ }
16
+ export function acceptsEventStream(header) {
17
+ if (!header)
18
+ return false;
19
+ return header
20
+ .split(',')
21
+ .some((value) => value.trim().toLowerCase().startsWith('text/event-stream'));
22
+ }
23
+ function hasAcceptedMediaType(header, exact, wildcardPrefix) {
24
+ if (!header)
25
+ return false;
26
+ return header.split(',').some((rawPart) => {
27
+ const mediaType = rawPart.trim().split(';', 1)[0]?.trim().toLowerCase();
28
+ if (!mediaType)
29
+ return false;
30
+ if (mediaType === '*/*')
31
+ return true;
32
+ if (mediaType === exact)
33
+ return true;
34
+ if (mediaType === wildcardPrefix)
35
+ return true;
36
+ return false;
37
+ });
38
+ }
39
+ export function acceptsJsonAndEventStream(header) {
40
+ const acceptsJson = hasAcceptedMediaType(header, 'application/json', 'application/*');
41
+ if (!acceptsJson)
42
+ return false;
43
+ return hasAcceptedMediaType(header, 'text/event-stream', 'text/*');
44
+ }
package/dist/mcp.d.ts ADDED
@@ -0,0 +1,4 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ export declare function cancelTasksForOwner(ownerKey: string, statusMessage?: string): number;
3
+ export declare function abortAllTaskExecutions(): void;
4
+ export declare function registerTaskHandlers(server: McpServer): void;