Files
SuperCharged-Claude-Code-Up…/dexto/packages/webui/components/AddCustomServerModal.tsx
admin b52318eeae feat: Add intelligent auto-router and enhanced integrations
- Add intelligent-router.sh hook for automatic agent routing
- Add AUTO-TRIGGER-SUMMARY.md documentation
- Add FINAL-INTEGRATION-SUMMARY.md documentation
- Complete Prometheus integration (6 commands + 4 tools)
- Complete Dexto integration (12 commands + 5 tools)
- Enhanced Ralph with access to all agents
- Fix /clawd command (removed disable-model-invocation)
- Update hooks.json to v5 with intelligent routing
- 291 total skills now available
- All 21 commands with automatic routing

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2026-01-28 00:27:56 +04:00

599 lines
24 KiB
TypeScript

import React, { useState } from 'react';
import type { ServerRegistryEntry } from '@dexto/registry';
import {
Dialog,
DialogContent,
DialogHeader,
DialogTitle,
DialogDescription,
DialogFooter,
} from './ui/dialog';
import { Button } from './ui/button';
import { Input } from './ui/input';
import { Label } from './ui/label';
import { Textarea } from './ui/textarea';
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from './ui/select';
import { Alert, AlertDescription } from './ui/alert';
import { Plus, Save } from 'lucide-react';
import { KeyValueEditor } from './ui/key-value-editor';
interface HeaderPair {
key: string;
value: string;
id: string;
}
interface AddCustomServerModalProps {
isOpen: boolean;
onClose: () => void;
onAddServer: (
entry: Omit<ServerRegistryEntry, 'id' | 'isOfficial' | 'lastUpdated'>
) => Promise<void>;
}
export default function AddCustomServerModal({
isOpen,
onClose,
onAddServer,
}: AddCustomServerModalProps) {
const [formData, setFormData] = useState<{
name: string;
description: string;
category:
| 'productivity'
| 'development'
| 'research'
| 'creative'
| 'data'
| 'communication'
| 'custom';
icon: string;
version: string;
author: string;
homepage: string;
config: {
type: 'stdio' | 'sse' | 'http';
command: string;
args: string[];
url: string;
env: Record<string, string>;
headers: Record<string, string>;
timeout: number;
};
tags: string[];
isInstalled: boolean;
requirements: {
platform: 'win32' | 'darwin' | 'linux' | 'all';
node: string;
python: string;
dependencies: string[];
};
}>({
name: '',
description: '',
category: 'custom',
icon: '',
version: '',
author: '',
homepage: '',
config: {
type: 'stdio',
command: '',
args: [],
url: '',
env: {},
headers: {},
timeout: 30000,
},
tags: [],
isInstalled: false,
requirements: {
platform: 'all',
node: '',
python: '',
dependencies: [],
},
});
const [argsInput, setArgsInput] = useState('');
const [tagsInput, setTagsInput] = useState('');
const [envInput, setEnvInput] = useState('');
const [headerPairs, setHeaderPairs] = useState<HeaderPair[]>([]);
const [dependenciesInput, setDependenciesInput] = useState('');
const [error, setError] = useState<string | null>(null);
const [isSubmitting, setIsSubmitting] = useState(false);
const categories = [
{ value: 'productivity', label: 'Productivity' },
{ value: 'development', label: 'Development' },
{ value: 'research', label: 'Research' },
{ value: 'creative', label: 'Creative' },
{ value: 'data', label: 'Data' },
{ value: 'communication', label: 'Communication' },
{ value: 'custom', label: 'Custom' },
];
const platforms = [
{ value: 'all', label: 'All Platforms' },
{ value: 'win32', label: 'Windows' },
{ value: 'darwin', label: 'macOS' },
{ value: 'linux', label: 'Linux' },
];
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError(null);
setIsSubmitting(true);
try {
// Parse inputs
const args = argsInput
.split(',')
.map((s) => s.trim())
.filter(Boolean);
const tags = tagsInput
.split(',')
.map((s) => s.trim())
.filter(Boolean);
const dependencies = dependenciesInput
.split(',')
.map((s) => s.trim())
.filter(Boolean);
// Parse environment variables
const env: Record<string, string> = {};
if (envInput.trim()) {
const envLines = envInput.split('\n');
for (const line of envLines) {
// Skip empty lines
const trimmedLine = line.trim();
if (!trimmedLine) {
continue;
}
// Split only at the first '=' character
const equalIndex = trimmedLine.indexOf('=');
if (equalIndex > 0) {
// Key must exist (equalIndex > 0, not >= 0)
const key = trimmedLine.substring(0, equalIndex).trim();
const value = trimmedLine.substring(equalIndex + 1).trim();
// Only add if key is not empty
if (key) {
env[key] = value; // Value can be empty string
}
}
}
}
// Convert header pairs to record
const headers: Record<string, string> = {};
headerPairs.forEach((pair) => {
if (pair.key.trim() && pair.value.trim()) {
headers[pair.key.trim()] = pair.value.trim();
}
});
// Validate required fields
if (!formData.name.trim()) {
throw new Error('Server name is required');
}
if (!formData.description.trim()) {
throw new Error('Description is required');
}
if (formData.config.type === 'stdio' && !formData.config.command.trim()) {
throw new Error('Command is required for stdio servers');
}
if (formData.config.type === 'sse' && !formData.config.url.trim()) {
throw new Error('URL is required for SSE servers');
}
if (formData.config.type === 'http' && !formData.config.url.trim()) {
throw new Error('URL is required for HTTP servers');
}
const entry: Omit<ServerRegistryEntry, 'id' | 'isOfficial' | 'lastUpdated'> = {
...formData,
config: {
...formData.config,
args,
env,
headers,
},
tags,
requirements: {
...formData.requirements,
dependencies,
},
};
await onAddServer(entry);
onClose();
// Reset form
setFormData({
name: '',
description: '',
category: 'custom',
icon: '',
version: '',
author: '',
homepage: '',
config: {
type: 'stdio',
command: '',
args: [],
url: '',
env: {},
headers: {},
timeout: 30000,
},
tags: [],
isInstalled: false,
requirements: {
platform: 'all',
node: '',
python: '',
dependencies: [],
},
});
setArgsInput('');
setTagsInput('');
setEnvInput('');
setHeaderPairs([]);
setDependenciesInput('');
} catch (err: any) {
setError(err.message || 'Failed to add custom server');
} finally {
setIsSubmitting(false);
}
};
const handleConfigChange = (e: React.ChangeEvent<HTMLInputElement>) => {
const { name, value } = e.target;
setFormData((prev) => ({
...prev,
config: {
...prev.config,
[name]: value,
},
}));
};
return (
<Dialog open={isOpen} onOpenChange={(open) => !open && onClose()}>
<DialogContent className="max-w-2xl max-h-[80vh] overflow-y-auto">
<DialogHeader>
<DialogTitle className="flex items-center gap-2">
<Plus className="h-5 w-5" />
Add Custom Server to Registry
</DialogTitle>
<DialogDescription>
Add your own custom MCP server configuration to the registry for easy reuse.
</DialogDescription>
</DialogHeader>
<form onSubmit={handleSubmit} className="space-y-4">
{error && (
<Alert variant="destructive">
<AlertDescription>{error}</AlertDescription>
</Alert>
)}
{/* Basic Information */}
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="name">Server Name *</Label>
<Input
id="name"
value={formData.name}
onChange={(e) =>
setFormData((prev) => ({ ...prev, name: e.target.value }))
}
placeholder="My Custom Server"
required
/>
</div>
<div>
<Label htmlFor="category">Category</Label>
<Select
value={formData.category}
onValueChange={(value: any) =>
setFormData((prev) => ({ ...prev, category: value }))
}
>
<SelectTrigger id="category">
<SelectValue />
</SelectTrigger>
<SelectContent>
{categories.map((cat) => (
<SelectItem key={cat.value} value={cat.value}>
{cat.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
</div>
<div>
<Label htmlFor="description">Description *</Label>
<Textarea
id="description"
value={formData.description}
onChange={(e) =>
setFormData((prev) => ({ ...prev, description: e.target.value }))
}
placeholder="Describe what this server does..."
required
/>
</div>
{/* Server Configuration */}
<div className="space-y-3">
<h3 className="text-sm font-medium">Server Configuration</h3>
<div>
<Label htmlFor="serverType">Server Type</Label>
<Select
value={formData.config.type}
onValueChange={(value: 'stdio' | 'sse' | 'http') =>
setFormData((prev) => ({
...prev,
config: { ...prev.config, type: value },
}))
}
>
<SelectTrigger id="serverType">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="stdio">stdio</SelectItem>
<SelectItem value="sse">sse</SelectItem>
<SelectItem value="http">http</SelectItem>
</SelectContent>
</Select>
</div>
{formData.config.type === 'stdio' ? (
<>
<div>
<Label htmlFor="command">Command *</Label>
<Input
id="command"
value={formData.config.command}
onChange={(e) =>
setFormData((prev) => ({
...prev,
config: { ...prev.config, command: e.target.value },
}))
}
placeholder="npx or python"
required
/>
</div>
<div>
<Label htmlFor="args">Arguments</Label>
<Input
id="args"
value={argsInput}
onChange={(e) => setArgsInput(e.target.value)}
placeholder="Comma-separated: -m,script.py,--port,8080"
/>
</div>
</>
) : formData.config.type === 'sse' ? (
<div className="space-y-2">
<Label htmlFor="url">URL *</Label>
<Input
id="url"
name="url"
value={formData.config.url}
onChange={handleConfigChange}
placeholder="http://localhost:8080/sse"
required
/>
</div>
) : (
<div className="space-y-2">
<Label htmlFor="url">URL *</Label>
<Input
id="url"
name="url"
value={formData.config.url}
onChange={handleConfigChange}
placeholder="https://example.com"
required
/>
</div>
)}
{formData.config.type === 'stdio' && (
<div>
<Label htmlFor="env">Environment Variables</Label>
<Textarea
id="env"
value={envInput}
onChange={(e) => setEnvInput(e.target.value)}
placeholder={`KEY1=value1\nKEY2=value2`}
rows={3}
/>
</div>
)}
{(formData.config.type === 'sse' || formData.config.type === 'http') && (
<div className="space-y-4">
<KeyValueEditor
label="Headers"
pairs={headerPairs}
onChange={setHeaderPairs}
placeholder={{
key: 'Authorization',
value: 'Bearer your-token',
}}
keyLabel="Header"
valueLabel="Value"
/>
</div>
)}
</div>
{/* Additional Information */}
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="tags">Tags</Label>
<Input
id="tags"
value={tagsInput}
onChange={(e) => setTagsInput(e.target.value)}
placeholder="Comma-separated: file, database, api"
/>
</div>
<div>
<Label htmlFor="icon">Icon (Emoji)</Label>
<Input
id="icon"
value={formData.icon}
onChange={(e) =>
setFormData((prev) => ({ ...prev, icon: e.target.value }))
}
placeholder="⚡"
maxLength={2}
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="version">Version</Label>
<Input
id="version"
value={formData.version}
onChange={(e) =>
setFormData((prev) => ({ ...prev, version: e.target.value }))
}
placeholder="1.0.0"
/>
</div>
<div>
<Label htmlFor="author">Author</Label>
<Input
id="author"
value={formData.author}
onChange={(e) =>
setFormData((prev) => ({ ...prev, author: e.target.value }))
}
placeholder="Your Name"
/>
</div>
</div>
<div>
<Label htmlFor="homepage">Homepage URL</Label>
<Input
id="homepage"
value={formData.homepage}
onChange={(e) =>
setFormData((prev) => ({ ...prev, homepage: e.target.value }))
}
placeholder="https://github.com/youruser/yourserver"
/>
</div>
{/* Requirements Section */}
<div className="space-y-3">
<h3 className="text-sm font-medium">Requirements</h3>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="platform">Platform</Label>
<Select
value={formData.requirements.platform}
onValueChange={(value: 'win32' | 'darwin' | 'linux' | 'all') =>
setFormData((prev) => ({
...prev,
requirements: { ...prev.requirements, platform: value },
}))
}
>
<SelectTrigger id="platform">
<SelectValue />
</SelectTrigger>
<SelectContent>
{platforms.map((platform) => (
<SelectItem key={platform.value} value={platform.value}>
{platform.label}
</SelectItem>
))}
</SelectContent>
</Select>
</div>
<div>
<Label htmlFor="dependencies">Dependencies</Label>
<Input
id="dependencies"
value={dependenciesInput}
onChange={(e) => setDependenciesInput(e.target.value)}
placeholder="Comma-separated: package1, package2"
/>
</div>
</div>
<div className="grid grid-cols-2 gap-4">
<div>
<Label htmlFor="nodeVersion">Node.js Version</Label>
<Input
id="nodeVersion"
value={formData.requirements.node}
onChange={(e) =>
setFormData((prev) => ({
...prev,
requirements: {
...prev.requirements,
node: e.target.value,
},
}))
}
placeholder=">=16.0.0"
/>
</div>
<div>
<Label htmlFor="pythonVersion">Python Version</Label>
<Input
id="pythonVersion"
value={formData.requirements.python}
onChange={(e) =>
setFormData((prev) => ({
...prev,
requirements: {
...prev.requirements,
python: e.target.value,
},
}))
}
placeholder=">=3.8"
/>
</div>
</div>
</div>
<DialogFooter className="flex gap-2">
<Button type="button" variant="outline" onClick={onClose}>
Cancel
</Button>
<Button type="submit" disabled={isSubmitting}>
{isSubmitting ? (
'Adding...'
) : (
<>
<Save className="h-4 w-4 mr-2" />
Add to Registry
</>
)}
</Button>
</DialogFooter>
</form>
</DialogContent>
</Dialog>
);
}