@apify/actors-mcp-server 0.11.6-beta.8 → 0.11.6

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 (214) hide show
  1. package/README.md +4 -0
  2. package/dist/apify_client.js +4 -1
  3. package/dist/apify_client.js.map +1 -1
  4. package/dist/const.d.ts +10 -0
  5. package/dist/const.d.ts.map +1 -1
  6. package/dist/const.js +14 -1
  7. package/dist/const.js.map +1 -1
  8. package/dist/dev_server.js +4 -1
  9. package/dist/dev_server.js.map +1 -1
  10. package/dist/errors.js +4 -1
  11. package/dist/errors.js.map +1 -1
  12. package/dist/index.js +4 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/index_internals.d.ts +1 -0
  15. package/dist/index_internals.d.ts.map +1 -1
  16. package/dist/index_internals.js +4 -1
  17. package/dist/index_internals.js.map +1 -1
  18. package/dist/input.js +4 -1
  19. package/dist/input.js.map +1 -1
  20. package/dist/instrument.js +4 -1
  21. package/dist/instrument.js.map +1 -1
  22. package/dist/mcp/actors.js +4 -1
  23. package/dist/mcp/actors.js.map +1 -1
  24. package/dist/mcp/client.js +4 -1
  25. package/dist/mcp/client.js.map +1 -1
  26. package/dist/mcp/const.js +4 -1
  27. package/dist/mcp/const.js.map +1 -1
  28. package/dist/mcp/proxy.js +4 -1
  29. package/dist/mcp/proxy.js.map +1 -1
  30. package/dist/mcp/server.d.ts +59 -10
  31. package/dist/mcp/server.d.ts.map +1 -1
  32. package/dist/mcp/server.js +225 -106
  33. package/dist/mcp/server.js.map +1 -1
  34. package/dist/mcp/utils.js +4 -1
  35. package/dist/mcp/utils.js.map +1 -1
  36. package/dist/payments/const.js +4 -1
  37. package/dist/payments/const.js.map +1 -1
  38. package/dist/payments/helpers.js +4 -1
  39. package/dist/payments/helpers.js.map +1 -1
  40. package/dist/payments/index.js +4 -1
  41. package/dist/payments/index.js.map +1 -1
  42. package/dist/payments/resolve.js +4 -1
  43. package/dist/payments/resolve.js.map +1 -1
  44. package/dist/payments/skyfire.js +4 -1
  45. package/dist/payments/skyfire.js.map +1 -1
  46. package/dist/payments/types.js +4 -1
  47. package/dist/payments/types.js.map +1 -1
  48. package/dist/payments/x402.js +4 -1
  49. package/dist/payments/x402.js.map +1 -1
  50. package/dist/prompts/index.js +4 -1
  51. package/dist/prompts/index.js.map +1 -1
  52. package/dist/resources/resource_service.js +4 -1
  53. package/dist/resources/resource_service.js.map +1 -1
  54. package/dist/resources/widgets.d.ts +5 -0
  55. package/dist/resources/widgets.d.ts.map +1 -1
  56. package/dist/resources/widgets.js +15 -1
  57. package/dist/resources/widgets.js.map +1 -1
  58. package/dist/server_card.js +4 -1
  59. package/dist/server_card.js.map +1 -1
  60. package/dist/state.js +4 -1
  61. package/dist/state.js.map +1 -1
  62. package/dist/stdio.js +4 -1
  63. package/dist/stdio.js.map +1 -1
  64. package/dist/telemetry.d.ts +14 -1
  65. package/dist/telemetry.d.ts.map +1 -1
  66. package/dist/telemetry.js +45 -1
  67. package/dist/telemetry.js.map +1 -1
  68. package/dist/tools/actor_input_schema.js +4 -1
  69. package/dist/tools/actor_input_schema.js.map +1 -1
  70. package/dist/tools/actor_tool_naming.js +4 -1
  71. package/dist/tools/actor_tool_naming.js.map +1 -1
  72. package/dist/tools/actors/actor_definition.js +4 -1
  73. package/dist/tools/actors/actor_definition.js.map +1 -1
  74. package/dist/tools/actors/actor_executor.d.ts.map +1 -1
  75. package/dist/tools/actors/actor_executor.js +6 -4
  76. package/dist/tools/actors/actor_executor.js.map +1 -1
  77. package/dist/tools/actors/actor_run_response.d.ts +11 -9
  78. package/dist/tools/actors/actor_run_response.d.ts.map +1 -1
  79. package/dist/tools/actors/actor_run_response.js +37 -25
  80. package/dist/tools/actors/actor_run_response.js.map +1 -1
  81. package/dist/tools/actors/actor_tools_factory.js +4 -1
  82. package/dist/tools/actors/actor_tools_factory.js.map +1 -1
  83. package/dist/tools/actors/add_actor.js +4 -1
  84. package/dist/tools/actors/add_actor.js.map +1 -1
  85. package/dist/tools/actors/call_actor.js +6 -3
  86. package/dist/tools/actors/call_actor.js.map +1 -1
  87. package/dist/tools/actors/fetch_actor_details.js +4 -1
  88. package/dist/tools/actors/fetch_actor_details.js.map +1 -1
  89. package/dist/tools/actors/search_actors.js +4 -1
  90. package/dist/tools/actors/search_actors.js.map +1 -1
  91. package/dist/tools/dev/report_problem.d.ts +40 -0
  92. package/dist/tools/dev/report_problem.d.ts.map +1 -0
  93. package/dist/tools/dev/report_problem.js +115 -0
  94. package/dist/tools/dev/report_problem.js.map +1 -0
  95. package/dist/tools/docs/fetch_apify_docs.js +4 -1
  96. package/dist/tools/docs/fetch_apify_docs.js.map +1 -1
  97. package/dist/tools/docs/search_apify_docs.js +4 -1
  98. package/dist/tools/docs/search_apify_docs.js.map +1 -1
  99. package/dist/tools/index.js +4 -1
  100. package/dist/tools/index.js.map +1 -1
  101. package/dist/tools/registry.d.ts +1 -1
  102. package/dist/tools/registry.d.ts.map +1 -1
  103. package/dist/tools/registry.js +6 -2
  104. package/dist/tools/registry.js.map +1 -1
  105. package/dist/tools/runs/abort_actor_run.js +4 -1
  106. package/dist/tools/runs/abort_actor_run.js.map +1 -1
  107. package/dist/tools/runs/get_actor_run.d.ts +7 -4
  108. package/dist/tools/runs/get_actor_run.d.ts.map +1 -1
  109. package/dist/tools/runs/get_actor_run.js +25 -20
  110. package/dist/tools/runs/get_actor_run.js.map +1 -1
  111. package/dist/tools/runs/get_actor_run_list.js +4 -1
  112. package/dist/tools/runs/get_actor_run_list.js.map +1 -1
  113. package/dist/tools/runs/get_actor_run_log.js +4 -1
  114. package/dist/tools/runs/get_actor_run_log.js.map +1 -1
  115. package/dist/tools/storage/get_dataset.js +4 -1
  116. package/dist/tools/storage/get_dataset.js.map +1 -1
  117. package/dist/tools/storage/get_dataset_items.js +4 -1
  118. package/dist/tools/storage/get_dataset_items.js.map +1 -1
  119. package/dist/tools/storage/get_dataset_list.js +4 -1
  120. package/dist/tools/storage/get_dataset_list.js.map +1 -1
  121. package/dist/tools/storage/get_dataset_schema.js +4 -1
  122. package/dist/tools/storage/get_dataset_schema.js.map +1 -1
  123. package/dist/tools/storage/get_key_value_store.js +4 -1
  124. package/dist/tools/storage/get_key_value_store.js.map +1 -1
  125. package/dist/tools/storage/get_key_value_store_keys.js +4 -1
  126. package/dist/tools/storage/get_key_value_store_keys.js.map +1 -1
  127. package/dist/tools/storage/get_key_value_store_list.js +4 -1
  128. package/dist/tools/storage/get_key_value_store_list.js.map +1 -1
  129. package/dist/tools/storage/get_key_value_store_record.js +4 -1
  130. package/dist/tools/storage/get_key_value_store_record.js.map +1 -1
  131. package/dist/tools/storage/storage_helpers.js +4 -1
  132. package/dist/tools/storage/storage_helpers.js.map +1 -1
  133. package/dist/tools/structured_output_schemas.js +4 -1
  134. package/dist/tools/structured_output_schemas.js.map +1 -1
  135. package/dist/tools/widgets/call_actor_widget.js +6 -3
  136. package/dist/tools/widgets/call_actor_widget.js.map +1 -1
  137. package/dist/tools/widgets/fetch_actor_details_widget.js +4 -1
  138. package/dist/tools/widgets/fetch_actor_details_widget.js.map +1 -1
  139. package/dist/tools/widgets/get_actor_run_widget.js +6 -3
  140. package/dist/tools/widgets/get_actor_run_widget.js.map +1 -1
  141. package/dist/tools/widgets/search_actors_widget.js +4 -1
  142. package/dist/tools/widgets/search_actors_widget.js.map +1 -1
  143. package/dist/tsconfig.tsbuildinfo +1 -1
  144. package/dist/types.d.ts +11 -0
  145. package/dist/types.d.ts.map +1 -1
  146. package/dist/types.js +4 -1
  147. package/dist/types.js.map +1 -1
  148. package/dist/utils/actor.js +4 -1
  149. package/dist/utils/actor.js.map +1 -1
  150. package/dist/utils/actor_card.js +4 -1
  151. package/dist/utils/actor_card.js.map +1 -1
  152. package/dist/utils/actor_details.js +4 -1
  153. package/dist/utils/actor_details.js.map +1 -1
  154. package/dist/utils/actor_search.js +4 -1
  155. package/dist/utils/actor_search.js.map +1 -1
  156. package/dist/utils/ajv.js +4 -1
  157. package/dist/utils/ajv.js.map +1 -1
  158. package/dist/utils/apify_docs.js +4 -1
  159. package/dist/utils/apify_docs.js.map +1 -1
  160. package/dist/utils/apify_errors.js +4 -1
  161. package/dist/utils/apify_errors.js.map +1 -1
  162. package/dist/utils/apify_properties.js +4 -1
  163. package/dist/utils/apify_properties.js.map +1 -1
  164. package/dist/utils/auth.js +4 -1
  165. package/dist/utils/auth.js.map +1 -1
  166. package/dist/utils/console_link.js +4 -1
  167. package/dist/utils/console_link.js.map +1 -1
  168. package/dist/utils/encode_text.js +4 -1
  169. package/dist/utils/encode_text.js.map +1 -1
  170. package/dist/utils/generic.js +4 -1
  171. package/dist/utils/generic.js.map +1 -1
  172. package/dist/utils/logging.js +4 -1
  173. package/dist/utils/logging.js.map +1 -1
  174. package/dist/utils/mcp.js +4 -1
  175. package/dist/utils/mcp.js.map +1 -1
  176. package/dist/utils/mcp_clients.d.ts +10 -0
  177. package/dist/utils/mcp_clients.d.ts.map +1 -0
  178. package/dist/utils/mcp_clients.js +17 -0
  179. package/dist/utils/mcp_clients.js.map +1 -0
  180. package/dist/utils/payment_errors.js +4 -1
  181. package/dist/utils/payment_errors.js.map +1 -1
  182. package/dist/utils/pricing_info.js +4 -1
  183. package/dist/utils/pricing_info.js.map +1 -1
  184. package/dist/utils/progress.js +4 -1
  185. package/dist/utils/progress.js.map +1 -1
  186. package/dist/utils/schema_generation.js +4 -1
  187. package/dist/utils/schema_generation.js.map +1 -1
  188. package/dist/utils/server-instructions/index.d.ts +5 -2
  189. package/dist/utils/server-instructions/index.d.ts.map +1 -1
  190. package/dist/utils/server-instructions/index.js +14 -4
  191. package/dist/utils/server-instructions/index.js.map +1 -1
  192. package/dist/utils/server_mode.js +4 -1
  193. package/dist/utils/server_mode.js.map +1 -1
  194. package/dist/utils/tool_categories_helpers.js +4 -1
  195. package/dist/utils/tool_categories_helpers.js.map +1 -1
  196. package/dist/utils/tool_status.d.ts +10 -0
  197. package/dist/utils/tool_status.d.ts.map +1 -1
  198. package/dist/utils/tool_status.js +14 -1
  199. package/dist/utils/tool_status.js.map +1 -1
  200. package/dist/utils/tools.js +4 -1
  201. package/dist/utils/tools.js.map +1 -1
  202. package/dist/utils/tools_loader.d.ts.map +1 -1
  203. package/dist/utils/tools_loader.js +10 -1
  204. package/dist/utils/tools_loader.js.map +1 -1
  205. package/dist/utils/ttl_lru.js +4 -1
  206. package/dist/utils/ttl_lru.js.map +1 -1
  207. package/dist/utils/userid_cache.js +4 -1
  208. package/dist/utils/userid_cache.js.map +1 -1
  209. package/dist/utils/version.js +4 -1
  210. package/dist/utils/version.js.map +1 -1
  211. package/dist/web/dist/actor-detail-widget.js +2 -0
  212. package/dist/web/dist/actor-run-widget.js +2 -0
  213. package/dist/web/dist/search-actors-widget.js +2 -0
  214. package/package.json +1 -1
