Skip to content

Commit 8c4426d

Browse files
committed
feat: add McpServerCard component for improved server management UI
Introduced the McpServerCard component to enhance the MCP page's server management interface. This new component encapsulates server details, enabling users to toggle server states, edit configurations, and delete servers more intuitively. Updated the MCP page to utilize this component, streamlining the server display and interaction logic. Additionally, re-exported the new component and updated the index file for better modularity.
1 parent 8ba4568 commit 8c4426d

File tree

3 files changed

+162
-128
lines changed

3 files changed

+162
-128
lines changed
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import type { Dispatch, SetStateAction } from 'react';
2+
import { invoke } from '@/lib/tauri-proxy';
3+
import { Button } from '@/components/ui/button';
4+
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
5+
import { Switch } from '@/components/ui/switch';
6+
import { Trash2, Edit } from 'lucide-react';
7+
import { McpServerConfig } from '@/types';
8+
import { toast } from 'sonner';
9+
10+
export const getServerProtocol = (config: McpServerConfig): 'stdio' | 'http' | 'sse' =>
11+
config.type ?? 'stdio';
12+
13+
interface McpServerCardProps {
14+
name: string;
15+
config: McpServerConfig;
16+
loadServers: () => Promise<void>;
17+
setServers: Dispatch<SetStateAction<Record<string, McpServerConfig>>>;
18+
onEdit: (name: string, config: McpServerConfig) => void;
19+
}
20+
21+
export function McpServerCard({ name, config, loadServers, setServers, onEdit }: McpServerCardProps) {
22+
const serverType = getServerProtocol(config);
23+
const isEnabled = config.enabled ?? true;
24+
25+
const handleDeleteServer = async () => {
26+
try {
27+
await invoke('delete_mcp_server', { name });
28+
await loadServers();
29+
} catch (error) {
30+
console.error('Failed to delete MCP server:', error);
31+
toast.error('Failed to delete MCP server: ' + error);
32+
}
33+
};
34+
35+
const handleToggleServerEnabled = async (enabled: boolean) => {
36+
try {
37+
await invoke('set_mcp_server_enabled', { name, enabled });
38+
setServers((prev) => {
39+
const server = prev[name];
40+
if (!server) {
41+
return prev;
42+
}
43+
return {
44+
...prev,
45+
[name]: {
46+
...server,
47+
enabled,
48+
},
49+
};
50+
});
51+
} catch (error) {
52+
console.error('Failed to update MCP server enabled flag:', error);
53+
toast.error('Failed to update MCP server enabled flag: ' + error);
54+
}
55+
};
56+
57+
return (
58+
<Card className="gap-0">
59+
<CardHeader>
60+
<CardTitle className="text-sm flex items-center justify-between">
61+
{name}
62+
<div className="flex gap-1 items-center">
63+
<Switch
64+
checked={isEnabled}
65+
onCheckedChange={(checked) => handleToggleServerEnabled(checked)}
66+
aria-label={`Toggle ${name} server`}
67+
/>
68+
<Button size="sm" variant="ghost" onClick={() => onEdit(name, config)}>
69+
<Edit className="h-4 w-4" />
70+
</Button>
71+
<Button size="sm" variant="ghost" onClick={handleDeleteServer}>
72+
<Trash2 className="h-4 w-4" />
73+
</Button>
74+
</div>
75+
</CardTitle>
76+
</CardHeader>
77+
<CardContent>
78+
<div className="text-xs text-gray-600">
79+
{serverType === 'stdio' && (
80+
<div>
81+
<strong>Command:</strong> {'command' in config ? config.command : ''}
82+
{'args' in config && config.args && config.args.length > 0 && (
83+
<div>
84+
<strong>Args:</strong> {config.args.join(' ')}
85+
</div>
86+
)}
87+
{'env' in config && config.env && (
88+
<div>
89+
<strong>Env:</strong> {Object.keys(config.env).join(', ')}
90+
</div>
91+
)}
92+
</div>
93+
)}
94+
{serverType === 'http' && 'url' in config && (
95+
<div>
96+
<strong>url:</strong> {config.url}
97+
</div>
98+
)}
99+
{serverType === 'sse' && 'url' in config && (
100+
<div>
101+
<strong>url:</strong> {config.url}
102+
</div>
103+
)}
104+
</div>
105+
</CardContent>
106+
</Card>
107+
);
108+
}

