Skip to content

Commit ad9f561

Browse files
committed
ai-plugin: Add advanced MCP configuration editor dialog
- Add MCPConfigEditorDialog with Monaco Editor integration - Provide syntax highlighting and auto-formatting for JSON configurations - Include tabbed interface with Configuration Editor and Schema Documentation - Add real-time validation with clear error messages - Support for environment variables in server configuration - Include comprehensive schema documentation with field descriptions - Add Load Example and Reset functionality for easy configuration management Signed-off-by: ashu8912 <aghildiyal@microsoft.com>
1 parent cdcadca commit ad9f561

File tree

1 file changed

+341
-0
lines changed

1 file changed

+341
-0
lines changed
Lines changed: 341 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,341 @@
1+
import { Icon } from '@iconify/react';
2+
import { Dialog } from '@kinvolk/headlamp-plugin/lib/CommonComponents';
3+
import Editor from '@monaco-editor/react';
4+
import {
5+
Alert,
6+
Box,
7+
Button,
8+
DialogActions,
9+
DialogContent,
10+
DialogTitle,
11+
Paper,
12+
Tab,
13+
Tabs,
14+
Typography,
15+
} from '@mui/material';
16+
import React, { useEffect, useState } from 'react';
17+
18+
export interface MCPServer {
19+
name: string;
20+
command: string;
21+
args: string[];
22+
env?: Record<string, string>;
23+
enabled: boolean;
24+
}
25+
26+
export interface MCPConfig {
27+
enabled: boolean;
28+
servers: MCPServer[];
29+
}
30+
31+
interface MCPConfigEditorDialogProps {
32+
open: boolean;
33+
onClose: () => void;
34+
config: MCPConfig;
35+
onSave: (config: MCPConfig) => void;
36+
}
37+
38+
export default function MCPConfigEditorDialog({
39+
open,
40+
onClose,
41+
config,
42+
onSave,
43+
}: MCPConfigEditorDialogProps) {
44+
const [content, setContent] = useState('');
45+
const [validationError, setValidationError] = useState('');
46+
const [tabValue, setTabValue] = useState(0);
47+
const themeName = localStorage.getItem('headlampThemePreference');
48+
49+
useEffect(() => {
50+
if (open) {
51+
setContent(JSON.stringify(config, null, 2));
52+
setValidationError('');
53+
setTabValue(0);
54+
}
55+
}, [config, open]);
56+
57+
const handleEditorChange = (value: string | undefined) => {
58+
if (value !== undefined) {
59+
setContent(value);
60+
setValidationError('');
61+
}
62+
};
63+
64+
const validateConfig = (configToValidate: any): string | null => {
65+
if (typeof configToValidate.enabled !== 'boolean') {
66+
return 'enabled field must be a boolean';
67+
}
68+
69+
if (!Array.isArray(configToValidate.servers)) {
70+
return 'servers field must be an array';
71+
}
72+
73+
for (let i = 0; i < configToValidate.servers.length; i++) {
74+
const server = configToValidate.servers[i];
75+
76+
if (typeof server.name !== 'string' || !server.name.trim()) {
77+
return `Server ${i + 1}: name must be a non-empty string`;
78+
}
79+
80+
if (typeof server.command !== 'string' || !server.command.trim()) {
81+
return `Server ${i + 1}: command must be a non-empty string`;
82+
}
83+
84+
if (!Array.isArray(server.args)) {
85+
return `Server ${i + 1}: args must be an array`;
86+
}
87+
88+
if (server.env !== undefined) {
89+
if (typeof server.env !== 'object' || server.env === null || Array.isArray(server.env)) {
90+
return `Server ${i + 1}: env must be an object with string key-value pairs`;
91+
}
92+
93+
for (const [key, value] of Object.entries(server.env)) {
94+
if (typeof key !== 'string' || typeof value !== 'string') {
95+
return `Server ${i + 1}: env must contain only string key-value pairs`;
96+
}
97+
}
98+
}
99+
100+
if (typeof server.enabled !== 'boolean') {
101+
return `Server ${i + 1}: enabled must be a boolean`;
102+
}
103+
}
104+
105+
return null;
106+
};
107+
108+
const handleSave = () => {
109+
try {
110+
const parsedConfig = JSON.parse(content);
111+
112+
const error = validateConfig(parsedConfig);
113+
if (error) {
114+
setValidationError(error);
115+
return;
116+
}
117+
118+
onSave(parsedConfig);
119+
onClose();
120+
} catch (error) {
121+
setValidationError(error instanceof Error ? error.message : 'Invalid JSON configuration');
122+
}
123+
};
124+
125+
const handleLoadExample = () => {
126+
const exampleConfig: MCPConfig = {
127+
enabled: true,
128+
servers: [
129+
{
130+
name: "inspektor-gadget",
131+
command: "docker",
132+
args: [
133+
"mcp",
134+
"gateway",
135+
"run"
136+
],
137+
enabled: true
138+
},
139+
{
140+
name: "flux-mcp",
141+
command: "flux-operator-mcp",
142+
args: [
143+
"serve"
144+
],
145+
env: {
146+
"KUBECONFIG": "/Users/ashughildiyal/.kube/config"
147+
},
148+
enabled: true
149+
}
150+
]
151+
};
152+
153+
setContent(JSON.stringify(exampleConfig, null, 2));
154+
setValidationError('');
155+
};
156+
157+
const handleReset = () => {
158+
setContent(JSON.stringify(config, null, 2));
159+
setValidationError('');
160+
};
161+
162+
const handleTabChange = (_event: React.SyntheticEvent, newValue: number) => {
163+
setTabValue(newValue);
164+
};
165+
166+
const getSchemaDocumentation = () => {
167+
return {
168+
enabled: "boolean - Enable/disable all MCP servers",
169+
servers: [
170+
{
171+
name: "string - Unique server name",
172+
command: "string - Executable command or path",
173+
args: ["array of strings - Command arguments"],
174+
env: {
175+
"KEY": "string value - Environment variables (optional)"
176+
},
177+
enabled: "boolean - Enable/disable this specific server"
178+
}
179+
]
180+
};
181+
};
182+
183+
return (
184+
<Dialog
185+
open={open}
186+
maxWidth="lg"
187+
fullWidth
188+
withFullScreen
189+
style={{ overflow: 'hidden' }}
190+
onClose={onClose}
191+
>
192+
<DialogTitle>
193+
<Box display="flex" justifyContent="space-between" alignItems="center">
194+
<Typography variant="h6">Edit MCP Configuration</Typography>
195+
<Box>
196+
<Button
197+
size="small"
198+
variant="text"
199+
onClick={handleLoadExample}
200+
startIcon={<Icon icon="mdi:file-document" />}
201+
>
202+
Load Example
203+
</Button>
204+
<Button
205+
size="small"
206+
variant="text"
207+
onClick={handleReset}
208+
startIcon={<Icon icon="mdi:refresh" />}
209+
>
210+
Reset
211+
</Button>
212+
</Box>
213+
</Box>
214+
</DialogTitle>
215+
216+
<DialogContent>
217+
<Tabs value={tabValue} onChange={handleTabChange} sx={{ mb: 2 }}>
218+
<Tab label="Configuration Editor" />
219+
<Tab label="Schema Documentation" />
220+
</Tabs>
221+
222+
{tabValue === 0 && (
223+
<Box height="100%">
224+
{validationError && (
225+
<Alert severity="error" sx={{ mb: 2 }}>
226+
{validationError}
227+
</Alert>
228+
)}
229+
230+
<Editor
231+
value={content}
232+
onChange={handleEditorChange}
233+
language="json"
234+
height="450px"
235+
options={{
236+
selectOnLineNumbers: true,
237+
minimap: { enabled: true },
238+
formatOnPaste: true,
239+
formatOnType: true,
240+
automaticLayout: true,
241+
wordWrap: 'on',
242+
scrollBeyondLastLine: false,
243+
fontSize: 14,
244+
}}
245+
theme={themeName === 'dark' ? 'vs-dark' : 'light'}
246+
/>
247+
248+
<Typography variant="caption" color="textSecondary" sx={{ mt: 1 }}>
249+
Edit the JSON configuration above. The editor will automatically format and validate your configuration.
250+
</Typography>
251+
</Box>
252+
)}
253+
254+
{tabValue === 1 && (
255+
<Box>
256+
<Paper sx={{ p: 3, bgcolor: 'grey.50' }}>
257+
<Typography variant="h6" gutterBottom>
258+
Configuration Schema
259+
</Typography>
260+
261+
<Box sx={{ mb: 3 }}>
262+
<Typography variant="body2" color="textSecondary" paragraph>
263+
The MCP configuration defines how your AI assistant connects to external tools and services.
264+
Each server represents a separate MCP server that provides specific capabilities.
265+
</Typography>
266+
</Box>
267+
268+
<Box sx={{ fontFamily: 'monospace' }}>
269+
<pre style={{
270+
backgroundColor: themeName === 'dark' ? '#1e1e1e' : '#f5f5f5',
271+
padding: '16px',
272+
borderRadius: '4px',
273+
overflow: 'auto',
274+
}}>
275+
{JSON.stringify(getSchemaDocumentation(), null, 2)}
276+
</pre>
277+
</Box>
278+
279+
<Box sx={{ mt: 3 }}>
280+
<Typography variant="subtitle2" gutterBottom>
281+
Field Descriptions:
282+
</Typography>
283+
<Box component="ul" sx={{ pl: 2 }}>
284+
<li>
285+
<Typography variant="body2">
286+
<strong>enabled</strong>: Master switch to enable/disable all MCP servers
287+
</Typography>
288+
</li>
289+
<li>
290+
<Typography variant="body2">
291+
<strong>servers</strong>: Array of MCP server configurations
292+
</Typography>
293+
</li>
294+
<li>
295+
<Typography variant="body2">
296+
<strong>name</strong>: Unique identifier for the server
297+
</Typography>
298+
</li>
299+
<li>
300+
<Typography variant="body2">
301+
<strong>command</strong>: The executable to run (e.g., "docker", "npx", "python")
302+
</Typography>
303+
</li>
304+
<li>
305+
<Typography variant="body2">
306+
<strong>args</strong>: Command-line arguments passed to the executable
307+
</Typography>
308+
</li>
309+
<li>
310+
<Typography variant="body2">
311+
<strong>env</strong>: Optional environment variables for the server process
312+
</Typography>
313+
</li>
314+
<li>
315+
<Typography variant="body2">
316+
<strong>enabled</strong>: Toggle individual server on/off without removing configuration
317+
</Typography>
318+
</li>
319+
</Box>
320+
</Box>
321+
</Paper>
322+
</Box>
323+
)}
324+
</DialogContent>
325+
326+
<DialogActions>
327+
<Button variant="outlined" onClick={onClose}>
328+
Cancel
329+
</Button>
330+
<Button
331+
variant="contained"
332+
color="primary"
333+
onClick={handleSave}
334+
disabled={tabValue === 1}
335+
>
336+
Save Configuration
337+
</Button>
338+
</DialogActions>
339+
</Dialog>
340+
);
341+
}

0 commit comments

Comments
 (0)