feat(cli): configure MCP natively across coding agents - #187
Conversation
There was a problem hiding this comment.
5 issues found across 10 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="src/utils/mcp-clients.ts">
<violation number="1" location="src/utils/mcp-clients.ts:243">
P2: The automatic picker misses VS Code when its app directory exists but `Code/User` has not been created yet. Detect the VS Code app directory and the existing `~/.vscode` marker instead of requiring the User subdirectory.</violation>
<violation number="2" location="src/utils/mcp-clients.ts:421">
P2: For `--agent toString` or `--agent __proto__`, this resolver returns an inherited property and the setup command crashes instead of rejecting the unknown agent. Check that the alias is an own property before returning it.</violation>
</file>
<file name="src/utils/agents.ts">
<violation number="1" location="src/utils/agents.ts:185">
P3: The recursion in `hasFirecrawlMcpEntry` now treats `servers`/`context_servers` found at any depth as an MCP server map, not just the top-level keys the new setup flow writes (VS Code `mcp.json` `servers`, Zed `context_servers`). Since every object is descended into, any unrelated `servers`/`context_servers` object anywhere in a scanned config (e.g. an extension-managed `servers` map in VS Code `settings.json`) that happens to contain a property named `firecrawl` will make `detectAgents`/doctor report the agent's firecrawl MCP as registered when it is not. The intended targets are top-level keys, so scope the widened match to the root object (or the known `mcp.*` structure) instead of the whole tree.</violation>
</file>
<file name="src/utils/mcp-install.ts">
<violation number="1" location="src/utils/mcp-install.ts:212">
P2: When an existing shared rule file uses CRLF, `fenced` fails to recognize the managed section and appends a duplicate on rerun. Accept `\r?\n` when matching the opening marker and preserve the file's newline style when replacing it.</violation>
<violation number="2" location="src/utils/mcp-install.ts:247">
P2: When an existing Codex config is malformed, `upsertTomlServer` appends a table without detecting the invalid input, so setup reports success while Codex remains unusable. Validate the existing TOML before editing and return a per-agent failure when it cannot be parsed.</violation>
</file>
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
| }; | ||
|
|
||
| export function resolveMcpClientId(agent: string): McpClientId | undefined { | ||
| return CLIENT_ALIASES[agent.trim().toLowerCase()]; |
There was a problem hiding this comment.
P2: For --agent toString or --agent __proto__, this resolver returns an inherited property and the setup command crashes instead of rejecting the unknown agent. Check that the alias is an own property before returning it.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/utils/mcp-clients.ts, line 421:
<comment>For `--agent toString` or `--agent __proto__`, this resolver returns an inherited property and the setup command crashes instead of rejecting the unknown agent. Check that the alias is an own property before returning it.</comment>
<file context>
@@ -0,0 +1,446 @@
+};
+
+export function resolveMcpClientId(agent: string): McpClientId | undefined {
+ return CLIENT_ALIASES[agent.trim().toLowerCase()];
+}
+
</file context>
| return CLIENT_ALIASES[agent.trim().toLowerCase()]; | |
| return Object.prototype.hasOwnProperty.call(CLIENT_ALIASES, agent.trim().toLowerCase()) | |
| ? CLIENT_ALIASES[agent.trim().toLowerCase()] | |
| : undefined; |
| 'firecrawl.instructions.md' | ||
| ), | ||
| }, | ||
| detectPaths: (ctx) => [vscodeUserDir(ctx)], |
There was a problem hiding this comment.
P2: The automatic picker misses VS Code when its app directory exists but Code/User has not been created yet. Detect the VS Code app directory and the existing ~/.vscode marker instead of requiring the User subdirectory.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/utils/mcp-clients.ts, line 243:
<comment>The automatic picker misses VS Code when its app directory exists but `Code/User` has not been created yet. Detect the VS Code app directory and the existing `~/.vscode` marker instead of requiring the User subdirectory.</comment>
<file context>
@@ -0,0 +1,446 @@
+ 'firecrawl.instructions.md'
+ ),
+ },
+ detectPaths: (ctx) => [vscodeUserDir(ctx)],
+ },
+ codex: {
</file context>
| const section = `${RULE_MARKER}\n${content}${RULE_MARKER}`; | ||
| const existing = (await readIfExists(filePath)) ?? ''; | ||
| const marker = escapeRegExp(RULE_MARKER); | ||
| const fenced = new RegExp(`${marker}\\n[\\s\\S]*?${marker}`); |
There was a problem hiding this comment.
P2: When an existing shared rule file uses CRLF, fenced fails to recognize the managed section and appends a duplicate on rerun. Accept \r?\n when matching the opening marker and preserve the file's newline style when replacing it.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/utils/mcp-install.ts, line 212:
<comment>When an existing shared rule file uses CRLF, `fenced` fails to recognize the managed section and appends a duplicate on rerun. Accept `\r?\n` when matching the opening marker and preserve the file's newline style when replacing it.</comment>
<file context>
@@ -0,0 +1,339 @@
+ const section = `${RULE_MARKER}\n${content}${RULE_MARKER}`;
+ const existing = (await readIfExists(filePath)) ?? '';
+ const marker = escapeRegExp(RULE_MARKER);
+ const fenced = new RegExp(`${marker}\\n[\\s\\S]*?${marker}`);
+
+ if (fenced.test(existing)) {
</file context>
| for (const [key, value] of Object.entries(entry)) { | ||
| if (typeof value === 'string') stringEntry[key] = value; | ||
| } | ||
| const { content, alreadyExists } = upsertTomlServer( |
There was a problem hiding this comment.
P2: When an existing Codex config is malformed, upsertTomlServer appends a table without detecting the invalid input, so setup reports success while Codex remains unusable. Validate the existing TOML before editing and return a per-agent failure when it cannot be parsed.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/utils/mcp-install.ts, line 247:
<comment>When an existing Codex config is malformed, `upsertTomlServer` appends a table without detecting the invalid input, so setup reports success while Codex remains unusable. Validate the existing TOML before editing and return a per-agent failure when it cannot be parsed.</comment>
<file context>
@@ -0,0 +1,339 @@
+ for (const [key, value] of Object.entries(entry)) {
+ if (typeof value === 'string') stringEntry[key] = value;
+ }
+ const { content, alreadyExists } = upsertTomlServer(
+ existing,
+ MCP_SERVER_NAME,
</file context>
| for (const key of Object.keys(obj)) { | ||
| const child = obj[key]; | ||
| if (key === 'mcpServers' && child && typeof child === 'object') { | ||
| if (SERVER_MAP_KEYS.has(key) && child && typeof child === 'object') { |
There was a problem hiding this comment.
P3: The recursion in hasFirecrawlMcpEntry now treats servers/context_servers found at any depth as an MCP server map, not just the top-level keys the new setup flow writes (VS Code mcp.json servers, Zed context_servers). Since every object is descended into, any unrelated servers/context_servers object anywhere in a scanned config (e.g. an extension-managed servers map in VS Code settings.json) that happens to contain a property named firecrawl will make detectAgents/doctor report the agent's firecrawl MCP as registered when it is not. The intended targets are top-level keys, so scope the widened match to the root object (or the known mcp.* structure) instead of the whole tree.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/utils/agents.ts, line 185:
<comment>The recursion in `hasFirecrawlMcpEntry` now treats `servers`/`context_servers` found at any depth as an MCP server map, not just the top-level keys the new setup flow writes (VS Code `mcp.json` `servers`, Zed `context_servers`). Since every object is descended into, any unrelated `servers`/`context_servers` object anywhere in a scanned config (e.g. an extension-managed `servers` map in VS Code `settings.json`) that happens to contain a property named `firecrawl` will make `detectAgents`/doctor report the agent's firecrawl MCP as registered when it is not. The intended targets are top-level keys, so scope the widened match to the root object (or the known `mcp.*` structure) instead of the whole tree.</comment>
<file context>
@@ -167,16 +167,22 @@ async function fileHasFirecrawlMcp(filePath: string): Promise<boolean> {
for (const key of Object.keys(obj)) {
const child = obj[key];
- if (key === 'mcpServers' && child && typeof child === 'object') {
+ if (SERVER_MAP_KEYS.has(key) && child && typeof child === 'object') {
if (Object.prototype.hasOwnProperty.call(child, 'firecrawl')) {
return true;
</file context>
There was a problem hiding this comment.
All reported issues were addressed across 5 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
Replace the subprocess installer in `setup mcp` with a built-in one that detects installed agents, pre-selects them in a picker, and offers to install rules telling those agents to prefer Firecrawl for web search and scraping. Covers Claude Code, Cursor, VS Code, Codex, OpenCode, Windsurf, Zed, Hermes Agent, and OpenClaw through per-agent flags, `--project` for project scope, and `--rules` / `--no-rules` for scripted runs. `-y` stays MCP-only. The two launchers were previously reachable only by flag, so a plain `setup mcp` never offered them; they now sit in the picker alongside the editors, and because a launcher shells out to a CLI, a missing binary is reported against that one agent instead of ending the run. Credential handling is unchanged in principle and stricter in reach: an API key is never written as a literal. Knowing which agents were selected means each one receives a reference to FIRECRAWL_API_KEY in the syntax it expands, so the setup no longer has to refuse a run that omits --agent. Agents with no verified syntax fall back to the keyless endpoint and say so rather than persisting a secret. Config edits are surgical. JSON is patched through a JSONC-aware editor so commented settings files parse at all and keep their comments, and TOML tables are replaced along with any stale sub-tables left by a previous stdio entry. Reruns are byte-identical. Also gives every setup test a throwaway HOME and resets spawn mocks between tests, since MCP setup now writes real config files and would otherwise rewrite the developer's own agent settings; and teaches doctor about the `servers` and `context_servers` keys so those registrations are recognized.
The PATH lookup matched any executable named `hermes`, including an unrelated JavaScript engine that ships with common toolchains, so the picker pre-selected an agent the user did not have. Detection now prefers a false negative to a false positive: every agent is listed either way, so missing one costs a keystroke while pre-selecting a missing one is misleading. Also pins HOME and PATH for setup tests. Both feed agent detection, so leaving the real ones visible made results depend on what happened to be installed on the machine running the suite.
Zed's native remote MCP support is version-gated and its handling of request headers is inconsistent across releases, so a written entry can report success while the agent never connects. That reads as Firecrawl being broken, which is worse than not offering the agent at all. Removing it until the shape can be confirmed against a live install. Also pins the picker page size to the number of agents. The default was smaller than the list, so the last agent scrolled out of view.
Windsurf's remote entry shape is not settled: sources disagree on whether a transport field is required and what its value should be, and one reports streamable HTTP working only through a local proxy. A wrong entry does not error, it reports success and then exposes no tools, so this stays out until the shape can be confirmed against a live install. With every supported agent now carrying a verified environment-reference syntax and project-level config, the keyless-fallback and global-fallback branches no longer have a case. Removing them rather than leaving unreachable logic behind; they come back with the agent that needs them.
Six defects, four of them silent: * Quiet mode returned before the total-failure check, so a run in which nothing was written resolved successfully. `firecrawl init` and `firecrawl launch` both use quiet mode and reported success regardless. * The TOML writer split on "\n" only, so a config.toml with CRLF endings never matched its existing table and gained a duplicate one, leaving the file invalid and taking the rest of the user's Codex config with it. * The TOML writer absorbed comment and blank lines directly above the next table into the replaced range and deleted them. * A leading byte order mark was reported as a parse error even though the document parses, so a config written by a Windows editor was refused. * The rule fence required "\n" after its marker, so a file converted to CRLF gained a second copy of the section instead of an updated one. * `--agent all` reached only detected clients. It means every client, which is what the installer it replaced did. The fence replacement now uses a function so nothing in the rule body can be read as a replacement pattern. Line endings and byte order marks are preserved on write rather than normalised away.
…scope flag `firecrawl setup --yes --agent windsurf` installed skills and then aborted, because MCP setup rejected a name it writes no config for. An agent we support for skills but not for MCP is not an error: the run now finishes, skips the MCP step, and prints the server URL so the user can wire it up themselves. A name nothing supports is still rejected, so a typo does not silently do nothing. Scope: global is the intended default, so that one command reaches every agent surface rather than the current checkout alone. `--project` is the only scope flag that means anything on setup. `-g` is accepted for existing scripts but hidden from help and reported as deprecated when used, and the mutually exclusive scope error it existed for is gone. `-g` is untouched on init and launch. Tests also pin USERPROFILE and APPDATA alongside HOME. os.homedir() reads USERPROFILE on Windows, so the sandbox that keeps a test run away from the developer's own agent config was doing nothing there.
cc7e59b to
9162131
Compare
…keys Project scope fought the one-command-every-agent goal, and --agent hermes/openclaw aborted on a stored key while the boolean flags wrote keyless config.
There was a problem hiding this comment.
All reported issues were addressed across 7 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
… artifacts Three Firecrawl MCP configs were tracked at the repository root. Tests wrote them into the working directory and a broad `git add` swept them in, so anyone opening this checkout inherited MCP servers from the repo. Removed, and every setup test now runs in its own working directory so project-relative writes cannot reach the repository again. A stored key behaved three different ways depending on how the target was named: keyless for the boolean flags, keyless for `--agent hermes`, and a hard abort for `--agent all`. The README documents keyless. `--agent all` now agrees with the rest, and the launchers report through the same summary so the keyless fallback is stated rather than implied by a bare installer log line. `--agent launchers` was a synonym for every agent plus both launchers, which is not what the name says. It now selects the launchers. Launcher dispatch is exhaustive rather than treating anything that is not Hermes as OpenClaw, and doctor recognises OpenCode's top-level `mcp` map, which it previously reported as unregistered right after setup wrote it.
Why
setup mcpshelled out to a third-party installer, putting a package we don't control on the critical path of a first-run command and capping which agents we support. It also only ever installed the server; nothing told the agent to actually use Firecrawl for web work.Summary
setup mcpnow configures agents directly: detects what's installed, lists those in a picker (already selected), then offers to add rules that make agents prefer Firecrawl for web search and scraping.--rules/--no-rulesfor scripts.-ystays MCP-only, so automation never rewrites instruction files. If nothing is detected, setup asks for an agent flag (--cursor,--agent all) instead of opening an empty picker.-gis still accepted for existing scripts, hidden from help, and reported as deprecated when used.-gis unchanged oninitandlaunch.--agent windsurf, for example) finishes the run, skips the MCP step, and prints the server URL. A name nothing supports is still rejected.FIRECRAWL_API_KEYis exported where the agent will run, each agent gets a reference to that variable in the syntax it expands. Otherwise setup stays keyless and says so.--agent hermes/--agent openclawuse that same keyless fallback as--hermes/--openclaw; they no longer abort on a stored-only key. Direct helper calls withkeyless=falsestill refuse to put a stored key into config or argv.--agent allstill fail-closes on a stored-only key because that path reaches subprocess launchers.JSON.parserejects outright. A leading byte order mark is preserved rather than treated as a parse failure. TOML tables are replaced cleanly, including stale sub-tables from an earlier stdio entry, and CRLF files keep their line endings instead of gaining a duplicate table. Reruns are byte-identical.initandlaunchcannot report success over an unwritten config. Quiet mode also suppresses the standalone Hermes/OpenClaw installer lines.Also: setup tests get a throwaway home (
HOME, plusUSERPROFILEandAPPDATAso a Windows run cannot touch the developer's own agent config),doctorlearns VS Code'sserverskey, andjsonc-parseris added.Test Plan
pnpm test,type-check,format:check,buildsetup mcp --claude --cursorand-ywrite global config only;--projectis unknown--agent hermesand--hermesboth write keyless YAML;--agent openclawand--openclawboth write keyless OpenClaw config; no literal key on diskFIRECRAWL_API_KEY: each agent gets its native env reference, not the literalfirecrawl launch hermes/openclawstay quiet (no standalone installer lines)