@j0hanz/superfetch 2.5.2 → 2.6.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 (46) hide show
  1. package/README.md +356 -223
  2. package/dist/assets/logo.svg +24837 -24835
  3. package/dist/cache.d.ts +28 -20
  4. package/dist/cache.js +292 -514
  5. package/dist/config.d.ts +41 -7
  6. package/dist/config.js +298 -148
  7. package/dist/crypto.js +25 -12
  8. package/dist/dom-noise-removal.js +379 -421
  9. package/dist/errors.d.ts +2 -2
  10. package/dist/errors.js +25 -8
  11. package/dist/fetch.d.ts +18 -16
  12. package/dist/fetch.js +1132 -526
  13. package/dist/host-normalization.js +40 -10
  14. package/dist/http-native.js +628 -287
  15. package/dist/index.js +67 -7
  16. package/dist/instructions.md +44 -30
  17. package/dist/ip-blocklist.d.ts +8 -0
  18. package/dist/ip-blocklist.js +65 -0
  19. package/dist/json.js +14 -9
  20. package/dist/language-detection.d.ts +2 -11
  21. package/dist/language-detection.js +289 -280
  22. package/dist/markdown-cleanup.d.ts +0 -1
  23. package/dist/markdown-cleanup.js +391 -429
  24. package/dist/mcp-validator.js +4 -2
  25. package/dist/mcp.js +184 -135
  26. package/dist/observability.js +89 -21
  27. package/dist/resources.js +16 -6
  28. package/dist/server-tuning.d.ts +2 -0
  29. package/dist/server-tuning.js +25 -23
  30. package/dist/session.d.ts +1 -0
  31. package/dist/session.js +41 -33
  32. package/dist/tasks.d.ts +2 -0
  33. package/dist/tasks.js +91 -9
  34. package/dist/timer-utils.d.ts +5 -0
  35. package/dist/timer-utils.js +20 -0
  36. package/dist/tools.d.ts +28 -5
  37. package/dist/tools.js +317 -183
  38. package/dist/transform-types.d.ts +5 -1
  39. package/dist/transform.d.ts +3 -2
  40. package/dist/transform.js +1138 -421
  41. package/dist/type-guards.d.ts +1 -0
  42. package/dist/type-guards.js +7 -0
  43. package/dist/workers/transform-child.d.ts +1 -0
  44. package/dist/workers/transform-child.js +118 -0
  45. package/dist/workers/transform-worker.js +87 -78
  46. package/package.json +21 -13
package/dist/resources.js CHANGED
@@ -4,17 +4,27 @@ import { stableStringify } from './json.js';
4
4
  /* -------------------------------------------------------------------------------------------------
5
5
  * Configuration Resource
6
6
  * ------------------------------------------------------------------------------------------------- */
7
+ const REDACTED = '<REDACTED>';
8
+ const CONFIG_RESOURCE_NAME = 'config';
9
+ const CONFIG_RESOURCE_URI = 'internal://config';
10
+ const JSON_MIME = 'application/json';
11
+ function redactIfPresent(value) {
12
+ return value ? REDACTED : undefined;
13
+ }
14
+ function redactArray(values) {
15
+ return values.map(() => REDACTED);
16
+ }
7
17
  function scrubAuth(auth) {
8
18
  return {
9
19
  ...auth,
10
- clientSecret: auth.clientSecret ? '<REDACTED>' : undefined,
11
- staticTokens: auth.staticTokens.map(() => '<REDACTED>'),
20
+ clientSecret: redactIfPresent(auth.clientSecret),
21
+ staticTokens: redactArray(auth.staticTokens),
12
22
  };
13
23
  }
14
24
  function scrubSecurity(security) {
15
25
  return {
16
26
  ...security,
17
- apiKey: security.apiKey ? '<REDACTED>' : undefined,
27
+ apiKey: redactIfPresent(security.apiKey),
18
28
  };
19
29
  }
20
30
  function scrubConfig(source) {
@@ -25,17 +35,17 @@ function scrubConfig(source) {
25
35
  };
26
36
  }
