Skip to content

fix(opencode): preserve config when updating MCP servers#1623

Merged
arnestrickmann merged 2 commits intogeneralaction:mainfrom
jasperan:fix/1616-opencode-mcp-preserve-config
Mar 29, 2026
Merged

fix(opencode): preserve config when updating MCP servers#1623
arnestrickmann merged 2 commits intogeneralaction:mainfrom
jasperan:fix/1616-opencode-mcp-preserve-config

Conversation

@jasperan
Copy link
Copy Markdown
Contributor

@jasperan jasperan commented Mar 28, 2026

Summary

  • treat OpenCode opencode.json as JSONC-capable even though the filename ends in .json
  • preserve sibling keys and comments when updating only the MCP subtree
  • fail safely on unreadable provider configs instead of silently resetting or partially mutating state

Fixes #1616

Test Plan

  • pnpm exec vitest run src/test/main/mcp/configPaths.test.ts src/test/main/mcp/configIO.test.ts src/test/main/mcp/McpService.test.ts
  • pnpm run type-check

Summary by CodeRabbit

  • New Features

    • OpenCode configs now support JSONC (comments, trailing commas) with safe parsing and preservation of non-server keys/comments.
  • Improvements

    • Multi-agent save/remove now use a read-then-write flow with clearer error reporting that distinguishes read vs write failures and reports partially-updated agents.
    • Safer template cloning and more robust handling when creating or updating configs.
  • Tests

    • Added coverage for JSONC handling and multi-provider failure scenarios.
  • Documentation

    • Integration docs updated to reflect the new flow and error semantics.

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 28, 2026

@jasperan is attempting to deploy a commit to the General Action Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 28, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 58dce8e1-a5eb-45d1-9b98-002d2f589c4d

📥 Commits

Reviewing files that changed from the base of the PR and between bd7db0f and 2f1a8aa.

📒 Files selected for processing (4)
  • agents/integrations/mcp.md
  • src/main/services/McpService.ts
  • src/main/services/mcp/configIO.ts
  • src/test/main/mcp/McpService.test.ts
✅ Files skipped from review due to trivial changes (2)
  • agents/integrations/mcp.md
  • src/main/services/mcp/configIO.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/test/main/mcp/McpService.test.ts
  • src/main/services/McpService.ts

📝 Walkthrough

Walkthrough

Refactors MCP service to perform an atomic read/parse phase across agents before any writes, accumulates per-agent pending writes, delays error throwing until after all agents are processed, and adds strict JSONC parsing and preserving JSONC-aware read/write behavior.

Changes

Cohort / File(s) Summary
Service Error Handling
src/main/services/McpService.ts
Introduce two‑phase flow: collect PendingWrite entries per agent after safe reads, track readFailures and writeFailures separately, defer writes, and throw `buildConfigFailureError('read'
JSONC Configuration I/O
src/main/services/mcp/configIO.ts
Introduce JSONC helpers (isJsoncConfig, parseJsoncConfig, cloneTemplate); replace lenient parsing with strict safe-parse; ensure JSONC writes validate existing content and preserve comments/structure when updating serversPath; use template cloning for empty targets.
Config Metadata & Paths
src/main/services/mcp/configPaths.ts, src/shared/mcp/types.ts
Add optional isJsonc?: boolean to AgentMcpMeta/AgentConfigDef, mark opencode as isJsonc: true, and propagate flag via getAgentMcpMeta.
Service Tests
src/test/main/mcp/McpService.test.ts
Expand multi-provider tests: simulate readServers failures for opencode, test write failure messages including partial-success reporting, and adjust fixtures/mocks for agent list and opencode meta.
JSONC I/O Tests
src/test/main/mcp/configIO.test.ts
Add JSONC parsing/writing tests (comments, trailing commas, safe-parse failures, preservation of sibling keys/comments on update).
ConfigPaths Tests & Docs
src/test/main/mcp/configPaths.test.ts, agents/integrations/mcp.md
Clear mocks between tests; assert exact opencode configPath and isJsonc propagation. Update docs to describe 2‑phase read-then-write strategy and partial-write error reporting.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Client
participant McpService as McpService (controller)
participant ConfigIO as configIO (read/write)
participant Disk as Filesystem/Repo
Client->>McpService: saveServer/removeServer(request)
McpService->>ConfigIO: readServers(meta) for each agent
alt read success
ConfigIO->>Disk: read file
Disk-->>ConfigIO: file contents
ConfigIO-->>McpService: parsed ServerMap
McpService->>McpService: compute PendingWrite (in-memory)
else read failure
ConfigIO-->>McpService: error (record readFailures)
end
McpService->>ConfigIO: writeServers(pendingWrite) for each successful read
alt write success
ConfigIO->>Disk: write updated config
Disk-->>ConfigIO: ack
else write failure
ConfigIO-->>McpService: error (record writeFailures)
end
McpService-->>Client: if any readFailures -> throw buildConfigFailureError('read')
McpService-->>Client: else if writeFailures -> throw buildConfigFailureError('write', failures, successes)
McpService-->>Client: else return success

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰
I hopped through configs, careful and spry,
Read first, then write — no more goodbye.
Comments kept snug, no file erased,
Partial wins noted, failures traced.
A happy rabbit, config-safe and spry.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 37.50% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: treating OpenCode config as JSONC to preserve it when updating MCP servers, directly addressing issue #1616.
Linked Issues check ✅ Passed The PR fully addresses issue #1616 by implementing JSONC support for opencode.json, enabling sibling keys/comments preservation and safe failure handling during MCP updates.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the opencode.json preservation issue: JSONC utilities, config metadata, error handling, and comprehensive tests—no unrelated modifications detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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

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)
src/main/services/mcp/configIO.ts (1)

111-124: Consider adding a brief comment explaining the validation-only call.

Line 113 calls parseJsoncConfig but discards its return value. While this is correct (it's validating before applying edits to preserve comments), a brief comment would clarify the intent.

📝 Suggested clarification
   if (isJsoncConfig(meta)) {
     if (existingRaw && existingRaw.trim()) {
+      // Validate existing content before modifying; throws if malformed
       parseJsoncConfig(meta, existingRaw);
       const edits = jsoncParser.modify(existingRaw, meta.serversPath, servers, {});
       const modified = jsoncParser.applyEdits(existingRaw, edits);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/services/mcp/configIO.ts` around lines 111 - 124, The call to
