Skip to content

Commit 5708907

Browse files
committed
feat(web): add mcp server toggle and fix dialog overflow
1 parent f258b3a commit 5708907

3 files changed

Lines changed: 72 additions & 33 deletions

File tree

web/src/components/CommandsManager.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -263,12 +263,12 @@ export default function CommandsManager() {
263263

264264
{/* Add Dialog */}
265265
<Dialog open={addDialogOpen} onOpenChange={setAddDialogOpen}>
266-
<DialogContent className="max-w-2xl">
266+
<DialogContent className="max-w-2xl h-[min(80vh,600px)] flex flex-col">
267267
<DialogHeader>
268268
<DialogTitle>Create New Command</DialogTitle>
269269
<DialogDescription>Create a new command with name, description and content.</DialogDescription>
270270
</DialogHeader>
271-
<div className="space-y-4">
271+
<div className="flex flex-col flex-1 min-h-0 gap-4">
272272
<div className="space-y-2">
273273
<label className="text-sm font-medium">Name (without slash)</label>
274274
<Input
@@ -285,10 +285,10 @@ export default function CommandsManager() {
285285
onChange={(e: React.ChangeEvent<HTMLInputElement>) => setNewCommandDescription(e.target.value)}
286286
/>
287287
</div>
288-
<div className="space-y-2">
288+
<div className="flex flex-col flex-1 min-h-0 space-y-2">
289289
<label className="text-sm font-medium">Content</label>
290290
<Textarea
291-
className="min-h-[200px] font-mono"
291+
className="flex-1 min-h-[100px] font-mono resize-none"
292292
placeholder="# Your Command&#10;&#10;Instructions here..."
293293
value={newCommandContent}
294294
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) => setNewCommandContent(e.target.value)}

web/src/components/McpManager.tsx

Lines changed: 65 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
AlertDialogHeader,
1616
AlertDialogTitle,
1717
} from "@/components/ui/alert-dialog";
18+
import { Switch } from '@/components/ui/switch';
1819
import { listMcpServers, removeMcpServer, updateMcpServer, type McpServer } from '@/utils/api';
1920

2021
export default function McpManager() {
@@ -63,10 +64,27 @@ export default function McpManager() {
6364
setEditConfig(getServerConfig(server));
6465
};
6566

67+
const parseServerConfig = (json: string, serverName: string) => {
68+
const parsed = JSON.parse(json);
69+
// Handle full mcpServers format: { "mcpServers": { "name": { ... } } }
70+
if (parsed.mcpServers && typeof parsed.mcpServers === 'object') {
71+
const servers = parsed.mcpServers;
72+
const keys = Object.keys(servers);
73+
if (keys.length === 1) {
74+
return servers[keys[0]];
75+
}
76+
if (servers[serverName]) {
77+
return servers[serverName];
78+
}
79+
throw new Error(`Server "${serverName}" not found in mcpServers`);
80+
}
81+
return parsed;
82+
};
83+
6684
const handleSaveEdit = async () => {
6785
if (!editingServer) return;
6886
try {
69-
const config = JSON.parse(editConfig);
87+
const config = parseServerConfig(editConfig, editingServer.name);
7088
await updateMcpServer(editingServer.name, config);
7189
setEditingServer(null);
7290
await loadServers();
@@ -78,7 +96,7 @@ export default function McpManager() {
7896
const handleAdd = async () => {
7997
if (!newServerName.trim()) return;
8098
try {
81-
const config = JSON.parse(newServerConfig);
99+
const config = parseServerConfig(newServerConfig, newServerName.trim());
82100
await updateMcpServer(newServerName.trim(), config);
83101
setNewServerName('');
84102
setNewServerConfig('{\n "type": "stdio",\n "command": "npx",\n "args": ["-y", "package-name"]\n}');
@@ -100,6 +118,17 @@ export default function McpManager() {
100118
setServerToDelete(null);
101119
};
102120

121+
const handleToggleEnabled = async (server: McpServer) => {
122+
try {
123+
const config = { ...server, disabled: !server.disabled };
124+
delete (config as Record<string, unknown>).name;
125+
await updateMcpServer(server.name, config);
126+
await loadServers();
127+
} catch (err) {
128+
alert(err instanceof Error ? err.message : 'Failed to toggle');
129+
}
130+
};
131+
103132
if (loading) {
104133
return (
105134
<div className="flex items-center justify-center h-64">
@@ -163,6 +192,13 @@ export default function McpManager() {
163192
className="flex items-center gap-3 px-4 py-3 cursor-pointer hover:bg-muted/50"
164193
onClick={() => setExpandedServer(isExpanded ? null : server.name)}
165194
>
195+
<div onClick={(e) => e.stopPropagation()}>
196+
<Switch
197+
checked={!server.disabled}
198+
onCheckedChange={() => handleToggleEnabled(server)}
199+
title={server.disabled ? 'Enable' : 'Disable'}
200+
/>
201+
</div>
166202
<div className="text-muted-foreground">
167203
{isExpanded ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
168204
</div>
@@ -178,33 +214,33 @@ export default function McpManager() {
178214
</p>
179215
</div>
180216
<div className="flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity" onClick={(e) => e.stopPropagation()}>
181-
<Button
182-
variant="ghost"
183-
size="icon"
184-
className="h-8 w-8"
185-
onClick={() => handleCopy(server)}
186-
title="Copy config"
187-
>
188-
{copiedServer === server.name ? <Check className="h-4 w-4 text-green-500" /> : <Copy className="h-4 w-4" />}
189-
</Button>
190-
<Button
191-
variant="ghost"
192-
size="icon"
193-
className="h-8 w-8"
194-
onClick={() => handleEdit(server)}
195-
title="Edit"
196-
>
197-
<Pencil className="h-4 w-4" />
198-
</Button>
199-
<Button
200-
variant="ghost"
201-
size="icon"
202-
className="h-8 w-8 text-muted-foreground hover:text-destructive hover:bg-destructive/10"
203-
onClick={() => setServerToDelete(server.name)}
204-
title="Delete"
205-
>
206-
<Trash2 className="h-4 w-4" />
207-
</Button>
217+
<Button
218+
variant="ghost"
219+
size="icon"
220+
className="h-8 w-8"
221+
onClick={() => handleCopy(server)}
222+
title="Copy config"
223+
>
224+
{copiedServer === server.name ? <Check className="h-4 w-4 text-green-500" /> : <Copy className="h-4 w-4" />}
225+
</Button>
226+
<Button
227+
variant="ghost"
228+
size="icon"
229+
className="h-8 w-8"
230+
onClick={() => handleEdit(server)}
231+
title="Edit"
232+
>
233+
<Pencil className="h-4 w-4" />
234+
</Button>
235+
<Button
236+
variant="ghost"
237+
size="icon"
238+
className="h-8 w-8 text-muted-foreground hover:text-destructive hover:bg-destructive/10"
239+
onClick={() => setServerToDelete(server.name)}
240+
title="Delete"
241+
>
242+
<Trash2 className="h-4 w-4" />
243+
</Button>
208244
</div>
209245
</div>
210246
{isExpanded && (

web/src/utils/api.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,9 @@ export interface McpServer {
228228
args?: string[];
229229
url?: string;
230230
env?: Record<string, string>;
231+
disabled?: boolean;
232+
alwaysAllow?: string[];
233+
[key: string]: unknown;
231234
}
232235

233236
export interface CustomModel {

0 commit comments

Comments
 (0)