@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
package/dist/server.js CHANGED
@@ -1,12 +1,12 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
1
  import * as fs from 'node:fs/promises';
4
2
  import process from 'node:process';
5
- import { z } from 'zod';
6
- import { config } from './lib/config.js';
7
- import { toError } from './lib/errors.js';
8
- import { abortAllTaskExecutions, registerTaskHandlers } from './lib/mcp.js';
9
- import { logError, logInfo, setLogLevel, setMcpServer, } from './lib/observability.js';
3
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
5
+ import { SetLevelRequestSchema } from '@modelcontextprotocol/sdk/types.js';
6
+ import { config } from './lib/core.js';
7
+ import { logError, logInfo, setLogLevel, setMcpServer } from './lib/core.js';
8
+ import { abortAllTaskExecutions, registerTaskHandlers, } from './lib/mcp-tools.js';
9
+ import { toError } from './lib/utils.js';
10
10
  import { registerGetHelpPrompt } from './prompts/index.js';
11
11
  import { registerCacheResourceTemplate, registerInstructionResource, } from './resources/index.js';
12
12
  import { buildServerInstructions } from './resources/instructions.js';
@@ -65,6 +65,10 @@ function createServerInfo(icons) {
65
65
  export async function createMcpServer() {
66
66
  return createMcpServerWithOptions({ registerObservabilityServer: true });
67
67
  }
68
+ const SHUTDOWN_SIGNALS = ['SIGINT', 'SIGTERM'];
69
+ function shouldRegisterObservabilityServer(options) {
70
+ return options?.registerObservabilityServer ?? true;
71
+ }
68
72
  async function createMcpServerWithOptions(options) {
69
73
  const localIcon = await getLocalIconInfo();
70
74
  const serverConfig = {
@@ -75,13 +79,16 @@ async function createMcpServerWithOptions(options) {
75
79
  }
76
80
  const serverInfo = createServerInfo(localIcon ? [localIcon] : undefined);
77
81
  const server = new McpServer(serverInfo, serverConfig);
78
- if (options?.registerObservabilityServer ?? true) {
82
+ if (shouldRegisterObservabilityServer(options)) {
79
83
  setMcpServer(server);
80
84
  }
81
85
  registerAllTools(server);
82
86
  registerGetHelpPrompt(server, serverInstructions, localIcon);
83
87
  registerInstructionResource(server, serverInstructions, localIcon);
84
88
  registerCacheResourceTemplate(server, localIcon);
89
+ // NOTE: Internally patches server.close and server.server.onclose for cleanup
90
+ // callbacks, and intercepts tools/call via Reflect.get on private SDK state.
91
+ // See src/lib/task-handlers.ts for risk documentation (S-2, S-3).
85
92
  registerTaskHandlers(server);
86
93
  registerLoggingSetLevelHandler(server);
87
94
  attachServerErrorHandler(server);
@@ -91,22 +98,6 @@ export async function createMcpServerForHttpSession() {
91
98
  return createMcpServerWithOptions({ registerObservabilityServer: false });
92
99
  }
93
100
  function registerLoggingSetLevelHandler(server) {
94
- const LoggingLevelSchema = z.enum([
95
- 'debug',
96
- 'info',
97
- 'notice',
98
- 'warning',
99
- 'error',
100
- 'critical',
101
- 'alert',
102
- 'emergency',
103
- ]);
104
- const SetLevelRequestSchema = z
105
- .object({
106
- method: z.literal('logging/setLevel'),
107
- params: z.object({ level: LoggingLevelSchema }).loose(),
108
- })
109
- .loose();
110
101
  server.server.setRequestHandler(SetLevelRequestSchema, (request) => {
111
102
  setLogLevel(request.params.level);
112
103
  return {};
@@ -151,7 +142,7 @@ function createShutdownHandler(server) {
151
142
  };
152
143
  }
153
144
  function registerSignalHandlers(handler) {
154
- for (const signal of ['SIGINT', 'SIGTERM']) {
145
+ for (const signal of SHUTDOWN_SIGNALS) {
155
146
  process.once(signal, () => {
156
147
  handler(signal);
157
148
  });
@@ -175,4 +166,3 @@ export async function startStdioServer() {
175
166
  registerSignalHandlers(createShutdownHandler(server));
176
167
  await connectStdioServer(server, transport);
177
168
  }
178
- //# sourceMappingURL=server.js.map
@@ -39,4 +39,3 @@ export declare function emitTaskStatusNotification(server: McpServer, task: Task
39
39
  export declare function throwTaskNotFound(): never;
40
40
  export declare function handleToolCallRequest(server: McpServer, request: ExtendedCallToolRequest, context: ToolCallContext): Promise<ServerResult>;
41
41
  export {};
42
- //# sourceMappingURL=execution.d.ts.map
@@ -1,8 +1,8 @@
1
1
  import { ErrorCode, McpError, } from '@modelcontextprotocol/sdk/types.js';
2
- import { config } from '../lib/config.js';
3
- import { getErrorMessage, RESOURCE_NOT_FOUND_ERROR_CODE, } from '../lib/errors.js';
4
- import { logWarn, runWithRequestContext } from '../lib/observability.js';
5
- import { isObject } from '../lib/type-guards.js';
2
+ import { config } from '../lib/core.js';
3
+ import { logWarn, runWithRequestContext } from '../lib/core.js';
4
+ import { getErrorMessage, RESOURCE_NOT_FOUND_ERROR_CODE, } from '../lib/utils.js';
5
+ import { isObject } from '../lib/utils.js';
6
6
  import { taskManager, } from './manager.js';
7
7
  import { compact, tryReadToolStructuredError, } from './owner.js';
8
8
  import { getTaskCapableTool, hasTaskCapableTool, } from './tool-registry.js';
@@ -82,8 +82,9 @@ export function emitTaskStatusNotification(server, task) {
82
82
  return;
83
83
  if (!server.isConnected())
84
84
  return;
85
- // NOTE: 'notifications/tasks/status' is not part of the MCP v2025-11-25 specification.
86
- // This relies on the experimental task infrastructure in the SDK and may change.
85
+ // NOTE: 'notifications/tasks/status' is a non-spec extension (not in MCP v2025-11-25).
86
+ // Gated by config.tasks.emitStatusNotifications (TASKS_STATUS_NOTIFICATIONS env var).
87
+ // Clients should NOT depend on this for interoperability — behavior may change.
87
88
  void server.server
88
89
  .notification({
89
90
  method: 'notifications/tasks/status',
@@ -161,10 +162,7 @@ function updateWorkingTaskStatus(server, taskId, statusMessage) {
161
162
  return;
162
163
  if (current.statusMessage === statusMessage)
163
164
  return;
164
- taskManager.updateTask(taskId, { statusMessage });
165
- const updated = taskManager.getTask(taskId);
166
- if (updated)
167
- emitTaskStatusNotification(server, updated);
165
+ updateTaskAndEmitStatus(server, taskId, { statusMessage });
168
166
  }
169
167
  function updateTaskAndEmitStatus(server, taskId, update) {
170
168
  taskManager.updateTask(taskId, update);
@@ -192,6 +190,21 @@ function buildTaskFailureState(error) {
192
190
  },
193
191
  };
194
192
  }
193
+ function resolveToolAndArgs(params) {
194
+ const tool = resolveTaskCapableTool(params.name);
195
+ const args = tool.parseArguments(params.arguments);
196
+ return { tool, args };
197
+ }
198
+ function buildTaskCompletionUpdate(result) {
199
+ const isToolError = isObject(result) && 'isError' in result && result.isError === true;
200
+ return {
201
+ status: isToolError ? 'failed' : 'completed',
202
+ statusMessage: isToolError
203
+ ? (tryReadToolStructuredError(result) ?? 'Tool execution failed')
204
+ : 'Task completed successfully.',
205
+ result,
206
+ };
207
+ }
195
208
  async function runTaskToolExecution(params) {
196
209
  const { server, taskId, args, tool, meta, sessionId, sendNotification } = params;
197
210
  return runWithRequestContext({
@@ -211,14 +224,7 @@ async function runTaskToolExecution(params) {
211
224
  updateWorkingTaskStatus(server, taskId, message);
212
225
  },
213
226
  });
214
- const isToolError = isObject(result) && 'isError' in result && result.isError === true;
215
- updateTaskAndEmitStatus(server, taskId, {
216
- status: isToolError ? 'failed' : 'completed',
217
- statusMessage: isToolError
218
- ? (tryReadToolStructuredError(result) ?? 'Tool execution failed')
219
- : 'Task completed successfully.',
220
- result,
221
- });
227
+ updateTaskAndEmitStatus(server, taskId, buildTaskCompletionUpdate(result));
222
228
  }
223
229
  catch (error) {
224
230
  const failure = buildTaskFailureState(error);
@@ -234,13 +240,12 @@ async function runTaskToolExecution(params) {
234
240
  });
235
241
  }
236
242
  function handleTaskToolCall(server, params, context) {
237
- const tool = resolveTaskCapableTool(params.name);
238
- const validArgs = tool.parseArguments(params.arguments);
243
+ const { tool, args } = resolveToolAndArgs(params);
239
244
  const task = taskManager.createTask(params.task?.ttl !== undefined ? { ttl: params.task.ttl } : undefined, 'Task started', context.ownerKey);
240
245
  void runTaskToolExecution({
241
246
  server,
242
247
  taskId: task.taskId,
243
- args: validArgs,
248
+ args,
244
249
  tool,
245
250
  ...compact({
246
251
  meta: params._meta,
@@ -251,8 +256,7 @@ function handleTaskToolCall(server, params, context) {
251
256
  return buildCreateTaskResult(toTaskSummary(task));
252
257
  }
253
258
  async function handleDirectToolCall(params, context) {
254
- const tool = resolveTaskCapableTool(params.name);
255
- const args = tool.parseArguments(params.arguments);
259
+ const { tool, args } = resolveToolAndArgs(params);
256
260
  const extra = compact({
257
261
  signal: context.signal,
258
262
  requestId: context.requestId,
@@ -271,4 +275,3 @@ export async function handleToolCallRequest(server, request, context) {
271
275
  }
272
276
  return handleDirectToolCall(params, context);
273
277
  }
274
- //# sourceMappingURL=execution.js.map
@@ -40,10 +40,13 @@ declare class TaskManager {
40
40
  private tasks;
41
41
  private ownerCounts;
42
42
  private waiters;
43
- constructor();
44
- private startCleanupLoop;
43
+ private cleanupInterval;
44
+ private ensureCleanupLoop;
45
+ private stopCleanupLoop;
45
46
  private removeExpiredTasks;
46
47
  private removeTask;
48
+ private applyTaskUpdate;
49
+ private cancelActiveTask;
47
50
  private releaseOwnerCount;
48
51
  private countTasksForOwner;
49
52
  private incrementOwnerCount;
@@ -64,11 +67,13 @@ declare class TaskManager {
64
67
  tasks: TaskState[];
65
68
  nextCursor?: string;
66
69
  };
70
+ private resolveAnchorTaskId;
67
71
  private addWaiter;
68
72
  private removeWaiter;
69
73
  waitForTerminalTask(taskId: string, ownerKey: string, signal?: AbortSignal): Promise<TaskState | undefined>;
70
74
  private notifyWaiters;
71
75
  private isExpired;
76
+ private maybeUpdateLastUpdatedAt;
72
77
  shrinkTtlAfterDelivery(taskId: string): void;
73
78
  private encodeCursor;
74
79
  private decodeCursor;
@@ -76,4 +81,3 @@ declare class TaskManager {
76
81
  }
77
82
  export declare const taskManager: TaskManager;
78
83
  export {};
79
- //# sourceMappingURL=manager.d.ts.map
@@ -1,11 +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 '../lib/config.js';
7
- import { toError } from '../lib/errors.js';
8
- import { createUnrefTimeout, } from '../lib/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';
9
9
  const DEFAULT_TTL_MS = 60_000;
10
10
  const MIN_TTL_MS = 1_000;
11
11
  const MAX_TTL_MS = 86_400_000;
@@ -32,14 +32,23 @@ class TaskManager {
32
32
  tasks = new Map();
33
33
  ownerCounts = new Map();
34
34
  waiters = new Map();
35
- constructor() {
36
- this.startCleanupLoop();
37
- }
38
- startCleanupLoop() {
39
- const interval = setInterval(() => {
35
+ cleanupInterval = null;
36
+ ensureCleanupLoop() {
37
+ if (this.cleanupInterval)
38
+ return;
39
+ this.cleanupInterval = setInterval(() => {
40
40
  this.removeExpiredTasks();
41
+ if (this.tasks.size === 0) {
42
+ this.stopCleanupLoop();
43
+ }
41
44
  }, CLEANUP_INTERVAL_MS);
42
- interval.unref();
45
+ this.cleanupInterval.unref();
46
+ }
47
+ stopCleanupLoop() {
48
+ if (!this.cleanupInterval)
49
+ return;
50
+ clearInterval(this.cleanupInterval);
51
+ this.cleanupInterval = null;
43
52
  }
44
53
  removeExpiredTasks() {
45
54
  const now = Date.now();
@@ -57,6 +66,18 @@ class TaskManager {
57
66
  this.releaseOwnerCount(task);
58
67
  return true;
59
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
+ }
60
81
  releaseOwnerCount(task) {
61
82
  const internal = task;
62
83
  if (internal._ownerCounted === false)
@@ -109,6 +130,7 @@ class TaskManager {
109
130
  };
110
131
  this.tasks.set(task.taskId, task);
111
132
  this.incrementOwnerCount(ownerKey);
133
+ this.ensureCleanupLoop();
112
134
  return task;
113
135
  }
114
136
  lookupActiveTask(taskId, ownerKey) {
@@ -132,22 +154,17 @@ class TaskManager {
132
154
  return;
133
155
  if (isTerminalStatus(task.status))
134
156
  return;
135
- Object.assign(task, updates);
136
- task.lastUpdatedAt = new Date().toISOString();
157
+ this.applyTaskUpdate(task, updates);
137
158
  this.notifyWaiters(task);
138
159
  }
139
160
  cancelTask(taskId, ownerKey) {
140
- const task = this.getTask(taskId, ownerKey);
161
+ const task = this.lookupActiveTask(taskId, ownerKey);
141
162
  if (!task)
142
163
  return undefined;
143
164
  if (isTerminalStatus(task.status)) {
144
165
  throw new McpError(ErrorCode.InvalidParams, `Cannot cancel task: already in terminal status '${task.status}'`);
145
166
  }
146
- this.updateTask(taskId, {
147
- status: 'cancelled',
148
- statusMessage: 'The task was cancelled by request.',
149
- });
150
- this.releaseOwnerCount(task);
167
+ this.cancelActiveTask(task, 'The task was cancelled by request.');
151
168
  return this.tasks.get(taskId);
152
169
  }
153
170
  cancelTasksByOwner(ownerKey, statusMessage = 'The task was cancelled because its owner is no longer active.') {
@@ -159,11 +176,7 @@ class TaskManager {
159
176
  continue;
160
177
  if (isTerminalStatus(task.status))
161
178
  continue;
162
- this.updateTask(task.taskId, {
163
- status: 'cancelled',
164
- statusMessage,
165
- });
166
- this.releaseOwnerCount(task);
179
+ this.cancelActiveTask(task, statusMessage);
167
180
  cancelled.push(task);
168
181
  }
169
182
  return cancelled;
@@ -202,14 +215,7 @@ class TaskManager {
202
215
  listTasks(options) {
203
216
  const { ownerKey, cursor, limit } = options;
204
217
  const pageSize = limit && limit > 0 ? limit : DEFAULT_PAGE_SIZE;
205
- let anchorTaskId = null;
206
- if (cursor !== undefined) {
207
- const decoded = this.decodeCursor(cursor);
208
- if (decoded === null) {
209
- throw new McpError(ErrorCode.InvalidParams, 'Invalid cursor');
210
- }
211
- ({ anchorTaskId } = decoded);
212
- }
218
+ const anchorTaskId = this.resolveAnchorTaskId(cursor);
213
219
  const page = this.collectPage(ownerKey, anchorTaskId, pageSize);
214
220
  const hasMore = page.length > pageSize;
215
221
  if (hasMore) {
@@ -218,6 +224,15 @@ class TaskManager {
218
224
  const nextCursor = this.resolveNextCursor(page, hasMore);
219
225
  return nextCursor ? { tasks: page, nextCursor } : { tasks: page };
220
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
+ }
221
236
  addWaiter(taskId, waiter) {
222
237
  let set = this.waiters.get(taskId);
223
238
  if (!set) {
@@ -305,7 +320,9 @@ class TaskManager {
305
320
  cleanup();
306
321
  this.removeWaiter(taskId, waiter);
307
322
  this.removeTask(taskId);
308
- resolveInContext(undefined);
323
+ rejectInContext(new McpError(RESOURCE_NOT_FOUND_ERROR_CODE, 'Task expired', {
324
+ taskId,
325
+ }));
309
326
  });
310
327
  })
311
328
  .catch(rejectInContext);
@@ -324,6 +341,9 @@ class TaskManager {
324
341
  isExpired(task, now = Date.now()) {
325
342
  return now - task._createdAtMs > task.ttl;
326
343
  }
344
+ maybeUpdateLastUpdatedAt(task) {
345
+ task.lastUpdatedAt = new Date().toISOString();
346
+ }
327
347
  shrinkTtlAfterDelivery(taskId) {
328
348
  const task = this.tasks.get(taskId);
329
349
  if (!task)
@@ -334,7 +354,7 @@ class TaskManager {
334
354
  const newTtl = elapsed + RESULT_DELIVERY_GRACE_MS;
335
355
  if (newTtl < task.ttl) {
336
356
  task.ttl = newTtl;
337
- task.lastUpdatedAt = new Date().toISOString();
357
+ this.maybeUpdateLastUpdatedAt(task);
338
358
  }
339
359
  }
340
360
  encodeCursor(taskId) {
@@ -381,4 +401,3 @@ function isValidBase64UrlCursor(cursor) {
381
401
  return cursor.length % 4 !== 1;
382
402
  }
383
403
  export const taskManager = new TaskManager();
384
- //# sourceMappingURL=manager.js.map
@@ -1,5 +1,5 @@
1
1
  import type { ServerResult } from '@modelcontextprotocol/sdk/types.js';
2
- import type { ProgressNotification } from '../lib/tool-progress.js';
2
+ import type { ProgressNotification } from '../lib/mcp-tools.js';
3
3
  interface HandlerExtra {
4
4
  sessionId?: string;
5
5
  authInfo?: {
@@ -30,4 +30,3 @@ export declare function resolveToolCallContext(extra?: HandlerExtra): ToolCallCo
30
30
  export declare function isServerResult(value: unknown): value is ServerResult;
31
31
  export declare function tryReadToolStructuredError(value: unknown): string | undefined;
32
32
  export {};
33
- //# sourceMappingURL=owner.d.ts.map
@@ -1,5 +1,5 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { isObject } from '../lib/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)) {
@@ -90,4 +90,3 @@ export function tryReadToolStructuredError(value) {
90
90
  return undefined;
91
91
  }
92
92
  }
93
- //# sourceMappingURL=owner.js.map
@@ -1,5 +1,5 @@
1
1
  import type { ServerResult } from '@modelcontextprotocol/sdk/types.js';
2
- import type { ToolHandlerExtra } from '../lib/tool-progress.js';
2
+ import type { ToolHandlerExtra } from '../lib/mcp-tools.js';
3
3
  export interface TaskCapableToolDescriptor<TArgs = unknown> {
4
4
  name: string;
5
5
  parseArguments: (args: unknown) => TArgs;
@@ -9,4 +9,3 @@ export declare function registerTaskCapableTool<TArgs>(descriptor: TaskCapableTo
9
9
  export declare function unregisterTaskCapableTool(name: string): void;
10
10
  export declare function getTaskCapableTool(name: string): TaskCapableToolDescriptor | undefined;
11
11
  export declare function hasTaskCapableTool(name: string): boolean;
12
- //# sourceMappingURL=tool-registry.d.ts.map
@@ -11,4 +11,3 @@ export function getTaskCapableTool(name) {
11
11
  export function hasTaskCapableTool(name) {
12
12
  return taskCapableTools.has(name);
13
13
  }
14
- //# sourceMappingURL=tool-registry.js.map
@@ -1,6 +1,6 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  import type { ContentBlock } from '@modelcontextprotocol/sdk/types.js';
3
- import { type ToolHandlerExtra } from '../lib/tool-progress.js';
3
+ import { type ToolHandlerExtra } from '../lib/mcp-tools.js';
4
4
  interface FetchUrlInput {
5
5
  url: string;
6
6
  skipNoiseRemoval?: boolean | undefined;
@@ -26,4 +26,3 @@ export declare function fetchUrlToolHandler(input: FetchUrlInput, extra?: ToolHa
26
26
  export declare function withRequestContextIfMissing<TParams, TResult, TExtra = unknown>(handler: (params: TParams, extra?: TExtra) => Promise<TResult>): (params: TParams, extra?: TExtra) => Promise<TResult>;
27
27
  export declare function registerTools(server: McpServer): void;
28
28
  export {};
29
- //# sourceMappingURL=fetch-url.d.ts.map
@@ -1,15 +1,14 @@
1
- import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
2
1
  import { randomUUID } from 'node:crypto';
2
+ import { ErrorCode, McpError } from '@modelcontextprotocol/sdk/types.js';
3
3
  import { z } from 'zod';
4
- import * as cache from '../lib/cache.js';
5
- import { config } from '../lib/config.js';
6
- import { generateSafeFilename } from '../lib/download.js';
7
- import { isAbortError, toError } from '../lib/errors.js';
8
- import { getRequestId, logDebug, logError, logWarn, runWithRequestContext, } from '../lib/observability.js';
9
- import { handleToolError } from '../lib/tool-errors.js';
10
- import { appendTruncationMarker, markdownTransform, parseCachedMarkdownResult, performSharedFetch, readNestedRecord, readString, serializeMarkdownResult, TRUNCATION_MARKER, withSignal, } from '../lib/tool-pipeline.js';
11
- import { createProgressReporter, } from '../lib/tool-progress.js';
12
- import { isObject } from '../lib/type-guards.js';
4
+ import * as cache from '../lib/core.js';
5
+ import { config } from '../lib/core.js';
6
+ import { getRequestId, logDebug, logError, logWarn, runWithRequestContext, } from '../lib/core.js';
7
+ import { generateSafeFilename } from '../lib/http.js';
8
+ import { handleToolError } from '../lib/mcp-tools.js';
9
+ import { appendTruncationMarker, markdownTransform, parseCachedMarkdownResult, performSharedFetch, readNestedRecord, readString, serializeMarkdownResult, TRUNCATION_MARKER, withSignal, } from '../lib/mcp-tools.js';
10
+ import { createProgressReporter, } from '../lib/mcp-tools.js';
11
+ import { isAbortError, isObject, toError } from '../lib/utils.js';
13
12
  import { fetchUrlInputSchema } from '../schemas/inputs.js';
14
13
  import { fetchUrlOutputSchema } from '../schemas/outputs.js';
15
14
  import { registerTaskCapableTool, unregisterTaskCapableTool, } from '../tasks/tool-registry.js';
@@ -83,11 +82,11 @@ function appendIfPresent(items, value) {
83
82
  /* -------------------------------------------------------------------------------------------------
84
83
  * Tool abort signal
85
84
  * ------------------------------------------------------------------------------------------------- */
85
+ const HARD_TOOL_TIMEOUT_MS = 300_000;
86
86
  function buildToolAbortSignal(extraSignal) {
87
87
  const { timeoutMs } = config.tools;
88
- if (timeoutMs <= 0)
89
- return extraSignal;
90
- const timeoutSignal = AbortSignal.timeout(timeoutMs);
88
+ const effectiveTimeout = timeoutMs > 0 ? timeoutMs : HARD_TOOL_TIMEOUT_MS;
89
+ const timeoutSignal = AbortSignal.timeout(effectiveTimeout);
91
90
  if (!extraSignal)
92
91
  return timeoutSignal;
93
92
  return AbortSignal.any([extraSignal, timeoutSignal]);
@@ -107,11 +106,11 @@ const METADATA_FIELD_LIMITS = {
107
106
  };
108
107
  function truncateMetadata(metadata) {
109
108
  const result = { ...metadata };
110
- for (const [key, limit] of Object.entries(METADATA_FIELD_LIMITS)) {
111
- const field = key;
112
- const value = result[field];
109
+ for (const [field, limit] of Object.entries(METADATA_FIELD_LIMITS)) {
110
+ const key = field;
111
+ const value = result[key];
113
112
  if (typeof value === 'string' && value.length > limit) {
114
- result[field] = value.slice(0, limit);
113
+ result[key] = value.slice(0, limit);
115
114
  }
116
115
  }
117
116
  return result;
@@ -228,11 +227,7 @@ function getUrlContext(urlStr) {
228
227
  }
229
228
  }
230
229
  async function fetchPipeline(url, signal, progress, skipNoiseRemoval, forceRefresh, maxInlineChars) {
231
- const reportProgress = (step, message) => {
232
- if (!progress)
233
- return;
234
- progress.report(step, message);
235
- };
230
+ const contextStr = getUrlContext(url);
236
231
  return performSharedFetch({
237
232
  url,
238
233
  ...withSignal(signal),
@@ -240,33 +235,43 @@ async function fetchPipeline(url, signal, progress, skipNoiseRemoval, forceRefre
240
235
  ...(forceRefresh ? { forceRefresh: true } : {}),
241
236
  ...(maxInlineChars !== undefined ? { maxInlineChars } : {}),
242
237
  transform: async ({ buffer, encoding, truncated }, normalizedUrl) => {
243
- const contextStr = getUrlContext(url);
244
- reportProgress(2, `fetch-url: ${contextStr} [converting to Markdown]`);
238
+ reportFetchProgress(progress, 2, contextStr, 'converting to Markdown');
245
239
  return markdownTransform({ buffer, encoding, ...(truncated ? { truncated } : {}) }, normalizedUrl, signal, skipNoiseRemoval);
246
240
  },
247
241
  serialize: serializeMarkdownResult,
248
242
  deserialize: parseCachedMarkdownResult,
249
243
  });
250
244
  }
245
+ function buildFetchProgressMessage(context, state) {
246
+ if (state === 'completed' || state === 'cancelled' || state === 'failed') {
247
+ return `fetch-url: ${context} • ${state}`;
248
+ }
249
+ return `fetch-url: ${context} [${state}]`;
250
+ }
251
+ function reportFetchProgress(progress, step, context, state) {
252
+ if (!progress)
253
+ return;
254
+ progress.report(step, buildFetchProgressMessage(context, state));
255
+ }
251
256
  async function executeFetch(input, extra) {
252
257
  const { url } = input;
253
258
  const signal = buildToolAbortSignal(extra?.signal);
254
259
  const progress = createProgressReporter(extra);
255
260
  const contextStr = getUrlContext(url);
256
- progress.report(0, `fetch-url: ${contextStr} [starting]`);
261
+ reportFetchProgress(progress, 0, contextStr, 'starting');
257
262
  logDebug('Fetching URL', { url });
258
263
  try {
259
- progress.report(1, `fetch-url: ${contextStr} [fetching HTML]`);
264
+ reportFetchProgress(progress, 1, contextStr, 'fetching HTML');
260
265
  const { pipeline, inlineResult } = await fetchPipeline(url, signal, progress, input.skipNoiseRemoval, input.forceRefresh, input.maxInlineChars);
261
266
  if (pipeline.fromCache) {
262
- progress.report(3, `fetch-url: ${contextStr} [loaded from cache]`);
267
+ reportFetchProgress(progress, 3, contextStr, 'loaded from cache');
263
268
  }
264
- progress.report(4, `fetch-url: ${contextStr} completed`);
269
+ reportFetchProgress(progress, 4, contextStr, 'completed');
265
270
  return buildResponse(pipeline, inlineResult, url);
266
271
  }
267
272
  catch (error) {
268
273
  const isAbort = isAbortError(error);
269
- progress.report(4, `fetch-url: ${contextStr} • ${isAbort ? 'cancelled' : 'failed'}`);
274
+ reportFetchProgress(progress, 4, contextStr, isAbort ? 'cancelled' : 'failed');
270
275
  throw error;
271
276
  }
272
277
  }
@@ -359,7 +364,11 @@ export function registerTools(server) {
359
364
  parseArguments: (args) => {
360
365
  const parsed = fetchUrlInputSchema.safeParse(args);
361
366
  if (!parsed.success) {
362
- throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments for fetch-url');
367
+ const flat = z.flattenError(parsed.error);
368
+ const details = Object.entries(flat.fieldErrors)
369
+ .map(([k, v]) => `${k}: ${v.join(', ')}`)
370
+ .join('; ') || flat.formErrors.join('; ');
371
+ throw new McpError(ErrorCode.InvalidParams, `Invalid arguments for ${FETCH_URL_TOOL_NAME}: ${details}`);
363
372
  }
364
373
  return parsed.data;
365
374
  },
@@ -378,4 +387,3 @@ export function registerTools(server) {
378
387
  // registered tool type includes this field.
379
388
  applyRegisteredToolExecutionMetadata(registeredTool, TOOL_DEFINITION.execution);
380
389
  }
381
- //# sourceMappingURL=fetch-url.js.map
@@ -1,3 +1,2 @@
1
1
  import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  export declare function registerAllTools(server: McpServer): void;
3
- //# sourceMappingURL=index.d.ts.map
@@ -2,4 +2,3 @@ import { registerTools as registerFetchUrlTool } from './fetch-url.js';
2
2
  export function registerAllTools(server) {
3
3
  registerFetchUrlTool(server);
4
4
  }
5
- //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ export declare function translateHtmlFragmentToMarkdown(html: string): string;