Skip to content

feat(backend): inject MCP servers in CLI passthrough mode per agent#1158

Open
carlosflorencio wants to merge 8 commits into
mainfrom
feature/mcp-servers-cli-agen-s1q
Open

feat(backend): inject MCP servers in CLI passthrough mode per agent#1158
carlosflorencio wants to merge 8 commits into
mainfrom
feature/mcp-servers-cli-agen-s1q

Conversation

@carlosflorencio
Copy link
Copy Markdown
Member

@carlosflorencio carlosflorencio commented May 29, 2026

CLI passthrough mode only ever wired kandev's own HTTP tools server into MCP config (and only for Claude) — codex, cursor, and opencode passthrough sessions got no MCP servers, and the servers configured on an agent profile's settings page were never applied. This adds a per-agent strategy that injects the full resolved server list into each CLI's native config shape so passthrough reaches parity with ACP mode, without ever writing to the user's global CLI config.

Important Changes

  • New PassthroughMCPStrategy (internal/agent/mcpconfig/passthrough.go): each passthrough agent declares how its CLI loads MCP servers. Claude → temp file + --mcp-config (additive); Codex → repeated -c mcp_servers.<name>.<key>=<json> overrides (no CODEX_HOME, so auth is untouched); Cursor → project-local .cursor/mcp.json, created only if absent; OpenCode → temp file + OPENCODE_CONFIG env var.
  • Runtime resolves servers once via the same path ACP uses (resolveMcpServersWithParams), prepends kandev's HTTP server, and drops a profile server named kandev so it can't shadow the real one. Written files are tracked (unioned across relaunch/resume/restart) and cleaned up; a user's pre-existing Cursor file is never overwritten or deleted.
  • MCP args are appended last so Claude's variadic --mcp-config can't swallow a positional prompt. Removes the old MCPConfigFlag/MCPConfigPath mechanism in favor of MCPStrategy/MCPArgs.
  • Decision recorded in ADR 0011.

Validation

  • go build ./..., go vet ./internal/agent/... — clean
  • go test ./internal/agent/... — 1171 passed (sole failure is the pre-existing, unrelated TestConnectWorkspaceStream_BackoffDrainsOnStop, confirmed failing on the clean tree)
  • golangci-lint run on mcpconfig/agents/lifecycle — 0 issues
  • gofmt clean

Possible Improvements

Codex currently loads url-based MCP servers natively; if a pinned Codex build ever regresses to requiring experimental_use_rmcp_client, the kandev tools server would silently not load (documented at the call site). Cursor's skip-if-exists means kandev tools are absent for Cursor when a user .cursor/mcp.json already exists.

Checklist

  • Tests added/updated
  • Docs updated
  • Self-reviewed

Preview Environment

URL https://kandev-pr-1158-bwo7.sprites.app
Commit 1537528
Agent Mock agent

Updates automatically on each push. Destroyed when the PR is closed.

@claude
Copy link
Copy Markdown

claude Bot commented May 29, 2026

Claude finished @carlosflorencio's task in 5m 27s —— View job


Findings