@@ -1,6 +1,8 @@
1
1
  /**
2
2
  * Model Context Protocol (MCP) server for Apify Actors
3
3
  */
4
+
5
+ !function(){try{var e="undefined"!=typeof window?window:"undefined"!=typeof global?global:"undefined"!=typeof globalThis?globalThis:"undefined"!=typeof self?self:{},n=(new e.Error).stack;n&&(e._sentryDebugIds=e._sentryDebugIds||{},e._sentryDebugIds[n]="5bffa047-752e-51d3-a0f8-131d529ca2f9")}catch(e){}}();
4
6
  import { randomUUID } from 'node:crypto';
5
7
  // The ext-apps package exposes `./server` via conditional exports only (no `./server/index.js`
6
8
  // wildcard), so we can't satisfy the `import/extensions` rule on this subpath.
@@ -19,21 +21,23 @@ import { prompts } from '../prompts/index.js';
19
21
  import { createResourceService } from '../resources/resource_service.js';
20
22
  import { resolveAvailableWidgets } from '../resources/widgets.js';
21
23
  import { getServerInfo } from '../server_card.js';
22
- import { getTelemetryEnv, trackToolCall } from '../telemetry.js';
24
+ import { buildReportedProblemProperties, getTelemetryEnv, trackReportedProblem, trackToolCall } from '../telemetry.js';
23
25
  import { decodeDotPropertyNames } from '../tools/actor_input_schema.js';
