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

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 (95) 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 -17
  10. package/dist/mcp/server.d.ts.map +1 -1
  11. package/dist/mcp/server.js +184 -435
  12. package/dist/mcp/server.js.map +1 -1
  13. package/dist/mcp/tool_call_error_mapper.d.ts +45 -0
  14. package/dist/mcp/tool_call_error_mapper.d.ts.map +1 -0
  15. package/dist/mcp/tool_call_error_mapper.js +59 -0
  16. package/dist/mcp/tool_call_error_mapper.js.map +1 -0
  17. package/dist/mcp/tool_call_telemetry.d.ts +37 -0
  18. package/dist/mcp/tool_call_telemetry.d.ts.map +1 -0
  19. package/dist/mcp/tool_call_telemetry.js +85 -0
  20. package/dist/mcp/tool_call_telemetry.js.map +1 -0
  21. package/dist/mcp/tool_dispatch.d.ts +49 -0
  22. package/dist/mcp/tool_dispatch.d.ts.map +1 -0
  23. package/dist/mcp/tool_dispatch.js +208 -0
  24. package/dist/mcp/tool_dispatch.js.map +1 -0
  25. package/dist/payments/const.d.ts +1 -0
  26. package/dist/payments/const.d.ts.map +1 -1
  27. package/dist/payments/const.js +1 -0
  28. package/dist/payments/const.js.map +1 -1
  29. package/dist/payments/skyfire.d.ts.map +1 -1
  30. package/dist/payments/skyfire.js +5 -6
  31. package/dist/payments/skyfire.js.map +1 -1
  32. package/dist/tools/actors/call_actor.d.ts +0 -3
  33. package/dist/tools/actors/call_actor.d.ts.map +1 -1
  34. package/dist/tools/actors/call_actor.js +2 -11
  35. package/dist/tools/actors/call_actor.js.map +1 -1
  36. package/dist/tools/actors/fetch_actor_details.d.ts +1 -7
  37. package/dist/tools/actors/fetch_actor_details.d.ts.map +1 -1
  38. package/dist/tools/actors/fetch_actor_details.js +14 -22
  39. package/dist/tools/actors/fetch_actor_details.js.map +1 -1
  40. package/dist/tools/actors/search_actors.d.ts +1 -6
  41. package/dist/tools/actors/search_actors.d.ts.map +1 -1
  42. package/dist/tools/actors/search_actors.js +17 -24
  43. package/dist/tools/actors/search_actors.js.map +1 -1
  44. package/dist/tools/dev/report_problem.d.ts +13 -0
  45. package/dist/tools/dev/report_problem.d.ts.map +1 -1
  46. package/dist/tools/dev/report_problem.js +14 -0
  47. package/dist/tools/dev/report_problem.js.map +1 -1
  48. package/dist/tools/docs/search_apify_docs.d.ts.map +1 -1
  49. package/dist/tools/docs/search_apify_docs.js +1 -0
  50. package/dist/tools/docs/search_apify_docs.js.map +1 -1
  51. package/dist/tools/runs/get_actor_run.d.ts +1 -6
  52. package/dist/tools/runs/get_actor_run.d.ts.map +1 -1
  53. package/dist/tools/runs/get_actor_run.js +17 -24
  54. package/dist/tools/runs/get_actor_run.js.map +1 -1
  55. package/dist/tools/storage/get_dataset.d.ts.map +1 -1
  56. package/dist/tools/storage/get_dataset.js +6 -5
  57. package/dist/tools/storage/get_dataset.js.map +1 -1
  58. package/dist/tools/storage/get_dataset_items.d.ts.map +1 -1
  59. package/dist/tools/storage/get_dataset_items.js +6 -8
  60. package/dist/tools/storage/get_dataset_items.js.map +1 -1
  61. package/dist/tools/storage/get_dataset_list.d.ts.map +1 -1
  62. package/dist/tools/storage/get_dataset_list.js +8 -9
  63. package/dist/tools/storage/get_dataset_list.js.map +1 -1
  64. package/dist/tools/storage/get_dataset_schema.d.ts.map +1 -1
  65. package/dist/tools/storage/get_dataset_schema.js +6 -4
  66. package/dist/tools/storage/get_dataset_schema.js.map +1 -1
  67. package/dist/tools/storage/get_key_value_store.js +3 -3
  68. package/dist/tools/storage/get_key_value_store.js.map +1 -1
  69. package/dist/tools/storage/get_key_value_store_keys.js +4 -4
  70. package/dist/tools/storage/get_key_value_store_keys.js.map +1 -1
  71. package/dist/tools/storage/get_key_value_store_list.d.ts.map +1 -1
  72. package/dist/tools/storage/get_key_value_store_list.js +8 -9
  73. package/dist/tools/storage/get_key_value_store_list.js.map +1 -1
  74. package/dist/tools/storage/get_key_value_store_record.d.ts.map +1 -1
  75. package/dist/tools/storage/get_key_value_store_record.js +3 -2
  76. package/dist/tools/storage/get_key_value_store_record.js.map +1 -1
  77. package/dist/tsconfig.tsbuildinfo +1 -1
  78. package/dist/types.d.ts +0 -11
  79. package/dist/types.d.ts.map +1 -1
  80. package/dist/types.js.map +1 -1
  81. package/dist/utils/actor_details.d.ts.map +1 -1
  82. package/dist/utils/actor_details.js +10 -3
  83. package/dist/utils/actor_details.js.map +1 -1
  84. package/dist/utils/logging.d.ts +2 -0
  85. package/dist/utils/logging.d.ts.map +1 -1
  86. package/dist/utils/logging.js +45 -1
  87. package/dist/utils/logging.js.map +1 -1
  88. package/dist/utils/payment_errors.d.ts +7 -1
  89. package/dist/utils/payment_errors.d.ts.map +1 -1
  90. package/dist/utils/payment_errors.js +10 -0
  91. package/dist/utils/payment_errors.js.map +1 -1
  92. package/dist/web/dist/actor-run-widget.js +0 -3
  93. package/dist/web/dist/search-actors-widget.js +0 -3
  94. package/package.json +2 -2
  95. 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,29 +19,26 @@ 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';
