@j0hanz/fetch-url-mcp 1.4.0 → 1.5.0

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 (215) hide show
  1. package/dist/cli.d.ts +2 -3
  2. package/dist/cli.js +1 -2
  3. package/dist/http/auth.d.ts +5 -3
  4. package/dist/http/auth.js +64 -15
  5. package/dist/http/health.d.ts +1 -2
  6. package/dist/http/health.js +7 -18
  7. package/dist/http/helpers.d.ts +3 -4
  8. package/dist/http/helpers.js +21 -21
  9. package/dist/http/native.d.ts +0 -1
  10. package/dist/http/native.js +34 -26
  11. package/dist/http/rate-limit.d.ts +0 -1
  12. package/dist/http/rate-limit.js +3 -4
  13. package/dist/index.d.ts +0 -1
  14. package/dist/index.js +17 -18
  15. package/dist/lib/{markdown-cleanup.d.ts → content.d.ts} +4 -2
  16. package/dist/lib/content.js +1356 -0
  17. package/dist/lib/core.d.ts +253 -0
  18. package/dist/lib/core.js +1228 -0
  19. package/dist/lib/{tool-pipeline.d.ts → fetch-pipeline.d.ts} +1 -2
  20. package/dist/lib/{tool-pipeline.js → fetch-pipeline.js} +10 -19
  21. package/dist/lib/{fetch.d.ts → http.d.ts} +7 -9
  22. package/dist/lib/{fetch.js → http.js} +706 -944
  23. package/dist/lib/mcp-tools.d.ts +28 -0
  24. package/dist/lib/mcp-tools.js +107 -0
  25. package/dist/lib/{tool-progress.d.ts → progress.d.ts} +0 -1
  26. package/dist/lib/{tool-progress.js → progress.js} +8 -13
  27. package/dist/lib/task-handlers.d.ts +5 -0
  28. package/dist/lib/{mcp.js → task-handlers.js} +56 -12
  29. package/dist/lib/url.d.ts +70 -0
  30. package/dist/lib/url.js +686 -0
  31. package/dist/lib/utils.d.ts +58 -0
  32. package/dist/lib/utils.js +304 -0
  33. package/dist/prompts/index.d.ts +0 -1
  34. package/dist/prompts/index.js +0 -1
  35. package/dist/resources/index.d.ts +0 -1
  36. package/dist/resources/index.js +74 -33
  37. package/dist/resources/instructions.d.ts +0 -1
  38. package/dist/resources/instructions.js +2 -2
  39. package/dist/schemas/inputs.d.ts +0 -1
  40. package/dist/schemas/inputs.js +2 -3
  41. package/dist/schemas/outputs.d.ts +0 -1
  42. package/dist/schemas/outputs.js +1 -2
  43. package/dist/server.d.ts +0 -1
  44. package/dist/server.js +16 -26
  45. package/dist/tasks/execution.d.ts +0 -1
  46. package/dist/tasks/execution.js +27 -24
  47. package/dist/tasks/manager.d.ts +7 -3
  48. package/dist/tasks/manager.js +53 -34
  49. package/dist/tasks/owner.d.ts +1 -2
  50. package/dist/tasks/owner.js +1 -2
  51. package/dist/tasks/tool-registry.d.ts +1 -2
  52. package/dist/tasks/tool-registry.js +0 -1
  53. package/dist/tools/fetch-url.d.ts +1 -2
  54. package/dist/tools/fetch-url.js +39 -31
  55. package/dist/tools/index.d.ts +0 -1
  56. package/dist/tools/index.js +0 -1
  57. package/dist/transform/html-translators.d.ts +1 -0
  58. package/dist/transform/html-translators.js +454 -0
  59. package/dist/transform/metadata.d.ts +4 -0
  60. package/dist/transform/metadata.js +183 -0
  61. package/dist/transform/transform.d.ts +0 -1
  62. package/dist/transform/transform.js +24 -641
  63. package/dist/transform/types.d.ts +9 -11
  64. package/dist/transform/types.js +0 -1
  65. package/dist/transform/worker-pool.d.ts +0 -1
  66. package/dist/transform/worker-pool.js +7 -16
  67. package/dist/transform/workers/shared.d.ts +0 -1
  68. package/dist/transform/workers/shared.js +1 -2
  69. package/dist/transform/workers/transform-child.d.ts +0 -1
  70. package/dist/transform/workers/transform-child.js +0 -1
  71. package/dist/transform/workers/transform-worker.d.ts +0 -1
  72. package/dist/transform/workers/transform-worker.js +0 -1
  73. package/package.json +6 -3
  74. package/dist/cli.d.ts.map +0 -1
  75. package/dist/cli.js.map +0 -1
  76. package/dist/http/auth.d.ts.map +0 -1
  77. package/dist/http/auth.js.map +0 -1
  78. package/dist/http/health.d.ts.map +0 -1
  79. package/dist/http/health.js.map +0 -1
  80. package/dist/http/helpers.d.ts.map +0 -1
  81. package/dist/http/helpers.js.map +0 -1
  82. package/dist/http/native.d.ts.map +0 -1
  83. package/dist/http/native.js.map +0 -1
  84. package/dist/http/rate-limit.d.ts.map +0 -1
  85. package/dist/http/rate-limit.js.map +0 -1
  86. package/dist/index.d.ts.map +0 -1
  87. package/dist/index.js.map +0 -1
  88. package/dist/lib/cache.d.ts +0 -54
  89. package/dist/lib/cache.d.ts.map +0 -1
  90. package/dist/lib/cache.js +0 -264
  91. package/dist/lib/cache.js.map +0 -1
  92. package/dist/lib/config.d.ts +0 -143
  93. package/dist/lib/config.d.ts.map +0 -1
  94. package/dist/lib/config.js +0 -476
  95. package/dist/lib/config.js.map +0 -1
  96. package/dist/lib/crypto.d.ts +0 -4
  97. package/dist/lib/crypto.d.ts.map +0 -1
  98. package/dist/lib/crypto.js +0 -56
  99. package/dist/lib/crypto.js.map +0 -1
  100. package/dist/lib/dom-noise-removal.d.ts +0 -2
  101. package/dist/lib/dom-noise-removal.d.ts.map +0 -1
  102. package/dist/lib/dom-noise-removal.js +0 -494
  103. package/dist/lib/dom-noise-removal.js.map +0 -1
  104. package/dist/lib/download.d.ts +0 -4
  105. package/dist/lib/download.d.ts.map +0 -1
  106. package/dist/lib/download.js +0 -106
  107. package/dist/lib/download.js.map +0 -1
  108. package/dist/lib/errors.d.ts +0 -14
  109. package/dist/lib/errors.d.ts.map +0 -1
  110. package/dist/lib/errors.js +0 -72
  111. package/dist/lib/errors.js.map +0 -1
  112. package/dist/lib/fetch-content.d.ts +0 -5
  113. package/dist/lib/fetch-content.d.ts.map +0 -1
  114. package/dist/lib/fetch-content.js +0 -164
  115. package/dist/lib/fetch-content.js.map +0 -1
  116. package/dist/lib/fetch-stream.d.ts +0 -5
  117. package/dist/lib/fetch-stream.d.ts.map +0 -1
  118. package/dist/lib/fetch-stream.js +0 -29
  119. package/dist/lib/fetch-stream.js.map +0 -1
  120. package/dist/lib/fetch.d.ts.map +0 -1
  121. package/dist/lib/fetch.js.map +0 -1
  122. package/dist/lib/host-normalization.d.ts +0 -2
  123. package/dist/lib/host-normalization.d.ts.map +0 -1
  124. package/dist/lib/host-normalization.js +0 -91
  125. package/dist/lib/host-normalization.js.map +0 -1
  126. package/dist/lib/ip-blocklist.d.ts +0 -9
  127. package/dist/lib/ip-blocklist.d.ts.map +0 -1
  128. package/dist/lib/ip-blocklist.js +0 -79
  129. package/dist/lib/ip-blocklist.js.map +0 -1
  130. package/dist/lib/json.d.ts +0 -2
  131. package/dist/lib/json.d.ts.map +0 -1
  132. package/dist/lib/json.js +0 -45
  133. package/dist/lib/json.js.map +0 -1
  134. package/dist/lib/language-detection.d.ts +0 -3
  135. package/dist/lib/language-detection.d.ts.map +0 -1
  136. package/dist/lib/language-detection.js +0 -355
  137. package/dist/lib/language-detection.js.map +0 -1
  138. package/dist/lib/markdown-cleanup.d.ts.map +0 -1
  139. package/dist/lib/markdown-cleanup.js +0 -532
  140. package/dist/lib/markdown-cleanup.js.map +0 -1
  141. package/dist/lib/mcp-lifecycle.d.ts +0 -5
  142. package/dist/lib/mcp-lifecycle.d.ts.map +0 -1
  143. package/dist/lib/mcp-lifecycle.js +0 -51
  144. package/dist/lib/mcp-lifecycle.js.map +0 -1
  145. package/dist/lib/mcp-validator.d.ts +0 -17
  146. package/dist/lib/mcp-validator.d.ts.map +0 -1
  147. package/dist/lib/mcp-validator.js +0 -45
  148. package/dist/lib/mcp-validator.js.map +0 -1
  149. package/dist/lib/mcp.d.ts +0 -4
  150. package/dist/lib/mcp.d.ts.map +0 -1
  151. package/dist/lib/mcp.js.map +0 -1
  152. package/dist/lib/observability.d.ts +0 -23
  153. package/dist/lib/observability.d.ts.map +0 -1
  154. package/dist/lib/observability.js +0 -238
  155. package/dist/lib/observability.js.map +0 -1
  156. package/dist/lib/server-tuning.d.ts +0 -15
  157. package/dist/lib/server-tuning.d.ts.map +0 -1
  158. package/dist/lib/server-tuning.js +0 -49
  159. package/dist/lib/server-tuning.js.map +0 -1
  160. package/dist/lib/session.d.ts +0 -45
  161. package/dist/lib/session.d.ts.map +0 -1
  162. package/dist/lib/session.js +0 -263
  163. package/dist/lib/session.js.map +0 -1
  164. package/dist/lib/timer-utils.d.ts +0 -13
  165. package/dist/lib/timer-utils.d.ts.map +0 -1
  166. package/dist/lib/timer-utils.js +0 -44
  167. package/dist/lib/timer-utils.js.map +0 -1
  168. package/dist/lib/tool-errors.d.ts +0 -12
  169. package/dist/lib/tool-errors.d.ts.map +0 -1
  170. package/dist/lib/tool-errors.js +0 -55
  171. package/dist/lib/tool-errors.js.map +0 -1
  172. package/dist/lib/tool-pipeline.d.ts.map +0 -1
  173. package/dist/lib/tool-pipeline.js.map +0 -1
  174. package/dist/lib/tool-progress.d.ts.map +0 -1
  175. package/dist/lib/tool-progress.js.map +0 -1
  176. package/dist/lib/type-guards.d.ts +0 -16
  177. package/dist/lib/type-guards.d.ts.map +0 -1
  178. package/dist/lib/type-guards.js +0 -13
  179. package/dist/lib/type-guards.js.map +0 -1
  180. package/dist/prompts/index.d.ts.map +0 -1
  181. package/dist/prompts/index.js.map +0 -1
  182. package/dist/resources/index.d.ts.map +0 -1
  183. package/dist/resources/index.js.map +0 -1
  184. package/dist/resources/instructions.d.ts.map +0 -1
  185. package/dist/resources/instructions.js.map +0 -1
  186. package/dist/schemas/inputs.d.ts.map +0 -1
  187. package/dist/schemas/inputs.js.map +0 -1
  188. package/dist/schemas/outputs.d.ts.map +0 -1
  189. package/dist/schemas/outputs.js.map +0 -1
  190. package/dist/server.d.ts.map +0 -1
  191. package/dist/server.js.map +0 -1
  192. package/dist/tasks/execution.d.ts.map +0 -1
  193. package/dist/tasks/execution.js.map +0 -1
  194. package/dist/tasks/manager.d.ts.map +0 -1
  195. package/dist/tasks/manager.js.map +0 -1
  196. package/dist/tasks/owner.d.ts.map +0 -1
  197. package/dist/tasks/owner.js.map +0 -1
  198. package/dist/tasks/tool-registry.d.ts.map +0 -1
  199. package/dist/tasks/tool-registry.js.map +0 -1
  200. package/dist/tools/fetch-url.d.ts.map +0 -1
  201. package/dist/tools/fetch-url.js.map +0 -1
  202. package/dist/tools/index.d.ts.map +0 -1
  203. package/dist/tools/index.js.map +0 -1
  204. package/dist/transform/transform.d.ts.map +0 -1
  205. package/dist/transform/transform.js.map +0 -1
  206. package/dist/transform/types.d.ts.map +0 -1
  207. package/dist/transform/types.js.map +0 -1
  208. package/dist/transform/worker-pool.d.ts.map +0 -1
  209. package/dist/transform/worker-pool.js.map +0 -1
  210. package/dist/transform/workers/shared.d.ts.map +0 -1
  211. package/dist/transform/workers/shared.js.map +0 -1
  212. package/dist/transform/workers/transform-child.d.ts.map +0 -1
  213. package/dist/transform/workers/transform-child.js.map +0 -1
  214. package/dist/transform/workers/transform-worker.d.ts.map +0 -1
  215. package/dist/transform/workers/transform-worker.js.map +0 -1