27
37
  export function registerConfigResource(server) {
28
- server.registerResource('config', new ResourceTemplate('internal://config', { list: undefined }), {
38
+ server.registerResource(CONFIG_RESOURCE_NAME, new ResourceTemplate(CONFIG_RESOURCE_URI, { list: undefined }), {
29
39
  title: 'Server Configuration',
30
40
  description: 'Current runtime configuration (secrets redacted)',
31
- mimeType: 'application/json',
41
+ mimeType: JSON_MIME,
32
42
  }, (uri) => {
33
43
  const scrubbed = scrubConfig(config);
34
44
  return {
35
45
  contents: [
36
46
  {
37
47
  uri: uri.href,
38
- mimeType: 'application/json',
48
+ mimeType: JSON_MIME,
39
49
  text: stableStringify(scrubbed),
40
50
  },
41
51
  ],
@@ -2,6 +2,8 @@ export interface HttpServerTuningTarget {
2
2
  headersTimeout?: number;
3
3
  requestTimeout?: number;
4
4
  keepAliveTimeout?: number;
5
+ maxConnections?: number;
6
+ on?: (event: string, listener: (...args: unknown[]) => void) => void;
5
7
  closeIdleConnections?: () => void;
6
8
  closeAllConnections?: () => void;
7
9
  }
@@ -1,30 +1,32 @@
1
1
  import { config } from './config.js';
2
- import { logDebug } from './observability.js';
2
+ import { logDebug, logWarn } from './observability.js';
3
+ const DROP_LOG_INTERVAL_MS = 10_000;
3
4
  export function applyHttpServerTuning(server) {
4
- const { headersTimeoutMs, requestTimeoutMs, keepAliveTimeoutMs } = config.server.http;
5
- if (headersTimeoutMs !== undefined) {
6
- server.headersTimeout = headersTimeoutMs;
7
- }
8
- if (requestTimeoutMs !== undefined) {
9
- server.requestTimeout = requestTimeoutMs;
10
- }
11
- if (keepAliveTimeoutMs !== undefined) {
12
- server.keepAliveTimeout = keepAliveTimeoutMs;
5
+ const { maxConnections } = config.server.http;
6
+ if (typeof maxConnections === 'number' && maxConnections > 0) {
7
+ server.maxConnections = maxConnections;
8
+ if (typeof server.on === 'function') {
9
+ let lastLoggedAt = 0;
10
+ let droppedSinceLastLog = 0;
11
+ server.on('drop', (data) => {
12
+ droppedSinceLastLog += 1;
13
+ const now = Date.now();
14
+ if (now - lastLoggedAt < DROP_LOG_INTERVAL_MS)
15
+ return;
16
+ logWarn('Incoming connection dropped (maxConnections reached)', {
17
+ maxConnections,
18
+ dropped: droppedSinceLastLog,
19
+ data,
20
+ });
21
+ lastLoggedAt = now;
22
+ droppedSinceLastLog = 0;
23
+ });
24
+ }
13
25
  }
14
26
  }
15
27
  export function drainConnectionsOnShutdown(server) {
16
- const { shutdownCloseAllConnections, shutdownCloseIdleConnections } = config.server.http;
17
- if (shutdownCloseAllConnections) {
18
- if (typeof server.closeAllConnections === 'function') {
19
- server.closeAllConnections();
20
- logDebug('Closed all HTTP connections during shutdown');
21
- }
22
- return;
23
- }
24
- if (shutdownCloseIdleConnections) {
25
- if (typeof server.closeIdleConnections === 'function') {
26
- server.closeIdleConnections();
27
- logDebug('Closed idle HTTP connections during shutdown');
28
- }
28
+ if (typeof server.closeIdleConnections === 'function') {
29
+ server.closeIdleConnections();
30
+ logDebug('Closed idle HTTP connections during shutdown');
29
31
  }
30
32
  }
package/dist/session.d.ts CHANGED
@@ -4,6 +4,7 @@ export interface SessionEntry {
4
4
  createdAt: number;
5
5
  lastSeen: number;
6
6
  protocolInitialized: boolean;
7
+ authFingerprint: string;
7
8
  }
8
9
  export interface SessionStore {
9
10
  get: (sessionId: string) => SessionEntry | undefined;
package/dist/session.js CHANGED
@@ -14,11 +14,13 @@ export function composeCloseHandlers(first, second) {
14
14
  }
15
15
  };
16
16
  }
17
- /* -------------------------------------------------------------------------------------------------
18
- * Cleanup loop
19
- * ------------------------------------------------------------------------------------------------- */
17
+ const MIN_CLEANUP_INTERVAL_MS = 10_000;
18
+ const MAX_CLEANUP_INTERVAL_MS = 60_000;
20
19
  function getCleanupIntervalMs(sessionTtlMs) {
21
- return Math.min(Math.max(Math.floor(sessionTtlMs / 2), 10_000), 60_000);
20
+ return Math.min(Math.max(Math.floor(sessionTtlMs / 2), MIN_CLEANUP_INTERVAL_MS), MAX_CLEANUP_INTERVAL_MS);
21
+ }
22
+ function formatError(error) {
23
+ return error instanceof Error ? error.message : String(error);
22
24
  }
23
25
  function isAbortError(error) {
24
26
  return error instanceof Error && error.name === 'AbortError';
@@ -26,11 +28,11 @@ function isAbortError(error) {
26
28
  function handleSessionCleanupError(error) {
27
29
  if (isAbortError(error))
28
30
  return;
29
- logWarn('Session cleanup loop failed', {
30
- error: error instanceof Error ? error.message : 'Unknown error',
31
- });
31
+ logWarn('Session cleanup loop failed', { error: formatError(error) });
32
32
  }
33
33
  function isSessionExpired(session, now, sessionTtlMs) {
34
+ if (sessionTtlMs <= 0)
35
+ return false;
34
36
  return now - session.lastSeen > sessionTtlMs;
35
37
  }
36
38
  class SessionCleanupLoop {
@@ -47,16 +49,17 @@ class SessionCleanupLoop {
47
49
  }
48
50
  async run(signal) {
49
51
  const intervalMs = getCleanupIntervalMs(this.sessionTtlMs);
50
- for await (const getNow of setIntervalPromise(intervalMs, Date.now, {
52
+ const ticks = setIntervalPromise(intervalMs, Date.now, {
51
53
  signal,
52
54
  ref: false,
53
- })) {
55
+ });
56
+ for await (const getNow of ticks) {
54
57
  const now = getNow();
55
58
  const evicted = this.store.evictExpired();
56
59
  for (const session of evicted) {
57
60
  void session.transport.close().catch((err) => {
58
61
  logWarn('Failed to close expired session', {
59
- error: err instanceof Error ? err : new Error(String(err)),
62
+ error: formatError(err),
60
63
  });
61
64
  });
62
65
  }
@@ -72,9 +75,6 @@ class SessionCleanupLoop {
72
75
  export function startSessionCleanupLoop(store, sessionTtlMs) {
73
76
  return new SessionCleanupLoop(store, sessionTtlMs).start();
74
77
  }
75
- /* -------------------------------------------------------------------------------------------------
76
- * Session store (in-memory, Map order used for LRU)
77
- * ------------------------------------------------------------------------------------------------- */
78
78
  function moveSessionToEnd(sessions, sessionId, session) {
79
79
  sessions.delete(sessionId);
80
80
  sessions.set(sessionId, session);
@@ -87,9 +87,13 @@ class InMemorySessionStore {
87
87
  this.sessionTtlMs = sessionTtlMs;
88
88
  }
89
89
  get(sessionId) {
90
+ if (!sessionId)
91
+ return undefined;
90
92
  return this.sessions.get(sessionId);
91
93
  }
92
94
  touch(sessionId) {
95
+ if (!sessionId)
96
+ return;
93
97
  const session = this.sessions.get(sessionId);
94
98
  if (!session)
95
99
  return;
@@ -97,9 +101,13 @@ class InMemorySessionStore {
97
101
  moveSessionToEnd(this.sessions, sessionId, session);
98
102
  }
99
103
  set(sessionId, entry) {
100
- this.sessions.set(sessionId, entry);
104
+ if (!sessionId)
105
+ return;
106
+ moveSessionToEnd(this.sessions, sessionId, entry);
101
107
  }
102
108
  remove(sessionId) {
109
+ if (!sessionId)
110
+ return undefined;
103
111
  const session = this.sessions.get(sessionId);
104
112
  this.sessions.delete(sessionId);
105
113
  return session;
@@ -114,8 +122,7 @@ class InMemorySessionStore {
114
122
  this.inflight += 1;
115
123
  }
116
124
  decrementInFlight() {
117
- if (this.inflight > 0)
118
- this.inflight -= 1;
125
+ this.inflight = Math.max(0, this.inflight - 1);
119
126
  }
120
127
  clear() {
121
128
  const entries = [...this.sessions.values()];
@@ -126,10 +133,10 @@ class InMemorySessionStore {
126
133
  const now = Date.now();
127
134
  const evicted = [];
128
135
  for (const [id, session] of this.sessions.entries()) {
129
- if (isSessionExpired(session, now, this.sessionTtlMs)) {
130
- this.sessions.delete(id);
131
- evicted.push(session);
132
- }
136
+ if (!isSessionExpired(session, now, this.sessionTtlMs))
137
+ continue;
138
+ this.sessions.delete(id);
139
+ evicted.push(session);
133
140
  }
134
141
  return evicted;
135
142
  }
@@ -146,9 +153,6 @@ class InMemorySessionStore {
146
153
  export function createSessionStore(sessionTtlMs) {
147
154
  return new InMemorySessionStore(sessionTtlMs);
148
155
  }
149
- /* -------------------------------------------------------------------------------------------------
150
- * Slot tracker
151
- * ------------------------------------------------------------------------------------------------- */
152
156
  class SessionSlotTracker {
153
157
  store;
154
158
  slotReleased = false;
@@ -172,27 +176,31 @@ class SessionSlotTracker {
172
176
  export function createSlotTracker(store) {
173
177
  return new SessionSlotTracker(store);
174
178
  }
179
+ function currentLoad(store) {
180
+ return store.size() + store.inFlight();
181
+ }
175
182
  export function reserveSessionSlot(store, maxSessions) {
176
- if (store.size() + store.inFlight() >= maxSessions) {
183
+ if (maxSessions <= 0)
184
+ return false;
185
+ if (currentLoad(store) >= maxSessions)
177
186
  return false;
178
- }
179
187
  store.incrementInFlight();
180
188
  return true;
181
189
  }
182
- /* -------------------------------------------------------------------------------------------------
183
- * Capacity policy
184
- * ------------------------------------------------------------------------------------------------- */
185
190
  function isAtCapacity(store, maxSessions) {
186
- return store.size() + store.inFlight() >= maxSessions;
191
+ return currentLoad(store) >= maxSessions;
187
192
  }
188
193
  export function ensureSessionCapacity({ store, maxSessions, evictOldest, }) {
194
+ if (maxSessions <= 0)
195
+ return false;
189
196
  const currentSize = store.size();
190
197
  const inflight = store.inFlight();
191
198
  if (currentSize + inflight < maxSessions)
192
199
  return true;
193
200
  const canFreeSlot = currentSize >= maxSessions && currentSize - 1 + inflight < maxSessions;
194
- if (canFreeSlot && evictOldest(store)) {
195
- return !isAtCapacity(store, maxSessions);
196
- }
197
- return false;
201
+ if (!canFreeSlot)
202
+ return false;
203
+ if (!evictOldest(store))
204
+ return false;
205
+ return !isAtCapacity(store, maxSessions);
198
206
  }
package/dist/tasks.d.ts CHANGED
@@ -34,6 +34,8 @@ export interface CreateTaskResult {
34
34
  declare class TaskManager {
35
35
  private tasks;
36
36
  private waiters;
37
+ constructor();
38
+ private startCleanupLoop;
37
39
  createTask(options?: CreateTaskOptions, statusMessage?: string, ownerKey?: string): TaskState;
38
40
  getTask(taskId: string, ownerKey?: string): TaskState | undefined;
39
41
  updateTask(taskId: string, updates: Partial<Omit<TaskState, 'taskId' | 'createdAt'>>): void;
package/dist/tasks.js CHANGED
@@ -1,5 +1,9 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { Buffer } from 'node:buffer';
1
3
  import { randomUUID } from 'node:crypto';
4
+ import { setInterval } from 'node:timers';
2
5
  import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
6
+ import { createUnrefTimeout } from './timer-utils.js';
3
7
  const DEFAULT_TTL_MS = 60000;
4
8
  const DEFAULT_POLL_INTERVAL_MS = 1000;
5
9
  const DEFAULT_OWNER_KEY = 'default';
@@ -15,6 +19,19 @@ function isTerminalStatus(status) {
15
19
  class TaskManager {
16
20
  tasks = new Map();
17
21
  waiters = new Map();
22
+ constructor() {
23
+ this.startCleanupLoop();
24
+ }
25
+ startCleanupLoop() {
26
+ const interval = setInterval(() => {
27
+ for (const [id, task] of this.tasks) {
28
+ if (this.isExpired(task)) {
29
+ this.tasks.delete(id);
30
+ }
31
+ }
32
+ }, 60000);
33
+ interval.unref();
34
+ }
18
35
  createTask(options, statusMessage = 'Task started', ownerKey = DEFAULT_OWNER_KEY) {
19
36
  const taskId = randomUUID();
20
37
  const now = new Date().toISOString();
@@ -99,13 +116,52 @@ class TaskManager {
99
116
  return undefined;
100
117
  if (isTerminalStatus(task.status))
101
118
  return task;
119
+ const createdAtMs = Date.parse(task.createdAt);
120
+ const deadlineMs = Number.isFinite(createdAtMs)
121
+ ? createdAtMs + task.ttl
122
+ : Number.NaN;
123
+ if (Number.isFinite(deadlineMs) && deadlineMs <= Date.now()) {
124
+ this.tasks.delete(taskId);
125
+ return undefined;
126
+ }
102
127
  return new Promise((resolve, reject) => {
128
+ const runInContext = AsyncLocalStorage.snapshot();
129
+ const resolveInContext = (value) => {
130
+ runInContext(() => {
131
+ resolve(value);
132
+ });
133
+ };
134
+ const rejectInContext = (error) => {
135
+ runInContext(() => {
136
+ if (error instanceof Error) {
137
+ reject(error);
138
+ }
139
+ else {
140
+ reject(new Error(String(error)));
141
+ }
142
+ });
143
+ };
144
+ let settled = false;
145
+ let waiter = null;
146
+ let deadlineTimeout;
147
+ const settle = (fn) => {
148
+ if (settled)
149
+ return;
150
+ settled = true;
151
+ fn();
152
+ };
103
153
  const onAbort = () => {
104
- cleanup();
105
- removeWaiter();
106
- reject(new McpError(ErrorCode.ConnectionClosed, 'Request was cancelled'));
154
+ settle(() => {
155
+ cleanup();
156
+ removeWaiter();
157
+ rejectInContext(new McpError(ErrorCode.ConnectionClosed, 'Request was cancelled'));
158
+ });
107
159
  };
108
160
  const cleanup = () => {
161
+ if (deadlineTimeout) {
162
+ deadlineTimeout.cancel();
163
+ deadlineTimeout = undefined;
164
+ }
109
165
  if (signal) {
110
166
  signal.removeEventListener('abort', onAbort);
111
167
  }
@@ -114,13 +170,16 @@ class TaskManager {
114
170
  const waiters = this.waiters.get(taskId);
115
171
  if (!waiters)
116
172
  return;
117
- waiters.delete(waiter);
173
+ if (waiter)
174
+ waiters.delete(waiter);
118
175
  if (waiters.size === 0)
119
176
  this.waiters.delete(taskId);
120
177
  };
121
- const waiter = (updated) => {
122
- cleanup();
123
- resolve(updated);
178
+ waiter = (updated) => {
179
+ settle(() => {
180
+ cleanup();
181
+ resolveInContext(updated);
182
+ });
124
183
  };
125
184
  if (signal?.aborted) {
126
185
  onAbort();
@@ -132,6 +191,29 @@ class TaskManager {
132
191
  if (signal) {
133
192
  signal.addEventListener('abort', onAbort, { once: true });
134
193
  }
194
+ if (Number.isFinite(deadlineMs)) {
195
+ const timeoutMs = Math.max(0, deadlineMs - Date.now());
196
+ if (timeoutMs === 0) {
197
+ settle(() => {
198
+ cleanup();
199
+ removeWaiter();
200
+ this.tasks.delete(taskId);
201
+ resolveInContext(undefined);
202
+ });
203
+ return;
204
+ }
205
+ deadlineTimeout = createUnrefTimeout(timeoutMs, { timeout: true });
206
+ void deadlineTimeout.promise
207
+ .then(() => {
208
+ settle(() => {
209
+ cleanup();
210
+ removeWaiter();
211
+ this.tasks.delete(taskId);
212
+ resolveInContext(undefined);
213
+ });
214
+ })
215
+ .catch(rejectInContext);
216
+ }
135
217
  });
136
218
  }
137
219
  notifyWaiters(task) {
@@ -151,11 +233,11 @@ class TaskManager {
151
233
  return Date.now() - createdAt > task.ttl;
152
234
  }
153
235
  encodeCursor(index) {
154
- return Buffer.from(String(index)).toString('base64');
236
+ return Buffer.from(String(index)).toString('base64url');
155
237
  }
156
238
  decodeCursor(cursor) {
157
239
  try {
158
- const decoded = Buffer.from(cursor, 'base64').toString('utf8');
240
+ const decoded = Buffer.from(cursor, 'base64url').toString('utf8');
159
241
  const value = Number.parseInt(decoded, 10);
160
242
  if (!Number.isFinite(value) || value < 0)
161
243
  return null;
@@ -0,0 +1,5 @@
1
+ export interface CancellableTimeout<T> {
2
+ promise: Promise<T>;
3
+ cancel: () => void;
4
+ }
5
+ export declare function createUnrefTimeout<T>(timeoutMs: number, value: T): CancellableTimeout<T>;
@@ -0,0 +1,20 @@
1
+ import { setTimeout as setTimeoutPromise } from 'node:timers/promises';
2
+ import { isError } from './type-guards.js';
3
+ export function createUnrefTimeout(timeoutMs, value) {
4
+ const controller = new AbortController();
5
+ const promise = setTimeoutPromise(timeoutMs, value, {
6
+ ref: false,
7
+ signal: controller.signal,
8
+ }).catch((err) => {
9
+ if (isError(err) && err.name === 'AbortError') {
10
+ return new Promise(() => { });
11
+ }
12
+ throw err;
13
+ });
14
+ return {
15
+ promise,
16
+ cancel: () => {
17
+ controller.abort();
18
+ },
19
+ };
20
+ }
package/dist/tools.d.ts CHANGED
@@ -1,8 +1,11 @@
1
+ import { z } from 'zod';
1
2
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
3
  import type { CallToolResult, ContentBlock } from '@modelcontextprotocol/sdk/types.js';
3
4
  import type { MarkdownTransformResult } from './transform-types.js';
4
5
  export interface FetchUrlInput {
5
6
  url: string;
7
+ skipNoiseRemoval?: boolean | undefined;
8
+ forceRefresh?: boolean | undefined;
6
9
  }
7
10
  export interface ToolContentBlock {
8
11
  type: 'text';
@@ -29,6 +32,8 @@ export type ToolErrorResponse = CallToolResult & {
29
32
  structuredContent: {
30
33
  error: string;
31
34
  url: string;
35
+ statusCode?: number;
36
+ details?: Record<string, unknown>;
32
37
  };
33
38
  isError: true;
34
39
  };
@@ -38,7 +43,11 @@ export interface FetchPipelineOptions<T> {
38
43
  cacheNamespace: string;
39
44
  signal?: AbortSignal;
40
45
  cacheVary?: Record<string, unknown> | string;
41
- transform: (html: string, url: string) => T | Promise<T>;
46
+ forceRefresh?: boolean;
47
+ transform: (input: {
48
+ buffer: Uint8Array;
49
+ encoding: string;
50
+ }, url: string) => T | Promise<T>;
42
51
  serialize?: (result: T) => string;
43
52
  deserialize?: (cached: string) => T | undefined;
44
53
  }
@@ -46,6 +55,7 @@ export interface PipelineResult<T> {
46
55
  data: T;
47
56
  fromCache: boolean;
48
57
  url: string;
58
+ originalUrl?: string;
49
59
  fetchedAt: string;
50
60
  cacheKey?: string | null;
51
61
  }
@@ -68,9 +78,16 @@ export interface ProgressNotification {
68
78
  export interface ToolHandlerExtra {
69
79
  signal?: AbortSignal;
70
80
  requestId?: string | number;
81
+ sessionId?: unknown;
82
+ requestInfo?: unknown;
71
83
  _meta?: RequestMeta;
72
84
  sendNotification?: (notification: ProgressNotification) => Promise<void>;
73
85
  }
86
+ export declare const fetchUrlInputSchema: z.ZodObject<{
87
+ url: z.ZodURL;
88
+ skipNoiseRemoval: z.ZodOptional<z.ZodBoolean>;
89
+ forceRefresh: z.ZodOptional<z.ZodBoolean>;
90
+ }, z.core.$strict>;
74
91
  export declare const FETCH_URL_TOOL_NAME = "fetch-url";
75
92
  interface ProgressReporter {
76
93
  report: (progress: number, message: string) => Promise<void>;
@@ -87,8 +104,6 @@ interface InlineContentResult {
87
104
  export type InlineResult = ReturnType<InlineContentLimiter['apply']>;
88
105
  declare class InlineContentLimiter {
89
106
  apply(content: string, cacheKey: string | null): InlineContentResult;
90
- private resolveResourceUri;
91
- private buildTruncatedFallback;
92
107
  }
93
108
  export declare function executeFetchPipeline<T>(options: FetchPipelineOptions<T>): Promise<PipelineResult<T>>;
94
109
  interface SharedFetchOptions<T extends {
@@ -96,7 +111,12 @@ interface SharedFetchOptions<T extends {
96
111
  }> {
97
112
  readonly url: string;
98
113
  readonly signal?: AbortSignal;
99
- readonly transform: (html: string, normalizedUrl: string) => T | Promise<T>;
114
+ readonly cacheVary?: Record<string, unknown> | string;
115
+ readonly forceRefresh?: boolean;
116
+ readonly transform: (input: {
117
+ buffer: Uint8Array;
118
+ encoding: string;
119
+ }, normalizedUrl: string) => T | Promise<T>;
100
120
  readonly serialize?: (result: T) => string;
101
121
  readonly deserialize?: (cached: string) => T | undefined;
102
122
  }
@@ -109,7 +129,10 @@ export declare function performSharedFetch<T extends {
109
129
  pipeline: PipelineResult<T>;
110
130
  inlineResult: InlineResult;
111
131
  }>;
112
- export declare function createToolErrorResponse(message: string, url: string): ToolErrorResponse;
132
+ export declare function createToolErrorResponse(message: string, url: string, extra?: {
133
+ statusCode?: number;
134
+ details?: Record<string, unknown>;
135
+ }): ToolErrorResponse;
113
136
  export declare function handleToolError(error: unknown, url: string, fallbackMessage?: string): ToolErrorResponse;
114
137
  type MarkdownPipelineResult = MarkdownTransformResult & {
115
138
  readonly content: string;