Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
127 commits
Select commit Hold shift + click to select a range
6ba95b7
feat: Add get-actor-version tool in a new source category
DaveHanns Sep 25, 2026
f45d08d
fix: Harden get-actor-version zip reading, inline files, and hints
DaveHanns Sep 25, 2026
6851981
docs: Link the current API pages in get-actor-version
DaveHanns Sep 25, 2026
a27395e
feat: Add create-actor and update-actor-version tools
DaveHanns Sep 25, 2026
534127d
fix: Harden create-actor and update-actor-version writes
DaveHanns Sep 25, 2026
f398803
refactor: Share file input parsing between source write tools
DaveHanns Sep 25, 2026
83951fd
docs: Mark where get-actor-version reads a whole source
DaveHanns Sep 25, 2026
8f297a1
Merge remote-tracking branch 'origin/feat/get-actor-version' into fea…
DaveHanns Sep 25, 2026
e2d7e8a
docs: Mark where update-actor-version writes a whole source
DaveHanns Sep 25, 2026
b9df355
refactor: Refuse zip-stored versions in get-actor-version and simplif…
DaveHanns Sep 27, 2026
24f9792
Merge branch 'feat/get-actor-version' into feat/update-actor-version
DaveHanns Sep 27, 2026
aa2d8fe
refactor: Share the manifest builder and align the zip refusal
DaveHanns Sep 27, 2026
8f2f928
refactor: Tidy get-actor-version after dropping zip reading
DaveHanns Sep 28, 2026
fa1c1b9
Merge branch 'feat/get-actor-version' into feat/update-actor-version
DaveHanns Sep 28, 2026
2f77449
refactor: Decode stored source entries in one place
DaveHanns Sep 28, 2026
4daa78b
refactor: Simplify get-actor-version
DaveHanns Sep 29, 2026
1b28077
refactor: Address review of the get-actor-version simplification
DaveHanns Sep 29, 2026
46a53c3
Merge branch 'feat/get-actor-version' into feat/update-actor-version
DaveHanns Sep 29, 2026
1c7bcab
refactor: Keep the write tools' source helpers after the get-actor-ve…
DaveHanns Sep 29, 2026
0519a95
refactor: Simplify create-actor and update-actor-version
DaveHanns Sep 29, 2026
639cffc
refactor: Address review of the create-actor and update-actor-version…
DaveHanns Sep 29, 2026
e489d3c
Merge remote-tracking branch 'origin/master' into feat/get-actor-version
DaveHanns Sep 29, 2026
0ecc220
Merge branch 'feat/get-actor-version' into feat/update-actor-version
DaveHanns Sep 29, 2026
b9e02ca
refactor: Report only API errors from the build start as buildError
DaveHanns Sep 29, 2026
f3da605
Merge remote-tracking branch 'origin/master' into feat/get-actor-version
DaveHanns Sep 30, 2026
5650f16
Merge branch 'feat/get-actor-version' into feat/update-actor-version
DaveHanns Sep 30, 2026
48f8db4
test: Cover read-only calls, env var leaks, and sub-resources in get-…
DaveHanns Sep 30, 2026
749d1f3
test: Cover exact content, line range edges, and the byte limit in ge…
DaveHanns Sep 30, 2026
8b22d9d
test: Cover hashes, revisions, path order, and URL redaction in get-a…
DaveHanns Sep 30, 2026
20b4796
test: Cover the text block, edge-case files, and the 256 KiB limit in…
DaveHanns Sep 30, 2026
c47b869
Merge branch 'feat/get-actor-version' into feat/update-actor-version
DaveHanns Sep 30, 2026
86fac27
test: Expect the shared refusal wording in the new get-actor-version …
DaveHanns Sep 30, 2026
11a6247
test: Cover untouched files, the PUT body, operation order, and refus…
DaveHanns Sep 30, 2026
44a3b59
test: Cover the POST body, file mapping, cancellation, and autoBuild …
DaveHanns Sep 30, 2026
9a88b74
test: Cover empty files in the PUT, multi-edit, whole-value hash chec…
DaveHanns Sep 30, 2026
49497d5
test: Cover a cancel during the build start in update-actor-version
DaveHanns Sep 30, 2026
704f329
refactor: Address review nits in get-actor-version
DaveHanns Oct 1, 2026
41285bc
fix: Name left-out and missing files in the get-actor-version summary
DaveHanns Oct 1, 2026
bae722b
fix: Normalize requested paths in get-actor-version
DaveHanns Oct 1, 2026
1b372e9
fix: Explain in plain words why get-actor-version refuses a version
DaveHanns Oct 1, 2026
0a183a5
test: Merge near-duplicate get-actor-version tests into it.each
DaveHanns Oct 1, 2026
5f6f54e
Merge branch 'feat/get-actor-version' into feat/update-actor-version
DaveHanns Oct 1, 2026
95785dd
refactor: Read the eager file content in update-actor-version
DaveHanns Oct 1, 2026
ea9a5e7
test: Expect the new refusal wording in update-actor-version
DaveHanns Oct 1, 2026
b25a51a
style: Format the refusal tests in update-actor-version
DaveHanns Oct 1, 2026
ea000a6
Merge remote-tracking branch 'origin/master' into feat/update-actor-v…
DaveHanns Oct 5, 2026
1ac3793
test: Cover stored names, the input schema, and more cases of update-…
DaveHanns Oct 5, 2026
379e254
test: Cover the input schema, Console link, and non-ASCII paths of cr…
DaveHanns Oct 5, 2026
72413ad
test: Check that get-actor-version names the Actor by its document
DaveHanns Oct 5, 2026
824b2a4
fix: Count overlapping matches of an oldText as several
DaveHanns Oct 5, 2026
5e479d1
fix: Refuse to write a file outside the Actor root or at a folder path
DaveHanns Oct 5, 2026
4c310fa
fix: Refuse a new file whose path collides with a file or folder
DaveHanns Oct 5, 2026
22d7709
fix: Refuse two files at the same path in create-actor
DaveHanns Oct 5, 2026
43823d7
fix: Check the expectedHash an edit operation carries
DaveHanns Oct 5, 2026
2ff9fb1
fix: Refuse content the stored file would not hold as sent
DaveHanns Oct 5, 2026
f82051c
fix: Report any failed build start after a write as buildError
DaveHanns Oct 5, 2026
94c660a
fix: Keep line endings CRLF when an LF edit changes a CRLF file
DaveHanns Oct 5, 2026
cfd8a4d
fix: Say that an Actor has no versions instead of listing none
DaveHanns Oct 5, 2026
32eed8c
test: Run create-actor, get-actor-version, and update-actor-version a…
DaveHanns Oct 5, 2026
dec4503
test: Add live integration cases for create-actor and update-actor-ve…
DaveHanns Oct 5, 2026
f4768c3
test: Remove the second live case for the source tools from actors.ca…
DaveHanns Oct 5, 2026
6cf8057
test: Seed entries without format or content in the live update case
DaveHanns Oct 5, 2026
b8cdc79
test: Check that update-actor-version names no deleted empty file as …
DaveHanns Oct 5, 2026
2a51ce4
test: Check that a folder stored under an unnormalized name blocks a …
DaveHanns Oct 5, 2026
3668c69
fix: Say that a retried create-actor POST may have created the Actor
DaveHanns Oct 5, 2026
f06edcd
Revert "fix: Say that an Actor has no versions instead of listing none"
DaveHanns Oct 7, 2026
7387891
Merge remote-tracking branch 'origin/master' into feat/update-actor-v…
DaveHanns Oct 7, 2026
986367d
fix: Name get-actor-version in the retried create-actor message only …
DaveHanns Oct 7, 2026
8eee7c8
fix: Say to build the version before running it, not to run the files
DaveHanns Oct 7, 2026
9cc64d9
fix: Say create-actor uploads new Actor code, not publishes it
DaveHanns Oct 7, 2026
88a8173
docs: Make the Actor source feature line one plain list
DaveHanns Oct 7, 2026
2774c2c
Merge remote-tracking branch 'origin/master' into feat/update-actor-v…
DaveHanns Oct 12, 2026
90737fb
fix: Limit update-actor-version to 100 operations and 100 edits per call
DaveHanns Oct 12, 2026
cf835c2
fix: Keep file content out of the logged arguments of the source tools
DaveHanns Oct 12, 2026
163d817
fix: Refuse control characters in source paths and encode the revisio…
DaveHanns Oct 12, 2026
9930b52
fix: Start no build after a source write when the call is cancelled d…
DaveHanns Oct 12, 2026
a2ac5fd
fix: Search an oldText over 250 characters in time linear in the file
DaveHanns Oct 12, 2026
6817b57
fix: Check create-actor file paths in time linear in the file count
DaveHanns Oct 12, 2026
300b1d1
Merge remote-tracking branch 'origin/master' into feat/update-actor-v…
DaveHanns Oct 16, 2026
0afdf69
fix: Cap update-actor-version at 20 edits per call
DaveHanns Oct 16, 2026
eced09b
fix: State facts instead of next calls in source tool texts
DaveHanns Oct 16, 2026
43e34e4
fix: Mark create-actor and update-actor-version as open-world
DaveHanns Oct 16, 2026
03a361a
fix: Take the create-actor version from .actor/actor.json
DaveHanns Oct 16, 2026
873e03e
refactor: Start the post-write build with the client directly
DaveHanns Oct 16, 2026
09812ec
refactor: Drop the post-write cancel checks in source write tools
DaveHanns Oct 16, 2026
f7445a7
test: Bound the 20-edit timing test at 6 s and say what it guards
DaveHanns Oct 16, 2026
a3d330a
fix: Read an empty actor.json version as none in create-actor
DaveHanns Oct 16, 2026
3fd4b24
fix: State what the update-actor-version revision is for as a fact
DaveHanns Oct 16, 2026
cb07786
fix: Take a null expectedHash, expectedRevision, or versionNumber as …
DaveHanns Oct 16, 2026
5c1d8ca
fix: Refuse a source path with a lone UTF-16 surrogate
DaveHanns Oct 16, 2026
be2dc8c
fix: Resolve a bare Actor name in the source tools
DaveHanns Oct 16, 2026
fb1c7c9
test: Read the only version for a null versionNumber in get-actor-ver…
DaveHanns Oct 16, 2026
9dc70f8
Merge remote-tracking branch 'origin/master' into feat/update-actor-v…
DaveHanns Oct 16, 2026
90f3108
test: Expect the taken-name error without the input hint master dropped
DaveHanns Oct 16, 2026
0d5b6f6
refactor: Move the ID-or-name resolver out of the task helpers
DaveHanns Oct 16, 2026
29ed1be
refactor: Import the Actor resolver from the shared resource ID module
DaveHanns Oct 16, 2026
8092217
refactor!: Rename the source tool category to versions
DaveHanns Oct 16, 2026
978d3bf
refactor: Move the versions tools to a versions folder
DaveHanns Oct 16, 2026
abfcc2b
fix: Start no build when update-actor-version changes nothing
DaveHanns Oct 16, 2026
084952f
fix: Say only that no build was started when update-actor-version cha…
DaveHanns Oct 16, 2026
7b6d9af
fix: Resolve a bare Actor name in build-actor
DaveHanns Oct 16, 2026
cf4e7de
fix: Resolve a bare Actor name in the build and run list filters
DaveHanns Oct 16, 2026
74b20dd
fix: Resolve a bare Actor name in call-actor and fetch-actor-details
DaveHanns Oct 16, 2026
cffc456
fix: Say a found own Actor has no successful build yet
DaveHanns Oct 16, 2026
ddb4b4e
fix: Say a bare Actor name never means another account's Actor
DaveHanns Oct 16, 2026
c49a92d
docs: Scope the bare-name resolution notes to sessions without payment
DaveHanns Oct 16, 2026
29a14b9
fix: Refuse an update-actor-version edit that grows a file past 3 MiB
DaveHanns Oct 19, 2026
12c6a0b
fix: Read the version of an .actor/actor.json that starts with a byte…
DaveHanns Oct 19, 2026
81d0b5e
fix: Say a 5xx or unanswered version PUT may have saved the change
DaveHanns Oct 19, 2026
68f10af
test: Check that update-actor-version refuses a NUL path in a single …
DaveHanns Oct 19, 2026
0c7e960
test: Check the build hint of update-actor-version in a session witho…
DaveHanns Oct 19, 2026
ce891f4
test: Abort the build of the live create-actor case in finally
DaveHanns Oct 19, 2026
bc9e2c3
fix: Refuse a backslash in the path of a written file
DaveHanns Oct 19, 2026
4984455
fix: Report a sub-resource reached by extra path segments in build-ac…
DaveHanns Oct 19, 2026
49abeec
refactor: Extract redactEdits from redactSourceToolArgs
DaveHanns Oct 19, 2026
b2af38b
refactor: Split applyOperation of update-actor-version by operation type
DaveHanns Oct 19, 2026
e2527c0
refactor: Extract startBuildAfterWrite from respondAfterWrite
DaveHanns Oct 19, 2026
61a58c4
refactor: Start the create-actor catch with a guard for a retried nam…
DaveHanns Oct 19, 2026
0fc3c09
refactor: Check file and folder collisions with one path index in bot…
DaveHanns Oct 19, 2026
9306c37
Merge branch 'feat/update-actor-version' into feat/bare-actor-names
DaveHanns Oct 19, 2026
bd0daaf
fix: Say a version PUT that failed after a retry may have saved the c…
DaveHanns Oct 19, 2026
766caf8
fix: Say no build was started after an unconfirmed save with autoBuild
DaveHanns Oct 19, 2026
9ee727c
test: Check that update-actor-version refuses an oversized edit befor…
DaveHanns Oct 19, 2026
a2f8494
refactor: Write the byte order mark in create-actor as an escape
DaveHanns Oct 19, 2026
4f5ff55
fix: Count only the LFs of newText that become CRLF in the length che…
DaveHanns Oct 19, 2026
e24a7e1
test: Log a failed build abort of the live create-actor case instead …
DaveHanns Oct 19, 2026
4b0c5ee
Merge branch 'feat/update-actor-version' into feat/bare-actor-names
DaveHanns Oct 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ After `pnpm run build`, run `mcpc` (no args) to check sessions: if `@stdio` (def
- **Integration tests**: `pnpm run test:integration` (needs build + `APIFY_TOKEN`, humans only).
- **Conformance tests**: `pnpm run test:conformance` (needs `APIFY_TOKEN`) builds and runs both protocol eras locally. `_integration_tests.yaml` calls `_conformance_tests.yaml` for the same `2026-07-28` and `2025-11-25` coverage in CI. Both eras always run; the command exits with the first non-zero code. Scenarios excluded from each era's gate are listed with reasons in `scripts/conformance_expected_failures_2026_07_28.yaml` and `scripts/conformance_expected_failures_2025_11_25.yaml`.
- **Package manager**: this repo uses **pnpm 11+**. `devEngines.packageManager` is pinned with `onFail: "error"`, so npm / yarn refuse to run inside the checkout — use `pnpm install` only.
- `tests/integration/suite.ts` wires the `stdio`, `2025-11-25` (streamable HTTP) and `2026-07-28` (stateless HTTP) transport dimensions into one shared suite. Add new integration cases to the matching capability array in `tests/test_kit/cases/*.cases.ts` (registration, tools, actors, apps, tasks, storage, schedules, payments) — each is a plain `Case[]` (`{ name, isDeploymentTest, run, ... }`), registered via `registerCases(name, cases, ctx)`. Mark a case `isDeploymentTest: true` only if it's worth running against `apify-mcp-server-internal`'s live staging and production deploys — the flag doesn't just share the case, it makes internal execute it for real on every release (see [DEVELOPMENT.md](./DEVELOPMENT.md#test-organization-across-repos)). Shared assertion helpers live in `tests/test_kit/helpers.ts`. Cases that share expensive setup (e.g. one seeded Actor run, see `storage.cases.ts`'s `normalModeRunFixture`) do so via `ctx.getFixture(fixture)`, memoized once per transport dimension — not a vitest `beforeAll`, so any subset of cases can share it while staying flat `Case` objects.
- `tests/integration/suite.ts` wires the `stdio`, `2025-11-25` (streamable HTTP) and `2026-07-28` (stateless HTTP) transport dimensions into one shared suite. Add new integration cases to the matching capability array in `tests/test_kit/cases/*.cases.ts` (registration, tools, actors, apps, tasks, storage, schedules, versions, payments) — each is a plain `Case[]` (`{ name, isDeploymentTest, run, ... }`), registered via `registerCases(name, cases, ctx)`. Mark a case `isDeploymentTest: true` only if it's worth running against `apify-mcp-server-internal`'s live staging and production deploys — the flag doesn't just share the case, it makes internal execute it for real on every release (see [DEVELOPMENT.md](./DEVELOPMENT.md#test-organization-across-repos)). Shared assertion helpers live in `tests/test_kit/helpers.ts`. Cases that share expensive setup (e.g. one seeded Actor run, see `storage.cases.ts`'s `normalModeRunFixture`) do so via `ctx.getFixture(fixture)`, memoized once per transport dimension — not a vitest `beforeAll`, so any subset of cases can share it while staying flat `Case` objects.
- Follow existing test patterns (names, structure) — check neighboring files.
- **Test naming**: `describe('fnName()')`, plain-verb `it()` names (no `should` prefix). Group with nested `describe()` per method when a factory/class exposes several.

Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -249,7 +249,7 @@ Here are some special MCP operations and how the Apify MCP Server supports them:
- **Actor tasks**: Create, inspect, and update your saved Actor tasks, and publish or unpublish their public landing pages.
- **Schedules**: Create, inspect, update, and delete schedules that run your Actors and tasks automatically.
- **Builds**: Build an Actor version, list builds, check the status of a build, and retrieve its build log.
- **Actor source**: Read an Actor version's files, with a hash for each file and a revision for the whole set.
- **Actor versions**: Read an Actor version's files with a hash for each file and a revision for the whole set, create an Actor from files, and change a version's files with checks that catch changes made since your last read.
- **Apify API**: Search the Apify API reference, inspect an operation, and send requests to the API to read or change data.

