@apify/actors-mcp-server 0.11.7-beta.1 → 0.11.7-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index_internals.d.ts +7 -12
- package/dist/index_internals.d.ts.map +1 -1
- package/dist/index_internals.js +6 -11
- package/dist/index_internals.js.map +1 -1
- package/dist/mcp/actors.d.ts +0 -4
- package/dist/mcp/actors.d.ts.map +1 -1
- package/dist/mcp/actors.js +0 -13
- package/dist/mcp/actors.js.map +1 -1
- package/dist/mcp/server.d.ts +3 -17
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +184 -435
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/tool_call_error_mapper.d.ts +45 -0
- package/dist/mcp/tool_call_error_mapper.d.ts.map +1 -0
- package/dist/mcp/tool_call_error_mapper.js +59 -0
- package/dist/mcp/tool_call_error_mapper.js.map +1 -0
- package/dist/mcp/tool_call_telemetry.d.ts +37 -0
- package/dist/mcp/tool_call_telemetry.d.ts.map +1 -0
- package/dist/mcp/tool_call_telemetry.js +85 -0
- package/dist/mcp/tool_call_telemetry.js.map +1 -0
- package/dist/mcp/tool_dispatch.d.ts +49 -0
- package/dist/mcp/tool_dispatch.d.ts.map +1 -0
- package/dist/mcp/tool_dispatch.js +208 -0
- package/dist/mcp/tool_dispatch.js.map +1 -0
- package/dist/payments/const.d.ts +1 -0
- package/dist/payments/const.d.ts.map +1 -1
- package/dist/payments/const.js +1 -0
- package/dist/payments/const.js.map +1 -1
- package/dist/payments/skyfire.d.ts.map +1 -1
- package/dist/payments/skyfire.js +5 -6
- package/dist/payments/skyfire.js.map +1 -1
- package/dist/tools/actors/call_actor.d.ts +0 -3
- package/dist/tools/actors/call_actor.d.ts.map +1 -1
- package/dist/tools/actors/call_actor.js +2 -11
- package/dist/tools/actors/call_actor.js.map +1 -1
- package/dist/tools/actors/fetch_actor_details.d.ts +1 -7
- package/dist/tools/actors/fetch_actor_details.d.ts.map +1 -1
- package/dist/tools/actors/fetch_actor_details.js +14 -22
- package/dist/tools/actors/fetch_actor_details.js.map +1 -1
- package/dist/tools/actors/search_actors.d.ts +1 -6
- package/dist/tools/actors/search_actors.d.ts.map +1 -1
- package/dist/tools/actors/search_actors.js +17 -24
- package/dist/tools/actors/search_actors.js.map +1 -1
- package/dist/tools/dev/report_problem.d.ts +13 -0
- package/dist/tools/dev/report_problem.d.ts.map +1 -1
- package/dist/tools/dev/report_problem.js +14 -0
- package/dist/tools/dev/report_problem.js.map +1 -1
- package/dist/tools/docs/search_apify_docs.d.ts.map +1 -1
- package/dist/tools/docs/search_apify_docs.js +1 -0
- package/dist/tools/docs/search_apify_docs.js.map +1 -1
- package/dist/tools/runs/get_actor_run.d.ts +1 -6
- package/dist/tools/runs/get_actor_run.d.ts.map +1 -1
- package/dist/tools/runs/get_actor_run.js +17 -24
- package/dist/tools/runs/get_actor_run.js.map +1 -1
- package/dist/tools/storage/get_dataset.d.ts.map +1 -1
- package/dist/tools/storage/get_dataset.js +6 -5
- package/dist/tools/storage/get_dataset.js.map +1 -1
- package/dist/tools/storage/get_dataset_items.d.ts.map +1 -1
- package/dist/tools/storage/get_dataset_items.js +6 -8
- package/dist/tools/storage/get_dataset_items.js.map +1 -1
- package/dist/tools/storage/get_dataset_list.d.ts.map +1 -1
- package/dist/tools/storage/get_dataset_list.js +8 -9
- package/dist/tools/storage/get_dataset_list.js.map +1 -1
- package/dist/tools/storage/get_dataset_schema.d.ts.map +1 -1
- package/dist/tools/storage/get_dataset_schema.js +6 -4
- package/dist/tools/storage/get_dataset_schema.js.map +1 -1
- package/dist/tools/storage/get_key_value_store.js +3 -3
- package/dist/tools/storage/get_key_value_store.js.map +1 -1
- package/dist/tools/storage/get_key_value_store_keys.js +4 -4
- package/dist/tools/storage/get_key_value_store_keys.js.map +1 -1
- package/dist/tools/storage/get_key_value_store_list.d.ts.map +1 -1
- package/dist/tools/storage/get_key_value_store_list.js +8 -9
- package/dist/tools/storage/get_key_value_store_list.js.map +1 -1
- package/dist/tools/storage/get_key_value_store_record.d.ts.map +1 -1
- package/dist/tools/storage/get_key_value_store_record.js +3 -2
- package/dist/tools/storage/get_key_value_store_record.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types.d.ts +0 -11
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/actor_details.d.ts.map +1 -1
- package/dist/utils/actor_details.js +10 -3
- package/dist/utils/actor_details.js.map +1 -1
- package/dist/utils/logging.d.ts +2 -0
- package/dist/utils/logging.d.ts.map +1 -1
- package/dist/utils/logging.js +45 -1
- package/dist/utils/logging.js.map +1 -1
- package/dist/utils/payment_errors.d.ts +7 -1
- package/dist/utils/payment_errors.d.ts.map +1 -1
- package/dist/utils/payment_errors.js +10 -0
- package/dist/utils/payment_errors.js.map +1 -1
- package/dist/web/dist/actor-run-widget.js +0 -3
- package/dist/web/dist/search-actors-widget.js +0 -3
- package/package.json +2 -2
- package/dist/web/dist/actor-detail-widget.js +0 -4551
package/dist/mcp/server.js
CHANGED
|
@@ -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,
|
|
11
|
+
import { CallToolRequestSchema, CancelTaskRequestSchema, ErrorCode, GetPromptRequestSchema, GetTaskPayloadRequestSchema, GetTaskRequestSchema, InitializeRequestSchema, ListPromptsRequestSchema, ListResourcesRequestSchema, ListResourceTemplatesRequestSchema, ListTasksRequestSchema, ListToolsRequestSchema, McpError, ReadResourceRequestSchema, RELATED_TASK_META_KEY, SetLevelRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
|
|
12
12
|
import dedent from 'dedent';
|
|
13
13
|
import log from '@apify/log';
|
|
14
14
|
import { parseBooleanOrNull } from '@apify/utilities';
|
|
@@ -19,29 +19,26 @@ import { prompts } from '../prompts/index.js';
|
|
|
19
19
|
import { createResourceService } from '../resources/resource_service.js';
|
|
20
20
|
import { resolveAvailableWidgets } from '../resources/widgets.js';
|
|
21
21
|
import { getServerInfo } from '../server_card.js';
|
|
22
|
-
import {
|
|
22
|
+
import { getTelemetryEnv } from '../telemetry.js';
|
|
23
23
|
import { decodeDotPropertyNames } from '../tools/actor_input_schema.js';
|
|
24
24
|
import { legacyToolNameToNew } from '../tools/actor_tool_naming.js';
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import { appendReportProblemNudge } from '../tools/dev/report_problem.js';
|
|
25
|
+
import { checkPaymentProviderStandbyConflict } from '../tools/actors/call_actor.js';
|
|
26
|
+
import { withReportProblemNudge } from '../tools/dev/report_problem.js';
|
|
28
27
|
import { getActorsAsTools } from '../tools/index.js';
|
|
29
28
|
import { SERVER_MODE, TOOL_TYPE } from '../types.js';
|
|
30
|
-
import {
|
|
31
|
-
import {
|
|
32
|
-
import { computeToolResponseBytes, getToolCallErrorUserText, respondErrorNoTelemetry, respondOk, } from '../utils/mcp.js';
|
|
29
|
+
import { isMcpClientFaultMessage, logHttpError, sanitizeMezmoMessage } from '../utils/logging.js';
|
|
30
|
+
import { respondOk } from '../utils/mcp.js';
|
|
33
31
|
import { isReportProblemBlockedForClient } from '../utils/mcp_clients.js';
|
|
34
|
-
import { buildPaymentRequiredResponse, isX402PaymentRequiredError } from '../utils/payment_errors.js';
|
|
35
32
|
import { createProgressTracker } from '../utils/progress.js';
|
|
36
33
|
import { getServerInstructions } from '../utils/server-instructions/index.js';
|
|
37
34
|
import { parseServerMode, resolveServerMode } from '../utils/server_mode.js';
|
|
38
|
-
import {
|
|
35
|
+
import { extractAjvErrorDetails } from '../utils/tool_status.js';
|
|
39
36
|
import { buildActorFields, extractActorId, extractActorName, getToolFullName, getToolPublicFieldOnly, } from '../utils/tools.js';
|
|
40
37
|
import { getActors, getToolsForServerMode, toolNamesToInput } from '../utils/tools_loader.js';
|
|
41
|
-
import {
|
|
42
|
-
import {
|
|
43
|
-
import {
|
|
44
|
-
import {
|
|
38
|
+
import { LOG_LEVEL_MAP } from './const.js';
|
|
39
|
+
import { buildToolCallErrorResult, TOOL_CALL_ERROR_KIND } from './tool_call_error_mapper.js';
|
|
40
|
+
import { logToolCallAndTelemetry, prepareTelemetryData } from './tool_call_telemetry.js';
|
|
41
|
+
import { dispatchToolCall } from './tool_dispatch.js';
|
|
45
42
|
import { createTaskCancellationWatcher, isTaskCancelled, isTaskNotFoundError, parseInputParamsFromUrl, storeTaskResultOrSkipIfExpired, } from './utils.js';
|
|
46
43
|
/**
|
|
47
44
|
* Returns true when the initialize request advertises the MCP Apps UI extension
|
|
@@ -199,18 +196,18 @@ export class ActorsMcpServer {
|
|
|
199
196
|
writable: true,
|
|
200
197
|
value: []
|
|
201
198
|
});
|
|
202
|
-
// Telemetry configuration (resolved from options and env vars
|
|
199
|
+
// Telemetry configuration (resolved from options and env vars, see setupTelemetry)
|
|
203
200
|
Object.defineProperty(this, "telemetryEnabled", {
|
|
204
201
|
enumerable: true,
|
|
205
202
|
configurable: true,
|
|
206
203
|
writable: true,
|
|
207
|
-
value:
|
|
204
|
+
value: void 0
|
|
208
205
|
});
|
|
209
206
|
Object.defineProperty(this, "telemetryEnv", {
|
|
210
207
|
enumerable: true,
|
|
211
208
|
configurable: true,
|
|
212
209
|
writable: true,
|
|
213
|
-
value:
|
|
210
|
+
value: void 0
|
|
214
211
|
});
|
|
215
212
|
// List of widgets that are ready to be served
|
|
216
213
|
Object.defineProperty(this, "availableWidgets", {
|
|
@@ -277,7 +274,9 @@ export class ActorsMcpServer {
|
|
|
277
274
|
},
|
|
278
275
|
instructions: getServerInstructions(),
|
|
279
276
|
});
|
|
280
|
-
this.setupTelemetry();
|
|
277
|
+
const { telemetryEnabled, telemetryEnv } = this.setupTelemetry();
|
|
278
|
+
this.telemetryEnabled = telemetryEnabled;
|
|
279
|
+
this.telemetryEnv = telemetryEnv;
|
|
281
280
|
this.setupInitializeHandler();
|
|
282
281
|
this.setupLoggingProxy();
|
|
283
282
|
this.tools = new Map();
|
|
@@ -296,18 +295,21 @@ export class ActorsMcpServer {
|
|
|
296
295
|
*/
|
|
297
296
|
setupTelemetry() {
|
|
298
297
|
var _a, _b, _c;
|
|
298
|
+
let telemetryEnabled;
|
|
299
299
|
const explicitEnabled = parseBooleanOrNull((_a = this.options.telemetry) === null || _a === void 0 ? void 0 : _a.enabled);
|
|
300
300
|
if (explicitEnabled !== null) {
|
|
301
|
-
|
|
301
|
+
telemetryEnabled = explicitEnabled;
|
|
302
302
|
}
|
|
303
303
|
else {
|
|
304
304
|
const envEnabled = parseBooleanOrNull(process.env.TELEMETRY_ENABLED);
|
|
305
|
-
|
|
305
|
+
telemetryEnabled = envEnabled !== null && envEnabled !== void 0 ? envEnabled : DEFAULT_TELEMETRY_ENABLED;
|
|
306
306
|
}
|
|
307
307
|
// Configure telemetryEnv: explicit option > env var > default ('PROD')
|
|
308
|
-
|
|
309
|
-
|
|
308
|
+
let telemetryEnv = DEFAULT_TELEMETRY_ENV;
|
|
309
|
+
if (telemetryEnabled) {
|
|
310
|
+
telemetryEnv = getTelemetryEnv((_c = (_b = this.options.telemetry) === null || _b === void 0 ? void 0 : _b.env) !== null && _c !== void 0 ? _c : process.env.TELEMETRY_ENV);
|
|
310
311
|
}
|
|
312
|
+
return { telemetryEnabled, telemetryEnv };
|
|
311
313
|
}
|
|
312
314
|
/**
|
|
313
315
|
* Override the SDK's `initialize` request handler to run mode resolution and
|
|
@@ -389,23 +391,10 @@ export class ActorsMcpServer {
|
|
|
389
391
|
* survive a load that never sees an initialize.
|
|
390
392
|
*/
|
|
391
393
|
isReportProblemServable() {
|
|
392
|
-
return (
|
|
394
|
+
return (this.telemetryEnabled &&
|
|
393
395
|
this.clientKnown &&
|
|
394
396
|
!isReportProblemBlockedForClient(this.options.initializeRequestData));
|
|
395
397
|
}
|
|
396
|
-
/**
|
|
397
|
-
* Append the report-problem nudge to a failed tool result. Thin wrapper over
|
|
398
|
-
* {@link appendReportProblemNudge} that fills in whether report-problem is served on this
|
|
399
|
-
* connection; suppression by category/402 is handled inside the helper.
|
|
400
|
-
*/
|
|
401
|
-
withReportProblemNudge(result, failingToolName, failureCategory, failureHttpStatus) {
|
|
402
|
-
return appendReportProblemNudge(result, {
|
|
403
|
-
failingToolName,
|
|
404
|
-
available: this.tools.has(HELPER_TOOLS.PROBLEM_REPORT),
|
|
405
|
-
failureCategory,
|
|
406
|
-
failureHttpStatus,
|
|
407
|
-
});
|
|
408
|
-
}
|
|
409
398
|
composePendingToolsForClient() {
|
|
410
399
|
if (this.pendingToolsUntilClientKnown.length === 0)
|
|
411
400
|
return;
|
|
@@ -843,7 +832,7 @@ export class ActorsMcpServer {
|
|
|
843
832
|
* @throws {McpError} - based on the McpServer class code from the typescript MCP SDK
|
|
844
833
|
*/
|
|
845
834
|
this.server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
|
|
846
|
-
var _a, _b, _c, _d, _e
|
|
835
|
+
var _a, _b, _c, _d, _e;
|
|
847
836
|
const params = request.params;
|
|
848
837
|
// eslint-disable-next-line prefer-const
|
|
849
838
|
let { name, arguments: args, _meta: meta } = params;
|
|
@@ -870,7 +859,13 @@ export class ActorsMcpServer {
|
|
|
870
859
|
// On a failed result, nudge the agent to report the blocker via report-problem at the
|
|
871
860
|
// moment it decides what to do next. Gated on the tool actually being served (see
|
|
872
861
|
// isReportProblemServable), so clients where it is blocklisted or telemetry is off never see it.
|
|
873
|
-
const augmented =
|
|
862
|
+
const augmented = withReportProblemNudge({
|
|
863
|
+
result: r,
|
|
864
|
+
tools: this.tools,
|
|
865
|
+
failingToolName: resolvedToolName,
|
|
866
|
+
failureCategory: callDiagnostics.failure_category,
|
|
867
|
+
failureHttpStatus: callDiagnostics.failure_http_status,
|
|
868
|
+
});
|
|
874
869
|
toolResult = augmented;
|
|
875
870
|
return augmented;
|
|
876
871
|
};
|
|
@@ -892,7 +887,12 @@ export class ActorsMcpServer {
|
|
|
892
887
|
};
|
|
893
888
|
// Initialize telemetry with raw tool name — updated below once the tool is resolved.
|
|
894
889
|
// This ensures telemetry is available even for early failures (missing token, tool not found).
|
|
895
|
-
const { telemetryData, userId } = await
|
|
890
|
+
const { telemetryData, userId } = await prepareTelemetryData({
|
|
891
|
+
toolName: name,
|
|
892
|
+
mcpSessionId,
|
|
893
|
+
apifyToken,
|
|
894
|
+
apifyMcpServer: this,
|
|
895
|
+
});
|
|
896
896
|
// actorName/actorId are declared here so they're available in the catch block for telemetry.
|
|
897
897
|
// Set after tool resolution (inside the try block).
|
|
898
898
|
let actorName;
|
|
@@ -1090,196 +1090,75 @@ export class ActorsMcpServer {
|
|
|
1090
1090
|
callDiagnostics = outcome.callDiagnostics;
|
|
1091
1091
|
return captureResult(outcome.result);
|
|
1092
1092
|
}
|
|
1093
|
-
//
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
if (tool.type === TOOL_TYPE.ACTOR_MCP) {
|
|
1122
|
-
let client = null;
|
|
1123
|
-
try {
|
|
1124
|
-
client = await connectMCPClient(tool.serverUrl, apifyToken, mcpSessionId);
|
|
1125
|
-
if (!client) {
|
|
1126
|
-
const msg = dedent `
|
|
1127
|
-
Failed to connect to MCP server at "${tool.serverUrl}".
|
|
1128
|
-
Please verify the server URL is correct and accessible, and ensure you have a valid Apify token with appropriate permissions.
|
|
1129
|
-
`;
|
|
1130
|
-
log.softFail(msg, { mcpSessionId, failureCategory: FAILURE_CATEGORY.INTERNAL_ERROR });
|
|
1131
|
-
await this.server.sendLoggingMessage({ level: 'error', data: msg });
|
|
1132
|
-
toolStatus = TOOL_STATUS.SOFT_FAIL;
|
|
1133
|
-
callDiagnostics = { ...callDiagnostics, failure_category: FAILURE_CATEGORY.INTERNAL_ERROR };
|
|
1134
|
-
return captureResult(respondErrorNoTelemetry(msg));
|
|
1135
|
-
}
|
|
1136
|
-
// Only set up notification handlers if progressToken is provided by the client
|
|
1137
|
-
if (progressToken !== undefined && progressToken !== null) {
|
|
1138
|
-
// Set up notification handlers for the client
|
|
1139
|
-
for (const schema of ServerNotificationSchema.options) {
|
|
1140
|
-
const method = schema.shape.method.value;
|
|
1141
|
-
// Forward notifications from the proxy client to the server
|
|
1142
|
-
client.setNotificationHandler(schema, async (notification) => {
|
|
1143
|
-
log.debug('Sending MCP notification', {
|
|
1144
|
-
method,
|
|
1145
|
-
mcpSessionId,
|
|
1146
|
-
notification,
|
|
1147
|
-
});
|
|
1148
|
-
await extra.sendNotification(notification);
|
|
1149
|
-
});
|
|
1150
|
-
}
|
|
1151
|
-
}
|
|
1152
|
-
log.info('Calling Actor-MCP', {
|
|
1153
|
-
toolName: tool.name,
|
|
1154
|
-
actorMcpToolName: tool.originToolName,
|
|
1155
|
-
actorId: tool.actorId,
|
|
1156
|
-
mcpSessionId,
|
|
1157
|
-
input: logSafeArgs,
|
|
1158
|
-
});
|
|
1159
|
-
const res = await client.callTool({
|
|
1160
|
-
name: tool.originToolName,
|
|
1161
|
-
arguments: toolArgs,
|
|
1162
|
-
_meta: { progressToken },
|
|
1163
|
-
}, CallToolResultSchema, {
|
|
1164
|
-
timeout: EXTERNAL_TOOL_CALL_TIMEOUT_MSEC,
|
|
1165
|
-
});
|
|
1166
|
-
// TODO: actor-mcp responses are opaque — isError could be a user input problem
|
|
1167
|
-
// (e.g. invalid query) or a genuine server failure. We can't distinguish without
|
|
1168
|
-
// parsing the error text. Defaulting to INTERNAL_ERROR for now; revisit when
|
|
1169
|
-
// actor-mcp gets deeper telemetry treatment.
|
|
1170
|
-
if ('isError' in res && res.isError) {
|
|
1171
|
-
toolStatus = TOOL_STATUS.SOFT_FAIL;
|
|
1172
|
-
callDiagnostics = {
|
|
1173
|
-
failure_category: FAILURE_CATEGORY.INTERNAL_ERROR,
|
|
1174
|
-
...buildActorFields(actorName, actorId),
|
|
1175
|
-
};
|
|
1176
|
-
}
|
|
1177
|
-
return captureResult({ ...res });
|
|
1178
|
-
}
|
|
1179
|
-
catch (error) {
|
|
1180
|
-
toolStatus = getToolStatusFromError(error, Boolean((_e = extra.signal) === null || _e === void 0 ? void 0 : _e.aborted));
|
|
1181
|
-
const failureDetail = error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200);
|
|
1182
|
-
callDiagnostics = {
|
|
1183
|
-
failure_category: classifyFailureCategory(error),
|
|
1184
|
-
failure_detail: failureDetail,
|
|
1185
|
-
...buildActorFields(actorName, actorId),
|
|
1186
|
-
};
|
|
1187
|
-
logHttpError(error, `Failed to call MCP tool '${tool.originToolName}' on Actor '${tool.actorId}'`, {
|
|
1188
|
-
actorId: tool.actorId,
|
|
1189
|
-
toolName: tool.originToolName,
|
|
1190
|
-
failureCategory: callDiagnostics.failure_category,
|
|
1191
|
-
});
|
|
1192
|
-
return captureResult(respondErrorNoTelemetry(`Failed to call MCP tool '${tool.originToolName}' on Actor '${tool.actorId}': ${remoteMcpFailureDetail(error)}`));
|
|
1193
|
-
}
|
|
1194
|
-
finally {
|
|
1195
|
-
if (client)
|
|
1196
|
-
await client.close();
|
|
1197
|
-
}
|
|
1198
|
-
}
|
|
1199
|
-
// Handle actor tool
|
|
1200
|
-
if (tool.type === TOOL_TYPE.ACTOR) {
|
|
1201
|
-
const progressTracker = createProgressTracker(progressToken, extra.sendNotification);
|
|
1202
|
-
try {
|
|
1203
|
-
log.info('Calling Actor', {
|
|
1204
|
-
toolName: tool.name,
|
|
1205
|
-
actorName: tool.actorFullName,
|
|
1206
|
-
mcpSessionId,
|
|
1207
|
-
input: logSafeArgs,
|
|
1208
|
-
});
|
|
1209
|
-
const executorResult = await actorExecutor.executeActorTool({
|
|
1210
|
-
actorFullName: tool.actorFullName,
|
|
1211
|
-
input: toolArgs,
|
|
1212
|
-
apifyClient: apifyClient,
|
|
1213
|
-
callOptions: { memory: tool.memoryMbytes },
|
|
1214
|
-
progressTracker,
|
|
1215
|
-
abortSignal: extra.signal,
|
|
1216
|
-
mcpSessionId,
|
|
1217
|
-
datasetItemsSchema: tool.datasetItemsSchema,
|
|
1218
|
-
});
|
|
1219
|
-
if (!executorResult) {
|
|
1220
|
-
toolStatus = TOOL_STATUS.ABORTED;
|
|
1221
|
-
// Receivers of cancellation notifications SHOULD NOT send a response for the cancelled request
|
|
1222
|
-
// https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/cancellation#behavior-requirements
|
|
1223
|
-
return captureResult({});
|
|
1224
|
-
}
|
|
1225
|
-
// Mirror the INTERNAL branch: read the telemetry the executor embedded on error
|
|
1226
|
-
// results (e.g. respondUserError → SOFT_FAIL/INVALID_INPUT), strip it from the wire,
|
|
1227
|
-
// and set failure_category so the report-problem nudge picks the softer variant.
|
|
1228
|
-
({ toolStatus, callDiagnostics } = applyToolTelemetry(executorResult, actorName, actorId, callDiagnostics));
|
|
1229
|
-
return captureResult(executorResult);
|
|
1230
|
-
}
|
|
1231
|
-
finally {
|
|
1232
|
-
if (progressTracker) {
|
|
1233
|
-
progressTracker.stop();
|
|
1234
|
-
}
|
|
1235
|
-
}
|
|
1236
|
-
}
|
|
1237
|
-
// If we reached here without returning, it means the tool type was not recognized (user error)
|
|
1238
|
-
toolStatus = TOOL_STATUS.SOFT_FAIL;
|
|
1093
|
+
// Progress tracker: opt in for the two INTERNAL tools that emit during a sync wait
|
|
1094
|
+
// (call-actor start+waitForFinish, get-actor-run when waitSecs > 0), and unconditionally
|
|
1095
|
+
// for ACTOR tools. ACTOR_MCP forwards notifications directly, not via a tracker.
|
|
1096
|
+
const progressTrackerOptIn = tool.type === TOOL_TYPE.ACTOR ||
|
|
1097
|
+
(tool.type === TOOL_TYPE.INTERNAL &&
|
|
1098
|
+
(tool.name === HELPER_TOOLS.ACTOR_CALL || tool.name === HELPER_TOOLS.ACTOR_RUNS_GET));
|
|
1099
|
+
const progressTracker = progressTrackerOptIn
|
|
1100
|
+
? createProgressTracker(progressToken, extra.sendNotification)
|
|
1101
|
+
: null;
|
|
1102
|
+
const dispatchResult = await dispatchToolCall({
|
|
1103
|
+
tool,
|
|
1104
|
+
toolArgs: toolArgs,
|
|
1105
|
+
logSafeArgs,
|
|
1106
|
+
apifyToken,
|
|
1107
|
+
apifyClient: apifyClient,
|
|
1108
|
+
apifyMcpServer: this,
|
|
1109
|
+
mcpSessionId,
|
|
1110
|
+
progressToken,
|
|
1111
|
+
progressTracker,
|
|
1112
|
+
shouldForwardNotifications: true,
|
|
1113
|
+
extra,
|
|
1114
|
+
actorName,
|
|
1115
|
+
actorId,
|
|
1116
|
+
taskMode: false,
|
|
1117
|
+
});
|
|
1118
|
+
toolStatus = dispatchResult.toolStatus;
|
|
1119
|
+
callDiagnostics = dispatchResult.callDiagnostics;
|
|
1120
|
+
return captureResult(dispatchResult.result);
|
|
1239
1121
|
}
|
|
1240
1122
|
catch (error) {
|
|
1241
|
-
|
|
1123
|
+
// Re-throw MCP protocol errors (e.g. from failInvalidParams) so the SDK
|
|
1124
|
+
// returns them as JSON-RPC errors. failInvalidParams already set callDiagnostics
|
|
1125
|
+
// with the correct semantic category (e.g. AUTH), so we must not overwrite it.
|
|
1126
|
+
// Re-throwing first is order-equivalent only because no McpError with an HTTP-range
|
|
1127
|
+
// code reaches this catch: an McpError(402) WOULD satisfy the x402 predicate
|
|
1128
|
+
// (getHttpStatusCode falls through to `.code`), but every remote-McpError route is
|
|
1129
|
+
// sealed by an inner catch (ACTOR_MCP branch, call-actor MCP passthrough) and all
|
|
1130
|
+
// in-repo McpErrors use negative ErrorCode.* values.
|
|
1131
|
+
if (error instanceof McpError) {
|
|
1132
|
+
throw error;
|
|
1133
|
+
}
|
|
1134
|
+
const errorResult = buildToolCallErrorResult(error, {
|
|
1135
|
+
toolName: name,
|
|
1136
|
+
actorName,
|
|
1137
|
+
actorId,
|
|
1138
|
+
isAborted: Boolean((_e = extra.signal) === null || _e === void 0 ? void 0 : _e.aborted),
|
|
1139
|
+
});
|
|
1140
|
+
toolStatus = errorResult.toolStatus;
|
|
1242
1141
|
// Propagate 402 Payment Required as a tool result per x402 MCP transport spec:
|
|
1243
|
-
// content[0].text (JSON) + isError: true.
|
|
1244
|
-
//
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
callDiagnostics =
|
|
1248
|
-
|
|
1249
|
-
failure_http_status: 402,
|
|
1250
|
-
...buildActorFields(actorName, actorId),
|
|
1251
|
-
};
|
|
1252
|
-
return captureResult(buildPaymentRequiredResponse(error));
|
|
1142
|
+
// content[0].text (JSON) + isError: true. No log here (unlike the task path). The
|
|
1143
|
+
// concurrent-run limit also surfaces as 402 but is excluded by the predicate and
|
|
1144
|
+
// falls through to the generic run-limit handling below.
|
|
1145
|
+
if (errorResult.kind === TOOL_CALL_ERROR_KIND.PAYMENT) {
|
|
1146
|
+
callDiagnostics = errorResult.callDiagnostics;
|
|
1147
|
+
return captureResult(errorResult.response);
|
|
1253
1148
|
}
|
|
1254
|
-
if (
|
|
1255
|
-
|
|
1256
|
-
callDiagnostics = {
|
|
1257
|
-
failure_category: FAILURE_CATEGORY.PERMISSION_APPROVAL_REQUIRED,
|
|
1258
|
-
failure_http_status: error.statusCode,
|
|
1259
|
-
...buildActorFields(actorName, actorId),
|
|
1260
|
-
};
|
|
1149
|
+
if (errorResult.kind === TOOL_CALL_ERROR_KIND.APPROVAL) {
|
|
1150
|
+
callDiagnostics = errorResult.callDiagnostics;
|
|
1261
1151
|
logHttpError(error, 'Permission approval required while calling tool', {
|
|
1262
1152
|
toolName: name,
|
|
1263
1153
|
mcpSessionId,
|
|
1264
1154
|
});
|
|
1265
|
-
return captureResult(
|
|
1155
|
+
return captureResult(errorResult.response);
|
|
1266
1156
|
}
|
|
1267
|
-
// Re-throw MCP protocol errors (e.g. from failInvalidParams) so the SDK
|
|
1268
|
-
// returns them as JSON-RPC errors. failInvalidParams already set callDiagnostics
|
|
1269
|
-
// with the correct semantic category (e.g. AUTH), so we must not overwrite it.
|
|
1270
|
-
if (error instanceof McpError) {
|
|
1271
|
-
throw error;
|
|
1272
|
-
}
|
|
1273
|
-
toolStatus = getToolStatusFromError(error, Boolean((_f = extra.signal) === null || _f === void 0 ? void 0 : _f.aborted));
|
|
1274
|
-
const failureDetail = error instanceof Error ? error.message.slice(0, 200) : String(error).slice(0, 200);
|
|
1275
1157
|
callDiagnostics = {
|
|
1276
1158
|
// Spread existing diagnostics first (e.g. validation_keyword from failInvalidParams),
|
|
1277
|
-
// then overwrite with freshly computed fields so they take precedence.
|
|
1159
|
+
// then overwrite with the mapper's freshly computed fields so they take precedence.
|
|
1278
1160
|
...callDiagnostics,
|
|
1279
|
-
|
|
1280
|
-
...(httpStatus !== undefined ? { failure_http_status: httpStatus } : {}),
|
|
1281
|
-
failure_detail: failureDetail,
|
|
1282
|
-
...buildActorFields(actorName, actorId),
|
|
1161
|
+
...errorResult.callDiagnostics,
|
|
1283
1162
|
};
|
|
1284
1163
|
logHttpError(error, 'Error occurred while calling tool', {
|
|
1285
1164
|
toolName: name,
|
|
@@ -1298,14 +1177,14 @@ export class ActorsMcpServer {
|
|
|
1298
1177
|
// reuse the local ABORTED-aware toolStatus, do NOT re-derive from the error (which
|
|
1299
1178
|
// would drop ABORTED and leak failureCategory/failureHttpStatus onto the wire).
|
|
1300
1179
|
return captureResult({
|
|
1301
|
-
...respondOk(
|
|
1180
|
+
...respondOk(errorResult.userText),
|
|
1302
1181
|
isError: true,
|
|
1303
1182
|
toolTelemetry: { toolStatus },
|
|
1304
1183
|
});
|
|
1305
1184
|
}
|
|
1306
1185
|
finally {
|
|
1307
1186
|
if (shouldTrackTelemetry) {
|
|
1308
|
-
|
|
1187
|
+
logToolCallAndTelemetry({
|
|
1309
1188
|
toolName: resolvedToolName,
|
|
1310
1189
|
mcpSessionId,
|
|
1311
1190
|
toolStatus,
|
|
@@ -1316,73 +1195,13 @@ export class ActorsMcpServer {
|
|
|
1316
1195
|
args,
|
|
1317
1196
|
result: toolResult,
|
|
1318
1197
|
taskId: preflightTaskId,
|
|
1198
|
+
apifyMcpServer: this,
|
|
1319
1199
|
});
|
|
1320
1200
|
}
|
|
1321
1201
|
}
|
|
1322
|
-
const availableTools = this.listToolNames();
|
|
1323
|
-
const msg = dedent `
|
|
1324
|
-
Unknown tool type for "${name}".
|
|
1325
|
-
Available tools: ${availableTools.length > 0 ? availableTools.join(', ') : 'none'}.
|
|
1326
|
-
Please verify the tool name and ensure the tool is properly registered.
|
|
1327
|
-
`;
|
|
1328
|
-
log.softFail(msg, { mcpSessionId, statusCode: 404 });
|
|
1329
|
-
await this.server.sendLoggingMessage({
|
|
1330
|
-
level: 'error',
|
|
1331
|
-
data: msg,
|
|
1332
|
-
});
|
|
1333
|
-
throw new McpError(ErrorCode.InvalidParams, msg);
|
|
1334
|
-
});
|
|
1335
|
-
}
|
|
1336
|
-
/**
|
|
1337
|
-
* Logs tool call completion at INFO level and tracks telemetry.
|
|
1338
|
-
* Computes duration once so both the log line and telemetry event use the same value.
|
|
1339
|
-
* Response bytes and resource ids are derived here from the raw `result` (+ `args`) so every
|
|
1340
|
-
* call site stays a plain hand-off — no path can forget to compute or strip them.
|
|
1341
|
-
*/
|
|
1342
|
-
logToolCallAndTelemetry(params) {
|
|
1343
|
-
const durationMs = Date.now() - params.startTime;
|
|
1344
|
-
// `result` is undefined only on short-circuit paths that never produced a payload (e.g. a
|
|
1345
|
-
// cancelled task); skip byte accounting there. `null`/`{}` still measure as zero bytes.
|
|
1346
|
-
const responseBytes = params.result === undefined ? undefined : computeToolResponseBytes(params.result);
|
|
1347
|
-
log.info('Tool call completed', {
|
|
1348
|
-
toolName: params.toolName,
|
|
1349
|
-
mcpSessionId: params.mcpSessionId,
|
|
1350
|
-
toolStatus: params.toolStatus,
|
|
1351
|
-
durationMs,
|
|
1352
|
-
...(responseBytes !== undefined && {
|
|
1353
|
-
responseContentBytes: responseBytes.contentBytes,
|
|
1354
|
-
responseStructuredContentBytes: responseBytes.structuredContentBytes,
|
|
1355
|
-
responseFileBytes: responseBytes.fileBytes,
|
|
1356
|
-
}),
|
|
1357
|
-
...(params.taskId !== undefined && { taskId: params.taskId }),
|
|
1358
1202
|
});
|
|
1359
|
-
if (params.telemetryData) {
|
|
1360
|
-
const finalizedTelemetryData = {
|
|
1361
|
-
...params.telemetryData,
|
|
1362
|
-
tool_status: params.toolStatus,
|
|
1363
|
-
tool_exec_time_ms: durationMs,
|
|
1364
|
-
...(responseBytes && {
|
|
1365
|
-
tool_response_content_bytes: responseBytes.contentBytes,
|
|
1366
|
-
tool_response_structured_content_bytes: responseBytes.structuredContentBytes,
|
|
1367
|
-
tool_response_file_bytes: responseBytes.fileBytes,
|
|
1368
|
-
}),
|
|
1369
|
-
// Always include actor_name/actor_id; failure-specific fields are only present when callDiagnostics has them.
|
|
1370
|
-
...params.callDiagnostics,
|
|
1371
|
-
// Resource ids are read once here from the args + the tool's public output; no tool
|
|
1372
|
-
// threads them back. Applied uniformly, last. See deriveResourceIds.
|
|
1373
|
-
...deriveResourceIds(params.args, params.result),
|
|
1374
|
-
};
|
|
1375
|
-
trackToolCall(params.userId, this.telemetryEnv, finalizedTelemetryData);
|
|
1376
|
-
// A successful report-problem call also emits a dedicated feedback event carrying the
|
|
1377
|
-
// submission. A downstream Segment destination fans it out to Slack/GitHub.
|
|
1378
|
-
if (params.toolName === HELPER_TOOLS.PROBLEM_REPORT &&
|
|
1379
|
-
params.toolStatus === TOOL_STATUS.SUCCEEDED &&
|
|
1380
|
-
params.args) {
|
|
1381
|
-
trackReportedProblem(params.userId, this.telemetryEnv, buildReportedProblemProperties(finalizedTelemetryData, params.args));
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
1203
|
}
|
|
1385
|
-
// TODO:
|
|
1204
|
+
// TODO: outer orchestration here (pre-flight, telemetry bookkeeping, task-store handling) still duplicates the CallToolRequestSchema handler's logic; the dispatch ladder is now shared. Refactor.
|
|
1386
1205
|
/**
|
|
1387
1206
|
* Executes a tool asynchronously for a long-running task and updates task status.
|
|
1388
1207
|
*
|
|
@@ -1408,9 +1227,14 @@ export class ActorsMcpServer {
|
|
|
1408
1227
|
});
|
|
1409
1228
|
// Prepare telemetry before try-catch so it's accessible to both paths.
|
|
1410
1229
|
// This avoids re-fetching user data in the error handler.
|
|
1411
|
-
const { telemetryData, userId } = await
|
|
1230
|
+
const { telemetryData, userId } = await prepareTelemetryData({
|
|
1231
|
+
toolName: getToolFullName(tool),
|
|
1232
|
+
mcpSessionId,
|
|
1233
|
+
apifyToken,
|
|
1234
|
+
apifyMcpServer: this,
|
|
1235
|
+
});
|
|
1412
1236
|
const finishTaskTracking = (status, diagnostics, result) => {
|
|
1413
|
-
|
|
1237
|
+
logToolCallAndTelemetry({
|
|
1414
1238
|
toolName: tool.name,
|
|
1415
1239
|
mcpSessionId,
|
|
1416
1240
|
toolStatus: status,
|
|
@@ -1421,8 +1245,16 @@ export class ActorsMcpServer {
|
|
|
1421
1245
|
callDiagnostics: diagnostics,
|
|
1422
1246
|
args: toolArgs,
|
|
1423
1247
|
result,
|
|
1248
|
+
apifyMcpServer: this,
|
|
1424
1249
|
});
|
|
1425
1250
|
};
|
|
1251
|
+
// Terminal step shared by every task outcome: store the result (expiry-tolerant),
|
|
1252
|
+
// emit the status notification, record telemetry.
|
|
1253
|
+
const completeTask = async (storeStatus, taskResult, status, diagnostics) => {
|
|
1254
|
+
await storeTaskResultOrSkipIfExpired(this.taskStore, tool.name, taskId, storeStatus, taskResult, mcpSessionId);
|
|
1255
|
+
await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
|
|
1256
|
+
finishTaskTracking(status, diagnostics, taskResult);
|
|
1257
|
+
};
|
|
1426
1258
|
// Once a task is cancelled the spec forbids writing a result; every storage path
|
|
1427
1259
|
// must short-circuit here. `logSuffix` is concatenated after "Task was cancelled"
|
|
1428
1260
|
// so we keep the existing log format and the existing telemetry status per path.
|
|
@@ -1475,92 +1307,51 @@ export class ActorsMcpServer {
|
|
|
1475
1307
|
await this.taskStore.updateTaskStatus(taskId, 'working', message, mcpSessionId);
|
|
1476
1308
|
await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
|
|
1477
1309
|
};
|
|
1478
|
-
//
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
progressTracker.stop();
|
|
1505
|
-
}
|
|
1506
|
-
}
|
|
1507
|
-
}
|
|
1508
|
-
// Handle actor tool execution in task mode
|
|
1509
|
-
if (toolStatus === TOOL_STATUS.SUCCEEDED && tool.type === TOOL_TYPE.ACTOR) {
|
|
1510
|
-
const progressTracker = createProgressTracker(progressToken, extra.sendNotification, taskId, onStatusMessage);
|
|
1511
|
-
try {
|
|
1512
|
-
log.info('Calling Actor for task', {
|
|
1513
|
-
taskId,
|
|
1514
|
-
toolName: tool.name,
|
|
1515
|
-
actorName: tool.actorFullName,
|
|
1516
|
-
mcpSessionId,
|
|
1517
|
-
input: logSafeArgs,
|
|
1518
|
-
});
|
|
1519
|
-
const executorResult = await actorExecutor.executeActorTool({
|
|
1520
|
-
actorFullName: tool.actorFullName,
|
|
1521
|
-
input: toolArgs,
|
|
1522
|
-
apifyClient,
|
|
1523
|
-
callOptions: { memory: tool.memoryMbytes },
|
|
1524
|
-
progressTracker,
|
|
1525
|
-
abortSignal: cancelWatcher.signal,
|
|
1526
|
-
mcpSessionId,
|
|
1527
|
-
datasetItemsSchema: tool.datasetItemsSchema,
|
|
1528
|
-
taskMode: true,
|
|
1529
|
-
});
|
|
1530
|
-
if (!executorResult) {
|
|
1531
|
-
toolStatus = TOOL_STATUS.ABORTED;
|
|
1532
|
-
// Receivers of cancellation notifications SHOULD NOT send a response for the cancelled request
|
|
1533
|
-
// https://modelcontextprotocol.io/specification/2025-06-18/basic/utilities/cancellation#behavior-requirements
|
|
1534
|
-
result = {};
|
|
1535
|
-
}
|
|
1536
|
-
else {
|
|
1537
|
-
// Mirror the INTERNAL task branch: extract embedded telemetry so failure_category
|
|
1538
|
-
// drives the success-path nudge below and toolTelemetry is stripped from the wire.
|
|
1539
|
-
({ toolStatus, callDiagnostics } = applyToolTelemetry(executorResult, actorName, actorId, callDiagnostics));
|
|
1540
|
-
result = executorResult;
|
|
1541
|
-
}
|
|
1542
|
-
}
|
|
1543
|
-
finally {
|
|
1544
|
-
if (progressTracker) {
|
|
1545
|
-
progressTracker.stop();
|
|
1546
|
-
}
|
|
1547
|
-
}
|
|
1548
|
-
}
|
|
1310
|
+
// ACTOR_MCP never reads the tracker (matching the sync path, which passes null for it);
|
|
1311
|
+
// INTERNAL/ACTOR get one built from taskId + onStatusMessage, which dispatch consumes
|
|
1312
|
+
// and stops.
|
|
1313
|
+
const progressTracker = tool.type === TOOL_TYPE.ACTOR_MCP
|
|
1314
|
+
? null
|
|
1315
|
+
: createProgressTracker(progressToken, extra.sendNotification, taskId, onStatusMessage);
|
|
1316
|
+
const dispatchResult = await dispatchToolCall({
|
|
1317
|
+
tool,
|
|
1318
|
+
toolArgs,
|
|
1319
|
+
logSafeArgs,
|
|
1320
|
+
apifyToken,
|
|
1321
|
+
apifyClient,
|
|
1322
|
+
apifyMcpServer: this,
|
|
1323
|
+
mcpSessionId,
|
|
1324
|
+
progressToken,
|
|
1325
|
+
progressTracker,
|
|
1326
|
+
shouldForwardNotifications: false,
|
|
1327
|
+
extra: taskExtra,
|
|
1328
|
+
actorName,
|
|
1329
|
+
actorId,
|
|
1330
|
+
taskMode: true,
|
|
1331
|
+
logContext: { messageSuffix: ' for task', fields: { taskId } },
|
|
1332
|
+
});
|
|
1333
|
+
result = dispatchResult.result;
|
|
1334
|
+
toolStatus = dispatchResult.toolStatus;
|
|
1335
|
+
callDiagnostics = dispatchResult.callDiagnostics;
|
|
1549
1336
|
// Check if task was cancelled before storing result
|
|
1550
1337
|
if (await skipIfTaskCancelled(', skipping result storage', toolStatus))
|
|
1551
1338
|
return;
|
|
1552
1339
|
// On a failed result, nudge the agent to report the blocker via report-problem (mirrors the
|
|
1553
1340
|
// synchronous CallTool path, which task-mode calls like call-actor bypass).
|
|
1554
|
-
result =
|
|
1341
|
+
result = withReportProblemNudge({
|
|
1342
|
+
result,
|
|
1343
|
+
tools: this.tools,
|
|
1344
|
+
failingToolName: tool.name,
|
|
1345
|
+
failureCategory: callDiagnostics.failure_category,
|
|
1346
|
+
failureHttpStatus: callDiagnostics.failure_http_status,
|
|
1347
|
+
});
|
|
1555
1348
|
// Store the result in the task store
|
|
1556
1349
|
log.debug('[executeToolAndUpdateTask] Storing completed result', {
|
|
1557
1350
|
taskId,
|
|
1558
1351
|
mcpSessionId,
|
|
1559
1352
|
});
|
|
1560
|
-
await
|
|
1353
|
+
await completeTask('completed', result, toolStatus, callDiagnostics);
|
|
1561
1354
|
log.debug('Task completed successfully', { taskId, toolName: tool.name, mcpSessionId });
|
|
1562
|
-
await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
|
|
1563
|
-
finishTaskTracking(toolStatus, callDiagnostics, result);
|
|
1564
1355
|
}
|
|
1565
1356
|
catch (error) {
|
|
1566
1357
|
// Reached only when the task expired before the `working` transition (updateTaskStatus
|
|
@@ -1579,9 +1370,14 @@ export class ActorsMcpServer {
|
|
|
1579
1370
|
});
|
|
1580
1371
|
return;
|
|
1581
1372
|
}
|
|
1373
|
+
const errorResult = buildToolCallErrorResult(error, {
|
|
1374
|
+
toolName: tool.name,
|
|
1375
|
+
actorName,
|
|
1376
|
+
actorId,
|
|
1377
|
+
isAborted: Boolean(cancelWatcher.signal.aborted),
|
|
1378
|
+
});
|
|
1582
1379
|
// Handle 402 Payment Required — return structured x402 result so clients can auto-pay
|
|
1583
|
-
|
|
1584
|
-
if (isX402PaymentRequiredError(error)) {
|
|
1380
|
+
if (errorResult.kind === TOOL_CALL_ERROR_KIND.PAYMENT) {
|
|
1585
1381
|
logHttpError(error, 'Payment required while calling tool (task mode)', { toolName: tool.name });
|
|
1586
1382
|
// Per MCP tasks spec: once a task is cancelled it MUST remain cancelled,
|
|
1587
1383
|
// so guard storeTaskResult against a cancel that raced with this 402.
|
|
@@ -1589,17 +1385,10 @@ export class ActorsMcpServer {
|
|
|
1589
1385
|
...buildActorFields(actorName, actorId),
|
|
1590
1386
|
}))
|
|
1591
1387
|
return;
|
|
1592
|
-
|
|
1593
|
-
await storeTaskResultOrSkipIfExpired(this.taskStore, tool.name, taskId, 'completed', paymentResponse, mcpSessionId);
|
|
1594
|
-
await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
|
|
1595
|
-
finishTaskTracking(TOOL_STATUS.SOFT_FAIL, {
|
|
1596
|
-
failure_category: FAILURE_CATEGORY.INVALID_INPUT,
|
|
1597
|
-
failure_http_status: 402,
|
|
1598
|
-
...buildActorFields(actorName, actorId),
|
|
1599
|
-
}, paymentResponse);
|
|
1388
|
+
await completeTask('completed', errorResult.response, errorResult.toolStatus, errorResult.callDiagnostics);
|
|
1600
1389
|
return;
|
|
1601
1390
|
}
|
|
1602
|
-
if (
|
|
1391
|
+
if (errorResult.kind === TOOL_CALL_ERROR_KIND.APPROVAL) {
|
|
1603
1392
|
logHttpError(error, 'Permission approval required while calling tool (task mode)', {
|
|
1604
1393
|
toolName: tool.name,
|
|
1605
1394
|
});
|
|
@@ -1609,24 +1398,11 @@ export class ActorsMcpServer {
|
|
|
1609
1398
|
...buildActorFields(actorName, actorId),
|
|
1610
1399
|
}))
|
|
1611
1400
|
return;
|
|
1612
|
-
|
|
1613
|
-
await storeTaskResultOrSkipIfExpired(this.taskStore, tool.name, taskId, 'completed', approvalResponse, mcpSessionId);
|
|
1614
|
-
await emitTaskStatusNotification(taskId, mcpSessionId, this.taskStore, this.server);
|
|
1615
|
-
finishTaskTracking(TOOL_STATUS.SOFT_FAIL, {
|
|
1616
|
-
failure_category: FAILURE_CATEGORY.PERMISSION_APPROVAL_REQUIRED,
|
|
1617
|
-
failure_http_status: error.statusCode,
|
|
1618
|
-
...buildActorFields(actorName, actorId),
|
|
1619
|
-
}, approvalResponse);
|
|
1401
|
+
await completeTask('completed', errorResult.response, errorResult.toolStatus, errorResult.callDiagnostics);
|
|
1620
1402
|
return;
|
|
1621
1403
|
}
|
|
1622
|
-
toolStatus =
|
|
1623
|
-
|
|
1624
|
-
callDiagnostics = {
|
|
1625
|
-
failure_category: classifyFailureCategory(error),
|
|
1626
|
-
...(httpStatus !== undefined ? { failure_http_status: httpStatus } : {}),
|
|
1627
|
-
failure_detail: failureDetail,
|
|
1628
|
-
...buildActorFields(actorName, actorId),
|
|
1629
|
-
};
|
|
1404
|
+
toolStatus = errorResult.toolStatus;
|
|
1405
|
+
callDiagnostics = errorResult.callDiagnostics;
|
|
1630
1406
|
// Log level follows the already-classified toolStatus:
|
|
1631
1407
|
// SOFT_FAIL (e.g. 402/403 user quota, client-side issues) → softFail
|
|
1632
1408
|
// FAILED/ABORTED/other → error
|
|
@@ -1656,7 +1432,7 @@ export class ActorsMcpServer {
|
|
|
1656
1432
|
error,
|
|
1657
1433
|
});
|
|
1658
1434
|
}
|
|
1659
|
-
const userText =
|
|
1435
|
+
const { userText } = errorResult;
|
|
1660
1436
|
// Check if task was cancelled before storing result
|
|
1661
1437
|
if (await skipIfTaskCancelled(', skipping result storage', toolStatus, callDiagnostics))
|
|
1662
1438
|
return;
|
|
@@ -1664,60 +1440,33 @@ export class ActorsMcpServer {
|
|
|
1664
1440
|
taskId,
|
|
1665
1441
|
mcpSessionId,
|
|
1666
1442
|
});
|
|
1667
|
-
// Nudge on a genuinely-failed task result
|
|
1668
|
-
//
|
|
1669
|
-
// the
|
|
1670
|
-
//
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1443
|
+
// Nudge on a genuinely-failed task result (mirrors the completed path and the sync
|
|
1444
|
+
// captureResult). INTERNAL_ERROR and an unknown category get the full nudge; a genuine
|
|
1445
|
+
// INVALID_INPUT gets the softer nudge; payment (402) is suppressed via the HTTP status and
|
|
1446
|
+
// AUTH / PERMISSION_APPROVAL_REQUIRED via NON_NUDGE_FAILURE_CATEGORIES. The 402 branch above
|
|
1447
|
+
// returns before reaching here, so this only sees non-payment failures.
|
|
1448
|
+
const failedResult = withReportProblemNudge({
|
|
1449
|
+
result: {
|
|
1450
|
+
content: [
|
|
1451
|
+
{
|
|
1452
|
+
type: 'text',
|
|
1453
|
+
text: userText,
|
|
1454
|
+
},
|
|
1455
|
+
],
|
|
1456
|
+
isError: true,
|
|
1457
|
+
internalToolStatus: toolStatus,
|
|
1458
|
+
},
|
|
1459
|
+
tools: this.tools,
|
|
1460
|
+
failingToolName: tool.name,
|
|
1461
|
+
failureCategory: callDiagnostics.failure_category,
|
|
1462
|
+
failureHttpStatus: callDiagnostics.failure_http_status,
|
|
1463
|
+
});
|
|
1464
|
+
await completeTask('failed', failedResult, toolStatus, callDiagnostics);
|
|
1684
1465
|
}
|
|
1685
1466
|
finally {
|
|
1686
1467
|
cancelWatcher.dispose();
|
|
1687
1468
|
}
|
|
1688
1469
|
}
|
|
1689
|
-
/*
|
|
1690
|
-
* Creates telemetry data for a tool call.
|
|
1691
|
-
*/
|
|
1692
|
-
async prepareTelemetryData(toolName, mcpSessionId, apifyToken) {
|
|
1693
|
-
var _a, _b, _c, _d, _e;
|
|
1694
|
-
if (!this.telemetryEnabled) {
|
|
1695
|
-
return { telemetryData: null, userId: null };
|
|
1696
|
-
}
|
|
1697
|
-
// Get userId from cache or fetch from API
|
|
1698
|
-
let userId = null;
|
|
1699
|
-
if (apifyToken) {
|
|
1700
|
-
const apifyClient = new ApifyClient({ token: apifyToken });
|
|
1701
|
-
({ userId } = await getUserInfoCached(apifyToken, apifyClient));
|
|
1702
|
-
log.debug('Telemetry: fetched userId', { userId, mcpSessionId });
|
|
1703
|
-
}
|
|
1704
|
-
const capabilities = (_b = (_a = this.options.initializeRequestData) === null || _a === void 0 ? void 0 : _a.params) === null || _b === void 0 ? void 0 : _b.capabilities;
|
|
1705
|
-
const params = (_c = this.options.initializeRequestData) === null || _c === void 0 ? void 0 : _c.params;
|
|
1706
|
-
const telemetryData = {
|
|
1707
|
-
app: 'mcp',
|
|
1708
|
-
app_version: getPackageVersion() || '',
|
|
1709
|
-
mcp_client_name: ((_d = params === null || params === void 0 ? void 0 : params.clientInfo) === null || _d === void 0 ? void 0 : _d.name) || '',
|
|
1710
|
-
mcp_client_version: ((_e = params === null || params === void 0 ? void 0 : params.clientInfo) === null || _e === void 0 ? void 0 : _e.version) || '',
|
|
1711
|
-
mcp_protocol_version: (params === null || params === void 0 ? void 0 : params.protocolVersion) || '',
|
|
1712
|
-
mcp_client_capabilities: capabilities || null,
|
|
1713
|
-
mcp_session_id: mcpSessionId || '',
|
|
1714
|
-
transport_type: this.options.transportType || '',
|
|
1715
|
-
tool_name: toolName,
|
|
1716
|
-
tool_status: TOOL_STATUS.SUCCEEDED, // Will be updated in finally
|
|
1717
|
-
tool_exec_time_ms: 0, // Will be calculated in finally
|
|
1718
|
-
};
|
|
1719
|
-
return { telemetryData, userId };
|
|
1720
|
-
}
|
|
1721
1470
|
/**
|
|
1722
1471
|
* Resolves widgets and determines which ones are ready to be served.
|
|
1723
1472
|
*/
|