@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
@@ -0,0 +1,23 @@
1
+ import { z } from 'zod';
2
+ export declare const fetchUrlOutputSchema: z.ZodObject<{
3
+ url: z.ZodString;
4
+ inputUrl: z.ZodOptional<z.ZodString>;
5
+ resolvedUrl: z.ZodOptional<z.ZodString>;
6
+ finalUrl: z.ZodOptional<z.ZodString>;
7
+ cacheResourceUri: z.ZodOptional<z.ZodString>;
8
+ title: z.ZodOptional<z.ZodString>;
9
+ metadata: z.ZodOptional<z.ZodObject<{
10
+ title: z.ZodOptional<z.ZodString>;
11
+ description: z.ZodOptional<z.ZodString>;
12
+ author: z.ZodOptional<z.ZodString>;
13
+ image: z.ZodOptional<z.ZodString>;
14
+ favicon: z.ZodOptional<z.ZodString>;
15
+ publishedAt: z.ZodOptional<z.ZodString>;
16
+ modifiedAt: z.ZodOptional<z.ZodString>;
17
+ }, z.core.$strict>>;
18
+ markdown: z.ZodOptional<z.ZodString>;
19
+ fromCache: z.ZodOptional<z.ZodBoolean>;
20
+ fetchedAt: z.ZodOptional<z.ZodString>;
21
+ contentSize: z.ZodOptional<z.ZodNumber>;
22
+ truncated: z.ZodOptional<z.ZodBoolean>;
23
+ }, z.core.$strict>;
@@ -0,0 +1,77 @@
1
+ import { z } from 'zod';
2
+ import { config } from '../lib/core.js';
3
+ export const fetchUrlOutputSchema = z.strictObject({
4
+ url: z
5
+ .string()
6
+ .min(1)
7
+ .max(config.constants.maxUrlLength)
8
+ .describe('Fetched URL.'),
9
+ inputUrl: z
10
+ .string()
11
+ .max(config.constants.maxUrlLength)
12
+ .optional()
13
+ .describe('Original requested URL.'),
14
+ resolvedUrl: z
15
+ .string()
16
+ .max(config.constants.maxUrlLength)
17
+ .optional()
18
+ .describe('Final URL after raw-content transformations.'),
19
+ finalUrl: z
20
+ .string()
21
+ .max(config.constants.maxUrlLength)
22
+ .optional()
23
+ .describe('Final URL after HTTP redirects.'),
24
+ cacheResourceUri: z
25
+ .string()
26
+ .max(config.constants.maxUrlLength)
27
+ .optional()
28
+ .describe('URI for resources/read to get full markdown.'),
29
+ title: z.string().max(512).optional().describe('Page title.'),
30
+ metadata: z
31
+ .strictObject({
32
+ title: z.string().max(512).optional().describe('Detected page title.'),
33
+ description: z
34
+ .string()
35
+ .max(2048)
36
+ .optional()
37
+ .describe('Detected page description.'),
38
+ author: z.string().max(512).optional().describe('Detected page author.'),
39
+ image: z
40
+ .string()
41
+ .max(config.constants.maxUrlLength)
42
+ .optional()
43
+ .describe('Detected page preview image URL.'),
44
+ favicon: z
45
+ .string()
46
+ .max(config.constants.maxUrlLength)
47
+ .optional()
48
+ .describe('Detected page favicon URL.'),
49
+ publishedAt: z
50
+ .string()
51
+ .max(64)
52
+ .optional()
53
+ .describe('Detected publication date.'),
54
+ modifiedAt: z
55
+ .string()
56
+ .max(64)
57
+ .optional()
58
+ .describe('Detected last modified date.'),
59
+ })
60
+ .optional()
61
+ .describe('Extracted page metadata.'),
62
+ markdown: (config.constants.maxInlineContentChars > 0
63
+ ? z.string().max(config.constants.maxInlineContentChars)
64
+ : z.string())
65
+ .optional()
66
+ .describe('Extracted Markdown. May be truncated (check truncated field).'),
67
+ fromCache: z.boolean().optional().describe('True if served from cache.'),
68
+ fetchedAt: z.string().max(64).optional().describe('ISO timestamp of fetch.'),
69
+ contentSize: z
70
+ .number()
71
+ .int()
72
+ .min(0)
73
+ .max(config.constants.maxHtmlSize * 4)
74
+ .optional()
75
+ .describe('Full markdown size before truncation.'),
76
+ truncated: z.boolean().optional().describe('True if markdown was truncated.'),
77
+ });
package/dist/server.d.ts CHANGED
@@ -2,4 +2,3 @@ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
2
  export declare function createMcpServer(): Promise<McpServer>;
