Skip to content
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
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
68 changes: 48 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -140,47 +140,75 @@ One of the most powerful features of using MCP with Apify is dynamic tool discov
It gives an AI agent the ability to find new tools (Actors) as needed and incorporate them.
Here are some special MCP operations and how the Apify MCP Server supports them:

- **Actor discovery and management**: Search for Actors, view their details, and dynamically add or remove them as available tools for the AI.
- **Apify Actors**: Search for Actors, view their details, and use them as tools for the AI.
- **Apify documentation**: Search the Apify documentation and fetch specific documents to provide context to the AI.
- **Actor runs (*)**: Get lists of your Actor runs, inspect their details, and retrieve logs.
- **Apify storage (*)**: Access data from your datasets and key-value stores.

**Note**: Helper tool categories marked with (*) are not enabled by default in the MCP server and must be explicitly enabled using the `tools` argument (either the `--tools` command line argument for the stdio server or the `?tools` URL query parameter for the remote MCP server). The `tools` argument is a comma-separated list of categories with the following possible values:

- `docs`: Search and fetch Apify documentation tools.
- `runs`: Get Actor run lists, run details, and logs from a specific Actor run.
- `storage`: Access datasets, key-value stores, and their records.
- `preview`: Experimental tools in preview mode.

For example, to enable all tools, use `npx @apify/actors-mcp-server --tools docs,runs,storage,preview` or `https://mcp.apify.com/?tools=docs,runs,storage,preview`.
- **Actor runs**: Get lists of your Actor runs, inspect their details, and retrieve logs.
- **Apify storage**: Access data from your datasets and key-value stores.

### Overview of available tools

Here is an overview list of all the tools provided by the Apify MCP Server.

