@apify/actors-mcp-server 0.11.6-beta.1 → 0.11.6-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 (129) hide show
  1. package/README.md +4 -0
  2. package/dist/const.d.ts +10 -0
  3. package/dist/const.d.ts.map +1 -1
  4. package/dist/const.js +10 -0
  5. package/dist/const.js.map +1 -1
  6. package/dist/dev_server.d.ts.map +1 -1
  7. package/dist/dev_server.js +4 -5
  8. package/dist/dev_server.js.map +1 -1
  9. package/dist/index_internals.d.ts +1 -0
  10. package/dist/index_internals.d.ts.map +1 -1
  11. package/dist/mcp/actors.d.ts +0 -4
  12. package/dist/mcp/actors.d.ts.map +1 -1
  13. package/dist/mcp/actors.js +1 -7
  14. package/dist/mcp/actors.js.map +1 -1
  15. package/dist/mcp/server.d.ts +63 -10
  16. package/dist/mcp/server.d.ts.map +1 -1
  17. package/dist/mcp/server.js +160 -82
  18. package/dist/mcp/server.js.map +1 -1
  19. package/dist/resources/resource_service.d.ts.map +1 -1
  20. package/dist/resources/resource_service.js +2 -2
  21. package/dist/resources/resource_service.js.map +1 -1
  22. package/dist/resources/widgets.d.ts.map +1 -1
  23. package/dist/resources/widgets.js +5 -5
  24. package/dist/resources/widgets.js.map +1 -1
  25. package/dist/stdio.js +2 -7
  26. package/dist/stdio.js.map +1 -1
  27. package/dist/telemetry.d.ts +14 -1
  28. package/dist/telemetry.d.ts.map +1 -1
  29. package/dist/telemetry.js +41 -0
  30. package/dist/telemetry.js.map +1 -1
  31. package/dist/tools/actors/actor_run_response.d.ts +1 -1
  32. package/dist/tools/actors/actor_run_response.d.ts.map +1 -1
  33. package/dist/tools/actors/call_actor.d.ts +4 -4
  34. package/dist/tools/actors/call_actor.d.ts.map +1 -1
  35. package/dist/tools/actors/call_actor.js +6 -6
  36. package/dist/tools/actors/call_actor.js.map +1 -1
  37. package/dist/tools/actors/search_actors.d.ts +11 -0
  38. package/dist/tools/actors/search_actors.d.ts.map +1 -1
  39. package/dist/tools/actors/search_actors.js +29 -21
  40. package/dist/tools/actors/search_actors.js.map +1 -1
  41. package/dist/tools/dev/report_problem.d.ts +40 -0
  42. package/dist/tools/dev/report_problem.d.ts.map +1 -0
  43. package/dist/tools/dev/report_problem.js +112 -0
  44. package/dist/tools/dev/report_problem.js.map +1 -0
  45. package/dist/tools/registry.d.ts +1 -1
  46. package/dist/tools/registry.d.ts.map +1 -1
  47. package/dist/tools/registry.js +2 -1
  48. package/dist/tools/registry.js.map +1 -1
  49. package/dist/tools/runs/get_actor_run.d.ts.map +1 -1
  50. package/dist/tools/runs/get_actor_run.js +2 -2
  51. package/dist/tools/runs/get_actor_run.js.map +1 -1
  52. package/dist/tools/runs/get_actor_run_list.d.ts.map +1 -1
  53. package/dist/tools/runs/get_actor_run_list.js +5 -2
  54. package/dist/tools/runs/get_actor_run_list.js.map +1 -1
  55. package/dist/tools/runs/get_actor_run_log.d.ts.map +1 -1
  56. package/dist/tools/runs/get_actor_run_log.js +2 -1
  57. package/dist/tools/runs/get_actor_run_log.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 +5 -17
  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 +0 -1
  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 +3 -14
  66. package/dist/tools/storage/get_dataset_schema.js.map +1 -1
  67. package/dist/tools/storage/get_key_value_store_keys.d.ts.map +1 -1
  68. package/dist/tools/storage/get_key_value_store_keys.js +4 -14
  69. package/dist/tools/storage/get_key_value_store_keys.js.map +1 -1
  70. package/dist/tools/storage/get_key_value_store_list.d.ts.map +1 -1
  71. package/dist/tools/storage/get_key_value_store_list.js +0 -1
  72. package/dist/tools/storage/get_key_value_store_list.js.map +1 -1
  73. package/dist/tools/storage/get_key_value_store_record.js +9 -9
  74. package/dist/tools/storage/get_key_value_store_record.js.map +1 -1
  75. package/dist/tools/storage/storage_helpers.d.ts +14 -17
  76. package/dist/tools/storage/storage_helpers.d.ts.map +1 -1
  77. package/dist/tools/storage/storage_helpers.js +27 -11
  78. package/dist/tools/storage/storage_helpers.js.map +1 -1
  79. package/dist/tools/widgets/get_actor_run_widget.d.ts.map +1 -1
  80. package/dist/tools/widgets/get_actor_run_widget.js +2 -1
  81. package/dist/tools/widgets/get_actor_run_widget.js.map +1 -1
  82. package/dist/tools/widgets/search_actors_widget.d.ts.map +1 -1
  83. package/dist/tools/widgets/search_actors_widget.js +2 -6
  84. package/dist/tools/widgets/search_actors_widget.js.map +1 -1
  85. package/dist/tsconfig.tsbuildinfo +1 -1
  86. package/dist/types.d.ts +13 -1
  87. package/dist/types.d.ts.map +1 -1
  88. package/dist/types.js.map +1 -1
  89. package/dist/utils/actor.d.ts +1 -15
  90. package/dist/utils/actor.d.ts.map +1 -1
  91. package/dist/utils/actor.js +0 -62
  92. package/dist/utils/actor.js.map +1 -1
  93. package/dist/utils/encode_text.d.ts +0 -30
  94. package/dist/utils/encode_text.d.ts.map +1 -1
  95. package/dist/utils/encode_text.js +1 -79
  96. package/dist/utils/encode_text.js.map +1 -1
  97. package/dist/utils/generic.d.ts +0 -17
  98. package/dist/utils/generic.d.ts.map +1 -1
  99. package/dist/utils/generic.js +0 -49
  100. package/dist/utils/generic.js.map +1 -1
  101. package/dist/utils/mcp.d.ts +40 -24
  102. package/dist/utils/mcp.d.ts.map +1 -1
  103. package/dist/utils/mcp.js +31 -0
  104. package/dist/utils/mcp.js.map +1 -1
  105. package/dist/utils/mcp_clients.d.ts +6 -2
  106. package/dist/utils/mcp_clients.d.ts.map +1 -1
  107. package/dist/utils/mcp_clients.js +9 -16
  108. package/dist/utils/mcp_clients.js.map +1 -1
  109. package/dist/utils/payment_errors.d.ts +1 -10
  110. package/dist/utils/payment_errors.d.ts.map +1 -1
  111. package/dist/utils/server-instructions/index.d.ts +5 -2
  112. package/dist/utils/server-instructions/index.d.ts.map +1 -1
  113. package/dist/utils/server-instructions/index.js +10 -3
  114. package/dist/utils/server-instructions/index.js.map +1 -1
  115. package/dist/utils/tool_status.d.ts +10 -0
  116. package/dist/utils/tool_status.d.ts.map +1 -1
  117. package/dist/utils/tool_status.js +10 -0
  118. package/dist/utils/tool_status.js.map +1 -1
  119. package/dist/utils/tools.d.ts +0 -10
  120. package/dist/utils/tools.d.ts.map +1 -1
  121. package/dist/utils/tools.js +0 -35
  122. package/dist/utils/tools.js.map +1 -1
  123. package/dist/utils/tools_loader.d.ts.map +1 -1
  124. package/dist/utils/tools_loader.js +6 -0
  125. package/dist/utils/tools_loader.js.map +1 -1
  126. package/dist/web/dist/actor-detail-widget.js +171 -462
  127. package/dist/web/dist/actor-run-widget.js +189 -480
  128. package/dist/web/dist/search-actors-widget.js +168 -459
  129. package/package.json +1 -3
