@apify/actors-mcp-server 0.11.7-beta.9 → 0.11.8-beta.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 (91) hide show
  1. package/dist/const.d.ts.map +1 -1
  2. package/dist/const.js +3 -1
  3. package/dist/const.js.map +1 -1
  4. package/dist/mcp/proxy.d.ts +1 -4
  5. package/dist/mcp/proxy.d.ts.map +1 -1
  6. package/dist/mcp/proxy.js +3 -8
  7. package/dist/mcp/proxy.js.map +1 -1
  8. package/dist/mcp/server.d.ts +10 -43
  9. package/dist/mcp/server.d.ts.map +1 -1
  10. package/dist/mcp/server.js +56 -430
  11. package/dist/mcp/server.js.map +1 -1
  12. package/dist/mcp/task_execution.d.ts +42 -0
  13. package/dist/mcp/task_execution.d.ts.map +1 -0
  14. package/dist/mcp/task_execution.js +310 -0
  15. package/dist/mcp/task_execution.js.map +1 -0
  16. package/dist/mcp/tool_call_telemetry.d.ts +37 -0
  17. package/dist/mcp/tool_call_telemetry.d.ts.map +1 -0
  18. package/dist/mcp/tool_call_telemetry.js +85 -0
  19. package/dist/mcp/tool_call_telemetry.js.map +1 -0
  20. package/dist/mcp/utils.d.ts +1 -2
  21. package/dist/mcp/utils.d.ts.map +1 -1
  22. package/dist/mcp/utils.js +1 -2
  23. package/dist/mcp/utils.js.map +1 -1
  24. package/dist/resources/widgets.d.ts.map +1 -1
  25. package/dist/resources/widgets.js +3 -2
  26. package/dist/resources/widgets.js.map +1 -1
  27. package/dist/telemetry.d.ts.map +1 -1
  28. package/dist/telemetry.js +3 -4
  29. package/dist/telemetry.js.map +1 -1
  30. package/dist/tools/actor_input_schema.d.ts +10 -20
  31. package/dist/tools/actor_input_schema.d.ts.map +1 -1
  32. package/dist/tools/actor_input_schema.js +10 -20
  33. package/dist/tools/actor_input_schema.js.map +1 -1
  34. package/dist/tools/actors/actor_definition.js +3 -5
  35. package/dist/tools/actors/actor_definition.js.map +1 -1
  36. package/dist/tools/actors/actor_tools_factory.d.ts +3 -8
  37. package/dist/tools/actors/actor_tools_factory.d.ts.map +1 -1
  38. package/dist/tools/actors/actor_tools_factory.js +5 -12
  39. package/dist/tools/actors/actor_tools_factory.js.map +1 -1
  40. package/dist/tools/actors/call_actor.d.ts.map +1 -1
  41. package/dist/tools/actors/call_actor.js +0 -1
  42. package/dist/tools/actors/call_actor.js.map +1 -1
  43. package/dist/tools/actors/fetch_actor_details.d.ts +2 -3
  44. package/dist/tools/actors/fetch_actor_details.d.ts.map +1 -1
  45. package/dist/tools/actors/fetch_actor_details.js +2 -3
  46. package/dist/tools/actors/fetch_actor_details.js.map +1 -1
  47. package/dist/tools/dev/report_problem.d.ts +13 -0
  48. package/dist/tools/dev/report_problem.d.ts.map +1 -1
  49. package/dist/tools/dev/report_problem.js +14 -0
  50. package/dist/tools/dev/report_problem.js.map +1 -1
  51. package/dist/tools/docs/search_apify_docs.d.ts.map +1 -1
  52. package/dist/tools/docs/search_apify_docs.js +0 -2
  53. package/dist/tools/docs/search_apify_docs.js.map +1 -1
  54. package/dist/tools/runs/get_actor_run_log.d.ts +1 -1
  55. package/dist/tools/runs/get_actor_run_log.js +1 -1
  56. package/dist/tools/storage/get_dataset_schema.d.ts.map +1 -1
  57. package/dist/tools/storage/get_dataset_schema.js +0 -1
  58. package/dist/tools/storage/get_dataset_schema.js.map +1 -1
  59. package/dist/tools/structured_output_schemas.d.ts +3 -0
  60. package/dist/tools/structured_output_schemas.d.ts.map +1 -1
  61. package/dist/tools/structured_output_schemas.js +29 -26
  62. package/dist/tools/structured_output_schemas.js.map +1 -1
  63. package/dist/tsconfig.tsbuildinfo +1 -1
  64. package/dist/types.d.ts +3 -6
  65. package/dist/types.d.ts.map +1 -1
  66. package/dist/utils/actor_card.d.ts.map +1 -1
  67. package/dist/utils/actor_card.js +0 -1
  68. package/dist/utils/actor_card.js.map +1 -1
  69. package/dist/utils/apify_docs.d.ts.map +1 -1
  70. package/dist/utils/apify_docs.js +0 -3
  71. package/dist/utils/apify_docs.js.map +1 -1
  72. package/dist/utils/auth.d.ts.map +1 -1
  73. package/dist/utils/auth.js +4 -14
  74. package/dist/utils/auth.js.map +1 -1
  75. package/dist/utils/logging.d.ts +2 -0
  76. package/dist/utils/logging.d.ts.map +1 -1
  77. package/dist/utils/logging.js +44 -0
  78. package/dist/utils/logging.js.map +1 -1
  79. package/dist/utils/progress.d.ts.map +1 -1
  80. package/dist/utils/progress.js +3 -2
  81. package/dist/utils/progress.js.map +1 -1
  82. package/dist/utils/tool_status.d.ts.map +1 -1
  83. package/dist/utils/tool_status.js +0 -1
  84. package/dist/utils/tool_status.js.map +1 -1
  85. package/dist/utils/ttl_lru.d.ts +2 -4
  86. package/dist/utils/ttl_lru.d.ts.map +1 -1
  87. package/dist/utils/ttl_lru.js +3 -8
  88. package/dist/utils/ttl_lru.js.map +1 -1
  89. package/manifest.json +1 -1
  90. package/package.json +1 -1
  91. package/server.json +1 -1
