↑ src/ · sideways: ../payments/AGENTS.md
The cross-file invariant: this directory is the published @apify/actors-mcp-server
surface — generic types only. No Apify-internal infrastructure (Redis, Mongo,
IAM) may leak in; the internal repo customizes behavior by swapping the SDK store
implementations, not by importing from here.
Two MCP protocol revisions are served, each by its own adapter:
- 2025-era stateful protocol (spec),
via the v1 SDK
@modelcontextprotocol/sdk—legacy_server.ts. - 2026-07-28 stateless revision (spec),
via the v2 SDK
@modelcontextprotocol/server—stateless_server.ts. Noinitializehandshake; every request carries a_metaenvelope with protocol version, client info, and capabilities.
server.ts—ActorsMcpServer, the shared facade for tools, server mode, services, widgets, payments, and telemetry. It constructs and delegates v1 work toLegacyMcpServer, and hands the stateless adapter a per-request snapshot viacreateRequestSnapshot.legacy_server.ts— package-private v1 SDK adapter for handlers, Tasks, errors, notifications, logging, and transport lifecycle. It reads shared state throughLegacyMcpServerHost.stateless_server.ts—createStatelessServer(host): the 2026-07-28 (v2 SDK) adapter, oneServerper request, reading shared state throughStatelessMcpServerHost. Servestools/*,resources/*andprompts/*; registers no Tasks (the SDK answers method-not-found) and declares nologging.client_context.ts— protocol-neutral client identity and capabilities.errors.ts— protocol-neutral domain errors mapped by each protocol adapter.tool_call_engine.ts— sharedtools/callorchestration.prepareToolCall()handles preparation;executeSyncToolCall()runs synchronous calls.client.ts—connectMCPClient(url, token): transport negotiation.proxy.ts— MCP-in-MCP:getMCPServerID(url),getProxyMCPServerToolName(url, toolName).actors.ts—getActorMCPServerPath(): parses an Actor'swebServerMcpPath.utils.ts—processParamsGetTools(): turns?actors=URL params into tools.tool_call_error_mapper.ts— shared tool-call error classification.tool_dispatch.ts— neutral dispatch for internal, Actor MCP, and Actor tools.tool_call_telemetry.ts— shared tool-call telemetry preparation and logging.task_execution.ts— legacy long-running task execution and status notifications.const.ts— the invariant constants below (the single source for these values).
- Facade → adapter, one direction only.
ActorsMcpServer(facade) constructs and delegates toLegacyMcpServer;createStatelessServerbuilds the stateless adapter per request from the same facade, which never constructs it. Each adapter reads shared state only through its own narrow host interface (LegacyMcpServerHost,StatelessMcpServerHost) and never imports the concrete facade class; the shared synchronous execution modules (tool_call_engine.ts,tool_dispatch.ts) take plain values and import noActorsMcpServer, v1RequestHandlerExtra, or v1McpError, and nothing shared imports either adapter. Keep it that way: the two adapters are siblings over one Apify core, not layers. - Per-request state lives in a snapshot, never on the facade. The stateless adapter resolves
'auto'mode and report-problem visibility from that request's_metaenvelope throughcreateRequestSnapshot, which writes no request-specific state back to the facade (the one instance field it touches is the identity-independent widget-resolution memo). Never resolve a stateless request by writing to the shared facade — concurrent requests would contaminate each other. - Tool names: capped + hash-deduped. Names are capped at
MAX_TOOL_NAME_LENGTH; over-length or colliding names get aTOOL_NAME_HASH_LENGTHhash suffix so the exposed set stays unique within the limit (Actor tools:../tools/actor_tool_naming.ts; proxied Actor-MCP tools:proxy.tsgetProxyMCPServerToolName). Never widen the cap — downstream clients depend on it. - Proxy server IDs are keyed by URL, not Actor ID.
getMCPServerID(url)issha256(url)sliced toSERVER_ID_LENGTH. One Actor can expose both an SSE and a streamable endpoint; keying by URL keeps those distinct. Keying by Actor ID would collapse them and cross transports. - Transport negotiation is streamable-first, SSE-fallback (
client.ts): try streamable HTTP, fall back to SSE on a protocol failure — but a connection timeout returnsnullwith no SSE fallback (a timeout means unreachable, not the wrong transport).getActorMCPServerPath()prioritizes the/mcpstreamable endpoint when an Actor lists several. - Two-phase tool loading (mode-agnostic
getActors()vs mode-dependentgetToolsForServerMode()) is documented once in../../DEVELOPMENT.md— read it before changing registration inserver.ts; not restated here. - Client data has two forms. Keep
options.initializeRequestDataunchanged for hosted session recovery. Use theMcpClientContextsnapshot for client gating, request origin, telemetry, resources, and scheduled tasks. Do not export the context from the package root or./internals.
pnpm run type-check
pnpm run test:unitDev server and manual MCP-client (mcpc) testing: see
../../DEVELOPMENT.md. After any change here run the root
Verification steps.
../../CONTRIBUTING.md— naming / coding standards (do not duplicate).../payments/AGENTS.md—CallToolRequestresolves payment context.