@apify/actors-mcp-server 0.11.7-beta.1 → 0.11.7-beta.11

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 (99) hide show
  1. package/dist/index_internals.d.ts +7 -12
  2. package/dist/index_internals.d.ts.map +1 -1
  3. package/dist/index_internals.js +6 -11
  4. package/dist/index_internals.js.map +1 -1
  5. package/dist/mcp/actors.d.ts +0 -4
  6. package/dist/mcp/actors.d.ts.map +1 -1
  7. package/dist/mcp/actors.js +0 -13
  8. package/dist/mcp/actors.js.map +1 -1
  9. package/dist/mcp/server.d.ts +3 -33
  10. package/dist/mcp/server.d.ts.map +1 -1
  11. package/dist/mcp/server.js +103 -645
  12. package/dist/mcp/server.js.map +1 -1
  13. package/dist/mcp/task_execution.d.ts +42 -0
  14. package/dist/mcp/task_execution.d.ts.map +1 -0
  15. package/dist/mcp/task_execution.js +310 -0
  16. package/dist/mcp/task_execution.js.map +1 -0
  17. package/dist/mcp/tool_call_error_mapper.d.ts +45 -0
  18. package/dist/mcp/tool_call_error_mapper.d.ts.map +1 -0
  19. package/dist/mcp/tool_call_error_mapper.js +59 -0
  20. package/dist/mcp/tool_call_error_mapper.js.map +1 -0
  21. package/dist/mcp/tool_call_telemetry.d.ts +37 -0
  22. package/dist/mcp/tool_call_telemetry.d.ts.map +1 -0
  23. package/dist/mcp/tool_call_telemetry.js +85 -0
  24. package/dist/mcp/tool_call_telemetry.js.map +1 -0
  25. package/dist/mcp/tool_dispatch.d.ts +49 -0
  26. package/dist/mcp/tool_dispatch.d.ts.map +1 -0
  27. package/dist/mcp/tool_dispatch.js +208 -0
  28. package/dist/mcp/tool_dispatch.js.map +1 -0
  29. package/dist/payments/const.d.ts +1 -0
  30. package/dist/payments/const.d.ts.map +1 -1
  31. package/dist/payments/const.js +1 -0
  32. package/dist/payments/const.js.map +1 -1
  33. package/dist/payments/skyfire.d.ts.map +1 -1
  34. package/dist/payments/skyfire.js +5 -6
  35. package/dist/payments/skyfire.js.map +1 -1
  36. package/dist/tools/actors/call_actor.d.ts +0 -3
  37. package/dist/tools/actors/call_actor.d.ts.map +1 -1
  38. package/dist/tools/actors/call_actor.js +2 -11
  39. package/dist/tools/actors/call_actor.js.map +1 -1
  40. package/dist/tools/actors/fetch_actor_details.d.ts +1 -7
  41. package/dist/tools/actors/fetch_actor_details.d.ts.map +1 -1
  42. package/dist/tools/actors/fetch_actor_details.js +14 -22
  43. package/dist/tools/actors/fetch_actor_details.js.map +1 -1
  44. package/dist/tools/actors/search_actors.d.ts +1 -6
  45. package/dist/tools/actors/search_actors.d.ts.map +1 -1
  46. package/dist/tools/actors/search_actors.js +17 -24
  47. package/dist/tools/actors/search_actors.js.map +1 -1
  48. package/dist/tools/dev/report_problem.d.ts +13 -0
  49. package/dist/tools/dev/report_problem.d.ts.map +1 -1
  50. package/dist/tools/dev/report_problem.js +14 -0
  51. package/dist/tools/dev/report_problem.js.map +1 -1
  52. package/dist/tools/docs/search_apify_docs.d.ts.map +1 -1
  53. package/dist/tools/docs/search_apify_docs.js +1 -0
  54. package/dist/tools/docs/search_apify_docs.js.map +1 -1
  55. package/dist/tools/runs/get_actor_run.d.ts +1 -6
  56. package/dist/tools/runs/get_actor_run.d.ts.map +1 -1
  57. package/dist/tools/runs/get_actor_run.js +17 -24
  58. package/dist/tools/runs/get_actor_run.js.map +1 -1
  59. package/dist/tools/storage/get_dataset.d.ts.map +1 -1
  60. package/dist/tools/storage/get_dataset.js +6 -5
  61. package/dist/tools/storage/get_dataset.js.map +1 -1
  62. package/dist/tools/storage/get_dataset_items.d.ts.map +1 -1
  63. package/dist/tools/storage/get_dataset_items.js +6 -8
  64. package/dist/tools/storage/get_dataset_items.js.map +1 -1
  65. package/dist/tools/storage/get_dataset_list.d.ts.map +1 -1
  66. package/dist/tools/storage/get_dataset_list.js +8 -9
  67. package/dist/tools/storage/get_dataset_list.js.map +1 -1
  68. package/dist/tools/storage/get_dataset_schema.d.ts.map +1 -1
  69. package/dist/tools/storage/get_dataset_schema.js +6 -4
  70. package/dist/tools/storage/get_dataset_schema.js.map +1 -1
  71. package/dist/tools/storage/get_key_value_store.js +3 -3
  72. package/dist/tools/storage/get_key_value_store.js.map +1 -1
  73. package/dist/tools/storage/get_key_value_store_keys.js +4 -4
  74. package/dist/tools/storage/get_key_value_store_keys.js.map +1 -1
  75. package/dist/tools/storage/get_key_value_store_list.d.ts.map +1 -1
  76. package/dist/tools/storage/get_key_value_store_list.js +8 -9
  77. package/dist/tools/storage/get_key_value_store_list.js.map +1 -1
  78. package/dist/tools/storage/get_key_value_store_record.d.ts.map +1 -1
  79. package/dist/tools/storage/get_key_value_store_record.js +3 -2
  80. package/dist/tools/storage/get_key_value_store_record.js.map +1 -1
  81. package/dist/tsconfig.tsbuildinfo +1 -1
  82. package/dist/types.d.ts +0 -11
  83. package/dist/types.d.ts.map +1 -1
  84. package/dist/types.js.map +1 -1
  85. package/dist/utils/actor_details.d.ts.map +1 -1
  86. package/dist/utils/actor_details.js +10 -3
  87. package/dist/utils/actor_details.js.map +1 -1
  88. package/dist/utils/logging.d.ts +2 -0
  89. package/dist/utils/logging.d.ts.map +1 -1
  90. package/dist/utils/logging.js +45 -1
  91. package/dist/utils/logging.js.map +1 -1
  92. package/dist/utils/payment_errors.d.ts +7 -1
  93. package/dist/utils/payment_errors.d.ts.map +1 -1
  94. package/dist/utils/payment_errors.js +10 -0
  95. package/dist/utils/payment_errors.js.map +1 -1
  96. package/dist/web/dist/actor-run-widget.js +0 -3
  97. package/dist/web/dist/search-actors-widget.js +0 -3
  98. package/package.json +2 -2
  99. package/dist/web/dist/actor-detail-widget.js +0 -4551
