feat(mcp): add 'apify mcp install <client>' command#1145
Conversation
Configures the Apify MCP server in six AI clients (claude-code, cursor, vscode, codex, kiro, antigravity) by writing the client's canonical config or shelling out to its own 'mcp add' CLI.
|
VS Code, and possibly it's forks (I only have Cursor that I checked), have a CLI interface for adding MCP servers let's avoid editing files wherever possible. And if we cannot avoid editing files, we must preserve everything in them at all costs (not necessarily indentation between tabs and spaces but definitely comments [so read the file via I think JSON5]) |
|
Let's also add in vscode-insiders please (identical to vscode except binary is code-insiders) |
|
Don't forget reference docs changes need to be applied in code, not the markdown file! |
Switch the vscode handler to shell out to 'code --add-mcp <json>' so VS Code itself owns the config write (preserves JSONC comments, format, and overwrite semantics). Add vscode-insiders as a sibling client using 'code-insiders --add-mcp'. Other file-edit clients (cursor, kiro, antigravity) keep their current handlers — their CLIs either don't expose '--add-mcp' (cursor's wrapper double-evals args breaking JSON quoting) or their --add-mcp behavior isn't suitable for our use case.
After a successful install, a link to the third-party client's MCP docs is noise. The user just configured the server — they don't need to look up how the client handles MCP.
|
@vladfrangu Good point! I verified and the CLI command approach works only for VSCode but not for the forks - so I am using the |
|
Really, it doesn't work for cursor? They DO document the flag, does it just not register correctly? 😢 |
|
@vladfrangu For Cursor I could not pass the JSON as they have some bug in the CLI wrapper bash script. And for antigravity and kiro it did not work - the commands return "Added MCP servers: apify" but the server is not actually written to the kiro or antigravity config and thus is not available. I think they just left it there as they are forks of vscode and never actually implemented this properly. |
Switch file-edit clients (cursor, kiro, antigravity) from JSON.parse + JSON.stringify to jsonc-parser's surgical modify()/applyEdits() API. The library patches the source text in place: comments, trailing commas, indentation, and unrelated keys all survive untouched. Adds a regression test that pre-seeds a hand-edited JSONC file with comments and asserts they survive a re-install.
Keep only Server URL, Auth, and Config path. The per-client step lists were noise — users already know how to restart their editor.
"Use these commands to configure the Apify MCP server in your AI client." — drop "favorite", drop parenthetical client list from the section intro. Colon-separate client names in the index description, drop parenthetical '(or merges)' and '(forwarded as…)' from the install command text.
|
@szaganek all you suggestions applied, thank you! |
|
@patrikbraborec in the current implementation we are using the @vladfrangu what is your take on this? |
| printResult({ | ||
| clientLabel: 'Codex CLI', | ||
| serverUrl: url, | ||
| authDescription: `Bearer token from APIFY_TOKEN environment variable`, |
There was a problem hiding this comment.
Do we have no way to tell MCP clients or w/e they need to run a cli command to get the token?
There was a problem hiding this comment.
what do you mean? Like we should try to detect client installed on the system? I would honestly no do that, but it is my personal preference - I like explicit control about what I install and what does it do.
There was a problem hiding this comment.
Nono, to tell these mcp clients like codex "hey to get this token please run apify auth token"
There was a problem hiding this comment.
based on openai docs this is the only way https://developers.openai.com/codex/mcp#streamable-http-servers
There was a problem hiding this comment.
That is so stupid...can we propose that to them? Maybe even clanker pr it 😂
There was a problem hiding this comment.
we can try but from my experience with this approach we never actually ship this feature as pushing PR there will be super painful - but maybe they are fast and responsive 😄 I would honestly merge this is we want to ever ship this and then we can negotiate with them and iterate on this Apify CLI feature.
Co-authored-by: Vlad Frangu <me@vladfrangu.dev>
Per PR review (#1145): hoist the inline 'originalJsonc' literal out of install.test.ts into test/__setup__/fixtures/mcp-install-fixtures.ts so similar fixtures land alongside the existing mock-openapi-spec.
|
@vladfrangu some tests are failing but it should be unrelated to this PR: |
useAuthSetup() already calls vitest.unstubAllEnvs() in its own afterEach
and immediately follows with rm(GLOBAL_CONFIGS_FOLDER(), { recursive,
force }). Our local afterEach was running the same unstubAllEnvs first
(vitest fires afterEach hooks LIFO), so by the time useAuthSetup's
cleanup ran, HOME was already restored to the real value and
GLOBAL_CONFIGS_FOLDER() resolved to <realHome>/.apify — wiping the
developer's auth.json, secrets.json, and telemetry on every local
run of this test file. CI users started clean so it didn't surface
there.
Drop the duplicate call; let useAuthSetup own env cleanup.
- install.ts --token help: 'apify auth token' -> 'apify login' (the command that stores the token; 'apify auth token' only prints it, and the auth.ts error message already references 'apify login'). - install.ts interactiveNote: scope --yes to JSON-merge clients (cursor, kiro, antigravity). For CLI-delegated clients (claude-code, vscode, vscode-insiders, codex), --yes is silently a no-op since overwrite semantics are owned by the child CLI. - clients.ts codexHandler: codex configures bearer_token_env_var but never embeds the token, so users hit 401 if APIFY_TOKEN isn't in the environment when they launch codex. The not-found branch already mentioned this; the success branch did not. Add a Next: line on success too, and reword both to say 'in the same shell' rather than 'in your shell rc' (a one-off export is fine). - printResult: new optional nextSteps field so other handlers can surface post-install instructions in the same block.
jju was already a dependency (used by actors/pull.ts for the same
class of JSONC round-trip). Verified that jju.update preserves
top-of-file comments, inline comments, trailing commas, indentation,
and unrelated sibling keys for every fixture the test suite asserts on.
Greenfield writes (missing/empty file) go through jju.stringify
instead of jju.update because update from a '{}' seed produces a
collapsed inline shape; stringify gives clean 2-space indented JSON.
Net result: one fewer direct dep, no behavior change at the
test-fixture level (all 13 install.test.ts cases pass).
Unrelated upstream drift picked up by 'pnpm run update-docs'. The api command's --describe and --search help strings were updated in #1128 but reference.md wasn't regenerated at the time. Including here only because update-docs is monolithic and refused to re-render just the mcp section.
Seven hand-rolled install handlers shared only two shapes: merge a JSON config file, or shell out to the client's own CLI. Replace them with a fileClient factory and a runCliInstall helper, derive the supported-client list from the handler map (one source of truth), fold exec-helpers in, and inline the single-use token resolver and test fixture path. No behavior change: every user-facing string and exit code is preserved; docs/reference.md is unchanged and the install test suite stays green. Net -44 lines, -3 files.
Context
Implements the "configuration only" half of apify/ai-team#147 — a new
apify mcp install <client>command that configures the Apify MCP server in six AI clients (Claude Code, Cursor, VS Code, Codex CLI, Kiro, Antigravity).Closes apify/ai-team#175
Part of apify/ai-team#147
Solution
Per-client handler dispatched by the
clientargument, writing the documented config shape to each client's user-global location, or shelling out to the client's own CLI (claude mcp add --scope user,codex mcp add). Token resolves from--token→APIFY_TOKENenv → storedapify login. JSON files merge theapifyentry without clobbering other entries; an existing entry triggers a--yes/prompt overwrite gate (matches theYesFlag()convention used byactors rm,builds rm, etc.).Worth your attention
claude mcp addechoes the Authorization header on success, so we usestdio: ['ignore', 'ignore', 'inherit']to discard child stdout and keep live errors. The overwrite-confirm prompt no longer prints the existing entry either. Twoexpect(stderr).not.toContain(TEST_TOKEN)tests guard against regression.mcp-remotelocal stdio bridge, which is on a deprecation track as Claude Desktop adds native remote-MCP support via Connectors. Easy to add back later.claude-codepasses--scope userso the server lands in user-wide config, matching every other client's default user-global location.bearer_token_env_var = "APIFY_TOKEN"— codex's runtime rejects literalbearer_token(Config schema exposes unsupported MCPbearer_tokenfield openai/codex#19275), so env var indirection is the only HTTP-transport auth it accepts. The next-steps tell the user toexport APIFY_TOKEN=<your-token>in their shell rc.userHomeDirhelper extracted tosrc/lib/utils.tsand reused fromcli-management/install.ts(removes the second copy of the same idiom).Follow-up
apify <prompt>) bundlingmcpc+ OpenRouter Actor.