Skip to content

feat: enhance MCP server management and JSON import/export functionality#325

Merged
webdevcody merged 2 commits intomainfrom
fix-mcp-bug
Dec 30, 2025
Merged

feat: enhance MCP server management and JSON import/export functionality#325
webdevcody merged 2 commits intomainfrom
fix-mcp-bug

Conversation

@webdevcody
Copy link
Collaborator

@webdevcody webdevcody commented Dec 30, 2025

  • Introduced pending sync handling for MCP servers to improve synchronization reliability.
  • Updated auto-test logic to skip servers pending sync, ensuring accurate testing.
  • Enhanced JSON import/export to support both array and object formats, preserving server IDs.
  • Added validation for server configurations during import to prevent errors.
  • Improved error handling and user feedback for sync operations and server updates.
    Fix: Race condition causes 'Server not found' when adding MCP servers #316

#317

grabbing #320 and #319

Summary by CodeRabbit

  • New Features

    • Improved MCP server sync: tracks pending servers, waits for sync on add/update/enable, and auto-tests after successful sync.
    • JSON import/export and global edit now support array (new) and legacy object formats, with unified save+sync and ID-aware updates/deletions.
    • Removed Electron-only gating so settings sync can run in web contexts.
  • Bug Fixes

    • Stronger validation and clearer error toasts; reverts local changes on sync failures to avoid partial persistence.

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

- Introduced pending sync handling for MCP servers to improve synchronization reliability.
- Updated auto-test logic to skip servers pending sync, ensuring accurate testing.
- Enhanced JSON import/export to support both array and object formats, preserving server IDs.
- Added validation for server configurations during import to prevent errors.
- Improved error handling and user feedback for sync operations and server updates.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 30, 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

Adds pending-sync tracking and awaits server syncs in MCP server add/update/enable/ import flows, changes JSON import/export to array-based formats with per-server validation, and removes Electron-only guards from settings migration functions to allow execution in non-Electron contexts.

Changes

Cohort / File(s) Summary
MCP Server sync & JSON import/export
apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts
Adds pendingSyncServerIdsRef; awaits syncSettingsToServer on add/update/enable/disable/import; reverts local changes and shows error toasts on sync failure; auto-test logic skips pending servers and runs silently on success; introduces parseServerConfig, supports array and legacy object JSON formats, validates stdio/SSE/HTTP fields, deduplicates, and emits array-based exports preserving IDs.
Settings migration runtime
apps/ui/src/hooks/use-settings-migration.ts
Removes isElectron() gating from sync/load functions so they run unconditionally at runtime (no longer short-circuit in non-Electron environments).

Sequence Diagram

sequenceDiagram
    actor User
    participant UI as UI Component
    participant Hook as use-mcp-servers Hook
    participant Store as Settings Storage / API
    participant Server as MCP Server

    User->>UI: Submit add/update/import/enable action
    UI->>Hook: invoke add/update/enable/import
    Hook->>Store: persist local settings (create/update)
    Store-->>Hook: returns server record(s) with id(s)

    rect rgb(220,235,255)
        Note over Hook: Mark new/changed IDs in pendingSyncServerIdsRef
    end

    Hook->>Store: syncSettingsToServer()
    alt sync succeeds
        Store-->>Hook: sync ok
        Hook->>Server: auto-test (silent, per-server if enabled)
        Server-->>Hook: test result
        Hook->>UI: success toast (if applicable)
        Hook->>Hook: clear pending IDs
    else sync fails
        Store-->>Hook: error
        Hook->>UI: error toast
        Hook->>Store: revert local change (if needed)
        Hook->>Hook: clear pending IDs
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related issues

Possibly related PRs

Suggested labels

Testers-Requested

Suggested reviewers

  • Shironex

Poem

🐰 I saved the IDs, I tracked the race,
I synced the servers at a careful pace.
JSON neat and tidy, no stray surprise,
Electron gates open — web now flies. ✨

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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: enhance MCP server management and JSON import/export functionality' directly aligns with the main changes, which focus on improving MCP server synchronization, validation, and JSON import/export capabilities.
✨ Finishing touches
  • 📝 Generate docstrings

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

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @webdevcody, 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 significantly refines the management of MCP servers by introducing a 'pending sync' state to ensure data consistency during operations like adding or importing servers. It also overhauls the JSON import and export capabilities, allowing for more flexible data structures and better preservation of server configurations, including unique identifiers. These changes collectively aim to make server management more robust, user-friendly, and adaptable to different deployment contexts.

