Skip to content

Commit 8ba4568

Browse files
committed
feat: implement server enable/disable functionality in MCP management
Added the ability to toggle the enabled state of MCP servers through a new switch component in the MCP page. Updated server configuration types to include an 'enabled' property, and implemented backend support for updating this state in the configuration file. Enhanced error handling and user feedback with toast notifications for better user experience.
1 parent 87c2cd4 commit 8ba4568

File tree

4 files changed

+136
-15
lines changed

4 files changed

+136
-15
lines changed

src-tauri/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ use filesystem::{
3030
},
3131
watch::{start_watch_directory, stop_watch_directory},
3232
};
33-
use mcp::{add_mcp_server, delete_mcp_server, read_mcp_servers};
33+
use mcp::{add_mcp_server, delete_mcp_server, read_mcp_servers, set_mcp_server_enabled};
3434
use session_files::{
3535
cache::{load_project_sessions, write_project_cache, update_project_favorites, remove_project_session},
3636
delete::{delete_session_file, delete_sessions_files},
@@ -107,6 +107,7 @@ pub fn run() {
107107
read_mcp_servers,
108108
add_mcp_server,
109109
delete_mcp_server,
110+
set_mcp_server_enabled,
110111
config::provider::read_model_providers,
111112
config::profile::read_profiles,
112113
config::profile::get_provider_config,

src-tauri/src/mcp.rs

Lines changed: 101 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,19 @@ use std::collections::HashMap;
33
use std::fs;
44
use std::str::FromStr;
55
use tauri::command;
6-
use toml_edit::{Document, Item, Table};
6+
use toml_edit::{value, Document, Item, Table};
77

88
use crate::config::{get_config_path, CodexConfig};
99
use crate::config::toml_helpers::serialize_to_table;
1010

11+
fn default_enabled() -> bool {
12+
true
13+
}
14+
15+
fn is_enabled_true(enabled: &bool) -> bool {
16+
*enabled
17+
}
18+
1119
#[derive(Debug, Clone, Serialize)]
1220
#[serde(tag = "type")]
1321
pub enum McpServerConfig {
@@ -17,11 +25,21 @@ pub enum McpServerConfig {
1725
args: Vec<String>,
1826
#[serde(skip_serializing_if = "Option::is_none")]
1927
env: Option<HashMap<String, String>>,
28+
#[serde(default = "default_enabled", skip_serializing_if = "is_enabled_true")]
29+
enabled: bool,
2030
},
2131
#[serde(rename = "http")]
22-
Http { url: String },
32+
Http {
33+
url: String,
34+
#[serde(default = "default_enabled", skip_serializing_if = "is_enabled_true")]
35+
enabled: bool,
36+
},
2337
#[serde(rename = "sse")]
24-
Sse { url: String },
38+
Sse {
39+
url: String,
40+
#[serde(default = "default_enabled", skip_serializing_if = "is_enabled_true")]
41+
enabled: bool,
42+
},
2543
}
2644

2745
impl<'de> Deserialize<'de> for McpServerConfig {
@@ -38,11 +56,21 @@ impl<'de> Deserialize<'de> for McpServerConfig {
3856
args: Vec<String>,
3957
#[serde(default)]
4058
env: Option<HashMap<String, String>>,
59+
#[serde(default = "default_enabled")]
60+
enabled: bool,
4161
},
4262
#[serde(rename = "http")]
43-
Http { url: String },
63+
Http {
64+
url: String,
65+
#[serde(default = "default_enabled")]
66+
enabled: bool,
67+
},
4468
#[serde(rename = "sse")]
45-
Sse { url: String },
69+
Sse {
70+
url: String,
71+
#[serde(default = "default_enabled")]
72+
enabled: bool,
73+
},
4674
}
4775

4876
#[derive(Deserialize)]
@@ -52,6 +80,8 @@ impl<'de> Deserialize<'de> for McpServerConfig {
5280
args: Vec<String>,
5381
#[serde(default)]
5482
env: Option<HashMap<String, String>>,
83+
#[serde(default = "default_enabled")]
84+
enabled: bool,
5585
}
5686

5787
#[derive(Deserialize)]
@@ -66,16 +96,30 @@ impl<'de> Deserialize<'de> for McpServerConfig {
6696
command,
6797
args,
6898
env,
69-
}) => Ok(McpServerConfig::Stdio { command, args, env }),
70-
McpServerConfigHelper::Tagged(TaggedMcpServerConfig::Http { url }) => {
71-
Ok(McpServerConfig::Http { url })
72-
}
73-
McpServerConfigHelper::Tagged(TaggedMcpServerConfig::Sse { url }) => {
74-
Ok(McpServerConfig::Sse { url })
99+
enabled,
100+
}) => Ok(McpServerConfig::Stdio {
101+
command,
102+
args,
103+
env,
104+
enabled,
105+
}),
106+
McpServerConfigHelper::Tagged(TaggedMcpServerConfig::Http { url, enabled }) => {
107+
Ok(McpServerConfig::Http { url, enabled })
75108
}
76-
McpServerConfigHelper::Legacy(LegacyStdioConfig { command, args, env }) => {
77-
Ok(McpServerConfig::Stdio { command, args, env })
109+
McpServerConfigHelper::Tagged(TaggedMcpServerConfig::Sse { url, enabled }) => {
110+
Ok(McpServerConfig::Sse { url, enabled })
78111
}
112+
McpServerConfigHelper::Legacy(LegacyStdioConfig {
113+
command,
114+
args,
115+
env,
116+
enabled,
117+
}) => Ok(McpServerConfig::Stdio {
118+
command,
119+
args,
120+
env,
121+
enabled,
122+
}),
79123
}
80124
}
81125
}
@@ -176,3 +220,47 @@ pub async fn delete_mcp_server(name: String) -> Result<(), String> {
176220

177221
Ok(())
178222
}
223+
224+
#[command]
225+
pub async fn set_mcp_server_enabled(name: String, enabled: bool) -> Result<(), String> {
226+
let config_path = get_config_path()?;
227+
228+
if !config_path.exists() {
229+
return Err("Config file does not exist".to_string());
230+
}
231+
232+
let content = fs::read_to_string(&config_path)
233+
.map_err(|e| format!("Failed to read config file: {}", e))?;
234+
235+
let mut doc = Document::from_str(&content)
236+
.map_err(|e| format!("Failed to parse config file: {}", e))?;
237+
238+
let mcp_servers_entry = doc
239+
.entry("mcp_servers")
240+
.or_insert(Item::Table(Table::new()));
241+
242+
let mcp_servers_table = mcp_servers_entry
243+
.as_table_mut()
244+
.ok_or("Failed to access mcp_servers table")?;
245+
246+
let server_entry = mcp_servers_table
247+
.get_mut(&name)
248+
.ok_or(format!("MCP server '{}' not found", name))?;
249+
250+
let server_table = server_entry
251+
.as_table_mut()
252+
.ok_or(format!("MCP server '{}' is not a table", name))?;
253+
254+
if enabled {
255+
server_table.remove("enabled");
256+
} else {
257+
server_table.insert("enabled", value(enabled));
258+
}
259+
260+
let toml_content = doc.to_string();
261+
262+
fs::write(&config_path, toml_content)
263+
.map_err(|e| format!("Failed to write config file: {}", e))?;
264+
265+
Ok(())
266+
}