src/components/mcp/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
export * from "./McpServerForm";
22
export * from "./McpLinkerButton";
3-
export * from "./DefaultMcpServers";
3+
export * from "./DefaultMcpServers";
4+
export * from "./McpServerCard";

src/pages/mcp.tsx

Lines changed: 52 additions & 127 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import { useState, useEffect } from 'react';
22
import { invoke } from '@/lib/tauri-proxy';
33
import { Button } from '@/components/ui/button';
4-
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
5-
import { Switch } from '@/components/ui/switch';
6-
import { Trash2, Plus, Edit, Save, X } from 'lucide-react';
4+
import { Plus, Save, X } from 'lucide-react';
75
import { McpServerConfig } from '@/types';
86
import { toast } from 'sonner';
9-
import { McpServerForm, McpLinkerButton, DefaultMcpServers } from '@/components/mcp';
10-
11-
const getServerProtocol = (config: McpServerConfig): 'stdio' | 'http' | 'sse' =>
12-
config.type ?? 'stdio';
7+
import {
8+
McpServerForm,
9+
McpLinkerButton,
10+
DefaultMcpServers,
11+
McpServerCard,
12+
getServerProtocol,
13+
} from '@/components/mcp';
1314

1415
export default function McpPage() {
1516
const [servers, setServers] = useState<Record<string, McpServerConfig>>({});
@@ -84,38 +85,6 @@ export default function McpPage() {
8485
}
8586
};
8687

