# Defense-in-depth score: Apify MCP Server

**Repo:** https://github.com/apify/apify-mcp-server · **Commit:** `47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6` (0.17.3) · **Reviewed:** 2026-10-05
**What it is:** MCP server that lets AI agents search, run and read results from Apify Actors (web scrapers and automations) on the user's Apify account.
**Category:** AI Assistants
**Scored configuration:** Local stdio package (npx @apify/actors-mcp-server) with APIFY_TOKEN set and no --tools flag: actors and docs categories, the rag-web-browser and web-fetch Actor tools, auto-injected run/storage tools, telemetry on.
**Agent surface (default):** code execution yes · filesystem write no · network egress yes · external credentials yes · persistent memory no · untrusted input yes · third party extensions yes · sub agents no · external communication yes

## Score: 3.7 / 10.0 (Minimal)

| # | Criterion | S | C | D | B | Raw | Cap | Score | Confidence |
|---|---|---|---|---|---|---|---|---|---|
| C1 | Identity & least privilege | L0 | L1 | L0 | L2 | 0.17 | none | **0.17** | High |
| C2 | Approval gates | L1 | L2 | L2 | L1 | 0.38 | none | **0.38** | High |
| C3 | Tool & action scoping | L2 | L3 | L2 | L1 | 0.53 | none | **0.53** | High |
| C4 | Code-execution isolation | L2 | L3 | L3 | L2 | 0.62 | none | **0.62** | Medium |
| C5 | Untrusted input blast radius | L0 | L0 | L1 | L0 | 0.05 | C5-WORSTCASE | **0.05** | High |
| C6 | Memory, context & configuration integrity | SA | SA | SA | SA | 1.00 | none | **1.00** (SA) | High |
| C7 | Third-party extensions | L0 | L0 | L0 | L1 | 0.05 | C7-RCELOAD | **0.05** | Medium |
| C8 | Secrets & sensitive-data protection | L1 | L1 | L1 | L1 | 0.25 | none | **0.25** | High |
| C9 | Audit & traceability | L1 | L1 | L1 | L1 | 0.25 | none | **0.25** | High |
| C10 | Limits & kill switch | L2 | L2 | L1 | L1 | 0.40 | none | **0.40** | High |

Controls where a risk surface exists: 2.70 / 9.0 (30%); 1 criterion scored SA (surface absent).

In its default configuration this server lets the model start any Actor from Apify Store, including third-party code, on the user's account and bill, using one account-wide API token, and read the web through Actor tools in the same session. Argument checking, URL and origin gates and tool annotations are careful, and nothing runs on the local machine, but there is no allowlist of Actors, no spend ceiling the operator sets, and no local record of tool calls. Deploy it with an explicit --tools list and a scoped Apify token.

## Critical gaps
- A default session can read untrusted web content, read the account's data, send it to any URL through a fetch Actor and start irreversible paid runs, with nothing in the server requiring a human. (ASI01, T6, LLM01; C5). Evidence: [src/const.ts:162-164](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/const.ts#L162-L164); [src/tools/actors/call_actor.ts:670](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/call_actor.ts#L670)
- By default the model can run any third-party Actor from Apify Store on the user's account, unpinned and without consent. (ASI04, T17, LLM03; C7). Evidence: [src/tools/actors/call_actor.ts:145](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/call_actor.ts#L145); [src/tools/registry.ts:97](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/registry.ts#L97)

## Criterion details

### C1 Identity & least privilege: 0.17 (high confidence)

The local server runs every tool with a single Apify API token taken from the APIFY_TOKEN environment variable or, failing that, from the Apify CLI login file in the home directory. Nothing in the server narrows that token per tool or checks a request against a policy before it is used, so by default every tool acts with the full authority of the user's Apify account. The one exception is the opt-in delete-actor tool, which checks that the Actor belongs to the token's account and is not public. Users can hand the server a scoped token from Apify Console, but the server neither asks for one nor behaves differently with one.

- **S L0:** The server uses whatever token the environment or the Apify CLI login file provides, which by default is the user's account-wide API token. Evidence: [src/stdio.ts:151](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/stdio.ts#L151); [src/stdio.ts:68-73](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/stdio.ts#L68-L73) (verified)
  - *To reach the next level:* No dedicated or narrowed identity: the server does not request or require a scoped token or separate read and write credentials.