38
+ import { LOG_LEVEL_MAP } from './const.js';
39
+ import { buildToolCallErrorResult, TOOL_CALL_ERROR_KIND } from './tool_call_error_mapper.js';
40
+ import { logToolCallAndTelemetry, prepareTelemetryData } from './tool_call_telemetry.js';
41
+ import { dispatchToolCall } from './tool_dispatch.js';
45
42
  import { createTaskCancellationWatcher, isTaskCancelled, isTaskNotFoundError, parseInputParamsFromUrl, storeTaskResultOrSkipIfExpired, } from './utils.js';
46
43
  /**
47
44
  * Returns true when the initialize request advertises the MCP Apps UI extension
@@ -199,18 +196,18 @@ export class ActorsMcpServer {
199
196
  writable: true,
200
197
  value: []
201
198
  });
202
- // Telemetry configuration (resolved from options and env vars in setupTelemetry)
199
+ // Telemetry configuration (resolved from options and env vars, see setupTelemetry)
203
200
  Object.defineProperty(this, "telemetryEnabled", {
204
201
  enumerable: true,
205
202
  configurable: true,
206
203
  writable: true,
207
- value: null
204
+ value: void 0
208
205
  });
209
206
  Object.defineProperty(this, "telemetryEnv", {
210
207
  enumerable: true,
211
208
  configurable: true,
212
209
  writable: true,
213
- value: DEFAULT_TELEMETRY_ENV
210
+ value: void 0
214
211
  });
215
212
  // List of widgets that are ready to be served
216
213
  Object.defineProperty(this, "availableWidgets", {
@@ -277,7 +274,9 @@ export class ActorsMcpServer {
277
274
  },
278
275
  instructions: getServerInstructions(),
279
276
  });
280
- this.setupTelemetry();
277
+ const { telemetryEnabled, telemetryEnv } = this.setupTelemetry();
278
+ this.telemetryEnabled = telemetryEnabled;
279
+ this.telemetryEnv = telemetryEnv;
281
280
  this.setupInitializeHandler();
282
281
  this.setupLoggingProxy();
283
282
  this.tools = new Map();
@@ -296,18 +295,21 @@ export class ActorsMcpServer {
296
295
  */