| Tool name | Category | Description | Enabled by default |
| :--- | :--- | :--- | :---: |
| `get-actor-details` | actor-discovery | Retrieve detailed information about a specific Actor. | ✅ |
| `search-actors` | actor-discovery | Search for Actors in the Apify Store. | ✅ |
| `add-actor` | default (see note below) | Add an Actor as a new tool for the user to call. | ✅ |
| [`apify-slash-rag-web-browser`](https://apify.com/apify/rag-web-browser) | Actor (see note below) | An Actor tool to browse the web. | ✅ |
| `search-actors` | actors | Search for Actors in the Apify Store. | ✅ |
| `fetch-actor-details` | actors | Retrieve detailed information about a specific Actor. | ✅ |
| `call-actor` | actors | Call an Actor and get its run results. | ✅ |
| [`apify-slash-rag-web-browser`](https://apify.com/apify/rag-web-browser) | Actor (see [tool configuration](#tools-configuration)) | An Actor tool to browse the web. | ✅ |
| `search-apify-docs` | docs | Search the Apify documentation for relevant pages. | ✅ |
| `fetch-apify-docs` | docs | Fetch the full content of an Apify documentation page by its URL. | ✅ |
| `call-actor` | preview | Call an Actor and get its run results. | |
| `get-actor-run` | runs | Get detailed information about a specific Actor run. | |
| `get-actor-run-list` | runs | Get a list of an Actor's runs, filterable by status. | |
| `get-actor-log` | runs | Retrieve the logs for a specific Actor run. | |
| `get-dataset` | storage | Get metadata about a specific dataset. | |
| `get-dataset-items` | storage | Retrieve items from a dataset with support for filtering and pagination. | |
| `get-dataset-schema` | storage | Generate a JSON schema from dataset items. | |
| `get-key-value-store` | storage | Get metadata about a specific key-value store. | |
| `get-key-value-store-keys`| storage | List the keys within a specific key-value store. | |
| `get-key-value-store-record`| storage | Get the value associated with a specific key in a key-value store. | |
| `get-dataset-list` | storage | List all available datasets for the user. | |
| `get-key-value-store-list`| storage | List all available key-value stores for the user. | |
| `add-actor` | experimental | Add an Actor as a new tool for the user to call. | |

### Tools configuration

The `tools` configuration parameter is used to specify loaded tools - either categories or specific tools directly, and Apify Actors. For example, `tools=storage,runs` loads two categories; `tools=add-actor` loads just one tool.

When no query parameters are provided, the MCP server loads the following `tools` by default:

- `actors`
- `docs`
- `apify/rag-web-browser`

It is thus the same as if you configured the `tools` parameter like this:

**For the hosted server:**
```
https://mcp.apify.com?tools=actors,docs,apify/rag-web-browser
```

**For the CLI:**
```bash
npx @apify/actors-mcp-server --tools actors,docs,apify/rag-web-browser
```

If the tools parameter is specified, only the listed tools or categories will be enabled - no default tools will be included.

> **⚠️ Important Recommendation**
>
> **The default tools configuration may change in future versions.** When no `tools` parameter is specified, the server currently loads default tools, but this behavior is subject to change.
>
> **For production use and stable interfaces, always explicitly specify the `tools` parameter** to ensure your configuration remains consistent across updates.

**Minimal configuration**

For example, to use only a single Actor tool - without any discovery or generic calling tools, the server can be configured like this:

```
https://mcp.apify.com?tools=apify/my-actor
```

> **Note:**
> The `add-actor` tool is always enabled by default and does not explicitly belong to any category. Currently, it can be disabled by setting `?enableAddingActors=false` or `--enable-adding-actors false`.
> The `apify-slash-rag-web-browser` is an Apify Actor tool loaded by default. You can disable it by loading a different set of Actors using `?actors=other/actor` or `--actors other/actor`, or you can disable pre-loading of Actors by setting `?actors=` or `--actors=` (to an empty string).
This setup exposes only the specified Actor (`apify/my-actor`) as a tool. No other tools will be available.

### Prompts

Expand Down
9 changes: 2 additions & 7 deletions src/actor/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,6 @@ import { getActorRunData } from './utils.js';

export function createExpressApp(
host: string,
mcpServerOptions: {
enableAddingActors?: boolean;
enableDefaultActors?: boolean;
actors?: string[];
},
): express.Express {
const app = express();
const mcpServers: { [sessionId: string]: ActorsMcpServer } = {};
Expand Down Expand Up @@ -74,7 +69,7 @@ export function createExpressApp(
rt: Routes.SSE,
tr: TransportType.SSE,
});
const mcpServer = new ActorsMcpServer(mcpServerOptions, false);
const mcpServer = new ActorsMcpServer(false);
const transport = new SSEServerTransport(Routes.MESSAGE, res);

// Load MCP server tools
Expand Down Expand Up @@ -157,7 +152,7 @@ export function createExpressApp(
sessionIdGenerator: () => randomUUID(),
enableJsonResponse: false, // Use SSE response mode
});
const mcpServer = new ActorsMcpServer(mcpServerOptions, false);
const mcpServer = new ActorsMcpServer(false);

// Load MCP server tools
const apifyToken = process.env.APIFY_TOKEN as string;
Expand Down
2 changes: 1 addition & 1 deletion src/const.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export enum HelperTools {
ACTOR_ADD = 'add-actor',
ACTOR_CALL = 'call-actor',
ACTOR_GET = 'get-actor',
ACTOR_GET_DETAILS = 'get-actor-details',
ACTOR_GET_DETAILS = 'fetch-actor-details',
ACTOR_REMOVE = 'remove-actor',
ACTOR_RUNS_ABORT = 'abort-actor-run',
ACTOR_RUNS_GET = 'get-actor-run',
Expand Down
5 changes: 3 additions & 2 deletions src/index-internals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

import { defaults, HelperTools } from './const.js';
import { parseInputParamsFromUrl, processParamsGetTools } from './mcp/utils.js';
import { addRemoveTools, defaultTools, getActorsAsTools, toolCategories, toolCategoriesEnabledByDefault } from './tools/index.js';
import { addTool } from './tools/helpers.js';
import { defaultTools, getActorsAsTools, toolCategories, toolCategoriesEnabledByDefault } from './tools/index.js';
import { actorNameToToolName } from './tools/utils.js';
import type { ToolCategory } from './types.js';
import { getToolPublicFieldOnly } from './utils/tools.js';
Expand All @@ -15,7 +16,7 @@ export {
HelperTools,
defaults,
defaultTools,
addRemoveTools,
addTool,
toolCategories,
toolCategoriesEnabledByDefault,
type ToolCategory,
Expand Down
95 changes: 62 additions & 33 deletions src/input.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,78 @@
/*
* Actor input processing.
*
* Normalizes raw inputs (CLI/env/HTTP) into a consistent `Input` shape.
* No tool-loading is done here; we only canonicalize values and preserve
* intent via `undefined` (use defaults later) vs empty (explicitly none).
*/
import log from '@apify/log';

import type { Input, ToolCategory } from './types.js';
import type { Input, ToolSelector } from './types.js';

// Helpers
// Normalize booleans that may arrive as strings or be undefined.
function toBoolean(value: unknown, defaultValue: boolean): boolean {
if (value === undefined) return defaultValue;
if (typeof value === 'boolean') return value;
if (typeof value === 'string') return value.toLowerCase() === 'true';
return defaultValue;
}

// Normalize lists from comma-separated strings or arrays.
function normalizeList(value: string | string[] | undefined): string[] | undefined {
if (value === undefined) return undefined;
if (Array.isArray(value)) return value.map((s) => String(s).trim()).filter((s) => s !== '');
const trimmed = String(value).trim();
if (trimmed === '') return [];
return trimmed.split(',').map((s) => s.trim()).filter((s) => s !== '');
}

/**
* Process input parameters, split Actors string into an array
* @param originalInput
* @returns input
* Normalize user-provided input into a canonical `Input`.
*
* Responsibilities:
* - Coerce `actors`, `tools` from string/array into trimmed arrays ('' → []).
* - Normalize booleans (including legacy `enableActorAutoLoading`).
* - Merge `actors` into `tools` so selection lives in one place.
*
* Semantics passed to the loader:
* - `undefined` → use defaults; `[]` → explicitly none.
*/
export function processInput(originalInput: Partial<Input>): Input {
const input = originalInput as Input;

// actors can be a string or an array of strings
if (input.actors && typeof input.actors === 'string') {
/**
* Filter out empty strings to prevent invalid Actor API error.
*/
input.actors = input.actors.split(',').map((format: string) => format.trim()).filter((actor) => actor !== '') as string[];
}
/**
* Replace empty string with empty array to prevent invalid Actor API error.
*/
if (input.actors === '') {
input.actors = [];
// Normalize actors (strings and arrays) to a clean array or undefined
const actors = normalizeList(originalInput.actors) as unknown as string[] | undefined;

// Map deprecated flag to the new one and normalize both to boolean.
let enableAddingActors: boolean;
if (originalInput.enableAddingActors === undefined && originalInput.enableActorAutoLoading !== undefined) {
log.warning('enableActorAutoLoading is deprecated, use enableAddingActors instead');
enableAddingActors = toBoolean(originalInput.enableActorAutoLoading, false);
} else {
enableAddingActors = toBoolean(originalInput.enableAddingActors, false);
}

// enableAddingActors is deprecated, use enableActorAutoLoading instead
if (input.enableAddingActors === undefined) {
if (input.enableActorAutoLoading !== undefined) {
log.warning('enableActorAutoLoading is deprecated, use enableAddingActors instead');
input.enableAddingActors = input.enableActorAutoLoading === true || input.enableActorAutoLoading === 'true';
// Normalize tools (strings/arrays) to a clean array or undefined
let tools = normalizeList(originalInput.tools as string | string[] | undefined) as unknown as ToolSelector[] | undefined;

// Merge actors into tools. If tools undefined → tools = actors, then remove actors;
// otherwise append actors to tools.
// NOTE (future): Actor names contain '/', unlike internal tool names or categories. We could use that to differentiate between the two.
if (Array.isArray(actors) && actors.length > 0) {
if (tools === undefined) {
tools = [...actors] as ToolSelector[];
} else {
input.enableAddingActors = true;
const currentTools: ToolSelector[] = Array.isArray(tools)
? tools
: [tools as ToolSelector];
tools = [...currentTools, ...actors] as ToolSelector[];
}
} else {
input.enableAddingActors = input.enableAddingActors === true || input.enableAddingActors === 'true';
}

if (input.tools && typeof input.tools === 'string') {
/**
* Filter out empty strings just in case.
*/
input.tools = input.tools.split(',').map((tool: string) => tool.trim()).filter((tool) => tool !== '') as ToolCategory[];
}
return input;
// Return a new object with all properties explicitly defined
return {
...originalInput,
actors: Array.isArray(actors) && actors.length > 0 && tools !== undefined ? undefined : actors,
enableAddingActors,
tools,
};
}
18 changes: 3 additions & 15 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,21 +29,9 @@ const input = processInput((await Actor.getInput<Partial<Input>>()) ?? ({} as In
log.info('Loaded input', { input: JSON.stringify(input) });

if (STANDBY_MODE) {
let actorsToLoad: string[] = [];
// TODO: in standby mode the input loading does not actually work,
// we should remove this since we are using the URL query parameters to load Actors
// Load only Actors specified in the input
// If you wish to start without any Actor, create a task and leave the input empty
if (input.actors && input.actors.length > 0) {
const { actors } = input;
actorsToLoad = Array.isArray(actors) ? actors : actors.split(',');
}
// Include Actors to load in the MCP server options for backwards compatibility
const app = createExpressApp(HOST, {
enableAddingActors: Boolean(input.enableAddingActors),
enableDefaultActors: false,
actors: actorsToLoad,
});
// In standby mode, actors and tools are provided via URL query params per request
// Start express app
const app = createExpressApp(HOST);
log.info('Actor is running in the STANDBY mode.');

app.listen(PORT, () => {
Expand Down
Loading