@@ -19,21 +19,23 @@ 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 { getTelemetryEnv, trackToolCall } from '../telemetry.js';
22
+ import { buildReportedProblemProperties, getTelemetryEnv, trackReportedProblem, trackToolCall } from '../telemetry.js';
23
23
  import { decodeDotPropertyNames } from '../tools/actor_input_schema.js';
24
24
  import { legacyToolNameToNew } from '../tools/actor_tool_naming.js';
25
25
  import { actorExecutor } from '../tools/actors/actor_executor.js';
26
26
  import { buildPermissionApprovalResponse, checkPaymentProviderStandbyConflict } from '../tools/actors/call_actor.js';
27
+ import { appendReportProblemNudge } from '../tools/dev/report_problem.js';
27
28
  import { getActorsAsTools } from '../tools/index.js';
28
29
  import { SERVER_MODE, TOOL_TYPE } from '../types.js';
29
30
  import { isPermissionApprovalError, remoteMcpFailureDetail } from '../utils/apify_errors.js';
30
31
  import { getHttpStatusCode, isMcpClientFaultMessage, logHttpError, sanitizeMezmoMessage } from '../utils/logging.js';
31
32
  import { computeToolResponseBytes, getToolCallErrorUserText, respondErrorNoTelemetry, respondOk, } from '../utils/mcp.js';
