@j0hanz/fetch-url-mcp 1.12.13 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +815 -809
  3. package/dist/assets/logo.svg +53 -53
  4. package/dist/http/auth.d.ts +7 -3
  5. package/dist/http/auth.d.ts.map +1 -1
  6. package/dist/http/auth.js +65 -37
  7. package/dist/http/helpers.d.ts +21 -0
  8. package/dist/http/helpers.d.ts.map +1 -0
  9. package/dist/http/helpers.js +31 -0
  10. package/dist/http/index.d.ts +1 -0
  11. package/dist/http/index.d.ts.map +1 -1
  12. package/dist/http/index.js +1 -0
  13. package/dist/http/native.d.ts +6 -24
  14. package/dist/http/native.d.ts.map +1 -1
  15. package/dist/http/native.js +141 -86
  16. package/dist/http/rate-limit.d.ts +1 -1
  17. package/dist/http/rate-limit.d.ts.map +1 -1
  18. package/dist/http/rate-limit.js +3 -9
  19. package/dist/index.js +0 -0
  20. package/dist/lib/config.d.ts +1 -0
  21. package/dist/lib/config.d.ts.map +1 -1
  22. package/dist/lib/config.js +1 -0
  23. package/dist/lib/core.d.ts +4 -3
  24. package/dist/lib/core.d.ts.map +1 -1
  25. package/dist/lib/core.js +4 -1
  26. package/dist/lib/error/classify.d.ts.map +1 -1
  27. package/dist/lib/error/classify.js +27 -6
  28. package/dist/lib/error/payload.d.ts +2 -2
  29. package/dist/lib/error/payload.d.ts.map +1 -1
  30. package/dist/lib/error/payload.js +2 -2
  31. package/dist/lib/mcp-interop.d.ts +9 -108
  32. package/dist/lib/mcp-interop.d.ts.map +1 -1
  33. package/dist/lib/mcp-interop.js +39 -240
  34. package/dist/lib/net/http.d.ts.map +1 -1
  35. package/dist/lib/net/http.js +4 -17
  36. package/dist/resources/index.d.ts +1 -1
  37. package/dist/resources/index.d.ts.map +1 -1
  38. package/dist/resources/index.js +67 -60
  39. package/dist/server.d.ts +1 -1
  40. package/dist/server.d.ts.map +1 -1
  41. package/dist/server.js +39 -34
  42. package/dist/tasks/adapter.d.ts +15 -0
  43. package/dist/tasks/adapter.d.ts.map +1 -0
  44. package/dist/tasks/adapter.js +138 -0
  45. package/dist/tasks/manager.d.ts +14 -82
  46. package/dist/tasks/manager.d.ts.map +1 -1
  47. package/dist/tasks/manager.js +48 -607
  48. package/dist/tasks/store.d.ts +33 -19
  49. package/dist/tasks/store.d.ts.map +1 -1
  50. package/dist/tasks/store.js +143 -80
  51. package/dist/tools/index.d.ts +7 -13
  52. package/dist/tools/index.d.ts.map +1 -1
  53. package/dist/tools/index.js +153 -58
  54. package/dist/transform/index.js +1 -1
  55. package/package.json +110 -108
@@ -1,72 +1,13 @@
1
- import {} from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { CallToolRequestSchema, ErrorCode, McpError, } from '@modelcontextprotocol/sdk/types.js';
3
- import { hash, randomUUID } from 'node:crypto';
1
+ import { ProtocolErrorCode, RELATED_TASK_META_KEY, } from '@modelcontextprotocol/server';
2
+ import { hash } from 'node:crypto';
4
3
  import { z } from 'zod';
5
4
  import { config } from '../lib/config.js';
6
- import { getRequestId, logDebug, logError, Loggers, logInfo, logWarn, resolveMcpSessionOwnerKey, runWithRequestContext, runWithTraceContext, } from '../lib/core.js';
7
- import { getErrorMessage, handleToolError, stripMcpErrorPrefix, tryReadToolErrorMessage, } from '../lib/error/index.js';
8
- import { createMcpError, getSdkCallToolHandler, registerServerLifecycleCleanup, } from '../lib/mcp-interop.js';
9
- import { formatZodError, isObject } from '../lib/utils.js';
5
+ import { logDebug, logError, Loggers, logWarn, resolveMcpSessionOwnerKey, } from '../lib/core.js';
6
+ import { getErrorMessage } from '../lib/error/index.js';
7
+ import { createProtocolError } from '../lib/mcp-interop.js';
8
+ import { isObject } from '../lib/utils.js';
10
9
  import { decodeTaskCursor, encodeTaskCursor, taskManager, TaskWaiterRegistry, waitForTerminalTask, } from './store.js';
