@j0hanz/fetch-url-mcp 1.10.2 → 1.10.3

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 (55) hide show
  1. package/dist/http/native.d.ts.map +1 -1
  2. package/dist/http/native.js +20 -26
  3. package/dist/http/session-teardown.d.ts +17 -0
  4. package/dist/http/session-teardown.d.ts.map +1 -0
  5. package/dist/http/session-teardown.js +30 -0
  6. package/dist/lib/core.d.ts.map +1 -1
  7. package/dist/lib/core.js +2 -0
  8. package/dist/lib/dom-prep.d.ts.map +1 -1
  9. package/dist/lib/dom-prep.js +19 -14
  10. package/dist/lib/progress.d.ts.map +1 -1
  11. package/dist/lib/progress.js +59 -32
  12. package/dist/lib/sdk-interop.d.ts +8 -0
  13. package/dist/lib/sdk-interop.d.ts.map +1 -0
  14. package/dist/lib/sdk-interop.js +73 -0
  15. package/dist/lib/task-handlers.d.ts +0 -2
  16. package/dist/lib/task-handlers.d.ts.map +1 -1
  17. package/dist/lib/task-handlers.js +9 -94
  18. package/dist/resources/index.js +1 -1
  19. package/dist/server.d.ts.map +1 -1
  20. package/dist/server.js +10 -3
  21. package/dist/tasks/call-contract.d.ts +25 -0
  22. package/dist/tasks/call-contract.d.ts.map +1 -0
  23. package/dist/tasks/call-contract.js +58 -0
  24. package/dist/tasks/cursor-codec.d.ts +5 -0
  25. package/dist/tasks/cursor-codec.d.ts.map +1 -0
  26. package/dist/tasks/cursor-codec.js +41 -0
  27. package/dist/tasks/execution.d.ts +1 -20
  28. package/dist/tasks/execution.d.ts.map +1 -1
  29. package/dist/tasks/execution.js +7 -32
  30. package/dist/tasks/manager.d.ts +1 -5
  31. package/dist/tasks/manager.d.ts.map +1 -1
  32. package/dist/tasks/manager.js +16 -144
  33. package/dist/tasks/owner.d.ts +12 -2
  34. package/dist/tasks/owner.d.ts.map +1 -1
  35. package/dist/tasks/owner.js +52 -3
  36. package/dist/tasks/tool-registry.d.ts +1 -0
  37. package/dist/tasks/tool-registry.d.ts.map +1 -1
  38. package/dist/tasks/waiters.d.ts +27 -0
  39. package/dist/tasks/waiters.d.ts.map +1 -0
  40. package/dist/tasks/waiters.js +113 -0
  41. package/dist/tools/fetch-url-progress.d.ts +17 -0
  42. package/dist/tools/fetch-url-progress.d.ts.map +1 -0
  43. package/dist/tools/fetch-url-progress.js +78 -0
  44. package/dist/tools/fetch-url.d.ts +5 -9
  45. package/dist/tools/fetch-url.d.ts.map +1 -1
  46. package/dist/tools/fetch-url.js +43 -96
  47. package/dist/transform/next-flight.d.ts +2 -0
  48. package/dist/transform/next-flight.d.ts.map +1 -0
  49. package/dist/transform/next-flight.js +285 -0
  50. package/dist/transform/transform.d.ts.map +1 -1
  51. package/dist/transform/transform.js +21 -336
  52. package/package.json +4 -4
  53. package/dist/tools/index.d.ts +0 -3
  54. package/dist/tools/index.d.ts.map +0 -1
  55. package/dist/tools/index.js +0 -4
@@ -1,11 +1,9 @@
1
- import { AsyncLocalStorage } from 'node:async_hooks';
2
- import { Buffer } from 'node:buffer';
3
1
  import { randomUUID } from 'node:crypto';
4
2
  import { setInterval } from 'node:timers';
5
3
  import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
6
4
  import { config } from '../lib/core.js';
7
- import { toError } from '../lib/utils.js';
8
- import { createUnrefTimeout } from '../lib/utils.js';
5
+ import { decodeTaskCursor, encodeTaskCursor } from './cursor-codec.js';
6
+ import { TaskWaiterRegistry, waitForTerminalTask as waitForTerminalTaskWithDeadline, } from './waiters.js';
9
7
  const DEFAULT_TTL_MS = 60_000;
10
8
  const MIN_TTL_MS = 1_000;
11
9
  const MAX_TTL_MS = 86_400_000;
@@ -13,7 +11,6 @@ const DEFAULT_POLL_INTERVAL_MS = 1_000;
13
11
  const DEFAULT_OWNER_KEY = 'default';
14
12
  const DEFAULT_PAGE_SIZE = 50;
15
13
  const CLEANUP_INTERVAL_MS = 60_000;
16
- const MAX_CURSOR_LENGTH = 256;
17
14
  const RESULT_DELIVERY_GRACE_MS = 10_000;