24
26
  import { legacyToolNameToNew } from '../tools/actor_tool_naming.js';
25
27
  import { actorExecutor } from '../tools/actors/actor_executor.js';
26
28
  import { buildPermissionApprovalResponse, checkPaymentProviderStandbyConflict } from '../tools/actors/call_actor.js';
29
+ import { appendReportProblemNudge } from '../tools/dev/report_problem.js';
27
30
  import { getActorsAsTools } from '../tools/index.js';
28
31
  import { SERVER_MODE, TOOL_TYPE } from '../types.js';
29
32
  import { isPermissionApprovalError, remoteMcpFailureDetail } from '../utils/apify_errors.js';
30
33
  import { getHttpStatusCode, isMcpClientFaultMessage, logHttpError, sanitizeMezmoMessage } from '../utils/logging.js';
31
34
  import { computeToolResponseBytes, getToolCallErrorUserText, respondErrorNoTelemetry, respondOk, } from '../utils/mcp.js';
35
+ import { isReportProblemBlockedForClient } from '../utils/mcp_clients.js';
32
36
  import { buildPaymentRequiredResponse, isX402PaymentRequiredError } from '../utils/payment_errors.js';
33
37
  import { createProgressTracker } from '../utils/progress.js';
34
38
  import { getServerInstructions } from '../utils/server-instructions/index.js';
35
39
  import { parseServerMode, resolveServerMode } from '../utils/server_mode.js';
36
- import { classifyFailureCategory, deriveResourceIds, extractAjvErrorDetails, extractToolTelemetry, getToolStatusFromError, } from '../utils/tool_status.js';
40
+ import { applyToolTelemetry, classifyFailureCategory, deriveResourceIds, extractAjvErrorDetails, getToolStatusFromError, } from '../utils/tool_status.js';
37
41
  import { buildActorFields, extractActorId, extractActorName, getToolFullName, getToolPublicFieldOnly, } from '../utils/tools.js';
38
42
  import { getActors, getToolsForServerMode, toolNamesToInput } from '../utils/tools_loader.js';
39
43
  import { getUserInfoCached } from '../utils/userid_cache.js';
@@ -81,6 +85,24 @@ export async function emitTaskStatusNotification(taskId, mcpSessionId, taskStore
81
85
  // Silent fail — notifications are advisory
82
86
  }
83
87
  }