@@ -19,28 +19,28 @@ import { prompts } from '../prompts/index.js';
19
19
  import { createResourceService } from '../resources/resource_service.js';
20
20
  import { resolveAvailableWidgets } from '../resources/widgets.js';
21
21
  import { getServerInfo } from '../server_card.js';
22
- import { buildReportedProblemProperties, getTelemetryEnv, trackReportedProblem, trackToolCall } from '../telemetry.js';
22
+ import { getTelemetryEnv } from '../telemetry.js';
23
23
  import { decodeDotPropertyNames } from '../tools/actor_input_schema.js';
24
24
  import { legacyToolNameToNew } from '../tools/actor_tool_naming.js';
25
25
  import { checkPaymentProviderStandbyConflict } from '../tools/actors/call_actor.js';
26
- import { appendReportProblemNudge } from '../tools/dev/report_problem.js';
26
+ import { withReportProblemNudge } from '../tools/dev/report_problem.js';
27
27
  import { getActorsAsTools } from '../tools/index.js';
28
28
  import { SERVER_MODE, TOOL_TYPE } from '../types.js';
29
29
  import { isMcpClientFaultMessage, logHttpError, sanitizeMezmoMessage } from '../utils/logging.js';
30
- import { computeToolResponseBytes, respondOk } from '../utils/mcp.js';
30
+ import { respondOk } from '../utils/mcp.js';
31
31
  import { isReportProblemBlockedForClient } from '../utils/mcp_clients.js';
32
32
  import { createProgressTracker } from '../utils/progress.js';
33
33
  import { getServerInstructions } from '../utils/server-instructions/index.js';
34
34
  import { parseServerMode, resolveServerMode } from '../utils/server_mode.js';
35
- import { deriveResourceIds, extractAjvErrorDetails } from '../utils/tool_status.js';
35
+ import { extractAjvErrorDetails } from '../utils/tool_status.js';
36
36
  import { buildActorFields, extractActorId, extractActorName, getToolFullName, getToolPublicFieldOnly, } from '../utils/tools.js';
37
37
  import { getActors, getToolsForServerMode, toolNamesToInput } from '../utils/tools_loader.js';
38
- import { getUserInfoCached } from '../utils/userid_cache.js';
39
- import { getPackageVersion } from '../utils/version.js';
40
38
  import { LOG_LEVEL_MAP } from './const.js';
39
+ import { emitTaskStatusNotification, executeToolAndUpdateTask } from './task_execution.js';
41
40
  import { buildToolCallErrorResult, TOOL_CALL_ERROR_KIND } from './tool_call_error_mapper.js';
41
+ import { logToolCallAndTelemetry, prepareTelemetryData } from './tool_call_telemetry.js';
42
42
  import { dispatchToolCall } from './tool_dispatch.js';
43
- import { createTaskCancellationWatcher, isTaskCancelled, isTaskNotFoundError, parseInputParamsFromUrl, storeTaskResultOrSkipIfExpired, } from './utils.js';
43
+ import { parseInputParamsFromUrl, storeTaskResultOrSkipIfExpired } from './utils.js';
44
44
  /**
45
45
  * Returns true when the initialize request advertises the MCP Apps UI extension
46
46
  * with the widget MIME type. Used to resolve `'auto'` server mode.
@@ -54,33 +54,6 @@ function isUiSupportedByClient(request) {
54
54
  const uiCap = getUiCapability((_a = request === null || request === void 0 ? void 0 : request.params) === null || _a === void 0 ? void 0 : _a.capabilities);
55
55
  return (_c = (_b = uiCap === null || uiCap === void 0 ? void 0 : uiCap.mimeTypes) === null || _b === void 0 ? void 0 : _b.includes(RESOURCE_MIME_TYPE)) !== null && _c !== void 0 ? _c : false;
56
56
  }
57
- /** Send notifications/tasks/status for taskId. Routes via session transport (no relatedRequestId).
58
- * Swallows errors — notifications are advisory. */
59
- export async function emitTaskStatusNotification(taskId, mcpSessionId, taskStore, server) {
60
- try {
61
- const task = await taskStore.getTask(taskId, mcpSessionId);
62
- if (!task)
63
- return;
64
- // Per spec: notifications/tasks/status MUST NOT carry _meta.related-task (task ID is in params).
65
- // Called without options so the notification routes through the session transport,
66
- // not the request-scoped stream (which closes once the initial { task } response is flushed).
67
- await server.notification({
68
- method: 'notifications/tasks/status',
69
- params: {
70
- taskId: task.taskId,
71
- status: task.status,
72
- createdAt: task.createdAt,
73
- lastUpdatedAt: task.lastUpdatedAt,
74
- ttl: task.ttl,
75
- ...(task.statusMessage != null && { statusMessage: task.statusMessage }),
76
- ...(task.pollInterval != null && { pollInterval: task.pollInterval }),
77
- },
78
- });
79
- }
80
- catch {
81
- // Silent fail — notifications are advisory
82
- }
83
- }
84
57
  /**
85
58
  * Shared diagnostics for a pre-flight failure (standby-provider conflict or missing/invalid payment
86
59
  * signature) — a call outcome already known before any Actor runs. Standby rejection wins over the
@@ -197,18 +170,18 @@ export class ActorsMcpServer {
197
170
  writable: true,
198
171
  value: []
199
172
  });
200
- // Telemetry configuration (resolved from options and env vars in setupTelemetry)
173
+ // Telemetry configuration (resolved from options and env vars, see setupTelemetry)
201
174
  Object.defineProperty(this, "telemetryEnabled", {
202
175
  enumerable: true,
203
176
  configurable: true,
204
177
  writable: true,
205
- value: null
178
+ value: void 0
206
179
  });
207
180
  Object.defineProperty(this, "telemetryEnv", {
208
181
  enumerable: true,
209
182
  configurable: true,
210
183
  writable: true,
211
- value: DEFAULT_TELEMETRY_ENV
184
+ value: void 0
212
185
  });
213
186
  // List of widgets that are ready to be served
214
187
  Object.defineProperty(this, "availableWidgets", {
@@ -265,17 +238,16 @@ export class ActorsMcpServer {
265
238
  },
266
239
  },
267
240
  },
268
- /**
269
- * Declaring resources even though we are not using them
270
- * to prevent clients like Claude desktop from failing.
271
- */
241
+ // Declared but unused — some clients (e.g. Claude Desktop) fail without it.
272
242
  resources: {},