Suggestion (recommended, doesn't block)

  1. Codex: server names containing . produce malformed TOML key pathsapps/backend/internal/agent/mcpconfig/passthrough.go:202

    • Issue: "mcp_servers." + srv.Name + "." + key with a name like "my.server" produces mcp_servers.my.server.command=..., which TOML parses as a deeply nested path (my → server → command) rather than a server named "my.server". The server is silently not injected at the expected name.
    • Fix: sanitize the name for the -c key path (strings.ReplaceAll(srv.Name, ".", "_")), or add a doc comment noting Codex server names must not contain dots.
  2. Codex: env vars and headers are in process args, visible via psapps/backend/internal/agent/mcpconfig/passthrough.go:186

    • Issue: srv.Env entries (e.g. GITHUB_TOKEN) and srv.Headers (e.g. Authorization: Bearer …) are serialized as JSON inside -c overrides and appear in the process argument list — readable by any local user via ps aux.
    • Fix: This is inherent to Codex's CLI override mechanism (no file-based injection path). A comment at the call site noting this trade-off would help future maintainers understand why it wasn't avoided.
  3. applyPassthroughMCP calls getAgentConfigForExecution redundantlyapps/backend/internal/agent/runtime/lifecycle/manager_passthrough.go:293

    • Issue: Both passthroughAgentCommand and freshPassthroughCommand already load the agent config before calling applyPassthroughMCP, but applyPassthroughMCP re-fetches it internally. The lookup is cheap (in-memory registry), so no correctness impact.
    • Fix: Thread agentConfig agents.Agent into applyPassthroughMCP as a parameter to avoid the second fetch.
  4. Missing port-absent tests for freshPassthroughCommand / resumePassthroughCommandapps/backend/internal/agent/runtime/lifecycle/manager_passthrough_test.go:403

    • Issue: TestPassthroughAgentCommandErrorsWhenMCPPortMissing only exercises the passthroughAgentCommand path. The restart (freshPassthroughCommand) and resume (resumePassthroughCommand) paths route through the same check — confirming those callers propagate the error correctly would round out coverage.
    • Fix: Two small tests mirroring the existing one, calling freshPassthroughCommand and resumePassthroughCommand with standalonePort = 0.

Summary

Severity Count
Blocker 0
Suggestion 4

Verdict: Ready with suggestions — the design is sound, the strategy abstraction is clean, cleanup is properly wired (verified in manager_lifecycle.go:347), and test coverage is solid. The Codex dot-in-name edge case (#1) is the most actionable fix before merge.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 29, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR refactors how MCP servers are injected into passthrough-mode agent invocations by introducing a strategy pattern that lets each CLI (Claude, Codex, Cursor, OpenCode) emit its own config format. The runtime resolves servers once and delegates materialization to agent-specific strategies, replacing the prior monolithic config-flag approach.

Changes

Passthrough MCP Strategy Refactoring

Layer / File(s) Summary
MCP Strategy Contract & Data Types
apps/backend/internal/agent/mcpconfig/passthrough.go
Defines the PassthroughMCPStrategy interface, PassthroughPaths/PassthroughConfigFile/PassthroughArtifacts types, and helpers used by strategies.
Strategy Implementations
apps/backend/internal/agent/mcpconfig/passthrough.go, apps/backend/internal/agent/mcpconfig/passthrough_test.go
Implements Claude/Codex/Cursor/OpenCode strategies that produce files/CLI args/env from resolved types.McpServer entries; tests validate file content, arg shapes, env vars, name normalization/quoting, and interface compliance.
Agent Configuration & Type Updates
apps/backend/internal/agent/agents/agent.go, claude_acp.go, codex_acp.go, cursor_acp.go, opencode_acp.go, helpers.go, passthrough.go
PassthroughOptions gains MCPArgs []string and PassthroughConfig uses MCPStrategy instead of MCPConfigFlag; agents import mcpconfig and declare per-CLI strategies; CmdBuilder.MCPConfig removed; BuildPassthroughCommand now appends MCPArgs.
Runtime Orchestration & File Management
apps/backend/internal/agent/runtime/lifecycle/manager_passthrough.go
Adds applyPassthroughMCP to resolve kandev+profile servers, call the agent's strategy, write generated files with SkipIfExists and workspace-escape checks, track injected files/env in execution metadata, and return extra CLI args.
Passthrough Command Integration
apps/backend/internal/agent/runtime/lifecycle/manager_passthrough.go, apps/backend/internal/agent/agents/passthrough.go
Command builders now accept context.Context, call applyPassthroughMCP to obtain MCPArgs, wire them into PassthroughOptions.MCPArgs, and merge MCP-provided env vars into the PTY environment.
Lifecycle & Strategy Tests
apps/backend/internal/agent/mcpconfig/passthrough_test.go, apps/backend/internal/agent/runtime/lifecycle/manager_passthrough_test.go
Adds tests covering all strategies’ outputs, ordering of MCP args, metadata persistence/rehydration, file tracking across relaunches, collision handling, symlink containment, and new error paths.
Architecture Decision Record
docs/decisions/0011-passthrough-mcp-injection-strategies.md, docs/decisions/INDEX.md
Documents the strategy-based design, per-CLI mechanisms, runtime responsibilities, sub-decisions (args appended last, Claude additive, Cursor skip-if-exists), consequences, and alternatives considered; updates ADR index.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • kdlbs/kandev#1078: Introduces the earlier MCP passthrough wiring (MCPConfigPath/MCPConfigFlag) that this PR replaces with strategy-driven MCPArgs artifact generation.
  • kdlbs/kandev#641: Related work on injecting the local kandev MCP server variants (SSE and HTTP) used by passthrough/ACP flows.

Poem

🐰 Four CLI paths, one tools spring,
I hop and write the configs they bring.
Claude gets JSON, Codex gets flags,
Cursor keeps projects safe in their bags,
OpenCode points env—now passthrough sings.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 29.09% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main architectural change: moving from a single MCP config approach to a per-agent strategy-driven injection model for CLI passthrough mode.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description is comprehensive and well-structured, following the template with all required sections completed.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/mcp-servers-cli-agen-s1q

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Comment thread apps/backend/internal/agent/mcpconfig/passthrough.go Outdated
Comment thread apps/backend/internal/agent/mcpconfig/passthrough.go
Comment thread apps/backend/internal/agent/runtime/lifecycle/manager_passthrough.go Outdated
@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 29, 2026

Greptile Summary

This PR brings passthrough (raw CLI) sessions to parity with ACP mode by injecting the full resolved MCP server list — kandev's own HTTP server plus profile-configured servers — into each CLI's native config shape without touching the user's global CLI config. It introduces a PassthroughMCPStrategy interface with four implementations: Claude (temp file + --mcp-config), Codex (repeated -c overrides), Cursor (project-local .cursor/mcp.json, skip-if-exists), and OpenCode (temp file + OPENCODE_CONFIG env var).

  • New strategy interface and four implementations in mcpconfig/passthrough.go, each mapping kandev's transport model to the target CLI's native MCP config shape; comprehensive unit tests cover filtering, serialisation, and all transport mappings.
  • Lifecycle manager gains applyPassthroughMCP, symlink-escape–safe file materialization, O_EXCL Cursor writes, and cleanup tracking that unions files across relaunch/resume/restart calls; Codex secret-bearing args are redacted in logs.
  • PassthroughConfig.MCPConfigFlag/MCPConfigPath removed in favor of MCPStrategy/MCPArgs; MCP args are appended last in BuildPassthroughCommand so Claude's variadic --mcp-config can't swallow a positional prompt.

Confidence Score: 5/5

Safe to merge. The core MCP injection paths are well-tested across all four agents, the symlink-escape and O_EXCL guards are solid, and no correctness bugs were found on the changed paths.

All four strategies serialize correctly, the workspace escape check and O_EXCL write prevent symlink-redirect attacks, cleanup tracking works across relaunch/resume/restart, and the test suite provides end-to-end coverage of every meaningful branch. The one finding — partial file tracking not flushed before an error return — has no impact with today's single-file-per-strategy constraint and is a forward-looking hardening item only.

apps/backend/internal/agent/runtime/lifecycle/manager_passthrough.go — the writePassthroughMCPFiles early-return path in the error case.

Important Files Changed

Filename Overview
apps/backend/internal/agent/runtime/lifecycle/manager_passthrough.go Core passthrough lifecycle logic with MCP injection, symlink-escape detection, O_EXCL writes, and cleanup tracking; writePassthroughMCPFiles has a latent tracking gap on error that would orphan already-written files in any future multi-file strategy.
apps/backend/internal/agent/mcpconfig/passthrough.go New file implementing four PassthroughMCPStrategy implementations (Claude, Codex, Cursor, OpenCode); serialization, filtering, and TOML key quoting are correct; empty-entries guard present after previous fix.
apps/backend/internal/agent/agents/passthrough.go BuildPassthroughCommand correctly appends MCPArgs last so Claude's variadic --mcp-config cannot swallow a positional prompt; logic is clean and well-tested.
apps/backend/internal/agent/mcpconfig/passthrough_test.go Thorough unit tests covering all four strategies, empty-entries guard, TOML quoting, transport mapping, and the interface-implementation check.
apps/backend/internal/agent/runtime/lifecycle/manager_passthrough_test.go Good integration-level coverage for all four agents, union-tracking across relaunches, symlink escape, redaction, cleanup on RemoveExecution, and MCP port absence errors.

Reviews (7): Last reviewed commit: "fix(backend): redact MCP secrets at inte..." | Re-trigger Greptile

Comment thread apps/backend/internal/agent/mcpconfig/passthrough.go
Comment thread apps/backend/internal/agent/runtime/lifecycle/manager_passthrough.go Outdated
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
apps/backend/internal/agent/mcpconfig/passthrough.go (1)

153-205: 💤 Low value

Codex server names are interpolated raw into the dotted -c key path.

mcp_servers."+srv.Name+"."+p.key inlines srv.Name into Codex's dotted config-key path. A profile server whose name contains a . (or other char Codex treats specially in a key path) would silently target the wrong nested key. kandev/github names are safe, but profile-supplied names may not be — worth confirming Codex's key-path grammar and/or sanitizing names.

Codex CLI -c override config key path syntax for nested keys with dots, e.g. mcp_servers.<name>.command
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/backend/internal/agent/mcpconfig/passthrough.go` around lines 153 - 205,
The code currently in codexServerArgs inlines srv.Name into the dotted override
key ("mcp_servers."+srv.Name+"."+p.key) which can silently mis-target nested
keys if the name contains '.' or other special characters; fix this by
validating/sanitizing server names early: in codexServerArgs (or called from
BuildPassthroughMCP) ensure srv.Name matches a safe pattern (e.g. regex
^[A-Za-z0-9_-]+$) and return a clear error if it contains disallowed characters
(like '.'), or alternatively normalize/escape disallowed characters before
composing the "-c" token so the produced key path cannot be interpreted as
nested (update the error text and validation in codexServerArgs and callers
accordingly).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/decisions/0011-passthrough-mcp-injection-strategies.md`:
- Line 40: The table's OpenCode cell contains unescaped pipe characters (the
JSON snippet referencing OPENCODE_CONFIG and the
{"mcp":{name:{type:"local"|"remote",…}}} payload) which breaks the 4-column
table; fix by escaping the inner pipe characters or wrapping the entire JSON
snippet in inline code/backticks so the pipes are treated as literal characters
(update the cell that includes OpenCode and OPENCODE_CONFIG to use escaped pipes
or backticks to restore proper Markdown table rendering).

---

Nitpick comments:
In `@apps/backend/internal/agent/mcpconfig/passthrough.go`:
- Around line 153-205: The code currently in codexServerArgs inlines srv.Name
into the dotted override key ("mcp_servers."+srv.Name+"."+p.key) which can
silently mis-target nested keys if the name contains '.' or other special
characters; fix this by validating/sanitizing server names early: in
codexServerArgs (or called from BuildPassthroughMCP) ensure srv.Name matches a
safe pattern (e.g. regex ^[A-Za-z0-9_-]+$) and return a clear error if it
contains disallowed characters (like '.'), or alternatively normalize/escape
disallowed characters before composing the "-c" token so the produced key path
cannot be interpreted as nested (update the error text and validation in
codexServerArgs and callers accordingly).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 2e8dedcb-bdda-4d8e-808f-b22a6b7e057f

📥 Commits

Reviewing files that changed from the base of the PR and between b109c84 and ed90273.

📒 Files selected for processing (13)
  • apps/backend/internal/agent/agents/agent.go
  • apps/backend/internal/agent/agents/claude_acp.go
  • apps/backend/internal/agent/agents/codex_acp.go
  • apps/backend/internal/agent/agents/cursor_acp.go
  • apps/backend/internal/agent/agents/helpers.go
  • apps/backend/internal/agent/agents/opencode_acp.go
  • apps/backend/internal/agent/agents/passthrough.go
  • apps/backend/internal/agent/mcpconfig/passthrough.go
  • apps/backend/internal/agent/mcpconfig/passthrough_test.go
  • apps/backend/internal/agent/runtime/lifecycle/manager_passthrough.go
  • apps/backend/internal/agent/runtime/lifecycle/manager_passthrough_test.go
  • docs/decisions/0011-passthrough-mcp-injection-strategies.md
  • docs/decisions/INDEX.md
💤 Files with no reviewable changes (1)
  • apps/backend/internal/agent/agents/helpers.go

Comment thread docs/decisions/0011-passthrough-mcp-injection-strategies.md Outdated
@claude
Copy link
Copy Markdown

claude Bot commented May 29, 2026

Claude finished @carlosflorencio's task in 3m 5s —— View job


Findings

Suggestion (recommended, doesn't block)

  1. ClaudeStrategy: no guard when all entries are filteredapps/backend/internal/agent/mcpconfig/passthrough.go:99

    • Issue: After filtering by srv.Name == "" || srv.Name == claudeReservedServerName, if entries ends up empty, ClaudeStrategy still marshals {"mcpServers":{}} to disk and returns --mcp-config <path>. CodexStrategy (line 166) short-circuits with if len(args) == 0 { return ... } — the two are inconsistent.
    • Why: Can't fire today (the prepended kandev server uses name "kandev", never "workspace"), but a future caller that passes a server list where every name is reserved or empty would silently inject a no-op config file instead of returning empty artifacts.
    • Fix: Add if len(entries) == 0 { return PassthroughArtifacts{}, nil } after the filter loop, mirroring the CodexStrategy pattern.
  2. ADR table line 40: unescaped pipe breaks markdowndocs/decisions/0011-passthrough-mcp-injection-strategies.md:40

    • Issue: The | inside the JSON literal in the OpenCode cell ("local"|"remote") is parsed as a column separator, giving the row 5 cells in a 4-column table. GitHub renders it leniently but markdownlint MD056 flags it. Fix this →
    • Fix: Escape as \|. (Inline suggestion already posted.)

Summary

Severity Count
Blocker 0
Suggestion 2

Verdict: Ready with suggestions — the strategy abstraction is clean, the four CLI-specific implementations are well-tested, cleanup is correctly wired, the metadata dual-shape tolerance (in-memory vs JSON-rehydrated) is thorough, and the previous reviewer's four suggestions (Codex dot-in-name, ps-visible tokens, redundant config fetch, missing port-absent tests) were all addressed in the follow-up commit 84bb172. The two remaining items are minor.

Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

4 issues found across 13 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="apps/backend/internal/agent/runtime/lifecycle/manager_passthrough.go">

<violation number="1" location="apps/backend/internal/agent/runtime/lifecycle/manager_passthrough.go:374">
P1: Validate parent directories against symlinks before `MkdirAll`/`WriteFile`; otherwise a symlinked `.cursor` path can redirect writes outside the intended workspace.

(Based on your team's feedback about validating parent chain before MkdirAll.) [FEEDBACK_USED]</violation>
</file>

<file name="apps/backend/internal/agent/mcpconfig/passthrough.go">

<violation number="1" location="apps/backend/internal/agent/mcpconfig/passthrough.go:109">
P3: Add a guard for `len(entries) == 0` before writing the Claude config; otherwise an empty `mcpServers` file is still emitted and `--mcp-config` is injected with no effective servers.</violation>

<violation number="2" location="apps/backend/internal/agent/mcpconfig/passthrough.go:184">
P1: Avoid passing `srv.Env` and `srv.Headers` through `-c` argv overrides because this exposes secret values in the process command line to other local users.</violation>

<violation number="3" location="apps/backend/internal/agent/mcpconfig/passthrough.go:202">
P2: Codex passthrough builds `-c` keys from unvalidated server names, so names with `.` (or other path-significant chars) can produce incorrect `mcp_servers` paths and fail to load the intended server.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/backend/internal/agent/runtime/lifecycle/manager_passthrough.go Outdated
Comment thread apps/backend/internal/agent/mcpconfig/passthrough.go
Comment thread apps/backend/internal/agent/mcpconfig/passthrough.go Outdated
Comment thread apps/backend/internal/agent/mcpconfig/passthrough.go
Comment thread docs/decisions/0011-passthrough-mcp-injection-strategies.md Outdated
Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

1 issue found across 4 files (changes from recent commits).

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread apps/backend/internal/agent/mcpconfig/passthrough.go Outdated
@claude
Copy link
Copy Markdown

claude Bot commented May 29, 2026

Findings

Suggestion (recommended, doesn't block)

  1. ClaudeStrategy: no guard when all entries are filteredapps/backend/internal/agent/mcpconfig/passthrough.go:99

    • Issue: After filtering by srv.Name == "" || srv.Name == claudeReservedServerName, if entries ends up empty, ClaudeStrategy still marshals {"mcpServers":{}} to disk and returns --mcp-config <path>. CodexStrategy (line 166) short-circuits with if len(args) == 0 { return ... } — the two are inconsistent.
    • Why: Can't fire today (the prepended kandev server uses name "kandev", never "workspace"), but a future caller that passes a server list where every name is reserved or empty would silently inject a no-op config file instead of returning empty artifacts. The inconsistency also makes ClaudeStrategy a surprising edge case to test or reason about.
    • Fix: Add if len(entries) == 0 { return PassthroughArtifacts{}, nil } after the filter loop, mirroring the CodexStrategy pattern.
  2. ADR table line 40: unescaped pipe breaks markdowndocs/decisions/0011-passthrough-mcp-injection-strategies.md:40

    • Issue: The | inside the JSON literal in the OpenCode cell ("local"|"remote") is parsed as a column separator, giving the row 5 cells in a 4-column table. GitHub renders it leniently but markdownlint MD056 flags it.
    • Fix: Escape as \|. (Inline suggestion posted.)

Summary

Severity Count
Blocker 0
Suggestion 2

Verdict: Ready with suggestions — the strategy abstraction is clean, the four CLI-specific implementations are well-tested, cleanup is correctly wired, the metadata dual-shape tolerance (in-memory vs JSON-rehydrated) is thorough, and the previous reviewer's four suggestions were all addressed in the follow-up commit (84bb172). The two remaining items are both minor.

@claude
Copy link
Copy Markdown

claude Bot commented May 29, 2026

Claude finished @carlosflorencio's task in 3m 24s —— View job


Findings

Suggestion (recommended, doesn't block)

  1. Dead len(servers) == 0 guardapps/backend/internal/agent/runtime/lifecycle/manager_passthrough.go:296

    • passthroughMCPServers unconditionally prepends the kandev server, so servers can never be empty after a successful return. The only empty-result path goes through an error (caught on the line above). The guard was flagged by Greptile but not removed in f098366; it creates a misleading impression of a reachable case.
    • Fix: Remove lines 296–298. (Inline suggestion posted.)
  2. CursorStrategy and OpenCodeStrategy lack the empty-entries guardapps/backend/internal/agent/mcpconfig/passthrough.go:286 / :346

    • ClaudeStrategy gained if len(entries) == 0 { return PassthroughArtifacts{}, nil } in f098366; both CursorStrategy and OpenCodeStrategy still miss it. If all server names happen to be empty, each writes a noisy but empty config file ({"mcpServers":{}} / {"mcp":{}}) instead of returning no-op artifacts. Unreachable today (kandev server is always named "kandev"), but the inconsistency is a latent trap for future callers or tests.
    • Fix: Add the same guard after each strategy's filter loop. (Inline suggestions posted.)
  3. Missing test for the len(entries) == 0 guard in ClaudeStrategyapps/backend/internal/agent/mcpconfig/passthrough_test.go:108

    • The new guard at passthrough.go:109 has no test exercising it. TestClaudeStrategy_SkipsReservedAndEmpty keeps one valid server ("ok"), so the early-return path is never reached. A single test with only "workspace" + "" names would close the gap.
    • Fix: Add TestClaudeStrategy_AllEntriesFiltered. (Inline suggestion with code posted.)

Summary

Severity Count
Blocker 0
Suggestion 3

Verdict: Ready with suggestions — all four previous review rounds' findings (Codex dot-in-name, ps-visible tokens, redundant config fetch, missing port-absent tests, ClaudeStrategy empty-entries guard, ADR pipe escaping, symlink containment) were fully addressed by f098366. The three remaining items are minor consistency/cleanup issues; none block merge.

Comment thread apps/backend/internal/agent/runtime/lifecycle/manager_passthrough.go Outdated
Comment thread apps/backend/internal/agent/mcpconfig/passthrough.go
Comment thread apps/backend/internal/agent/mcpconfig/passthrough.go
Comment thread apps/backend/internal/agent/mcpconfig/passthrough_test.go
@claude
Copy link
Copy Markdown

claude Bot commented May 29, 2026

Claude finished @carlosflorencio's task in 3m 27s —— View job


Findings

All previous-round findings (Codex TOML dot-quoting, ps-visibility comment, redundant getAgentConfigForExecution fetch, port-absent tests for fresh/resume, ClaudeStrategy empty-entries guard, ADR pipe escaping, symlink escape protection, CursorStrategy/OpenCodeStrategy empty-entries guards, missing all-filtered test) have been resolved across commits 84bb172, f098366, and 7757775.

No new issues found in the current tree.

Summary

Severity Count
Blocker 0
Suggestion 0

Verdict: Ready to merge — the strategy abstraction is clean, all four CLI-specific implementations are well-tested (including the edge-case TestStrategies_NoArtifactsWhenAllServersFiltered and TestWorkspacePathEscapesViaSymlink), the metadata dual-shape tolerance for post-restart rehydration is thorough, the dead unreachable guard was removed with a clarifying comment, and all reviewer suggestions from four prior passes were addressed.

@claude
Copy link
Copy Markdown

claude Bot commented May 29, 2026

Findings

All previous-round findings (Codex TOML dot-quoting, ps-visibility comment, redundant getAgentConfigForExecution fetch, port-absent tests for fresh/resume, ClaudeStrategy empty-entries guard, ADR pipe escaping, symlink escape protection, CursorStrategy/OpenCodeStrategy empty-entries guards, missing all-filtered test) have been resolved across commits 84bb172, f098366, and 7757775.

No new issues found in the current tree.

Summary

Severity Count
Blocker 0
Suggestion 0

Verdict: Ready to merge — the strategy abstraction is clean, all four CLI-specific implementations are well-tested (including the edge-case TestStrategies_NoArtifactsWhenAllServersFiltered and TestWorkspacePathEscapesViaSymlink), the metadata dual-shape tolerance for post-restart rehydration is thorough, the dead unreachable guard was removed with a clarifying comment, and all reviewer suggestions from four prior passes were addressed.

@claude
Copy link
Copy Markdown

claude Bot commented May 29, 2026

Claude finished @carlosflorencio's task in 2m 12s —— View job


Findings

All previous-round findings have been resolved across commits 84bb172, f098366, and 7757775:

  • Codex TOML dot-quoting (→ codexKeyName with JSON-encoded quoted keys)
  • ps-visible tokens (→ NOTE comment at call site)
  • Redundant getAgentConfigForExecution fetch (→ threaded agentConfig parameter)
  • Missing port-absent tests for freshPassthroughCommand / resumePassthroughCommand (→ added)
  • ClaudeStrategy empty-entries guard (→ if len(entries) == 0 early return)
  • ADR pipe escaping (→ local\|remote in table cell)
  • Symlink escape protection (→ workspacePathEscapes with EvalSymlinks containment check)
  • CursorStrategy / OpenCodeStrategy empty-entries guards (→ matched ClaudeStrategy pattern)
  • Missing all-filtered test (→ TestStrategies_NoArtifactsWhenAllServersFiltered)
  • Dead unreachable guard (→ removed with clarifying comment)

The new commit (9047aed) adds one documentation bullet to docs/add-agent-cli.md:144 — it accurately describes MCPStrategy, the four available strategies, the extension point for new mechanisms, and points to ADR 0011 and the source file. Nothing to flag.

Summary

Severity Count
Blocker 0
Suggestion 0

Verdict: Ready to merge — the strategy abstraction is clean, all four CLI-specific implementations are well-tested (including TestStrategies_NoArtifactsWhenAllServersFiltered, TestWorkspacePathEscapesViaSymlink, port-absent error propagation across all three command builders), metadata dual-shape tolerance for post-restart rehydration is thorough, and the documentation accurately describes the pattern for future agent authors.

@claude
Copy link
Copy Markdown

claude Bot commented May 30, 2026

Claude finished @carlosflorencio's task in 5m 7s —— View job


Findings

  • Load review skill and gather context
  • Examine the latest commit changes (3317519)
  • Review changed files in depth
  • Post inline findings

Findings

Blocker (must fix before merge)

  1. Missed log site in interactive_lifecycle.go — Codex secrets still written to backend logsmanager_passthrough.go:692 (inline comment posted)
    • Issue: 3317519 redacted cmd.Args() at three manager_passthrough.go log sites, but buildInteractiveStartRequest (line 644) still passes the unredacted args as Command: cmd.Args(). The InteractiveRunner.StartimmediateStartProcess path logs req.Command verbatim at interactive_lifecycle.go:211. Since all four interactiveRunner.Start calls in manager_passthrough.go use ImmediateStart=true, this fires on every passthrough start, restart, resume, and resume-fallback.
    • Fix: Either (A) add a LogCommand []string field to InteractiveStartRequest carrying redactPassthroughArgs(cmd.Args()) and log that in interactive_lifecycle.go, or (B) drop the command field from those two log lines in interactive_lifecycle.go entirely — the redacted version is already logged before .Start() in manager_passthrough.go.

Summary

Severity Count
Blocker 1
Suggestion 0

Verdict: Blocked — the security fix introduced in 3317519 is incomplete. One log site in interactive_lifecycle.go:211 still writes Codex MCP tokens (env vars, HTTP headers) to backend logs on every passthrough session launch. The symlink-hardening changes (Lstat + O_EXCL) and the redaction logic itself are correct; only the missing log site needs addressing.

Copy link
Copy Markdown

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

Choose a reason for hiding this comment

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

2 issues found across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Passthrough mode previously wired only kandev's own HTTP server (Claude only)
into MCP config; codex/cursor/opencode got nothing and profile-configured
servers were never applied. Add a per-agent PassthroughMCPStrategy that
materializes the resolved server list (kandev + profile servers, via the same
resolver ACP uses) into each CLI's native shape without touching the user's
global config:

- Claude: temp file + --mcp-config (additive)
- Codex:  repeated -c mcp_servers.<name>.<key>=<json> overrides
- Cursor: project-local .cursor/mcp.json, created only if absent
- OpenCode: temp file + OPENCODE_CONFIG env var

Replaces the old MCPConfigFlag/MCPConfigPath mechanism. See ADR 0011.
- Quote Codex server names containing dots so the -c key path isn't misread as TOML nesting
- Thread agentConfig through applyPassthroughMCP (drop redundant registry lookup)
- Document that Codex env/headers land in process args (ps-visible), unavoidable with -c
- Add port-absent tests for freshPassthroughCommand and resumePassthroughCommand
- Guard the Cursor workspace write against a symlinked parent escaping the
  worktree (mirrors the copyfiles containment check)
- codexKeyName now JSON-encodes non-bare names, escaping control chars as well
  as dots/quotes for valid TOML keys
- ClaudeStrategy emits nothing when all servers are filtered out (no empty
  --mcp-config), matching CodexStrategy
- Escape the pipe in the ADR 0011 table cell
…egies

- Cursor and OpenCode strategies now also return no artifacts when every server
  is filtered out (matching Claude), so no empty mcp.json / OPENCODE_CONFIG is emitted
- Drop the now-unreachable len(servers)==0 guard in applyPassthroughMCP (the
  kandev server is always present); strategies guard their own empty case
- Add TestStrategies_NoArtifactsWhenAllServersFiltered
…ink write

Two security issues introduced by passthrough MCP injection:

- Codex serializes MCP env/http_headers into -c argv, which the passthrough
  start/resume paths logged via cmd.Args(), writing tokens into backend logs.
  Redact mcp_servers.*.(env|http_headers) values at every log site (the executed
  command is unchanged).
- The Cursor workspace write guarded symlinked parents but probed existence with
  os.Stat (follows symlinks), so a committed dangling .cursor/mcp.json symlink
  would be written through to an outside path. Probe with os.Lstat and write with
  O_CREATE|O_EXCL so a leaf symlink (even racing in) is never followed.

Regression tests for both.
main landed ADR 0011 (transient provider-error retry); the passthrough MCP
ADR is renumbered to 0012. File rename and INDEX were resolved during the
rebase; this fixes the lingering reference in add-agent-cli.md.
…n orphan config

- The interactive runner logged req.Command verbatim on every passthrough
  start/restart/resume, re-leaking the Codex MCP -c env/header tokens that the
  manager log sites redacted. Add a LogCommand field (redacted) and log it.
- writeFileNoFollow now removes the empty/partial file on write or close failure
  so a later SkipIfExists probe doesn't skip writing the real config.
@carlosflorencio carlosflorencio force-pushed the feature/mcp-servers-cli-agen-s1q branch from 3317519 to 1537528 Compare May 30, 2026 18:33
@claude
Copy link
Copy Markdown

claude Bot commented May 30, 2026

Claude finished @carlosflorencio's task in 2m 15s —— View job


Findings

All previous-round findings have been fully resolved across commits 84bb172, f098366, 7757775, and now 1537528:

  • Interactive runner log site (interactive_lifecycle.go:156,211) — both log sites now call req.commandForLog() instead of req.Command. LogCommand is populated with redactPassthroughArgs(cmd.Args()) in buildInteractiveStartRequest, so all four call paths (start, restart, resume, fallback) log the redacted command. ✓
  • Orphan file on write/close failure (writeFileNoFollow) — os.Remove(path) is now called on both the write-failure and close-failure paths, so a later SkipIfExists probe won't find a partial/empty file and silently skip the real config. ✓
  • Codex TOML dot-quoting (codexKeyName) — JSON-encodes non-bare-key names so dots and control chars are correct. ✓
  • ps-visible tokens — NOTE comment at call site documents the unavoidable trade-off. ✓
  • Redundant getAgentConfigForExecution fetchagentConfig now threaded through as a parameter. ✓
  • Missing port-absent tests for freshPassthroughCommand / resumePassthroughCommand — added. ✓
  • ClaudeStrategy empty-entries guardif len(entries) == 0 early return present. ✓
  • CursorStrategy / OpenCodeStrategy empty-entries guards — both added. ✓
  • ADR pipe escapinglocal\|remote in table cell. ✓
  • Symlink escape protectionworkspacePathEscapes with EvalSymlinks containment check + O_EXCL. ✓
  • Dead unreachable guard — removed with clarifying comment. ✓
  • redactPassthroughArgs testTestRedactPassthroughArgsRedactsCodexSecrets covers all cases. ✓

No new issues found.

Summary

Severity Count
Blocker 0
Suggestion 0

Verdict: Ready to merge — the strategy abstraction is clean, all four CLI-specific implementations are well-tested (including edge cases: all-servers-filtered, TOML-quoted names, dangling-symlink leaf, workspace-path-escaping via symlink, port-absent error propagation across all three command builders), the metadata dual-shape tolerance for post-restart rehydration is thorough, and all blocker and suggestion findings from five prior review passes have been addressed.

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