88
+ /**
89
+ * Shared diagnostics for a pre-flight failure (standby-provider conflict or missing/invalid payment
90
+ * signature) — a call outcome already known before any Actor runs. Standby rejection wins over the
91
+ * generic payment-required failure so the agent gets the precise reason instead of a generic 402.
92
+ * Pure: callers own their own post-handling (return the result directly, or store + notify + synthesize
93
+ * a terminal task) — call this only once `standbyRejection ?? paymentRequiredResult` is already truthy.
94
+ */
95
+ function buildPreflightFailureOutcome(standbyRejection, paymentRequiredResult, actorName, actorId) {
96
+ return {
97
+ toolStatus: TOOL_STATUS.SOFT_FAIL,
98
+ callDiagnostics: {
99
+ failure_category: FAILURE_CATEGORY.INVALID_INPUT,
100
+ ...(standbyRejection ? {} : { failure_http_status: 402 }),
101
+ ...buildActorFields(actorName, actorId),
102
+ },
103
+ result: (standbyRejection !== null && standbyRejection !== void 0 ? standbyRejection : paymentRequiredResult),
104
+ };
105
+ }
84
106
  /**
85
107
  * Create Apify MCP server
86
108
  */
@@ -156,7 +178,10 @@ export class ActorsMcpServer {
156
178
  writable: true,
157
179
  value: void 0
158
180
  });
