@apify/actors-mcp-server 0.10.5-beta.0 → 0.10.5-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 (107) hide show
  1. package/dist/apify_client.d.ts.map +1 -1
  2. package/dist/apify_client.js +16 -1
  3. package/dist/apify_client.js.map +1 -1
  4. package/dist/const.d.ts +1 -1
  5. package/dist/const.d.ts.map +1 -1
  6. package/dist/const.js +1 -3
  7. package/dist/const.js.map +1 -1
  8. package/dist/dev_server.d.ts.map +1 -1
  9. package/dist/dev_server.js +24 -5
  10. package/dist/dev_server.js.map +1 -1
  11. package/dist/mcp/server.d.ts +3 -2
  12. package/dist/mcp/server.d.ts.map +1 -1
  13. package/dist/mcp/server.js +96 -51
  14. package/dist/mcp/server.js.map +1 -1
  15. package/dist/mcp/utils.d.ts +1 -1
  16. package/dist/mcp/utils.js +1 -1
  17. package/dist/tools/actor_executor.d.ts +10 -0
  18. package/dist/tools/actor_executor.d.ts.map +1 -0
  19. package/dist/tools/actor_executor.js +79 -0
  20. package/dist/tools/actor_executor.js.map +1 -0
  21. package/dist/tools/apps/call_actor.d.ts +0 -1
  22. package/dist/tools/apps/call_actor.d.ts.map +1 -1
  23. package/dist/tools/apps/call_actor.js +8 -54
  24. package/dist/tools/apps/call_actor.js.map +1 -1
  25. package/dist/tools/apps/call_actor_widget.d.ts.map +1 -1
  26. package/dist/tools/apps/call_actor_widget.js +8 -32
  27. package/dist/tools/apps/call_actor_widget.js.map +1 -1
  28. package/dist/tools/apps/get_actor_run_widget.d.ts.map +1 -1
  29. package/dist/tools/apps/get_actor_run_widget.js +15 -9
  30. package/dist/tools/apps/get_actor_run_widget.js.map +1 -1
  31. package/dist/tools/categories.d.ts +6 -0
  32. package/dist/tools/categories.d.ts.map +1 -1
  33. package/dist/tools/categories.js +7 -1
  34. package/dist/tools/categories.js.map +1 -1
  35. package/dist/tools/core/actor_run_response.d.ts +127 -0
  36. package/dist/tools/core/actor_run_response.d.ts.map +1 -0
  37. package/dist/tools/core/actor_run_response.js +551 -0
  38. package/dist/tools/core/actor_run_response.js.map +1 -0
  39. package/dist/tools/core/actor_tools_factory.d.ts +8 -3
  40. package/dist/tools/core/actor_tools_factory.d.ts.map +1 -1
  41. package/dist/tools/core/actor_tools_factory.js +39 -15
  42. package/dist/tools/core/actor_tools_factory.js.map +1 -1
  43. package/dist/tools/core/call_actor_common.d.ts +19 -39
  44. package/dist/tools/core/call_actor_common.d.ts.map +1 -1
  45. package/dist/tools/core/call_actor_common.js +196 -129
  46. package/dist/tools/core/call_actor_common.js.map +1 -1
  47. package/dist/tools/core/fetch_actor_details_common.d.ts.map +1 -1
  48. package/dist/tools/core/fetch_actor_details_common.js +1 -0
  49. package/dist/tools/core/fetch_actor_details_common.js.map +1 -1
  50. package/dist/tools/core/get_actor_run_common.d.ts +10 -49
  51. package/dist/tools/core/get_actor_run_common.d.ts.map +1 -1
  52. package/dist/tools/core/get_actor_run_common.js +51 -86
  53. package/dist/tools/core/get_actor_run_common.js.map +1 -1
  54. package/dist/tools/default/call_actor.d.ts +0 -2
  55. package/dist/tools/default/call_actor.d.ts.map +1 -1
  56. package/dist/tools/default/call_actor.js +5 -85
  57. package/dist/tools/default/call_actor.js.map +1 -1
  58. package/dist/tools/default/get_actor_run.d.ts +1 -2
  59. package/dist/tools/default/get_actor_run.d.ts.map +1 -1
  60. package/dist/tools/default/get_actor_run.js +12 -6
  61. package/dist/tools/default/get_actor_run.js.map +1 -1
  62. package/dist/tools/index.d.ts +1 -2
  63. package/dist/tools/index.d.ts.map +1 -1
  64. package/dist/tools/index.js +1 -2
  65. package/dist/tools/index.js.map +1 -1
  66. package/dist/tools/structured_output_schemas.d.ts +258 -51
  67. package/dist/tools/structured_output_schemas.d.ts.map +1 -1
  68. package/dist/tools/structured_output_schemas.js +128 -68
  69. package/dist/tools/structured_output_schemas.js.map +1 -1
  70. package/dist/tsconfig.tsbuildinfo +1 -1
  71. package/dist/types.d.ts +26 -1
  72. package/dist/types.d.ts.map +1 -1
  73. package/dist/types.js.map +1 -1
  74. package/dist/utils/mcp.d.ts +6 -0
  75. package/dist/utils/mcp.d.ts.map +1 -1
  76. package/dist/utils/mcp.js +30 -0
  77. package/dist/utils/mcp.js.map +1 -1
  78. package/dist/utils/progress.d.ts +1 -0
  79. package/dist/utils/progress.d.ts.map +1 -1
  80. package/dist/utils/progress.js +2 -2
  81. package/dist/utils/progress.js.map +1 -1
  82. package/dist/utils/server-instructions/index.d.ts.map +1 -1
  83. package/dist/utils/server-instructions/index.js +6 -7
  84. package/dist/utils/server-instructions/index.js.map +1 -1
  85. package/dist/utils/tools_loader.d.ts.map +1 -1
  86. package/dist/utils/tools_loader.js +9 -3
  87. package/dist/utils/tools_loader.js.map +1 -1
  88. package/dist/web/dist/actor-detail-widget.js +3 -0
  89. package/dist/web/dist/actor-run-widget.js +219 -216
  90. package/dist/web/dist/search-actors-widget.js +3 -0
  91. package/package.json +7 -7
  92. package/dist/tools/apps/actor_executor.d.ts +0 -8
  93. package/dist/tools/apps/actor_executor.d.ts.map +0 -1
  94. package/dist/tools/apps/actor_executor.js +0 -29
  95. package/dist/tools/apps/actor_executor.js.map +0 -1
  96. package/dist/tools/core/actor_execution.d.ts +0 -39
  97. package/dist/tools/core/actor_execution.d.ts.map +0 -1
  98. package/dist/tools/core/actor_execution.js +0 -135
  99. package/dist/tools/core/actor_execution.js.map +0 -1
  100. package/dist/tools/core/actor_response.d.ts +0 -37
  101. package/dist/tools/core/actor_response.d.ts.map +0 -1
  102. package/dist/tools/core/actor_response.js +0 -94
  103. package/dist/tools/core/actor_response.js.map +0 -1
  104. package/dist/tools/default/actor_executor.d.ts +0 -7
  105. package/dist/tools/default/actor_executor.d.ts.map +0 -1
  106. package/dist/tools/default/actor_executor.js +0 -31
  107. package/dist/tools/default/actor_executor.js.map +0 -1
