Problem
call-actor does two unrelated things under one tool name:
| Input shape |
Path |
Returns |
actor: "apify/rag-web-browser" |
Apify run via SDK |
Canonical RunResponse (waitSecs, storages, summary, nextStep) |
actor: "apify/actors-mcp-server:fetch-apify-docs" |
Pass-through to remote MCP tool |
Arbitrary remote content + isError |
#859 patches the immediate SDK-rejection bug by synthesizing a sentinel RunResponse, but runId: 'mcp-passthrough' / actorId: <serverName> / empty storages are dishonest values for a path that has no run. The schema fits, the semantics don't.
// src/tools/core/call_actor_common.ts (after #859)
return {
content: result.content,
isError: isErrorFromRemote,
structuredContent: {
runId: 'mcp-passthrough', // ← sentinel, no Apify run exists
actorId: baseActorName, // ← actor ID, not "actor that produced this content"
actorName: baseActorName,
status: isErrorFromRemote ? 'FAILED' : 'SUCCEEDED',
storages: {}, // ← always empty
summary: `Called MCP tool '${mcpToolName}' on '${baseActorName}'.`,
nextStep: 'Response content carries the remote MCP tool result; no Apify run was started.',
},
};
LLMs reading tools/list see one call-actor with one outputSchema, but the actual return shape depends on whether the actor string contains a :. Side effects: waitSecs is silently ignored on pass-through, taskMode reaches the path but is meaningless, telemetry mixes run starts with MCP tool calls.
Fix
Pick one direction:
Option A — separate tool: keep call-actor for Apify runs only; add call-mcp-actor-tool (or similar) with its own input schema (actor, tool, input), its own output schema mirroring the remote MCP CallToolResult shape, and no waitSecs / taskMode. Tool descriptions disambiguate, both can be auto-injected when an MCP-server Actor is in scope.
Option B — code mode as the default: position code mode (where the model writes JS/Python that calls apifyClient directly) as the recommended path for MCP-server Actor tools, deprecate the pass-through syntax over one minor cycle, remove it.
Out of scope
Problem
call-actordoes two unrelated things under one tool name:actor: "apify/rag-web-browser"RunResponse(waitSecs, storages, summary, nextStep)actor: "apify/actors-mcp-server:fetch-apify-docs"content+isError#859 patches the immediate SDK-rejection bug by synthesizing a sentinel
RunResponse, butrunId: 'mcp-passthrough'/actorId: <serverName>/ emptystoragesare dishonest values for a path that has no run. The schema fits, the semantics don't.LLMs reading
tools/listsee onecall-actorwith oneoutputSchema, but the actual return shape depends on whether theactorstring contains a:. Side effects:waitSecsis silently ignored on pass-through,taskModereaches the path but is meaningless, telemetry mixes run starts with MCP tool calls.Fix
Pick one direction:
Option A — separate tool: keep
call-actorfor Apify runs only; addcall-mcp-actor-tool(or similar) with its own input schema (actor,tool,input), its own output schema mirroring the remote MCPCallToolResultshape, and nowaitSecs/taskMode. Tool descriptions disambiguate, both can be auto-injected when an MCP-server Actor is in scope.Option B — code mode as the default: position code mode (where the model writes JS/Python that calls
apifyClientdirectly) as the recommended path for MCP-server Actor tools, deprecate the pass-through syntax over one minor cycle, remove it.Out of scope
actor:"X:Y""MCP server but no:tool" guard — already handled byCALL_ACTOR_MCP_MISSING_TOOL_NAME_MSG.