Highlights

  • Pending Sync Handling for MCP Servers: Introduced a mechanism to track MCP servers that are currently undergoing a synchronization process. This prevents auto-testing of servers that are not yet fully synced, improving reliability and preventing premature status checks.
  • Enhanced JSON Import/Export Functionality: The JSON import and export features for MCP servers have been significantly improved. Export now uses an array format that preserves server IDs and all configuration details, while import supports both array and object formats, including robust validation and ID-based matching for updates.
  • Improved Error Handling for Server Operations: Added comprehensive error handling and user feedback (via toasts) for various MCP server management operations, including adding, updating, deleting, toggling, and importing servers. This ensures users are clearly informed if a save or sync operation fails.
  • Refactored Settings Synchronization: Removed environment-specific checks (e.g., isElectron()) from core settings synchronization functions, making them more generic and potentially usable across different environments or future architectures.

🧠 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.

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 significantly enhances MCP server management by improving synchronization reliability, expanding JSON import/export capabilities, and strengthening error handling. The introduction of a pending sync state for servers is a smart move to prevent race conditions with auto-testing. The refactoring of JSON handling to support both array and object formats, while preserving server IDs, is a major improvement for data integrity and user experience. The code is cleaner and more robust, especially with the extraction of parsing logic into a helper function and better error feedback. My review includes suggestions to further simplify some of the new logic and to fix a potential bug in the validation of imported server configurations.