18
15
  const TASK_STATUS_VALUES = new Set([
19
16
  'working',
@@ -59,7 +56,7 @@ function normalizeTaskTtl(ttl) {
59
56
  class TaskManager {
60
57
  tasks = new Map();
61
58
  ownerCounts = new Map();
62
- waiters = new Map();
59
+ waiters = new TaskWaiterRegistry(isTerminalStatus);
63
60
  cleanupInterval = null;
64
61
  ensureCleanupLoop() {
65
62
  if (this.cleanupInterval)
@@ -259,116 +256,27 @@ class TaskManager {
259
256
  resolveAnchorTaskId(cursor) {
260
257
  if (cursor === undefined)
261
258
  return null;
262
- const decoded = this.decodeCursor(cursor);
259
+ const decoded = decodeTaskCursor(cursor);
263
260
  if (decoded === null) {
264
261
  throw new McpError(ErrorCode.InvalidParams, 'Invalid cursor');
265
262
  }
266
263
  return decoded.anchorTaskId;
267
264
  }
268
- addWaiter(taskId, waiter) {
269
- let set = this.waiters.get(taskId);
270
- if (!set) {
271
- set = new Set();
272
- this.waiters.set(taskId, set);
273
- }
274
- set.add(waiter);
275
- }
276
- removeWaiter(taskId, waiter) {
277
- if (!waiter)
278
- return;
279
- const set = this.waiters.get(taskId);
280
- if (!set)
281
- return;
282
- set.delete(waiter);
283
- if (set.size === 0) {
284
- this.waiters.delete(taskId);
285
- }
286
- }
287
265
  async waitForTerminalTask(taskId, ownerKey, signal) {
288
- const task = this.lookupActiveTask(taskId, ownerKey);
289
- if (!task)
290
- return undefined;
291
- if (isTerminalStatus(task.status))
292
- return task;
293
- // isExpired() above guarantees task.ttl has not elapsed; compute deadlineMs
294
- // for the promise-based timeout below.
295
- const deadlineMs = task._createdAtMs + task.ttl;
296
- return new Promise((resolve, reject) => {
297
- // Bind resolve/reject to the AsyncLocalStorage context of the caller, so that any context values (e.g. requestId) are preserved when we later call them from a different tick.
298
- const resolveInContext = AsyncLocalStorage.bind((value) => {
299
- resolve(value);
300
- });
301
- const rejectInContext = AsyncLocalStorage.bind((error) => {
302
- reject(toError(error));
303
- });
304
- let settled = false;
305
- let waiter = null;
306
- let deadlineTimeout;
307
- const cleanup = () => {
308
- if (deadlineTimeout) {
309
- deadlineTimeout.cancel();
310
- deadlineTimeout = undefined;
311
- }
312
- if (signal) {
313
- signal.removeEventListener('abort', onAbort);
314
- }
315
- };
316
- const settleOnce = (fn) => {
317
- if (settled)
318
- return;
319
- settled = true;
320
- fn();
321
- };
322
- const onAbort = () => {
323
- settleOnce(() => {
324
- cleanup();
325
- this.removeWaiter(taskId, waiter);
326
- rejectInContext(new McpError(ErrorCode.ConnectionClosed, 'Request was cancelled'));
327
- });
328
- };
329
- waiter = (updated) => {
330
- settleOnce(() => {
331
- cleanup();
332
- if (updated.ownerKey !== ownerKey) {
333
- resolveInContext(undefined);
334
- return;
335
- }
336
- resolveInContext(updated);
337
- });
338
- };
339
- if (signal?.aborted) {
340
- onAbort();
341
- return;
342
- }
343
- this.addWaiter(taskId, waiter);
344
- if (signal) {
345
- signal.addEventListener('abort', onAbort, { once: true });
346
- }
347
- const timeoutMs = Math.max(0, deadlineMs - Date.now());
348
- deadlineTimeout = createUnrefTimeout(timeoutMs, { timeout: true });
349
- void deadlineTimeout.promise
350
- .then(() => {
351
- settleOnce(() => {
352
- cleanup();
353
- this.removeWaiter(taskId, waiter);
354
- this.removeTask(taskId);
355
- rejectInContext(new McpError(ErrorCode.InvalidParams, 'Task expired', {
356
- taskId,
357
- }));
358
- });
359
- })
360
- .catch(rejectInContext);
266
+ return waitForTerminalTaskWithDeadline({
267
+ taskId,
268
+ ownerKey,
269
+ ...(signal ? { signal } : {}),
270
+ lookupTask: (currentTaskId, currentOwnerKey) => this.lookupActiveTask(currentTaskId, currentOwnerKey),
271
+ removeTask: (currentTaskId) => {
272
+ this.removeTask(currentTaskId);
273
+ },
274
+ registry: this.waiters,
275
+ isTerminalStatus,
361
276
  });
362
277
  }
363
278
  notifyWaiters(task) {
364
- if (!isTerminalStatus(task.status))
365
- return;
366
- const waiters = this.waiters.get(task.taskId);
367
- if (!waiters)
368
- return;
369
- this.waiters.delete(task.taskId);
370
- for (const waiter of waiters)
371
- waiter(task);
279
+ this.waiters.notify(task);
372
280
  }
373
281
  isExpired(task, now = Date.now()) {
374
282
  return now - task._createdAtMs > task.ttl;
@@ -389,47 +297,11 @@ class TaskManager {
389
297
  this.maybeUpdateLastUpdatedAt(task);
390
298
  }
391
299
  }
392
- encodeCursor(taskId) {
393
- // Base64url-encode the taskId to produce a compact opaque cursor string.
394
- // The taskId is a UUID, which is 36 ASCII chars, so the resulting cursor will be 48 chars (36 * 4/3) plus padding if any.
395
- return Buffer.from(taskId, 'utf8').toString('base64url');
396
- }
397
- decodeCursor(cursor) {
398
- try {
399
- if (!isValidBase64UrlCursor(cursor))
400
- return null;
401
- const decoded = Buffer.from(cursor, 'base64url').toString('utf8');
402
- // Basic sanity: non-empty and plausible taskId length (UUIDs are 36 chars)
403
- if (!decoded || decoded.length > 128)
404
- return null;
405
- return { anchorTaskId: decoded };
406
- }
407
- catch {
408
- return null;
409
- }
410
- }
411
300
  resolveNextCursor(page, hasMore) {
412
301
  if (!hasMore)
413
302
  return undefined;
414
303
  const lastTask = page.at(-1);
415
- return lastTask ? this.encodeCursor(lastTask.taskId) : undefined;
416
- }
417
- }
418
- function isValidBase64UrlCursor(cursor) {
419
- if (!cursor)
420
- return false;
421
- if (cursor.length > MAX_CURSOR_LENGTH)
422
- return false;
423
- if (!/^[A-Za-z0-9_-]+={0,2}$/u.test(cursor))
424
- return false;
425
- const firstPaddingIndex = cursor.indexOf('=');
426
- if (firstPaddingIndex !== -1) {
427
- for (let i = firstPaddingIndex; i < cursor.length; i += 1) {
428
- if (cursor[i] !== '=')
429
- return false;
430
- }
431
- return cursor.length % 4 === 0;
304
+ return lastTask ? encodeTaskCursor(lastTask.taskId) : undefined;
432
305
  }
433
- return cursor.length % 4 !== 1;
434
306
  }
435
307
  export const taskManager = new TaskManager();
@@ -1,5 +1,7 @@
1
1
  import type { ServerResult } from '@modelcontextprotocol/sdk/types.js';
2
2
  import type { ProgressNotification } from '../lib/progress.js';
3
+ import type { ToolHandlerExtra } from '../lib/progress.js';
4
+ import { type ToolCallRequestMeta } from './call-contract.js';
3
5
  interface HandlerExtra {
4
6
  sessionId?: string;
5
7
  authInfo?: {
@@ -9,14 +11,17 @@ interface HandlerExtra {
9
11
  signal?: AbortSignal;
10
12
  requestId?: string | number;
11
13
  sendNotification?: (notification: ProgressNotification) => Promise<void>;
14
+ requestInfo?: unknown;
12
15
  }
13
- export interface ToolCallContext {
16
+ export interface ToolExecutionContext {
14
17
  ownerKey: string;
15
18
  sessionId?: string;
16
19
  signal?: AbortSignal;
17
20
  requestId?: string | number;
18
21
  sendNotification?: (notification: ProgressNotification) => Promise<void>;
22
+ requestMeta?: ToolCallRequestMeta;
19
23
  }
24
+ export type ToolCallContext = ToolExecutionContext;
20
25
  /** Strip keys whose value is `undefined`, returning an object with only the
21
26
  * present keys. Return type correctly omits the `undefined` union so the result
22
27
  * is compatible with `exactOptionalPropertyTypes`. */
@@ -26,7 +31,12 @@ type Compacted<T extends object> = {
26
31
  export declare function compact<T extends object>(obj: T): Compacted<T>;
27
32
  export declare function parseHandlerExtra(extra: unknown): HandlerExtra | undefined;
28
33
  export declare function resolveTaskOwnerKey(extra?: HandlerExtra): string;
29
- export declare function resolveToolCallContext(extra?: HandlerExtra): ToolCallContext;
34
+ export declare function resolveRequestIdFromExtra(extra: unknown): string | undefined;
35
+ export declare function resolveSessionIdFromExtra(extra: unknown): string | undefined;
36
+ export declare function resolveToolExecutionContext(extra?: HandlerExtra, requestMeta?: ToolCallRequestMeta): ToolExecutionContext;
37
+ export declare function resolveToolCallContext(extra?: HandlerExtra, requestMeta?: ToolCallRequestMeta): ToolCallContext;
38
+ export declare function buildToolHandlerExtra(context: ToolExecutionContext, requestMeta?: ToolCallRequestMeta): ToolHandlerExtra;
39
+ export declare function withRequestContextIfMissing<TParams, TResult, TExtra = unknown>(handler: (params: TParams, extra?: TExtra) => Promise<TResult>): (params: TParams, extra?: TExtra) => Promise<TResult>;
30
40
  export declare function isServerResult(value: unknown): value is ServerResult;
31
41
  export declare function tryReadToolStructuredError(value: unknown): string | undefined;
32
42
  export {};
@@ -1 +1 @@
1
- {"version":3,"file":"owner.d.ts","sourceRoot":"","sources":["../../src/tasks/owner.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAGvE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAO/D,UAAU,YAAY;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACjD,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,gBAAgB,CAAC,EAAE,CAAC,YAAY,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1E;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IAC5B,gBAAgB,CAAC,EAAE,CAAC,YAAY,EAAE,oBAAoB,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1E;AAED;;sDAEsD;AACtD,KAAK,SAAS,CAAC,CAAC,SAAS,MAAM,IAAI;KAChC,CAAC,IAAI,MAAM,CAAC,IAAI,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC,SAAS,KAAK,GACnD,KAAK,GACL,CAAC,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,SAAS,CAAC;CAClC,CAAC;AAEF,wBAAgB,OAAO,CAAC,CAAC,SAAS,MAAM,EAAE,GAAG,EAAE,CAAC,GAAG,SAAS,CAAC,CAAC,CAAC,CAQ9D;AA2BD,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,OAAO,GAAG,YAAY,GAAG,SAAS,CAyB1E;AAED,wBAAgB,mBAAmB,CAAC,KAAK,CAAC,EAAE,YAAY,GAAG,MAAM,CAMhE;AAED,wBAAgB,sBAAsB,CAAC,KAAK,CAAC,EAAE,YAAY,GAAG,eAAe,CAQ5E;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,YAAY,CAIpE;AAWD,wBAAgB,0BAA0B,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,GAAG,SAAS,CAgB7E"}
1
+ {"version":3,"file":"owner.d.ts","sourceRoot":"","sources":["../../src/tasks/owner.ts"],"names":[],"mappings":"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"}
@@ -1,6 +1,8 @@
1
- import { createHash } from 'node:crypto';
1
+ import { createHash, randomUUID } from 'node:crypto';
2
2
  import { z } from 'zod';
3
- import { isObject } from '../lib/utils.js';
3
+ import { getRequestId, runWithRequestContext } from '../lib/core.js';
4
+ import { isObject, readNestedRecord } from '../lib/utils.js';
5
+ import { sanitizeToolCallMeta, } from './call-contract.js';
4
6
  export function compact(obj) {
5
7
  const result = {};
6
8
  for (const key of Object.keys(obj)) {
@@ -60,15 +62,62 @@ export function resolveTaskOwnerKey(extra) {
60
62
  return `token:${createHash('sha256').update(extra.authInfo.token).digest('hex')}`;
61
63
  return 'default';
62
64
  }
63
- export function resolveToolCallContext(extra) {
65
+ export function resolveRequestIdFromExtra(extra) {
66
+ if (!isObject(extra))
67
+ return undefined;
68
+ const { requestId } = extra;
69
+ if (typeof requestId === 'string')
70
+ return requestId;
71
+ if (typeof requestId === 'number')
72
+ return String(requestId);
73
+ return undefined;
74
+ }
75
+ export function resolveSessionIdFromExtra(extra) {
76
+ if (!isObject(extra))
77
+ return undefined;
78
+ const { sessionId } = extra;
79
+ if (typeof sessionId === 'string')
80
+ return sessionId;
81
+ const headers = readNestedRecord(extra, ['requestInfo', 'headers']);
82
+ const headerValue = headers ? headers['mcp-session-id'] : undefined;
83
+ return typeof headerValue === 'string' ? headerValue : undefined;
84
+ }
85
+ export function resolveToolExecutionContext(extra, requestMeta) {
64
86
  return compact({
65
87
  ownerKey: resolveTaskOwnerKey(extra),
66
88
  sessionId: extra?.sessionId,
67
89
  signal: extra?.signal,
68
90
  requestId: extra?.requestId,
69
91
  sendNotification: extra?.sendNotification,
92
+ requestMeta: sanitizeToolCallMeta(requestMeta),
70
93
  });
71
94
  }
95
+ export function resolveToolCallContext(extra, requestMeta) {
96
+ return resolveToolExecutionContext(extra, requestMeta);
97
+ }
98
+ export function buildToolHandlerExtra(context, requestMeta) {
99
+ return compact({
100
+ signal: context.signal,
101
+ requestId: context.requestId,
102
+ sendNotification: context.sendNotification,
103
+ _meta: sanitizeToolCallMeta(requestMeta ?? context.requestMeta),
104
+ });
105
+ }
106
+ export function withRequestContextIfMissing(handler) {
107
+ return async (params, extra) => {
108
+ const existingRequestId = getRequestId();
109
+ if (existingRequestId) {
110
+ return handler(params, extra);
111
+ }
112
+ const derivedRequestId = resolveRequestIdFromExtra(extra) ?? randomUUID();
113
+ const derivedSessionId = resolveSessionIdFromExtra(extra);
114
+ return runWithRequestContext({
115
+ requestId: derivedRequestId,
116
+ operationId: derivedRequestId,
117
+ ...(derivedSessionId ? { sessionId: derivedSessionId } : {}),
118
+ }, () => handler(params, extra));
119
+ };
120
+ }
72
121
  export function isServerResult(value) {
73
122
  return (isObject(value) && Array.isArray(value.content));
74
123
  }
@@ -4,6 +4,7 @@ export interface TaskCapableToolDescriptor<TArgs = unknown> {
4
4
  name: string;
5
5
  parseArguments: (args: unknown) => TArgs;
6
6
  execute: (args: TArgs, extra?: ToolHandlerExtra) => Promise<ServerResult>;
7
+ getCompletionStatusMessage?: (result: ServerResult) => string | undefined;
7
8
  }
8
9
  export declare function registerTaskCapableTool<TArgs>(descriptor: TaskCapableToolDescriptor<TArgs>): void;
9
10
  export declare function unregisterTaskCapableTool(name: string): void;
@@ -1 +1 @@
1
- {"version":3,"file":"tool-registry.d.ts","sourceRoot":"","sources":["../../src/tasks/tool-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAEvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,MAAM,WAAW,yBAAyB,CAAC,KAAK,GAAG,OAAO;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,KAAK,CAAC;IACzC,OAAO,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;CAC3E;AAID,wBAAgB,uBAAuB,CAAC,KAAK,EAC3C,UAAU,EAAE,yBAAyB,CAAC,KAAK,CAAC,GAC3C,IAAI,CAKN;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAE5D;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,GACX,yBAAyB,GAAG,SAAS,CAEvC;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,wBAAgB,6BAA6B,IAAI,OAAO,CAEvD"}
1
+ {"version":3,"file":"tool-registry.d.ts","sourceRoot":"","sources":["../../src/tasks/tool-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAEvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAE3D,MAAM,WAAW,yBAAyB,CAAC,KAAK,GAAG,OAAO;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,EAAE,CAAC,IAAI,EAAE,OAAO,KAAK,KAAK,CAAC;IACzC,OAAO,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,gBAAgB,KAAK,OAAO,CAAC,YAAY,CAAC,CAAC;IAC1E,0BAA0B,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,MAAM,GAAG,SAAS,CAAC;CAC3E;AAID,wBAAgB,uBAAuB,CAAC,KAAK,EAC3C,UAAU,EAAE,yBAAyB,CAAC,KAAK,CAAC,GAC3C,IAAI,CAKN;AAED,wBAAgB,yBAAyB,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAE5D;AAED,wBAAgB,kBAAkB,CAChC,IAAI,EAAE,MAAM,GACX,yBAAyB,GAAG,SAAS,CAEvC;AAED,wBAAgB,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAExD;AAED,wBAAgB,6BAA6B,IAAI,OAAO,CAEvD"}
@@ -0,0 +1,27 @@
1
+ interface WaitableTask {
2
+ taskId: string;
3
+ ownerKey: string;
4
+ status: string;
5
+ ttl: number;
6
+ _createdAtMs: number;
7
+ }
8
+ type TaskWaiter<TTask extends WaitableTask> = (task: TTask) => void;
9
+ export declare class TaskWaiterRegistry<TTask extends WaitableTask> {
10
+ private readonly isTerminalStatus;
11
+ private waiters;
12
+ constructor(isTerminalStatus: (status: TTask['status']) => boolean);
13
+ add(taskId: string, waiter: TaskWaiter<TTask>): void;
14
+ remove(taskId: string, waiter: TaskWaiter<TTask> | null): void;
15
+ notify(task: TTask): void;
16
+ }
17
+ export declare function waitForTerminalTask<TTask extends WaitableTask>(options: {
18
+ taskId: string;
19
+ ownerKey: string;
20
+ signal?: AbortSignal;
21
+ lookupTask: (taskId: string, ownerKey: string) => TTask | undefined;
22
+ removeTask: (taskId: string) => void;
23
+ registry: TaskWaiterRegistry<TTask>;
24
+ isTerminalStatus: (status: TTask['status']) => boolean;
25
+ }): Promise<TTask | undefined>;
26
+ export {};
27
+ //# sourceMappingURL=waiters.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"waiters.d.ts","sourceRoot":"","sources":["../../src/tasks/waiters.ts"],"names":[],"mappings":"AAOA,UAAU,YAAY;IACpB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,KAAK,UAAU,CAAC,KAAK,SAAS,YAAY,IAAI,CAAC,IAAI,EAAE,KAAK,KAAK,IAAI,CAAC;AAEpE,qBAAa,kBAAkB,CAAC,KAAK,SAAS,YAAY;IAItD,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAHnC,OAAO,CAAC,OAAO,CAA6C;gBAGzC,gBAAgB,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,OAAO;IAGzE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,GAAG,IAAI;IASpD,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,KAAK,CAAC,GAAG,IAAI,GAAG,IAAI;IAY9D,MAAM,CAAC,IAAI,EAAE,KAAK,GAAG,IAAI;CAS1B;AAED,wBAAsB,mBAAmB,CAAC,KAAK,SAAS,YAAY,EAAE,OAAO,EAAE;IAC7E,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,WAAW,CAAC;IACrB,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,KAAK,GAAG,SAAS,CAAC;IACpE,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACrC,QAAQ,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAC;IACpC,gBAAgB,EAAE,CAAC,MAAM,EAAE,KAAK,CAAC,QAAQ,CAAC,KAAK,OAAO,CAAC;CACxD,GAAG,OAAO,CAAC,KAAK,GAAG,SAAS,CAAC,CAwF7B"}
@@ -0,0 +1,113 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
2
+ import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
3
+ import { toError } from '../lib/utils.js';
4
+ import { createUnrefTimeout } from '../lib/utils.js';
5
+ export class TaskWaiterRegistry {
6
+ isTerminalStatus;
7
+ waiters = new Map();
8
+ constructor(isTerminalStatus) {
9
+ this.isTerminalStatus = isTerminalStatus;
10
+ }
11
+ add(taskId, waiter) {
12
+ let set = this.waiters.get(taskId);
13
+ if (!set) {
14
+ set = new Set();
15
+ this.waiters.set(taskId, set);
16
+ }
17
+ set.add(waiter);
18
+ }
19
+ remove(taskId, waiter) {
20
+ if (!waiter)
21
+ return;
22
+ const set = this.waiters.get(taskId);
23
+ if (!set)
24
+ return;
25
+ set.delete(waiter);
26
+ if (set.size === 0) {
27
+ this.waiters.delete(taskId);
28
+ }
29
+ }
30
+ notify(task) {
31
+ if (!this.isTerminalStatus(task.status))
32
+ return;
33
+ const waiters = this.waiters.get(task.taskId);
34
+ if (!waiters)
35
+ return;
36
+ this.waiters.delete(task.taskId);
37
+ for (const waiter of waiters)
38
+ waiter(task);
39
+ }
40
+ }
41
+ export async function waitForTerminalTask(options) {
42
+ const task = options.lookupTask(options.taskId, options.ownerKey);
43
+ if (!task)
44
+ return undefined;
45
+ if (options.isTerminalStatus(task.status))
46
+ return task;
47
+ const deadlineMs = task._createdAtMs + task.ttl;
48
+ return new Promise((resolve, reject) => {
49
+ const resolveInContext = AsyncLocalStorage.bind((value) => {
50
+ resolve(value);
51
+ });
52
+ const rejectInContext = AsyncLocalStorage.bind((error) => {
53
+ reject(toError(error));
54
+ });
55
+ let settled = false;
56
+ let waiter = null;
57
+ let deadlineTimeout;
58
+ const cleanup = () => {
59
+ if (deadlineTimeout) {
60
+ deadlineTimeout.cancel();
61
+ deadlineTimeout = undefined;
62
+ }
63
+ if (options.signal) {
64
+ options.signal.removeEventListener('abort', onAbort);
65
+ }
66
+ };
67
+ const settleOnce = (fn) => {
68
+ if (settled)
69
+ return;
70
+ settled = true;
71
+ fn();
72
+ };
73
+ const onAbort = () => {
74
+ settleOnce(() => {
75
+ cleanup();
76
+ options.registry.remove(options.taskId, waiter);
77
+ rejectInContext(new McpError(ErrorCode.ConnectionClosed, 'Request was cancelled'));
78
+ });
79
+ };
80
+ waiter = (updated) => {
81
+ settleOnce(() => {
82
+ cleanup();
83
+ if (updated.ownerKey !== options.ownerKey) {
84
+ resolveInContext(undefined);
85
+ return;
86
+ }
87
+ resolveInContext(updated);
88
+ });
89
+ };
90
+ if (options.signal?.aborted) {
91
+ onAbort();
92
+ return;
93
+ }
94
+ options.registry.add(options.taskId, waiter);
95
+ if (options.signal) {
96
+ options.signal.addEventListener('abort', onAbort, { once: true });
97
+ }
98
+ const timeoutMs = Math.max(0, deadlineMs - Date.now());
99
+ deadlineTimeout = createUnrefTimeout(timeoutMs, { timeout: true });
100
+ void deadlineTimeout.promise
101
+ .then(() => {
102
+ settleOnce(() => {
103
+ cleanup();
104
+ options.registry.remove(options.taskId, waiter);
105
+ options.removeTask(options.taskId);
106
+ rejectInContext(new McpError(ErrorCode.InvalidParams, 'Task expired', {
107
+ taskId: options.taskId,
108
+ }));
109
+ });
110
+ })
111
+ .catch(rejectInContext);
112
+ });
113
+ }
@@ -0,0 +1,17 @@
1
+ import type { ServerResult } from '@modelcontextprotocol/sdk/types.js';
2
+ import type { SharedFetchStage } from '../lib/fetch-pipeline.js';
3
+ import type { ProgressReporter } from '../lib/progress.js';
4
+ export declare function buildFetchSuccessSummary(contentSize: number): string;
5
+ export declare function getFetchCompletionStatusMessage(result: ServerResult): string | undefined;
6
+ export declare class FetchUrlProgressPlan {
7
+ private readonly reporter;
8
+ private readonly context;
9
+ private path;
10
+ constructor(reporter: ProgressReporter, context: string);
11
+ reportStart(): void;
12
+ reportStage(stage: SharedFetchStage): void;
13
+ reportSuccess(contentSize: number): void;
14
+ reportFailure(cancelled: boolean): void;
15
+ private mapStage;
16
+ }
17
+ //# sourceMappingURL=fetch-url-progress.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"fetch-url-progress.d.ts","sourceRoot":"","sources":["../../src/tools/fetch-url-progress.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oCAAoC,CAAC;AAEvE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,0BAA0B,CAAC;AACjE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAW3D,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM,CAEpE;AAED,wBAAgB,+BAA+B,CAC7C,MAAM,EAAE,YAAY,GACnB,MAAM,GAAG,SAAS,CAUpB;AAED,qBAAa,oBAAoB;IAI7B,OAAO,CAAC,QAAQ,CAAC,QAAQ;IACzB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAJ1B,OAAO,CAAC,IAAI,CAAwB;gBAGjB,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,MAAM;IAGlC,WAAW,IAAI,IAAI;IAInB,WAAW,CAAC,KAAK,EAAE,gBAAgB,GAAG,IAAI;IAM1C,aAAa,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI;IAIxC,aAAa,CAAC,SAAS,EAAE,OAAO,GAAG,IAAI;IAIvC,OAAO,CAAC,QAAQ;CAiCjB"}
@@ -0,0 +1,78 @@
1
+ import { isObject } from '../lib/utils.js';
2
+ function formatContentSize(contentSize) {
3
+ if (contentSize < 1000)
4
+ return `${contentSize} chars`;
5
+ if (contentSize < 1_000_000)
6
+ return `${(contentSize / 1024).toFixed(1)} KB`;
7
+ return `${(contentSize / (1024 * 1024)).toFixed(1)} MB`;
8
+ }
9
+ export function buildFetchSuccessSummary(contentSize) {
10
+ return `Done — ${formatContentSize(contentSize)}`;
11
+ }
12
+ export function getFetchCompletionStatusMessage(result) {
13
+ if (!isObject(result))
14
+ return undefined;
15
+ const { structuredContent } = result;
16
+ if (!isObject(structuredContent))
17
+ return undefined;
18
+ const { contentSize } = structuredContent;
19
+ return typeof contentSize === 'number'
20
+ ? buildFetchSuccessSummary(contentSize)
21
+ : undefined;
22
+ }
23
+ export class FetchUrlProgressPlan {
24
+ reporter;
25
+ context;
26
+ path = 'unknown';
27
+ constructor(reporter, context) {
28
+ this.reporter = reporter;
29
+ this.context = context;
30
+ }
31
+ reportStart() {
32
+ this.reporter.report(1, 'Preparing request');
33
+ }
34
+ reportStage(stage) {
35
+ const mapped = this.mapStage(stage);
36
+ if (!mapped)
37
+ return;
38
+ this.reporter.report(mapped.step, mapped.message);
39
+ }
40
+ reportSuccess(contentSize) {
41
+ this.reporter.report(8, buildFetchSuccessSummary(contentSize));
42
+ }
43
+ reportFailure(cancelled) {
44
+ this.reporter.report(8, cancelled ? 'Cancelled' : 'Failed');
45
+ }
46
+ mapStage(stage) {
47
+ switch (stage) {
48
+ case 'resolve_url':
49
+ return { step: 2, message: 'Resolving URL' };
50
+ case 'check_cache':
51
+ return { step: 3, message: 'Checking cache' };
52
+ case 'cache_hit':
53
+ this.path = 'cache_hit';
54
+ return { step: 4, message: 'Loaded from cache' };
55
+ case 'cache_restore':
56
+ this.path = 'cache_hit';
57
+ return { step: 5, message: 'Restoring cached content' };
58
+ case 'fetch_remote':
59
+ this.path = 'cache_miss';
60
+ return { step: 4, message: `Fetching ${this.context}` };
61
+ case 'response_ready':
62
+ this.path = 'cache_miss';
63
+ return { step: 5, message: 'Received response' };
64
+ case 'transform_start':
65
+ this.path = 'cache_miss';
66
+ return { step: 6, message: 'Parsing HTML -> Markdown' };
67
+ case 'prepare_output':
68
+ return {
69
+ step: this.path === 'cache_miss' ? 7 : 6,
70
+ message: 'Preparing output',
71
+ };
72
+ case 'finalize_output':
73
+ if (this.path === 'cache_miss')
74
+ return undefined;
75
+ return { step: 7, message: 'Finalizing output' };
76
+ }
77
+ }
78
+ }
@@ -12,14 +12,10 @@ interface ToolResponseBase {
12
12
  }
13
13
  export declare const FETCH_URL_TOOL_NAME = "fetch-url";
14
14
  export declare function fetchUrlToolHandler(input: FetchUrlInput, extra?: ToolHandlerExtra): Promise<ToolResponseBase>;
15
- /**
16
- * Stdio-path guard: ensures a request context (requestId, sessionId) is set
17
- * in AsyncLocalStorage before invoking the handler. On the HTTP path the SDK
18
- * populates `extra.requestId`/`extra.requestInfo`, so this is a no-op there.
19
- * On the stdio path there is no SDK-provided context, so we derive one from
20
- * the extra fields or generate a fresh UUID.
21
- */
22
- export declare function withRequestContextIfMissing<TParams, TResult, TExtra = unknown>(handler: (params: TParams, extra?: TExtra) => Promise<TResult>): (params: TParams, extra?: TExtra) => Promise<TResult>;
23
- export declare function registerTools(server: McpServer): void;
15
+ type TaskSupport = 'optional' | 'forbidden';
16
+ export interface ToolRegistrationControls {
17
+ setTaskSupport: (support: TaskSupport) => void;
18
+ }
19
+ export declare function registerTools(server: McpServer): ToolRegistrationControls;
24
20
  export {};
25
21
  //# sourceMappingURL=fetch-url.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"fetch-url.d.ts","sourceRoot":"","sources":["../../src/tools/fetch-url.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EACV,YAAY,EAEb,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAyBxB,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EACL,mBAAmB,EAIpB,MAAM,eAAe,CAAC;AAMvB,KAAK,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEzD,UAAU,gBAAgB;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACxD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,eAAO,MAAM,mBAAmB,cAAc,CAAC;AA4N/C,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,aAAa,EACpB,KAAK,CAAC,EAAE,gBAAgB,GACvB,OAAO,CAAC,gBAAgB,CAAC,CAQ3B;AAsBD;;;;;;GAMG;AACH,wBAAgB,2BAA2B,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,EAC5E,OAAO,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,GAC7D,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,MAAM,KAAK,OAAO,CAAC,OAAO,CAAC,CAmBvD;AAwBD,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,IAAI,CAqCrD"}
1
+ {"version":3,"file":"fetch-url.d.ts","sourceRoot":"","sources":["../../src/tools/fetch-url.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACzE,OAAO,KAAK,EACV,YAAY,EAEb,MAAM,oCAAoC,CAAC;AAE5C,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAgBxB,OAAO,EAEL,KAAK,gBAAgB,EACtB,MAAM,oBAAoB,CAAC;AAI5B,OAAO,EACL,mBAAmB,EAIpB,MAAM,eAAe,CAAC;AAYvB,KAAK,aAAa,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,mBAAmB,CAAC,CAAC;AAEzD,UAAU,gBAAgB;IACxB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;IACvB,OAAO,EAAE,YAAY,EAAE,CAAC;IACxB,iBAAiB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC;IACxD,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,eAAO,MAAM,mBAAmB,cAAc,CAAC;AAsL/C,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,aAAa,EACpB,KAAK,CAAC,EAAE,gBAAgB,GACvB,OAAO,CAAC,gBAAgB,CAAC,CAQ3B;AAqBD,KAAK,WAAW,GAAG,UAAU,GAAG,WAAW,CAAC;AAE5C,MAAM,WAAW,wBAAwB;IACvC,cAAc,EAAE,CAAC,OAAO,EAAE,WAAW,KAAK,IAAI,CAAC;CAChD;AA2BD,wBAAgB,aAAa,CAAC,MAAM,EAAE,SAAS,GAAG,wBAAwB,CAsCzE"}