- **C L1:** Every tool and every proxied Actor MCP call uses the same session token; only delete-actor checks ownership before acting. Evidence: [src/stdio.ts:198](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/stdio.ts#L198); [src/tools/actors/delete_actor.ts:100-103](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/delete_actor.ts#L100-L103); [src/mcp/client.ts:105-112](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/mcp/client.ts#L105-L112) (verified)
  - *To reach the next level:* No authorization layer that every tool path passes through; per-request checks exist only inside delete-actor.
- **D L0:** The documented setup passes the account API token, and the fallback reads the CLI login token, so the default install runs with owner privilege on the Apify account. Evidence: [src/stdio.ts:150-151](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/stdio.ts#L150-L151); [manifest.json:59](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/manifest.json#L59) (verified)
  - *To reach the next level:* The default does not use a read-only or minimal token; least privilege requires the user to create a scoped token by hand.
- **B L2:** A hijacked session can do anything the Apify account can: start paid runs, read and write its storages and, with opt-in tools, delete Actors and change schedules and tasks; the reach is one system, Apify. Evidence: [src/tools/registry.ts:60-79](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/registry.ts#L60-L79); [src/tools/actors/call_actor.ts:670](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/call_actor.ts#L670) (verified)
  - *To reach the next level:* Credentials are not scoped to one project or mostly read; writes include spending and destructive account changes.
- **Cap:** none
- **Notes:** The hosted server at mcp.apify.com (OAuth, per-request tokens) runs from a private repository and was not scored.

### C2 Approval gates: 0.38 (high confidence)

As a tool server it leaves approval to the MCP host, and gives the host risk hints: every built-in tool declares read-only, destructive and open-world hints, and the tool that starts Actor runs is marked destructive. A few opt-in tools that overwrite or publish settings are marked non-destructive, and tools proxied from Actor MCP servers carry whatever hints the remote server declares. There is no dry-run or preview and no confirmation step the server enforces itself. The default consequential actions are paid Actor runs, which cannot be undone and whose spend cap is optional and chosen by the model.

- **S L1:** Read and write tools are separate and all carry annotations, but update-schedule, update-actor-task and publish-actor-task declare destructiveHint false although they overwrite or publish existing configuration. Evidence: [src/tools/actors/call_actor.ts:735-736](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/call_actor.ts#L735-L736); [src/tools/schedules/update_schedule.ts:76](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/schedules/update_schedule.ts#L76); [src/tools/tasks/publish_actor_task.ts:57](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/tasks/publish_actor_task.ts#L57) (verified)
  - *To reach the next level:* Hints are not accurate on every mutating tool, and there is no preview or dry-run for destructive operations.
- **C L2:** Every built-in tool and every Actor tool carries server-written hints, but tools proxied from Actor MCP servers pass the remote server's own annotations through unchanged. Evidence: [src/tools/actors/actor_tools_factory.ts:179-184](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/actor_tools_factory.ts#L179-L184); [src/mcp/proxy.ts:74](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/mcp/proxy.ts#L74) (verified)
  - *To reach the next level:* Proxied MCP tools are not given server-determined hints, so their risk signal depends on a third party.
- **D L2:** Hints are always sent and cannot be turned off, and the riskiest account tools (delete-actor, schedules, tasks, builds) are not in the default tool set. Evidence: [src/tools/registry.ts:97](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/registry.ts#L97); [src/tools/registry.ts:108-112](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/registry.ts#L108-L112) (verified)
  - *To reach the next level:* The default set still includes the generic call-actor tool, and nothing the server enforces stands in for a host that ignores hints.
- **B L1:** A wrongly approved call-actor starts a paid run of any Actor with model-chosen memory and timeout; the charge cap is optional and set by the model, and there is no preview or undo. Evidence: [src/tools/actors/call_actor.ts:302-305](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/call_actor.ts#L302-L305); searched `rg -n -i -e 'dry.?run|elicit'` in `src` → 0 hits (no dry-run, preview or elicitation-based confirmation anywhere in the server) (verified)
  - *To reach the next level:* Consequential actions are mostly irreversible spend with no server-side preview, rollback or operator-set quantity bound.
- **Cap:** none

### C3 Tool & action scoping: 0.53 (high confidence)

Every tool call is validated against the tool's JSON schema in one central step before it runs, including schemas that come from Actors and proxied MCP servers. Several tools go further: the docs fetcher only accepts HTTPS URLs on the Apify and Crawlee documentation hosts, generic API reads are limited to the Apify API origin, Actor MCP endpoints are confined to the Actor's own standby origin, and delete-actor refuses Actors the user does not own. The default tool set, however, includes call-actor, which runs any Actor in Apify Store with free-form input, so the default reach is the whole Store and, through web-fetch Actors, any URL. Tool categories can be narrowed with --tools, but the default includes this generic tool.

- **S L2:** Arguments are schema-validated and narrow tools use parsed host and origin allowlists, but call-actor accepts any Actor name and free-form input with no allowlist of Actors or destinations. Evidence: [src/tools/actors/call_actor.ts:309-315](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/call_actor.ts#L309-L315); [src/tools/docs/fetch_apify_docs.ts:41-51](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/docs/fetch_apify_docs.ts#L41-L51); [src/resources/api_resources.ts:65-70](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/resources/api_resources.ts#L65-L70) (verified)
  - *To reach the next level:* No allowlist on which Actors may be started or which URLs and quantities their input may carry.
- **C L3:** The shared call path resolves only loaded tools and runs schema validation on every call, so built-in, Actor and proxied tools all pass the same validation layer. Evidence: [src/mcp/tool_call_engine.ts:155](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/mcp/tool_call_engine.ts#L155); [src/mcp/tool_call_engine.ts:223](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/mcp/tool_call_engine.ts#L223) (verified)
  - *To reach the next level:* The shared layer checks types only; allowlist policy lives in a few individual tools rather than in a central policy that new tools inherit.
- **D L2:** Tool categories are selectable and account-changing tools are off by default, but the default set includes call-actor, which can run any Store Actor. Evidence: [src/tools/registry.ts:61](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/registry.ts#L61); [src/tools/registry.ts:97](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/registry.ts#L97) (verified)
  - *To reach the next level:* The default tool set is not read-only; running arbitrary Actors needs no explicit enabling.
- **B L1:** Misuse reaches every public Actor and, through them, arbitrary web hosts, bounded only by per-call wait limits, a model-chosen memory size up to 32 GB and output-size caps; dataset reads have no maximum page size. Evidence: [src/tools/actors/call_actor.ts:280-283](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/call_actor.ts#L280-L283); [src/tools/storage/get_dataset_items.ts:37-42](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/storage/get_dataset_items.ts#L37-L42) (verified)
  - *To reach the next level:* Reach is not scoped to an operator-chosen set of Actors and is not bounded by operator-set quantities.
- **Cap:** none

### C4 Code-execution isolation: 0.62 (medium confidence)

The server never runs model-influenced code on the local machine: there is no shell, eval or subprocess call anywhere in its source. Code runs only as Apify Actor runs, which the server starts through the Apify API, so isolation is provided by Apify's cloud containers rather than by anything in this repository, and could not be verified here. Every execution path goes to that remote service and there is no local fallback. Inside a run, an Actor has unrestricted network access and the run's platform token, and the model chooses memory and timeout, so a misbehaving run can still spend money and reach the internet.

- **S L2:** Execution is delegated to remote Apify Actor runs started through the API; the strength of that container isolation lives in the Apify platform and cannot be confirmed from this repository. Evidence: [src/tools/actors/call_actor.ts:670](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/call_actor.ts#L670); searched `rg -n -S -e 'child_process|execSync|spawnSync|\beval\(|new Function\(|vm\.run|runInNewContext'` in `src` → 0 hits (no local process spawning or dynamic code evaluation in the server) (inferred)
  - *To reach the next level:* The isolation boundary is not in the scored code, so it can only be credited as inferred.
- **C L3:** Every model-reachable execution path is a remote Actor run or a call to an Actor's standby endpoint; the server has no local execution path to fall back to. Evidence: searched `rg -n -S -e 'child_process|execSync|spawnSync|\beval\(|new Function\(|vm\.run|runInNewContext'` in `src` → 0 hits (zero local execution sites); [src/tools/actors/call_actor.ts:422](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/call_actor.ts#L422) (verified)
  - *To reach the next level:* Coverage is limited by the level of the mechanism, which is only inferred here.
- **D L3:** There is no flag or setting that runs Actors locally, so the remote boundary cannot be switched off by the model or a workspace file. Evidence: [src/tools/actors/call_actor.ts:279-296](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/call_actor.ts#L279-L296) (verified)
  - *To reach the next level:* The model sets run memory, timeout and build itself, so the run policy is not defined entirely outside model control.
- **B L2:** A run gets unrestricted network egress, its own run storage and a platform token whose reach depends on the Actor's permission level; full-permission Actors need a separate approval in Apify Console, which the server surfaces rather than bypasses. Evidence: [src/utils/apify_errors.ts:20-23](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/utils/apify_errors.ts#L20-L23); [src/tools/actors/call_actor.ts:288-291](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/call_actor.ts#L288-L291) (inferred)
  - *To reach the next level:* Runs are not network-restricted and their resource limits are chosen by the model rather than fixed.
- **Cap:** none
- **Notes:** Apify platform behaviour (container hardening, run token scope, permission approval) is outside the repository and was inferred from the error handling and docs in this code.

### C5 Untrusted input blast radius: 0.05 (high confidence)

Web pages fetched by the default rag-web-browser and web-fetch Actors, third-party Actor READMEs and descriptions, and run results all come back to the model without any marking that they are untrusted. Third-party Actor descriptions are written into tool descriptions, tools proxied from Actor MCP servers keep the remote server's descriptions, and the server's own results add instructions to the model alongside that content. Some outputs do carry structured run metadata and a source URL or run ID. Nothing in the server stops a session that has read untrusted web content from then starting arbitrary paid Actor runs, sending data to any URL through a fetch Actor, or reading the account's private storages.

- **S L0:** Tool outputs carry directives to the model (next-step instructions and a nudge appended to failed results), and third-party Actor descriptions are concatenated into tool descriptions, with fetched content returned inline without an untrusted flag. Evidence: [src/tools/actors/actor_tools_factory.ts:133-135](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/actor_tools_factory.ts#L133-L135); [src/tools/dev/report_problem.ts:27](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/dev/report_problem.ts#L27); [src/tools/docs/fetch_apify_docs.ts:140](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/docs/fetch_apify_docs.ts#L140); [src/mcp/proxy.ts:71](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/mcp/proxy.ts#L71) (verified)
  - *To reach the next level:* Outputs and descriptions mix server instructions with third-party text; there is no clean separation of content from metadata and no provenance flag.
- **C L0:** No source of untrusted content is distinguished from trusted output anywhere in the server. Evidence: searched `rg -n -i -e 'prompt.?injection|<untrusted|spotlight|datamark|taint'` in `src` → 0 hits (no untrusted-content marking or handling) (verified)
  - *To reach the next level:* Untrusted sources (web content, Actor READMEs, proxied MCP results) are not marked or treated differently.
- **D L1:** The structured run metadata that exists is always sent, but it is the only safeguard and nothing narrows a session once untrusted content has been read. Evidence: [src/tools/storage/get_dataset_items.ts:120-128](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/storage/get_dataset_items.ts#L120-L128) (verified)
  - *To reach the next level:* No mode that drops a Rule-of-Two leg is on by default, and the weak mechanism limits how much its default posture can earn.
- **B L0:** In the default session a hijacked model can read the account's data, send it to any URL through the default web-fetch Actor or call-actor, and start irreversible paid runs, with no server-side step involving a human. Evidence: [src/const.ts:162-164](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/const.ts#L162-L164); [src/resources/api_resources.ts:194-200](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/resources/api_resources.ts#L194-L200); [src/tools/actors/call_actor.ts:670](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/call_actor.ts#L670) (verified)
  - *To reach the next level:* Default sessions combine untrusted input, account data and egress plus spend; nothing removes one of the three.
- **Cap:** C5-WORSTCASE: In the default configuration a hijacked session can leak account data and take irreversible paid actions without a human in the loop.

### C6 Memory, context & configuration integrity: 1.00 (high confidence)

The server keeps no memory that outlives the process: its only state is in-memory caches of Actor definitions and documentation pages that expire within an hour. It writes no files and loads no instruction or settings files from the working directory; the only files it reads are its own package metadata, its bundled widget scripts, and the Apify CLI login file in the user's home directory. No project or workspace file can add tools or change its behaviour.

- **Structural absence:** searched `rg -n -S -e 'writeFileSync|appendFile|createWriteStream|dotenv|process\.cwd\(\)'` in `src` → 0 hits (no file writes, no .env loading, no working-directory lookups); searched `rg -n -S -e 'readFileSync\('` in `src` → 3 hits (package metadata (utils/generic.ts), the user-scope ~/.apify/auth.json token file (stdio.ts) and bundled widget JS (resource_service.ts); none is workspace-controlled or model-writable)

### C7 Third-party extensions: 0.05 (medium confidence)

The third-party code this server can launch is Apify Actors, which run in Apify's cloud. In the default configuration the model can start any public Actor in Apify Store through call-actor, at whatever build the developer has tagged or one the model names, with no pin, integrity check, allowlist or user consent in the server. Actor definitions and proxied MCP tool lists are fetched again each session with no check for changes. Runs do not execute on the local machine, and Apify requires a separate approval for Actors that ask for full account permissions, but Actor MCP servers are contacted with the session's account credentials, as a source comment documents, so what a malicious Actor gets depends on platform controls that are outside this repository.

- **S L0:** The model chooses which Store Actor to run and may pick its build; nothing pins or verifies the code that runs. Evidence: [src/tools/actors/call_actor.ts:145](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/call_actor.ts#L145); [src/tools/actors/call_actor.ts:292-297](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/call_actor.ts#L292-L297) (verified)
  - *To reach the next level:* No pinning, integrity check or curated allowlist for the Actors the model can run.
- **C L0:** Neither Actor runs nor proxied Actor MCP servers are verified; their tool lists are taken as served. Evidence: [src/mcp/proxy.ts:57](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/mcp/proxy.ts#L57); [src/tools/actors/call_actor.ts:670](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/call_actor.ts#L670) (verified)
  - *To reach the next level:* No extension type is verified.
- **D L0:** call-actor is in the default tool set, so any Store Actor can be run without the operator adding it. Evidence: [src/tools/registry.ts:61](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/registry.ts#L61); [src/tools/registry.ts:97](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/registry.ts#L97) (verified)
  - *To reach the next level:* Third-party Actors are usable by default without an explicit operator choice showing what will run.
- **B L1:** Actors run remotely in separate containers, not in this process, but proxied Actor MCP servers are reached with the session's account credentials (confined to the Actor's standby origin), and what an Actor run can reach depends on platform permission levels not visible here. Evidence: [src/mcp/actors.ts:38-42](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/mcp/actors.ts#L38-L42); [src/mcp/client.ts:105-112](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/mcp/client.ts#L105-L112) (inferred)
  - *To reach the next level:* Extensions do not get a scrubbed, extension-specific credential from this server.
- **Cap:** C7-RCELOAD: By default the model can start any third-party Actor from Apify Store on the user's account without consent or verification.

### C8 Secrets & sensitive-data protection: 0.25 (high confidence)

The Apify token comes from an environment variable or the Apify CLI's plaintext login file, and is attached to API requests by the Apify client rather than placed in model-visible output; the user-ID cache keys on a hash of the token. Log redaction covers only the Skyfire payment token. Usage telemetry to Segment and crash reporting to Sentry are on by default and send tool names, statuses, Actor and run IDs and short error details, but not tool arguments. The token itself is long-lived and, as usually configured, account-wide. A report-problem tool, served by default to some clients while telemetry is on, sends model-written problem reports to Apify; only its description asks the model to leave out personal data and credentials.

- **S L1:** Secrets come from the environment or a plaintext CLI file, and the only redaction helper masks the Skyfire payment id in logged arguments. Evidence: [src/utils/logging.ts:209-226](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/utils/logging.ts#L209-L226); [src/stdio.ts:68-73](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/stdio.ts#L68-L73) (verified)
  - *To reach the next level:* No type-level masking or log filter for the Apify token and no redaction of model-bound or telemetry content.
- **C L1:** Only the debug log of tool arguments goes through redaction; error messages forwarded to telemetry and to the model are not filtered. Evidence: [src/mcp/tool_call_engine.ts:203-222](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/mcp/tool_call_engine.ts#L203-L222); [src/tools/actors/call_actor.ts:269-276](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/call_actor.ts#L269-L276); [src/tools/dev/report_problem.ts:14-17](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/dev/report_problem.ts#L14-L17) (verified)
  - *To reach the next level:* Logs, telemetry and error paths are not all covered by a redaction step.
- **D L1:** Segment usage telemetry and Sentry error reporting are enabled by default and must be turned off with a flag or environment variable; events carry metadata, not tool arguments. Evidence: [src/stdio.ts:100-105](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/stdio.ts#L100-L105); [src/instrument.ts:16-25](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/instrument.ts#L16-L25) (verified)
  - *To reach the next level:* Telemetry and crash reporting are opt-out rather than opt-in.
- **B L1:** The key at risk is a long-lived Apify API token that is account-wide unless the user scoped it; it is not exposed to the model or to subprocesses. Evidence: [src/stdio.ts:151](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/stdio.ts#L151); [src/utils/userid_cache.ts:44](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/utils/userid_cache.ts#L44) (verified)
  - *To reach the next level:* The server does not use scoped or short-lived credentials.
- **Cap:** none

### C9 Audit & traceability: 0.25 (high confidence)

The server writes a structured line for every completed tool call, but the local stdio entry point sets the log level to errors only, so in the default configuration successful calls leave no local record; only failures are printed to standard error, where the MCP host may capture them. Every call is also reported to Apify's usage telemetry with the tool name, status and Actor and run IDs, but that goes to the vendor, not to the operator. The Apify platform keeps its own run history, which is outside this server. No record links a call to an approver.

- **S L1:** Per-call completion is logged at INFO, which the stdio entry point suppresses by setting the level to ERROR; errors are mirrored to stderr. Evidence: [src/stdio.ts:79](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/stdio.ts#L79); [src/mcp/tool_call_telemetry.ts:107](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/mcp/tool_call_telemetry.ts#L107) (verified)
  - *To reach the next level:* No structured local record of every tool call with arguments and status in the default configuration.
- **C L1:** Only failures reach the local output; successful calls, including paid runs, are recorded only in vendor telemetry. Evidence: [src/stdio.ts:142-148](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/stdio.ts#L142-L148) (verified)
  - *To reach the next level:* Successful built-in, Actor and proxied tool calls are not all recorded locally.
- **D L1:** Error output is on by default but goes to the host's stderr handling; the server keeps no record of its own. Evidence: [src/stdio.ts:142-148](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/stdio.ts#L142-L148) (verified)
  - *To reach the next level:* No server-owned record outside the process that the operator controls.
- **B L1:** Recording is best effort: telemetry is batched and flushed every few seconds, and nothing blocks an action when a record cannot be written. Evidence: [src/telemetry.ts:19-22](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/telemetry.ts#L19-L22) (verified)
  - *To reach the next level:* Records are not flushed durably per action.
- **Cap:** none

### C10 Limits & kill switch: 0.40 (high confidence)

The server caps how long it waits for an Actor run (45 seconds at most), times out calls to Actor MCP servers after two minutes, limits inline output to 256 KB, and caps search and key listings. When the client cancels a call while the server is waiting, it aborts the Actor run it started. But a run keeps going in Apify's cloud after the tool returns, the model chooses its timeout (zero means no limit), memory and optional spend cap, and dataset reads have no maximum page size. Any spend ceiling comes from the user's Apify plan rather than from the server.

- **S L2:** Server-enforced caps exist on waits, proxied MCP calls and output size, but not on every operation, and there is no concurrency or rate limit. Evidence: [src/tools/actors/call_actor.ts:316-321](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/call_actor.ts#L316-L321); [src/mcp/const.ts:7-8](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/mcp/const.ts#L7-L8); [src/const.ts:25](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/const.ts#L25) (verified)
  - *To reach the next level:* No cap on every operation (dataset page size, run timeout and memory are model-chosen) and no rate or concurrency limit.
- **C L2:** Waits and remote calls are bounded and cancelling an in-flight call aborts its run, but runs that outlive the tool call are not bounded by the server. Evidence: [src/tools/actors/actor_run_response.ts:900-907](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/actor_run_response.ts#L900-L907); [src/tools/actors/call_actor.ts:686-690](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/call_actor.ts#L686-L690) (verified)
  - *To reach the next level:* Started runs and long-running task-mode calls do not count against any server budget.
- **D L1:** Wait limits have sensible fixed ceilings, but the model sets the run timeout (including no limit), memory up to 32 GB and whether a spend cap applies. Evidence: [src/tools/actors/call_actor.ts:288-291](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/call_actor.ts#L288-L291) (verified)
  - *To reach the next level:* The model can raise the limits that bound spend; there are no operator-set run ceilings.
- **B L1:** Stopping the client or the server leaves started Actor runs running in the cloud until their own timeout, which may be unlimited. Evidence: [src/tools/actors/call_actor.ts:647-649](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/call_actor.ts#L647-L649); [src/tools/actors/call_actor.ts:686-690](https://github.com/apify/apify-mcp-server/blob/47a6c9fe4f9e96445316285fb4dfcfaee4cb28c6/src/tools/actors/call_actor.ts#L686-L690) (verified)
  - *To reach the next level:* Stopping does not end work already started, and the server sets no per-session spend ceiling.
- **Cap:** none

## Rule-of-Two check
[A] untrusted input: Web pages via the default rag-web-browser and web-fetch Actor tools (src/const.ts:163), third-party Actor READMEs and descriptions (src/tools/actors/actor_tools_factory.ts:135) · [B] sensitive data/systems: One account-wide Apify token for every tool (src/stdio.ts:151); any Apify API GET through resources/read (src/resources/api_resources.ts:194) · [C] state change / egress: call-actor starts any Store Actor, including fetches of arbitrary URLs and paid runs (src/tools/actors/call_actor.ts:670) · Same default session? Yes

## Highest-impact improvements
1. Leave call-actor out of the default tool set (or restrict it to an operator-supplied Actor allowlist), so a default session can only run the Actors the operator named. (C7 D L0→L3, +0.150 before caps; Playbook 3)
2. Enforce an operator-set default run timeout, memory and maxTotalChargeUsd on every run instead of letting the model choose them. (C10 D L1→L3, +0.100 before caps; Playbook 3 step 3)
3. Write a structured local record of every tool call (tool, arguments, run ID, status) regardless of the console log level. (C9 S L1→L2, +0.075 before caps; Playbook 1 step 3)
4. Keep server instructions out of results that carry third-party content, and return Actor descriptions, READMEs, fetched pages and proxied MCP tool text as structured, source-labelled data. (C5 S L0→L2, +0.150 before caps; Playbook 1)
5. Make Segment telemetry and Sentry error reporting opt-in for the local stdio server. (C8 D L1→L2, +0.050 before caps; Playbook 4)

## Re-audit log
- C2 S: L2 → L1. Literal anchor check: L2 needs accurate hints on every tool, but update-schedule, update-actor-task and publish-actor-task declare destructiveHint false while overwriting or publishing configuration.
- C3 S: L1 → L2. Defending the low rating: every tool is schema-validated in the shared call path and the narrow tools use parsed host and origin allowlists (fetch_apify_docs.ts:41-51, api_resources.ts:65-70); the generic call-actor keeps it below L3.
- C5 S: L1 → L0. Literal anchor check for tool servers: outputs carry directives to the model (report_problem.ts:27 nudge, nextStep text) and third-party Actor descriptions are concatenated into tool descriptions (actor_tools_factory.ts:135).
- C5 D: L2 → L1. D may be at most one level above S after S moved to L0.
- C9 S: L0 → L1. Defending the zero: failures are mirrored to stderr (stdio.ts:142-148), an unstructured record of some actions, although successful calls are suppressed by the ERROR log level.

## Limitations
- Static source review of the pinned commit only; nothing was executed, installed, or probed.
- The repository was cloned from https://github.com/apify/apify-mcp-server; the older name apify/actors-mcp-server serves the same commit.
- Scored the local stdio npm package. The hosted server at mcp.apify.com, which the README recommends, runs authentication, rate limiting and session handling from a private repository and was not reviewed.
- Apify platform behaviour (Actor container isolation, run token scope, full-permission approval, Store safety filtering, account quotas) is outside the repository; C4 and C7 ratings that depend on it are marked inferred.
- The MCP Apps widget mode, payment modes (x402, Skyfire) and the development HTTP server were read only where they share code with the scored path.
- Repository AGENTS.md/CLAUDE.md files address contributors' coding agents; no text aimed at steering reviewers was found.