273
243
  prompts: {},
274
244
  logging: {},
275
245
  },
276
246
  instructions: getServerInstructions(),
277
247
  });
278
- this.setupTelemetry();
248
+ const { telemetryEnabled, telemetryEnv } = this.setupTelemetry();
249
+ this.telemetryEnabled = telemetryEnabled;
250
+ this.telemetryEnv = telemetryEnv;
279
251
  this.setupInitializeHandler();
280
252
  this.setupLoggingProxy();
281
253
  this.tools = new Map();
@@ -283,9 +255,7 @@ export class ActorsMcpServer {
283
255
  this.setupLoggingHandlers();
284
256
  this.setupToolHandlers();
285
257
  this.setupPromptHandlers();
286
- /**
287
- * We need to handle resource requests to prevent clients like Claude desktop from failing.
288
- */
258
+ // Handle resource requests so clients like Claude Desktop don't fail.
289
259
  this.setupResourceHandlers();
290
260
  this.setupTaskHandlers();
291
261
  }
@@ -294,18 +264,21 @@ export class ActorsMcpServer {
294
264
  */
295
265
  setupTelemetry() {
296
266
  var _a, _b, _c;
267
+ let telemetryEnabled;
297
268
  const explicitEnabled = parseBooleanOrNull((_a = this.options.telemetry) === null || _a === void 0 ? void 0 : _a.enabled);
298
269
  if (explicitEnabled !== null) {
299
- this.telemetryEnabled = explicitEnabled;
270
+ telemetryEnabled = explicitEnabled;
300
271
  }
301
272
  else {
302
273
  const envEnabled = parseBooleanOrNull(process.env.TELEMETRY_ENABLED);
303
- this.telemetryEnabled = envEnabled !== null && envEnabled !== void 0 ? envEnabled : DEFAULT_TELEMETRY_ENABLED;
274
+ telemetryEnabled = envEnabled !== null && envEnabled !== void 0 ? envEnabled : DEFAULT_TELEMETRY_ENABLED;
304
275
  }
305
276
  // Configure telemetryEnv: explicit option > env var > default ('PROD')
306
- if (this.telemetryEnabled) {
307
- this.telemetryEnv = getTelemetryEnv((_c = (_b = this.options.telemetry) === null || _b === void 0 ? void 0 : _b.env) !== null && _c !== void 0 ? _c : process.env.TELEMETRY_ENV);
277
+ let telemetryEnv = DEFAULT_TELEMETRY_ENV;
278
+ if (telemetryEnabled) {
279
+ telemetryEnv = getTelemetryEnv((_c = (_b = this.options.telemetry) === null || _b === void 0 ? void 0 : _b.env) !== null && _c !== void 0 ? _c : process.env.TELEMETRY_ENV);
308
280
  }
281
+ return { telemetryEnabled, telemetryEnv };
309
282
  }
310
283
  /**
311
284
  * Override the SDK's `initialize` request handler to run mode resolution and
@@ -387,23 +360,10 @@ export class ActorsMcpServer {
387
360
  * survive a load that never sees an initialize.
388
361
  */
389
362
  isReportProblemServable() {
390
- return (!!this.telemetryEnabled &&
363
+ return (this.telemetryEnabled &&
391
364
  this.clientKnown &&
392
365
  !isReportProblemBlockedForClient(this.options.initializeRequestData));
393
366
  }
394
- /**
395
- * Append the report-problem nudge to a failed tool result. Thin wrapper over
396
- * {@link appendReportProblemNudge} that fills in whether report-problem is served on this
397
- * connection; suppression by category/402 is handled inside the helper.
398
- */
399
- withReportProblemNudge(result, failingToolName, failureCategory, failureHttpStatus) {
400
- return appendReportProblemNudge(result, {
401
- failingToolName,
402
- available: this.tools.has(HELPER_TOOLS.PROBLEM_REPORT),
403
- failureCategory,
404
- failureHttpStatus,
405
- });
406
- }
407
367
  composePendingToolsForClient() {
408
368
  if (this.pendingToolsUntilClientKnown.length === 0)
409
369
  return;
@@ -418,7 +378,6 @@ export class ActorsMcpServer {
418
378
  }
419
379
  /**
420
380
  * Returns an array of tool names.
421
- * @returns {string[]} - An array of tool names.
422
381
  */
423
382
  listToolNames() {
424
383
  return Array.from(this.tools.keys());
@@ -448,8 +407,7 @@ export class ActorsMcpServer {
448
407
  this.toolsChangedHandler = undefined;
449
408
  }
450
409
  /**
451
- * Returns the list of all internal tool names
452
- * @returns {string[]} - Array of loaded tool IDs (e.g., 'apify/rag-web-browser')
410
+ * Returns the list of all internal tool names (e.g., 'call-actor', 'search-actors').
453
411
  */
454
412
  listInternalToolNames() {
455
413
  return Array.from(this.tools.values())
@@ -457,8 +415,7 @@ export class ActorsMcpServer {
457
415
  .map((tool) => tool.name);
458
416
  }
459
417
  /**
460
- * Returns the list of all currently loaded Actor tool IDs.
461
- * @returns {string[]} - Array of loaded Actor tool IDs (e.g., 'apify/rag-web-browser')
418
+ * Returns the currently loaded Actor tool full names (e.g., 'apify/rag-web-browser').
462
419
  */
463
420
  listActorToolNames() {
464
421
  return Array.from(this.tools.values())
@@ -466,19 +423,17 @@ export class ActorsMcpServer {
466
423
  .map((tool) => tool.actorFullName);
467
424
  }
468
425
  /**
469
- * Returns a list of Actor IDs that are registered as MCP servers.
470
- * @returns {string[]} - An array of Actor MCP server Actor IDs (e.g., 'apify/actors-mcp-server').
426
+ * Returns the unique Actor IDs registered as MCP servers (e.g., 'apify/actors-mcp-server').
471
427
  */
472
428
  listActorMcpServerToolIds() {
473
429
  const ids = Array.from(this.tools.values())
474
430
  .filter((tool) => tool.type === TOOL_TYPE.ACTOR_MCP)
475
431
  .map((tool) => tool.actorId);
476
- // Ensure uniqueness
477
432
  return Array.from(new Set(ids));
478
433
  }
479
434
  /**
480
- * Returns a list of Actor name and MCP server tool IDs.
481
- * @returns {string[]} - An array of Actor MCP server Actor IDs (e.g., 'apify/actors-mcp-server').
435
+ * Returns the combined internal tool names, Actor full names, and Actor-MCP server Actor IDs
436
+ * currently loaded.
482
437
  */
483
438
  listAllToolNames() {
484
439
  return [...this.listInternalToolNames(), ...this.listActorToolNames(), ...this.listActorMcpServerToolIds()];
@@ -512,7 +467,6 @@ export class ActorsMcpServer {
512
467
  * Loads missing toolNames from a provided list of tool names.
513
468
  * Skips toolNames that are already loaded and loads only the missing ones.
514
469
  * @param toolNames - Array of tool names to ensure are loaded
515
- * @param apifyClient
516
470
  */
517
471
  async loadToolsByName(toolNames, apifyClient) {
518
472
  const loadedTools = new Set(this.listAllToolNames());
@@ -603,10 +557,8 @@ export class ActorsMcpServer {
603
557
  return tools;
604
558
  }
605
559
  notifyToolsChangedHandler() {
606
- // If no handler is registered, do nothing
607
560
  if (!this.toolsChangedHandler)
608
561
  return;
609
- // Get the list of tool names
610
562
  this.toolsChangedHandler(this.listAllToolNames());
611
563
  }
612
564
  removeToolByName(toolName) {
@@ -640,13 +592,12 @@ export class ActorsMcpServer {
640
592
  process.exit(0);
641
593
  };
642
594
  process.once('SIGINT', handler);
643
- this.sigintHandler = handler; // Store the actual handler
595
+ this.sigintHandler = handler;
644
596
  }
645
597
  }
646
598
  setupLoggingProxy() {
647
- // Store original sendLoggingMessage
648
599
  const originalSendLoggingMessage = this.server.sendLoggingMessage.bind(this.server);
649
- // Proxy sendLoggingMessage to filter logs
600
+ // Filter outgoing log messages below the client's requested level.
650
601
  this.server.sendLoggingMessage = async (params) => {
651
602
  var _a, _b;
652
603
  const messageLevelValue = (_a = LOG_LEVEL_MAP[params.level]) !== null && _a !== void 0 ? _a : -1; // Unknown levels get -1, discard
@@ -750,12 +701,13 @@ export class ActorsMcpServer {
750
701
  }
751
702
  /**
752
703
  * Sets up MCP request handlers for long-running tasks.
704
+ * Each handler reads `_meta.mcpSessionId` (injected at the transport layer) to isolate
705
+ * per-session task stores.
753
706
  */
754
707
  setupTaskHandlers() {
755
708
  // List tasks
756
709
  this.server.setRequestHandler(ListTasksRequestSchema, async (request) => {
757
710
  var _a;
758
- // mcpSessionId is injected at transport layer for session isolation in task stores
759
711
  const params = (request.params || {});
760
712
  const { cursor } = params;
761
713
  const mcpSessionId = (_a = params._meta) === null || _a === void 0 ? void 0 : _a.mcpSessionId;
@@ -766,7 +718,6 @@ export class ActorsMcpServer {
766
718
  // Get task status
767
719
  this.server.setRequestHandler(GetTaskRequestSchema, async (request) => {
768
720
  var _a;
769
- // mcpSessionId is injected at transport layer for session isolation in task stores
770
721
  const params = (request.params || {});
771
722
  const { taskId } = params;
772
723
  const mcpSessionId = (_a = params._meta) === null || _a === void 0 ? void 0 : _a.mcpSessionId;
@@ -776,7 +727,6 @@ export class ActorsMcpServer {
776
727
  // Get task result payload
777
728
  this.server.setRequestHandler(GetTaskPayloadRequestSchema, async (request) => {
778
729
  var _a;
779
- // mcpSessionId is injected at transport layer for session isolation in task stores
780
730
  const params = (request.params || {});
781
731
  const { taskId } = params;
782
732
  const mcpSessionId = (_a = params._meta) === null || _a === void 0 ? void 0 : _a.mcpSessionId;
@@ -798,7 +748,6 @@ export class ActorsMcpServer {
798
748
  // Cancel task
799
749
  this.server.setRequestHandler(CancelTaskRequestSchema, async (request) => {
800
750
  var _a;
801
- // mcpSessionId is injected at transport layer for session isolation in task stores
802
751
  const params = (request.params || {});
803
752
  const { taskId } = params;
804
753
  const mcpSessionId = (_a = params._meta) === null || _a === void 0 ? void 0 : _a.mcpSessionId;
@@ -822,11 +771,7 @@ export class ActorsMcpServer {
822
771
  });
823
772
  }
824
773
  setupToolHandlers() {
825
- /**
826
- * Handles the request to list tools.
827
- * @param {object} request - The request object.
828
- * @returns {object} - The response object containing the tools.
829
- */
774
+ // Handles the request to list tools.
830
775
  this.server.setRequestHandler(ListToolsRequestSchema, async () => {
831
776
  const tools = Array.from(this.tools.values()).map((tool) => getToolPublicFieldOnly(tool, {
832
777
  mode: this.serverMode,
@@ -835,10 +780,8 @@ export class ActorsMcpServer {
835
780
  return { tools };
836
781
  });
837
782
  /**
838
- * Handles the request to call a tool.
839
- * @param {object} request - The request object containing tool name and arguments.
840
- * @param {object} extra - Extra data given to the request handler, such as sendNotification function.
841
- * @throws {McpError} - based on the McpServer class code from the typescript MCP SDK
783
+ * Handles the request to call a tool. `extra` carries request-scoped helpers such as
784
+ * `sendNotification`. Throws {@link McpError} to mirror the MCP SDK's McpServer error codes.
842
785
  */
843
786
  this.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
844
787
  var _a, _b, _c, _d, _e;
@@ -847,7 +790,8 @@ export class ActorsMcpServer {
847
790
  let { name, arguments: args, _meta: meta } = params;
848
791
  const progressToken = meta === null || meta === void 0 ? void 0 : meta.progressToken;
849
792
  const apifyToken = this.resolveApifyToken(meta);
850
- // mcpSessionId was injected upstream it is important and required for long running tasks as the store uses it and there is not other way to pass it
793
+ // Injected upstream; required for long-running tasks — the task store keys on it and
794
+ // there is no other channel to pass it.
851
795
  const mcpSessionId = meta === null || meta === void 0 ? void 0 : meta.mcpSessionId;
852
796
  if (!mcpSessionId) {
853
797
  log.error('MCP Session ID is missing in tool call request. This should never happen.');
@@ -868,7 +812,13 @@ export class ActorsMcpServer {
868
812
  // On a failed result, nudge the agent to report the blocker via report-problem at the
869
813
  // moment it decides what to do next. Gated on the tool actually being served (see
870
814
  // isReportProblemServable), so clients where it is blocklisted or telemetry is off never see it.
871
- const augmented = this.withReportProblemNudge(r, resolvedToolName, callDiagnostics.failure_category, callDiagnostics.failure_http_status);
815
+ const augmented = withReportProblemNudge({
816
+ result: r,
817
+ tools: this.tools,
818
+ failingToolName: resolvedToolName,
819
+ failureCategory: callDiagnostics.failure_category,
820
+ failureHttpStatus: callDiagnostics.failure_http_status,
821
+ });
872
822
  toolResult = augmented;
873
823
  return augmented;
874
824
  };
@@ -890,7 +840,12 @@ export class ActorsMcpServer {
890
840
  };
891
841
  // Initialize telemetry with raw tool name — updated below once the tool is resolved.
892
842
  // This ensures telemetry is available even for early failures (missing token, tool not found).
893
- const { telemetryData, userId } = await this.prepareTelemetryData(name, mcpSessionId, apifyToken);
843
+ const { telemetryData, userId } = await prepareTelemetryData({
844
+ toolName: name,
845
+ mcpSessionId,
846
+ apifyToken,
847
+ apifyMcpServer: this,
848
+ });
894
849
  // actorName/actorId are declared here so they're available in the catch block for telemetry.
895
850
  // Set after tool resolution (inside the try block).
896
851
  let actorName;
@@ -1058,7 +1013,7 @@ export class ActorsMcpServer {
1058
1013
  }
1059
1014
  // Execute the tool asynchronously and update task status
1060
1015
  setImmediate(() => {
1061
- this.executeToolAndUpdateTask({
1016
+ executeToolAndUpdateTask({
1062
1017
  taskId: task.taskId,
1063
1018
  tool,
1064
1019
  toolArgs: toolArgs,
@@ -1070,6 +1025,7 @@ export class ActorsMcpServer {
1070
1025
  mcpSessionId,
1071
1026
  actorName,
1072
1027
  actorId,
1028
+ apifyMcpServer: this,
1073
1029
  }).catch((error) =>
1074
1030
  // Benign task-expiry is handled in-method (see the catch block and
1075
1031
  // storeTaskResultOrSkipIfExpired); anything reaching here is genuinely unexpected.
@@ -1182,7 +1138,7 @@ export class ActorsMcpServer {
1182
1138
  }
1183
1139
  finally {
1184
1140
  if (shouldTrackTelemetry) {
1185
- this.logToolCallAndTelemetry({
1141
+ logToolCallAndTelemetry({
1186
1142
  toolName: resolvedToolName,
1187
1143
  mcpSessionId,
1188
1144
  toolStatus,
@@ -1193,340 +1149,12 @@ export class ActorsMcpServer {
1193
1149
  args,
1194
1150
  result: toolResult,
1195
1151
  taskId: preflightTaskId,
1152
+ apifyMcpServer: this,
1196
1153
  });
1197
1154
  }
1198
1155
  }
1199
1156
  });
1200
1157
  }
1201
- /**
1202
- * Logs tool call completion at INFO level and tracks telemetry.
1203
- * Computes duration once so both the log line and telemetry event use the same value.
1204
- * Response bytes and resource ids are derived here from the raw `result` (+ `args`) so every
1205
- * call site stays a plain hand-off — no path can forget to compute or strip them.
1206
- */
1207
- logToolCallAndTelemetry(params) {
1208
- const durationMs = Date.now() - params.startTime;
1209
- // `result` is undefined only on short-circuit paths that never produced a payload (e.g. a
1210
- // cancelled task); skip byte accounting there. `null`/`{}` still measure as zero bytes.
1211
- const responseBytes = params.result === undefined ? undefined : computeToolResponseBytes(params.result);
1212
- log.info('Tool call completed', {
1213
- toolName: params.toolName,
1214
- mcpSessionId: params.mcpSessionId,
1215
- toolStatus: params.toolStatus,
1216
- durationMs,
1217
- ...(responseBytes !== undefined && {
1218
- responseContentBytes: responseBytes.contentBytes,
1219
- responseStructuredContentBytes: responseBytes.structuredContentBytes,
1220
- responseFileBytes: responseBytes.fileBytes,
1221
- }),
1222
- ...(params.taskId !== undefined && { taskId: params.taskId }),
1223
- });
1224
- if (params.telemetryData) {
1225
- const finalizedTelemetryData = {
1226
- ...params.telemetryData,
1227
- tool_status: params.toolStatus,
1228
- tool_exec_time_ms: durationMs,
1229
- ...(responseBytes && {
1230
- tool_response_content_bytes: responseBytes.contentBytes,
1231
- tool_response_structured_content_bytes: responseBytes.structuredContentBytes,
1232
- tool_response_file_bytes: responseBytes.fileBytes,
1233
- }),
1234
- // Always include actor_name/actor_id; failure-specific fields are only present when callDiagnostics has them.
1235
- ...params.callDiagnostics,
1236
- // Resource ids are read once here from the args + the tool's public output; no tool
1237
- // threads them back. Applied uniformly, last. See deriveResourceIds.
1238
- ...deriveResourceIds(params.args, params.result),
1239
- };
1240
- trackToolCall(params.userId, this.telemetryEnv, finalizedTelemetryData);
1241
- // A successful report-problem call also emits a dedicated feedback event carrying the
1242
- // submission. A downstream Segment destination fans it out to Slack/GitHub.
1243
- if (params.toolName === HELPER_TOOLS.PROBLEM_REPORT &&
1244
- params.toolStatus === TOOL_STATUS.SUCCEEDED &&
1245
- params.args) {
1246
- trackReportedProblem(params.userId, this.telemetryEnv, buildReportedProblemProperties(finalizedTelemetryData, params.args));
1247
- }
1248
- }
1249
- }
1250
- // TODO: outer orchestration here (pre-flight, telemetry bookkeeping, task-store handling) still duplicates the CallToolRequestSchema handler's logic; the dispatch ladder is now shared. Refactor.
1251
- /**
1252
- * Executes a tool asynchronously for a long-running task and updates task status.
1253
- *
1254
- * @param params - Tool execution parameters
1255
- * @param params.taskId - The task identifier
1256
- * @param params.tool - The tool to execute
1257
- * @param params.args - Tool arguments
1258
- * @param params.apifyToken - Apify API token
1259
- * @param params.progressToken - Progress token for notifications
1260
- * @param params.extra - Extra request handler context
1261
- * @param params.mcpSessionId - MCP session ID for telemetry
1262
- */
1263
- async executeToolAndUpdateTask(params) {
1264
- const { taskId, tool, toolArgs, logSafeArgs, apifyClient, apifyToken, progressToken, extra, mcpSessionId, actorName, actorId, } = params;
1265
- let toolStatus = TOOL_STATUS.SUCCEEDED;
1266
- // Always populate actor fields so they're tracked on both success and failure paths.
1267
- let callDiagnostics = { ...buildActorFields(actorName, actorId) };
1268
- const startTime = Date.now();
1269
- log.debug('[executeToolAndUpdateTask] Starting task execution', {
1270
- taskId,
1271
- toolName: tool.name,
1272
- mcpSessionId,
1273
- });
1274
- // Prepare telemetry before try-catch so it's accessible to both paths.
1275
- // This avoids re-fetching user data in the error handler.
1276
- const { telemetryData, userId } = await this.prepareTelemetryData(getToolFullName(tool), mcpSessionId, apifyToken);
1277
- const finishTaskTracking = (status, diagnostics, result) => {
1278
- this.logToolCallAndTelemetry({
1279
- toolName: tool.name,
1280
- mcpSessionId,
1281
- toolStatus: status,
1282
- startTime,
1283
- taskId,
1284
- telemetryData,
1285
- userId,
1286
- callDiagnostics: diagnostics,
1287
- args: toolArgs,
1288
- result,
1289
- });
1290
- };
1291
- // Terminal step shared by every task outcome: store the result (expiry-tolerant),
1292
- // emit the status notification, record telemetry.
1293
- const completeTask = async (storeStatus, taskResult, status, diagnostics) => {
1294
- await storeTaskResultOrSkipIfExpired(this.taskStore, tool.name, taskId, storeStatus, taskResult, mcpSessionId);
1295
- await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
1296
- finishTaskTracking(status, diagnostics, taskResult);
1297
- };
1298
- // Once a task is cancelled the spec forbids writing a result; every storage path
1299
- // must short-circuit here. `logSuffix` is concatenated after "Task was cancelled"
1300
- // so we keep the existing log format and the existing telemetry status per path.
1301
- const skipIfTaskCancelled = async (logSuffix, status, diagnostics) => {
1302
- if (!(await isTaskCancelled(taskId, mcpSessionId, this.taskStore)))
1303
- return false;
1304
- log.debug(`[executeToolAndUpdateTask] Task was cancelled${logSuffix}`, { taskId, mcpSessionId });
1305
- finishTaskTracking(status, diagnostics);
1306
- return true;
1307
- };
1308
- // Bridges MCP `tasks/cancel` to the running handler: when the client
1309
- // explicitly cancels the task, this signal aborts so the underlying
1310
- // Actor run stops instead of consuming compute until natural completion.
1311
- // Per MCP tasks spec, request-level aborts (client disconnect,
1312
- // notifications/cancelled for the original request ID) MUST NOT cancel
1313
- // the task — `extra.signal` is intentionally not chained here.
1314
- const cancelWatcher = createTaskCancellationWatcher({
1315
- taskId,
1316
- mcpSessionId,
1317
- taskStore: this.taskStore,
1318
- });
1319
- const taskExtra = { ...extra, signal: cancelWatcher.signal };
1320
- try {
1321
- log.debug('[executeToolAndUpdateTask] Updating task status to working', {
1322
- taskId,
1323
- mcpSessionId,
1324
- });
1325
- // The store rejects terminal → 'working' transitions. If `tasks/cancel` raced
1326
- // with us (between handler dispatch and the first watcher tick at ~500 ms),
1327
- // updateTaskStatus throws — re-check the store to tell a clean cancel-race
1328
- // apart from a genuine store error.
1329
- // noinspection ExceptionCaughtLocallyJS
1330
- try {
1331
- await this.taskStore.updateTaskStatus(taskId, 'working', undefined, mcpSessionId);
1332
- }
1333
- catch (err) {
1334
- if (await skipIfTaskCancelled(' before execution started, skipping', TOOL_STATUS.ABORTED, {
1335
- ...buildActorFields(actorName, actorId),
1336
- }))
1337
- return;
1338
- throw err;
1339
- }
1340
- await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
1341
- // Execute the tool and get the result
1342
- let result = {};
1343
- // Callback to propagate Actor run statusMessage into the task store and emit a push notification.
1344
- // TODO(TC-3): cancel arriving while this is scheduled throws cancelled → working;
1345
- // currently swallowed by progress.ts's tick catch — guard or catch explicitly.
1346
- const onStatusMessage = async (message) => {
1347
- await this.taskStore.updateTaskStatus(taskId, 'working', message, mcpSessionId);
1348
- await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
1349
- };
1350
- // ACTOR_MCP never reads the tracker (matching the sync path, which passes null for it);
1351
- // INTERNAL/ACTOR get one built from taskId + onStatusMessage, which dispatch consumes
1352
- // and stops.
1353
- const progressTracker = tool.type === TOOL_TYPE.ACTOR_MCP
1354
- ? null
1355
- : createProgressTracker(progressToken, extra.sendNotification, taskId, onStatusMessage);
1356
- const dispatchResult = await dispatchToolCall({
1357
- tool,
1358
- toolArgs,
1359
- logSafeArgs,
1360
- apifyToken,
1361
- apifyClient,
1362
- apifyMcpServer: this,
1363
- mcpSessionId,
1364
- progressToken,
1365
- progressTracker,
1366
- shouldForwardNotifications: false,
1367
- extra: taskExtra,
1368
- actorName,
1369
- actorId,
1370
- taskMode: true,
1371
- logContext: { messageSuffix: ' for task', fields: { taskId } },
1372
- });
1373
- result = dispatchResult.result;
1374
- toolStatus = dispatchResult.toolStatus;
1375
- callDiagnostics = dispatchResult.callDiagnostics;
1376
- // Check if task was cancelled before storing result
1377
- if (await skipIfTaskCancelled(', skipping result storage', toolStatus))
1378
- return;
1379
- // On a failed result, nudge the agent to report the blocker via report-problem (mirrors the
1380
- // synchronous CallTool path, which task-mode calls like call-actor bypass).
1381
- result = this.withReportProblemNudge(result, tool.name, callDiagnostics.failure_category, callDiagnostics.failure_http_status);
1382
- // Store the result in the task store
1383
- log.debug('[executeToolAndUpdateTask] Storing completed result', {
1384
- taskId,
1385
- mcpSessionId,
1386
- });
1387
- await completeTask('completed', result, toolStatus, callDiagnostics);
1388
- log.debug('Task completed successfully', { taskId, toolName: tool.name, mcpSessionId });
1389
- }
1390
- catch (error) {
1391
- // Reached only when the task expired before the `working` transition (updateTaskStatus
1392
- // above rethrows the store's unknown-taskId error). The tool never ran and the task is
1393
- // gone, so soft-fail, record telemetry, and stop. Every result store (success and error
1394
- // paths) tolerates expiry via storeTaskResultOrSkipIfExpired, so they don't reach here.
1395
- if (isTaskNotFoundError(error)) {
1396
- log.softFail('Task expired before execution started', {
1397
- taskId,
1398
- toolName: tool.name,
1399
- mcpSessionId,
1400
- });
1401
- finishTaskTracking(TOOL_STATUS.SOFT_FAIL, {
1402
- failure_category: FAILURE_CATEGORY.INTERNAL_ERROR,
1403
- ...buildActorFields(actorName, actorId),
1404
- });
1405
- return;
1406
- }
1407
- const errorResult = buildToolCallErrorResult(error, {
1408
- toolName: tool.name,
1409
- actorName,
1410
- actorId,
1411
- isAborted: Boolean(cancelWatcher.signal.aborted),
1412
- });
1413
- // Handle 402 Payment Required — return structured x402 result so clients can auto-pay
1414
- if (errorResult.kind === TOOL_CALL_ERROR_KIND.PAYMENT) {
1415
- logHttpError(error, 'Payment required while calling tool (task mode)', { toolName: tool.name });
1416
- // Per MCP tasks spec: once a task is cancelled it MUST remain cancelled,
1417
- // so guard storeTaskResult against a cancel that raced with this 402.
1418
- if (await skipIfTaskCancelled(', skipping 402 result storage', TOOL_STATUS.ABORTED, {
1419
- ...buildActorFields(actorName, actorId),
1420
- }))
1421
- return;
1422
- await completeTask('completed', errorResult.response, errorResult.toolStatus, errorResult.callDiagnostics);
1423
- return;
1424
- }
1425
- if (errorResult.kind === TOOL_CALL_ERROR_KIND.APPROVAL) {
1426
- logHttpError(error, 'Permission approval required while calling tool (task mode)', {
1427
- toolName: tool.name,
1428
- });
1429
- // Per MCP tasks spec: once a task is cancelled it MUST remain cancelled,
1430
- // so guard storeTaskResult against a cancel that raced with this approval error.
1431
- if (await skipIfTaskCancelled(', skipping permission-approval result storage', TOOL_STATUS.ABORTED, {
1432
- ...buildActorFields(actorName, actorId),
1433
- }))
1434
- return;
1435
- await completeTask('completed', errorResult.response, errorResult.toolStatus, errorResult.callDiagnostics);
1436
- return;
1437
- }
1438
- toolStatus = errorResult.toolStatus;
1439
- callDiagnostics = errorResult.callDiagnostics;
1440
- // Log level follows the already-classified toolStatus:
1441
- // SOFT_FAIL (e.g. 402/403 user quota, client-side issues) → softFail
1442
- // FAILED/ABORTED/other → error
1443
- if (toolStatus === TOOL_STATUS.SOFT_FAIL) {
1444
- // Mezmo promotes on "error" in message/keys — use errMessage key, sanitized.
1445
- const errMessage = sanitizeMezmoMessage(error instanceof Error ? error.message : String(error));
1446
- log.softFail('Tool execution soft-failed for task', {
1447
- taskId,
1448
- toolName: tool.name,
1449
- toolStatus,
1450
- mcpSessionId,
1451
- failureCategory: callDiagnostics.failure_category,
1452
- failureHttpStatus: callDiagnostics.failure_http_status,
1453
- actorName: callDiagnostics.actor_name,
1454
- errMessage,
1455
- });
1456
- }
1457
- else {
1458
- log.error('Error executing tool for task', {
1459
- taskId,
1460
- toolName: tool.name,
1461
- toolStatus,
1462
- mcpSessionId,
1463
- failureCategory: callDiagnostics.failure_category,
1464
- failureHttpStatus: callDiagnostics.failure_http_status,
1465
- actorName: callDiagnostics.actor_name,
1466
- error,
1467
- });
1468
- }
1469
- const { userText } = errorResult;
1470
- // Check if task was cancelled before storing result
1471
- if (await skipIfTaskCancelled(', skipping result storage', toolStatus, callDiagnostics))
1472
- return;
1473
- log.debug('[executeToolAndUpdateTask] Storing failed result', {
1474
- taskId,
1475
- mcpSessionId,
1476
- });
1477
- // Nudge on a genuinely-failed task result (mirrors the completed path and the sync
1478
- // captureResult). INTERNAL_ERROR and an unknown category get the full nudge; a genuine
1479
- // INVALID_INPUT gets the softer nudge; payment (402) is suppressed via the HTTP status and
1480
- // AUTH / PERMISSION_APPROVAL_REQUIRED via NON_NUDGE_FAILURE_CATEGORIES. The 402 branch above
1481
- // returns before reaching here, so this only sees non-payment failures.
1482
- const failedResult = this.withReportProblemNudge({
1483
- content: [
1484
- {
1485
- type: 'text',
1486
- text: userText,
1487
- },
1488
- ],
1489
- isError: true,
1490
- internalToolStatus: toolStatus,
1491
- }, tool.name, callDiagnostics.failure_category, callDiagnostics.failure_http_status);
1492
- await completeTask('failed', failedResult, toolStatus, callDiagnostics);
1493
- }
1494
- finally {
1495
- cancelWatcher.dispose();
1496
- }
1497
- }
1498
- /*
1499
- * Creates telemetry data for a tool call.
1500
- */
1501
- async prepareTelemetryData(toolName, mcpSessionId, apifyToken) {
1502
- var _a, _b, _c, _d, _e;
1503
- if (!this.telemetryEnabled) {
1504
- return { telemetryData: null, userId: null };
1505
- }
1506
- // Get userId from cache or fetch from API
1507
- let userId = null;
1508
- if (apifyToken) {
1509
- const apifyClient = new ApifyClient({ token: apifyToken });
1510
- ({ userId } = await getUserInfoCached(apifyToken, apifyClient));
1511
- log.debug('Telemetry: fetched userId', { userId, mcpSessionId });
1512
- }
1513
- const capabilities = (_b = (_a = this.options.initializeRequestData) === null || _a === void 0 ? void 0 : _a.params) === null || _b === void 0 ? void 0 : _b.capabilities;
1514
- const params = (_c = this.options.initializeRequestData) === null || _c === void 0 ? void 0 : _c.params;
1515
- const telemetryData = {
1516
- app: 'mcp',
1517
- app_version: getPackageVersion() || '',
1518
- mcp_client_name: ((_d = params === null || params === void 0 ? void 0 : params.clientInfo) === null || _d === void 0 ? void 0 : _d.name) || '',
1519
- mcp_client_version: ((_e = params === null || params === void 0 ? void 0 : params.clientInfo) === null || _e === void 0 ? void 0 : _e.version) || '',
1520
- mcp_protocol_version: (params === null || params === void 0 ? void 0 : params.protocolVersion) || '',
1521
- mcp_client_capabilities: capabilities || null,
1522
- mcp_session_id: mcpSessionId || '',
1523
- transport_type: this.options.transportType || '',
1524
- tool_name: toolName,
1525
- tool_status: TOOL_STATUS.SUCCEEDED, // Will be updated in finally
1526
- tool_exec_time_ms: 0, // Will be calculated in finally
1527
- };
1528
- return { telemetryData, userId };
1529
- }
1530
1158
  /**
1531
1159
  * Resolves widgets and determines which ones are ready to be served.
1532
1160
  */
@@ -1573,23 +1201,21 @@ export class ActorsMcpServer {
1573
1201
  await this.server.connect(transport);
1574
1202
  }
1575
1203
  async close() {
1576
- // Remove SIGINT handler
1577
1204
  if (this.sigintHandler) {
1578
1205
  process.removeListener('SIGINT', this.sigintHandler);
1579
1206
  this.sigintHandler = undefined;
1580
1207
  }
1581
- // Clear all tools and their compiled schemas
1208
+ // Clear all tools and null their compiled schemas.
1582
1209
  for (const tool of this.tools.values()) {
1583
1210
  if (tool.ajvValidate && typeof tool.ajvValidate === 'function') {
1584
1211
  tool.ajvValidate = null;
1585
1212
  }
1586
1213
  }
1587
1214
  this.tools.clear();
1588
- // Unregister tools changed handler
1589
1215
  if (this.toolsChangedHandler) {
1590
1216
  this.unregisterToolsChangedHandler();
1591
1217
  }
1592
- // Close server (which should also remove its event handlers)
1218
+ // Closing the server also removes its event handlers.
1593
1219
  await this.server.close();
1594
1220
  }
1595
1221
  }