@j0hanz/fetch-url-mcp 1.3.1 → 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 (205) hide show
  1. package/README.md +24 -21
  2. package/dist/cli.d.ts +3 -3
  3. package/dist/cli.js +15 -8
  4. package/dist/http/auth.d.ts +6 -6
  5. package/dist/http/auth.js +78 -23
  6. package/dist/http/health.d.ts +1 -2
  7. package/dist/http/health.js +7 -18
  8. package/dist/http/helpers.d.ts +3 -11
  9. package/dist/http/helpers.js +28 -26
  10. package/dist/http/native.d.ts +0 -1
  11. package/dist/http/native.js +63 -41
  12. package/dist/http/rate-limit.d.ts +2 -2
  13. package/dist/http/rate-limit.js +11 -16
  14. package/dist/index.d.ts +0 -1
  15. package/dist/index.js +17 -20
  16. package/dist/{markdown-cleanup.d.ts → lib/content.d.ts} +4 -2
  17. package/dist/lib/content.js +1356 -0
  18. package/dist/lib/core.d.ts +253 -0
  19. package/dist/lib/core.js +1228 -0
  20. package/dist/{tool-pipeline.d.ts → lib/fetch-pipeline.d.ts} +1 -3
  21. package/dist/{tool-pipeline.js → lib/fetch-pipeline.js} +18 -44
  22. package/dist/{fetch.d.ts → lib/http.d.ts} +7 -9
  23. package/dist/{fetch.js → lib/http.js} +721 -1004
  24. package/dist/lib/mcp-tools.d.ts +28 -0
  25. package/dist/lib/mcp-tools.js +107 -0
  26. package/dist/{tool-progress.d.ts → lib/progress.d.ts} +0 -2
  27. package/dist/{tool-progress.js → lib/progress.js} +9 -14
  28. package/dist/lib/task-handlers.d.ts +5 -0
  29. package/dist/{mcp.js → lib/task-handlers.js} +95 -31
  30. package/dist/lib/url.d.ts +70 -0
  31. package/dist/lib/url.js +686 -0
  32. package/dist/lib/utils.d.ts +58 -0
  33. package/dist/lib/utils.js +304 -0
  34. package/dist/{prompts.d.ts → prompts/index.d.ts} +0 -1
  35. package/dist/{prompts.js → prompts/index.js} +1 -2
  36. package/dist/{resources.d.ts → resources/index.d.ts} +0 -1
  37. package/dist/{resources.js → resources/index.js} +87 -64
  38. package/dist/{instructions.d.ts → resources/instructions.d.ts} +0 -1
  39. package/dist/{instructions.js → resources/instructions.js} +5 -3
  40. package/dist/schemas/inputs.d.ts +7 -0
  41. package/dist/schemas/inputs.js +24 -0
  42. package/dist/schemas/outputs.d.ts +23 -0
  43. package/dist/schemas/outputs.js +77 -0
  44. package/dist/server.d.ts +0 -1
  45. package/dist/server.js +26 -25
  46. package/dist/tasks/execution.d.ts +0 -1
  47. package/dist/tasks/execution.js +106 -70
  48. package/dist/tasks/manager.d.ts +11 -3
  49. package/dist/tasks/manager.js +97 -73
  50. package/dist/tasks/owner.d.ts +3 -3
  51. package/dist/tasks/owner.js +2 -2
  52. package/dist/tasks/tool-registry.d.ts +11 -0
  53. package/dist/tasks/tool-registry.js +13 -0
  54. package/dist/tools/fetch-url.d.ts +28 -0
  55. package/dist/{tools.js → tools/fetch-url.js} +95 -147
  56. package/dist/tools/index.d.ts +2 -0
  57. package/dist/tools/index.js +4 -0
  58. package/dist/transform/html-translators.d.ts +1 -0
  59. package/dist/transform/html-translators.js +454 -0
  60. package/dist/transform/metadata.d.ts +4 -0
  61. package/dist/transform/metadata.js +183 -0
  62. package/dist/transform/transform.d.ts +0 -1
  63. package/dist/transform/transform.js +44 -679
  64. package/dist/transform/types.d.ts +9 -12
  65. package/dist/transform/types.js +0 -1
  66. package/dist/transform/worker-pool.d.ts +0 -1
  67. package/dist/transform/worker-pool.js +7 -16
  68. package/dist/transform/workers/shared.d.ts +7 -0
  69. package/dist/transform/workers/shared.js +130 -0
  70. package/dist/transform/workers/transform-child.d.ts +0 -1
  71. package/dist/transform/workers/transform-child.js +5 -135
  72. package/dist/transform/workers/transform-worker.d.ts +0 -1
  73. package/dist/transform/workers/transform-worker.js +7 -128
  74. package/package.json +11 -7
  75. package/dist/cache.d.ts +0 -54
  76. package/dist/cache.d.ts.map +0 -1
  77. package/dist/cache.js +0 -261
  78. package/dist/cache.js.map +0 -1
  79. package/dist/cli.d.ts.map +0 -1
  80. package/dist/cli.js.map +0 -1
  81. package/dist/config.d.ts +0 -141
  82. package/dist/config.d.ts.map +0 -1
  83. package/dist/config.js +0 -473
  84. package/dist/config.js.map +0 -1
  85. package/dist/crypto.d.ts +0 -4
  86. package/dist/crypto.d.ts.map +0 -1
  87. package/dist/crypto.js +0 -56
  88. package/dist/crypto.js.map +0 -1
  89. package/dist/dom-noise-removal.d.ts +0 -2
  90. package/dist/dom-noise-removal.d.ts.map +0 -1
  91. package/dist/dom-noise-removal.js +0 -494
  92. package/dist/dom-noise-removal.js.map +0 -1
  93. package/dist/download.d.ts +0 -4
  94. package/dist/download.d.ts.map +0 -1
  95. package/dist/download.js +0 -106
  96. package/dist/download.js.map +0 -1
  97. package/dist/errors.d.ts +0 -11
  98. package/dist/errors.d.ts.map +0 -1
  99. package/dist/errors.js +0 -65
  100. package/dist/errors.js.map +0 -1
  101. package/dist/examples/mcp-fetch-url-client.js +0 -329
  102. package/dist/examples/mcp-fetch-url-client.js.map +0 -1
  103. package/dist/fetch-content.d.ts +0 -5
  104. package/dist/fetch-content.d.ts.map +0 -1
  105. package/dist/fetch-content.js +0 -164
  106. package/dist/fetch-content.js.map +0 -1
  107. package/dist/fetch-stream.d.ts +0 -5
  108. package/dist/fetch-stream.d.ts.map +0 -1
  109. package/dist/fetch-stream.js +0 -29
  110. package/dist/fetch-stream.js.map +0 -1
  111. package/dist/fetch.d.ts.map +0 -1
  112. package/dist/fetch.js.map +0 -1
  113. package/dist/host-normalization.d.ts +0 -2
  114. package/dist/host-normalization.d.ts.map +0 -1
  115. package/dist/host-normalization.js +0 -91
  116. package/dist/host-normalization.js.map +0 -1
  117. package/dist/http/auth.d.ts.map +0 -1
  118. package/dist/http/auth.js.map +0 -1
  119. package/dist/http/health.d.ts.map +0 -1
  120. package/dist/http/health.js.map +0 -1
  121. package/dist/http/helpers.d.ts.map +0 -1
  122. package/dist/http/helpers.js.map +0 -1
  123. package/dist/http/native.d.ts.map +0 -1
  124. package/dist/http/native.js.map +0 -1
  125. package/dist/http/rate-limit.d.ts.map +0 -1
  126. package/dist/http/rate-limit.js.map +0 -1
  127. package/dist/index.d.ts.map +0 -1
  128. package/dist/index.js.map +0 -1
  129. package/dist/instructions.d.ts.map +0 -1
  130. package/dist/instructions.js.map +0 -1
  131. package/dist/ip-blocklist.d.ts +0 -9
  132. package/dist/ip-blocklist.d.ts.map +0 -1
  133. package/dist/ip-blocklist.js +0 -79
  134. package/dist/ip-blocklist.js.map +0 -1
  135. package/dist/json.d.ts +0 -2
  136. package/dist/json.d.ts.map +0 -1
  137. package/dist/json.js +0 -45
  138. package/dist/json.js.map +0 -1
  139. package/dist/language-detection.d.ts +0 -3
  140. package/dist/language-detection.d.ts.map +0 -1
  141. package/dist/language-detection.js +0 -355
  142. package/dist/language-detection.js.map +0 -1
  143. package/dist/markdown-cleanup.d.ts.map +0 -1
  144. package/dist/markdown-cleanup.js +0 -534
  145. package/dist/markdown-cleanup.js.map +0 -1
  146. package/dist/mcp-validator.d.ts +0 -17
  147. package/dist/mcp-validator.d.ts.map +0 -1
  148. package/dist/mcp-validator.js +0 -45
  149. package/dist/mcp-validator.js.map +0 -1
  150. package/dist/mcp.d.ts +0 -4
  151. package/dist/mcp.d.ts.map +0 -1
  152. package/dist/mcp.js.map +0 -1
  153. package/dist/observability.d.ts +0 -23
  154. package/dist/observability.d.ts.map +0 -1
  155. package/dist/observability.js +0 -238
  156. package/dist/observability.js.map +0 -1
  157. package/dist/prompts.d.ts.map +0 -1
  158. package/dist/prompts.js.map +0 -1
  159. package/dist/resources.d.ts.map +0 -1
  160. package/dist/resources.js.map +0 -1
  161. package/dist/server-tuning.d.ts +0 -15
  162. package/dist/server-tuning.d.ts.map +0 -1
  163. package/dist/server-tuning.js +0 -49
  164. package/dist/server-tuning.js.map +0 -1
  165. package/dist/server.d.ts.map +0 -1
  166. package/dist/server.js.map +0 -1
  167. package/dist/session.d.ts +0 -42
  168. package/dist/session.d.ts.map +0 -1
  169. package/dist/session.js +0 -255
  170. package/dist/session.js.map +0 -1
  171. package/dist/tasks/execution.d.ts.map +0 -1
  172. package/dist/tasks/execution.js.map +0 -1
  173. package/dist/tasks/manager.d.ts.map +0 -1
  174. package/dist/tasks/manager.js.map +0 -1
  175. package/dist/tasks/owner.d.ts.map +0 -1
  176. package/dist/tasks/owner.js.map +0 -1
  177. package/dist/timer-utils.d.ts +0 -6
  178. package/dist/timer-utils.d.ts.map +0 -1
  179. package/dist/timer-utils.js +0 -27
  180. package/dist/timer-utils.js.map +0 -1
  181. package/dist/tool-errors.d.ts +0 -12
  182. package/dist/tool-errors.d.ts.map +0 -1
  183. package/dist/tool-errors.js +0 -55
  184. package/dist/tool-errors.js.map +0 -1
  185. package/dist/tool-pipeline.d.ts.map +0 -1
  186. package/dist/tool-pipeline.js.map +0 -1
  187. package/dist/tool-progress.d.ts.map +0 -1
  188. package/dist/tool-progress.js.map +0 -1
  189. package/dist/tools.d.ts +0 -54
  190. package/dist/tools.d.ts.map +0 -1
  191. package/dist/tools.js.map +0 -1
  192. package/dist/transform/transform.d.ts.map +0 -1
  193. package/dist/transform/transform.js.map +0 -1
  194. package/dist/transform/types.d.ts.map +0 -1
  195. package/dist/transform/types.js.map +0 -1
  196. package/dist/transform/worker-pool.d.ts.map +0 -1
  197. package/dist/transform/worker-pool.js.map +0 -1
  198. package/dist/transform/workers/transform-child.d.ts.map +0 -1
  199. package/dist/transform/workers/transform-child.js.map +0 -1
  200. package/dist/transform/workers/transform-worker.d.ts.map +0 -1
  201. package/dist/transform/workers/transform-worker.js.map +0 -1
  202. package/dist/type-guards.d.ts +0 -16
  203. package/dist/type-guards.d.ts.map +0 -1
  204. package/dist/type-guards.js +0 -13
  205. package/dist/type-guards.js.map +0 -1