@@ -0,0 +1,58 @@
1
+ export declare function throwIfAborted(signal: AbortSignal | undefined, url: string, stage: string): void;
2
+ export declare function createAbortError(url: string, stage: string): FetchError;
3
+ export declare function timingSafeEqualUtf8(a: string, b: string): boolean;
4
+ export declare function sha256Hex(input: string | Uint8Array): string;
5
+ export declare function hmacSha256Hex(key: string | Uint8Array, input: string | Uint8Array): string;
6
+ export declare class FetchError extends Error {
7
+ readonly url: string;
8
+ readonly statusCode: number;
9
+ readonly code: string;
10
+ readonly details: Readonly<Record<string, unknown>>;
11
+ constructor(message: string, url: string, httpStatus?: number, details?: Record<string, unknown>, options?: ErrorOptions);
12
+ }
13
+ export declare function getErrorMessage(error: unknown): string;
14
+ export declare function toError(error: unknown): Error;
15
+ export declare function isAbortError(error: unknown): boolean;
16
+ export declare function createErrorWithCode(message: string, code: string, options?: ErrorOptions): NodeJS.ErrnoException;
17
+ export declare function isSystemError(error: unknown): error is NodeJS.ErrnoException;
18
+ export declare const RESOURCE_NOT_FOUND_ERROR_CODE = -32002;
19
+ export declare function stableStringify(obj: unknown, depth?: number, seen?: WeakSet<WeakKey>): string;
20
+ interface HttpServerTuningTarget {
21
+ headersTimeout?: number;
22
+ requestTimeout?: number;
23
+ keepAliveTimeout?: number;
24
+ keepAliveTimeoutBuffer?: number;
25
+ maxHeadersCount?: number | null;
26
+ maxConnections?: number;
27
+ on?: (event: string, listener: (...args: unknown[]) => void) => void;
28
+ closeIdleConnections?: () => void;
29
+ closeAllConnections?: () => void;
30
+ }
31
+ export declare function applyHttpServerTuning(server: HttpServerTuningTarget): void;
32
+ export declare function drainConnectionsOnShutdown(server: HttpServerTuningTarget): void;
33
+ export interface CancellableTimeout<T> {
34
+ promise: Promise<T>;
35
+ cancel: () => void;
36
+ }
37
+ interface IntervalLoopOptions<T> {
38
+ signal: AbortSignal;
39
+ onTick: (value: T) => void | Promise<void>;
40
+ onError?: (error: unknown) => void;
41
+ }
42
+ export declare function createUnrefTimeout<T>(timeoutMs: number, value: T): CancellableTimeout<T>;
43
+ export declare function startAbortableIntervalLoop<T>(intervalMs: number, value: T, options: IntervalLoopOptions<T>): void;
44
+ export declare function isObject(value: unknown): value is Record<PropertyKey, unknown>;
45
+ export declare function isError(value: unknown): value is Error;
46
+ interface LikeNode {
47
+ readonly tagName?: string | undefined;
48
+ readonly nodeName?: string | undefined;
49
+ readonly nodeType?: number | undefined;
50
+ readonly textContent?: string | null | undefined;
51
+ readonly innerHTML?: string | undefined;
52
+ readonly parentNode?: unknown;
53
+ readonly childNodes?: ArrayLike<unknown>;
54
+ readonly rawTagName?: string | undefined;
55
+ getAttribute?(name: string): string | null;
56
+ }
57
+ export declare function isLikeNode(value: unknown): value is LikeNode;
58
+ export {};
@@ -0,0 +1,304 @@
1
+ import { Buffer } from 'node:buffer';
2
+ import { createHash, createHmac, hash as oneShotHash, timingSafeEqual, } from 'node:crypto';
3
+ import { setInterval as setIntervalPromise, setTimeout as setTimeoutPromise, } from 'node:timers/promises';
4
+ import { inspect } from 'node:util';
5
+ import { config, logDebug, logWarn } from './core.js';
6
+ const UNKNOWN_ERROR_MESSAGE = 'Unknown error';
7
+ function getAbortReason(signal) {
8
+ const record = isObject(signal) ? signal : null;
9
+ return record && 'reason' in record ? record['reason'] : undefined;
10
+ }
11
+ function isTimeoutAbortReason(reason) {
12
+ return reason instanceof Error && reason.name === 'TimeoutError';
13
+ }
14
+ export function throwIfAborted(signal, url, stage) {
15
+ if (!signal?.aborted)
16
+ return;
17
+ const reason = getAbortReason(signal);
18
+ if (isTimeoutAbortReason(reason)) {
19
+ throw new FetchError('Request timeout', url, 504, {
20
+ reason: 'timeout',
21
+ stage,
22
+ });
23
+ }
24
+ throw new FetchError('Request was canceled', url, 499, {
25
+ reason: 'aborted',
26
+ stage,
27
+ });
28
+ }
29
+ export function createAbortError(url, stage) {
30
+ return new FetchError('Request was canceled', url, 499, {
31
+ reason: 'aborted',
32
+ stage,
33
+ });
34
+ }
35
+ const MAX_HASH_INPUT_BYTES = 5 * 1024 * 1024;
36
+ const ALLOWED_HASH_ALGORITHMS = new Set([
37
+ 'sha256',
38
+ 'sha512',
39
+ ]);
40
+ function byteLengthUtf8(input) {
41
+ // Avoid allocating (unlike TextEncoder().encode()).
42
+ return Buffer.byteLength(input, 'utf8');
43
+ }
44
+ function byteLength(input) {
45
+ return typeof input === 'string' ? byteLengthUtf8(input) : input.byteLength;
46
+ }
47
+ function assertAllowedAlgorithm(algorithm) {
48
+ // Defensive: protects against `any` / unchecked external inputs.
49
+ if (!ALLOWED_HASH_ALGORITHMS.has(algorithm)) {
50
+ throw new Error(`Hash algorithm not allowed: ${algorithm}`);
51
+ }
52
+ }
53
+ function padBuffer(buffer, length) {
54
+ const padded = Buffer.alloc(length);
55
+ buffer.copy(padded);
56
+ return padded;
57
+ }
58
+ function equalWithPadding(aBuffer, bBuffer, paddedLength) {
59
+ const paddedA = padBuffer(aBuffer, paddedLength);
60
+ const paddedB = padBuffer(bBuffer, paddedLength);
61
+ return timingSafeEqual(paddedA, paddedB) && aBuffer.length === bBuffer.length;
62
+ }
63
+ export function timingSafeEqualUtf8(a, b) {
64
+ const aBuffer = Buffer.from(a, 'utf8');
65
+ const bBuffer = Buffer.from(b, 'utf8');
66
+ if (aBuffer.length === bBuffer.length) {
67
+ return timingSafeEqual(aBuffer, bBuffer);
68
+ }
69
+ // Avoid early return timing differences on length mismatch.
70
+ const maxLength = Math.max(aBuffer.length, bBuffer.length);
71
+ return equalWithPadding(aBuffer, bBuffer, maxLength);
72
+ }
73
+ function hashHex(algorithm, input) {
74
+ assertAllowedAlgorithm(algorithm);
75
+ if (byteLength(input) <= MAX_HASH_INPUT_BYTES) {
76
+ return oneShotHash(algorithm, input, 'hex');
77
+ }
78
+ const hasher = createHash(algorithm);
79
+ hasher.update(input);
80
+ return hasher.digest('hex');
81
+ }
82
+ export function sha256Hex(input) {
83
+ return hashHex('sha256', input);
84
+ }
85
+ export function hmacSha256Hex(key, input) {
86
+ return createHmac('sha256', key).update(input).digest('hex');
87
+ }
88
+ const DEFAULT_HTTP_STATUS = 502;
89
+ export class FetchError extends Error {
90
+ url;
91
+ statusCode;
92
+ code;
93
+ details;
94
+ constructor(message, url, httpStatus, details = {}, options) {
95
+ super(message, options);
96
+ this.url = url;
97
+ this.name = 'FetchError';
98
+ this.statusCode = httpStatus ?? DEFAULT_HTTP_STATUS;
99
+ this.code = httpStatus ? `HTTP_${httpStatus}` : 'FETCH_ERROR';
100
+ this.details = Object.freeze({ url, httpStatus, ...details });
101
+ Error.captureStackTrace(this, this.constructor);
102
+ }
103
+ }
104
+ export function getErrorMessage(error) {
105
+ if (isError(error))
106
+ return error.message;
107
+ if (isNonEmptyString(error))
108
+ return error;
109
+ if (isErrorWithMessage(error))
110
+ return error.message;
111
+ return formatUnknownError(error);
112
+ }
113
+ export function toError(error) {
114
+ return isError(error) ? error : new Error(getErrorMessage(error));
115
+ }
116
+ export function isAbortError(error) {
117
+ return isError(error) && error.name === 'AbortError';
118
+ }
119
+ function isNonEmptyString(value) {
120
+ return typeof value === 'string' && value.length > 0;
121
+ }
122
+ function isErrorWithMessage(error) {
123
+ if (!isObject(error))
124
+ return false;
125
+ const { message } = error;
126
+ return typeof message === 'string' && message.length > 0;
127
+ }
128
+ function formatUnknownError(error) {
129
+ if (error === null || error === undefined)
130
+ return UNKNOWN_ERROR_MESSAGE;
131
+ try {
132
+ return inspect(error, {
133
+ depth: 2,
134
+ maxStringLength: 200,
135
+ breakLength: Infinity,
136
+ compact: true,
137
+ colors: false,
138
+ });
139
+ }
140
+ catch {
141
+ return UNKNOWN_ERROR_MESSAGE;
142
+ }
143
+ }
144
+ export function createErrorWithCode(message, code, options) {
145
+ const error = new Error(message, options);
146
+ return Object.assign(error, { code });
147
+ }
148
+ export function isSystemError(error) {
149
+ if (!isError(error))
150
+ return false;
151
+ if (!('code' in error))
152
+ return false;
153
+ const { code } = error;
154
+ return typeof code === 'string';
155
+ }
156
+ export const RESOURCE_NOT_FOUND_ERROR_CODE = -32002;
157
+ const MAX_DEPTH = 20;
158
+ const MAX_DEPTH_ERROR = `stableStringify: Max depth (${MAX_DEPTH}) exceeded`;
159
+ const CIRCULAR_ERROR = 'stableStringify: Circular reference detected';
160
+ function compareObjectKeys(a, b) {
161
+ if (a === b)
162
+ return 0;
163
+ return a < b ? -1 : 1;
164
+ }
165
+ function getSortedObjectKeys(obj) {
166
+ return Object.keys(obj).sort(compareObjectKeys);
167
+ }
168
+ function processValue(obj, depth, seen) {
169
+ if (typeof obj !== 'object' || obj === null) {
170
+ return obj;
171
+ }
172
+ // Depth guard
173
+ if (depth > MAX_DEPTH) {
174
+ throw new Error(MAX_DEPTH_ERROR);
175
+ }
176
+ // Cycle detection (track active recursion stack only).
177
+ if (seen.has(obj)) {
178
+ throw new Error(CIRCULAR_ERROR);
179
+ }
180
+ seen.add(obj);
181
+ try {
182
+ if (Array.isArray(obj)) {
183
+ return obj.map((item) => processValue(item, depth + 1, seen));
184
+ }
185
+ const keys = getSortedObjectKeys(obj);
186
+ const record = obj;
187
+ const sortedObj = {};
188
+ for (const key of keys) {
189
+ sortedObj[key] = processValue(record[key], depth + 1, seen);
190
+ }
191
+ return sortedObj;
192
+ }
193
+ finally {
194
+ seen.delete(obj);
195
+ }
196
+ }
197
+ export function stableStringify(obj, depth = 0, seen = new WeakSet()) {
198
+ const processed = processValue(obj, depth, seen);
199
+ return JSON.stringify(processed);
200
+ }
201
+ const DROP_LOG_INTERVAL_MS = 10_000;
202
+ function setIfDefined(value, setter) {
203
+ if (value === undefined)
204
+ return;
205
+ setter(value);
206
+ }
207
+ function assignServerValue(server, key, value) {
208
+ setIfDefined(value, (resolved) => {
209
+ server[key] = resolved;
210
+ });
211
+ }
212
+ export function applyHttpServerTuning(server) {
213
+ const { headersTimeoutMs, requestTimeoutMs, keepAliveTimeoutMs, keepAliveTimeoutBufferMs, maxHeadersCount, maxConnections, } = config.server.http;
214
+ const tuningValues = [
215
+ ['headersTimeout', headersTimeoutMs],
216
+ ['requestTimeout', requestTimeoutMs],
217
+ ['keepAliveTimeout', keepAliveTimeoutMs],
218
+ ['keepAliveTimeoutBuffer', keepAliveTimeoutBufferMs],
219
+ ['maxHeadersCount', maxHeadersCount],
220
+ ];
221
+ for (const [key, value] of tuningValues) {
222
+ assignServerValue(server, key, value);
223
+ }
224
+ if (typeof maxConnections === 'number' && maxConnections > 0) {
225
+ server.maxConnections = maxConnections;
226
+ if (typeof server.on === 'function') {
227
+ let lastLoggedAt = 0;
228
+ let droppedSinceLastLog = 0;
229
+ const onDrop = (data) => {
230
+ droppedSinceLastLog += 1;
231
+ const now = Date.now();
232
+ if (now - lastLoggedAt < DROP_LOG_INTERVAL_MS)
233
+ return;
234
+ logWarn('Incoming connection dropped (maxConnections reached)', {
235
+ maxConnections,
236
+ dropped: droppedSinceLastLog,
237
+ data,
238
+ });
239
+ lastLoggedAt = now;
240
+ droppedSinceLastLog = 0;
241
+ };
242
+ server.on('drop', onDrop);
243
+ }
244
+ }
245
+ }
246
+ export function drainConnectionsOnShutdown(server) {
247
+ if (typeof server.closeIdleConnections === 'function') {
248
+ server.closeIdleConnections();
249
+ logDebug('Closed idle HTTP connections during shutdown');
250
+ }
251
+ }
252
+ function createAbortSafeTimeoutPromise(timeoutMs, value, signal) {
253
+ return setTimeoutPromise(timeoutMs, value, {
254
+ ref: false,
255
+ signal,
256
+ }).catch((err) => {
257
+ if (isAbortError(err)) {
258
+ return new Promise(() => { });
259
+ }
260
+ throw err;
261
+ });
262
+ }
263
+ export function createUnrefTimeout(timeoutMs, value) {
264
+ const controller = new AbortController();
265
+ const promise = createAbortSafeTimeoutPromise(timeoutMs, value, controller.signal);
266
+ return {
267
+ promise,
268
+ cancel: () => {
269
+ controller.abort();
270
+ },
271
+ };
272
+ }
273
+ export function startAbortableIntervalLoop(intervalMs, value, options) {
274
+ const ticks = setIntervalPromise(intervalMs, value, {
275
+ signal: options.signal,
276
+ ref: false,
277
+ });
278
+ void (async () => {
279
+ try {
280
+ for await (const tickValue of ticks) {
281
+ await options.onTick(tickValue);
282
+ if (options.signal.aborted)
283
+ return;
284
+ }
285
+ }
286
+ catch (error) {
287
+ if (isAbortError(error))
288
+ return;
289
+ options.onError?.(error);
290
+ }
291
+ })();
292
+ }
293
+ export function isObject(value) {
294
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
295
+ }
296
+ export function isError(value) {
297
+ const { isError: isErrorFn } = Error;
298
+ return typeof isErrorFn === 'function'
299
+ ? isErrorFn(value)
300
+ : value instanceof Error;
301
+ }
302
+ export function isLikeNode(value) {
303
+ return isObject(value);
304
+ }
@@ -5,4 +5,3 @@ interface IconInfo {
5
5
  }