159
- /** True once mode is final. False for `'auto'` until the initialize handler resolves client capabilities. */
181
+ /** True once the server mode is final: at construction for explicit `default`/`apps`, or after
182
+ * the initialize handler resolves `'auto'`. Composing before this in `'auto'` mode would use
183
+ * the preliminary DEFAULT mode and produce the wrong (non-widget) tool variants, so composition
184
+ * waits for it. Distinct from {@link clientKnown}, which only withholds client-gated tools. */
160
185
  Object.defineProperty(this, "serverModeResolved", {
161
186
  enumerable: true,
162
187
  configurable: true,
@@ -164,12 +189,13 @@ export class ActorsMcpServer {
164
189
  value: void 0
165
190
  });
166
191
  /**
167
- * Tool requests queued before mode is final. Actor tools are upserted immediately
168
- * (mode-agnostic); we also capture the exact actor-tool slice fetched for each
169
- * request so the flush composes every entry against *its own* actor list rather
170
- * than the accumulated union across unrelated requests.
192
+ * Tool sources queued until composition is possible. Enqueued when the mode is not yet resolved
193
+ * (`'auto'` before initialize), and re-composed by the initialize flush — which is also when the
194
+ * client becomes known, so any client-gated tools withheld by an eager compose are added then.
195
+ * We capture the exact actor-tool slice fetched for each request so the flush composes every
196
+ * entry against *its own* actor list rather than the accumulated union across unrelated requests.
171
197
  */
172
- Object.defineProperty(this, "pendingToolsAfterModeResolved", {
198
+ Object.defineProperty(this, "pendingToolsUntilClientKnown", {
173
199
  enumerable: true,
174
200
  configurable: true,
175
201
  writable: true,
@@ -314,6 +340,8 @@ export class ActorsMcpServer {
314
340
  }
315
341
  this.serverModeResolved = true;
316
342
  }
343
+ // Setting this makes `clientKnown` true, so the queued compose below (and any later
344
+ // load) resolves the tool set for this client and applies the per-client blocklist.
317
345
  this.options.initializeRequestData = request;
318
346
  log.info('Resolved server mode for client capabilities', {
319
347
  serverMode: this.serverMode,
@@ -321,23 +349,74 @@ export class ActorsMcpServer {
321
349
  clientSupportsUi: this.clientSupportsUi,
322
350
  capabilities: (_a = request === null || request === void 0 ? void 0 : request.params) === null || _a === void 0 ? void 0 : _a.capabilities,
323
351
  });
324
- this.updateToolsAfterServerModeResolved();
352
+ this.composePendingToolsForClient();
325
353
  await this.resolveWidgets();
326
354
  const result = await sdkInitHandler(request);
327
- result.instructions = getServerInstructions(this.serverMode);
355
+ // Tools are final here (composePendingToolsForClient ran above, applying the per-client
356
+ // blocklist), so tool presence is the ground truth for whether to advertise
357
+ // report-problem in the instructions.
358
+ result.instructions = getServerInstructions(this.serverMode, this.tools.has(HELPER_TOOLS.PROBLEM_REPORT));
328
359
  return result;
329
360
  });
330
361
  }
331
- updateToolsAfterServerModeResolved() {
332
- if (this.pendingToolsAfterModeResolved.length === 0)
362
+ /** True once the connecting client is known (set in the initialize handler, or hydrated by a
363
+ * recovery path). Only client-gated tools wait for this so the per-client blocklist can be
364
+ * applied; client-agnostic tools compose regardless. */
365
+ get clientKnown() {
366
+ return this.options.initializeRequestData != null;
367
+ }
368
+ /**
369
+ * Compose the tool list for the current connection: resolve mode-specific tools, then drop
370
+ * report-problem unless it is currently servable (see {@link isReportProblemServable}).
371
+ * report-problem is a default-injected tool (via tools_loader) rather than a category member,
372
+ * gated here by servability. Every other tool composes eagerly, so a recovery/rehydration load
373
+ * without an initialize still restores them. report-problem is withheld until the client is known
374
+ * and re-added by the initialize flush. Used by every input-driven load path and the flush.
375
+ * (loadActorsAsTools upserts actor tools directly; actor tools are never gated, so they need no
376
+ * filtering.)
377
+ */
378
+ composeToolsForClient(input, actorTools) {
379
+ const tools = getToolsForServerMode(input, actorTools, this.serverMode);
380
+ if (this.isReportProblemServable())
381
+ return tools;
382
+ return tools.filter((tool) => tool.name !== HELPER_TOOLS.PROBLEM_REPORT);
383
+ }
384
+ /**
385
+ * Whether report-problem may be served on this connection right now:
386
+ * - Its only function is forwarding submissions via telemetry, so it is never servable when
387
+ * telemetry is disabled (it would just fake an acknowledgement into the void).
388
+ * - It cannot be judged until the connecting client is known, so it is withheld until then;
389
+ * the initialize flush re-composes and adds it if the client allows.
390
+ * Every other tool is unconditionally servable, so recovery loads compose them eagerly and they
391
+ * survive a load that never sees an initialize.
392
+ */
393
+ isReportProblemServable() {
394
+ return (!!this.telemetryEnabled &&
395
+ this.clientKnown &&
396
+ !isReportProblemBlockedForClient(this.options.initializeRequestData));
397
+ }
398
+ /**
399
+ * Append the report-problem nudge to a failed tool result. Thin wrapper over
400
+ * {@link appendReportProblemNudge} that fills in whether report-problem is served on this
401
+ * connection; suppression by category/402 is handled inside the helper.
402
+ */
403
+ withReportProblemNudge(result, failingToolName, failureCategory, failureHttpStatus) {
404
+ return appendReportProblemNudge(result, {
405
+ failingToolName,
406
+ available: this.tools.has(HELPER_TOOLS.PROBLEM_REPORT),
407
+ failureCategory,
408
+ failureHttpStatus,
409
+ });
410
+ }
411
+ composePendingToolsForClient() {
412
+ if (this.pendingToolsUntilClientKnown.length === 0)
333
413
  return;
334
- const tools = this.pendingToolsAfterModeResolved.flatMap(({ input, actorTools }) => getToolsForServerMode(input, actorTools, this.serverMode));
335
- this.pendingToolsAfterModeResolved = [];
336
- // Notify after the flush so shared-state handlers (e.g. Redis recovery) see
337
- // the final tool set, including mode-specific helpers added here. Pre-init,
338
- // `loadToolsByName` may have fired `upsertTools(actorTools, true)` with actor
339
- // tools only (helpers still queued), and `loadToolsFromUrl` / `loadToolsFromInput`
340
- // don't notify at all — this call reconciles both paths to the complete set.
414
+ const tools = this.pendingToolsUntilClientKnown.flatMap(({ input, actorTools }) => this.composeToolsForClient(input, actorTools));
415
+ this.pendingToolsUntilClientKnown = [];
416
+ // Notify after the flush so shared-state handlers (e.g. Redis recovery) see the final tool
417
+ // set. Load paths already upserted the client-agnostic tools pre-init; re-upserting is
418
+ // idempotent, and this pass adds the client-gated tools (e.g. report-problem) now that the
419
+ // client is known, reconciling shared state to the complete set.
341
420
  if (tools.length > 0)
342
421
  this.upsertTools(tools, true);
343
422
  }
@@ -408,6 +487,31 @@ export class ActorsMcpServer {
408
487
  listAllToolNames() {
409
488
  return [...this.listInternalToolNames(), ...this.listActorToolNames(), ...this.listActorMcpServerToolIds()];
410
489
  }
490
+ /**
491
+ * Buffer-or-compose gate shared by the actor-tools loaders. If the server mode isn't resolved
492
+ * yet ('auto' before initialize), queue the whole source for `composePendingToolsForClient` and
493
+ * (if non-empty) upsert the mode-agnostic actor tools immediately with the given `shouldNotify`.
494
+ * Once the mode is resolved, compose the client-specific set via `composeToolsForClient` (which
495
+ * withholds report-problem until the client is known) and upsert it; if the client still isn't
496
+ * known, queue the source so the initialize flush re-composes and adds the client-gated tools.
497
+ *
498
+ * Callers pass different `shouldNotify` values: `loadToolsByName` forwards `actorTools.length > 0`
499
+ * (notify only when actor tools were fetched), while `loadToolsFromUrl` and `loadToolsFromInput`
500
+ * pass `false` and defer to the post-initialize reconcile. See `composePendingToolsForClient`.
501
+ */
502
+ registerFetchedActorTools(input, actorTools, shouldNotify) {
503
+ if (!this.serverModeResolved) {
504
+ this.pendingToolsUntilClientKnown.push({ input, actorTools });
505
+ if (actorTools.length > 0)
506
+ this.upsertTools(actorTools, shouldNotify);
507
+ return;
508
+ }
509
+ const tools = this.composeToolsForClient(input, actorTools);
510
+ if (tools.length > 0)
511
+ this.upsertTools(tools, shouldNotify);
512
+ if (!this.clientKnown)
513
+ this.pendingToolsUntilClientKnown.push({ input, actorTools });
514
+ }
411
515
  /**
412
516
  * Loads missing toolNames from a provided list of tool names.
413
517
  * Skips toolNames that are already loaded and loads only the missing ones.
@@ -424,16 +528,7 @@ export class ActorsMcpServer {
424
528
  actorStore: this.actorStore,
425
529
  paymentProvider: this.options.paymentProvider,
426
530
  });
427
- if (!this.serverModeResolved) {
428
- this.pendingToolsAfterModeResolved.push({ input: restoreInput, actorTools });
429
- if (actorTools.length > 0)
430
- this.upsertTools(actorTools, true);
431
- return;
432
- }
433
- const toolsToLoad = getToolsForServerMode(restoreInput, actorTools, this.serverMode);
434
- if (toolsToLoad.length > 0) {
435
- this.upsertTools(toolsToLoad, actorTools.length > 0);
436
- }
531
+ this.registerFetchedActorTools(restoreInput, actorTools, actorTools.length > 0);
437
532
  }
438
533
  /**
439
534
  * Load Actors as tools, upsert successes into the server, and return both the tool
@@ -458,24 +553,14 @@ export class ActorsMcpServer {
458
553
  actorStore: this.actorStore,
459
554
  paymentProvider: this.options.paymentProvider,
460
555
  });
461
- if (!this.serverModeResolved) {
462
- this.pendingToolsAfterModeResolved.push({ input, actorTools });
463
- if (actorTools.length > 0) {
464
- log.debug('Loading actor tools from query parameters before mode resolution');
465
- this.upsertTools(actorTools, false);
466
- }
467
- return;
468
- }
469
- const tools = getToolsForServerMode(input, actorTools, this.serverMode);
470
- if (tools.length > 0) {
471
- log.debug('Loading tools from query parameters');
472
- this.upsertTools(tools, false);
473
- }
556
+ log.debug('Loading tools from query parameters');
557
+ this.registerFetchedActorTools(input, actorTools, false);
474
558
  }
475
559
  /**
476
- * Two-phase: getActors (async, mode-agnostic Apify fetch) then getToolsForServerMode
477
- * (sync, mode-dependent compose). When mode is unresolved, queue actorTools and let
478
- * the initialize handler compose them later.
560
+ * Two-phase: getActors (async, client-agnostic Apify fetch) then composeToolsForClient
561
+ * (sync compose + servability filter). If the mode isn't resolved yet ('auto' before initialize)
562
+ * the whole source is queued for the flush. Otherwise tools compose immediately; client-gated
563
+ * tools are withheld until the client is known, and the source is queued so the flush adds them.
479
564
  *
480
565
  * Don't move the getActors await into the initialize handler — clients time out
481
566
  * waiting for InitializeResult. The queue buffers already-fetched data, not network
@@ -486,15 +571,7 @@ export class ActorsMcpServer {
486
571
  actorStore: this.actorStore,
487
572
  paymentProvider: this.options.paymentProvider,
488
573
  });
489
- if (!this.serverModeResolved) {
490
- this.pendingToolsAfterModeResolved.push({ input, actorTools });
491
- if (actorTools.length > 0)
492
- this.upsertTools(actorTools);
493
- return;
494
- }
495
- const tools = getToolsForServerMode(input, actorTools, this.serverMode);
496
- if (tools.length > 0)
497
- this.upsertTools(tools);
574
+ this.registerFetchedActorTools(input, actorTools, false);
498
575
  }
499
576
  /** Delete tools from the server and notify the handler.
500
577
  */
@@ -518,6 +595,9 @@ export class ActorsMcpServer {
518
595
  * @returns Array of added/updated tool wrappers
519
596
  */
520
597
  upsertTools(tools, shouldNotifyToolsChangedHandler = false) {
598
+ // Client gating (e.g. hiding report-problem from Anthropic surfaces) is applied earlier, in
599
+ // composeToolsForClient — the single compose choke point where the client is known. Do not
600
+ // filter here: this is a low-level commit point reached before the client is known too.
521
601
  for (const tool of tools) {
522
602
  const stored = this.options.paymentProvider ? this.options.paymentProvider.decorateToolSchema(tool) : tool;
523
603
  this.tools.set(stored.name, stored);
@@ -769,11 +849,19 @@ export class ActorsMcpServer {
769
849
  let callDiagnostics = {};
770
850
  let shouldTrackTelemetry = true;
771
851
  let resolvedToolName = name;
852
+ // Set only on the pre-flight task path — the one task-mode flow whose telemetry rides
853
+ // this handler's `finally` — so its `Tool call completed` log line keeps the taskId the
854
+ // async path logs via finishTaskTracking.
855
+ let preflightTaskId;
772
856
  // Captured by `captureResult` so the `finally` block can measure response size for telemetry.
773
857
  let toolResult = null;
774
858
  const captureResult = (r) => {
775
- toolResult = r;
776
- return r;
859
+ // On a failed result, nudge the agent to report the blocker via report-problem at the
860
+ // moment it decides what to do next. Gated on the tool actually being served (see
861
+ // isReportProblemServable), so clients where it is blocklisted or telemetry is off never see it.
862
+ const augmented = this.withReportProblemNudge(r, resolvedToolName, callDiagnostics.failure_category, callDiagnostics.failure_http_status);
863
+ toolResult = augmented;
864
+ return augmented;
777
865
  };
778
866
  const failInvalidParams = async (message, details, logFields) => {
779
867
  toolStatus = TOOL_STATUS.SOFT_FAIL;
@@ -915,6 +1003,50 @@ export class ActorsMcpServer {
915
1003
  toolName: tool.name,
916
1004
  mcpSessionId,
917
1005
  });
1006
+ // Pre-flight failure is already known — the outcome needs no work. Resolve the
1007
+ // task synchronously: store the failure as the terminal `completed` result and emit
1008
+ // exactly one `completed` status notification (no `updateTaskStatus('working')`, so no
1009
+ // `working` notification). Standby rejection wins over the generic payment-required
1010
+ // short-circuit, matching the sync path's precedence. Telemetry rides the handler's
1011
+ // outer `finally` (shouldTrackTelemetry stays true), firing once with the sync-path
1012
+ // properties plus the taskId on the log line.
1013
+ const preflightResult = standbyRejection !== null && standbyRejection !== void 0 ? standbyRejection : paymentRequiredResult;
1014
+ if (preflightResult) {
1015
+ const outcome = buildPreflightFailureOutcome(standbyRejection, paymentRequiredResult, actorName, actorId);
1016
+ toolStatus = outcome.toolStatus;
1017
+ callDiagnostics = outcome.callDiagnostics;
1018
+ preflightTaskId = task.taskId;
1019
+ try {
1020
+ await storeTaskResultOrSkipIfExpired(this.taskStore, tool.name, task.taskId, 'completed', outcome.result, mcpSessionId);
1021
+ }
1022
+ catch (error) {
1023
+ // A store failure (not expiry) would otherwise fall through to the generic
1024
+ // catch and return a task-less tool result the client rejects as a
1025
+ // CreateTaskResult parse error; surface it as a protocol error instead.
1026
+ // The pre-flight outcome left toolStatus=SOFT_FAIL, but a genuine store
1027
+ // outage is a hard failure — correct it before throwing so the handler
1028
+ // `finally` logs FAILED/INTERNAL_ERROR (the outer McpError re-throw preserves
1029
+ // these), not the stale pre-flight SOFT_FAIL.
1030
+ toolStatus = TOOL_STATUS.FAILED;
1031
+ callDiagnostics = {
1032
+ failure_category: FAILURE_CATEGORY.INTERNAL_ERROR,
1033
+ ...buildActorFields(actorName, actorId),
1034
+ };
1035
+ throw new McpError(ErrorCode.InternalError, `Failed to store the pre-flight result for task "${task.taskId}": ${error instanceof Error ? error.message : String(error)}`);
1036
+ }
1037
+ // Defer so the client sees the CreateTaskResult (and learns the taskId) before
1038
+ // the terminal status notification — the async path's post-response ordering.
1039
+ // emitTaskStatusNotification never throws and no-ops if the task expired.
1040
+ setImmediate(() => {
1041
+ void emitTaskStatusNotification(task.taskId, mcpSessionId, this.taskStore, this.server);
1042
+ });
1043
+ captureResult(outcome.result);
1044
+ // createTask returned status `working`; synthesize the terminal status instead of
1045
+ // re-fetching — if the task expired before the result store (the one case
1046
+ // storeTaskResultOrSkipIfExpired tolerates), a re-fetch would come back empty and a
1047
+ // `working` fallback would contradict the tasks/get 404 the client sees next.
1048
+ return { task: { ...task, status: 'completed' } };
1049
+ }
918
1050
  // Execute the tool asynchronously and update task status
919
1051
  setImmediate(() => {
920
1052
  this.executeToolAndUpdateTask({
@@ -922,8 +1054,6 @@ export class ActorsMcpServer {
922
1054
  tool,
923
1055
  toolArgs: toolArgs,
924
1056
  logSafeArgs,
925
- standbyRejection,
926
- paymentRequiredResult,
927
1057
  apifyClient: apifyClient,
928
1058
  apifyToken,
929
1059
  progressToken,
@@ -940,25 +1070,14 @@ export class ActorsMcpServer {
940
1070
  shouldTrackTelemetry = false;
941
1071
  return { task };
942
1072
  }
943
- // Sync path: standby rejection wins over the generic payment-required short-circuit
944
- // so the agent gets the precise reason instead of a 402.
945
- if (standbyRejection) {
946
- toolStatus = TOOL_STATUS.SOFT_FAIL;
947
- callDiagnostics = {
948
- failure_category: FAILURE_CATEGORY.INVALID_INPUT,
949
- ...buildActorFields(actorName, actorId),
950
- };
951
- return captureResult(standbyRejection);
952
- }
953
- // Check payment validation (already computed by prepareToolCallContext)
954
- if (paymentRequiredResult) {
955
- toolStatus = TOOL_STATUS.SOFT_FAIL;
956
- callDiagnostics = {
957
- failure_category: FAILURE_CATEGORY.INVALID_INPUT,
958
- failure_http_status: 402,
959
- ...buildActorFields(actorName, actorId),
960
- };
961
- return captureResult(paymentRequiredResult);
1073
+ // Sync path: short-circuit on either pre-flight failure. buildPreflightFailureOutcome
1074
+ // encodes the precedence — standby rejection wins over the generic payment-required
1075
+ // 402, so the agent gets the precise reason.
1076
+ if (standbyRejection || paymentRequiredResult) {
1077
+ const outcome = buildPreflightFailureOutcome(standbyRejection, paymentRequiredResult, actorName, actorId);
1078
+ toolStatus = outcome.toolStatus;
1079
+ callDiagnostics = outcome.callDiagnostics;
1080
+ return captureResult(outcome.result);
962
1081
  }
963
1082
  // Handle internal tool
964
1083
  if (tool.type === TOOL_TYPE.INTERNAL) {
@@ -981,9 +1100,7 @@ export class ActorsMcpServer {
981
1100
  mcpSessionId,
982
1101
  }));
983
1102
  // Extract diagnostics and strip internal fields from res before returning to client.
984
- const diag = extractToolTelemetry(res, actorName, actorId);
985
- toolStatus = diag.toolStatus;
986
- callDiagnostics = { ...callDiagnostics, ...diag.callDiagnostics };
1103
+ ({ toolStatus, callDiagnostics } = applyToolTelemetry(res, actorName, actorId, callDiagnostics));
987
1104
  return captureResult(res);
988
1105
  }
989
1106
  finally {
@@ -1094,6 +1211,10 @@ export class ActorsMcpServer {
1094
1211
  // https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/cancellation#behavior-requirements
1095
1212
  return captureResult({});
1096
1213
  }
1214
+ // Mirror the INTERNAL branch: read the telemetry the executor embedded on error
1215
+ // results (e.g. respondUserError → SOFT_FAIL/INVALID_INPUT), strip it from the wire,
1216
+ // and set failure_category so the report-problem nudge picks the softer variant.
1217
+ ({ toolStatus, callDiagnostics } = applyToolTelemetry(executorResult, actorName, actorId, callDiagnostics));
1097
1218
  return captureResult(executorResult);
1098
1219
  }
1099
1220
  finally {
@@ -1183,6 +1304,7 @@ export class ActorsMcpServer {
1183
1304
  callDiagnostics,
1184
1305
  args,
1185
1306
  result: toolResult,
1307
+ taskId: preflightTaskId,
1186
1308
  });
1187
1309
  }
1188
1310
  }
@@ -1240,6 +1362,13 @@ export class ActorsMcpServer {
1240
1362
  ...deriveResourceIds(params.args, params.result),
1241
1363
  };
1242
1364
  trackToolCall(params.userId, this.telemetryEnv, finalizedTelemetryData);
1365
+ // A successful report-problem call also emits a dedicated feedback event carrying the
1366
+ // submission. A downstream Segment destination fans it out to Slack/GitHub.
1367
+ if (params.toolName === HELPER_TOOLS.PROBLEM_REPORT &&
1368
+ params.toolStatus === TOOL_STATUS.SUCCEEDED &&
1369
+ params.args) {
1370
+ trackReportedProblem(params.userId, this.telemetryEnv, buildReportedProblemProperties(finalizedTelemetryData, params.args));
1371
+ }
1243
1372
  }
1244
1373
  }
1245
1374
  // TODO: this function quite duplicates the main tool call login the CallToolRequestSchema handler, we should refactor
@@ -1256,7 +1385,7 @@ export class ActorsMcpServer {
1256
1385
  * @param params.mcpSessionId - MCP session ID for telemetry
1257
1386
  */
1258
1387
  async executeToolAndUpdateTask(params) {
1259
- const { taskId, tool, toolArgs, logSafeArgs, standbyRejection, paymentRequiredResult, apifyClient, apifyToken, progressToken, extra, mcpSessionId, actorName, actorId, } = params;
1388
+ const { taskId, tool, toolArgs, logSafeArgs, apifyClient, apifyToken, progressToken, extra, mcpSessionId, actorName, actorId, } = params;
1260
1389
  let toolStatus = TOOL_STATUS.SUCCEEDED;
1261
1390
  // Always populate actor fields so they're tracked on both success and failure paths.
1262
1391
  let callDiagnostics = { ...buildActorFields(actorName, actorId) };
@@ -1328,25 +1457,6 @@ export class ActorsMcpServer {
1328
1457
  await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
1329
1458
  // Execute the tool and get the result
1330
1459
  let result = {};
1331
- // Mirror the sync-path ordering: standby rejection wins over the generic 402 so the
1332
- // stored task result carries the precise reason the agent should act on.
1333
- if (standbyRejection) {
1334
- toolStatus = TOOL_STATUS.SOFT_FAIL;
1335
- callDiagnostics = {
1336
- failure_category: FAILURE_CATEGORY.INVALID_INPUT,
1337
- ...buildActorFields(actorName, actorId),
1338
- };
1339
- result = standbyRejection;
1340
- }
1341
- else if (paymentRequiredResult) {
1342
- toolStatus = TOOL_STATUS.SOFT_FAIL;
1343
- callDiagnostics = {
1344
- failure_category: FAILURE_CATEGORY.INVALID_INPUT,
1345
- failure_http_status: 402,
1346
- ...buildActorFields(actorName, actorId),
1347
- };
1348
- result = paymentRequiredResult;
1349
- }
1350
1460
  // Callback to propagate Actor run statusMessage into the task store and emit a push notification.
1351
1461
  // TODO(TC-3): cancel arriving while this is scheduled throws cancelled → working;
1352
1462
  // currently swallowed by progress.ts's tick catch — guard or catch explicitly.
@@ -1375,9 +1485,7 @@ export class ActorsMcpServer {
1375
1485
  mcpSessionId,
1376
1486
  taskMode: true,
1377
1487
  }));
1378
- const diag = extractToolTelemetry(res, actorName, actorId);
1379
- toolStatus = diag.toolStatus;
1380
- callDiagnostics = { ...callDiagnostics, ...diag.callDiagnostics };
1488
+ ({ toolStatus, callDiagnostics } = applyToolTelemetry(res, actorName, actorId, callDiagnostics));
1381
1489
  result = res;
1382
1490
  }
1383
1491
  finally {
@@ -1415,6 +1523,9 @@ export class ActorsMcpServer {
1415
1523
  result = {};
1416
1524
  }
1417
1525
  else {
1526
+ // Mirror the INTERNAL task branch: extract embedded telemetry so failure_category
1527
+ // drives the success-path nudge below and toolTelemetry is stripped from the wire.
1528
+ ({ toolStatus, callDiagnostics } = applyToolTelemetry(executorResult, actorName, actorId, callDiagnostics));
1418
1529
  result = executorResult;
1419
1530
  }
1420
1531
  }
@@ -1427,6 +1538,9 @@ export class ActorsMcpServer {
1427
1538
  // Check if task was cancelled before storing result
1428
1539
  if (await skipIfTaskCancelled(', skipping result storage', toolStatus))
1429
1540
  return;
1541
+ // On a failed result, nudge the agent to report the blocker via report-problem (mirrors the
1542
+ // synchronous CallTool path, which task-mode calls like call-actor bypass).
1543
+ result = this.withReportProblemNudge(result, tool.name, callDiagnostics.failure_category, callDiagnostics.failure_http_status);
1430
1544
  // Store the result in the task store
1431
1545
  log.debug('[executeToolAndUpdateTask] Storing completed result', {
1432
1546
  taskId,
@@ -1539,7 +1653,11 @@ export class ActorsMcpServer {
1539
1653
  taskId,
1540
1654
  mcpSessionId,
1541
1655
  });
1542
- const failedResult = {
1656
+ // Nudge on a genuinely-failed task result. INTERNAL_ERROR and an unknown category get the
1657
+ // full nudge; a genuine INVALID_INPUT gets the softer nudge; payment (402) is suppressed via
1658
+ // the HTTP status and AUTH / PERMISSION_APPROVAL_REQUIRED via NON_NUDGE_FAILURE_CATEGORIES.
1659
+ // The 402 branch above returns before reaching here, so this only sees non-payment failures.
1660
+ const failedResult = this.withReportProblemNudge({
1543
1661
  content: [
1544
1662
  {
1545
1663
  type: 'text',
@@ -1548,7 +1666,7 @@ export class ActorsMcpServer {
1548
1666
  ],
1549
1667
  isError: true,
1550
1668
  internalToolStatus: toolStatus,
1551
- };
1669
+ }, tool.name, callDiagnostics.failure_category, callDiagnostics.failure_http_status);
1552
1670
  await storeTaskResultOrSkipIfExpired(this.taskStore, tool.name, taskId, 'failed', failedResult, mcpSessionId);
1553
1671
  await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
1554
1672
  finishTaskTracking(toolStatus, callDiagnostics, failedResult);
@@ -1655,4 +1773,5 @@ export class ActorsMcpServer {
1655
1773
  await this.server.close();
1656
1774
  }
1657
1775
  }
1658
- //# sourceMappingURL=server.js.map
1776
+ //# sourceMappingURL=server.js.map
1777
+ //# debugId=5bffa047-752e-51d3-a0f8-131d529ca2f9