@@ -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, ServerNotificationSchema, SetLevelRequestSchema, } from '@modelcontextprotocol/sdk/types.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';
12
12
  import dedent from 'dedent';
13
13
  import log from '@apify/log';
14
14
  import { parseBooleanOrNull } from '@apify/utilities';
@@ -19,14 +19,13 @@ 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 { getTelemetryEnv, trackToolCall } from '../telemetry.js';
22
- import { appsActorExecutor } from '../tools/apps/actor_executor.js';
22
+ import { actorExecutor } from '../tools/actor_executor.js';
23
23
  import { buildPermissionApprovalResponse, isPermissionApprovalError } from '../tools/core/call_actor_common.js';
24
- import { defaultActorExecutor } from '../tools/default/actor_executor.js';
25
24
  import { getActorsAsTools } from '../tools/index.js';
26
25
  import { decodeDotPropertyNames, legacyToolNameToNew } from '../tools/utils.js';
27
26
  import { ServerMode } from '../types.js';
28
27
  import { getHttpStatusCode, logHttpError } from '../utils/logging.js';
29
- import { buildMCPResponse, getToolCallErrorUserText } from '../utils/mcp.js';
28
+ import { buildMCPResponse, computeToolResponseSizeBytes, getToolCallErrorUserText } from '../utils/mcp.js';
30
29
  import { buildPaymentRequiredResponse } from '../utils/payment_errors.js';