@@ -8,7 +8,7 @@ import { randomUUID } from 'node:crypto';
8
8
  import { getUiCapability, RESOURCE_MIME_TYPE } from '@modelcontextprotocol/ext-apps/server';
9
9
  import { InMemoryTaskStore } from '@modelcontextprotocol/sdk/experimental/tasks/stores/in-memory.js';
10
10
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
11
- import { CallToolRequestSchema, CallToolResultSchema, CancelTaskRequestSchema, ErrorCode, GetPromptRequestSchema, GetTaskPayloadRequestSchema, GetTaskRequestSchema, InitializeRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListTasksRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema, RELATED_TASK_META_KEY, ServerNotificationSchema, SetLevelRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
11
+ import { CallToolRequestSchema, CancelTaskRequestSchema, ErrorCode, GetPromptRequestSchema, GetTaskPayloadRequestSchema, GetTaskRequestSchema, InitializeRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListTasksRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema, RELATED_TASK_META_KEY, SetLevelRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
12
12
  import dedent from 'dedent';
13
13
  import log from '@apify/log';
14
14
  import { parseBooleanOrNull } from '@apify/utilities';
@@ -19,30 +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
- import { actorExecutor } from '../tools/actors/actor_executor.js';
26
- import { buildPermissionApprovalResponse, checkPaymentProviderStandbyConflict } from '../tools/actors/call_actor.js';
27
- import { appendReportProblemNudge } from '../tools/dev/report_problem.js';
25
+ import { checkPaymentProviderStandbyConflict } from '../tools/actors/call_actor.js';
26
+ import { withReportProblemNudge } from '../tools/dev/report_problem.js';
28
27
  import { getActorsAsTools } from '../tools/index.js';
29
28
  import { SERVER_MODE, TOOL_TYPE } from '../types.js';
30
- import { isPermissionApprovalError, remoteMcpFailureDetail } from '../utils/apify_errors.js';
31
- import { getHttpStatusCode, isMcpClientFaultMessage, logHttpError, sanitizeMezmoMessage } from '../utils/logging.js';
32
- import { computeToolResponseBytes, getToolCallErrorUserText, respondErrorNoTelemetry, respondOk, } from '../utils/mcp.js';
29
+ import { isMcpClientFaultMessage, logHttpError, sanitizeMezmoMessage } from '../utils/logging.js';
30
+ import { respondOk } from '../utils/mcp.js';
33
31
  import { isReportProblemBlockedForClient } from '../utils/mcp_clients.js';
34
- import { buildPaymentRequiredResponse, isX402PaymentRequiredError } from '../utils/payment_errors.js';
35
32
  import { createProgressTracker } from '../utils/progress.js';
36
33
  import { getServerInstructions } from '../utils/server-instructions/index.js';
37
34
  import { parseServerMode, resolveServerMode } from '../utils/server_mode.js';
38
- import { applyToolTelemetry, classifyFailureCategory, deriveResourceIds, extractAjvErrorDetails, getToolStatusFromError, } from '../utils/tool_status.js';
35
+ import { extractAjvErrorDetails } from '../utils/tool_status.js';
39
36
  import { buildActorFields, extractActorId, extractActorName, getToolFullName, getToolPublicFieldOnly, } from '../utils/tools.js';
40
37
  import { getActors, getToolsForServerMode, toolNamesToInput } from '../utils/tools_loader.js';
41
- import { getUserInfoCached } from '../utils/userid_cache.js';
42
- import { getPackageVersion } from '../utils/version.js';
43
- import { connectMCPClient } from './client.js';
44
- import { EXTERNAL_TOOL_CALL_TIMEOUT_MSEC, LOG_LEVEL_MAP } from './const.js';
45
- import { createTaskCancellationWatcher, isTaskCancelled, isTaskNotFoundError, parseInputParamsFromUrl, storeTaskResultOrSkipIfExpired, } from './utils.js';
38
+ import { LOG_LEVEL_MAP } from './const.js';
39
+ import { emitTaskStatusNotification, executeToolAndUpdateTask } from './task_execution.js';
40
+ import { buildToolCallErrorResult, TOOL_CALL_ERROR_KIND } from './tool_call_error_mapper.js';
41
+ import { logToolCallAndTelemetry, prepareTelemetryData } from './tool_call_telemetry.js';
42
+ import { dispatchToolCall } from './tool_dispatch.js';
43
+ import { parseInputParamsFromUrl, storeTaskResultOrSkipIfExpired } from './utils.js';
46
44
  /**
47
45
  * Returns true when the initialize request advertises the MCP Apps UI extension
48
46
  * with the widget MIME type. Used to resolve `'auto'` server mode.
@@ -56,33 +54,6 @@ function isUiSupportedByClient(request) {
56
54
  const uiCap = getUiCapability((_a = request === null || request === void 0 ? void 0 : request.params) === null || _a === void 0 ? void 0 : _a.capabilities);
57
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;
58
56
  }
59
- /** Send notifications/tasks/status for taskId. Routes via session transport (no relatedRequestId).
60
- * Swallows errors — notifications are advisory. */
61
- export async function emitTaskStatusNotification(taskId, mcpSessionId, taskStore, server) {
62
- try {
63
- const task = await taskStore.getTask(taskId, mcpSessionId);
64
- if (!task)
65
- return;
66
- // Per spec: notifications/tasks/status MUST NOT carry _meta.related-task (task ID is in params).
67
- // Called without options so the notification routes through the session transport,
68
- // not the request-scoped stream (which closes once the initial { task } response is flushed).
69
- await server.notification({
70
- method: 'notifications/tasks/status',
71
- params: {
72
- taskId: task.taskId,
73
- status: task.status,
74
- createdAt: task.createdAt,
75
- lastUpdatedAt: task.lastUpdatedAt,
76
- ttl: task.ttl,
77
- ...(task.statusMessage != null && { statusMessage: task.statusMessage }),
78
- ...(task.pollInterval != null && { pollInterval: task.pollInterval }),
79
- },
80
- });
81
- }
82
- catch {
83
- // Silent fail — notifications are advisory
84
- }
85
- }
86
57
  /**
87
58
  * Shared diagnostics for a pre-flight failure (standby-provider conflict or missing/invalid payment
88
59
  * signature) — a call outcome already known before any Actor runs. Standby rejection wins over the
@@ -199,18 +170,18 @@ export class ActorsMcpServer {
199
170
  writable: true,
200
171
  value: []
201
172
  });
202
- // Telemetry configuration (resolved from options and env vars in setupTelemetry)
173
+ // Telemetry configuration (resolved from options and env vars, see setupTelemetry)
203
174
  Object.defineProperty(this, "telemetryEnabled", {
204
175
  enumerable: true,
205
176
  configurable: true,
206
177
  writable: true,
207
- value: null
178
+ value: void 0
208
179
  });
209
180
  Object.defineProperty(this, "telemetryEnv", {
210
181
  enumerable: true,
211
182
  configurable: true,
212
183
  writable: true,
213
- value: DEFAULT_TELEMETRY_ENV
184
+ value: void 0
214
185
  });
215
186
  // List of widgets that are ready to be served
216
187
  Object.defineProperty(this, "availableWidgets", {
@@ -277,7 +248,9 @@ export class ActorsMcpServer {
277
248
  },
278
249
  instructions: getServerInstructions(),
279
250
  });
280
- this.setupTelemetry();
251
+ const { telemetryEnabled, telemetryEnv } = this.setupTelemetry();
252
+ this.telemetryEnabled = telemetryEnabled;
253
+ this.telemetryEnv = telemetryEnv;
281
254
  this.setupInitializeHandler();
282
255
  this.setupLoggingProxy();
283
256
  this.tools = new Map();
@@ -296,18 +269,21 @@ export class ActorsMcpServer {
296
269
  */
297
270
  setupTelemetry() {
298
271
  var _a, _b, _c;
272
+ let telemetryEnabled;
299
273
  const explicitEnabled = parseBooleanOrNull((_a = this.options.telemetry) === null || _a === void 0 ? void 0 : _a.enabled);
300
274
  if (explicitEnabled !== null) {
301
- this.telemetryEnabled = explicitEnabled;
275
+ telemetryEnabled = explicitEnabled;
302
276
  }
303
277
  else {
304
278
  const envEnabled = parseBooleanOrNull(process.env.TELEMETRY_ENABLED);
305
- this.telemetryEnabled = envEnabled !== null && envEnabled !== void 0 ? envEnabled : DEFAULT_TELEMETRY_ENABLED;
279
+ telemetryEnabled = envEnabled !== null && envEnabled !== void 0 ? envEnabled : DEFAULT_TELEMETRY_ENABLED;
306
280
  }
307
281
  // Configure telemetryEnv: explicit option > env var > default ('PROD')
308
- if (this.telemetryEnabled) {
309
- 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);
282
+ let telemetryEnv = DEFAULT_TELEMETRY_ENV;
283
+ if (telemetryEnabled) {
284
+ telemetryEnv = getTelemetryEnv((_c = (_b = this.options.telemetry) === null || _b === void 0 ? void 0 : _b.env) !== null && _c !== void 0 ? _c : process.env.TELEMETRY_ENV);
310
285
  }
286
+ return { telemetryEnabled, telemetryEnv };
311
287
  }
312
288
  /**
313
289
  * Override the SDK's `initialize` request handler to run mode resolution and
@@ -389,23 +365,10 @@ export class ActorsMcpServer {
389
365
  * survive a load that never sees an initialize.
390
366
  */
391
367
  isReportProblemServable() {
392
- return (!!this.telemetryEnabled &&
368
+ return (this.telemetryEnabled &&
393
369
  this.clientKnown &&
394
370
  !isReportProblemBlockedForClient(this.options.initializeRequestData));
395
371
  }
396
- /**
397
- * Append the report-problem nudge to a failed tool result. Thin wrapper over
398
- * {@link appendReportProblemNudge} that fills in whether report-problem is served on this
399
- * connection; suppression by category/402 is handled inside the helper.
400
- */
401
- withReportProblemNudge(result, failingToolName, failureCategory, failureHttpStatus) {
402
- return appendReportProblemNudge(result, {
403
- failingToolName,
404
- available: this.tools.has(HELPER_TOOLS.PROBLEM_REPORT),
405
- failureCategory,
406
- failureHttpStatus,
407
- });
408
- }
409
372
  composePendingToolsForClient() {
410
373
  if (this.pendingToolsUntilClientKnown.length === 0)
411
374
  return;
@@ -843,7 +806,7 @@ export class ActorsMcpServer {
843
806
  * @throws {McpError} - based on the McpServer class code from the typescript MCP SDK
844
807
  */
845
808
  this.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
846
- var _a, _b, _c, _d, _e, _f;
809
+ var _a, _b, _c, _d, _e;
847
810
  const params = request.params;
848
811
  // eslint-disable-next-line prefer-const
849
812
  let { name, arguments: args, _meta: meta } = params;
@@ -870,7 +833,13 @@ export class ActorsMcpServer {
870
833
  // On a failed result, nudge the agent to report the blocker via report-problem at the
871
834
  // moment it decides what to do next. Gated on the tool actually being served (see
872
835
  // isReportProblemServable), so clients where it is blocklisted or telemetry is off never see it.
873
- const augmented = this.withReportProblemNudge(r, resolvedToolName, callDiagnostics.failure_category, callDiagnostics.failure_http_status);
836
+ const augmented = withReportProblemNudge({
837
+ result: r,
838
+ tools: this.tools,
839
+ failingToolName: resolvedToolName,
840
+ failureCategory: callDiagnostics.failure_category,
841
+ failureHttpStatus: callDiagnostics.failure_http_status,
842
+ });
874
843
  toolResult = augmented;
875
844
  return augmented;
876
845
  };
@@ -892,7 +861,12 @@ export class ActorsMcpServer {
892
861
  };
893
862
  // Initialize telemetry with raw tool name — updated below once the tool is resolved.
894
863
  // This ensures telemetry is available even for early failures (missing token, tool not found).
895
- const { telemetryData, userId } = await this.prepareTelemetryData(name, mcpSessionId, apifyToken);
864
+ const { telemetryData, userId } = await prepareTelemetryData({
865
+ toolName: name,
866
+ mcpSessionId,
867
+ apifyToken,
868
+ apifyMcpServer: this,
869
+ });
896
870
  // actorName/actorId are declared here so they're available in the catch block for telemetry.
897
871
  // Set after tool resolution (inside the try block).
898
872
  let actorName;
@@ -1060,7 +1034,7 @@ export class ActorsMcpServer {
1060
1034
  }
1061
1035
  // Execute the tool asynchronously and update task status
1062
1036
  setImmediate(() => {
1063
- this.executeToolAndUpdateTask({
1037
+ executeToolAndUpdateTask({
1064
1038
  taskId: task.taskId,
1065
1039
  tool,
1066
1040
  toolArgs: toolArgs,
@@ -1072,6 +1046,7 @@ export class ActorsMcpServer {
1072
1046
  mcpSessionId,
1073
1047
  actorName,
1074
1048
  actorId,
1049
+ apifyMcpServer: this,
1075
1050
  }).catch((error) =>
1076
1051
  // Benign task-expiry is handled in-method (see the catch block and
1077
1052
  // storeTaskResultOrSkipIfExpired); anything reaching here is genuinely unexpected.
@@ -1090,196 +1065,75 @@ export class ActorsMcpServer {
1090
1065
  callDiagnostics = outcome.callDiagnostics;
1091
1066
  return captureResult(outcome.result);
1092
1067
  }
1093
- // Handle internal tool
1094
- if (tool.type === TOOL_TYPE.INTERNAL) {
1095
- // Tools that may emit notifications/progress during a sync wait must be opted in here.
1096
- // call-actor: emits during start+waitForFinish. get-actor-run: emits when waitSecs > 0.
1097
- const progressTrackerOptIn = tool.name === HELPER_TOOLS.ACTOR_CALL || tool.name === HELPER_TOOLS.ACTOR_RUNS_GET;
1098
- const progressTracker = progressTrackerOptIn
1099
- ? createProgressTracker(progressToken, extra.sendNotification)
1100
- : null;
1101
- try {
1102
- log.info('Calling internal tool', { toolName: tool.name, mcpSessionId, input: logSafeArgs });
1103
- const res = (await tool.call({
1104
- args: toolArgs,
1105
- extra,
1106
- apifyMcpServer: this,
1107
- mcpServer: this.server,
1108
- apifyToken,
1109
- apifyClient: apifyClient,
1110
- progressTracker,
1111
- mcpSessionId,
1112
- }));
1113
- // Extract diagnostics and strip internal fields from res before returning to client.
1114
- ({ toolStatus, callDiagnostics } = applyToolTelemetry(res, actorName, actorId, callDiagnostics));
1115
- return captureResult(res);
1116
- }
1117
- finally {
1118
- progressTracker === null || progressTracker === void 0 ? void 0 : progressTracker.stop();
1119
- }
1120
- }
1121
- if (tool.type === TOOL_TYPE.ACTOR_MCP) {
1122
- let client = null;
1123
- try {
1124
- client = await connectMCPClient(tool.serverUrl, apifyToken, mcpSessionId);
1125
- if (!client) {
1126
- const msg = dedent `
1127
- Failed to connect to MCP server at "${tool.serverUrl}".
1128
- Please verify the server URL is correct and accessible, and ensure you have a valid Apify token with appropriate permissions.
1129
- `;
1130
- log.softFail(msg, { mcpSessionId, failureCategory: FAILURE_CATEGORY.INTERNAL_ERROR });
1131
- await this.server.sendLoggingMessage({ level: 'error', data: msg });
1132
- toolStatus = TOOL_STATUS.SOFT_FAIL;
1133
- callDiagnostics = { ...callDiagnostics, failure_category: FAILURE_CATEGORY.INTERNAL_ERROR };
1134
- return captureResult(respondErrorNoTelemetry(msg));
1135
- }
1136
- // Only set up notification handlers if progressToken is provided by the client
1137
- if (progressToken !== undefined && progressToken !== null) {
1138
- // Set up notification handlers for the client
1139
- for (const schema of ServerNotificationSchema.options) {
1140
- const method = schema.shape.method.value;
1141
- // Forward notifications from the proxy client to the server
1142
- client.setNotificationHandler(schema, async (notification) => {
1143
- log.debug('Sending MCP notification', {
1144
- method,
1145
- mcpSessionId,
1146
- notification,
1147
- });
1148
- await extra.sendNotification(notification);
1149
- });
1150
- }
1151
- }
1152
- log.info('Calling Actor-MCP', {
1153
- toolName: tool.name,
1154
- actorMcpToolName: tool.originToolName,
1155
- actorId: tool.actorId,
1156
- mcpSessionId,
1157
- input: logSafeArgs,
1158
- });
1159
- const res = await client.callTool({
1160
- name: tool.originToolName,
1161
- arguments: toolArgs,
1162
- _meta: { progressToken },
1163
- }, CallToolResultSchema, {
1164
- timeout: EXTERNAL_TOOL_CALL_TIMEOUT_MSEC,
1165
- });
1166
- // TODO: actor-mcp responses are opaque — isError could be a user input problem
1167
- // (e.g. invalid query) or a genuine server failure. We can't distinguish without
1168
- // parsing the error text. Defaulting to INTERNAL_ERROR for now; revisit when
1169
- // actor-mcp gets deeper telemetry treatment.
1170
- if ('isError' in res && res.isError) {
1171
- toolStatus = TOOL_STATUS.SOFT_FAIL;
1172
- callDiagnostics = {
1173
- failure_category: FAILURE_CATEGORY.INTERNAL_ERROR,
1174
- ...buildActorFields(actorName, actorId),
1175
- };
1176
- }
1177
- return captureResult({ ...res });
1178
- }
1179
- catch (error) {
1180
- toolStatus = getToolStatusFromError(error, Boolean((_e = extra.signal) === null || _e === void 0 ? void 0 : _e.aborted));
1181
- const failureDetail = error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200);
1182
- callDiagnostics = {
1183
- failure_category: classifyFailureCategory(error),
1184
- failure_detail: failureDetail,
1185
- ...buildActorFields(actorName, actorId),
1186
- };
1187
- logHttpError(error, `Failed to call MCP tool '${tool.originToolName}' on Actor '${tool.actorId}'`, {
1188
- actorId: tool.actorId,
1189
- toolName: tool.originToolName,
1190
- failureCategory: callDiagnostics.failure_category,
1191
- });
1192
- return captureResult(respondErrorNoTelemetry(`Failed to call MCP tool '${tool.originToolName}' on Actor '${tool.actorId}': ${remoteMcpFailureDetail(error)}`));
1193
- }
1194
- finally {
1195
- if (client)
1196
- await client.close();
1197
- }
1198
- }
1199
- // Handle actor tool
1200
- if (tool.type === TOOL_TYPE.ACTOR) {
1201
- const progressTracker = createProgressTracker(progressToken, extra.sendNotification);
1202
- try {
1203
- log.info('Calling Actor', {
1204
- toolName: tool.name,
1205
- actorName: tool.actorFullName,
1206
- mcpSessionId,
1207
- input: logSafeArgs,
1208
- });
1209
- const executorResult = await actorExecutor.executeActorTool({
1210
- actorFullName: tool.actorFullName,
1211
- input: toolArgs,
1212
- apifyClient: apifyClient,
1213
- callOptions: { memory: tool.memoryMbytes },
1214
- progressTracker,
1215
- abortSignal: extra.signal,
1216
- mcpSessionId,
1217
- datasetItemsSchema: tool.datasetItemsSchema,
1218
- });
1219
- if (!executorResult) {
1220
- toolStatus = TOOL_STATUS.ABORTED;
1221
- // Receivers of cancellation notifications SHOULD NOT send a response for the cancelled request
1222
- // https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/cancellation#behavior-requirements
1223
- return captureResult({});
1224
- }
1225
- // Mirror the INTERNAL branch: read the telemetry the executor embedded on error
1226
- // results (e.g. respondUserError → SOFT_FAIL/INVALID_INPUT), strip it from the wire,
1227
- // and set failure_category so the report-problem nudge picks the softer variant.
1228
- ({ toolStatus, callDiagnostics } = applyToolTelemetry(executorResult, actorName, actorId, callDiagnostics));
1229
- return captureResult(executorResult);
1230
- }
1231
- finally {
1232
- if (progressTracker) {
1233
- progressTracker.stop();
1234
- }
1235
- }
1236
- }
1237
- // If we reached here without returning, it means the tool type was not recognized (user error)
1238
- toolStatus = TOOL_STATUS.SOFT_FAIL;
1068
+ // Progress tracker: opt in for the two INTERNAL tools that emit during a sync wait
1069
+ // (call-actor start+waitForFinish, get-actor-run when waitSecs > 0), and unconditionally
1070
+ // for ACTOR tools. ACTOR_MCP forwards notifications directly, not via a tracker.
1071
+ const progressTrackerOptIn = tool.type === TOOL_TYPE.ACTOR ||
1072
+ (tool.type === TOOL_TYPE.INTERNAL &&
1073
+ (tool.name === HELPER_TOOLS.ACTOR_CALL || tool.name === HELPER_TOOLS.ACTOR_RUNS_GET));
1074
+ const progressTracker = progressTrackerOptIn
1075
+ ? createProgressTracker(progressToken, extra.sendNotification)
1076
+ : null;
1077
+ const dispatchResult = await dispatchToolCall({
1078
+ tool,
1079
+ toolArgs: toolArgs,
1080
+ logSafeArgs,
1081
+ apifyToken,
1082
+ apifyClient: apifyClient,
1083
+ apifyMcpServer: this,
1084
+ mcpSessionId,
1085
+ progressToken,
1086
+ progressTracker,
1087
+ shouldForwardNotifications: true,
1088
+ extra,
1089
+ actorName,
1090
+ actorId,
1091
+ taskMode: false,
1092
+ });
1093
+ toolStatus = dispatchResult.toolStatus;
1094
+ callDiagnostics = dispatchResult.callDiagnostics;
1095
+ return captureResult(dispatchResult.result);
1239
1096
  }
1240
1097
  catch (error) {
1241
- const httpStatus = getHttpStatusCode(error);
1098
+ // Re-throw MCP protocol errors (e.g. from failInvalidParams) so the SDK
1099
+ // returns them as JSON-RPC errors. failInvalidParams already set callDiagnostics
1100
+ // with the correct semantic category (e.g. AUTH), so we must not overwrite it.
1101
+ // Re-throwing first is order-equivalent only because no McpError with an HTTP-range
1102
+ // code reaches this catch: an McpError(402) WOULD satisfy the x402 predicate
1103
+ // (getHttpStatusCode falls through to `.code`), but every remote-McpError route is
1104
+ // sealed by an inner catch (ACTOR_MCP branch, call-actor MCP passthrough) and all
1105
+ // in-repo McpErrors use negative ErrorCode.* values.
1106
+ if (error instanceof McpError) {
1107
+ throw error;
1108
+ }
1109
+ const errorResult = buildToolCallErrorResult(error, {
1110
+ toolName: name,
1111
+ actorName,
1112
+ actorId,
1113
+ isAborted: Boolean((_e = extra.signal) === null || _e === void 0 ? void 0 : _e.aborted),
1114
+ });
1115
+ toolStatus = errorResult.toolStatus;
1242
1116
  // Propagate 402 Payment Required as a tool result per x402 MCP transport spec:
1243
- // content[0].text (JSON) + isError: true. The concurrent-run limit falls through
1244
- // to the run-limit message below.
1245
- if (isX402PaymentRequiredError(error)) {
1246
- toolStatus = TOOL_STATUS.SOFT_FAIL;
1247
- callDiagnostics = {
1248
- failure_category: FAILURE_CATEGORY.INVALID_INPUT,
1249
- failure_http_status: 402,
1250
- ...buildActorFields(actorName, actorId),
1251
- };
1252
- return captureResult(buildPaymentRequiredResponse(error));
1117
+ // content[0].text (JSON) + isError: true. No log here (unlike the task path). The
1118
+ // concurrent-run limit also surfaces as 402 but is excluded by the predicate and
1119
+ // falls through to the generic run-limit handling below.
1120
+ if (errorResult.kind === TOOL_CALL_ERROR_KIND.PAYMENT) {
1121
+ callDiagnostics = errorResult.callDiagnostics;
1122
+ return captureResult(errorResult.response);
1253
1123
  }
1254
- if (isPermissionApprovalError(error)) {
1255
- toolStatus = TOOL_STATUS.SOFT_FAIL;
1256
- callDiagnostics = {
1257
- failure_category: FAILURE_CATEGORY.PERMISSION_APPROVAL_REQUIRED,
1258
- failure_http_status: error.statusCode,
1259
- ...buildActorFields(actorName, actorId),
1260
- };
1124
+ if (errorResult.kind === TOOL_CALL_ERROR_KIND.APPROVAL) {
1125
+ callDiagnostics = errorResult.callDiagnostics;
1261
1126
  logHttpError(error, 'Permission approval required while calling tool', {
1262
1127
  toolName: name,
1263
1128
  mcpSessionId,
1264
1129
  });
1265
- return captureResult(buildPermissionApprovalResponse(error));
1266
- }
1267
- // Re-throw MCP protocol errors (e.g. from failInvalidParams) so the SDK
1268
- // returns them as JSON-RPC errors. failInvalidParams already set callDiagnostics
1269
- // with the correct semantic category (e.g. AUTH), so we must not overwrite it.
1270
- if (error instanceof McpError) {
1271
- throw error;
1130
+ return captureResult(errorResult.response);
1272
1131
  }
1273
- toolStatus = getToolStatusFromError(error, Boolean((_f = extra.signal) === null || _f === void 0 ? void 0 : _f.aborted));
1274
- const failureDetail = error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200);
1275
1132
  callDiagnostics = {
1276
1133
  // Spread existing diagnostics first (e.g. validation_keyword from failInvalidParams),
1277
- // then overwrite with freshly computed fields so they take precedence.
1134
+ // then overwrite with the mapper's freshly computed fields so they take precedence.
1278
1135
  ...callDiagnostics,
1279
- failure_category: classifyFailureCategory(error),
1280
- ...(httpStatus !== undefined ? { failure_http_status: httpStatus } : {}),
1281
- failure_detail: failureDetail,
1282
- ...buildActorFields(actorName, actorId),
1136
+ ...errorResult.callDiagnostics,
1283
1137
  };
1284
1138
  logHttpError(error, 'Error occurred while calling tool', {
1285
1139
  toolName: name,
@@ -1298,14 +1152,14 @@ export class ActorsMcpServer {
1298
1152
  // reuse the local ABORTED-aware toolStatus, do NOT re-derive from the error (which
1299
1153
  // would drop ABORTED and leak failureCategory/failureHttpStatus onto the wire).
1300
1154
  return captureResult({
1301
- ...respondOk(getToolCallErrorUserText(name, error)),
1155
+ ...respondOk(errorResult.userText),
1302
1156
  isError: true,
1303
1157
  toolTelemetry: { toolStatus },
1304
1158
  });
1305
1159
  }
1306
1160
  finally {
1307
1161
  if (shouldTrackTelemetry) {
1308
- this.logToolCallAndTelemetry({
1162
+ logToolCallAndTelemetry({
1309
1163
  toolName: resolvedToolName,
1310
1164
  mcpSessionId,
1311
1165
  toolStatus,
@@ -1316,407 +1170,11 @@ export class ActorsMcpServer {
1316
1170
  args,
1317
1171
  result: toolResult,
1318
1172
  taskId: preflightTaskId,
1319
- });
1320
- }
1321
- }
1322
- const availableTools = this.listToolNames();
1323
- const msg = dedent `
1324
- Unknown tool type for "${name}".
1325
- Available tools: ${availableTools.length > 0 ? availableTools.join(', ') : 'none'}.
1326
- Please verify the tool name and ensure the tool is properly registered.
1327
- `;
1328
- log.softFail(msg, { mcpSessionId, statusCode: 404 });
1329
- await this.server.sendLoggingMessage({
1330
- level: 'error',
1331
- data: msg,
1332
- });
1333
- throw new McpError(ErrorCode.InvalidParams, msg);
1334
- });
1335
- }
1336
- /**
1337
- * Logs tool call completion at INFO level and tracks telemetry.
1338
- * Computes duration once so both the log line and telemetry event use the same value.
1339
- * Response bytes and resource ids are derived here from the raw `result` (+ `args`) so every
1340
- * call site stays a plain hand-off — no path can forget to compute or strip them.
1341
- */
1342
- logToolCallAndTelemetry(params) {
1343
- const durationMs = Date.now() - params.startTime;
1344
- // `result` is undefined only on short-circuit paths that never produced a payload (e.g. a
1345
- // cancelled task); skip byte accounting there. `null`/`{}` still measure as zero bytes.
1346
- const responseBytes = params.result === undefined ? undefined : computeToolResponseBytes(params.result);
1347
- log.info('Tool call completed', {
1348
- toolName: params.toolName,
1349
- mcpSessionId: params.mcpSessionId,
1350
- toolStatus: params.toolStatus,
1351
- durationMs,
1352
- ...(responseBytes !== undefined && {
1353
- responseContentBytes: responseBytes.contentBytes,
1354
- responseStructuredContentBytes: responseBytes.structuredContentBytes,
1355
- responseFileBytes: responseBytes.fileBytes,
1356
- }),
1357
- ...(params.taskId !== undefined && { taskId: params.taskId }),
1358
- });
1359
- if (params.telemetryData) {
1360
- const finalizedTelemetryData = {
1361
- ...params.telemetryData,
1362
- tool_status: params.toolStatus,
1363
- tool_exec_time_ms: durationMs,
1364
- ...(responseBytes && {
1365
- tool_response_content_bytes: responseBytes.contentBytes,
1366
- tool_response_structured_content_bytes: responseBytes.structuredContentBytes,
1367
- tool_response_file_bytes: responseBytes.fileBytes,
1368
- }),
1369
- // Always include actor_name/actor_id; failure-specific fields are only present when callDiagnostics has them.
1370
- ...params.callDiagnostics,
1371
- // Resource ids are read once here from the args + the tool's public output; no tool
1372
- // threads them back. Applied uniformly, last. See deriveResourceIds.
1373
- ...deriveResourceIds(params.args, params.result),
1374
- };
1375
- trackToolCall(params.userId, this.telemetryEnv, finalizedTelemetryData);
1376
- // A successful report-problem call also emits a dedicated feedback event carrying the
1377
- // submission. A downstream Segment destination fans it out to Slack/GitHub.
1378
- if (params.toolName === HELPER_TOOLS.PROBLEM_REPORT &&
1379
- params.toolStatus === TOOL_STATUS.SUCCEEDED &&
1380
- params.args) {
1381
- trackReportedProblem(params.userId, this.telemetryEnv, buildReportedProblemProperties(finalizedTelemetryData, params.args));
1382
- }
1383
- }
1384
- }
1385
- // TODO: this function quite duplicates the main tool call login the CallToolRequestSchema handler, we should refactor
1386
- /**
1387
- * Executes a tool asynchronously for a long-running task and updates task status.
1388
- *
1389
- * @param params - Tool execution parameters
1390
- * @param params.taskId - The task identifier
1391
- * @param params.tool - The tool to execute
1392
- * @param params.args - Tool arguments
1393
- * @param params.apifyToken - Apify API token
1394
- * @param params.progressToken - Progress token for notifications
1395
- * @param params.extra - Extra request handler context
1396
- * @param params.mcpSessionId - MCP session ID for telemetry
1397
- */
1398
- async executeToolAndUpdateTask(params) {
1399
- const { taskId, tool, toolArgs, logSafeArgs, apifyClient, apifyToken, progressToken, extra, mcpSessionId, actorName, actorId, } = params;
1400
- let toolStatus = TOOL_STATUS.SUCCEEDED;
1401
- // Always populate actor fields so they're tracked on both success and failure paths.
1402
- let callDiagnostics = { ...buildActorFields(actorName, actorId) };
1403
- const startTime = Date.now();
1404
- log.debug('[executeToolAndUpdateTask] Starting task execution', {
1405
- taskId,
1406
- toolName: tool.name,
1407
- mcpSessionId,
1408
- });
1409
- // Prepare telemetry before try-catch so it's accessible to both paths.
1410
- // This avoids re-fetching user data in the error handler.
1411
- const { telemetryData, userId } = await this.prepareTelemetryData(getToolFullName(tool), mcpSessionId, apifyToken);
1412
- const finishTaskTracking = (status, diagnostics, result) => {
1413
- this.logToolCallAndTelemetry({
1414
- toolName: tool.name,
1415
- mcpSessionId,
1416
- toolStatus: status,
1417
- startTime,
1418
- taskId,
1419
- telemetryData,
1420
- userId,
1421
- callDiagnostics: diagnostics,
1422
- args: toolArgs,
1423
- result,
1424
- });
1425
- };
1426
- // Once a task is cancelled the spec forbids writing a result; every storage path
1427
- // must short-circuit here. `logSuffix` is concatenated after "Task was cancelled"
1428
- // so we keep the existing log format and the existing telemetry status per path.
1429
- const skipIfTaskCancelled = async (logSuffix, status, diagnostics) => {
1430
- if (!(await isTaskCancelled(taskId, mcpSessionId, this.taskStore)))
1431
- return false;
1432
- log.debug(`[executeToolAndUpdateTask] Task was cancelled${logSuffix}`, { taskId, mcpSessionId });
1433
- finishTaskTracking(status, diagnostics);
1434
- return true;
1435
- };
1436
- // Bridges MCP `tasks/cancel` to the running handler: when the client
1437
- // explicitly cancels the task, this signal aborts so the underlying
1438
- // Actor run stops instead of consuming compute until natural completion.
1439
- // Per MCP tasks spec, request-level aborts (client disconnect,
1440
- // notifications/cancelled for the original request ID) MUST NOT cancel
1441
- // the task — `extra.signal` is intentionally not chained here.
1442
- const cancelWatcher = createTaskCancellationWatcher({
1443
- taskId,
1444
- mcpSessionId,
1445
- taskStore: this.taskStore,
1446
- });
1447
- const taskExtra = { ...extra, signal: cancelWatcher.signal };
1448
- try {
1449
- log.debug('[executeToolAndUpdateTask] Updating task status to working', {
1450
- taskId,
1451
- mcpSessionId,
1452
- });
1453
- // The store rejects terminal → 'working' transitions. If `tasks/cancel` raced
1454
- // with us (between handler dispatch and the first watcher tick at ~500 ms),
1455
- // updateTaskStatus throws — re-check the store to tell a clean cancel-race
1456
- // apart from a genuine store error.
1457
- // noinspection ExceptionCaughtLocallyJS
1458
- try {
1459
- await this.taskStore.updateTaskStatus(taskId, 'working', undefined, mcpSessionId);
1460
- }
1461
- catch (err) {
1462
- if (await skipIfTaskCancelled(' before execution started, skipping', TOOL_STATUS.ABORTED, {
1463
- ...buildActorFields(actorName, actorId),
1464
- }))
1465
- return;
1466
- throw err;
1467
- }
1468
- await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
1469
- // Execute the tool and get the result
1470
- let result = {};
1471
- // Callback to propagate Actor run statusMessage into the task store and emit a push notification.
1472
- // TODO(TC-3): cancel arriving while this is scheduled throws cancelled → working;
1473
- // currently swallowed by progress.ts's tick catch — guard or catch explicitly.
1474
- const onStatusMessage = async (message) => {
1475
- await this.taskStore.updateTaskStatus(taskId, 'working', message, mcpSessionId);
1476
- await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
1477
- };
1478
- // Handle internal tool execution in task mode
1479
- if (toolStatus === TOOL_STATUS.SUCCEEDED && tool.type === TOOL_TYPE.INTERNAL) {
1480
- const progressTracker = createProgressTracker(progressToken, extra.sendNotification, taskId, onStatusMessage);
1481
- try {
1482
- log.info('Calling internal tool for task', {
1483
- taskId,
1484
- toolName: tool.name,
1485
- mcpSessionId,
1486
- input: logSafeArgs,
1487
- });
1488
- const res = (await tool.call({
1489
- args: toolArgs,
1490
- extra: taskExtra,
1491
1173
  apifyMcpServer: this,
1492
- mcpServer: this.server,
1493
- apifyToken,
1494
- apifyClient,
1495
- progressTracker,
1496
- mcpSessionId,
1497
- taskMode: true,
1498
- }));
1499
- ({ toolStatus, callDiagnostics } = applyToolTelemetry(res, actorName, actorId, callDiagnostics));
1500
- result = res;
1501
- }
1502
- finally {
1503
- if (progressTracker) {
1504
- progressTracker.stop();
1505
- }
1506
- }
1507
- }
1508
- // Handle actor tool execution in task mode
1509
- if (toolStatus === TOOL_STATUS.SUCCEEDED && tool.type === TOOL_TYPE.ACTOR) {
1510
- const progressTracker = createProgressTracker(progressToken, extra.sendNotification, taskId, onStatusMessage);
1511
- try {
1512
- log.info('Calling Actor for task', {
1513
- taskId,
1514
- toolName: tool.name,
1515
- actorName: tool.actorFullName,
1516
- mcpSessionId,
1517
- input: logSafeArgs,
1518
1174
  });
1519
- const executorResult = await actorExecutor.executeActorTool({
1520
- actorFullName: tool.actorFullName,
1521
- input: toolArgs,
1522
- apifyClient,
1523
- callOptions: { memory: tool.memoryMbytes },
1524
- progressTracker,
1525
- abortSignal: cancelWatcher.signal,
1526
- mcpSessionId,
1527
- datasetItemsSchema: tool.datasetItemsSchema,
1528
- taskMode: true,
1529
- });
1530
- if (!executorResult) {
1531
- toolStatus = TOOL_STATUS.ABORTED;
1532
- // Receivers of cancellation notifications SHOULD NOT send a response for the cancelled request
1533
- // https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/cancellation#behavior-requirements
1534
- result = {};
1535
- }
1536
- else {
1537
- // Mirror the INTERNAL task branch: extract embedded telemetry so failure_category
1538
- // drives the success-path nudge below and toolTelemetry is stripped from the wire.
1539
- ({ toolStatus, callDiagnostics } = applyToolTelemetry(executorResult, actorName, actorId, callDiagnostics));
1540
- result = executorResult;
1541
- }
1542
- }
1543
- finally {
1544
- if (progressTracker) {
1545
- progressTracker.stop();
1546
- }
1547
1175
  }
1548
1176
  }
1549
- // Check if task was cancelled before storing result
1550
- if (await skipIfTaskCancelled(', skipping result storage', toolStatus))
1551
- return;
1552
- // On a failed result, nudge the agent to report the blocker via report-problem (mirrors the
1553
- // synchronous CallTool path, which task-mode calls like call-actor bypass).
1554
- result = this.withReportProblemNudge(result, tool.name, callDiagnostics.failure_category, callDiagnostics.failure_http_status);
1555
- // Store the result in the task store
1556
- log.debug('[executeToolAndUpdateTask] Storing completed result', {
1557
- taskId,
1558
- mcpSessionId,
1559
- });
1560
- await storeTaskResultOrSkipIfExpired(this.taskStore, tool.name, taskId, 'completed', result, mcpSessionId);
1561
- log.debug('Task completed successfully', { taskId, toolName: tool.name, mcpSessionId });
1562
- await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
1563
- finishTaskTracking(toolStatus, callDiagnostics, result);
1564
- }
1565
- catch (error) {
1566
- // Reached only when the task expired before the `working` transition (updateTaskStatus
1567
- // above rethrows the store's unknown-taskId error). The tool never ran and the task is
1568
- // gone, so soft-fail, record telemetry, and stop. Every result store (success and error
1569
- // paths) tolerates expiry via storeTaskResultOrSkipIfExpired, so they don't reach here.
1570
- if (isTaskNotFoundError(error)) {
1571
- log.softFail('Task expired before execution started', {
1572
- taskId,
1573
- toolName: tool.name,
1574
- mcpSessionId,
1575
- });
1576
- finishTaskTracking(TOOL_STATUS.SOFT_FAIL, {
1577
- failure_category: FAILURE_CATEGORY.INTERNAL_ERROR,
1578
- ...buildActorFields(actorName, actorId),
1579
- });
1580
- return;
1581
- }
1582
- // Handle 402 Payment Required — return structured x402 result so clients can auto-pay
1583
- const httpStatus = getHttpStatusCode(error);
1584
- if (isX402PaymentRequiredError(error)) {
1585
- logHttpError(error, 'Payment required while calling tool (task mode)', { toolName: tool.name });
1586
- // Per MCP tasks spec: once a task is cancelled it MUST remain cancelled,
1587
- // so guard storeTaskResult against a cancel that raced with this 402.
1588
- if (await skipIfTaskCancelled(', skipping 402 result storage', TOOL_STATUS.ABORTED, {
1589
- ...buildActorFields(actorName, actorId),
1590
- }))
1591
- return;
1592
- const paymentResponse = buildPaymentRequiredResponse(error);
1593
- await storeTaskResultOrSkipIfExpired(this.taskStore, tool.name, taskId, 'completed', paymentResponse, mcpSessionId);
1594
- await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
1595
- finishTaskTracking(TOOL_STATUS.SOFT_FAIL, {
1596
- failure_category: FAILURE_CATEGORY.INVALID_INPUT,
1597
- failure_http_status: 402,
1598
- ...buildActorFields(actorName, actorId),
1599
- }, paymentResponse);
1600
- return;
1601
- }
1602
- if (isPermissionApprovalError(error)) {
1603
- logHttpError(error, 'Permission approval required while calling tool (task mode)', {
1604
- toolName: tool.name,
1605
- });
1606
- // Per MCP tasks spec: once a task is cancelled it MUST remain cancelled,
1607
- // so guard storeTaskResult against a cancel that raced with this approval error.
1608
- if (await skipIfTaskCancelled(', skipping permission-approval result storage', TOOL_STATUS.ABORTED, {
1609
- ...buildActorFields(actorName, actorId),
1610
- }))
1611
- return;
1612
- const approvalResponse = buildPermissionApprovalResponse(error);
1613
- await storeTaskResultOrSkipIfExpired(this.taskStore, tool.name, taskId, 'completed', approvalResponse, mcpSessionId);
1614
- await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
1615
- finishTaskTracking(TOOL_STATUS.SOFT_FAIL, {
1616
- failure_category: FAILURE_CATEGORY.PERMISSION_APPROVAL_REQUIRED,
1617
- failure_http_status: error.statusCode,
1618
- ...buildActorFields(actorName, actorId),
1619
- }, approvalResponse);
1620
- return;
1621
- }
1622
- toolStatus = getToolStatusFromError(error, Boolean(cancelWatcher.signal.aborted));
1623
- const failureDetail = error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200);
1624
- callDiagnostics = {
1625
- failure_category: classifyFailureCategory(error),
1626
- ...(httpStatus !== undefined ? { failure_http_status: httpStatus } : {}),
1627
- failure_detail: failureDetail,
1628
- ...buildActorFields(actorName, actorId),
1629
- };
1630
- // Log level follows the already-classified toolStatus:
1631
- // SOFT_FAIL (e.g. 402/403 user quota, client-side issues) → softFail
1632
- // FAILED/ABORTED/other → error
1633
- if (toolStatus === TOOL_STATUS.SOFT_FAIL) {
1634
- // Mezmo promotes on "error" in message/keys — use errMessage key, sanitized.
1635
- const errMessage = sanitizeMezmoMessage(error instanceof Error ? error.message : String(error));
1636
- log.softFail('Tool execution soft-failed for task', {
1637
- taskId,
1638
- toolName: tool.name,
1639
- toolStatus,
1640
- mcpSessionId,
1641
- failureCategory: callDiagnostics.failure_category,
1642
- failureHttpStatus: callDiagnostics.failure_http_status,
1643
- actorName: callDiagnostics.actor_name,
1644
- errMessage,
1645
- });
1646
- }
1647
- else {
1648
- log.error('Error executing tool for task', {
1649
- taskId,
1650
- toolName: tool.name,
1651
- toolStatus,
1652
- mcpSessionId,
1653
- failureCategory: callDiagnostics.failure_category,
1654
- failureHttpStatus: callDiagnostics.failure_http_status,
1655
- actorName: callDiagnostics.actor_name,
1656
- error,
1657
- });
1658
- }
1659
- const userText = getToolCallErrorUserText(tool.name, error);
1660
- // Check if task was cancelled before storing result
1661
- if (await skipIfTaskCancelled(', skipping result storage', toolStatus, callDiagnostics))
1662
- return;
1663
- log.debug('[executeToolAndUpdateTask] Storing failed result', {
1664
- taskId,
1665
- mcpSessionId,
1666
- });
1667
- // Nudge on a genuinely-failed task result. INTERNAL_ERROR and an unknown category get the
1668
- // full nudge; a genuine INVALID_INPUT gets the softer nudge; payment (402) is suppressed via
1669
- // the HTTP status and AUTH / PERMISSION_APPROVAL_REQUIRED via NON_NUDGE_FAILURE_CATEGORIES.
1670
- // The 402 branch above returns before reaching here, so this only sees non-payment failures.
1671
- const failedResult = this.withReportProblemNudge({
1672
- content: [
1673
- {
1674
- type: 'text',
1675
- text: userText,
1676
- },
1677
- ],
1678
- isError: true,
1679
- internalToolStatus: toolStatus,
1680
- }, tool.name, callDiagnostics.failure_category, callDiagnostics.failure_http_status);
1681
- await storeTaskResultOrSkipIfExpired(this.taskStore, tool.name, taskId, 'failed', failedResult, mcpSessionId);
1682
- await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
1683
- finishTaskTracking(toolStatus, callDiagnostics, failedResult);
1684
- }
1685
- finally {
1686
- cancelWatcher.dispose();
1687
- }
1688
- }
1689
- /*
1690
- * Creates telemetry data for a tool call.
1691
- */
1692
- async prepareTelemetryData(toolName, mcpSessionId, apifyToken) {
1693
- var _a, _b, _c, _d, _e;
1694
- if (!this.telemetryEnabled) {
1695
- return { telemetryData: null, userId: null };
1696
- }
1697
- // Get userId from cache or fetch from API
1698
- let userId = null;
1699
- if (apifyToken) {
1700
- const apifyClient = new ApifyClient({ token: apifyToken });
1701
- ({ userId } = await getUserInfoCached(apifyToken, apifyClient));
1702
- log.debug('Telemetry: fetched userId', { userId, mcpSessionId });
1703
- }
1704
- const capabilities = (_b = (_a = this.options.initializeRequestData) === null || _a === void 0 ? void 0 : _a.params) === null || _b === void 0 ? void 0 : _b.capabilities;
1705
- const params = (_c = this.options.initializeRequestData) === null || _c === void 0 ? void 0 : _c.params;
1706
- const telemetryData = {
1707
- app: 'mcp',
1708
- app_version: getPackageVersion() || '',
1709
- mcp_client_name: ((_d = params === null || params === void 0 ? void 0 : params.clientInfo) === null || _d === void 0 ? void 0 : _d.name) || '',
1710
- mcp_client_version: ((_e = params === null || params === void 0 ? void 0 : params.clientInfo) === null || _e === void 0 ? void 0 : _e.version) || '',
1711
- mcp_protocol_version: (params === null || params === void 0 ? void 0 : params.protocolVersion) || '',
1712
- mcp_client_capabilities: capabilities || null,
1713
- mcp_session_id: mcpSessionId || '',
1714
- transport_type: this.options.transportType || '',
1715
- tool_name: toolName,
1716
- tool_status: TOOL_STATUS.SUCCEEDED, // Will be updated in finally
1717
- tool_exec_time_ms: 0, // Will be calculated in finally
1718
- };
1719
- return { telemetryData, userId };
1177
+ });
1720
1178
  }
1721
1179
  /**
1722
1180
  * Resolves widgets and determines which ones are ready to be served.