↑ src/ · sideways: ../mcp/AGENTS.md
The cross-file invariant: a tool is defined here as a Zod-validated entry, and the
same implementation serves both server modes — only *-widget tools differ
between default and apps mode. Every non-widget tool (call-actor, get-actor-run,
direct actor tools, search-actors, fetch-actor-details) is mode-agnostic.
registry.ts— tool categories and the tools in each (index.tsre-exports them), plus tools in no category (ALL_WIDGET_TOOLS,UNCATEGORIZED_TOOLS).structured_output_schemas.ts— shared JSON-schema definitions for structured output across tools.utils.ts— shared tool helpers (schema property shaping, AJV compile).- Tool implementations are grouped by domain, each registered through
registry.ts:actors/— search, details, call, the list of the account's Actors (get-actor-list), delete (delete_actor.ts), the actor-tools factory, the direct actor-tool executor (actor_executor.ts),actor_definition.ts(fetches and prunes an Actor's definition,getActorDefinition),actor_tool_contract.ts(Actor-specific presentation policies plus direct-tool input restrictions and defaults, shared by schema generation, the call engine's denied-input check and execution; generic calls skip direct-only restrictions and defaults), andactor_run_response.ts— the one canonical run shapecall-actorandget-actor-runshare across sync, task and wait-timeout modes: storage IDs plus asummary(past) /nextStep(one primary action) pair, never inline dataset items or KV bodies — except the reservedTIPkey, inlined astipon terminal RAG Web Browser runs only.runs/— get/abort runs, run logs, run list.storage/— dataset and key-value-store tools plusstorage_helpers.ts.tasks/— Actor task create/get/update plus publish/unpublish of the task's public landing page (task_helpers.tsholds the shared task response shape and the publication call).schedules/— schedule create/get/update/delete for Actors and tasks;schedule_helpers.tsconverts the flat action shape to the API shape and back, and reuses the id helpers fromtasks/task_helpers.ts.builds/—get-actor-build(build status),get-actor-build-log(build log tail),get-actor-build-list(the account's builds, or one Actor's withactorId, in every status, newest first, pointing at the newest failed one) andbuild-actor(start a build of one version and wait for it);build_helpers.tsholds the allowlisted build result shape, the build start and wait calls (the wait reports progress), the sharedwaitSecsfield, the shared build response and the by-status next-step text.source/:get-actor-version(an Actor version's file manifest with hashes, revision, and requested content).source_files.tsholds the file shape, the manifest builder, the hash and revision rules, and the text-or-base64 detection. Versions not stored as files (Git repository, gist, or zip) are refused.api/— the generic Apify API tools: search the operations of the published OpenAPI spec, get the operations on a path, and send a GET, or a POST, PUT, PATCH, or DELETE, to a path. The write tool is in no category (UNCATEGORIZED_TOOLS):tools=apiserves the other three, and a session gets the write tool only by naming it. The call tools are proxies to the API, likeapify apiin the Apify CLI. They refuse whatAPI_BLOCK_RULESinapify_api_blocklist.tslists: themethodquery parameter, which the API takes as the HTTP method, and a GET to the synchronous run endpoints of Actors and tasks, which starts a paid run (the write tool still sends the POST). So the read tool is annotated read-only.callApichecks every call (validateApiBlocklist), fail-closed, as the API reads the request URL, and refuses a path whose dot segments climb out of/v2/; the index leaves out each operation a rule matches (isEndpointBlocked), so search, details, the closest-path hints, and the write tool's method choice never offer it.apify_api_spec.tsbuilds the operation index from the spec (cached for a day); search and details use it, the write tool uses it to choose a method that was not given, and a call uses it otherwise only for hints, waiting at most a few seconds, so a failed download does not stop a call.apify_api_request.tsnormalizes the path as the CLI does (actors,v2/actors, and/v2/actorsare the same), sends it as written with the query added after any query string in it, and looks up a legacyactspath asactors, the prefix the spec lists. It asserts that the URL stays on the API origin, sends one request with no retries, masks the session token in the response, redacts theurlSigningSecretKeyandpasswordvalues in a JSON body, keeps a query in the path out of the error it throws, and adds the closest spec paths to apage-not-found404 (a wrong path), not to a missing record. The one capped request (sendApifyApiRequest) is in../apify_client.ts. The origin check (isApifyApiUri), the detection of a body overMAX_INLINE_BYTES(isMaxContentLengthAbort), the binary token mask (maskSessionToken), the secret redaction (redactJsonSecrets) andREDACTEDare the API resource's own, imported from../resources/api_resources.ts(../resources/AGENTS.md). A request failure is thrown astoPlainError(../utils/logging.ts), without the axios config. The details and call tools log their arguments throughredactApiCallArgs(redactArgs): an allowlist of path, method, query, and body, with the body and thetoken,signature, andwebhooksquery values redacted, and a query written into the path cut to?[REDACTED].docs/— search and fetch Apify docs.dev/— thereport-problemtool for reporting a problem with a tool or Actor.widgets/— the*-widgettool variants (apps mode only).
- Validate inputs with Zod; no ad-hoc shape checks. AJV + Zod already validate before a tool runs — don't re-check the same constraint inside the tool body.
- Reference tool names via the
HELPER_TOOLSas constobject, never hardcoded strings (exception: the integration suite,tests/integration/and the sharedtests/test_kit/cases and helpers, which hardcode them so a renamed tool fails). - Keep a new tool mode-agnostic unless it is genuinely a widget variant.
- Descriptions, result text, annotations and the tool set follow TOOL_GUIDELINES.md; the gating mechanics below implement its "name only what the session has" rule.
Storage tool description skeleton (storage/, all 8 tools): lead sentence stating what the tool
returns, then a disambiguation line naming the sibling tool(s) it's confused with via
${HELPER_TOOLS.X} (never a hardcoded name), proportional caveats, then USAGE: (one or more
bullets) and USAGE EXAMPLES: (one or more user_input: bullets). Match this shape when touching
these files.
Exception: the AUTO_INJECTED_TOOLS (../utils/tools_loader.ts) — get-actor-run,
get-dataset-items, get-key-value-store-record, abort-actor-run — land in sessions that never
loaded their own category, so their disambiguation line must describe what they return instead of
naming a sibling — a name the client never received in tools/list invites a call to a tool that
does not exist. Any other cross-tool reference must be gated per session: define a
buildDescription(ctx) on the entry (see ToolDescriptionContext in ../types.ts), wrap the
reference in ctx.hasTool(...), and set description to the ALL_TOOLS_PRESENT render. Rendering
happens once, at the tools/list boundary (getToolPublicFieldOnly in ../utils/tools.ts). Enforced
by tests/unit/tools.mode_contract.test.ts, which scans description only.
Input-schema field text (.describe() on a Zod field) reaches tools/list verbatim — nothing
renders it per session — so never name a tool there; put the guidance in buildDescription behind
hasTool. actors/actor_tools_factory.ts's waitSecs is the one exception: an Actor tool always
auto-injects the get-actor-run it names.
Result text (summary / nextStep in content[1]) has no hasTool, but it does have a per-session
gate: InternalToolArgs.loadedToolNames. A nextStep that names a sibling tool goes through
resolveSiblingNextStep in storage/storage_helpers.ts. Name a tool there only through that gate,
or when it is the calling tool itself (the "call it again with the
next offset" pagination hint); otherwise leave the cross-tool guidance to the gated description.
An AUTO_INJECTED_TOOLS member is no exception — the injection is conditional on call-actor, an
Actor tool, or get-actor-run being loaded, so a session that loaded only abort-actor-run gets
none of them. The task and schedule tools name no tool, enforced by
tests/unit/tools.actor_task_crud.test.ts and tests/unit/tools.schedule_crud.test.ts; result text
elsewhere predates the gate, and suggestTool is the pattern to fix it with. Grep
HELPER_TOOLS outside buildDescription for the current set rather than trusting a list here.
Result text is a second surface, and hasTool does not reach it. A tool name in a response
body (summary, nextStep, instructions, an error's recovery sentence) is built while the tool
runs, not at the tools/list boundary, so nothing gates it for you and tools.mode_contract.test.ts
— which renders descriptions only — cannot see it. Gate it on toolArgs.loadedToolNames
(InternalToolArgs in ../types.ts), the names the session was actually served: see
suggestTool in storage/storage_helpers.ts and the recovery hints in actors/call_actor.ts,
actors/fetch_actor_details.ts and actors/search_actors.ts. Two rules when the name drops out:
never leave a dead end — if the sentence is the recovery path, replace it rather than omit it
(storage_helpers.ts substitutes "Inspect the returned items directly") — and gate in place, so a
session holding every tool gets byte-identical text and gating a hint is never also a rewording
(call_actor.ts and search_actors.ts were gated that way; the reworded sentence in
fetch_actor_details.ts is a deliberate wording change that rode along with its gate, not the
pattern to copy). Each gate needs its own test; there is no sweeping guard for this surface.
- Tool-name cap + hash dedupe, transport:
../mcp/AGENTS.md. - Two-phase tool loading:
../../DEVELOPMENT.md. - Naming / coding standards:
../../CONTRIBUTING.md.
After any change here run the root Verification steps.