Skip to content

fix: race condition and JSON format issues in MCP server management#319

Closed
juniorcammel wants to merge 4 commits intoAutoMaker-Org:mainfrom
juniorcammel:fix/race-condition-and-json-format
Closed

fix: race condition and JSON format issues in MCP server management#319
juniorcammel wants to merge 4 commits intoAutoMaker-Org:mainfrom
juniorcammel:fix/race-condition-and-json-format

Conversation

@juniorcammel
Copy link

@juniorcammel juniorcammel commented Dec 29, 2025

Fixes #316, Fixes #317

This PR addresses two critical bugs in MCP server management that affect data persistence and timing.

Problem 1: Race Condition (#316)

Success toasts and auto-tests executed before syncSettingsToServer() completed, causing "Server not found" errors during auto-test because servers weren't persisted to disk yet.

User Impact:

  1. User adds MCP server
  2. Success toast appears immediately ✅
  3. Auto-test fires immediately ❌
  4. "Server not found" error (not on disk yet)
  5. Sync completes in background

Problem 2: JSON Format (#317)

JSON editor exported servers as an object keyed by name (losing server IDs), but backend required IDs to locate servers. After editing JSON, backend couldn't find servers because IDs were missing.

User Impact:

  • Edit server via JSON → IDs lost
  • Backend operations fail: "Server not found"
  • Data integrity compromised

Solution

Race Condition Fix

Wait for syncSettingsToServer() completion before showing success notifications:

// Before
addMCPServer(serverData);
toast.success('MCP server added'); // ❌ Shows before sync
await syncSettingsToServer();

// After
addMCPServer(serverData);

const syncSuccess = await syncSettingsToServer(); // ✅ Wait for sync

if (!syncSuccess) {
  toast.error('Failed to save MCP server to disk');
  return;
}

toast.success('MCP server added'); // ✅ Only after sync succeeds

Applied to:

  • handleSave (edit existing server)
  • handleSecurityWarningConfirm (add new / import multiple)
  • handleToggleEnabled (enable/disable toggle)
  • handleDelete (remove server)

JSON Format Fix

Changed export format from object to array with IDs preserved:

Before (object format - loses IDs):

{
  "mcpServers": {
    "shadcn": {
      "type": "stdio",
      "command": "cmd",
      "args": ["/c", "npx", "shadcn@latest", "mcp"]
    }
  }
}

After (array format - preserves IDs):

{
  "mcpServers": [
    {
      "id": "mcp-1767024860547-90lvecvle",
      "name": "shadcn",
      "type": "stdio",
      "command": "cmd",
      "args": ["/c", "npx", "shadcn@latest", "mcp"],
      "enabled": true
    }
  ]
}

Key changes:

  • Export as array instead of object (preserves IDs)
  • Match servers by ID first, then by name
  • Support both formats for backward compatibility
  • Created helper functions: handleSaveGlobalJsonArray and handleSaveGlobalJsonObject

Impact

Race Condition

  • ✅ Auto-test no longer fails with "Server not found" immediately after adding
  • ✅ Success toasts appear only after persistence completes
  • ✅ Error toasts shown if sync fails
  • ✅ UI and disk state properly synchronized

JSON Format

  • ✅ JSON editor displays servers with IDs preserved
  • ✅ Editing JSON maintains server IDs
  • ✅ Backend operations succeed after JSON edits
  • ✅ Backward compatible with Claude Desktop object format
  • ✅ Data integrity maintained

Testing

Build:

  • npm run build passes with no TypeScript errors

Race Condition:

  • ✅ Add server → sync completes → success toast → auto-test succeeds
  • ✅ Edit server → sync completes → success toast
  • ✅ Import servers → sync completes → success toast
  • ✅ Toggle enabled → sync completes → success toast
  • ✅ Delete server → sync completes → success toast

JSON Format:

  • ✅ JSON export shows array format with IDs
  • ✅ Edit JSON and save → IDs preserved
  • ✅ Legacy object format (Claude Desktop) still imports correctly
  • ✅ Backend operations succeed with preserved IDs

Real MCP Servers Tested:

  • ✅ shadcn (stdio, 7 tools)
  • ✅ playwright (stdio, 22 tools)
  • ✅ context7 (HTTP, 2 tools)

Files Changed

apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts:

  • Lines 276-290: Fixed handleSave async timing
  • Lines 307-343: Fixed handleSecurityWarningConfirm async timing
  • Lines 345-356: Fixed handleToggleEnabled async timing
  • Lines 358-370: Fixed handleDelete async timing
  • Lines 606-639: Changed handleOpenGlobalJsonEdit to export array with IDs
  • Lines 641-828: Modified handleSaveGlobalJsonEdit with dual-format support
    • Added handleSaveGlobalJsonArray helper (array format processor)
    • Added handleSaveGlobalJsonObject helper (legacy object format processor)

Dependencies

Requires: #318 (HTTP error handling) for proper error visibility during sync operations


Additional Context

  • Breaking Changes: None - backward compatible with Claude Desktop
  • Performance Impact: ~50-200ms delay before success toast (acceptable for correct behavior)
  • Data Migration: Automatic on next JSON edit (object → array format)
  • Default Format: Array (with IDs)
  • Supported Formats: Array (new) + Object (Claude Desktop legacy)

Summary by CodeRabbit

  • Bug Fixes

    • Server operations (edit, enable/disable, delete, import) now sync changes to disk with error handling and toast notifications on failure.
    • Enhanced validation and error messaging for JSON-based server configuration edits.
  • New Features

    • Explicit success messaging displayed after server modifications.
    • Improved data handling for global JSON editing with better format compatibility.

✏️ Tip: You can customize this high-level summary in your review settings.

Phase 1 complete - Document fixes for 3 MCP server bugs:
- Bug AutoMaker-Org#1: JSON format inconsistency (array vs object, IDs lost)
- Bug AutoMaker-Org#2: HTTP error handling (cryptic error messages)
- Bug AutoMaker-Org#3: Race condition in auto-test (server not found)

All bugs validated with multiple MCP server types (stdio + HTTP).
Patches available for re-application after upstream updates.

Related to upstream PR AutoMaker-Org#286 (MCP server support).
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 29, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Walkthrough

This change fixes race conditions and data integrity issues in MCP server management by ensuring disk synchronization completes before UI feedback is shown, and by preserving server IDs in JSON editing through support for both array and legacy object formats with comprehensive validation.

Changes

Cohort / File(s) Summary
Sync-on-save operations
apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts (Lines 276–290, 307–343, 345–356, 358–370)
Modified handleSave, handleSecurityWarningConfirm, handleToggleEnabled, and handleDelete to await syncSettingsToServer() completion before showing success toasts and proceeding with dependent operations. Added error toast handling on sync failure; operations abort if sync fails. Addresses race condition where UI feedback appeared before disk persistence.
Global JSON editing with dual-format support
apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts (Lines 606–639, 641–828)
Reworked handleOpenGlobalJsonEdit to export servers as an array with IDs preserved (instead of object keyed by name). Reworked handleSaveGlobalJsonEdit to detect and route between two new helpers: handleSaveGlobalJsonArray (array format with IDs) and handleSaveGlobalJsonObject (legacy object format). Added comprehensive validation for JSON inputs including name, type, and required fields. Both handlers update/create servers by ID or name and remove missing servers.
UI state and sync integration
apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts (JSON editing flow)
Added explicit success messaging and UI state resets after global JSON edits and per-server operations. Integrated syncSettingsToServer() into JSON edit flow after server state updates. ID preservation in export/import aligns UI editing with array-based persistence format.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant UI as UI Component
    participant Store as Zustand Store
    participant Disk as Disk/Backend
    participant Toast as Toast System

    rect rgb(220, 240, 255)
    Note over User,Toast: Before Fix (Race Condition)
    User->>UI: Add/Edit/Toggle/Delete Server
    UI->>Store: Update state immediately
    Note over UI,Toast: ❌ Feedback shown before sync
    UI->>Toast: Show success/loading
    UI->>Disk: Sync settings (async, not awaited)
    Note over Toast,Disk: ⚠️ Dependent ops execute<br/>before disk state ready
    Disk-->>UI: Sync completes (too late)
    end

    rect rgb(240, 255, 240)
    Note over User,Toast: After Fix (Correct Sequencing)
    User->>UI: Add/Edit/Toggle/Delete Server
    UI->>Store: Update state immediately
    UI->>Disk: Await sync settings to complete
    alt Sync Success
        Disk-->>UI: ✅ Sync confirmed
        UI->>Toast: Show success message
        Note over Toast: ✅ Feedback after<br/>disk state confirmed
    else Sync Failure
        Disk-->>UI: ❌ Sync error
        UI->>Toast: Show error message
        UI->>Store: Revert/abort operation
        Note over Store: ❌ State rolled back
    end
    end
Loading
sequenceDiagram
    actor User
    participant UI as UI Component
    participant Parser as JSON Parser
    participant Handler as Format Handler
    participant Store as Zustand Store
    participant Disk as Disk/Backend
    participant Toast as Toast System

    User->>UI: Open or Save JSON Edit
    
    alt Opening JSON (Export)
        UI->>Store: Fetch MCP servers
        Store-->>UI: Return server list
        UI->>Handler: Build array with IDs
        Handler-->>UI: Servers array + IDs preserved
        UI->>UI: Display JSON editor
    end

    alt Saving JSON (Import)
        User->>UI: Submit edited JSON
        UI->>Parser: Parse JSON string
        Parser-->>UI: Parsed object
        
        alt Array Format Detected
            UI->>Handler: Route to handleSaveGlobalJsonArray
            Handler->>Handler: Validate fields & structure
            Handler->>Handler: Match by ID first, then name
            Handler->>Store: Update/create/delete servers
            Note over Handler: IDs preserved from array
        else Object Format Detected
            UI->>Handler: Route to handleSaveGlobalJsonObject
            Handler->>Handler: Validate fields & structure
            Handler->>Handler: Match by name only
            Handler->>Store: Update/create/delete servers
            Note over Handler: Legacy format support
        end
        
        Store-->>UI: State updated
        UI->>Disk: Await sync to complete
        
        alt Sync Success
            Disk-->>UI: ✅ Confirmed
            UI->>Toast: Show success message
            UI->>UI: Reset editor state
        else Sync Failure
            Disk-->>UI: ❌ Error
            UI->>Toast: Show error message
            Note over Toast: Operation aborted
        end
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A fix for servers lost in time,
Where IDs fled and sync got out of rhyme,
Now wait we do for disk to save,
Before the toast pops—bold and brave,
Array and object, both formats fit,
Race conditions fixed—oh what a hit! 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 accurately reflects the main changes: fixing race condition issues (awaiting sync before showing toasts) and JSON format issues (array format with ID preservation).
Linked Issues check ✅ Passed The PR addresses both #316 (race condition fix via awaiting syncSettingsToServer) and #317 (JSON format fix via array export with ID preservation), implementing all required coding changes across affected handlers.
Out of Scope Changes check ✅ Passed All changes are scoped to use-mcp-servers.ts and directly address the two linked issues. The author acknowledged patch files were mistakenly included and committed to removing them, keeping only direct code changes.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53e4087 and 628ccc9.

📒 Files selected for processing (1)
  • apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts
🔇 Additional comments (1)
apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts (1)

606-639: Good implementation of ID preservation in global JSON export.

The array format with IDs preserved (lines 607-635) addresses the JSON data integrity issue from #317. The wrapper { mcpServers: array } maintains compatibility with the import flow.


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

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @juniorcammel, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request delivers crucial stability and usability improvements to MCP server management. It eliminates frustrating 'Server not found' errors by ensuring that server data is fully persisted before success notifications are displayed or auto-tests are run. Additionally, it rectifies a data integrity issue with the JSON editor, guaranteeing that server IDs are preserved during export and import, while maintaining backward compatibility with older configurations. The underlying HTTP communication also benefits from more informative error reporting.

Highlights

  • Race Condition Fix: Resolved a race condition where success toasts and auto-tests would execute before syncSettingsToServer() completed, leading to 'Server not found' errors. Success notifications now await the completion of the sync operation, ensuring data persistence before user feedback or subsequent actions.
  • JSON Format Consistency: Addressed an issue where the JSON editor exported MCP servers as an object keyed by name, causing server IDs to be lost. The export format is now an array that preserves server IDs, and the system supports both the new array format and the legacy object format for backward compatibility.
  • Improved HTTP Error Handling: Enhanced HTTP API client methods (post, get, put, httpDelete) to check response.ok before parsing JSON. This change provides more meaningful error messages (e.g., 'Server not found' for 404s) instead of generic 'Unexpected end of JSON input' errors, significantly improving debugging.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

juniorcammel pushed a commit to juniorcammel/automaker that referenced this pull request Dec 29, 2025
Added links to PRs AutoMaker-Org#318 and AutoMaker-Org#319 in CUSTOM_CHANGES.md.
Updated contribution status to reflect Phase 3 completion.

Phase 3 completed:
- PR AutoMaker-Org#318: HTTP error handling (fixes AutoMaker-Org#315)
- PR AutoMaker-Org#319: Race condition + JSON format (fixes AutoMaker-Org#316, AutoMaker-Org#317)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request effectively addresses two important issues: a race condition in MCP server management and a data integrity problem with JSON exports. The changes to await the syncSettingsToServer function before showing success toasts are well-implemented across all relevant handlers, ensuring UI feedback aligns with the persisted state. The move to an array format for JSON editing correctly preserves server IDs, and the support for both old and new formats provides good backward compatibility. I've added a few suggestions to improve error handling in the new JSON editing logic and a note on the documentation.

Comment on lines 239 to 240
- [ ] `npm run dev:web` works correctly
- [ ] `npm run dev:electron` works correctly
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

I noticed that some items in the 'Build Tests' checklist are still unchecked. It would be great to complete this checklist to ensure all tests have passed before merging.

Comment on lines +669 to +671
} catch (error) {
toast.error('Invalid JSON: ' + (error instanceof Error ? error.message : 'Parse error'));
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The current error handling prefixes all errors with 'Invalid JSON:', which can be misleading for data validation errors (e.g., a missing server name). It's better to distinguish between JSON syntax errors and other validation errors to provide clearer feedback to the user.

    } catch (error) {
      if (error instanceof SyntaxError) {
        toast.error('Invalid JSON: ' + error.message);
      } else if (error instanceof Error) {
        toast.error(error.message);
      } else {
        toast.error('An unknown error occurred while saving.');
      }
    }

Comment on lines +678 to +681
if (typeof config !== 'object' || config === null) {
toast.error('Invalid server config in array');
throw new Error('Invalid server config');
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

To avoid showing duplicate error toasts, the toast.error call should be removed from this helper function. The calling function handleSaveGlobalJsonEdit will now handle displaying the error toast. Please apply this change to all validation checks in both handleSaveGlobalJsonArray and handleSaveGlobalJsonObject, and ensure the Error constructor receives the user-friendly message.

      if (typeof config !== 'object' || config === null) {
        throw new Error('Invalid server config in array');
      }

Copy link
Contributor

@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: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts (2)

323-339: Import flow has partial state on sync failure.

If sync fails after importing multiple servers, all servers are added to local state but none persisted to disk. On app restart, all imported servers will be lost. Consider tracking newly added server IDs and rolling them back on sync failure.


595-598: Sync result not checked in single-server JSON edit.

Unlike other handlers, handleSaveJsonEdit doesn't check the return value of syncSettingsToServer(). This is inconsistent with the pattern applied elsewhere and means sync failures go unnoticed.

🔎 Proposed fix
       updateMCPServer(jsonEditServer.id, updateData);
-      await syncSettingsToServer();

-      toast.success('Server configuration updated');
+      const syncSuccess = await syncSettingsToServer();
+
+      if (!syncSuccess) {
+        toast.error('Failed to save server configuration to disk');
+        return;
+      }
+
+      toast.success('Server configuration updated');
       setJsonEditServer(null);
🧹 Nitpick comments (2)
patches/01-fix-http-error-handling.patch (1)

10-21: Consider extracting duplicate error handling into a helper.

The same error-handling pattern is repeated in all four HTTP methods. While correct, this creates maintenance burden.

🔎 Proposed refactor to reduce duplication
// Add a private helper method:
private async handleResponse<T>(response: Response): Promise<T> {
  if (!response.ok) {
    let errorMessage = `HTTP ${response.status}: ${response.statusText}`;
    try {
      const errorData = await response.json();
      if (errorData.error) {
        errorMessage = errorData.error;
      }
    } catch {
      // If parsing JSON fails, use status text
    }
    throw new Error(errorMessage);
  }
  return response.json();
}

// Then use in each method:
private async post<T>(endpoint: string, body?: unknown): Promise<T> {
  const response = await fetch(`${this.serverUrl}${endpoint}`, {
    method: 'POST',
    headers: this.getHeaders(),
    body: body ? JSON.stringify(body) : undefined,
  });
  return this.handleResponse<T>(response);
}
CUSTOM_CHANGES.md (1)

249-251: Use markdown link syntax for URLs.

Per markdownlint (MD034), bare URLs should be wrapped in angle brackets or use proper link syntax for better rendering and accessibility.

🔎 Proposed fix
-- Issue #315: Fix HTTP error handling - https://github.com/AutoMaker-Org/automaker/issues/315
-- Issue #316: Fix race condition - https://github.com/AutoMaker-Org/automaker/issues/316
-- Issue #317: Fix JSON format - https://github.com/AutoMaker-Org/automaker/issues/317
+- Issue #315: Fix HTTP error handling - <https://github.com/AutoMaker-Org/automaker/issues/315>
+- Issue #316: Fix race condition - <https://github.com/AutoMaker-Org/automaker/issues/316>
+- Issue #317: Fix JSON format - <https://github.com/AutoMaker-Org/automaker/issues/317>
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between dd822c4 and 53e4087.

📒 Files selected for processing (5)
  • CUSTOM_CHANGES.md
  • apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts
  • patches/01-fix-http-error-handling.patch
  • patches/02-fix-race-condition-and-json-format.patch
  • patches/mcp-fixes-combined-2025-12-29.patch
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts
🧬 Code graph analysis (1)
apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts (3)
apps/ui/src/hooks/use-settings-migration.ts (1)
  • syncSettingsToServer (196-247)
apps/ui/src/components/views/settings-view/mcp-servers/types.ts (1)
  • ServerType (3-3)
libs/types/src/settings.ts (1)
  • MCPServerConfig (188-213)
🪛 LanguageTool
CUSTOM_CHANGES.md

[grammar] ~263-~263: Ensure spelling is correct
Context: ...es - Performance impact is minimal (~50-200ms for sync operations) - Bug #2 (HTTP err...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 markdownlint-cli2 (0.18.1)
CUSTOM_CHANGES.md

249-249: Bare URL used

(MD034, no-bare-urls)


250-250: Bare URL used

(MD034, no-bare-urls)


251-251: Bare URL used

(MD034, no-bare-urls)

🔇 Additional comments (8)
apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts (5)

345-356: LGTM with note on consistency.

The race condition fix pattern is correctly applied. The state inconsistency concern from the edit path applies here as well, though toggling is lower impact than adding/editing.


606-639: LGTM!

The change to export servers as an array with preserved IDs addresses the JSON format issue. The server configuration now round-trips correctly through the JSON editor.


641-672: LGTM!

The dual-format support with proper sync validation follows the correct pattern. Good separation of concerns by delegating to helper functions.


757-828: LGTM!

The object format handler correctly tracks processed names and removes servers that are no longer in the JSON. Unlike the array handler, this doesn't have the same ID-tracking issue because it tracks by name, which is known from the input.


739-754: Cleanup iterates pre-mutation server list
Newly added servers aren’t in the original mcpServers snapshot and won’t be removed.

Likely an incorrect or invalid review comment.

patches/mcp-fixes-combined-2025-12-29.patch (1)

1-5: Combined patch contains changes already reviewed.

This patch combines the HTTP error handling and MCP server fixes. The issues identified in the individual file reviews (state inconsistency on sync failure, newly added servers removal bug) apply to this patch as well.

patches/02-fix-race-condition-and-json-format.patch (1)

1-5: Patch file mirrors main implementation.

This patch contains the same changes reviewed in use-mcp-servers.ts. The issues identified there apply here.

CUSTOM_CHANGES.md (1)

1-267: Documentation is comprehensive and well-structured.

The CUSTOM_CHANGES.md file provides clear documentation of the bug fixes with before/after code examples, testing checklists, and contribution tracking. This will be valuable for maintaining these patches and upstream contribution.

Comment on lines 277 to 290
if (editingServer) {
updateMCPServer(editingServer.id, serverData);

const syncSuccess = await syncSettingsToServer();

if (!syncSuccess) {
toast.error('Failed to save MCP server to disk');
return;
}

toast.success('MCP server updated');
await syncSettingsToServer();
handleCloseDialog();
return;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

State inconsistency on sync failure.

When syncSettingsToServer() fails, the local Zustand store already contains the update from updateMCPServer(), but the disk state doesn't. The user sees an error toast, yet the UI shows the updated server. On next app restart, the changes will be lost.

Consider rolling back the local state on sync failure, or at minimum, inform the user that the change exists only locally.

🔎 Proposed fix with rollback
     if (editingServer) {
+      // Capture previous state for potential rollback
+      const previousState = mcpServers.find(s => s.id === editingServer.id);
       updateMCPServer(editingServer.id, serverData);

       const syncSuccess = await syncSettingsToServer();

       if (!syncSuccess) {
+        // Rollback local state
+        if (previousState) {
+          updateMCPServer(editingServer.id, previousState);
+        }
         toast.error('Failed to save MCP server to disk');
         return;
       }

Comment on lines 358 to 370
const handleDelete = async (id: string) => {
removeMCPServer(id);
await syncSettingsToServer();

const syncSuccess = await syncSettingsToServer();

if (!syncSuccess) {
toast.error('Failed to save changes to disk');
return;
}

setDeleteConfirmId(null);
toast.success('MCP server removed');
};
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Deleted server reappears after failed sync.

If syncSettingsToServer() fails, the server is removed from UI state but persists on disk. On app restart, the "deleted" server will reappear, confusing the user.

🤖 Prompt for AI Agents
In
apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts
around lines 358 to 370, the code removes the server from local state before
persisting changes so if syncSettingsToServer() fails the deleted server remains
on disk and will reappear; change the flow to perform the persistent sync first
and only remove from state after sync succeeds (or keep the optimistic removal
but immediately restore the server if sync fails). Specifically: attempt to sync
the updated settings to server before calling removeMCPServer (or save the
removed server data to a temp variable so you can call addMCPServer to rollback
on failure), show error toast and do not clear delete confirmation when sync
fails, and only clear setDeleteConfirmId and show success toast after successful
sync and state update.

Comment on lines 23 to 24
return response.json();
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n "return.*204|res\.status\(204\)|\.send\(\)|\.end\(\)" --type=ts -g '!node_modules/**'

Repository: AutoMaker-Org/automaker

Length of output: 297


Handle JSON parse errors
Unconditional response.json() will throw on empty or invalid JSON. Wrap it in try/catch or verify Content-Type/status before parsing to avoid unhandled exceptions.

🤖 Prompt for AI Agents
In patches/01-fix-http-error-handling.patch around lines 23-24, the code calls
response.json() unconditionally which throws on empty or invalid JSON; update
the code to guard parsing by either checking
response.headers.get('content-type') includes 'application/json' and
response.status is not 204/205 before calling json(), or wrap response.json() in
a try/catch and return a safe default (null or {}) on parse failure; ensure any
caught errors are handled or rethrown with context so callers don't get an
unhandled exception.

@Shironex Shironex added Testers-Requested Request for others to test an enhancement or bug fix/etc. Do Not Merge Use this label if something should not be merged. labels Dec 29, 2025
@webdevcody
Copy link
Collaborator

I'm curious why your PR submits patches instead of just including the fixed code?

@trueheads
Copy link
Collaborator

trueheads commented Dec 29, 2025

@juniorcammel Can you elaborate on what @webdevcody asked?

@juniorcammel
Copy link
Author

juniorcammel commented Dec 30, 2025

Thanks for pointing that out @webdevcody! You're absolutely right. The patch files shouldn't be in this PR. Those patches exist in my local fork to easily re-apply these fixes after pulling upstream updates, but they're not meant for contribution to the main repo. The actual fix is just the changes to use-mcp-servers.ts. Let me update the PR to remove the patch files and keep only the direct code changes.

junior-cammel and others added 2 commits December 30, 2025 00:48
Fixes AutoMaker-Org#316, Fixes AutoMaker-Org#317

## Problem 1: Race Condition (AutoMaker-Org#316)

Success toasts and auto-tests executed before `syncSettingsToServer()`
completed, causing "Server not found" errors because servers weren't
persisted to disk yet.

**Impact**: Add server → success toast → auto-test fails immediately

## Problem 2: JSON Format (AutoMaker-Org#317)

JSON editor exported servers as object keyed by name (losing IDs), but
backend required IDs to locate servers. After editing JSON, backend
couldn't find servers.

**Impact**: Edit JSON → server IDs lost → backend operations fail

## Solution

### Race Condition Fix
Wait for sync completion before showing success notifications:

```typescript
const syncSuccess = await syncSettingsToServer();

if (!syncSuccess) {
  toast.error('Failed to save MCP server to disk');
  return;
}

toast.success('MCP server added'); // Only after sync succeeds
```

Applied to:
- `handleSave` (edit flow)
- `handleSecurityWarningConfirm` (add/import flows)
- `handleToggleEnabled` (enable/disable)
- `handleDelete` (remove server)

### JSON Format Fix
Changed export from object to array with IDs preserved:

**Before** (loses IDs):
```json
{
  "mcpServers": {
    "shadcn": { "type": "stdio", "command": "cmd", ... }
  }
}
```

**After** (preserves IDs):
```json
{
  "mcpServers": [
    {
      "id": "mcp-1767024860547-90lvecvle",
      "name": "shadcn",
      "type": "stdio",
      "command": "cmd",
      ...
    }
  ]
}
```

Supports both formats for backward compatibility with Claude Desktop.

## Impact

- ✅ Auto-test no longer fails with "Server not found" after adding
- ✅ Success toasts appear only after persistence completes
- ✅ JSON editor preserves server IDs
- ✅ Backend operations succeed after JSON edits
- ✅ Backward compatible with Claude Desktop object format

## Testing

- ✅ `npm run build` passes with no TypeScript errors
- ✅ Add server → sync completes → success toast → auto-test succeeds
- ✅ JSON export shows array format with IDs
- ✅ JSON edit preserves server IDs
- ✅ Legacy object format still imports correctly
- ✅ Tested with 3 MCP servers (shadcn, playwright, context7)

## Files Changed

- `apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts`
  - Fixed async timing in 4 server operation handlers
  - Changed JSON export format from object to array
  - Added dual-format support for JSON import
  - Created helper functions for array and object processing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
These files are for local fork maintenance only and should not be
included in upstream PRs:
- CUSTOM_CHANGES.md (fork-specific changelog)
- patches/*.patch (re-application tools for fork)

The actual fix remains in use-mcp-servers.ts
@juniorcammel juniorcammel force-pushed the fix/race-condition-and-json-format branch from 53e4087 to 628ccc9 Compare December 30, 2025 00:51
Copy link
Contributor

@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: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts (2)

308-343: State inconsistency on sync failure in both add and import flows.

When syncSettingsToServer() fails:

  • Add flow (line 312): The server is added to local state but not persisted to disk.
  • Import flow (lines 324-326): Multiple servers are added to local state but not persisted.

In both cases, the UI shows the new servers, but they will disappear on app restart.

🔎 Proposed fix with rollback for both paths
     if (pendingServerData.type === 'add' && pendingServerData.serverData) {
-      addMCPServer(pendingServerData.serverData);
+      const newServer = addMCPServer(pendingServerData.serverData);

       const syncSuccess = await syncSettingsToServer();

       if (!syncSuccess) {
+        // Rollback: remove the server we just added
+        if (newServer?.id) {
+          removeMCPServer(newServer.id);
+        }
         toast.error('Failed to save MCP server to disk');
         return;
       }

       toast.success('MCP server added');
       handleCloseDialog();
     } else if (pendingServerData.type === 'import' && pendingServerData.importServers) {
+      const addedServerIds: string[] = [];
       for (const serverData of pendingServerData.importServers) {
-        addMCPServer(serverData);
+        const server = addMCPServer(serverData);
+        if (server?.id) addedServerIds.push(server.id);
       }

       const syncSuccess = await syncSettingsToServer();

       if (!syncSuccess) {
+        // Rollback: remove all servers we just added
+        for (const id of addedServerIds) {
+          removeMCPServer(id);
+        }
         toast.error('Failed to save MCP servers to disk');
         return;
       }

595-600: Missing error handling for syncSettingsToServer().

Line 596 calls await syncSettingsToServer() but does not check the result or handle errors. If the sync fails:

  • No error toast is shown to the user
  • The success toast is displayed regardless (line 598)
  • Local state is updated but not persisted to disk

This is inconsistent with the error-handling pattern used elsewhere in this PR and constitutes a silent failure.

🔎 Proposed fix with consistent error handling and rollback
       updateMCPServer(jsonEditServer.id, updateData);
-      await syncSettingsToServer();
+      const syncSuccess = await syncSettingsToServer();
+
+      if (!syncSuccess) {
+        toast.error('Failed to save server configuration to disk');
+        return;
+      }

       toast.success('Server configuration updated');
       setJsonEditServer(null);
       setJsonEditValue('');
♻️ Duplicate comments (6)
apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts (6)

277-290: State inconsistency on sync failure remains unresolved.

When syncSettingsToServer() fails, the local Zustand store contains the update from updateMCPServer() (line 278), but the disk does not. The user sees an error toast, yet the UI still shows the updated server. On app restart, the changes are lost, leading to data loss and user confusion.

Consider rolling back the local state on sync failure.

🔎 Proposed fix with rollback
     if (editingServer) {
+      // Capture previous state for potential rollback
+      const previousState = mcpServers.find(s => s.id === editingServer.id);
       updateMCPServer(editingServer.id, serverData);

       const syncSuccess = await syncSettingsToServer();

       if (!syncSuccess) {
+        // Rollback local state
+        if (previousState) {
+          updateMCPServer(editingServer.id, previousState);
+        }
         toast.error('Failed to save MCP server to disk');
         return;
       }

358-370: Deleted server reappears after failed sync.

When syncSettingsToServer() fails, the server is removed from local state (line 359) but persists on disk. On app restart, the "deleted" server will reappear, causing user confusion.

🔎 Proposed fix with rollback
   const handleDelete = async (id: string) => {
+    // Save the server for potential rollback
+    const deletedServer = mcpServers.find(s => s.id === id);
     removeMCPServer(id);

     const syncSuccess = await syncSettingsToServer();

     if (!syncSuccess) {
+      // Rollback: restore the server
+      if (deletedServer) {
+        addMCPServer(deletedServer);
+      }
       toast.error('Failed to save changes to disk');
       return;
     }

669-671: Misleading error messages for validation errors.

The catch block prefixes all errors with "Invalid JSON:", which is misleading for validation errors thrown by the helper functions (e.g., "Server name is required"). Distinguish between JSON syntax errors and validation errors for clearer user feedback.

🔎 Proposed fix to distinguish error types
     } catch (error) {
-      toast.error('Invalid JSON: ' + (error instanceof Error ? error.message : 'Parse error'));
+      if (error instanceof SyntaxError) {
+        toast.error('Invalid JSON: ' + error.message);
+      } else if (error instanceof Error) {
+        toast.error(error.message);
+      } else {
+        toast.error('An unknown error occurred while saving.');
+      }
     }

675-703: Remove duplicate error toasts from validation logic.

Lines 679, 688, 694, and 699 call toast.error before throwing an error. The caller's catch block (lines 669-671) also displays a toast, potentially showing duplicate error messages. The helper should only throw errors, and the caller should handle toast notifications.

Additionally, error messages could include the array index to help users identify which server config is invalid.

🔎 Proposed fix to remove duplicate toasts
   const handleSaveGlobalJsonArray = async (serversArray: unknown[]) => {
     // Validate all servers first
-    for (const config of serversArray) {
+    for (let i = 0; i < serversArray.length; i++) {
+      const config = serversArray[i];
       if (typeof config !== 'object' || config === null) {
-        toast.error('Invalid server config in array');
-        throw new Error('Invalid server config');
+        throw new Error(`Invalid server config at index ${i}`);
       }

       const serverConfig = config as Record<string, unknown>;
       const name = serverConfig.name as string;
       const serverType = (serverConfig.type as string) || 'stdio';

       if (!name || typeof name !== 'string') {
-        toast.error('Server name is required');
-        throw new Error('Server name required');
+        throw new Error(`Server name is required at index ${i}`);
       }

       if (serverType === 'stdio') {
         if (!serverConfig.command || typeof serverConfig.command !== 'string') {
-          toast.error(`Command is required for "${name}" (stdio)`);
-          throw new Error('Command required');
+          throw new Error(`Command is required for "${name}" (stdio)`);
         }
       } else if (serverType === 'sse' || serverType === 'http') {
         if (!serverConfig.url || typeof serverConfig.url !== 'string') {
-          toast.error(`URL is required for "${name}" (${serverType})`);
-          throw new Error('URL required');
+          throw new Error(`URL is required for "${name}" (${serverType})`);
         }
       }
     }

Apply similar changes to handleSaveGlobalJsonObject (lines 758-828).


345-356: State inconsistency on sync failure in toggle flow.

When syncSettingsToServer() fails, the server's enabled state is toggled in local state (line 346) but not persisted to disk. On app restart, the toggle is lost, and the server reverts to its previous enabled state.

🔎 Proposed fix with rollback
   const handleToggleEnabled = async (server: MCPServerConfig) => {
+    const previousEnabled = server.enabled;
     updateMCPServer(server.id, { enabled: !server.enabled });

     const syncSuccess = await syncSettingsToServer();

     if (!syncSuccess) {
+      // Rollback toggle
+      updateMCPServer(server.id, { enabled: previousEnabled });
       toast.error('Failed to save MCP server to disk');
       return;
     }

641-672: State inconsistency on sync failure in global JSON edit.

When handleSaveGlobalJsonArray or handleSaveGlobalJsonObject executes (lines 650, 653), they call updateMCPServer, addMCPServer, and removeMCPServer multiple times, mutating local state. If syncSettingsToServer() fails at line 659, these mutations persist in memory but not on disk. On app restart, all changes are lost, yet the UI shows them.

This affects multiple servers and is more severe than single-server edits.

🔎 Proposed fix: capture state before mutations
   const handleSaveGlobalJsonEdit = async () => {
     try {
       const parsed = JSON.parse(globalJsonValue);

       // Support both formats: array and object
       const servers = parsed.mcpServers || parsed;

+      // Capture current state for rollback
+      const previousServers = [...mcpServers];
+
       if (Array.isArray(servers)) {
         // Array format (new format with IDs)
         await handleSaveGlobalJsonArray(servers);
       } else if (typeof servers === 'object' && servers !== null) {
         // Object format (legacy Claude Desktop format)
         await handleSaveGlobalJsonObject(servers);
       } else {
         toast.error('Invalid format: expected array or object with server configurations');
         return;
       }

       const syncSuccess = await syncSettingsToServer();

       if (!syncSuccess) {
+        // Rollback: restore previous state
+        // Clear current servers
+        for (const server of mcpServers) {
+          removeMCPServer(server.id);
+        }
+        // Re-add previous servers
+        for (const server of previousServers) {
+          addMCPServer(server);
+        }
         toast.error('Failed to save MCP servers to disk');
         return;
       }

Note: This assumes addMCPServer can accept a full MCPServerConfig with id. If not, you may need to adjust the Zustand store API.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 53e4087 and 628ccc9.

📒 Files selected for processing (1)
  • apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx,js,jsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Always import from shared packages (@automaker/*), never from old relative paths

Files:

  • apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

Use resolveModelString() from @automaker/model-resolver to convert model aliases (haiku, sonnet, opus) to full model names

Files:

  • apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts
🔇 Additional comments (1)
apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts (1)

606-639: Good implementation of ID preservation in global JSON export.

The array format with IDs preserved (lines 607-635) addresses the JSON data integrity issue from #317. The wrapper { mcpServers: array } maintains compatibility with the import flow.

@webdevcody
Copy link
Collaborator

ty for the fix, I'm grabbing and combining your other one into #325

@webdevcody webdevcody closed this Dec 30, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Do Not Merge Use this label if something should not be merged. Testers-Requested Request for others to test an enhancement or bug fix/etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fix: MCP servers JSON editor loses server IDs (data integrity issue) Fix: Race condition causes 'Server not found' when adding MCP servers

5 participants