Welcome to the fifth chapter of the cc-switch tutorial!
In the previous chapter, SQLite Persistence & Schema, we built a permanent memory for our application. We can now remember settings and logs even after the computer restarts.
Now we are going to use that database to solve a massive headache for AI developers: managing MCP Servers.
MCP (Model Context Protocol) is a standard that lets AI models (like Claude or Gemini) talk to your local toolsβlike reading files, querying a database, or searching your git history.
But there is a catch. Each AI tool on your computer has its own configuration file.
claude_desktop_config.json.settings.json.If you download a new MCP tool (like a "Postgres Viewer"), you have to manually edit three different files to let your AI assistants use it. If you change the password, you have to update it three times. It's tedious and error-prone.
Unified MCP Management acts like a Universal Remote Control for your AI tools.
Instead of configuring the "Postgres Viewer" separately for every app, you add it to cc-switch once. Then, you simply toggle switches to "sync" that configuration to Claude, Codex, or Gemini.
This is the master list of all MCP servers you have installed. We store this in our SQLite Persistence & Schema. It holds the command to run the server (e.g., node server.js) and its environment variables (e.g., API_KEY).
The Syncer is the worker bee. When you make a change in cc-switch, the Syncer immediately:
This is the user interface. Itβs a simple "On/Off" switch for each app.
From the user's perspective, managing servers happens in the UnifiedMcpPanel.
Let's look at how the React frontend handles the complexity. The user sees a list of servers and a row of icons representing apps (Claude, Codex, etc.).
When a user clicks an icon to enable a server for an app, we trigger a mutation.
// src/components/mcp/UnifiedMcpPanel.tsx
// This function runs when you click an app icon (like the Claude logo)
const handleToggleApp = async (
serverId: string,
app: AppId,
enabled: boolean,
) => {
try {
// 1. Send command to Rust backend
await toggleAppMutation.mutateAsync({ serverId, app, enabled });
} catch (error) {
// 2. Show error if sync fails
toast.error(t("common.error"), { description: String(error) });
}
};
Beginner Note:
mutateAsyncsends a message to the Rust backend saying: "Hey, the user wants Server X enabled for Claude. Please make it happen."
We use a custom component UnifiedMcpListItem to show the server details and the toggle buttons.
// src/components/mcp/UnifiedMcpPanel.tsx
// Inside the render loop
<UnifiedMcpListItem
key={id}
id={id}
server={server}
// Connect the toggle action to our handler
onToggleApp={handleToggleApp}
onEdit={handleEdit}
onDelete={handleDelete}
/>
What happens inside the Rust backend when you toggle that switch?
We organize our backend logic by "Target App". Each app (Claude, Codex, Gemini) has its own module because their configuration files look different.
// src-tauri/src/mcp/mod.rs
// We create separate modules for each target application
mod claude; // Logic to edit claude_desktop_config.json
mod codex; // Logic to edit VS Code settings
mod gemini; // Logic for Gemini
// We verify the server configuration is valid
mod validation;
// We expose simple functions to the rest of the app
pub use claude::sync_enabled_to_claude;
pub use codex::sync_enabled_to_codex;
While the actual file parsing code is detailed, the logic follows a simple pattern. Here is a simplified version of what sync_enabled_to_claude does:
// Simplified logic inside src-tauri/src/mcp/claude.rs
pub fn sync_enabled_to_claude(servers: Vec<McpServer>) -> Result<()> {
// 1. Find where Claude stores its config file
let config_path = get_claude_config_path();
// 2. Read the existing JSON
let mut config_json = read_json_file(&config_path)?;
// 3. Update the "mcpServers" section
// We convert our internal server format to Claude's format
config_json["mcpServers"] = convert_to_claude_format(servers);
// 4. Save the file back to disk
write_json_file(&config_path, config_json)?;
Ok(())
}
Beginner Note: This function is a "Translator". It translates the data from our database format into the specific JSON format that Claude understands, and puts it in the file where Claude expects to find it.
cc-switch handles the writing programmatically, ensuring the syntax is always correct.In this chapter, we built the Unified MCP Management system:
Now we have a fully functional system! We can route traffic, handle failovers, adapt protocols, persist settings, and manage external tools.
But... how do we know if it's working well? Are we saving money? Which provider is failing the most?
In the final chapter, we will visualize our data.
Next Chapter: Usage Analytics Dashboard
Generated by Code IQ