87-
const handleDeleteServer = async (name: string) => {
88-
try {
89-
await invoke('delete_mcp_server', { name });
90-
loadServers();
91-
} catch (error) {
92-
console.error('Failed to delete MCP server:', error);
93-
toast.error('Failed to delete MCP server: ' + error);
94-
}
95-
};
96-
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-
11988
const handleEditServer = (name: string, config: McpServerConfig) => {
12089
const protocol = getServerProtocol(config);
12190
const httpUrl = protocol === 'stdio' ? '' : ('url' in config ? config.url : '');
@@ -197,96 +166,52 @@ export default function McpPage() {
197166
<div>
198167
<h3 className="text-lg font-semibold mb-4">Configured Servers</h3>
199168
<div className="space-y-2 max-h-96 overflow-y-auto">
200-
{Object.entries(servers).map(([name, config]) => {
201-
const serverType = getServerProtocol(config);
202-
const isEnabled = config.enabled ?? true;
203-
return (
204-
<Card key={name}>
205-
{editingServer === name ? (
206-
<div className="px-4">
207-
<div className="space-y-4">
208-
<McpServerForm
209-
serverName={editConfig?.name ?? ''}
210-
onServerNameChange={(name) => setEditConfig(prev => prev ? { ...prev, name } : null)}
211-
protocol={editConfig?.protocol ?? 'stdio'}
212-
onProtocolChange={(protocol) => setEditConfig(prev => prev ? { ...prev, protocol } : null)}
213-
commandConfig={editConfig?.command ?? { command: '', args: '', env: '' }}
214-
onCommandConfigChange={(command) => setEditConfig(prev => prev ? { ...prev, command } : null)}
215-
httpConfig={editConfig?.http ?? { url: '' }}
216-
onHttpConfigChange={(http) => setEditConfig(prev => prev ? { ...prev, http } : null)}
217-
isEditMode={true}
218-
/>
219-
220-
<div className="flex gap-2">
221-
<Button size="sm" onClick={handleSaveEdit}>
222-
<Save className="h-4 w-4 mr-1" />
223-
Save
224-
</Button>
225-
<Button size="sm" variant="outline" onClick={handleCancelEdit}>
226-
<X className="h-4 w-4 mr-1" />
227-
Cancel
228-
</Button>
229-
</div>
169+
{Object.entries(servers).map(([name, config]) => (
170+
<div key={name}>
171+
{editingServer === name ? (
172+
<div className="px-4">
173+
<div className="space-y-4">
174+
<McpServerForm
175+
serverName={editConfig?.name ?? ''}
176+
onServerNameChange={(name) => setEditConfig((prev) => (prev ? { ...prev, name } : null))}
177+
protocol={editConfig?.protocol ?? 'stdio'}
178+
onProtocolChange={(protocol) =>
179+
setEditConfig((prev) => (prev ? { ...prev, protocol } : null))
180+
}
181+
commandConfig={editConfig?.command ?? { command: '', args: '', env: '' }}
182+
onCommandConfigChange={(command) =>
183+
setEditConfig((prev) => (prev ? { ...prev, command } : null))
184+
}
185+
httpConfig={editConfig?.http ?? { url: '' }}
186+
onHttpConfigChange={(http) =>
187+
setEditConfig((prev) => (prev ? { ...prev, http } : null))
188+
}
189+
isEditMode={true}
190+
/>
191+
192+
<div className="flex gap-2">
193+
<Button size="sm" onClick={handleSaveEdit}>
194+
<Save className="h-4 w-4 mr-1" />
195+
Save
196+
</Button>
197+
<Button size="sm" variant="outline" onClick={handleCancelEdit}>
198+
<X className="h-4 w-4 mr-1" />
199+
Cancel
200+
</Button>
230201
</div>
231202
</div>
232-
) : (
233-
<>
234-
<CardHeader>
235-
<CardTitle className="text-sm flex items-center justify-between">
236-
{name}
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-
/>
243-
<Button
244-
size="sm"
245-
variant="ghost"
246-
onClick={() => handleEditServer(name, config)}
247-
>
248-
<Edit className="h-4 w-4" />
249-
</Button>
250-
<Button
251-
size="sm"
252-
variant="ghost"
253-
onClick={() => handleDeleteServer(name)}
254-
>
255-
<Trash2 className="h-4 w-4" />
256-
</Button>
257-
</div>
258-
</CardTitle>
259-
</CardHeader>
260-
<CardContent>
261-
<div className="text-xs text-gray-600">
262-
{serverType === 'stdio' && (
263-
<div>
264-
<strong>Command:</strong> {'command' in config ? config.command : ''}
265-
{'args' in config && config.args && config.args.length > 0 && (
266-
<div><strong>Args:</strong> {config.args.join(' ')}</div>
267-
)}
268-
{'env' in config && config.env && (
269-
<div><strong>Env:</strong> {Object.keys(config.env).join(', ')}</div>
270-
)}
271-
</div>
272-
)}
273-
{serverType === 'http' && 'url' in config && (
274-
<div>
275-
<strong>url:</strong> {config.url}
276-
</div>
277-
)}
278-
{serverType === 'sse' && 'url' in config && (
279-
<div>
280-
<strong>url:</strong> {config.url}
281-
</div>
282-
)}
283-
</div>
284-
</CardContent>
285-
</>
286-
)}
287-
</Card>
288-
);
289-
})}
203+
</div>
204+
) : (
205+
<McpServerCard
206+
name={name}
207+
config={config}
208+
loadServers={loadServers}
209+
setServers={setServers}
210+
onEdit={handleEditServer}
211+
/>
212+
)}
213+
</div>
214+
))}
290215
{Object.keys(servers).length === 0 && (
291216
<div className="text-gray-500 text-center py-8">
292217
No MCP servers configured

0 commit comments

Comments
 (0)