Comment on lines 738 to 758
// Validate all servers first
for (const serverConfig of serversArray) {
const name = serverConfig.name as string;
if (!name || typeof name !== 'string') {
toast.error('Each server must have a name');
return false;
}

if (typeof servers !== 'object' || Array.isArray(servers)) {
toast.error('Invalid format: expected object with server configurations');
return;
const serverType = (serverConfig.type as string) || 'stdio';
if (serverType === 'stdio') {
if (!serverConfig.command || typeof serverConfig.command !== 'string') {
toast.error(`Command is required for "${name}" (stdio)`);
return false;
}
} else if (serverType === 'sse' || serverType === 'http') {
if (!serverConfig.url || typeof serverConfig.url !== 'string') {
toast.error(`URL is required for "${name}" (${serverType})`);
return false;
}
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

high

The validation loop in handleSaveGlobalJsonArray does not check for duplicate server names within the input array. This could lead to incorrect behavior later in the function, as it relies on finding a newly added server by its name to get its ID. If duplicate names exist, it might retrieve the wrong server's ID.

    // Validate all servers first
    const names = new Set<string>();
    for (const serverConfig of serversArray) {
      const name = serverConfig.name as string;
      if (!name || typeof name !== 'string') {
        toast.error('Each server must have a name');
        return false;
      }
      if (names.has(name)) {
        toast.error(`Duplicate server name found: "${name}"`);
        return false;
      }
      names.add(name);

      const serverType = (serverConfig.type as string) || 'stdio';
      if (serverType === 'stdio') {
        if (!serverConfig.command || typeof serverConfig.command !== 'string') {
          toast.error(`Command is required for "${name}" (stdio)`);
          return false;
        }
      } else if (serverType === 'sse' || serverType === 'http') {
        if (!serverConfig.url || typeof serverConfig.url !== 'string') {
          toast.error(`URL is required for "${name}" (${serverType})`);
          return false;
        }
      }
    }

Comment on lines 317 to 334
const newServers = useAppStore.getState().mcpServers;
const newServerId = newServers[currentCount]?.id;
if (newServerId) {
pendingSyncServerIdsRef.current.add(newServerId);
}

const syncSuccess = await syncSettingsToServer();

// Clear pending sync and trigger auto-test after sync
if (newServerId) {
pendingSyncServerIdsRef.current.delete(newServerId);
if (syncSuccess) {
const newServer = useAppStore.getState().mcpServers.find((s) => s.id === newServerId);
if (newServer && newServer.enabled !== false) {
testServer(newServer, true);
}
}
}
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 code to get the newly added server is a bit inefficient. It retrieves the server's ID, and then later re-queries the entire server list from the store to find the server object by that ID. You can get the server object directly when you get the ID, which simplifies the code and avoids an extra store access and search.

      const newServers = useAppStore.getState().mcpServers;
      const newServer = newServers[currentCount];
      if (newServer) {
        pendingSyncServerIdsRef.current.add(newServer.id);
      }

      const syncSuccess = await syncSettingsToServer();

      // Clear pending sync and trigger auto-test after sync
      if (newServer) {
        pendingSyncServerIdsRef.current.delete(newServer.id);
        if (syncSuccess) {
          if (newServer.enabled !== false) {
            testServer(newServer, true);
          }
        }
      }

Comment on lines 353 to 369
const newServers = useAppStore.getState().mcpServers.slice(currentCount);
const newServerIds = newServers.map((s) => s.id);
newServerIds.forEach((id) => pendingSyncServerIdsRef.current.add(id));

const syncSuccess = await syncSettingsToServer();

// Clear pending sync and trigger auto-test after sync
newServerIds.forEach((id) => pendingSyncServerIdsRef.current.delete(id));
if (syncSuccess) {
const latestServers = useAppStore.getState().mcpServers;
for (const id of newServerIds) {
const server = latestServers.find((s) => s.id === id);
if (server && server.enabled !== false) {
testServer(server, true);
}
}
}
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 logic for handling imported servers is a bit redundant. It gets the new server objects, maps them to an array of IDs, and then later iterates through the IDs to find the server objects again from the store. You can iterate over the newServers array of objects directly to make the code more efficient and readable.

      const newServers = useAppStore.getState().mcpServers.slice(currentCount);
      newServers.forEach((s) => pendingSyncServerIdsRef.current.add(s.id));

      const syncSuccess = await syncSettingsToServer();

      // Clear pending sync and trigger auto-test after sync
      newServers.forEach((s) => pendingSyncServerIdsRef.current.delete(s.id));
      if (syncSuccess) {
        for (const server of newServers) {
          if (server && server.enabled !== false) {
            testServer(server, true);
          }
        }
      }

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: 4

🧹 Nitpick comments (1)
apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts (1)

446-454: Consider documenting the command parsing limitation.

The regex handles basic quoting but doesn't support escaped quotes (e.g., "arg with \"nested\" quotes"). This is likely acceptable for most MCP server commands, but a brief comment would help future maintainers understand the limitation.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a4d1a14 and cf62dbb.

📒 Files selected for processing (2)
  • apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts
  • apps/ui/src/hooks/use-settings-migration.ts
💤 Files with no reviewable changes (1)
  • apps/ui/src/hooks/use-settings-migration.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
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e
🔇 Additional comments (6)
apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts (6)

49-50: LGTM!

Good use of a ref to track pending sync state without triggering re-renders. This correctly prevents race conditions during auto-testing.


134-150: LGTM!

The filtering logic correctly skips both previously auto-tested servers and those pending sync, preventing unnecessary test attempts during synchronization.


473-561: LGTM!

Good implementation supporting both legacy object format and new array format. The validation, deduplication by name, and security warning flow are well-handled.


563-595: LGTM!

The array-based export format with preserved IDs enables full-fidelity re-import. The conditional field inclusion keeps the output clean.


820-892: LGTM!

Good legacy format support. Since object keys are inherently unique, this avoids the duplicate name issue present in the array handler.


894-930: LGTM!

Clean format detection and delegation. The sync-and-feedback pattern is consistent with other handlers.

- Added rollback functionality for server updates on sync failure to maintain local state integrity.
- Enhanced logic for identifying newly added servers during addition and import processes, ensuring accurate pending sync tracking.
- Implemented duplicate server name validation during configuration to prevent errors in server management.
@webdevcody webdevcody merged commit c5ae9ad into main Dec 30, 2025
5 of 6 checks passed
@webdevcody webdevcody deleted the fix-mcp-bug branch December 30, 2025 06: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: 2

🧹 Nitpick comments (1)
apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts (1)

281-289: Good addition of rollback logic on sync failure.

This properly addresses the past review comment about reverting local state when sync fails. The rollback ensures UI consistency when persistence fails.

Optional: Consider deep copy for nested objects

The shallow copy with spread operator works for the current use case, but a deep copy would be more robust for nested fields like env, args, and headers:

-      const previousData = { ...editingServer };
+      const previousData = structuredClone(editingServer);

This prevents potential issues if the store performs deep merges or if nested objects are mutated.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cf62dbb and 5a0ad75.

📒 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
🧬 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 (194-244)
libs/types/src/settings.ts (1)
  • MCPServerConfig (189-214)
apps/ui/src/components/views/settings-view/mcp-servers/types.ts (1)
  • ServerType (3-3)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e
🔇 Additional comments (3)
apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts (3)

384-404: Well-implemented toggle with rollback and auto-test.

The handler properly reverts the enabled state on sync failure and auto-tests newly enabled servers. This provides good user experience and maintains consistency with other handlers.


417-471: Excellent validation and format compatibility.

The parseServerConfig helper properly validates required fields and supports both inline command format (Claude Desktop style) and separate command/args format. This improves import reliability and user experience.


738-764: Thorough validation addresses past review concerns.

The duplicate name validation at lines 746-750 properly addresses the past review comment about potential name collisions. Combined with the per-server type validation, this ensures only valid configurations are imported.

Based on past review comments, this validation prevents the issue where duplicate names could cause incorrect server ID retrieval during import.

Comment on lines 686 to +691
updateMCPServer(jsonEditServer.id, updateData);
await syncSettingsToServer();

const syncSuccess = await syncSettingsToServer();
if (!syncSuccess) {
toast.error('Failed to save settings to disk');
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

Local state not reverted on sync failure.

The handler updates the server at line 686 but doesn't rollback if syncSettingsToServer() fails at line 687. This creates an inconsistency where the UI reflects changes that aren't persisted, similar to the issue addressed at lines 281-289 and 384-393.

🔎 Proposed fix to add rollback
+      const previousServer = structuredClone(jsonEditServer);
       updateMCPServer(jsonEditServer.id, updateData);
       const syncSuccess = await syncSettingsToServer();
       if (!syncSuccess) {
+        updateMCPServer(jsonEditServer.id, previousServer);
         toast.error('Failed to save settings to disk');
         return;
       }

Note: Using structuredClone for a deep copy ensures nested objects (env, headers, args) are properly restored.

🤖 Prompt for AI Agents
In
apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts
around lines 686 to 691, the code updates the local server state then calls
syncSettingsToServer() but does not revert the local update if the sync fails,
leaving the UI inconsistent with persisted state; fix by capturing a deep copy
of the original server (e.g., const prev = structuredClone(jsonEditServer) or
the full servers list) before calling updateMCPServer, then perform the update,
call await syncSettingsToServer() inside try/catch, and if it returns false or
throws, call updateMCPServer(prev.id, prev) to rollback the local change, show
the toast error, and return.

Comment on lines +806 to +814
addMCPServer(serverData);
// Get the newly added server ID
const newServers = useAppStore.getState().mcpServers;
const newServer = newServers.find((s) => s.name === serverName);
if (newServer) {
processedIds.add(newServer.id);
}
}
}
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

Inconsistent ID retrieval method compared to other handlers.

The name-based lookup for newly added servers is less robust than the Set-based approach used at lines 315-334 and 345-373. While the duplicate name validation at lines 746-750 prevents collisions, the approach is still fragile and inefficient.

🔎 Proposed fix using Set-based approach for consistency
+    const addedIds = new Set<string>();
+    const existingIds = new Set(mcpServers.map((s) => s.id));
+
     // Update or add servers
     for (const serverConfig of serversArray) {
       // ... build serverData ...
       
       const existingServer = serverId ? existingById.get(serverId) : existingByName.get(serverName);

       if (existingServer) {
         updateMCPServer(existingServer.id, serverData);
         processedIds.add(existingServer.id);
       } else {
         addMCPServer(serverData);
-        // Get the newly added server ID
+        // Find the newly added server by comparing IDs
         const newServers = useAppStore.getState().mcpServers;
-        const newServer = newServers.find((s) => s.name === serverName);
+        const newServer = newServers.find((s) => !existingIds.has(s.id) && !addedIds.has(s.id));
         if (newServer) {
           processedIds.add(newServer.id);
+          addedIds.add(newServer.id);
+          existingIds.add(newServer.id); // Update for next iteration
         }
       }
     }

This approach matches the pattern used elsewhere in this file (lines 315-334) and is more robust against edge cases.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
apps/ui/src/components/views/settings-view/mcp-servers/hooks/use-mcp-servers.ts
around lines 806 to 814, the code looks up the newly added server by name which
is less robust and inconsistent with the Set-based ID retrieval used elsewhere;
change this to the same pattern used at lines ~315-334 by capturing the
pre-addition set of IDs, calling addMCPServer, then computing the difference
between the new state IDs and the previous set to get the new server ID(s) (or,
if addMCPServer returns the created ID, use that return value), and add those
ID(s) into processedIds instead of searching by name.

@coderabbitai coderabbitai bot mentioned this pull request Jan 4, 2026
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