3
3
  export declare function createMcpServerForHttpSession(): Promise<McpServer>;
4
4
  export declare function startStdioServer(): Promise<void>;
5
- //# sourceMappingURL=server.d.ts.map
package/dist/server.js CHANGED
@@ -1,15 +1,16 @@
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 './config.js';
7
- import { buildServerInstructions } from './instructions.js';
8
- import { abortAllTaskExecutions, registerTaskHandlers } from './mcp.js';
9
- import { logError, logInfo, setLogLevel, setMcpServer, } from './observability.js';
10
- import { registerGetHelpPrompt } from './prompts.js';
11
- import { registerCacheResourceTemplate, registerInstructionResource, } from './resources.js';
12
- import { registerTools } from './tools.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
+ import { registerGetHelpPrompt } from './prompts/index.js';
11
+ import { registerCacheResourceTemplate, registerInstructionResource, } from './resources/index.js';
12
+ import { buildServerInstructions } from './resources/instructions.js';
13
+ import { registerAllTools } from './tools/index.js';
13
14
  import { shutdownTransformWorkerPool } from './transform/transform.js';
14
15
  async function getLocalIconInfo() {
15
16
  const name = 'logo.svg';
@@ -64,6 +65,10 @@ function createServerInfo(icons) {
64
65
  export async function createMcpServer() {
65
66
  return createMcpServerWithOptions({ registerObservabilityServer: true });
66
67
  }
68
+ const SHUTDOWN_SIGNALS = ['SIGINT', 'SIGTERM'];
69
+ function shouldRegisterObservabilityServer(options) {
70
+ return options?.registerObservabilityServer ?? true;
71
+ }
67
72
  async function createMcpServerWithOptions(options) {
68
73
  const localIcon = await getLocalIconInfo();
69
74
  const serverConfig = {
@@ -74,13 +79,16 @@ async function createMcpServerWithOptions(options) {
74
79
  }
75
80
  const serverInfo = createServerInfo(localIcon ? [localIcon] : undefined);
76
81
  const server = new McpServer(serverInfo, serverConfig);
77
- if (options?.registerObservabilityServer ?? true) {
82
+ if (shouldRegisterObservabilityServer(options)) {
78
83
  setMcpServer(server);
79
84
  }
80
- registerTools(server);
85
+ registerAllTools(server);
81
86
  registerGetHelpPrompt(server, serverInstructions, localIcon);
82
87
  registerInstructionResource(server, serverInstructions, localIcon);
83
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).
84
92
  registerTaskHandlers(server);
85
93
  registerLoggingSetLevelHandler(server);
86
94
  attachServerErrorHandler(server);
@@ -90,20 +98,14 @@ export async function createMcpServerForHttpSession() {
90
98
  return createMcpServerWithOptions({ registerObservabilityServer: false });
91
99
  }
92
100
  function registerLoggingSetLevelHandler(server) {
93
- const SetLevelRequestSchema = z
94
- .object({
95
- method: z.literal('logging/setLevel'),
96
- params: z.object({ level: z.string() }).loose(),
97
- })
98
- .loose();
99
- server.server.setRequestHandler(SetLevelRequestSchema, async (request) => {
101
+ server.server.setRequestHandler(SetLevelRequestSchema, (request) => {
100
102
  setLogLevel(request.params.level);
101
- return Promise.resolve({});
103
+ return {};
102
104
  });
103
105
  }
104
106
  function attachServerErrorHandler(server) {
105
107
  server.server.onerror = (error) => {
106
- logError('[MCP Error]', error instanceof Error ? error : { error });
108
+ logError('[MCP Error]', toError(error));
107
109
  };
108
110
  }
109
111
  async function shutdownServer(server, signal) {
@@ -129,7 +131,7 @@ function createShutdownHandler(server) {
129
131
  Promise.resolve()
130
132
  .then(() => shutdownServer(server, signal))
131
133
  .catch((err) => {
132
- const error = err instanceof Error ? err : new Error(String(err));
134
+ const error = toError(err);
133
135
  logError('Error during shutdown', error);
134
136
  process.exitCode = 1;
135
137
  })
@@ -140,7 +142,7 @@ function createShutdownHandler(server) {
140
142
  };
141
143
  }
142
144
  function registerSignalHandlers(handler) {
143
- for (const signal of ['SIGINT', 'SIGTERM']) {
145
+ for (const signal of SHUTDOWN_SIGNALS) {
144
146
  process.once(signal, () => {
145
147
  handler(signal);
146
148
  });
@@ -152,7 +154,7 @@ async function connectStdioServer(server, transport) {
152
154
  logInfo('Fetch URL MCP server running on stdio');
153
155
  }
154
156
  catch (error) {
155
- const err = error instanceof Error ? error : new Error(String(error));
157
+ const err = toError(error);
156
158
  throw new Error(`Failed to start stdio server: ${err.message}`, {
157
159
  cause: error,
158
160
  });
@@ -164,4 +166,3 @@ export async function startStdioServer() {
164
166
  registerSignalHandlers(createShutdownHandler(server));
165
167
  await connectStdioServer(server, transport);
166
168
  }
167
- //# 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,19 +1,25 @@
1
1
  import { ErrorCode, McpError, } from '@modelcontextprotocol/sdk/types.js';
2
- import { logWarn, runWithRequestContext } from '../observability.js';
3
- import { FETCH_URL_TOOL_NAME, fetchUrlInputSchema, fetchUrlToolHandler, } from '../tools.js';
4
- import { isObject } from '../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';
5
6
  import { taskManager, } from './manager.js';
6
7
  import { compact, tryReadToolStructuredError, } from './owner.js';
8
+ import { getTaskCapableTool, hasTaskCapableTool, } from './tool-registry.js';
7
9
  /* -------------------------------------------------------------------------------------------------
8
10
  * Abort-controller management for in-flight task executions
9
11
  * ------------------------------------------------------------------------------------------------- */
10
12
  // Intentionally process-global (not session-scoped): abortAllTaskExecutions() is called
11
13
  // during SIGTERM/SIGINT shutdown to cancel every in-flight task across all sessions.
12
- // TODO: consider per-session isolation if stricter task-ownership semantics are needed.
14
+ // Keep this process-global so shutdown can cancel all in-flight tasks across sessions.
15
+ // Per-session isolation would require a different cancellation fan-out strategy.
13
16
  const taskAbortControllers = new Map();
14
17
  function attachAbortController(taskId) {
15
18
  const existing = taskAbortControllers.get(taskId);
16
19
  if (existing) {
20
+ // Abort the previous controller before replacing it — avoids stranding
21
+ // a running fetch that can no longer be cancelled via abortTaskExecution().
22
+ existing.abort();
17
23
  taskAbortControllers.delete(taskId);
18
24
  }
19
25
  const controller = new AbortController();
@@ -72,43 +78,56 @@ export function withRelatedTaskMeta(result, taskId) {
72
78
  };
73
79
  }
74
80
  export function emitTaskStatusNotification(server, task) {
81
+ if (!config.tasks.emitStatusNotifications)
82
+ return;
75
83
  if (!server.isConnected())
76
84
  return;
77
- // NOTE: 'notifications/tasks/status' is not part of the MCP v2025-11-25 specification.
78
- // 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.
79
88
  void server.server
80
89
  .notification({
81
90
  method: 'notifications/tasks/status',
82
- params: toTaskSummary(task),
91
+ params: buildTaskStatusNotificationParams(task),
83
92
  })
84
93
  .catch((error) => {
85
94
  logWarn('Failed to send task status notification', {
86
95
  taskId: task.taskId,
87
96
  status: task.status,
88
- error,
97
+ error: getErrorMessage(error),
89
98
  });
90
99
  });
91
100
  }
101
+ function buildTaskStatusNotificationParams(task) {
102
+ return {
103
+ taskId: task.taskId,
104
+ status: task.status,
105
+ ...(task.statusMessage ? { statusMessage: task.statusMessage } : {}),
106
+ createdAt: task.createdAt,
107
+ lastUpdatedAt: task.lastUpdatedAt,
108
+ ttl: task.ttl,
109
+ pollInterval: task.pollInterval,
110
+ };
111
+ }
92
112
  /* -------------------------------------------------------------------------------------------------
93
113
  * Validation helpers
94
114
  * ------------------------------------------------------------------------------------------------- */
95
- function requireFetchUrlArgs(args) {
96
- const parsed = fetchUrlInputSchema.safeParse(args);
97
- if (!parsed.success) {
98
- throw new McpError(ErrorCode.InvalidParams, 'Invalid arguments for fetch-url');
99
- }
100
- return parsed.data;
101
- }
102
- // -32002 is the MCP extension code for resource-not-found; the SDK ErrorCode enum does not export it.
103
- const RESOURCE_NOT_FOUND_ERROR_CODE = -32002;
104
115
  export function throwTaskNotFound() {
105
116
  throw new McpError(RESOURCE_NOT_FOUND_ERROR_CODE, 'Task not found');
106
117
  }
107
- function requireFetchUrlToolName(name) {
108
- if (name === FETCH_URL_TOOL_NAME)
109
- return;
118
+ function resolveTaskCapableTool(name) {
119
+ const descriptor = getTaskCapableTool(name);
120
+ if (descriptor)
121
+ return descriptor;
110
122
  throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: '${name}'`);
111
123
  }
124
+ // Validates that the tool name is recognized before we attempt to execute it.
125
+ // This ensures that an unknown tool produces a MethodNotFound error, rather than potentially executing and failing with an internal error if the tool handler does not properly validate its input.
126
+ function assertKnownTool(name) {
127
+ if (!hasTaskCapableTool(name)) {
128
+ throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: '${name}'`);
129
+ }
130
+ }
112
131
  /* -------------------------------------------------------------------------------------------------
113
132
  * Task result builders
114
133
  * ------------------------------------------------------------------------------------------------- */
@@ -143,18 +162,60 @@ function updateWorkingTaskStatus(server, taskId, statusMessage) {
143
162
  return;
144
163
  if (current.statusMessage === statusMessage)
145
164
  return;
146
- taskManager.updateTask(taskId, { statusMessage });
147
- const updated = taskManager.getTask(taskId);
148
- if (updated)
149
- emitTaskStatusNotification(server, updated);
150
- }
151
- async function runFetchTaskExecution(params) {
152
- const { server, taskId, args, meta, sendNotification } = params;
153
- return runWithRequestContext({ requestId: taskId, operationId: taskId }, async () => {
165
+ updateTaskAndEmitStatus(server, taskId, { statusMessage });
166
+ }
167
+ function updateTaskAndEmitStatus(server, taskId, update) {
168
+ taskManager.updateTask(taskId, update);
169
+ const task = taskManager.getTask(taskId);
170
+ if (task)
171
+ emitTaskStatusNotification(server, task);
172
+ }
173
+ function buildTaskFailureState(error) {
174
+ const statusMessage = getErrorMessage(error);
175
+ if (error instanceof McpError) {
176
+ return {
177
+ statusMessage,
178
+ error: {
179
+ code: error.code,
180
+ message: statusMessage,
181
+ data: error.data,
182
+ },
183
+ };
184
+ }
185
+ return {
186
+ statusMessage,
187
+ error: {
188
+ code: ErrorCode.InternalError,
189
+ message: statusMessage,
190
+ },
191
+ };
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
+ }
208
+ async function runTaskToolExecution(params) {
209
+ const { server, taskId, args, tool, meta, sessionId, sendNotification } = params;
210
+ return runWithRequestContext({
211
+ requestId: taskId,
212
+ operationId: taskId,
213
+ ...(sessionId ? { sessionId } : {}),
214
+ }, async () => {
154
215
  const controller = attachAbortController(taskId);
155
216
  try {
156
217
  const relatedMeta = buildRelatedTaskMeta(taskId, meta);
157
- const result = await fetchUrlToolHandler(args, {
218
+ const result = await tool.execute(args, {
158
219
  signal: controller.signal,
159
220
  requestId: taskId,
160
221
  _meta: relatedMeta,
@@ -163,40 +224,15 @@ async function runFetchTaskExecution(params) {
163
224
  updateWorkingTaskStatus(server, taskId, message);
164
225
  },
165
226
  });
166
- const isToolError = isObject(result) &&
167
- typeof result['isError'] === 'boolean' &&
168
- result['isError'];
169
- taskManager.updateTask(taskId, {
170
- status: isToolError ? 'failed' : 'completed',
171
- statusMessage: isToolError
172
- ? (tryReadToolStructuredError(result) ?? 'Tool execution failed')
173
- : 'Task completed successfully.',
174
- result,
175
- });
176
- const task = taskManager.getTask(taskId);
177
- if (task)
178
- emitTaskStatusNotification(server, task);
227
+ updateTaskAndEmitStatus(server, taskId, buildTaskCompletionUpdate(result));
179
228
  }
180
229
  catch (error) {
181
- const errorMessage = error instanceof Error ? error.message : String(error);
182
- const errorPayload = error instanceof McpError
183
- ? {
184
- code: error.code,
185
- message: errorMessage,
186
- data: error.data,
187
- }
188
- : {
189
- code: ErrorCode.InternalError,
190
- message: errorMessage,
191
- };
192
- taskManager.updateTask(taskId, {
230
+ const failure = buildTaskFailureState(error);
231
+ updateTaskAndEmitStatus(server, taskId, {
193
232
  status: 'failed',
194
- statusMessage: errorMessage,
195
- error: errorPayload,
233
+ statusMessage: failure.statusMessage,
234
+ error: failure.error,
196
235
  });
197
- const task = taskManager.getTask(taskId);
198
- if (task)
199
- emitTaskStatusNotification(server, task);
200
236
  }
201
237
  finally {
202
238
  clearTaskExecution(taskId);
@@ -204,38 +240,38 @@ async function runFetchTaskExecution(params) {
204
240
  });
205
241
  }
206
242
  function handleTaskToolCall(server, params, context) {
207
- requireFetchUrlToolName(params.name);
208
- const validArgs = requireFetchUrlArgs(params.arguments);
243
+ const { tool, args } = resolveToolAndArgs(params);
209
244
  const task = taskManager.createTask(params.task?.ttl !== undefined ? { ttl: params.task.ttl } : undefined, 'Task started', context.ownerKey);
210
- void runFetchTaskExecution({
245
+ void runTaskToolExecution({
211
246
  server,
212
247
  taskId: task.taskId,
213
- args: validArgs,
248
+ args,
249
+ tool,
214
250
  ...compact({
215
251
  meta: params._meta,
252
+ sessionId: context.sessionId,
216
253
  sendNotification: context.sendNotification,
217
254
  }),
218
255
  });
219
256
  return buildCreateTaskResult(toTaskSummary(task));
220
257
  }
221
258
  async function handleDirectToolCall(params, context) {
222
- const args = requireFetchUrlArgs(params.arguments);
259
+ const { tool, args } = resolveToolAndArgs(params);
223
260
  const extra = compact({
224
261
  signal: context.signal,
225
262
  requestId: context.requestId,
226
263
  sendNotification: context.sendNotification,
227
264
  _meta: params._meta,
228
265
  });
229
- return fetchUrlToolHandler(args, extra);
266
+ return tool.execute(args, extra);
230
267
  }
231
268
  export async function handleToolCallRequest(server, request, context) {
232
269
  const { params } = request;
270
+ // Validate the tool name first so an unknown tool always produces MethodNotFound,
271
+ // regardless of whether a task:{} param was supplied (H-4).
272
+ assertKnownTool(params.name);
233
273
  if (params.task) {
234
274
  return handleTaskToolCall(server, params, context);
235
275
  }
236
- if (params.name === FETCH_URL_TOOL_NAME) {
237
- return handleDirectToolCall(params, context);
238
- }
239
- throw new McpError(ErrorCode.MethodNotFound, `Unknown tool: ${params.name}`);
276
+ return handleDirectToolCall(params, context);
240
277
  }
241
- //# sourceMappingURL=execution.js.map
@@ -40,15 +40,20 @@ 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;
50
+ private releaseOwnerCount;
47
51
  private countTasksForOwner;
48
52
  private incrementOwnerCount;
49
53
  private decrementOwnerCount;
50
54
  private assertTaskCapacity;
51
55
  createTask(options?: CreateTaskOptions, statusMessage?: string, ownerKey?: string): TaskState;
56
+ private lookupActiveTask;
52
57
  getTask(taskId: string, ownerKey?: string): TaskState | undefined;
53
58
  updateTask(taskId: string, updates: Partial<Omit<TaskState, 'taskId' | 'createdAt'>>): void;
54
59
  cancelTask(taskId: string, ownerKey?: string): TaskState | undefined;
@@ -62,9 +67,13 @@ declare class TaskManager {
62
67
  tasks: TaskState[];
63
68
  nextCursor?: string;
64
69
  };
70
+ private resolveAnchorTaskId;
71
+ private addWaiter;
72
+ private removeWaiter;
65
73
  waitForTerminalTask(taskId: string, ownerKey: string, signal?: AbortSignal): Promise<TaskState | undefined>;
66
74
  private notifyWaiters;
67
75
  private isExpired;
76
+ private maybeUpdateLastUpdatedAt;
68
77
  shrinkTtlAfterDelivery(taskId: string): void;
69
78
  private encodeCursor;
70
79
  private decodeCursor;
@@ -72,4 +81,3 @@ declare class TaskManager {
72
81
  }
73
82
  export declare const taskManager: TaskManager;
74
83
  export {};
75
- //# sourceMappingURL=manager.d.ts.map