31
30
  import { createProgressTracker } from '../utils/progress.js';
32
31
  import { parseServerMode, resolveServerMode } from '../utils/server_mode.js';
@@ -39,11 +38,6 @@ import { getPackageVersion } from '../utils/version.js';
39
38
  import { connectMCPClient } from './client.js';
40
39
  import { EXTERNAL_TOOL_CALL_TIMEOUT_MSEC, LOG_LEVEL_MAP } from './const.js';
41
40
  import { createTaskCancellationWatcher, isTaskCancelled, parseInputParamsFromUrl } from './utils.js';
42
- /** Mode → actor executor. Add new modes here. */
43
- const actorExecutorsByMode = {
44
- [ServerMode.DEFAULT]: defaultActorExecutor,
45
- [ServerMode.APPS]: appsActorExecutor,
46
- };
47
41
  /**
48
42
  * Returns true when the initialize request advertises the MCP Apps UI extension
49
43
  * with the widget MIME type. Used to resolve `'auto'` server mode.
@@ -57,6 +51,33 @@ function isUiSupportedByClient(request) {
57
51
  const uiCap = getUiCapability((_a = request === null || request === void 0 ? void 0 : request.params) === null || _a === void 0 ? void 0 : _a.capabilities);
58
52
  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;
59
53
  }
54
+ /** Send notifications/tasks/status for taskId. Routes via session transport (no relatedRequestId).
55
+ * Swallows errors — notifications are advisory. */
56
+ export async function emitTaskStatusNotification(taskId, mcpSessionId, taskStore, server) {
57
+ try {
58
+ const task = await taskStore.getTask(taskId, mcpSessionId);
59
+ if (!task)
60
+ return;
61
+ // Per spec: notifications/tasks/status MUST NOT carry _meta.related-task (task ID is in params).
62
+ // Called without options so the notification routes through the session transport,
63
+ // not the request-scoped stream (which closes once the initial { task } response is flushed).
64
+ await server.notification({
65
+ method: 'notifications/tasks/status',
66
+ params: {
67
+ taskId: task.taskId,
68
+ status: task.status,
69
+ createdAt: task.createdAt,
70
+ lastUpdatedAt: task.lastUpdatedAt,
71
+ ttl: task.ttl,
72
+ ...(task.statusMessage != null && { statusMessage: task.statusMessage }),
73
+ ...(task.pollInterval != null && { pollInterval: task.pollInterval }),
74
+ },
75
+ });
76
+ }
77
+ catch {
78
+ // Silent fail — notifications are advisory
79
+ }
80
+ }
60
81
  /**
61
82
  * Create Apify MCP server
62
83
  */
@@ -121,13 +142,6 @@ export class ActorsMcpServer {
121
142
  writable: true,
122
143
  value: void 0
123
144
  });