### Overview of available tools
Expand Down Expand Up @@ -298,7 +298,9 @@ Legend for the **Enabled by default** column:
| `get-actor-build-log` | builds | Retrieve the logs for a specific Actor build. | |
| `get-actor-build-list` | builds | List the account's builds, or one Actor's, in every status, newest first. | |
| `build-actor` | builds | Build an Actor version and wait a bounded time for the build to finish. | |
| `get-actor-version` | source | Get an Actor version's file listing with hashes and the content of the files you ask for. | |
| `get-actor-version` | versions | Get an Actor version's file listing with hashes and the content of the files you ask for. | |
| `create-actor` | versions | Create a private Actor in your account from files, optionally starting a build. | |
| `update-actor-version` | versions | Write, edit, or delete files of an Actor version in one all-or-nothing write. | |
| `apify-api-search` | api | Search the Apify API reference for operations by keywords. Returns each one's method, path, and docs page. | |
| `apify-api-details` | api | Get the parameters and request body schema of the Apify API operations on a path. | |
| `apify-api-read` | api | Send a GET request to an Apify API path, such as `actor-runs` or `/v2/actor-runs`, like `apify api` in the Apify CLI. Read-only: it refuses the `method` query parameter and a GET to the synchronous run endpoints, which starts a paid run. | |
Expand Down Expand Up @@ -424,6 +426,7 @@ The v2 configuration preserves backward compatibility with v1 usage. Notes:
- `call-actor` is now included by default via the `actors` category (additive change). To exclude it, specify an explicit `tools` list without `actors`.
- `tools=add-actor`, `tools=experimental`, and `tools=preview` are retired: they are ignored and load no tools. Use `tools=call-actor` (or the default `actors` category) instead.
- `tools=get-actor-log` is retired: the tool was renamed to `get-actor-run-log`. The old selector is ignored and loads no tools. Use `tools=get-actor-run-log` (or the `runs` category) instead.
- `tools=source` is retired: the category was renamed to `versions`. The old selector is ignored and loads no tools. Use `tools=versions` instead.