33
+ import { isReportProblemBlockedForClient } from '../utils/mcp_clients.js';
32
34
  import { buildPaymentRequiredResponse, isX402PaymentRequiredError } from '../utils/payment_errors.js';
33
35
  import { createProgressTracker } from '../utils/progress.js';
34
36
  import { getServerInstructions } from '../utils/server-instructions/index.js';
35
37
  import { parseServerMode, resolveServerMode } from '../utils/server_mode.js';
36
- import { classifyFailureCategory, deriveResourceIds, extractAjvErrorDetails, extractToolTelemetry, getToolStatusFromError, } from '../utils/tool_status.js';
38
+ import { applyToolTelemetry, classifyFailureCategory, deriveResourceIds, extractAjvErrorDetails, getToolStatusFromError, } from '../utils/tool_status.js';
37
39
  import { buildActorFields, extractActorId, extractActorName, getToolFullName, getToolPublicFieldOnly, } from '../utils/tools.js';
38
40
  import { getActors, getToolsForServerMode, toolNamesToInput } from '../utils/tools_loader.js';
39
41
  import { getUserInfoCached } from '../utils/userid_cache.js';
@@ -156,7 +158,10 @@ export class ActorsMcpServer {
156
158
  writable: true,
157
159
  value: void 0
158
160
  });