11
- /*
12
- * Module map:
13
- * - call-tool request parsing
14
- * - Abort-controller management for in-flight task executions
15
- * - Task notification and validation helpers
16
- * - Execution pipeline
17
- * - Task handler schemas and registration
18
- * - Handler extra parsing & owner-key resolution
19
- * Own task lifecycle and MCP task wiring here. Keep tool business logic and HTTP transport details elsewhere.
20
- */
21
- const MIN_TASK_KEEP_ALIVE_MS = 1_000;
22
- const MAX_TASK_KEEP_ALIVE_MS = 86_400_000;
23
- const RELATED_TASK_META_KEY = 'modelcontextprotocol.io/related-task';
24
- const taskMetaSchema = z.strictObject({
25
- taskId: z.string().min(1, 'Task id required'),
26
- keepAlive: z
27
- .number()
28
- .int()
29
- .min(MIN_TASK_KEEP_ALIVE_MS, `Minimum ${MIN_TASK_KEEP_ALIVE_MS}ms`)
30
- .max(MAX_TASK_KEEP_ALIVE_MS, `Maximum ${MAX_TASK_KEEP_ALIVE_MS}ms`)
31
- .optional(),
32
- });
33
- const relatedTaskMetaSchema = z.strictObject({
34
- taskId: z.string(),
35
- });
36
- const toolCallMetaSchema = z.looseObject({
37
- progressToken: z.union([z.string(), z.number()]).optional(),
38
- 'modelcontextprotocol.io/task': taskMetaSchema.optional(),
39
- [RELATED_TASK_META_KEY]: relatedTaskMetaSchema.optional(),
40
- });
41
- const sdkTaskParamsSchema = z.object({ ttl: z.number().optional() }).optional();
42
- export const extendedCallToolRequestSchema = z.looseObject({
43
- method: z.literal('tools/call'),
44
- params: z.strictObject({
45
- name: z.string().min(1, 'Tool name required'),
46
- arguments: z.record(z.string(), z.unknown()).optional(),
47
- _meta: toolCallMetaSchema.optional(),
48
- task: sdkTaskParamsSchema,
49
- }),
50
- });
51
10
  export { decodeTaskCursor, encodeTaskCursor, taskManager, TaskWaiterRegistry, waitForTerminalTask, };