src/pages/mcp.tsx

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { useState, useEffect } from 'react';
22
import { invoke } from '@/lib/tauri-proxy';
33
import { Button } from '@/components/ui/button';
44
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
5+
import { Switch } from '@/components/ui/switch';
56
import { Trash2, Plus, Edit, Save, X } from 'lucide-react';
67
import { McpServerConfig } from '@/types';
78
import { toast } from 'sonner';
@@ -93,6 +94,28 @@ export default function McpPage() {
9394
}
9495
};
9596

97+
const handleToggleServerEnabled = async (name: string, enabled: boolean) => {
98+
try {
99+
await invoke('set_mcp_server_enabled', { name, enabled });
100+
setServers((prev) => {
101+
const server = prev[name];
102+
if (!server) {
103+
return prev;
104+
}
105+
return {
106+
...prev,
107+
[name]: {
108+
...server,
109+
enabled,
110+
},
111+
};
112+
});
113+
} catch (error) {
114+
console.error('Failed to update MCP server enabled flag:', error);
115+
toast.error('Failed to update MCP server enabled flag: ' + error);
116+
}
117+
};
118+
96119
const handleEditServer = (name: string, config: McpServerConfig) => {
97120
const protocol = getServerProtocol(config);
98121
const httpUrl = protocol === 'stdio' ? '' : ('url' in config ? config.url : '');
@@ -176,6 +199,7 @@ export default function McpPage() {
176199
<div className="space-y-2 max-h-96 overflow-y-auto">
177200
{Object.entries(servers).map(([name, config]) => {
178201
const serverType = getServerProtocol(config);
202+
const isEnabled = config.enabled ?? true;
179203
return (
180204
<Card key={name}>
181205
{editingServer === name ? (
@@ -210,7 +234,12 @@ export default function McpPage() {
210234
<CardHeader>
211235
<CardTitle className="text-sm flex items-center justify-between">
212236
{name}
213-
<div className="flex gap-1">
237+
<div className="flex gap-1 items-center">
238+
<Switch
239+
checked={isEnabled}
240+
onCheckedChange={(checked) => handleToggleServerEnabled(name, checked)}
241+
aria-label={`Toggle ${name} server`}
242+
/>
214243
<Button
215244
size="sm"
216245
variant="ghost"

src/types/mcp.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,19 @@ type StdioMcpServerConfig = {
33
command: string;
44
args: string[];
55
env?: Record<string, string>;
6+
enabled?: boolean;
67
};
78

89
type HttpMcpServerConfig = {
910
type: "http";
1011
url: string;
12+
enabled?: boolean;
1113
};
1214

1315
type SseMcpServerConfig = {
1416
type: "sse";
1517
url: string;
18+
enabled?: boolean;
1619
};
1720

1821
export type McpServerConfig =

0 commit comments

Comments
 (0)