Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
48 changes: 48 additions & 0 deletions docs/reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1573,3 +1573,51 @@ FLAGS
```
<!-- task-commands-end -->
<!-- prettier-ignore-end -->

### MCP

Use these commands to configure the Apify MCP server in your AI client.

<!-- prettier-ignore-start -->
<!-- mcp-commands-start -->
##### `apify mcp`

```sh
DESCRIPTION
Configure the Apify MCP server in your AI client: Claude Code, Cursor, VS
Code, Codex CLI, Kiro, or Antigravity.

SUBCOMMANDS
mcp install Configure a local MCP client to use the Apify MCP
server. Writes or merges a server entry named 'apify' into the
client's config file, or runs the client's own 'mcp add' command
when available.
```

##### `apify mcp install`

```sh
DESCRIPTION
Configure a local MCP client to use the Apify MCP server. Writes or merges a
server entry named 'apify' into the client's config file, or runs the client's
own 'mcp add' command when available.

USAGE
$ apify mcp install <client> [-t <value>] [--tools <value>]
[--url <value>] [-y]

ARGUMENTS
client Target MCP client. One of: claude-code, cursor, vscode,
vscode-insiders, codex, kiro, antigravity.

FLAGS
-t, --token=<value> Apify API token to embed in the config.
Defaults to the token from 'apify login'.
--tools=<value> Comma-separated tool IDs or Actor full names
to expose. Forwarded as a '?tools=' query parameter.
--url=<value> Apify MCP server URL.
-y, --yes Overwrite an existing 'apify' entry
without prompting.
```
<!-- mcp-commands-end -->
<!-- prettier-ignore-end -->
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@
"jju": "~1.4.0",
"js-levenshtein": "^1.1.6",
"json-schema-to-typescript": "^15.0.4",
"jsonc-parser": "3.3.1",
"mime": "~4.1.0",
"open": "~11.0.0",
"rimraf": "~6.1.3",
Expand Down
8 changes: 8 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions scripts/generate-cli-docs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,11 @@ const categories: Record<string, CommandsInCategory[]> = {
{ command: Commands.task },
{ command: Commands.taskRun },
],
'mcp': [
//
{ command: Commands.mcp },
{ command: Commands.mcpInstall },
],
};

await renderDocs(categories);
9 changes: 9 additions & 0 deletions scripts/reference-template.md
Original file line number Diff line number Diff line change
Expand Up @@ -110,3 +110,12 @@ These commands help you manage scheduled and configured Actor runs. Use them to
<!-- task-commands-start -->
<!-- task-commands-end -->
<!-- prettier-ignore-end -->

### MCP

Use these commands to configure the Apify MCP server in your AI client.

<!-- prettier-ignore-start -->
<!-- mcp-commands-start -->
<!-- mcp-commands-end -->
<!-- prettier-ignore-end -->
2 changes: 2 additions & 0 deletions src/commands/_register.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { InitCommand } from './init.js';
import { KeyValueStoresIndexCommand } from './key-value-stores/_index.js';
import { LoginCommand } from './login.js';
import { LogoutCommand } from './logout.js';
import { MCPIndexCommand } from './mcp/_index.js';
import { TopLevelPullCommand } from './pull.js';
import { ToplevelPushCommand } from './push.js';
import { RequestQueuesIndexCommand } from './request-queues/_index.js';
Expand All @@ -43,6 +44,7 @@ export const apifyCommands = [
BuildsIndexCommand,
DatasetsIndexCommand,
KeyValueStoresIndexCommand,
MCPIndexCommand,
RequestQueuesIndexCommand,
RunsIndexCommand,
SecretsIndexCommand,
Expand Down
8 changes: 3 additions & 5 deletions src/commands/cli-management/install.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import assert from 'node:assert';
import { existsSync, openSync } from 'node:fs';
import { mkdir, readFile, symlink, unlink, writeFile } from 'node:fs/promises';
import { homedir } from 'node:os';
import { dirname, join } from 'node:path';
import { ReadStream } from 'node:tty';

Expand All @@ -12,11 +11,10 @@ import { ApifyCommand } from '../../lib/command-framework/apify-command.js';
import { useCLIMetadata } from '../../lib/hooks/useCLIMetadata.js';
import { useYesNoConfirm } from '../../lib/hooks/user-confirmations/useYesNoConfirm.js';
import { error, info, simpleLog, success, warning } from '../../lib/outputs.js';
import { detectShell, shellConfigFile, tildify } from '../../lib/utils.js';
import { detectShell, shellConfigFile, tildify, userHomeDir } from '../../lib/utils.js';
import { cliDebugPrint } from '../../lib/utils/cliDebugPrint.js';

const pathToInstallMarker = (installPath: string) => join(installPath, '.install-marker');
const HOMEDIR = () => process.env.HOME ?? homedir();

export class InstallCommand extends ApifyCommand<typeof InstallCommand> {
static override name = 'install' as const;
Expand Down Expand Up @@ -77,7 +75,7 @@ export class InstallCommand extends ApifyCommand<typeof InstallCommand> {
}

private async symlinkToLocalBin(installPath: string) {
const userHomeDirectory = HOMEDIR();
const userHomeDirectory = userHomeDir();

cliDebugPrint('[install -> symlinkToLocalBin] user home directory', userHomeDirectory);

Expand Down Expand Up @@ -211,7 +209,7 @@ export class InstallCommand extends ApifyCommand<typeof InstallCommand> {
return;
}

const userHomeDirectory = HOMEDIR();
const userHomeDirectory = userHomeDir();

cliDebugPrint('[install -> promptAddToShell] user home directory', userHomeDirectory);

Expand Down
18 changes: 18 additions & 0 deletions src/commands/mcp/_index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { ApifyCommand } from '../../lib/command-framework/apify-command.js';
import { MCPInstallCommand } from './install.js';

export class MCPIndexCommand extends ApifyCommand<typeof MCPIndexCommand> {
static override name = 'mcp' as const;

static override description = `Configure the Apify MCP server in your AI client: Claude Code, Cursor, VS Code, Codex CLI, Kiro, or Antigravity.`;

static override group = 'MCP';

static override docsUrl = 'https://docs.apify.com/cli/docs/reference#apify-mcp';

static override subcommands = [MCPInstallCommand];

async run() {
this.printHelp();
}
}
84 changes: 84 additions & 0 deletions src/commands/mcp/install.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import process from 'node:process';

import { ApifyCommand } from '../../lib/command-framework/apify-command.js';
import { Args } from '../../lib/command-framework/args.js';
import { Flags, YesFlag } from '../../lib/command-framework/flags.js';
import { CommandExitCodes } from '../../lib/consts.js';
import { resolveApifyToken } from '../../lib/mcp/auth.js';
import { getClientHandler, isSupportedClient, SUPPORTED_CLIENTS } from '../../lib/mcp/clients.js';
import { buildMcpUrl, DEFAULT_MCP_URL } from '../../lib/mcp/url.js';
import { error } from '../../lib/outputs.js';

export class MCPInstallCommand extends ApifyCommand<typeof MCPInstallCommand> {
static override name = 'install' as const;

static override description = `Configure a local MCP client to use the Apify MCP server. Writes or merges a server entry named 'apify' into the client's config file, or runs the client's own 'mcp add' command when available.`;

static override group = 'MCP';

static override interactive = true;

static override interactiveNote =
'Prompts before overwriting an existing config entry. Pass --yes to overwrite without prompting.';

static override examples = [
{
description: 'Add Apify to Claude Code using the stored API token.',
command: 'apify mcp install claude-code',
},
{
description: 'Add Apify to Cursor.',
command: 'apify mcp install cursor',
},
{
description: `Add only the 'search-actors' tool and the 'apify/rag-web-browser' Actor to VS Code.`,
command: 'apify mcp install vscode --tools search-actors,apify/rag-web-browser',
},
{
description: 'Add Apify to Codex CLI with an explicit token (non-interactive).',
command: 'apify mcp install codex --token apify_api_xxxxx --yes',
},
];

static override docsUrl = 'https://docs.apify.com/cli/docs/reference#apify-mcp-install';

static override args = {
client: Args.string({
required: true,
description: `Target MCP client. One of: ${SUPPORTED_CLIENTS.join(', ')}.`,
}),
};

static override flags = {
...YesFlag(`Overwrite an existing 'apify' entry without prompting.`),
token: Flags.string({
char: 't',
description: `Apify API token to embed in the config. Defaults to the token from 'apify auth token'.`,
}),
url: Flags.string({
description: 'Apify MCP server URL.',
default: DEFAULT_MCP_URL,
}),
tools: Flags.string({
description: `Comma-separated tool IDs or Actor full names to expose. Forwarded as a '?tools=' query parameter.`,
}),
};

async run() {
const { client } = this.args;
const { token: tokenFlag, url: baseUrl, tools, yes } = this.flags;

if (!isSupportedClient(client)) {
error({
message: `Unknown MCP client '${client}'. Supported clients: ${SUPPORTED_CLIENTS.join(', ')}.`,
});
process.exitCode = CommandExitCodes.InvalidInput;
return;
}

const token = await resolveApifyToken(tokenFlag);
if (!token) return;

await getClientHandler(client)({ url: buildMcpUrl(baseUrl, tools), token, yes });
}
}
23 changes: 23 additions & 0 deletions src/lib/mcp/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import process from 'node:process';

import { CommandExitCodes } from '../consts.js';
import { error } from '../outputs.js';
import { getLocalUserInfo } from '../utils.js';

/**
* Resolution order: --token flag → APIFY_TOKEN env → stored login.
* Prints a user-facing error and sets process.exitCode when no token is available.
*/
export async function resolveApifyToken(tokenFlag: string | undefined): Promise<string | null> {
if (tokenFlag) return tokenFlag;
if (process.env.APIFY_TOKEN) return process.env.APIFY_TOKEN;

const userInfo = await getLocalUserInfo();
if (userInfo.token) return userInfo.token;

error({
message: `You are not logged in to Apify. Run 'apify login' first, or pass --token <api-token>.`,
});
process.exitCode = CommandExitCodes.MissingAuth;
return null;
}
Loading
Loading