52
- function getTaskMeta(params) {
53
- const legacyMeta = params._meta?.['modelcontextprotocol.io/task'];
54
- if (legacyMeta)
55
- return legacyMeta;
56
- if (params.task) {
57
- return {
58
- taskId: randomUUID(),
59
- ...(params.task.ttl !== undefined ? { keepAlive: params.task.ttl } : {}),
60
- };
61
- }
62
- return undefined;
63
- }
64
- export function parseExtendedCallToolRequest(request) {
65
- const parsed = extendedCallToolRequestSchema.safeParse(request);
66
- if (parsed.success)
67
- return parsed.data;
68
- throw createMcpError(ErrorCode.InvalidParams, formatZodError(parsed.error));
69
- }
70
11
  export function sanitizeToolCallMeta(meta) {
71
12
  if (!meta)
72
13
  return undefined;
@@ -103,14 +44,14 @@ function withRelatedTaskSummaryMeta(payload, taskId) {
103
44
  // Intentionally process-global (not session-scoped): abortAllTaskExecutions() is called
104
45
  // during SIGTERM/SIGINT shutdown to cancel every in-flight task across all sessions.
105
46
  const taskAbortControllers = new Map();
106
- function detachAbortController(taskId) {
47
+ export function detachAbortController(taskId) {
107
48
  const controller = taskAbortControllers.get(taskId);
108
49
  if (controller) {
109
50
  taskAbortControllers.delete(taskId);
110
51
  }
111
52
  return controller;
112
53
  }
113
- function attachAbortController(taskId) {
54
+ export function attachAbortController(taskId) {
114
55
  detachAbortController(taskId)?.abort();
115
56
  if (taskAbortControllers.size >= config.tasks.maxTotal) {
116
57
  logWarn('Abort controller map reached task capacity — possible leak', {
@@ -143,14 +84,10 @@ export function toTaskSummary(task) {
143
84
  taskId: task.taskId,
144
85
  status: task.status,
145
86
  ...(task.statusMessage ? { statusMessage: task.statusMessage } : {}),
146
- ...(task.progress !== undefined ? { progress: task.progress } : {}),
147
- ...(task.total !== undefined ? { total: task.total } : {}),
148
87
  createdAt: task.createdAt,
149
88
  lastUpdatedAt: task.lastUpdatedAt,
150
- keepAlive: task.keepAlive,
151
- pollFrequency: task.pollFrequency,
152
- ttl: task.keepAlive,
153
- pollInterval: task.pollFrequency,
89
+ ttl: task.ttl,
90
+ pollInterval: task.pollInterval,
154
91
  };
155
92
  }
156
93
  export function emitTaskStatusNotification(server, task) {
@@ -169,444 +106,51 @@ export function emitTaskStatusNotification(server, task) {
169
106
  }, Loggers.LOG_TASKS);
170
107
  });
171
108
  }
172
- function emitTaskCreatedNotification(server, task) {
173
- if (!config.tasks.emitStatusNotifications || !server.isConnected())
174
- return;
175
- void server.server
176
- .notification({
177
- method: 'notifications/tasks/created',
178
- params: {
179
- _meta: {
180
- [RELATED_TASK_META_KEY]: { taskId: task.taskId },
181
- },
182
- },
183
- })
184
- .catch((error) => {
185
- logError('Failed to send task created notification', {
186
- taskId: task.taskId,
187
- error: getErrorMessage(error),
188
- }, Loggers.LOG_TASKS);
189
- });
190
- }
191
109
  export function throwTaskNotFound() {
192
- throw createMcpError(ErrorCode.InvalidParams, 'Task not found');
193
- }
194
- /* -------------------------------------------------------------------------------------------------
195
- * Execution pipeline
196
- * ------------------------------------------------------------------------------------------------- */
197
- function updateTaskAndEmitStatus(server, taskId, update) {
198
- taskManager.updateTask(taskId, update);
199
- const task = taskManager.getTask(taskId);
200
- if (task)
201
- emitTaskStatusNotification(server, task);
202
- }
203
- function buildTaskFailureState(error) {
204
- const mcpErrorMessage = error instanceof McpError ? stripMcpErrorPrefix(error.message) : undefined;
205
- const statusMessage = mcpErrorMessage ?? getErrorMessage(error);
206
- if (error instanceof McpError) {
207
- return {
208
- status: 'failed',
209
- statusMessage,
210
- error: {
211
- code: error.code,
212
- ...(error.data !== undefined ? { data: error.data } : {}),
213
- message: statusMessage,
214
- },
215
- };
216
- }
217
- return {
218
- status: 'failed',
219
- statusMessage,
220
- error: {
221
- code: ErrorCode.InternalError,
222
- message: statusMessage,
223
- },
224
- };
225
- }
226
- function buildTaskCompletionUpdate(result, tool) {
227
- const isError = isObject(result) && 'isError' in result && result.isError === true;
228
- const errorMessage = tryReadToolErrorMessage(result) ?? 'Execution failed';
229
- return {
230
- status: isError ? 'failed' : 'completed',
231
- statusMessage: isError
232
- ? errorMessage
233
- : (tool.getCompletionStatusMessage?.(result) ??
234
- 'Task completed successfully.'),
235
- result,
236
- ...(isError
237
- ? {
238
- error: {
239
- code: ErrorCode.InternalError,
240
- message: errorMessage,
241
- },
242
- }
243
- : {}),
244
- };
245
- }
246
- async function runTaskToolExecution(params) {
247
- const { server, taskId, args, tool, meta, sessionId, sendNotification } = params;
248
- return runWithRequestContext({
249
- requestId: taskId,
250
- operationId: taskId,
251
- ...(sessionId ? { sessionId } : {}),
252
- }, () => runWithTraceContext(meta, async () => {
253
- const controller = attachAbortController(taskId);
254
- const progressState = { closed: false };
255
- try {
256
- updateTaskAndEmitStatus(server, taskId, {
257
- status: 'working',
258
- statusMessage: 'Task started',
259
- });
260
- logInfo('Task execution started', { taskId, tool: tool.name }, Loggers.LOG_TASKS);
261
- const relatedMeta = buildRelatedTaskMeta(taskId, meta);
262
- const result = await tool.execute(args, {
263
- signal: controller.signal,
264
- requestId: taskId,
265
- _meta: relatedMeta,
266
- progressState,
267
- canReportProgress: () => taskManager.getTask(taskId)?.status === 'working',
268
- ...compact({ sendNotification }),
269
- onProgress: (progress, message, total) => {
270
- const current = taskManager.getTask(taskId);
271
- if (current?.status === 'working' &&
272
- (current.statusMessage !== message ||
273
- current.progress !== progress ||
274
- (total !== undefined && current.total !== total))) {
275
- updateTaskAndEmitStatus(server, taskId, {
276
- statusMessage: message,
277
- progress,
278
- ...(total !== undefined ? { total } : {}),
279
- });
280
- }
281
- },
282
- });
283
- const completionUpdate = buildTaskCompletionUpdate(result, tool);
284
- updateTaskAndEmitStatus(server, taskId, completionUpdate);
285
- if (completionUpdate.status === 'completed') {
286
- logInfo('Task execution completed', { taskId, tool: tool.name }, Loggers.LOG_TASKS);
287
- }
288
- else {
289
- logWarn('Task execution completed with tool error result', { taskId, tool: tool.name }, Loggers.LOG_TASKS);
290
- }
291
- }
292
- catch (error) {
293
- logError('Task execution failed', {
294
- taskId,
295
- tool: tool.name,
296
- error: getErrorMessage(error),
297
- }, Loggers.LOG_TASKS);
298
- updateTaskAndEmitStatus(server, taskId, buildTaskFailureState(error));
299
- }
300
- finally {
301
- progressState.closed = true;
302
- detachAbortController(taskId);
303
- }
304
- }));
305
- }
306
- function extractRawUrl(args) {
307
- const url = args?.['url'];
308
- return typeof url === 'string' ? url : 'unknown';
309
- }
310
- function tryParseArguments(tool, args) {
311
- try {
312
- return { ok: true, value: tool.parseArguments(args) };
313
- }
314
- catch (error) {
315
- if (error instanceof McpError) {
316
- return {
317
- ok: false,
318
- response: handleToolError(error, extractRawUrl(args)),
319
- };
320
- }
321
- throw error;
322
- }
323
- }
324
- function validateTaskSupport(server, toolName, isTaskMode) {
325
- const support = getTaskCapableToolSupport(server, toolName);
326
- if (isTaskMode && support === 'forbidden') {
327
- throw createMcpError(ErrorCode.MethodNotFound, `Task mode is not supported for tool: ${toolName}`);
328
- }
329
- if (!isTaskMode && support === 'required') {
330
- throw createMcpError(ErrorCode.MethodNotFound, `Task mode is required for tool: ${toolName}`);
331
- }
332
- }
333
- function enqueueTaskToolExecution(server, tool, params, taskMeta, context, parsedArgs) {
334
- const task = taskManager.createTask({
335
- taskId: taskMeta.taskId,
336
- ...(taskMeta.keepAlive !== undefined
337
- ? { keepAlive: taskMeta.keepAlive }
338
- : {}),
339
- }, 'Task submitted', context.ownerKey);
340
- emitTaskCreatedNotification(server, task);
341
- logInfo('Task execution queued', {
342
- taskId: task.taskId,
343
- tool: params.name,
344
- ...(taskMeta.keepAlive !== undefined
345
- ? { keepAlive: taskMeta.keepAlive }
346
- : {}),
347
- }, Loggers.LOG_TASKS);
348
- void runTaskToolExecution({
349
- server,
350
- taskId: task.taskId,
351
- args: parsedArgs,
352
- tool,
353
- ...compact({
354
- meta: params._meta,
355
- sessionId: context.sessionId,
356
- sendNotification: context.sendNotification,
357
- }),
358
- });
359
- return {
360
- task: toTaskSummary(task),
361
- ...(tool.immediateResponse
362
- ? {
363
- _meta: {
364
- 'io.modelcontextprotocol/model-immediate-response': tool.immediateResponse,
365
- },
366
- }
367
- : {}),
368
- };
369
- }
370
- export async function handleToolCallRequest(server, request, context) {
371
- const { params } = request;
372
- return runWithTraceContext(params._meta, async () => {
373
- // Validate the tool name first so an unknown tool always produces MethodNotFound
374
- const tool = getTaskCapableTool(server, params.name);
375
- if (!tool) {
376
- throw createMcpError(ErrorCode.MethodNotFound, `Unknown tool: ${params.name}`);
377
- }
378
- const taskMeta = getTaskMeta(params);
379
- validateTaskSupport(server, params.name, !!taskMeta);
380
- const parsed = tryParseArguments(tool, params.arguments);
381
- if (!parsed.ok)
382
- return parsed.response;
383
- if (taskMeta) {
384
- return enqueueTaskToolExecution(server, tool, params, taskMeta, context, parsed.value);
385
- }
386
- const progressState = { closed: false };
387
- logDebug('Executing task-capable tool inline', {
388
- tool: params.name,
389
- hasProgressToken: params._meta?.progressToken !== undefined,
390
- }, Loggers.LOG_TASKS);
391
- try {
392
- return await tool.execute(parsed.value, {
393
- ...buildToolHandlerExtra(context, params._meta),
394
- progressState,
395
- });
396
- }
397
- finally {
398
- progressState.closed = true;
399
- }
400
- });
110
+ throw createProtocolError(ProtocolErrorCode.ResourceNotFound, 'Task not found');
401
111
  }
402
112
  /* -------------------------------------------------------------------------------------------------
403
113
  * Task handler schemas and registration
404
114
  * ------------------------------------------------------------------------------------------------- */
405
- const TaskGetSchema = z.looseObject({
406
- method: z.literal('tasks/get', 'Expected "tasks/get"'),
407
- params: z.looseObject({ taskId: z.string('Expected string') }, 'Expected object'),
408
- }, 'Invalid request');
409
- const TaskListSchema = z.looseObject({
410
- method: z.literal('tasks/list', 'Expected "tasks/list"'),
411
- params: z
412
- .looseObject({
413
- cursor: z.string('Expected string').optional(),
414
- }, 'Expected object')
415
- .optional(),
416
- }, 'Invalid request');
417
- const TaskCancelSchema = z.looseObject({
418
- method: z.literal('tasks/cancel', 'Expected "tasks/cancel"'),
419
- params: z.looseObject({ taskId: z.string('Expected string') }, 'Expected object'),
420
- }, 'Invalid request');
421
115
  const TaskDeleteSchema = z.looseObject({
422
116
  method: z.literal('tasks/delete', 'Expected "tasks/delete"'),
423
117
  params: z.looseObject({ taskId: z.string('Expected string') }, 'Expected object'),
424
118
  }, 'Invalid request');
425
- const TaskResultSchema = z.looseObject({
426
- method: z.literal('tasks/result', 'Expected "tasks/result"'),
427
- params: z.looseObject({ taskId: z.string('Expected string') }, 'Expected object'),
428
- }, 'Invalid request');
429
- function resolveOwnerScopedExtra(extra) {
430
- const parsedExtra = parseHandlerExtra(extra);
431
- return {
432
- parsedExtra,
433
- ownerKey: resolveTaskOwnerKey(parsedExtra),
434
- };
435
- }
436
- function throwStoredTaskError(task) {
437
- if (task.error) {
438
- throw createMcpError(task.error.code, task.error.message, task.error.data);
439
- }
440
- throw createMcpError(ErrorCode.InternalError, task.statusMessage ?? 'Execution failed', { taskId: task.taskId });
441
- }
442
- export function registerTaskHandlers(server, options) {
443
- const sdkCallToolHandler = getSdkCallToolHandler(server);
444
- const taskCapableToolsRegistered = hasRegisteredTaskCapableTools(server);
445
- const requireInterception = options?.requireInterception ?? true;
446
- if (!sdkCallToolHandler) {
447
- if (taskCapableToolsRegistered && requireInterception) {
448
- throw Error('Task-capable tools are registered but SDK tools/call interception is unavailable. Upgrade compatibility or disable strict interception with TASKS_REQUIRE_INTERCEPTION=false.');
449
- }
450
- logWarn('Task call interception disabled: SDK tools/call handler unavailable; task-capable tools require MCP SDK compatibility update', { sdkVersion: 'unknown' }, Loggers.LOG_TASKS);
451
- }
452
- if (sdkCallToolHandler) {
453
- server.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
454
- const parsedExtra = parseHandlerExtra(extra);
455
- const requestId = parsedExtra?.requestId !== undefined
456
- ? String(parsedExtra.requestId)
457
- : randomUUID();
458
- return runWithRequestContext({
459
- requestId,
460
- operationId: requestId,
461
- ...(parsedExtra?.sessionId
462
- ? { sessionId: parsedExtra.sessionId }
463
- : {}),
464
- }, () => {
465
- const toolName = request.params.name;
466
- // Only intercept task-capable tools managed by the local task registry.
467
- // Delegate all other tools to the SDK handler to avoid shadowing future tools.
468
- if (!hasTaskCapableTool(server, toolName)) {
469
- return sdkCallToolHandler(request, extra);
470
- }
471
- const parsed = parseExtendedCallToolRequest(request);
472
- const context = resolveToolCallContext(parsedExtra, parsed.params._meta);
473
- logDebug('Intercepted task-capable tool call', {
474
- tool: toolName,
475
- taskRequested: getTaskMeta(parsed.params) !== undefined,
476
- hasProgressToken: parsed.params._meta?.progressToken !== undefined,
477
- }, Loggers.LOG_TASKS);
478
- return handleToolCallRequest(server, parsed, context);
479
- });
480
- });
481
- }
482
- server.server.setRequestHandler(TaskGetSchema, (request, extra) => {
483
- const { taskId } = request.params;
484
- const { ownerKey } = resolveOwnerScopedExtra(extra);
485
- logDebug('tasks/get requested', { taskId }, Loggers.LOG_TASKS);
486
- const task = taskManager.getTask(taskId, ownerKey);
487
- if (!task)
488
- throwTaskNotFound();
489
- return withRelatedTaskSummaryMeta(toTaskSummary(task), task.taskId);
490
- });
491
- server.server.setRequestHandler(TaskResultSchema, (request, extra) => {
492
- const { taskId } = request.params;
493
- const { ownerKey } = resolveOwnerScopedExtra(extra);
494
- logDebug('tasks/result requested', { taskId }, Loggers.LOG_TASKS);
495
- const task = taskManager.getTask(taskId, ownerKey);
496
- if (!task)
497
- throwTaskNotFound();
498
- if (task.status === 'submitted' || task.status === 'working') {
499
- throw createMcpError(ErrorCode.InvalidParams, 'Task result is not available until the task completes', { taskId, status: task.status });
500
- }
501
- if (task.status === 'input_required') {
502
- throw createMcpError(ErrorCode.InvalidParams, 'Task result is not available while the task is waiting for input', { taskId, status: task.status });
503
- }
504
- try {
505
- if (task.status === 'cancelled') {
506
- throwStoredTaskError(task);
507
- }
508
- if (task.status === 'failed') {
509
- throwStoredTaskError(task);
510
- }
511
- const result = isServerResult(task.result)
512
- ? task.result
513
- : { content: [] };
514
- return withRelatedTaskMeta(result, task.taskId);
515
- }
516
- finally {
517
- // Shrink keepAlive only after the result has been fully constructed
518
- // and is about to be delivered — avoids premature expiry if result
519
- // construction throws.
520
- taskManager.shrinkKeepAliveAfterDelivery(taskId);
119
+ export function registerTaskHandlers(server) {
120
+ const taskCapableToolsRegistered = config.tools.enabled.includes('fetch-url');
121
+ server.server.fallbackRequestHandler = (request, ctx) => {
122
+ if (request.method !== 'tasks/delete') {
123
+ throw createProtocolError(ProtocolErrorCode.MethodNotFound, `Method not found: ${request.method}`);
521
124
  }
522
- });
523
- server.server.setRequestHandler(TaskListSchema, (request, extra) => {
524
- const { ownerKey } = resolveOwnerScopedExtra(extra);
525
- const cursor = request.params?.cursor;
526
- logDebug('tasks/list requested', { hasCursor: cursor !== undefined }, Loggers.LOG_TASKS);
527
- const { tasks, nextCursor } = taskManager.listTasks(cursor === undefined ? { ownerKey } : { ownerKey, cursor });
528
- return {
529
- tasks: tasks.map((task) => withRelatedTaskSummaryMeta(toTaskSummary(task), task.taskId)),
530
- nextCursor,
531
- };
532
- });
533
- server.server.setRequestHandler(TaskCancelSchema, (request, extra) => {
534
- const { taskId } = request.params;
535
- const { ownerKey } = resolveOwnerScopedExtra(extra);
536
- logDebug('tasks/cancel requested', { taskId }, Loggers.LOG_TASKS);
537
- const task = taskManager.cancelTask(taskId, ownerKey);
538
- if (!task)
539
- throwTaskNotFound();
540
- abortTaskExecution(taskId);
541
- emitTaskStatusNotification(server, task);
542
- return withRelatedTaskSummaryMeta(toTaskSummary(task), task.taskId);
543
- });
544
- server.server.setRequestHandler(TaskDeleteSchema, (request, extra) => {
545
- const { taskId } = request.params;
546
- const { ownerKey } = resolveOwnerScopedExtra(extra);
125
+ const parsedRequest = TaskDeleteSchema.parse(request);
126
+ const { taskId } = parsedRequest.params;
127
+ const ownerKey = resolveOwnerKeyFromContext(ctx);
547
128
  logDebug('tasks/delete requested', { taskId }, Loggers.LOG_TASKS);
548
129
  const deleted = taskManager.deleteTask(taskId, ownerKey);
549
130
  if (!deleted)
550
131
  throwTaskNotFound();
551
- return {};
552
- });
132
+ return Promise.resolve({});
133
+ };
553
134
  return {
554
- interceptedToolsCall: sdkCallToolHandler !== null,
555
135
  taskCapableToolsRegistered,
556
136
  };
557
137
  }
558
- export function compact(obj) {
559
- const result = {};
560
- for (const key of Object.keys(obj)) {
561
- if (obj[key] !== undefined) {
562
- result[key] = obj[key];
563
- }
564
- }
565
- return result;
566
- }
567
- function normalizeSendNotification(sendNotification) {
568
- if (typeof sendNotification !== 'function')
138
+ function resolveAuthenticatedSubject(authInfo) {
139
+ const extra = isObject(authInfo?.extra) ? authInfo.extra : undefined;
140
+ if (!extra)
569
141
  return undefined;
570
- const notify = sendNotification;
571
- return async (notification) => {
572
- await Promise.resolve(notify(notification));
573
- };
574
- }
575
- function normalizeAuthInfo(authInfo) {
576
- if (!isObject(authInfo))
577
- return undefined;
578
- const { clientId, token } = authInfo;
579
- const normalized = {};
580
- if (typeof clientId === 'string')
581
- normalized.clientId = clientId;
582
- if (typeof token === 'string')
583
- normalized.token = token;
584
- return normalized.clientId || normalized.token ? normalized : undefined;
585
- }
586
- export function parseHandlerExtra(extra) {
587
- if (!isObject(extra))
588
- return undefined;
589
- const parsed = {};
590
- const { authInfo, signal, requestId, sendNotification } = extra;
591
- const sessionId = resolveSessionIdFromExtra(extra);
592
- if (sessionId)
593
- parsed.sessionId = sessionId;
594
- const normalizedAuthInfo = normalizeAuthInfo(authInfo);
595
- if (normalizedAuthInfo) {
596
- parsed.authInfo = normalizedAuthInfo;
142
+ const { subject, sub } = extra;
143
+ if (typeof subject === 'string' && subject.length > 0) {
144
+ return subject;
597
145
  }
598
- if (signal instanceof AbortSignal)
599
- parsed.signal = signal;
600
- if (typeof requestId === 'string' || typeof requestId === 'number') {
601
- parsed.requestId = requestId;
602
- }
603
- const normalizedSendNotification = normalizeSendNotification(sendNotification);
604
- if (normalizedSendNotification) {
605
- parsed.sendNotification = normalizedSendNotification;
606
- }
607
- return parsed;
146
+ return typeof sub === 'string' && sub.length > 0 ? sub : undefined;
608
147
  }
609
148
  export function buildAuthenticatedOwnerKey(authInfo) {
149
+ const authSubject = resolveAuthenticatedSubject(authInfo);
150
+ if (authSubject) {
151
+ const hashInput = `subject:${authSubject}`;
152
+ return `auth:${hash('sha256', hashInput, 'hex')}`;
153
+ }
610
154
  const authClientId = typeof authInfo?.clientId === 'string' ? authInfo.clientId : '';
611
155
  const authToken = typeof authInfo?.token === 'string' ? authInfo.token : '';
612
156
  if (authClientId || authToken) {
@@ -615,127 +159,24 @@ export function buildAuthenticatedOwnerKey(authInfo) {
615
159
  }
616
160
  return undefined;
617
161
  }
618
- export function resolveTaskOwnerKey(extra) {
619
- const authenticatedOwnerKey = buildAuthenticatedOwnerKey(extra?.authInfo);
620
- if (authenticatedOwnerKey)
621
- return authenticatedOwnerKey;
622
- if (extra?.sessionId) {
623
- return (resolveMcpSessionOwnerKey(extra.sessionId) ?? `session:${extra.sessionId}`);
162
+ export function resolveOwnerKeyFromContext(ctx) {
163
+ const authInfo = ctx.http?.authInfo;
164
+ if (authInfo) {
165
+ const identity = {
166
+ clientId: authInfo.clientId,
167
+ token: authInfo.token,
168
+ ...(authInfo.extra ? { extra: authInfo.extra } : {}),
169
+ };
170
+ const authenticatedOwnerKey = buildAuthenticatedOwnerKey(identity);
171
+ if (authenticatedOwnerKey)
172
+ return authenticatedOwnerKey;
173
+ }
174
+ const { sessionId } = ctx;
175
+ if (typeof sessionId === 'string' && sessionId.length > 0) {
176
+ return resolveMcpSessionOwnerKey(sessionId) ?? `session:${sessionId}`;
624
177
  }
625
178
  return 'default';
626
179
  }
627
- function resolveRequestIdFromExtra(extra) {
628
- if (!isObject(extra))
629
- return undefined;
630
- const { requestId } = extra;
631
- if (typeof requestId === 'string')
632
- return requestId;
633
- if (typeof requestId === 'number')
634
- return String(requestId);
635
- return undefined;
636
- }
637
- function getHeaderString(headers, name) {
638
- const value = headers[name];
639
- if (typeof value === 'string')
640
- return value;
641
- if (!Array.isArray(value))
642
- return undefined;
643
- return value.find((entry) => typeof entry === 'string');
644
- }
645
- function resolveSessionIdFromExtra(extra) {
646
- if (!isObject(extra))
647
- return undefined;
648
- const { sessionId } = extra;
649
- if (typeof sessionId === 'string')
650
- return sessionId;
651
- const { requestInfo } = extra;
652
- if (!isObject(requestInfo))
653
- return undefined;
654
- const { headers } = requestInfo;
655
- if (!isObject(headers))
656
- return undefined;
657
- return (getHeaderString(headers, 'mcp-session-id') ??
658
- getHeaderString(headers, 'x-mcp-session-id'));
659
- }
660
- function resolveToolExecutionContext(extra, requestMeta) {
661
- return compact({
662
- ownerKey: resolveTaskOwnerKey(extra),
663
- sessionId: extra?.sessionId,
664
- signal: extra?.signal,
665
- requestId: extra?.requestId,
666
- sendNotification: extra?.sendNotification,
667
- requestMeta: sanitizeToolCallMeta(requestMeta),
668
- });
669
- }
670
- export function resolveToolCallContext(extra, requestMeta) {
671
- return resolveToolExecutionContext(extra, requestMeta);
672
- }
673
- export function buildToolHandlerExtra(context, requestMeta) {
674
- return compact({
675
- signal: context.signal,
676
- requestId: context.requestId,
677
- sendNotification: context.sendNotification,
678
- _meta: sanitizeToolCallMeta(requestMeta ?? context.requestMeta),
679
- });
680
- }
681
- export function withRequestContextIfMissing(handler) {
682
- return async (params, extra) => {
683
- const existingRequestId = getRequestId();
684
- if (existingRequestId) {
685
- const traceMeta = isObject(extra) && isObject(extra['_meta'])
686
- ? extra['_meta']
687
- : undefined;
688
- return runWithTraceContext(traceMeta, () => handler(params, extra));
689
- }
690
- const derivedRequestId = resolveRequestIdFromExtra(extra) ?? randomUUID();
691
- const derivedSessionId = resolveSessionIdFromExtra(extra);
692
- const traceMeta = isObject(extra) && isObject(extra['_meta']) ? extra['_meta'] : undefined;
693
- return runWithRequestContext({
694
- requestId: derivedRequestId,
695
- operationId: derivedRequestId,
696
- ...(derivedSessionId ? { sessionId: derivedSessionId } : {}),
697
- }, () => runWithTraceContext(traceMeta, () => handler(params, extra)));
698
- };
699
- }
700
180
  export function isServerResult(value) {
701
181
  return isObject(value) && Array.isArray(value['content']);
702
182
  }
703
- const taskCapableToolsByServer = new WeakMap();
704
- function getServerToolMap(server) {
705
- let toolMap = taskCapableToolsByServer.get(server);
706
- if (toolMap)
707
- return toolMap;
708
- toolMap = new Map();
709
- taskCapableToolsByServer.set(server, toolMap);
710
- registerServerLifecycleCleanup(server, () => {
711
- taskCapableToolsByServer.delete(server);
712
- });
713
- return toolMap;
714
- }
715
- export function registerTaskCapableTool(server, descriptor) {
716
- getServerToolMap(server).set(descriptor.name, {
717
- ...descriptor,
718
- taskSupport: descriptor.taskSupport ?? 'optional',
719
- });
720
- }
721
- export function unregisterTaskCapableTool(server, name) {
722
- getServerToolMap(server).delete(name);
723
- }
724
- export function getTaskCapableTool(server, name) {
725
- return getServerToolMap(server).get(name);
726
- }
727
- export function getTaskCapableToolSupport(server, name) {
728
- return getServerToolMap(server).get(name)?.taskSupport;
729
- }
730
- export function hasTaskCapableTool(server, name) {
731
- return getServerToolMap(server).has(name);
732
- }
733
- export function hasRegisteredTaskCapableTools(server) {
734
- return getServerToolMap(server).size > 0;
735
- }
736
- export function setTaskCapableToolSupport(server, name, support) {
737
- const descriptor = getServerToolMap(server).get(name);
738
- if (!descriptor)
739
- return;
740
- descriptor.taskSupport = support;
741
- }