6
6
  export declare function registerGetHelpPrompt(server: McpServer, instructions: string, iconInfo?: IconInfo): void;
7
7
  export {};
8
- //# sourceMappingURL=index.d.ts.map
@@ -29,4 +29,3 @@ export function registerGetHelpPrompt(server, instructions, iconInfo) {
29
29
  ],
30
30
  }));
31
31
  }
32
- //# sourceMappingURL=index.js.map
@@ -6,4 +6,3 @@ interface IconInfo {
6
6
  export declare function registerInstructionResource(server: McpServer, instructions: string, iconInfo?: IconInfo): void;
7
7
  export declare function registerCacheResourceTemplate(server: McpServer, iconInfo?: IconInfo): void;
8
8
  export {};
9
- //# sourceMappingURL=index.d.ts.map
@@ -1,15 +1,23 @@
1
1
  import { ResourceTemplate, } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import { ErrorCode, McpError, SubscribeRequestSchema, UnsubscribeRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
3
- import { get as getCacheEntry, getEntryMeta, keys as listCacheKeys, onCacheUpdate, parseCachedPayload, parseCacheKey, resolveCachedPayloadContent, } from '../lib/cache.js';
4
- import { RESOURCE_NOT_FOUND_ERROR_CODE } from '../lib/errors.js';
5
- import { registerServerLifecycleCleanup } from '../lib/mcp-lifecycle.js';
6
- import { logWarn } from '../lib/observability.js';
7
- import { isObject } from '../lib/type-guards.js';
3
+ import { get as getCacheEntry, getEntryMeta, keys as listCacheKeys, onCacheUpdate, parseCachedPayload, parseCacheKey, resolveCachedPayloadContent, } from '../lib/core.js';
4
+ import { logWarn } from '../lib/core.js';
5
+ import { registerServerLifecycleCleanup } from '../lib/mcp-tools.js';
6
+ import { RESOURCE_NOT_FOUND_ERROR_CODE } from '../lib/utils.js';
7
+ import { isObject } from '../lib/utils.js';
8
8
  const CACHE_RESOURCE_TEMPLATE_URI = 'internal://cache/{namespace}/{hash}';
9
9
  const CACHE_RESOURCE_PREFIX = 'internal://cache/';
10
10
  const CACHE_NAMESPACE_PATTERN = /^[a-z0-9_-]{1,64}$/i;
11
11
  const CACHE_HASH_PATTERN = /^[a-f0-9.]{8,64}$/i;
12
12
  const MAX_COMPLETION_VALUES = 100;
13
+ function normalizeCompletionPrefix(value) {
14
+ return value.trim().toLowerCase();
15
+ }
16
+ function sortAndLimitValues(values) {
17
+ return [...values]
18
+ .sort((left, right) => left.localeCompare(right))
19
+ .slice(0, MAX_COMPLETION_VALUES);
20
+ }
13
21
  function buildOptionalIcons(iconInfo) {
14
22
  if (!iconInfo)
15
23
  return {};
@@ -84,39 +92,68 @@ function toCacheResourceUri(parts) {
84
92
  const hash = encodeURIComponent(parts.hash);
85
93
  return `${CACHE_RESOURCE_PREFIX}${namespace}/${hash}`;
86
94
  }
87
- function listCacheNamespaces() {
88
- const namespaces = new Set();
89
- for (const key of listCacheKeys()) {
90
- const parsed = parseCacheKey(key);
91
- if (!parsed)
92
- continue;
93
- namespaces.add(parsed.namespace);
95
+ class CompletionIndex {
96
+ namespaces = null;
97
+ hashIndex = null;
98
+ invalidate() {
99
+ this.namespaces = null;
100
+ this.hashIndex = null;
101
+ }
102
+ getNamespaces() {
103
+ if (!this.namespaces) {
104
+ const seen = new Set();
105
+ for (const key of listCacheKeys()) {
106
+ const parsed = parseCacheKey(key);
107
+ if (parsed)
108
+ seen.add(parsed.namespace);
109
+ }
110
+ this.namespaces = [...seen].sort((a, b) => a.localeCompare(b));
111
+ }
112
+ return this.namespaces;
113
+ }
114
+ getHashes(namespace) {
115
+ if (!this.hashIndex) {
116
+ const index = new Map();
117
+ for (const key of listCacheKeys()) {
118
+ const parsed = parseCacheKey(key);
119
+ if (!parsed)
120
+ continue;
121
+ let set = index.get(parsed.namespace);
122
+ if (!set) {
123
+ set = new Set();
124
+ index.set(parsed.namespace, set);
125
+ }
126
+ set.add(parsed.urlHash);
127
+ }
128
+ this.hashIndex = new Map();
129
+ for (const [ns, set] of index) {
130
+ this.hashIndex.set(ns, [...set].sort((a, b) => a.localeCompare(b)));
131
+ }
132
+ }
133
+ if (namespace)
134
+ return this.hashIndex.get(namespace) ?? [];
135
+ const all = [];
136
+ for (const hashes of this.hashIndex.values()) {
137
+ all.push(...hashes);
138
+ }
139
+ return all.sort((a, b) => a.localeCompare(b));
94
140
  }
95
- return [...namespaces].sort((left, right) => left.localeCompare(right));
96
141
  }
142
+ const completionIndex = new CompletionIndex();
97
143
  function completeCacheNamespaces(value) {
98
- const normalized = value.trim().toLowerCase();
99
- return listCacheNamespaces()
100
- .filter((namespace) => namespace.toLowerCase().startsWith(normalized))
101
- .slice(0, MAX_COMPLETION_VALUES);
144
+ const normalized = normalizeCompletionPrefix(value);
145
+ const namespaces = completionIndex
146
+ .getNamespaces()
147
+ .filter((ns) => ns.toLowerCase().startsWith(normalized));
148
+ return sortAndLimitValues(namespaces);
102
149
  }
103
150
  function completeCacheHashes(value, context) {
104
- const normalized = value.trim().toLowerCase();
151
+ const normalized = normalizeCompletionPrefix(value);
105
152
  const namespace = context?.arguments?.['namespace']?.trim();
106
- const hashes = new Set();
107
- for (const key of listCacheKeys()) {
108
- const parsed = parseCacheKey(key);
109
- if (!parsed)
110
- continue;
111
- if (namespace && parsed.namespace !== namespace)
112
- continue;
113
- if (!parsed.urlHash.toLowerCase().startsWith(normalized))
114
- continue;
115
- hashes.add(parsed.urlHash);
116
- }
117
- return [...hashes]
118
- .sort((left, right) => left.localeCompare(right))
119
- .slice(0, MAX_COMPLETION_VALUES);
153
+ const hashes = completionIndex
154
+ .getHashes(namespace)
155
+ .filter((h) => h.toLowerCase().startsWith(normalized));
156
+ return sortAndLimitValues(hashes);
120
157
  }
121
158
  function listCacheResources() {
122
159
  const resources = listCacheKeys()
@@ -159,7 +196,11 @@ function normalizeSubscriptionUri(uri) {
159
196
  // Must only be called once per server instance; duplicate calls would register
160
197
  // redundant Subscribe/Unsubscribe handlers and create a second onCacheUpdate
161
198
  // listener, causing duplicate notifications.
199
+ let cacheNotificationsRegistered = false;
162
200
  function registerCacheResourceNotifications(server) {
201
+ if (cacheNotificationsRegistered)
202
+ return;
203
+ cacheNotificationsRegistered = true;
163
204
  const subscribedResourceUris = new Set();
164
205
  const setSubscription = (uri, subscribed) => {
165
206
  const normalized = normalizeSubscriptionUri(uri);
@@ -179,6 +220,7 @@ function registerCacheResourceNotifications(server) {
179
220
  return Promise.resolve({});
180
221
  });
181
222
  const unsubscribe = onCacheUpdate((event) => {
223
+ completionIndex.invalidate();
182
224
  const changedUri = toCacheResourceUri({
183
225
  namespace: event.namespace,
184
226
  hash: event.urlHash,
@@ -299,4 +341,3 @@ export function registerCacheResourceTemplate(server, iconInfo) {
299
341
  }, (uri, variables) => readCacheResource(uri, normalizeTemplateVariables(variables)));
300
342
  registerCacheResourceNotifications(server);
301
343
  }
302
- //# sourceMappingURL=index.js.map
@@ -1,2 +1 @@
1
1
  export declare function buildServerInstructions(): string;
2
- //# sourceMappingURL=instructions.d.ts.map
@@ -1,4 +1,4 @@
1
- import { config } from '../lib/config.js';
1
+ import { config } from '../lib/core.js';
2
2
  import { FETCH_URL_TOOL_NAME } from '../tools/fetch-url.js';
3
3
  export function buildServerInstructions() {
4
4
  const maxHtmlSizeMb = config.constants.maxHtmlSize / 1024 / 1024;
@@ -30,6 +30,7 @@ export function buildServerInstructions() {
30
30
  - Batch JSON-RPC: Array requests (\`[{...}]\`) are rejected with HTTP 400.
31
31
  - Tasks API: Experimental (SDK v1.26). \`tasks/get\`, \`tasks/result\`, \`tasks/list\`, \`tasks/cancel\` may change.
32
32
  - Notifications: Optional non-spec extension. Set \`TASKS_STATUS_NOTIFICATIONS=true\` to emit \`notifications/tasks/status\`.
33
+ - URI scheme: \`internal://\` is a server-scoped custom scheme for cache resources. Not IANA-registered; valid only within this server's session lifetime.
33
34
  </constraints>
34
35
 
35
36
  <error_handling>
@@ -40,4 +41,3 @@ export function buildServerInstructions() {
40
41
  - queue_full: Worker pool busy. Wait and retry, or use task mode.
41
42
  </error_handling>`;
42
43
  }
43
- //# sourceMappingURL=instructions.js.map
@@ -5,4 +5,3 @@ export declare const fetchUrlInputSchema: z.ZodObject<{
5
5
  forceRefresh: z.ZodOptional<z.ZodBoolean>;
6
6
  maxInlineChars: z.ZodOptional<z.ZodNumber>;
7
7
  }, z.core.$strict>;
8
- //# sourceMappingURL=inputs.d.ts.map
@@ -1,8 +1,8 @@
1
1
  import { z } from 'zod';
2
- import { config } from '../lib/config.js';
2
+ import { config } from '../lib/core.js';
3
3
  export const fetchUrlInputSchema = z.strictObject({
4
4
  url: z
5
- .url({ protocol: /^https?$/i })
5
+ .httpUrl()
6
6
  .min(1)
7
7
  .max(config.constants.maxUrlLength)
8
8
  .describe(`Target URL. Max ${config.constants.maxUrlLength} chars.`),
@@ -22,4 +22,3 @@ export const fetchUrlInputSchema = z.strictObject({
22
22
  .optional()
23
23
  .describe(`Inline markdown limit (0-${config.constants.maxHtmlSize}, 0=unlimited). Lower of this or global limit applies.`),
24
24
  });
25
- //# sourceMappingURL=inputs.js.map
@@ -21,4 +21,3 @@ export declare const fetchUrlOutputSchema: z.ZodObject<{
21
21
  contentSize: z.ZodOptional<z.ZodNumber>;
22
22
  truncated: z.ZodOptional<z.ZodBoolean>;
23
23
  }, z.core.$strict>;
24
- //# sourceMappingURL=outputs.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { config } from '../lib/config.js';
2
+ import { config } from '../lib/core.js';
3
3
  export const fetchUrlOutputSchema = z.strictObject({
4
4
  url: z
5
5
  .string()
@@ -75,4 +75,3 @@ export const fetchUrlOutputSchema = z.strictObject({
75
75
  .describe('Full markdown size before truncation.'),
76
76
  truncated: z.boolean().optional().describe('True if markdown was truncated.'),
77
77
  });
78
- //# sourceMappingURL=outputs.js.map
package/dist/server.d.ts CHANGED
@@ -2,4 +2,3 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  export declare function createMcpServer(): Promise<McpServer>;
3
3
  export declare function createMcpServerForHttpSession(): Promise<McpServer>;
4
4
  export declare function startStdioServer(): Promise<void>;
5
- //# sourceMappingURL=server.d.ts.map