parseJsoncConfig(meta, existingRaw) in the isJsoncConfig branch is used solely
to validate the existing JSONC before generating edits (so comments are
preserved) but the return value is intentionally discarded; add a short inline
comment above that call explaining it performs validation/parse for side-effects
only (e.g., "validate/parse JSONC to ensure it's safe to compute edits; return
value intentionally ignored") so future readers know why parseJsoncConfig is
invoked even though its result isn't used, referencing parseJsoncConfig,
jsoncParser.modify, and jsoncParser.applyEdits.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/services/McpService.ts`:
- Around line 130-145: Update the documentation agents/integrations/mcp.md to
state explicitly that the read phase is atomic (any read error aborts the
operation) and the write phase is best-effort (writeServers is called for each
entry in pendingWrites and the operation continues on individual failures). In
code, enhance the post-write error reporting where writeFailures, writeServers,
pendingWrites, readFailures and buildConfigFailureError are used: after the
for-loop collect both failed agentIds (writeFailures) and successful agentIds
(those from pendingWrites not in writeFailures) and include both lists in the
thrown buildConfigFailureError (or in a new more descriptive error payload) so
the error message clearly shows which agents succeeded and which failed.

---

Nitpick comments:
In `@src/main/services/mcp/configIO.ts`:
- Around line 111-124: The call to parseJsoncConfig(meta, existingRaw) in the
isJsoncConfig branch is used solely to validate the existing JSONC before
generating edits (so comments are preserved) but the return value is
intentionally discarded; add a short inline comment above that call explaining
it performs validation/parse for side-effects only (e.g., "validate/parse JSONC
to ensure it's safe to compute edits; return value intentionally ignored") so
future readers know why parseJsoncConfig is invoked even though its result isn't
used, referencing parseJsoncConfig, jsoncParser.modify, and
jsoncParser.applyEdits.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: a8d15c27-7250-48d7-b4cf-848e7daca84f

📥 Commits

Reviewing files that changed from the base of the PR and between 668cf9b and bd7db0f.

📒 Files selected for processing (7)
  • src/main/services/McpService.ts
  • src/main/services/mcp/configIO.ts
  • src/main/services/mcp/configPaths.ts
  • src/shared/mcp/types.ts
  • src/test/main/mcp/McpService.test.ts
  • src/test/main/mcp/configIO.test.ts
  • src/test/main/mcp/configPaths.test.ts

@arnestrickmann
Copy link
Copy Markdown
Contributor

Thank you for taking this on and for fixing this. @jasperan
LGTM.

@arnestrickmann arnestrickmann merged commit 42e634a into generalaction:main Mar 29, 2026
3 of 4 checks passed
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.

[bug]: Adding and removing MCP from OpenCode via emdash resets the entire opencode.json file

2 participants