124
- /** Mode-specific executor for direct actor tools (`type: 'actor'`). Finalized with `serverMode`. */
125
- Object.defineProperty(this, "actorExecutor", {
126
- enumerable: true,
127
- configurable: true,
128
- writable: true,
129
- value: void 0
130
- });
131
145
  /**
132
146
  * Raw option captured from `options.serverMode` (or the legacy `uiMode`). Re-resolved
133
147
  * inside the initialize handler when set to `'auto'`; explicit `'default'`/`'apps'`
@@ -211,7 +225,6 @@ export class ActorsMcpServer {
211
225
  // client capabilities are known (only for 'auto').
212
226
  this.serverMode = resolveServerMode(this.serverModeOption, false);
213
227
  this.serverModeResolved = this.serverModeOption !== 'auto';
214
- this.actorExecutor = actorExecutorsByMode[this.serverMode];
215
228
  const { setupSigintHandler = true } = options;
216
229
  this.server = new Server({
217
230
  name: SERVER_NAME,
@@ -300,7 +313,6 @@ export class ActorsMcpServer {
300
313
  const resolved = resolveServerMode('auto', this.clientSupportsUi);
301
314
  if (resolved !== this.serverMode) {
302
315
  this.serverMode = resolved;
303
- this.actorExecutor = actorExecutorsByMode[this.serverMode];
304
316
  }
305
317
  this.serverModeResolved = true;
306
318
  }
@@ -684,7 +696,15 @@ export class ActorsMcpServer {
684
696
  if (task.status !== 'completed' && task.status !== 'failed') {
685
697
  throw new McpError(ErrorCode.InvalidParams, `Task "${taskId}" is not completed yet. Current status: ${task.status}`);
686
698
  }
687
- return await this.taskStore.getTaskResult(taskId, mcpSessionId);
699
+ const result = await this.taskStore.getTaskResult(taskId, mcpSessionId);
700
+ // taskId is not in the result body — _meta.related-task lets clients correlate them
701
+ return {
702
+ ...result,
703
+ _meta: {
704
+ ...result._meta,
705
+ [RELATED_TASK_META_KEY]: { taskId },
706
+ },
707
+ };
688
708
  });
689
709
  // Cancel task
690
710
  this.server.setRequestHandler(CancelTaskRequestSchema, async (request) => {
@@ -713,6 +733,7 @@ export class ActorsMcpServer {
713
733
  await this.taskStore.updateTaskStatus(taskId, 'cancelled', 'Cancelled by client', mcpSessionId);
714
734
  const updatedTask = await this.taskStore.getTask(taskId, mcpSessionId);
715
735
  log.debug('[CancelTaskRequestSchema] Task cancelled successfully', { taskId, mcpSessionId });
736
+ await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
716
737
  return updatedTask;
717
738
  });
718
739
  }
@@ -755,6 +776,12 @@ export class ActorsMcpServer {
755
776
  let callDiagnostics = {};
756
777
  let shouldTrackTelemetry = true;
757
778
  let resolvedToolName = name;
779
+ // Captured by `captureResult` so the `finally` block can measure response size for telemetry.
780
+ let toolResult = null;
781
+ const captureResult = (r) => {
782
+ toolResult = r;
783
+ return r;
784
+ };
758
785
  const failInvalidParams = async (message, details, logFields) => {
759
786
  toolStatus = TOOL_STATUS.SOFT_FAIL;
760
787
  callDiagnostics = details;
@@ -869,11 +896,11 @@ export class ActorsMcpServer {
869
896
  if (request.params.task) {
870
897
  const task = await this.taskStore.createTask({
871
898
  ttl: request.params.task.ttl,
872
- }, `call-tool-${name}-${randomUUID()}`, request);
899
+ }, `call-tool-${name}-${randomUUID()}`, request, mcpSessionId);
873
900
  log.debug('Created task for tool execution', { taskId: task.taskId, toolName: tool.name, mcpSessionId });
874
901
  // Execute the tool asynchronously and update task status
875
- setImmediate(async () => {
876
- await this.executeToolAndUpdateTask({
902
+ setImmediate(() => {
903
+ this.executeToolAndUpdateTask({
877
904
  taskId: task.taskId,
878
905
  tool,
879
906
  toolArgs: toolArgs,
@@ -887,7 +914,7 @@ export class ActorsMcpServer {
887
914
  actorName,
888
915
  actorId,
889
916
  userRentedActorIds,
890
- });
917
+ }).catch((error) => log.error('executeToolAndUpdateTask failed unexpectedly', { taskId: task.taskId, error }));
891
918
  });
892
919
  // Return the task immediately; execution continues asynchronously
893
920
  shouldTrackTelemetry = false;
@@ -901,12 +928,15 @@ export class ActorsMcpServer {
901
928
  failure_http_status: 402,
902
929
  ...buildActorFields(actorName, actorId),
903
930
  };
904
- return paymentRequiredResult;
931
+ return captureResult(paymentRequiredResult);
905
932
  }
906
933
  // Handle internal tool
907
934
  if (tool.type === 'internal') {
908
- // Only create a progress tracker for call-actor tool
909
- const progressTracker = tool.name === 'call-actor'
935
+ // Tools that may emit notifications/progress during a sync wait must be opted in here.
936
+ // call-actor: emits during start+waitForFinish. get-actor-run: emits when waitSecs > 0.
937
+ const progressTrackerOptIn = tool.name === HelperTools.ACTOR_CALL
938
+ || tool.name === HelperTools.ACTOR_RUNS_GET;
939
+ const progressTracker = progressTrackerOptIn
910
940
  ? createProgressTracker(progressToken, extra.sendNotification)
911
941
  : null;
912
942
  try {
@@ -926,7 +956,7 @@ export class ActorsMcpServer {
926
956
  const diag = extractToolTelemetry(res, actorName, actorId);
927
957
  toolStatus = diag.toolStatus;
928
958
  callDiagnostics = { ...callDiagnostics, ...diag.callDiagnostics };
929
- return res;
959
+ return captureResult(res);
930
960
  }
931
961
  finally {
932
962
  progressTracker === null || progressTracker === void 0 ? void 0 : progressTracker.stop();
@@ -945,7 +975,7 @@ export class ActorsMcpServer {
945
975
  await this.server.sendLoggingMessage({ level: 'error', data: msg });
946
976
  toolStatus = TOOL_STATUS.SOFT_FAIL;
947
977
  callDiagnostics = { ...callDiagnostics, failure_category: FAILURE_CATEGORY.INTERNAL_ERROR };
948
- return buildMCPResponse({ texts: [msg], isError: true });
978
+ return captureResult(buildMCPResponse({ texts: [msg], isError: true }));
949
979
  }
950
980
  // Only set up notification handlers if progressToken is provided by the client
951
981
  if (progressToken !== undefined && progressToken !== null) {
@@ -985,7 +1015,7 @@ export class ActorsMcpServer {
985
1015
  toolStatus = TOOL_STATUS.SOFT_FAIL;
986
1016
  callDiagnostics = { failure_category: FAILURE_CATEGORY.INTERNAL_ERROR, ...buildActorFields(actorName, actorId) };
987
1017
  }
988
- return { ...res };
1018
+ return captureResult({ ...res });
989
1019
  }
990
1020
  catch (error) {
991
1021
  toolStatus = getToolStatusFromError(error, Boolean((_e = extra.signal) === null || _e === void 0 ? void 0 : _e.aborted));
@@ -1000,10 +1030,10 @@ export class ActorsMcpServer {
1000
1030
  toolName: tool.originToolName,
1001
1031
  failureCategory: callDiagnostics.failure_category,
1002
1032
  });
1003
- return buildMCPResponse({
1033
+ return captureResult(buildMCPResponse({
1004
1034
  texts: [`Failed to call MCP tool '${tool.originToolName}' on Actor '${tool.actorId}': ${error instanceof Error ? error.message : String(error)}. The MCP server may be temporarily unavailable.`],
1005
1035
  isError: true,
1006
- });
1036
+ }));
1007
1037
  }
1008
1038
  finally {
1009
1039
  if (client)
@@ -1015,7 +1045,7 @@ export class ActorsMcpServer {
1015
1045
  const progressTracker = createProgressTracker(progressToken, extra.sendNotification);
1016
1046
  try {
1017
1047
  log.info('Calling Actor', { toolName: tool.name, actorName: tool.actorFullName, mcpSessionId, input: logSafeArgs });
1018
- const executorResult = await this.actorExecutor.executeActorTool({
1048
+ const executorResult = await actorExecutor.executeActorTool({
1019
1049
  actorFullName: tool.actorFullName,
1020
1050
  input: toolArgs,
1021
1051
  apifyClient: apifyClient,
@@ -1023,14 +1053,15 @@ export class ActorsMcpServer {
1023
1053
  progressTracker,
1024
1054
  abortSignal: extra.signal,
1025
1055
  mcpSessionId,
1056
+ datasetItemsSchema: tool.datasetItemsSchema,
1026
1057
  });
1027
1058
  if (!executorResult) {
1028
1059
  toolStatus = TOOL_STATUS.ABORTED;
1029
1060
  // Receivers of cancellation notifications SHOULD NOT send a response for the cancelled request
1030
1061
  // https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/cancellation#behavior-requirements
1031
- return {};
1062
+ return captureResult({});
1032
1063
  }
1033
- return executorResult;
1064
+ return captureResult(executorResult);
1034
1065
  }
1035
1066
  finally {
1036
1067
  if (progressTracker) {
@@ -1052,7 +1083,7 @@ export class ActorsMcpServer {
1052
1083
  failure_http_status: 402,
1053
1084
  ...buildActorFields(actorName, actorId),
1054
1085
  };
1055
- return buildPaymentRequiredResponse(error);
1086
+ return captureResult(buildPaymentRequiredResponse(error));
1056
1087
  }
1057
1088
  if (isPermissionApprovalError(error)) {
1058
1089
  toolStatus = TOOL_STATUS.SOFT_FAIL;
@@ -1062,7 +1093,7 @@ export class ActorsMcpServer {
1062
1093
  ...buildActorFields(actorName, actorId),
1063
1094
  };
1064
1095
  logHttpError(error, 'Permission approval required while calling tool', { toolName: name, mcpSessionId });
1065
- return buildPermissionApprovalResponse(error);
1096
+ return captureResult(buildPermissionApprovalResponse(error));
1066
1097
  }
1067
1098
  // Re-throw MCP protocol errors (e.g. from failInvalidParams) so the SDK
1068
1099
  // returns them as JSON-RPC errors. failInvalidParams already set callDiagnostics
@@ -1093,11 +1124,11 @@ export class ActorsMcpServer {
1093
1124
  validationMissingProperty: callDiagnostics.validation_missing_property,
1094
1125
  validationAdditionalProperty: callDiagnostics.validation_additional_property,
1095
1126
  });
1096
- return buildMCPResponse({
1127
+ return captureResult(buildMCPResponse({
1097
1128
  texts: [getToolCallErrorUserText(name, error)],
1098
1129
  isError: true,
1099
1130
  telemetry: { toolStatus },
1100
- });
1131
+ }));
1101
1132
  }
1102
1133
  finally {
1103
1134
  if (shouldTrackTelemetry) {
@@ -1109,6 +1140,7 @@ export class ActorsMcpServer {
1109
1140
  telemetryData,
1110
1141
  userId,
1111
1142
  callDiagnostics,
1143
+ responseSizeBytes: computeToolResponseSizeBytes(toolResult),
1112
1144
  });
1113
1145
  }
1114
1146
  }
@@ -1137,6 +1169,7 @@ export class ActorsMcpServer {
1137
1169
  mcpSessionId: params.mcpSessionId,
1138
1170
  toolStatus: params.toolStatus,
1139
1171
  durationMs,
1172
+ ...(params.responseSizeBytes !== undefined && { responseSizeBytes: params.responseSizeBytes }),
1140
1173
  ...(params.taskId !== undefined && { taskId: params.taskId }),
1141
1174
  });
1142
1175
  if (params.telemetryData) {
@@ -1144,6 +1177,7 @@ export class ActorsMcpServer {
1144
1177
  ...params.telemetryData,
1145
1178
  tool_status: params.toolStatus,
1146
1179
  tool_exec_time_ms: durationMs,
1180
+ ...(params.responseSizeBytes !== undefined && { tool_response_size_bytes: params.responseSizeBytes }),
1147
1181
  // Always include actor_name/actor_id; failure-specific fields are only present when callDiagnostics has them.
1148
1182
  ...params.callDiagnostics,
1149
1183
  };
@@ -1177,7 +1211,7 @@ export class ActorsMcpServer {
1177
1211
  // Prepare telemetry before try-catch so it's accessible to both paths.
1178
1212
  // This avoids re-fetching user data in the error handler.
1179
1213
  const { telemetryData, userId } = await this.prepareTelemetryData(getToolFullName(tool), mcpSessionId, apifyToken);
1180
- const finishTaskTracking = (status, diagnostics) => {
1214
+ const finishTaskTracking = (status, diagnostics, responseSizeBytes) => {
1181
1215
  this.logToolCallAndTelemetry({
1182
1216
  toolName: tool.name,
1183
1217
  mcpSessionId,
@@ -1187,6 +1221,7 @@ export class ActorsMcpServer {
1187
1221
  telemetryData,
1188
1222
  userId,
1189
1223
  callDiagnostics: diagnostics,
1224
+ responseSizeBytes,
1190
1225
  });
1191
1226
  };
1192
1227
  // Once a task is cancelled the spec forbids writing a result; every storage path
@@ -1229,6 +1264,7 @@ export class ActorsMcpServer {
1229
1264
  return;
1230
1265
  throw err;
1231
1266
  }
1267
+ await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
1232
1268
  // Execute the tool and get the result
1233
1269
  let result = {};
1234
1270
  // Check payment validation (already computed by prepareToolCallContext in the caller)
@@ -1241,13 +1277,12 @@ export class ActorsMcpServer {
1241
1277
  };
1242
1278
  result = paymentRequiredResult;
1243
1279
  }
1244
- // Callback to propagate Actor run statusMessage into the task store.
1245
- // Clients retrieve it via tasks/get and tasks/list polling.
1246
- // TODO: Also send notifications/tasks/status so clients get real-time push updates
1280
+ // Callback to propagate Actor run statusMessage into the task store and emit a push notification.
1247
1281
  // TODO(TC-3): cancel arriving while this is scheduled throws cancelled → working;
1248
1282
  // currently swallowed by progress.ts's tick catch — guard or catch explicitly.
1249
1283
  const onStatusMessage = async (message) => {
1250
1284
  await this.taskStore.updateTaskStatus(taskId, 'working', message, mcpSessionId);
1285
+ await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
1251
1286
  };
1252
1287
  // Handle internal tool execution in task mode
1253
1288
  if (toolStatus === TOOL_STATUS.SUCCEEDED && tool.type === 'internal') {
@@ -1264,6 +1299,7 @@ export class ActorsMcpServer {
1264
1299
  userRentedActorIds,
1265
1300
  progressTracker,
1266
1301
  mcpSessionId,
1302
+ taskMode: true,
1267
1303
  });
1268
1304
  const diag = extractToolTelemetry(res, actorName, actorId);
1269
1305
  toolStatus = diag.toolStatus;
@@ -1281,7 +1317,7 @@ export class ActorsMcpServer {
1281
1317
  const progressTracker = createProgressTracker(progressToken, extra.sendNotification, taskId, onStatusMessage);
1282
1318
  try {
1283
1319
  log.info('Calling Actor for task', { taskId, toolName: tool.name, actorName: tool.actorFullName, mcpSessionId, input: logSafeArgs });
1284
- const executorResult = await this.actorExecutor.executeActorTool({
1320
+ const executorResult = await actorExecutor.executeActorTool({
1285
1321
  actorFullName: tool.actorFullName,
1286
1322
  input: toolArgs,
1287
1323
  apifyClient,
@@ -1289,6 +1325,8 @@ export class ActorsMcpServer {
1289
1325
  progressTracker,
1290
1326
  abortSignal: cancelWatcher.signal,
1291
1327
  mcpSessionId,
1328
+ datasetItemsSchema: tool.datasetItemsSchema,
1329
+ taskMode: true,
1292
1330
  });
1293
1331
  if (!executorResult) {
1294
1332
  toolStatus = TOOL_STATUS.ABORTED;
@@ -1316,7 +1354,8 @@ export class ActorsMcpServer {
1316
1354
  });
1317
1355
  await this.taskStore.storeTaskResult(taskId, 'completed', result, mcpSessionId);
1318
1356
  log.debug('Task completed successfully', { taskId, toolName: tool.name, mcpSessionId });
1319
- finishTaskTracking(toolStatus, callDiagnostics);
1357
+ await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
1358
+ finishTaskTracking(toolStatus, callDiagnostics, computeToolResponseSizeBytes(result));
1320
1359
  }
1321
1360
  catch (error) {
1322
1361
  // Handle 402 Payment Required — return structured x402 result so clients can auto-pay
@@ -1327,12 +1366,14 @@ export class ActorsMcpServer {
1327
1366
  // so guard storeTaskResult against a cancel that raced with this 402.
1328
1367
  if (await skipIfTaskCancelled(', skipping 402 result storage', TOOL_STATUS.ABORTED, { ...buildActorFields(actorName, actorId) }))
1329
1368
  return;
1330
- await this.taskStore.storeTaskResult(taskId, 'completed', buildPaymentRequiredResponse(error), mcpSessionId);
1369
+ const paymentResponse = buildPaymentRequiredResponse(error);
1370
+ await this.taskStore.storeTaskResult(taskId, 'completed', paymentResponse, mcpSessionId);
1371
+ await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
1331
1372
  finishTaskTracking(TOOL_STATUS.SOFT_FAIL, {
1332
1373
  failure_category: FAILURE_CATEGORY.INVALID_INPUT,
1333
1374
  failure_http_status: 402,
1334
1375
  ...buildActorFields(actorName, actorId),
1335
- });
1376
+ }, computeToolResponseSizeBytes(paymentResponse));
1336
1377
  return;
1337
1378
  }
1338
1379
  if (isPermissionApprovalError(error)) {
@@ -1341,12 +1382,14 @@ export class ActorsMcpServer {
1341
1382
  // so guard storeTaskResult against a cancel that raced with this approval error.
1342
1383
  if (await skipIfTaskCancelled(', skipping permission-approval result storage', TOOL_STATUS.ABORTED, { ...buildActorFields(actorName, actorId) }))
1343
1384
  return;
1344
- await this.taskStore.storeTaskResult(taskId, 'completed', buildPermissionApprovalResponse(error), mcpSessionId);
1385
+ const approvalResponse = buildPermissionApprovalResponse(error);
1386
+ await this.taskStore.storeTaskResult(taskId, 'completed', approvalResponse, mcpSessionId);
1387
+ await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
1345
1388
  finishTaskTracking(TOOL_STATUS.SOFT_FAIL, {
1346
1389
  failure_category: FAILURE_CATEGORY.PERMISSION_APPROVAL_REQUIRED,
1347
1390
  failure_http_status: error.statusCode,
1348
1391
  ...buildActorFields(actorName, actorId),
1349
- });
1392
+ }, computeToolResponseSizeBytes(approvalResponse));
1350
1393
  return;
1351
1394
  }
1352
1395
  toolStatus = getToolStatusFromError(error, Boolean(cancelWatcher.signal.aborted));
@@ -1395,15 +1438,17 @@ export class ActorsMcpServer {
1395
1438
  taskId,
1396
1439
  mcpSessionId,
1397
1440
  });
1398
- await this.taskStore.storeTaskResult(taskId, 'failed', {
1441
+ const failedResult = {
1399
1442
  content: [{
1400
1443
  type: 'text',
1401
1444
  text: userText,
1402
1445
  }],
1403
1446
  isError: true,
1404
1447
  internalToolStatus: toolStatus,
1405
- }, mcpSessionId);
1406
- finishTaskTracking(toolStatus, callDiagnostics);
1448
+ };
1449
+ await this.taskStore.storeTaskResult(taskId, 'failed', failedResult, mcpSessionId);
1450
+ await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
1451
+ finishTaskTracking(toolStatus, callDiagnostics, computeToolResponseSizeBytes(failedResult));
1407
1452
  }
1408
1453
  finally {
1409
1454
  cancelWatcher.dispose();