297
296
  setupTelemetry() {
298
297
  var _a, _b, _c;
298
+ let telemetryEnabled;
299
299
  const explicitEnabled = parseBooleanOrNull((_a = this.options.telemetry) === null || _a === void 0 ? void 0 : _a.enabled);
300
300
  if (explicitEnabled !== null) {
301
- this.telemetryEnabled = explicitEnabled;
301
+ telemetryEnabled = explicitEnabled;
302
302
  }
303
303
  else {
304
304
  const envEnabled = parseBooleanOrNull(process.env.TELEMETRY_ENABLED);
305
- this.telemetryEnabled = envEnabled !== null && envEnabled !== void 0 ? envEnabled : DEFAULT_TELEMETRY_ENABLED;
305
+ telemetryEnabled = envEnabled !== null && envEnabled !== void 0 ? envEnabled : DEFAULT_TELEMETRY_ENABLED;
306
306
  }
307
307
  // 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);
308
+ let telemetryEnv = DEFAULT_TELEMETRY_ENV;
309
+ if (telemetryEnabled) {
310
+ 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
311
  }
312
+ return { telemetryEnabled, telemetryEnv };
311
313
  }
312
314
  /**
313
315
  * Override the SDK's `initialize` request handler to run mode resolution and
@@ -389,23 +391,10 @@ export class ActorsMcpServer {
389
391
  * survive a load that never sees an initialize.
390
392
  */