159
- /** True once mode is final. False for `'auto'` until the initialize handler resolves client capabilities. */
161
+ /** True once the server mode is final: at construction for explicit `default`/`apps`, or after
162
+ * the initialize handler resolves `'auto'`. Composing before this in `'auto'` mode would use
163
+ * the preliminary DEFAULT mode and produce the wrong (non-widget) tool variants, so composition
164
+ * waits for it. Distinct from {@link clientKnown}, which only withholds client-gated tools. */
160
165
  Object.defineProperty(this, "serverModeResolved", {
161
166
  enumerable: true,
162
167
  configurable: true,
@@ -164,12 +169,13 @@ export class ActorsMcpServer {
164
169
  value: void 0
165
170
  });
166
171
  /**
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.
172
+ * Tool sources queued until composition is possible. Enqueued when the mode is not yet resolved
173
+ * (`'auto'` before initialize), and re-composed by the initialize flush — which is also when the
174
+ * client becomes known, so any client-gated tools withheld by an eager compose are added then.
175
+ * We capture the exact actor-tool slice fetched for each request so the flush composes every
176
+ * entry against *its own* actor list rather than the accumulated union across unrelated requests.
171
177
  */
172
- Object.defineProperty(this, "pendingToolsAfterModeResolved", {
178
+ Object.defineProperty(this, "pendingToolsUntilClientKnown", {
173
179
  enumerable: true,
174
180
  configurable: true,
175
181
  writable: true,
@@ -314,6 +320,8 @@ export class ActorsMcpServer {
314
320
  }
315
321
  this.serverModeResolved = true;
316
322
  }
323
+ // Setting this makes `clientKnown` true, so the queued compose below (and any later
324
+ // load) resolves the tool set for this client and applies the per-client blocklist.
317
325
  this.options.initializeRequestData = request;
318
326
  log.info('Resolved server mode for client capabilities', {
319
327
  serverMode: this.serverMode,
@@ -321,23 +329,74 @@ export class ActorsMcpServer {
321
329
  clientSupportsUi: this.clientSupportsUi,
322
330
  capabilities: (_a = request === null || request === void 0 ? void 0 : request.params) === null || _a === void 0 ? void 0 : _a.capabilities,
323
331
  });
324
- this.updateToolsAfterServerModeResolved();
332
+ this.composePendingToolsForClient();
325
333
  await this.resolveWidgets();
326
334
  const result = await sdkInitHandler(request);
327
- result.instructions = getServerInstructions(this.serverMode);
335
+ // Tools are final here (composePendingToolsForClient ran above, applying the per-client
336
+ // blocklist), so tool presence is the ground truth for whether to advertise
337
+ // report-problem in the instructions.
338
+ result.instructions = getServerInstructions(this.serverMode, this.tools.has(HELPER_TOOLS.PROBLEM_REPORT));
328
339
  return result;
329
340
  });
330
341
  }
331
- updateToolsAfterServerModeResolved() {
332
- if (this.pendingToolsAfterModeResolved.length === 0)
342
+ /** True once the connecting client is known (set in the initialize handler, or hydrated by a
343
+ * recovery path). Only client-gated tools wait for this so the per-client blocklist can be
344
+ * applied; client-agnostic tools compose regardless. */
345
+ get clientKnown() {
346
+ return this.options.initializeRequestData != null;
347
+ }
348
+ /**
349
+ * Compose the tool list for the current connection: resolve mode-specific tools, then drop
350
+ * report-problem unless it is currently servable (see {@link isReportProblemServable}).
351
+ * report-problem is a default-injected tool (via tools_loader) rather than a category member,
352
+ * gated here by servability. Every other tool composes eagerly, so a recovery/rehydration load
353
+ * without an initialize still restores them. report-problem is withheld until the client is known
354
+ * and re-added by the initialize flush. Used by every input-driven load path and the flush.
355
+ * (loadActorsAsTools upserts actor tools directly; actor tools are never gated, so they need no
356
+ * filtering.)
357
+ */
358
+ composeToolsForClient(input, actorTools) {
359
+ const tools = getToolsForServerMode(input, actorTools, this.serverMode);
360
+ if (this.isReportProblemServable())
361
+ return tools;
362
+ return tools.filter((tool) => tool.name !== HELPER_TOOLS.PROBLEM_REPORT);
363
+ }
364
+ /**
365
+ * Whether report-problem may be served on this connection right now:
366
+ * - Its only function is forwarding submissions via telemetry, so it is never servable when
367
+ * telemetry is disabled (it would just fake an acknowledgement into the void).
368
+ * - It cannot be judged until the connecting client is known, so it is withheld until then;
369
+ * the initialize flush re-composes and adds it if the client allows.
370
+ * Every other tool is unconditionally servable, so recovery loads compose them eagerly and they
371
+ * survive a load that never sees an initialize.
372
+ */
373
+ isReportProblemServable() {
374
+ return (!!this.telemetryEnabled &&
375
+ this.clientKnown &&
376
+ !isReportProblemBlockedForClient(this.options.initializeRequestData));
377
+ }
378
+ /**
379
+ * Append the report-problem nudge to a failed tool result. Thin wrapper over
380
+ * {@link appendReportProblemNudge} that fills in whether report-problem is served on this
381
+ * connection; suppression by category/402 is handled inside the helper.
382
+ */
383
+ withReportProblemNudge(result, failingToolName, failureCategory, failureHttpStatus) {
384
+ return appendReportProblemNudge(result, {
385
+ failingToolName,
386
+ available: this.tools.has(HELPER_TOOLS.PROBLEM_REPORT),
387
+ failureCategory,
388
+ failureHttpStatus,
389
+ });
390
+ }
391
+ composePendingToolsForClient() {
392
+ if (this.pendingToolsUntilClientKnown.length === 0)
333
393
  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.
394
+ const tools = this.pendingToolsUntilClientKnown.flatMap(({ input, actorTools }) => this.composeToolsForClient(input, actorTools));
395
+ this.pendingToolsUntilClientKnown = [];
396
+ // Notify after the flush so shared-state handlers (e.g. Redis recovery) see the final tool
397
+ // set. Load paths already upserted the client-agnostic tools pre-init; re-upserting is
398
+ // idempotent, and this pass adds the client-gated tools (e.g. report-problem) now that the
399
+ // client is known, reconciling shared state to the complete set.
341
400
  if (tools.length > 0)
342
401
  this.upsertTools(tools, true);
343
402
  }
@@ -408,6 +467,31 @@ export class ActorsMcpServer {
408
467
  listAllToolNames() {
409
468
  return [...this.listInternalToolNames(), ...this.listActorToolNames(), ...this.listActorMcpServerToolIds()];
410
469
  }
470
+ /**
471
+ * Buffer-or-compose gate shared by the actor-tools loaders. If the server mode isn't resolved
472
+ * yet ('auto' before initialize), queue the whole source for `composePendingToolsForClient` and
473
+ * (if non-empty) upsert the mode-agnostic actor tools immediately with the given `shouldNotify`.
474
+ * Once the mode is resolved, compose the client-specific set via `composeToolsForClient` (which
475
+ * withholds report-problem until the client is known) and upsert it; if the client still isn't
476
+ * known, queue the source so the initialize flush re-composes and adds the client-gated tools.
477
+ *
478
+ * Callers pass different `shouldNotify` values: `loadToolsByName` forwards `actorTools.length > 0`
479
+ * (notify only when actor tools were fetched), while `loadToolsFromUrl` and `loadToolsFromInput`
480
+ * pass `false` and defer to the post-initialize reconcile. See `composePendingToolsForClient`.
481
+ */
482
+ registerFetchedActorTools(input, actorTools, shouldNotify) {
483
+ if (!this.serverModeResolved) {
484
+ this.pendingToolsUntilClientKnown.push({ input, actorTools });
485
+ if (actorTools.length > 0)
486
+ this.upsertTools(actorTools, shouldNotify);
487
+ return;
488
+ }
489
+ const tools = this.composeToolsForClient(input, actorTools);
490
+ if (tools.length > 0)
491
+ this.upsertTools(tools, shouldNotify);
492
+ if (!this.clientKnown)
493
+ this.pendingToolsUntilClientKnown.push({ input, actorTools });
494
+ }
411
495
  /**
412
496
  * Loads missing toolNames from a provided list of tool names.
413
497
  * Skips toolNames that are already loaded and loads only the missing ones.
@@ -424,16 +508,7 @@ export class ActorsMcpServer {
424
508
  actorStore: this.actorStore,
425
509
  paymentProvider: this.options.paymentProvider,
426
510
  });
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
- }
511
+ this.registerFetchedActorTools(restoreInput, actorTools, actorTools.length > 0);
437
512
  }
438
513
  /**
439
514
  * Load Actors as tools, upsert successes into the server, and return both the tool
@@ -458,24 +533,14 @@ export class ActorsMcpServer {
458
533
  actorStore: this.actorStore,
459
534
  paymentProvider: this.options.paymentProvider,
460
535
  });
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
- }
536
+ log.debug('Loading tools from query parameters');
537
+ this.registerFetchedActorTools(input, actorTools, false);
474
538
  }
475
539
  /**
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.
540
+ * Two-phase: getActors (async, client-agnostic Apify fetch) then composeToolsForClient
541
+ * (sync compose + servability filter). If the mode isn't resolved yet ('auto' before initialize)
542
+ * the whole source is queued for the flush. Otherwise tools compose immediately; client-gated
543
+ * tools are withheld until the client is known, and the source is queued so the flush adds them.
479
544
  *
480
545
  * Don't move the getActors await into the initialize handler — clients time out
481
546
  * waiting for InitializeResult. The queue buffers already-fetched data, not network
@@ -486,15 +551,7 @@ export class ActorsMcpServer {
486
551
  actorStore: this.actorStore,
487
552
  paymentProvider: this.options.paymentProvider,
488
553
  });
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);
554
+ this.registerFetchedActorTools(input, actorTools, false);
498
555
  }
499
556
  /** Delete tools from the server and notify the handler.
500
557
  */
@@ -518,6 +575,9 @@ export class ActorsMcpServer {
518
575
  * @returns Array of added/updated tool wrappers
519
576
  */
520
577
  upsertTools(tools, shouldNotifyToolsChangedHandler = false) {
578
+ // Client gating (e.g. hiding report-problem from Anthropic surfaces) is applied earlier, in
579
+ // composeToolsForClient — the single compose choke point where the client is known. Do not
580
+ // filter here: this is a low-level commit point reached before the client is known too.
521
581
  for (const tool of tools) {
522
582
  const stored = this.options.paymentProvider ? this.options.paymentProvider.decorateToolSchema(tool) : tool;
523
583
  this.tools.set(stored.name, stored);
@@ -642,6 +702,18 @@ export class ActorsMcpServer {
642
702
  };
643
703
  });
644
704
  }
705
+ /**
706
+ * Fetches a task by ID, softFail-logging and throwing a client-facing McpError if it doesn't exist.
707
+ */
708
+ async getTaskOrThrow(taskId, mcpSessionId, logTag) {
709
+ const task = await this.taskStore.getTask(taskId, mcpSessionId);
710
+ if (!task) {
711
+ // Client error (invalid/unknown taskId) — softFail to avoid polluting error logs.
712
+ log.softFail(`[${logTag}] Task not found`, { taskId, mcpSessionId, statusCode: 404 });
713
+ throw new McpError(ErrorCode.InvalidParams, `Task "${taskId}" not found`);
714
+ }
715
+ return task;
716
+ }
645
717
  /**
646
718
  * Sets up MCP request handlers for long-running tasks.
647
719
  */
@@ -665,12 +737,7 @@ export class ActorsMcpServer {
665
737
  const { taskId } = params;
666
738
  const mcpSessionId = (_a = params._meta) === null || _a === void 0 ? void 0 : _a.mcpSessionId;
667
739
  log.debug('[GetTaskRequestSchema] Getting task status', { taskId, mcpSessionId });
668
- const task = await this.taskStore.getTask(taskId, mcpSessionId);
669
- if (task)
670
- return task;
671
- // Client error (invalid/unknown taskId) — softFail to avoid polluting error logs.
672
- log.softFail('[GetTaskRequestSchema] Task not found', { taskId, mcpSessionId, statusCode: 404 });
673
- throw new McpError(ErrorCode.InvalidParams, `Task "${taskId}" not found`);
740
+ return await this.getTaskOrThrow(taskId, mcpSessionId, 'GetTaskRequestSchema');
674
741
  });
675
742
  // Get task result payload
676
743
  this.server.setRequestHandler(GetTaskPayloadRequestSchema, async (request) => {
@@ -680,12 +747,7 @@ export class ActorsMcpServer {
680
747
  const { taskId } = params;
681
748
  const mcpSessionId = (_a = params._meta) === null || _a === void 0 ? void 0 : _a.mcpSessionId;
682
749
  log.debug('[GetTaskPayloadRequestSchema] Getting task result', { taskId, mcpSessionId });
683
- const task = await this.taskStore.getTask(taskId, mcpSessionId);
684
- if (!task) {
685
- // Client error (invalid/unknown taskId) — softFail to avoid polluting error logs.
686
- log.softFail('[GetTaskPayloadRequestSchema] Task not found', { taskId, mcpSessionId, statusCode: 404 });
687
- throw new McpError(ErrorCode.InvalidParams, `Task "${taskId}" not found`);
688
- }
750
+ const task = await this.getTaskOrThrow(taskId, mcpSessionId, 'GetTaskPayloadRequestSchema');
689
751
  if (task.status !== 'completed' && task.status !== 'failed') {
690
752
  throw new McpError(ErrorCode.InvalidParams, `Task "${taskId}" is not completed yet. Current status: ${task.status}`);
691
753
  }
@@ -707,12 +769,7 @@ export class ActorsMcpServer {
707
769
  const { taskId } = params;
708
770
  const mcpSessionId = (_a = params._meta) === null || _a === void 0 ? void 0 : _a.mcpSessionId;
709
771
  log.debug('[CancelTaskRequestSchema] Cancelling task', { taskId, mcpSessionId });
710
- const task = await this.taskStore.getTask(taskId, mcpSessionId);
711
- if (!task) {
712
- // Client error (invalid/unknown taskId) — softFail to avoid polluting error logs.
713
- log.softFail('[CancelTaskRequestSchema] Task not found', { taskId, mcpSessionId, statusCode: 404 });
714
- throw new McpError(ErrorCode.InvalidParams, `Task "${taskId}" not found`);
715
- }
772
+ const task = await this.getTaskOrThrow(taskId, mcpSessionId, 'CancelTaskRequestSchema');
716
773
  if (task.status === 'completed' || task.status === 'failed' || task.status === 'cancelled') {
717
774
  // Client error (cancel on terminal task) — softFail to avoid polluting error logs.
718
775
  log.softFail('[CancelTaskRequestSchema] Task already in terminal state', {
@@ -775,8 +832,12 @@ export class ActorsMcpServer {
775
832
  // Captured by `captureResult` so the `finally` block can measure response size for telemetry.
776
833
  let toolResult = null;
777
834
  const captureResult = (r) => {
778
- toolResult = r;
779
- return r;
835
+ // On a failed result, nudge the agent to report the blocker via report-problem at the
836
+ // moment it decides what to do next. Gated on the tool actually being served (see
837
+ // isReportProblemServable), so clients where it is blocklisted or telemetry is off never see it.
838
+ const augmented = this.withReportProblemNudge(r, resolvedToolName, callDiagnostics.failure_category, callDiagnostics.failure_http_status);
839
+ toolResult = augmented;
840
+ return augmented;
780
841
  };
781
842
  const failInvalidParams = async (message, details, logFields) => {
782
843
  toolStatus = TOOL_STATUS.SOFT_FAIL;
@@ -984,9 +1045,7 @@ export class ActorsMcpServer {
984
1045
  mcpSessionId,
985
1046
  }));
986
1047
  // Extract diagnostics and strip internal fields from res before returning to client.
987
- const diag = extractToolTelemetry(res, actorName, actorId);
988
- toolStatus = diag.toolStatus;
989
- callDiagnostics = { ...callDiagnostics, ...diag.callDiagnostics };
1048
+ ({ toolStatus, callDiagnostics } = applyToolTelemetry(res, actorName, actorId, callDiagnostics));
990
1049
  return captureResult(res);
991
1050
  }
992
1051
  finally {
@@ -1097,6 +1156,10 @@ export class ActorsMcpServer {
1097
1156
  // https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/cancellation#behavior-requirements
1098
1157
  return captureResult({});
1099
1158
  }
1159
+ // Mirror the INTERNAL branch: read the telemetry the executor embedded on error
1160
+ // results (e.g. respondUserError → SOFT_FAIL/INVALID_INPUT), strip it from the wire,
1161
+ // and set failure_category so the report-problem nudge picks the softer variant.
1162
+ ({ toolStatus, callDiagnostics } = applyToolTelemetry(executorResult, actorName, actorId, callDiagnostics));
1100
1163
  return captureResult(executorResult);
1101
1164
  }
1102
1165
  finally {
@@ -1243,6 +1306,13 @@ export class ActorsMcpServer {
1243
1306
  ...deriveResourceIds(params.args, params.result),
1244
1307
  };
1245
1308
  trackToolCall(params.userId, this.telemetryEnv, finalizedTelemetryData);
1309
+ // A successful report-problem call also emits a dedicated feedback event carrying the
1310
+ // submission. A downstream Segment destination fans it out to Slack/GitHub.
1311
+ if (params.toolName === HELPER_TOOLS.PROBLEM_REPORT &&
1312
+ params.toolStatus === TOOL_STATUS.SUCCEEDED &&
1313
+ params.args) {
1314
+ trackReportedProblem(params.userId, this.telemetryEnv, buildReportedProblemProperties(finalizedTelemetryData, params.args));
1315
+ }
1246
1316
  }
1247
1317
  }
1248
1318
  // TODO: this function quite duplicates the main tool call login the CallToolRequestSchema handler, we should refactor
@@ -1378,9 +1448,7 @@ export class ActorsMcpServer {
1378
1448
  mcpSessionId,
1379
1449
  taskMode: true,
1380
1450
  }));
1381
- const diag = extractToolTelemetry(res, actorName, actorId);
1382
- toolStatus = diag.toolStatus;
1383
- callDiagnostics = { ...callDiagnostics, ...diag.callDiagnostics };
1451
+ ({ toolStatus, callDiagnostics } = applyToolTelemetry(res, actorName, actorId, callDiagnostics));
1384
1452
  result = res;
1385
1453
  }
1386
1454
  finally {
@@ -1418,6 +1486,9 @@ export class ActorsMcpServer {
1418
1486
  result = {};
1419
1487
  }
1420
1488
  else {
1489
+ // Mirror the INTERNAL task branch: extract embedded telemetry so failure_category
1490
+ // drives the success-path nudge below and toolTelemetry is stripped from the wire.
1491
+ ({ toolStatus, callDiagnostics } = applyToolTelemetry(executorResult, actorName, actorId, callDiagnostics));
1421
1492
  result = executorResult;
1422
1493
  }
1423
1494
  }
@@ -1430,6 +1501,9 @@ export class ActorsMcpServer {
1430
1501
  // Check if task was cancelled before storing result
1431
1502
  if (await skipIfTaskCancelled(', skipping result storage', toolStatus))
1432
1503
  return;
1504
+ // On a failed result, nudge the agent to report the blocker via report-problem (mirrors the
1505
+ // synchronous CallTool path, which task-mode calls like call-actor bypass).
1506
+ result = this.withReportProblemNudge(result, tool.name, callDiagnostics.failure_category, callDiagnostics.failure_http_status);
1433
1507
  // Store the result in the task store
1434
1508
  log.debug('[executeToolAndUpdateTask] Storing completed result', {
1435
1509
  taskId,
@@ -1542,7 +1616,11 @@ export class ActorsMcpServer {
1542
1616
  taskId,
1543
1617
  mcpSessionId,
1544
1618
  });
1545
- const failedResult = {
1619
+ // Nudge on a genuinely-failed task result. INTERNAL_ERROR and an unknown category get the
1620
+ // full nudge; a genuine INVALID_INPUT gets the softer nudge; payment (402) is suppressed via
1621
+ // the HTTP status and AUTH / PERMISSION_APPROVAL_REQUIRED via NON_NUDGE_FAILURE_CATEGORIES.
1622
+ // The 402 branch above returns before reaching here, so this only sees non-payment failures.
1623
+ const failedResult = this.withReportProblemNudge({
1546
1624
  content: [
1547
1625
  {
1548
1626
  type: 'text',
@@ -1551,7 +1629,7 @@ export class ActorsMcpServer {
1551
1629
  ],
1552
1630
  isError: true,
1553
1631
  internalToolStatus: toolStatus,
1554
- };
1632
+ }, tool.name, callDiagnostics.failure_category, callDiagnostics.failure_http_status);
1555
1633
  await storeTaskResultOrSkipIfExpired(this.taskStore, tool.name, taskId, 'failed', failedResult, mcpSessionId);
1556
1634
  await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
1557
1635
  finishTaskTracking(toolStatus, callDiagnostics, failedResult);