Existing URLs and commands using `?actors=...` or `--actors` continue to work unchanged.

Expand Down
12 changes: 8 additions & 4 deletions src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ export const HELPER_TOOLS = {
ACTOR_BUILD_LOG: 'get-actor-build-log',
ACTOR_BUILD_LIST_GET: 'get-actor-build-list',
ACTOR_CALL: 'call-actor',
ACTOR_CREATE: 'create-actor',
ACTOR_CALL_WIDGET: 'call-actor-widget',
ACTOR_DELETE: 'delete-actor',
ACTOR_GET_DETAILS: 'fetch-actor-details',
Expand All @@ -70,6 +71,7 @@ export const HELPER_TOOLS = {
ACTOR_TASK_PUBLISH: 'publish-actor-task',
ACTOR_TASK_UNPUBLISH: 'unpublish-actor-task',
ACTOR_VERSION_GET: 'get-actor-version',
ACTOR_VERSION_UPDATE: 'update-actor-version',
DATASET_GET: 'get-dataset',
DATASET_LIST_GET: 'get-dataset-list',
DATASET_GET_ITEMS: 'get-dataset-items',
Expand All @@ -96,16 +98,17 @@ export type HelperToolName = (typeof HELPER_TOOLS)[keyof typeof HELPER_TOOLS];

/**
* Retired tool selectors: `add-actor` and `experimental` (add-actor was deleted in the stateless
* migration), the deprecated `preview` pseudo-category, and `get-actor-log` (renamed to
* `get-actor-run-log`). They name neither a registry category nor a real tool anymore, so they
* resolve to nothing — never loaded, never treated as an Actor ID, never requiring a token by
* themselves.
* migration), the deprecated `preview` pseudo-category, `get-actor-log` (renamed to
* `get-actor-run-log`), and the `source` category (renamed to `versions`). They name neither a
* registry category nor a real tool anymore, so they resolve to nothing — never loaded, never
* treated as an Actor ID, never requiring a token by themselves.
*/
export const RETIRED_SELECTOR_NAMES: ReadonlySet<string> = new Set([
'add-actor',
'experimental',
'preview',
'get-actor-log',
'source',
]);

/**
Expand Down Expand Up @@ -283,6 +286,7 @@ export const APIFY_ERROR_TYPE_MEMORY_LIMIT_EXCEEDED = 'memory-limit-exceeded';
export const APIFY_ERROR_TYPE_CANNOT_START_ACTOR_RUNS = 'cannot-start-actor-runs';
export const APIFY_ERROR_TYPE_CANNOT_PUBLISH_ACTOR_TASK = 'cannot-publish-actor-task';
export const APIFY_ERROR_TYPE_INVALID_INPUT = 'invalid-input';
export const APIFY_ERROR_TYPE_ACTOR_NAME_NOT_UNIQUE = 'actor-name-not-unique';
export const APIFY_ERROR_TYPE_PAGE_NOT_FOUND = 'page-not-found';

// HTTP status codes
Expand Down
19 changes: 15 additions & 4 deletions src/tools/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,16 +32,20 @@ direct actor tools, `search-actors`, `fetch-actor-details`) is mode-agnostic.
- `tasks/` — Actor task create/get/update plus publish/unpublish of the task's public
landing page (`task_helpers.ts` holds the shared task response shape and the publication call).
- `schedules/` — schedule create/get/update/delete for Actors and tasks; `schedule_helpers.ts`
converts the flat action shape to the API shape and back, and reuses the id helpers from
converts the flat action shape to the API shape and back, and reuses `getTaskByIdOrName` from
`tasks/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 with `actorId`, in every status, newest
first, pointing at the newest failed one) and `build-actor` (start a build of one version and wait for it); `build_helpers.ts` holds the
allowlisted build result shape, the build start and wait calls (the wait reports progress), the
shared `waitSecs` field, 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.ts` holds the file shape, the manifest builder, the hash and
revision rules, and the text-or-base64 detection. Versions not stored as files (Git repository,
- `versions/`: `get-actor-version` (an Actor version's file manifest with hashes, revision, and
requested content), `create-actor` (a new Actor with one version in a single POST), and
`update-actor-version` (write, edit, and delete operations applied all-or-nothing to a fresh read,
stored with one version PUT that carries only the source keys). `source_files.ts` holds the file
shape, the manifest builder, the hash and revision rules, and the text-or-base64 detection;
`source_helpers.ts` holds the Actor and version lookup, the file input shape, and the response
after a write, which starts the build for autoBuild. 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
Expand Down Expand Up @@ -76,6 +80,13 @@ direct actor tools, `search-actors`, `fetch-actor-details`) is mode-agnostic.
- `docs/` — search and fetch Apify docs.
- `dev/` — the `report-problem` tool for reporting a problem with a tool or Actor.
- `widgets/` — the `*-widget` tool variants (apps mode only).
- `../utils/apify_resource_id.ts` holds the helpers that resolve an Actor, task, or schedule given by ID or
name, where a name alone means one of the caller's own. The task, schedule, and versions tools use them,
and so do `build-actor` and the `actorId` filter of `get-actor-build-list` and `get-actor-run-list`.
`call-actor` (and `call-actor-widget`, which shares its pre-execution) and `fetch-actor-details` go through
`getActorByIdOrName` in `../utils/actor.ts`. Outside payment sessions it turns a name alone into
`username/name` with an uncached read, so `~name` never keys `actorDefinitionCache`. Under a payment provider
it resolves nothing: every value is passed on as given, a literal `~name` too.

## Rules when editing here

Expand Down
56 changes: 46 additions & 10 deletions src/tools/actors/call_actor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,14 @@ import { withRemoteMcpClient } from '../../mcp/remote_tool_call.js';
import type { PaymentProvider } from '../../payments/types.js';
import type { ApifyToken, InternalToolArgs, ToolDescriptionContext, ToolEntry, ToolInputSchema } from '../../types.js';
import { ACTOR_TOOL_MODE, ALL_TOOLS_PRESENT, TOOL_TYPE } from '../../types.js';
import { getActorDefinitionCached, getActorToolResolutionCached } from '../../utils/actor.js';
import {
getActorByIdOrName,
getActorDefinitionCached,
getActorToolResolutionCached,
isOwnActorLookup,
OWN_ACTOR_NOT_FOUND_FACT,
ownActorWithoutBuildFact,
} from '../../utils/actor.js';
import { compileSchema } from '../../utils/ajv.js';
import {
ACTOR_RUN_LIMIT_MESSAGE,
Expand Down Expand Up @@ -137,7 +144,7 @@ export function buildCallActorDescription(ctx: ToolDescriptionContext = ALL_TOOL
...(hasTool(HELPER_TOOLS.STORE_SEARCH)
? [
'',
`If the actor name is not in "username/name" format, use ${HELPER_TOOLS.STORE_SEARCH} to resolve the correct Actor first.`,
`A name without a username means one of your own Actors, never another account's Actor; ${HELPER_TOOLS.STORE_SEARCH} returns the full "username/name" of Store Actors.`,
]
: []),
].join('\n');
Expand Down Expand Up @@ -188,17 +195,24 @@ function respondStandbyRejection(
* Canonical "Actor not found" answer, shared by the run path and the `actor:toolName` route so an
* unknown Actor gets the same response regardless of call shape.
*/
function respondActorNotFound(actorName: string, loadedToolNames: readonly string[]): ToolResponse {
function respondActorNotFound(
actorName: string,
loadedToolNames: readonly string[],
{ isOwnActorName = false }: { isOwnActorName?: boolean } = {},
): ToolResponse {
// See apify/apify-mcp-server#1296.
// Only search-actors: the Actor does not exist, so buildCallFailureRecoveryHint's second
// offer (get Actor details) would send the model to a lookup that fails the same way.
const recovery = loadedToolNames.includes(HELPER_TOOLS.STORE_SEARCH)
? `\nYou can search for available Actors using the tool: ${HELPER_TOOLS.STORE_SEARCH}.`
: '';
const reason = isOwnActorName
? OWN_ACTOR_NOT_FOUND_FACT
: 'Please verify Actor ID or name format (e.g., "username/name" like "apify/rag-web-browser") and ensure that the Actor exists.';
return respondUserError(
dedent`
Actor '${actorName}' was not found.
Please verify Actor ID or name format (e.g., "username/name" like "apify/rag-web-browser") and ensure that the Actor exists.
${reason}
` + recovery,
{ httpStatus: 404, detail: `Actor '${actorName}' was not found` },
);
Expand Down Expand Up @@ -308,7 +322,8 @@ export const callOptionsSchema = z.object({
/** Zod schema for call-actor arguments — shared between default and apps variants. */
export const callActorArgs = z.object({
actor: z.string().describe(dedent`
The Actor to call: its ID, or its name in the "username/name" form (e.g., "apify/rag-web-browser").
The Actor to call: its ID, its name (resolved against your own Actors), or its full name as username/name or username~name (e.g., "apify/rag-web-browser").
A name without a username always means one of your own Actors, never another account's Actor.

For MCP server Actors, use format "actorName:toolName" to call a specific tool (e.g., "username/name:tool-name").
`),
Expand Down Expand Up @@ -348,7 +363,8 @@ export type CallActorParsedArgs = z.infer<typeof callActorArgs>;
* generic payment-required short-circuit in the tool-call handler so the
* agent receives the precise reason instead of a generic 402.
*
* Uses `actorDefinitionCache` — one definition fetch on a cold cache.
* Uses `actorDefinitionCache` — one definition fetch on a cold cache. Checks the name as given: under a payment
* provider `callActorPreExecute` does not resolve a name alone either (`isOwnActorLookup`), so both read the same Actor.
*/
export async function checkPaymentProviderStandbyConflict(params: {
actorName: string;
Expand Down Expand Up @@ -572,18 +588,38 @@ export async function callActorPreExecute(
mcpToolName: string | undefined;
}
> {
const { args, apifyToken, mcpSessionId, loadedToolNames } = toolArgs;
const { args, apifyToken, apifyClient, paymentProvider, mcpSessionId, loadedToolNames } = toolArgs;
const parsedArgs = callActorArgs.parse(args);
const actorName = fixActorNameInputAndLog(parsedArgs.actor, { mcpSessionId, route: options.route });
const parsed: CallActorParsedArgs = { ...parsedArgs, actor: actorName };

const { baseActorName, mcpToolName } = resolveActorContext(parsed.actor);
const { baseActorName: requestedActorName, mcpToolName } = resolveActorContext(parsed.actor);

// For definition resolution we always use a token-based client; payment provider is only for actual Actor runs.
// Standby/MCP-server Actors under a third-party payment provider are rejected upstream by
// `checkPaymentProviderStandbyConflict` — see src/mcp/tool_call_engine.ts.
// `checkPaymentProviderStandbyConflict` — see src/mcp/tool_call_engine.ts. `apifyClient` reads `~name` only
// without a payment provider, when it is token-based too.
const apifyClientForDefinition = new ApifyClient({ token: apifyToken });
const resolution = await getActorToolResolutionCached(baseActorName, apifyClientForDefinition);
const lookup = await getActorByIdOrName({
actorIdOrName: requestedActorName,
apifyClient,
paymentProvider,
fetch: async (actorKey) => getActorToolResolutionCached(actorKey, apifyClientForDefinition),
});
if (lookup?.actor === null) {
return {
earlyResponse: respondUserError(ownActorWithoutBuildFact(lookup.actorKey), {
detail: 'own Actor has no default build',
}),
};
}
if (!lookup && isOwnActorLookup(requestedActorName, paymentProvider)) {
return { earlyResponse: respondActorNotFound(requestedActorName, loadedToolNames, { isOwnActorName: true }) };
}
// The run, the MCP tool call and their responses use the key the Actor was found under: a name alone became
// `username/name`, and `~name` must not reach the shared definition cache.
const baseActorName = lookup?.actorKey ?? requestedActorName;
const resolution = lookup?.actor ?? null;

if (resolution?.toolMode === ACTOR_TOOL_MODE.STANDBY_WITHOUT_MCP) {
return {
Expand Down
Loading
Loading