391
393
  isReportProblemServable() {
392
- return (!!this.telemetryEnabled &&
394
+ return (this.telemetryEnabled &&
393
395
  this.clientKnown &&
394
396
  !isReportProblemBlockedForClient(this.options.initializeRequestData));
395
397
  }
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
398
  composePendingToolsForClient() {
410
399
  if (this.pendingToolsUntilClientKnown.length === 0)
411
400
  return;
@@ -843,7 +832,7 @@ export class ActorsMcpServer {
843
832
  * @throws {McpError} - based on the McpServer class code from the typescript MCP SDK
844
833
  */
845
834
  this.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
846
- var _a, _b, _c, _d, _e, _f;
835
+ var _a, _b, _c, _d, _e;
847
836
  const params = request.params;
848
837
  // eslint-disable-next-line prefer-const
849
838
  let { name, arguments: args, _meta: meta } = params;
@@ -870,7 +859,13 @@ export class ActorsMcpServer {
870
859
  // On a failed result, nudge the agent to report the blocker via report-problem at the
871
860
  // moment it decides what to do next. Gated on the tool actually being served (see
872
861
  // 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);
862
+ const augmented = withReportProblemNudge({
863
+ result: r,
864
+ tools: this.tools,
865
+ failingToolName: resolvedToolName,
866
+ failureCategory: callDiagnostics.failure_category,
867
+ failureHttpStatus: callDiagnostics.failure_http_status,
868
+ });
874
869
  toolResult = augmented;
875
870
  return augmented;
876
871
  };
@@ -892,7 +887,12 @@ export class ActorsMcpServer {
892
887
  };
893
888
  // Initialize telemetry with raw tool name — updated below once the tool is resolved.
894
889
  // This ensures telemetry is available even for early failures (missing token, tool not found).
895
- const { telemetryData, userId } = await this.prepareTelemetryData(name, mcpSessionId, apifyToken);
890
+ const { telemetryData, userId } = await prepareTelemetryData({
891
+ toolName: name,
892
+ mcpSessionId,
893
+ apifyToken,
894
+ apifyMcpServer: this,
895
+ });
896
896
  // actorName/actorId are declared here so they're available in the catch block for telemetry.
897
897
  // Set after tool resolution (inside the try block).
898
898
  let actorName;
@@ -1090,196 +1090,75 @@ export class ActorsMcpServer {
1090
1090
  callDiagnostics = outcome.callDiagnostics;
1091
1091
  return captureResult(outcome.result);
1092
1092
  }
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;
1093
+ // Progress tracker: opt in for the two INTERNAL tools that emit during a sync wait
1094
+ // (call-actor start+waitForFinish, get-actor-run when waitSecs > 0), and unconditionally
1095
+ // for ACTOR tools. ACTOR_MCP forwards notifications directly, not via a tracker.
1096
+ const progressTrackerOptIn = tool.type === TOOL_TYPE.ACTOR ||
1097
+ (tool.type === TOOL_TYPE.INTERNAL &&
1098
+ (tool.name === HELPER_TOOLS.ACTOR_CALL || tool.name === HELPER_TOOLS.ACTOR_RUNS_GET));
1099
+ const progressTracker = progressTrackerOptIn
1100
+ ? createProgressTracker(progressToken, extra.sendNotification)
1101
+ : null;
1102
+ const dispatchResult = await dispatchToolCall({
1103
+ tool,
1104
+ toolArgs: toolArgs,
1105
+ logSafeArgs,
1106
+ apifyToken,
1107
+ apifyClient: apifyClient,
1108
+ apifyMcpServer: this,
1109
+ mcpSessionId,
1110
+ progressToken,
1111
+ progressTracker,
1112
+ shouldForwardNotifications: true,
1113
+ extra,
1114
+ actorName,
1115
+ actorId,
1116
+ taskMode: false,
1117
+ });
1118
+ toolStatus = dispatchResult.toolStatus;
1119
+ callDiagnostics = dispatchResult.callDiagnostics;
1120
+ return captureResult(dispatchResult.result);
1239
1121
  }
1240
1122
  catch (error) {
1241
- const httpStatus = getHttpStatusCode(error);
1123
+ // Re-throw MCP protocol errors (e.g. from failInvalidParams) so the SDK
1124
+ // returns them as JSON-RPC errors. failInvalidParams already set callDiagnostics
1125
+ // with the correct semantic category (e.g. AUTH), so we must not overwrite it.
1126
+ // Re-throwing first is order-equivalent only because no McpError with an HTTP-range
1127
+ // code reaches this catch: an McpError(402) WOULD satisfy the x402 predicate
1128
+ // (getHttpStatusCode falls through to `.code`), but every remote-McpError route is
1129
+ // sealed by an inner catch (ACTOR_MCP branch, call-actor MCP passthrough) and all
1130
+ // in-repo McpErrors use negative ErrorCode.* values.
1131
+ if (error instanceof McpError) {
1132
+ throw error;
1133
+ }
1134
+ const errorResult = buildToolCallErrorResult(error, {
1135
+ toolName: name,
1136
+ actorName,
1137
+ actorId,
1138
+ isAborted: Boolean((_e = extra.signal) === null || _e === void 0 ? void 0 : _e.aborted),
1139
+ });
1140
+ toolStatus = errorResult.toolStatus;
1242
1141
  // 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));
1142
+ // content[0].text (JSON) + isError: true. No log here (unlike the task path). The
1143
+ // concurrent-run limit also surfaces as 402 but is excluded by the predicate and
1144
+ // falls through to the generic run-limit handling below.
1145
+ if (errorResult.kind === TOOL_CALL_ERROR_KIND.PAYMENT) {
1146
+ callDiagnostics = errorResult.callDiagnostics;
1147
+ return captureResult(errorResult.response);
1253
1148
  }
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
- };
1149
+ if (errorResult.kind === TOOL_CALL_ERROR_KIND.APPROVAL) {
1150
+ callDiagnostics = errorResult.callDiagnostics;
1261
1151
  logHttpError(error, 'Permission approval required while calling tool', {
1262
1152
  toolName: name,
1263
1153
  mcpSessionId,
1264
1154
  });
1265
- return captureResult(buildPermissionApprovalResponse(error));
1155
+ return captureResult(errorResult.response);
1266
1156
  }
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;
1272
- }
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
1157
  callDiagnostics = {
1276
1158
  // Spread existing diagnostics first (e.g. validation_keyword from failInvalidParams),
1277
- // then overwrite with freshly computed fields so they take precedence.
1159
+ // then overwrite with the mapper's freshly computed fields so they take precedence.
1278
1160
  ...callDiagnostics,
1279
- failure_category: classifyFailureCategory(error),
1280
- ...(httpStatus !== undefined ? { failure_http_status: httpStatus } : {}),
1281
- failure_detail: failureDetail,
1282
- ...buildActorFields(actorName, actorId),
1161
+ ...errorResult.callDiagnostics,
1283
1162
  };
1284
1163
  logHttpError(error, 'Error occurred while calling tool', {
1285
1164
  toolName: name,
@@ -1298,14 +1177,14 @@ export class ActorsMcpServer {
1298
1177
  // reuse the local ABORTED-aware toolStatus, do NOT re-derive from the error (which
1299
1178
  // would drop ABORTED and leak failureCategory/failureHttpStatus onto the wire).
1300
1179
  return captureResult({
1301
- ...respondOk(getToolCallErrorUserText(name, error)),
1180
+ ...respondOk(errorResult.userText),
1302
1181
  isError: true,
1303
1182
  toolTelemetry: { toolStatus },
1304
1183
  });
1305
1184
  }
1306
1185
  finally {
1307
1186
  if (shouldTrackTelemetry) {
1308
- this.logToolCallAndTelemetry({
1187
+ logToolCallAndTelemetry({
1309
1188
  toolName: resolvedToolName,
1310
1189
  mcpSessionId,
1311
1190
  toolStatus,
@@ -1316,73 +1195,13 @@ export class ActorsMcpServer {
1316
1195
  args,
1317
1196
  result: toolResult,
1318
1197
  taskId: preflightTaskId,
1198
+ apifyMcpServer: this,
1319
1199
  });
1320
1200
  }
1321
1201
  }
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
1202
  });
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
1203
  }
1385
- // TODO: this function quite duplicates the main tool call login the CallToolRequestSchema handler, we should refactor
1204
+ // 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.
1386
1205
  /**
1387
1206
  * Executes a tool asynchronously for a long-running task and updates task status.
1388
1207
  *
@@ -1408,9 +1227,14 @@ export class ActorsMcpServer {
1408
1227
  });
1409
1228
  // Prepare telemetry before try-catch so it's accessible to both paths.
1410
1229
  // This avoids re-fetching user data in the error handler.
1411
- const { telemetryData, userId } = await this.prepareTelemetryData(getToolFullName(tool), mcpSessionId, apifyToken);
1230
+ const { telemetryData, userId } = await prepareTelemetryData({
1231
+ toolName: getToolFullName(tool),
1232
+ mcpSessionId,
1233
+ apifyToken,
1234
+ apifyMcpServer: this,
1235
+ });
1412
1236
  const finishTaskTracking = (status, diagnostics, result) => {
1413
- this.logToolCallAndTelemetry({
1237
+ logToolCallAndTelemetry({
1414
1238
  toolName: tool.name,
1415
1239
  mcpSessionId,
1416
1240
  toolStatus: status,
@@ -1421,8 +1245,16 @@ export class ActorsMcpServer {
1421
1245
  callDiagnostics: diagnostics,
1422
1246
  args: toolArgs,
1423
1247
  result,
1248
+ apifyMcpServer: this,
1424
1249
  });
1425
1250
  };
1251
+ // Terminal step shared by every task outcome: store the result (expiry-tolerant),
1252
+ // emit the status notification, record telemetry.
1253
+ const completeTask = async (storeStatus, taskResult, status, diagnostics) => {
1254
+ await storeTaskResultOrSkipIfExpired(this.taskStore, tool.name, taskId, storeStatus, taskResult, mcpSessionId);
1255
+ await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
1256
+ finishTaskTracking(status, diagnostics, taskResult);
1257
+ };
1426
1258
  // Once a task is cancelled the spec forbids writing a result; every storage path
1427
1259
  // must short-circuit here. `logSuffix` is concatenated after "Task was cancelled"
1428
1260
  // so we keep the existing log format and the existing telemetry status per path.
@@ -1475,92 +1307,51 @@ export class ActorsMcpServer {
1475
1307
  await this.taskStore.updateTaskStatus(taskId, 'working', message, mcpSessionId);
1476
1308
  await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
1477
1309
  };
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
- 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
- });
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
- }
1548
- }
1310
+ // ACTOR_MCP never reads the tracker (matching the sync path, which passes null for it);
1311
+ // INTERNAL/ACTOR get one built from taskId + onStatusMessage, which dispatch consumes
1312
+ // and stops.
1313
+ const progressTracker = tool.type === TOOL_TYPE.ACTOR_MCP
1314
+ ? null
1315
+ : createProgressTracker(progressToken, extra.sendNotification, taskId, onStatusMessage);
1316
+ const dispatchResult = await dispatchToolCall({
1317
+ tool,
1318
+ toolArgs,
1319
+ logSafeArgs,
1320
+ apifyToken,
1321
+ apifyClient,
1322
+ apifyMcpServer: this,
1323
+ mcpSessionId,
1324
+ progressToken,
1325
+ progressTracker,
1326
+ shouldForwardNotifications: false,
1327
+ extra: taskExtra,
1328
+ actorName,
1329
+ actorId,
1330
+ taskMode: true,
1331
+ logContext: { messageSuffix: ' for task', fields: { taskId } },
1332
+ });
1333
+ result = dispatchResult.result;
1334
+ toolStatus = dispatchResult.toolStatus;
1335
+ callDiagnostics = dispatchResult.callDiagnostics;
1549
1336
  // Check if task was cancelled before storing result
1550
1337
  if (await skipIfTaskCancelled(', skipping result storage', toolStatus))
1551
1338
  return;
1552
1339
  // On a failed result, nudge the agent to report the blocker via report-problem (mirrors the
1553
1340
  // 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);
1341
+ result = withReportProblemNudge({
1342
+ result,
1343
+ tools: this.tools,
1344
+ failingToolName: tool.name,
1345
+ failureCategory: callDiagnostics.failure_category,
1346
+ failureHttpStatus: callDiagnostics.failure_http_status,
1347
+ });
1555
1348
  // Store the result in the task store
1556
1349
  log.debug('[executeToolAndUpdateTask] Storing completed result', {
1557
1350
  taskId,
1558
1351
  mcpSessionId,
1559
1352
  });
1560
- await storeTaskResultOrSkipIfExpired(this.taskStore, tool.name, taskId, 'completed', result, mcpSessionId);
1353
+ await completeTask('completed', result, toolStatus, callDiagnostics);
1561
1354
  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
1355
  }
1565
1356
  catch (error) {
1566
1357
  // Reached only when the task expired before the `working` transition (updateTaskStatus
@@ -1579,9 +1370,14 @@ export class ActorsMcpServer {
1579
1370
  });
1580
1371
  return;
1581
1372
  }
1373
+ const errorResult = buildToolCallErrorResult(error, {
1374
+ toolName: tool.name,
1375
+ actorName,
1376
+ actorId,
1377
+ isAborted: Boolean(cancelWatcher.signal.aborted),
1378
+ });
1582
1379
  // Handle 402 Payment Required — return structured x402 result so clients can auto-pay
1583
- const httpStatus = getHttpStatusCode(error);
1584
- if (isX402PaymentRequiredError(error)) {
1380
+ if (errorResult.kind === TOOL_CALL_ERROR_KIND.PAYMENT) {
1585
1381
  logHttpError(error, 'Payment required while calling tool (task mode)', { toolName: tool.name });
1586
1382
  // Per MCP tasks spec: once a task is cancelled it MUST remain cancelled,
1587
1383
  // so guard storeTaskResult against a cancel that raced with this 402.
@@ -1589,17 +1385,10 @@ export class ActorsMcpServer {
1589
1385
  ...buildActorFields(actorName, actorId),
1590
1386
  }))
1591
1387
  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);
1388
+ await completeTask('completed', errorResult.response, errorResult.toolStatus, errorResult.callDiagnostics);
1600
1389
  return;
1601
1390
  }
1602
- if (isPermissionApprovalError(error)) {
1391
+ if (errorResult.kind === TOOL_CALL_ERROR_KIND.APPROVAL) {
1603
1392
  logHttpError(error, 'Permission approval required while calling tool (task mode)', {
1604
1393
  toolName: tool.name,
1605
1394
  });
@@ -1609,24 +1398,11 @@ export class ActorsMcpServer {
1609
1398
  ...buildActorFields(actorName, actorId),
1610
1399
  }))
1611
1400
  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);
1401
+ await completeTask('completed', errorResult.response, errorResult.toolStatus, errorResult.callDiagnostics);
1620
1402
  return;
1621
1403
  }
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
- };
1404
+ toolStatus = errorResult.toolStatus;
1405
+ callDiagnostics = errorResult.callDiagnostics;
1630
1406
  // Log level follows the already-classified toolStatus:
1631
1407
  // SOFT_FAIL (e.g. 402/403 user quota, client-side issues) → softFail
1632
1408
  // FAILED/ABORTED/other → error
@@ -1656,7 +1432,7 @@ export class ActorsMcpServer {
1656
1432
  error,
1657
1433
  });
1658
1434
  }
1659
- const userText = getToolCallErrorUserText(tool.name, error);
1435
+ const { userText } = errorResult;
1660
1436
  // Check if task was cancelled before storing result
1661
1437
  if (await skipIfTaskCancelled(', skipping result storage', toolStatus, callDiagnostics))
1662
1438
  return;
@@ -1664,60 +1440,33 @@ export class ActorsMcpServer {
1664
1440
  taskId,
1665
1441
  mcpSessionId,
1666
1442
  });
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);
1443
+ // Nudge on a genuinely-failed task result (mirrors the completed path and the sync
1444
+ // captureResult). INTERNAL_ERROR and an unknown category get the full nudge; a genuine
1445
+ // INVALID_INPUT gets the softer nudge; payment (402) is suppressed via the HTTP status and
1446
+ // AUTH / PERMISSION_APPROVAL_REQUIRED via NON_NUDGE_FAILURE_CATEGORIES. The 402 branch above
1447
+ // returns before reaching here, so this only sees non-payment failures.
1448
+ const failedResult = withReportProblemNudge({
1449
+ result: {
1450
+ content: [
1451
+ {
1452
+ type: 'text',
1453
+ text: userText,
1454
+ },
1455
+ ],
1456
+ isError: true,
1457
+ internalToolStatus: toolStatus,
1458
+ },
1459
+ tools: this.tools,
1460
+ failingToolName: tool.name,
1461
+ failureCategory: callDiagnostics.failure_category,
1462
+ failureHttpStatus: callDiagnostics.failure_http_status,
1463
+ });
1464
+ await completeTask('failed', failedResult, toolStatus, callDiagnostics);
1684
1465
  }
1685
1466
  finally {
1686
1467
  cancelWatcher.dispose();
1687
1468
  }
1688
1469
  }
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 };
1720
- }
1721
1470
  /**
1722
1471
  * Resolves widgets and determines which ones are ready to be served.
1723
1472
  */