@@ -1,10 +1,11 @@
1
- import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
1
  import { AsyncLocalStorage } from 'node:async_hooks';
3
2
  import { Buffer } from 'node:buffer';
4
3
  import { randomUUID } from 'node:crypto';
5
4
  import { setInterval } from 'node:timers';
6
- import { config } from '../config.js';
7
- import { createUnrefTimeout } from '../timer-utils.js';
5
+ import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
6
+ import { config } from '../lib/core.js';
7
+ import { RESOURCE_NOT_FOUND_ERROR_CODE, toError } from '../lib/utils.js';
8
+ import { createUnrefTimeout } from '../lib/utils.js';
8
9
  const DEFAULT_TTL_MS = 60_000;
9
10
  const MIN_TTL_MS = 1_000;
10
11
  const MAX_TTL_MS = 86_400_000;
@@ -31,35 +32,61 @@ class TaskManager {
31
32
  tasks = new Map();
32
33
  ownerCounts = new Map();
33
34
  waiters = new Map();
34
- constructor() {
35
- this.startCleanupLoop();
36
- }
37
- startCleanupLoop() {
38
- const interval = setInterval(() => {
35
+ cleanupInterval = null;
36
+ ensureCleanupLoop() {
37
+ if (this.cleanupInterval)
38
+ return;
39
+ this.cleanupInterval = setInterval(() => {
39
40
  this.removeExpiredTasks();
41
+ if (this.tasks.size === 0) {
42
+ this.stopCleanupLoop();
43
+ }
40
44
  }, CLEANUP_INTERVAL_MS);
41
- interval.unref();
45
+ this.cleanupInterval.unref();
46
+ }
47
+ stopCleanupLoop() {
48
+ if (!this.cleanupInterval)
49
+ return;
50
+ clearInterval(this.cleanupInterval);
51
+ this.cleanupInterval = null;
42
52
  }
43
53
  removeExpiredTasks() {
44
54
  const now = Date.now();
45
- const expired = [];
46
55
  for (const [id, task] of this.tasks) {
47
56
  if (this.isExpired(task, now)) {
48
- expired.push(id);
57
+ this.removeTask(id);
49
58
  }
50
59
  }
51
- for (const id of expired) {
52
- this.removeTask(id);
53
- }
54
60
  }
55
61
  removeTask(taskId) {
56
62
  const task = this.tasks.get(taskId);
57
63
  if (!task)
58
64
  return false;
59
65
  this.tasks.delete(taskId);
60
- this.decrementOwnerCount(task.ownerKey);
66
+ this.releaseOwnerCount(task);
61
67
  return true;
62
68
  }
69
+ applyTaskUpdate(task, updates) {
70
+ Object.assign(task, updates);
71
+ task.lastUpdatedAt = new Date().toISOString();
72
+ }
73
+ cancelActiveTask(task, statusMessage) {
74
+ this.applyTaskUpdate(task, {
75
+ status: 'cancelled',
76
+ statusMessage,
77
+ });
78
+ this.notifyWaiters(task);
79
+ this.releaseOwnerCount(task);
80
+ }
81
+ releaseOwnerCount(task) {
82
+ const internal = task;
83
+ if (internal._ownerCounted === false)
84
+ return;
85
+ if ('_ownerCounted' in internal) {
86
+ internal._ownerCounted = false;
87
+ }
88
+ this.decrementOwnerCount(task.ownerKey);
89
+ }
63
90
  countTasksForOwner(ownerKey) {
64
91
  return this.ownerCounts.get(ownerKey) ?? 0;
65
92
  }
@@ -99,12 +126,14 @@ class TaskManager {
99
126
  ttl: normalizeTaskTtl(options?.ttl),
100
127
  pollInterval: DEFAULT_POLL_INTERVAL_MS,
101
128
  _createdAtMs: now.getTime(),
129
+ _ownerCounted: true,
102
130
  };
103
131
  this.tasks.set(task.taskId, task);
104
132
  this.incrementOwnerCount(ownerKey);
133
+ this.ensureCleanupLoop();
105
134
  return task;
106
135
  }
107
- getTask(taskId, ownerKey) {
136
+ lookupActiveTask(taskId, ownerKey) {
108
137
  const task = this.tasks.get(taskId);
109
138
  if (!task)
110
139
  return undefined;
@@ -116,30 +145,26 @@ class TaskManager {
116
145
  }
117
146
  return task;
118
147
  }
148
+ getTask(taskId, ownerKey) {
149
+ return this.lookupActiveTask(taskId, ownerKey);
150
+ }
119
151
  updateTask(taskId, updates) {
120
152
  const task = this.tasks.get(taskId);
121
153
  if (!task)
122
154
  return;
123
155
  if (isTerminalStatus(task.status))
124
156
  return;
125
- Object.assign(task, {
126
- ...updates,
127
- lastUpdatedAt: new Date().toISOString(),
128
- });
157
+ this.applyTaskUpdate(task, updates);
129
158
  this.notifyWaiters(task);
130
159
  }
131
160
  cancelTask(taskId, ownerKey) {
132
- const task = this.getTask(taskId, ownerKey);
161
+ const task = this.lookupActiveTask(taskId, ownerKey);
133
162
  if (!task)
134
163
  return undefined;
135
164
  if (isTerminalStatus(task.status)) {
136
165
  throw new McpError(ErrorCode.InvalidParams, `Cannot cancel task: already in terminal status '${task.status}'`);
137
166
  }
138
- this.updateTask(taskId, {
139
- status: 'cancelled',
140
- statusMessage: 'The task was cancelled by request.',
141
- });
142
- this.decrementOwnerCount(task.ownerKey);
167
+ this.cancelActiveTask(task, 'The task was cancelled by request.');
143
168
  return this.tasks.get(taskId);
144
169
  }
145
170
  cancelTasksByOwner(ownerKey, statusMessage = 'The task was cancelled because its owner is no longer active.') {
@@ -151,11 +176,7 @@ class TaskManager {
151
176
  continue;
152
177
  if (isTerminalStatus(task.status))
153
178
  continue;
154
- this.updateTask(task.taskId, {
155
- status: 'cancelled',
156
- statusMessage,
157
- });
158
- this.decrementOwnerCount(task.ownerKey);
179
+ this.cancelActiveTask(task, statusMessage);
159
180
  cancelled.push(task);
160
181
  }
161
182
  return cancelled;
@@ -165,12 +186,11 @@ class TaskManager {
165
186
  let collecting = anchorTaskId === null;
166
187
  let anchorFound = anchorTaskId === null;
167
188
  const now = Date.now();
168
- const expired = [];
169
189
  for (const task of this.tasks.values()) {
170
190
  if (task.ownerKey !== ownerKey)
171
191
  continue;
172
192
  if (this.isExpired(task, now)) {
173
- expired.push(task.taskId);
193
+ this.removeTask(task.taskId);
174
194
  continue;
175
195
  }
176
196
  if (!collecting) {
@@ -184,9 +204,6 @@ class TaskManager {
184
204
  if (page.length > pageSize)
185
205
  break;
186
206
  }
187
- for (const id of expired) {
188
- this.removeTask(id);
189
- }
190
207
  // Anchor task expired between pages; return empty list so callers stop
191
208
  // pagination cleanly. Silently falling back to page 0 risks infinite loops
192
209
  // for automated clients that always follow nextCursor.
@@ -198,14 +215,7 @@ class TaskManager {
198
215
  listTasks(options) {
199
216
  const { ownerKey, cursor, limit } = options;
200
217
  const pageSize = limit && limit > 0 ? limit : DEFAULT_PAGE_SIZE;
201
- let anchorTaskId = null;
202
- if (cursor !== undefined) {
203
- const decoded = this.decodeCursor(cursor);
204
- if (decoded === null) {
205
- throw new McpError(ErrorCode.InvalidParams, 'Invalid cursor');
206
- }
207
- ({ anchorTaskId } = decoded);
208
- }
218
+ const anchorTaskId = this.resolveAnchorTaskId(cursor);
209
219
  const page = this.collectPage(ownerKey, anchorTaskId, pageSize);
210
220
  const hasMore = page.length > pageSize;
211
221
  if (hasMore) {
@@ -214,27 +224,50 @@ class TaskManager {
214
224
  const nextCursor = this.resolveNextCursor(page, hasMore);
215
225
  return nextCursor ? { tasks: page, nextCursor } : { tasks: page };
216
226
  }
227
+ resolveAnchorTaskId(cursor) {
228
+ if (cursor === undefined)
229
+ return null;
230
+ const decoded = this.decodeCursor(cursor);
231
+ if (decoded === null) {
232
+ throw new McpError(ErrorCode.InvalidParams, 'Invalid cursor');
233
+ }
234
+ return decoded.anchorTaskId;
235
+ }
236
+ addWaiter(taskId, waiter) {
237
+ let set = this.waiters.get(taskId);
238
+ if (!set) {
239
+ set = new Set();
240
+ this.waiters.set(taskId, set);
241
+ }
242
+ set.add(waiter);
243
+ }
244
+ removeWaiter(taskId, waiter) {
245
+ if (!waiter)
246
+ return;
247
+ const set = this.waiters.get(taskId);
248
+ if (!set)
249
+ return;
250
+ set.delete(waiter);
251
+ if (set.size === 0) {
252
+ this.waiters.delete(taskId);
253
+ }
254
+ }
217
255
  async waitForTerminalTask(taskId, ownerKey, signal) {
218
- const task = this.tasks.get(taskId);
256
+ const task = this.lookupActiveTask(taskId, ownerKey);
219
257
  if (!task)
220
258
  return undefined;
221
- if (ownerKey && task.ownerKey !== ownerKey)
222
- return undefined;
223
- if (this.isExpired(task)) {
224
- this.removeTask(taskId);
225
- return undefined;
226
- }
227
259
  if (isTerminalStatus(task.status))
228
260
  return task;
229
261
  // isExpired() above guarantees task.ttl has not elapsed; compute deadlineMs
230
262
  // for the promise-based timeout below.
231
263
  const deadlineMs = task._createdAtMs + task.ttl;
232
264
  return new Promise((resolve, reject) => {
265
+ // 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.
233
266
  const resolveInContext = AsyncLocalStorage.bind((value) => {
234
267
  resolve(value);
235
268
  });
236
269
  const rejectInContext = AsyncLocalStorage.bind((error) => {
237
- reject(error instanceof Error ? error : new Error(String(error)));
270
+ reject(toError(error));
238
271
  });
239
272
  let settled = false;
240
273
  let waiter = null;
@@ -248,16 +281,6 @@ class TaskManager {
248
281
  signal.removeEventListener('abort', onAbort);
249
282
  }
250
283
  };
251
- const removeWaiter = () => {
252
- if (waiter) {
253
- const set = this.waiters.get(taskId);
254
- if (set) {
255
- set.delete(waiter);
256
- if (set.size === 0)
257
- this.waiters.delete(taskId);
258
- }
259
- }
260
- };
261
284
  const settleOnce = (fn) => {
262
285
  if (settled)
263
286
  return;
@@ -267,7 +290,7 @@ class TaskManager {
267
290
  const onAbort = () => {
268
291
  settleOnce(() => {
269
292
  cleanup();
270
- removeWaiter();
293
+ this.removeWaiter(taskId, waiter);
271
294
  rejectInContext(new McpError(ErrorCode.ConnectionClosed, 'Request was cancelled'));
272
295
  });
273
296
  };
@@ -285,12 +308,7 @@ class TaskManager {
285
308
  onAbort();
286
309
  return;
287
310
  }
288
- let set = this.waiters.get(taskId);
289
- if (!set) {
290
- set = new Set();
291
- this.waiters.set(taskId, set);
292
- }
293
- set.add(waiter);
311
+ this.addWaiter(taskId, waiter);
294
312
  if (signal) {
295
313
  signal.addEventListener('abort', onAbort, { once: true });
296
314
  }
@@ -300,9 +318,11 @@ class TaskManager {
300
318
  .then(() => {
301
319
  settleOnce(() => {
302
320
  cleanup();
303
- removeWaiter();
321
+ this.removeWaiter(taskId, waiter);
304
322
  this.removeTask(taskId);
305
- resolveInContext(undefined);
323
+ rejectInContext(new McpError(RESOURCE_NOT_FOUND_ERROR_CODE, 'Task expired', {
324
+ taskId,
325
+ }));
306
326
  });
307
327
  })
308
328
  .catch(rejectInContext);
@@ -321,6 +341,9 @@ class TaskManager {
321
341
  isExpired(task, now = Date.now()) {
322
342
  return now - task._createdAtMs > task.ttl;
323
343
  }
344
+ maybeUpdateLastUpdatedAt(task) {
345
+ task.lastUpdatedAt = new Date().toISOString();
346
+ }
324
347
  shrinkTtlAfterDelivery(taskId) {
325
348
  const task = this.tasks.get(taskId);
326
349
  if (!task)
@@ -331,10 +354,12 @@ class TaskManager {
331
354
  const newTtl = elapsed + RESULT_DELIVERY_GRACE_MS;
332
355
  if (newTtl < task.ttl) {
333
356
  task.ttl = newTtl;
334
- task.lastUpdatedAt = new Date().toISOString();
357
+ this.maybeUpdateLastUpdatedAt(task);
335
358
  }
336
359
  }
337
360
  encodeCursor(taskId) {
361
+ // Base64url-encode the taskId to produce a compact opaque cursor string.
362
+ // 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.
338
363
  return Buffer.from(taskId, 'utf8').toString('base64url');
339
364
  }
340
365
  decodeCursor(cursor) {
@@ -376,4 +401,3 @@ function isValidBase64UrlCursor(cursor) {
376
401
  return cursor.length % 4 !== 1;
377
402
  }
378
403
  export const taskManager = new TaskManager();
379
- //# sourceMappingURL=manager.js.map
@@ -1,6 +1,6 @@
1
1
  import type { ServerResult } from '@modelcontextprotocol/sdk/types.js';
2
- import type { ProgressNotification } from '../tools.js';
3
- export interface HandlerExtra {
2
+ import type { ProgressNotification } from '../lib/mcp-tools.js';
3
+ interface HandlerExtra {
4
4
  sessionId?: string;
5
5
  authInfo?: {
6
6
  clientId?: string;
@@ -12,6 +12,7 @@ export interface HandlerExtra {
12
12
  }
13
13
  export interface ToolCallContext {
14
14
  ownerKey: string;
15
+ sessionId?: string;
15
16
  signal?: AbortSignal;
16
17
  requestId?: string | number;
17
18
  sendNotification?: (notification: ProgressNotification) => Promise<void>;
@@ -29,4 +30,3 @@ export declare function resolveToolCallContext(extra?: HandlerExtra): ToolCallCo
29
30
  export declare function isServerResult(value: unknown): value is ServerResult;
30
31
  export declare function tryReadToolStructuredError(value: unknown): string | undefined;
31
32
  export {};
32
- //# sourceMappingURL=owner.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { isObject } from '../type-guards.js';
2
+ import { isObject } from '../lib/utils.js';
3
3
  export function compact(obj) {
4
4
  const result = {};
5
5
  for (const key of Object.keys(obj)) {
@@ -62,6 +62,7 @@ export function resolveTaskOwnerKey(extra) {
62
62
  export function resolveToolCallContext(extra) {
63
63
  return compact({
64
64
  ownerKey: resolveTaskOwnerKey(extra),
65
+ sessionId: extra?.sessionId,
65
66
  signal: extra?.signal,
66
67
  requestId: extra?.requestId,
67
68
  sendNotification: extra?.sendNotification,
@@ -89,4 +90,3 @@ export function tryReadToolStructuredError(value) {
89
90
  return undefined;
90
91
  }
91
92
  }
92
- //# sourceMappingURL=owner.js.map
@@ -0,0 +1,11 @@
1
+ import type { ServerResult } from '@modelcontextprotocol/sdk/types.js';
2
+ import type { ToolHandlerExtra } from '../lib/mcp-tools.js';
3
+ export interface TaskCapableToolDescriptor<TArgs = unknown> {
4
+ name: string;
5
+ parseArguments: (args: unknown) => TArgs;
6
+ execute: (args: TArgs, extra?: ToolHandlerExtra) => Promise<ServerResult>;
7
+ }
8
+ export declare function registerTaskCapableTool<TArgs>(descriptor: TaskCapableToolDescriptor<TArgs>): void;
9
+ export declare function unregisterTaskCapableTool(name: string): void;
10
+ export declare function getTaskCapableTool(name: string): TaskCapableToolDescriptor | undefined;
11
+ export declare function hasTaskCapableTool(name: string): boolean;
@@ -0,0 +1,13 @@
1
+ const taskCapableTools = new Map();
2
+ export function registerTaskCapableTool(descriptor) {
3
+ taskCapableTools.set(descriptor.name, descriptor);
4
+ }
5
+ export function unregisterTaskCapableTool(name) {
6
+ taskCapableTools.delete(name);
7
+ }
8
+ export function getTaskCapableTool(name) {
9
+ return taskCapableTools.get(name);
10
+ }
11
+ export function hasTaskCapableTool(name) {
12
+ return taskCapableTools.has(name);
13
+ }
@@ -0,0 +1,28 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
+ import type { ContentBlock } from '@modelcontextprotocol/sdk/types.js';
3
+ import { type ToolHandlerExtra } from '../lib/mcp-tools.js';
4
+ interface FetchUrlInput {
5
+ url: string;
6
+ skipNoiseRemoval?: boolean | undefined;
7
+ forceRefresh?: boolean | undefined;
8
+ maxInlineChars?: number | undefined;
9
+ }
10
+ type ToolContentBlockUnion = ContentBlock;
11
+ interface ToolResponseBase {
12
+ [key: string]: unknown;
13
+ content: ToolContentBlockUnion[];
14
+ structuredContent?: Record<string, unknown> | undefined;
15
+ isError?: boolean;
16
+ }
17
+ export declare const FETCH_URL_TOOL_NAME = "fetch-url";
18
+ export declare function fetchUrlToolHandler(input: FetchUrlInput, extra?: ToolHandlerExtra): Promise<ToolResponseBase>;
19
+ /**
20
+ * Stdio-path guard: ensures a request context (requestId, sessionId) is set
21
+ * in AsyncLocalStorage before invoking the handler. On the HTTP path the SDK
22
+ * populates `extra.requestId`/`extra.requestInfo`, so this is a no-op there.
23
+ * On the stdio path there is no SDK-provided context, so we derive one from
24
+ * the extra fields or generate a fresh UUID.
25
+ */
26
+ export declare function withRequestContextIfMissing<TParams, TResult, TExtra = unknown>(handler: (params: TParams, extra?: TExtra) => Promise<TResult>): (params: TParams, extra?: TExtra) => Promise<TResult>;
27
+ export declare function registerTools(server: McpServer): void;
28
+ export {};