Skip to content

feat(cli): configure MCP natively across coding agents - #187

Open
Max17190 wants to merge 8 commits into
mainfrom
feat/native-mcp-setup
Open

feat(cli): configure MCP natively across coding agents#187
Max17190 wants to merge 8 commits into
mainfrom
feat/native-mcp-setup

Conversation

@Max17190

@Max17190 Max17190 commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Why

setup mcp shelled 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 mcp now 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.

Claude Code
  MCP configured ~/.claude.json
  Rules installed ~/.claude/rules/firecrawl.md
Cursor
  MCP configured ~/.cursor/mcp.json
  Rules installed ~/.cursor/rules/firecrawl.mdc

Firecrawl MCP set up for 2/2 agents. Restart your agents to load it.
  • Seven agents. Claude Code, Cursor, VS Code, Codex, OpenCode, Hermes Agent, OpenClaw. Every config shape and environment-reference syntax is verified against vendor documentation; an agent stays out until it is. The launchers were previously flag-only, so the picker never offered them.
  • Flags. One per agent to skip the picker, --rules / --no-rules for scripts. -y stays 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.
  • Scope. Setup always writes global agent config: one command should reach every agent surface, not only the current checkout. -g is still accepted for existing scripts, hidden from help, and reported as deprecated when used. -g is unchanged on init and launch.
  • Skills-only agents. Naming an agent we install skills for but write no MCP config for (--agent windsurf, for example) finishes the run, skips the MCP step, and prints the server URL. A name nothing supports is still rejected.
  • Credentials. Never written as a literal. When FIRECRAWL_API_KEY is 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 openclaw use that same keyless fallback as --hermes / --openclaw; they no longer abort on a stored-only key. Direct helper calls with keyless=false still refuse to put a stored key into config or argv. --agent all still fail-closes on a stored-only key because that path reaches subprocess launchers.
  • Surgical edits. JSONC-aware patching preserves comments, which several supported agents ship by default and JSON.parse rejects 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.
  • Per-agent failure. A launcher shells out, so a missing binary is reported against that agent rather than ending the run. A run in which nothing was written fails for programmatic callers too, so init and launch cannot report success over an unwritten config. Quiet mode also suppresses the standalone Hermes/OpenClaw installer lines.

Also: setup tests get a throwaway home (HOME, plus USERPROFILE and APPDATA so a Windows run cannot touch the developer's own agent config), doctor learns VS Code's servers key, and jsonc-parser is added.

Test Plan

  • pnpm test, type-check, format:check, build
  • Interactive picker lists only detected agents, all pre-checked; empty detect asks for a flag
  • setup mcp --claude --cursor and -y write global config only; --project is unknown
  • Stored-only key: --agent hermes and --hermes both write keyless YAML; --agent openclaw and --openclaw both write keyless OpenClaw config; no literal key on disk
  • Exported FIRECRAWL_API_KEY: each agent gets its native env reference, not the literal
  • firecrawl launch hermes / openclaw stay quiet (no standalone installer lines)
  • Pre-existing comments, servers, and keys preserved; stale stdio entries replaced; reruns byte-identical
  • Malformed config isolated to its own agent; total failure under quiet mode still throws
  • Live check: valid key in header returns the full tool surface; invalid/missing key returns the keyless surface

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/utils/mcp-install.ts
Comment thread src/commands/setup.ts
Comment thread src/utils/mcp-clients.ts
};

export function resolveMcpClientId(agent: string): McpClientId | undefined {
return CLIENT_ALIASES[agent.trim().toLowerCase()];

@cubic-dev-ai cubic-dev-ai Bot Aug 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Suggested change
return CLIENT_ALIASES[agent.trim().toLowerCase()];
return Object.prototype.hasOwnProperty.call(CLIENT_ALIASES, agent.trim().toLowerCase())
? CLIENT_ALIASES[agent.trim().toLowerCase()]
: undefined;
Fix with cubic

Comment thread src/utils/mcp-clients.ts
'firecrawl.instructions.md'
),
},
detectPaths: (ctx) => [vscodeUserDir(ctx)],

@cubic-dev-ai cubic-dev-ai Bot Aug 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

Comment thread src/utils/mcp-clients.ts Outdated
Comment thread src/__tests__/commands/setup.test.ts Outdated
Comment thread src/__tests__/commands/setup.test.ts Outdated
Comment thread src/utils/mcp-install.ts Outdated
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}`);

@cubic-dev-ai cubic-dev-ai Bot Aug 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

Comment thread src/utils/mcp-install.ts
for (const [key, value] of Object.entries(entry)) {
if (typeof value === 'string') stringEntry[key] = value;
}
const { content, alreadyExists } = upsertTomlServer(

@cubic-dev-ai cubic-dev-ai Bot Aug 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

Comment thread src/utils/agents.ts
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') {

@cubic-dev-ai cubic-dev-ai Bot Aug 12, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>
Fix with cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/__tests__/utils/mcp-install.test.ts
Comment thread README.md
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.
@Max17190
Max17190 force-pushed the feat/native-mcp-setup branch from cc7e59b to 9162131 Compare August 12, 2026 21:55
…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.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

Comment thread src/commands/setup.ts
… 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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant