import React, { useState } from 'react'; import { Card } from '../../ui/card'; import { Button } from '../../ui/button'; import { Input } from '../../ui/input'; import { FullExtensionConfig, DEFAULT_EXTENSION_TIMEOUT } from '../../../extensions'; import { toast } from 'react-toastify'; import Select from 'react-select'; import { createDarkSelectStyles, darkSelectTheme } from '../../ui/select-styles'; import { getApiUrl, getSecretKey } from '../../../config'; import { toastError } from '../../../toasts'; interface ManualExtensionModalProps { isOpen: boolean; onClose: () => void; onSubmit: (extension: FullExtensionConfig) => void; } export function ManualExtensionModal({ isOpen, onClose, onSubmit }: ManualExtensionModalProps) { const [formData, setFormData] = useState< Partial & { commandInput?: string } >({ type: 'stdio', enabled: true, args: [], commandInput: '', timeout: DEFAULT_EXTENSION_TIMEOUT, }); const [envKey, setEnvKey] = useState(''); const [envValue, setEnvValue] = useState(''); const [envVars, setEnvVars] = useState>([]); const typeOptions = [ { value: 'stdio', label: 'Standard IO' }, { value: 'sse', label: 'Server-Sent Events' }, { value: 'builtin', label: 'Built-in' }, ]; const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!formData.id || !formData.name || !formData.description) { toastError({ title: 'Please fill in all required fields' }); return; } if (formData.type === 'stdio' && !formData.commandInput) { toastError({ title: 'Command is required for stdio type' }); return; } if (formData.type === 'sse' && !formData.uri) { toastError({ title: 'URI is required for SSE type' }); return; } if (formData.type === 'builtin' && !formData.name) { toastError({ title: 'Name is required for builtin type' }); return; } try { // Store environment variables as secrets for (const envVar of envVars) { const storeResponse = await fetch(getApiUrl('/configs/store'), { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Secret-Key': getSecretKey(), }, body: JSON.stringify({ key: envVar.key, value: envVar.value.trim(), isSecret: true, }), }); if (!storeResponse.ok) { throw new Error(`Failed to store environment variable: ${envVar.key}`); } } // Parse command input into cmd and args let cmd = ''; let args: string[] = []; if (formData.type === 'stdio' && formData.commandInput) { const parts = formData.commandInput.trim().split(/\s+/); [cmd, ...args] = parts; } const extension: FullExtensionConfig = { ...formData, type: formData.type!, enabled: true, env_keys: envVars.map((v) => v.key), ...(formData.type === 'stdio' && { cmd, args }), } as FullExtensionConfig; onSubmit(extension); resetForm(); } catch (error) { console.error('Error configuring extension:', error); toastError({ title: 'Failed to configure extension', traceback: error.message }); } }; const resetForm = () => { setFormData({ type: 'stdio', enabled: true, args: [], commandInput: '', }); setEnvVars([]); setEnvKey(''); setEnvValue(''); }; const handleAddEnvVar = () => { if (envKey && !envVars.some((v) => v.key === envKey)) { setEnvVars([...envVars, { key: envKey, value: envValue }]); setEnvKey(''); setEnvValue(''); } }; const handleRemoveEnvVar = (key: string) => { setEnvVars(envVars.filter((v) => v.key !== key)); }; if (!isOpen) return null; return (

Add custom extension

setFormData({ ...formData, id: e.target.value })} className="w-full" required />
setFormData({ ...formData, name: e.target.value })} className="w-full" required />
setFormData({ ...formData, description: e.target.value })} className="w-full" required />
{formData.type === 'stdio' && (
setFormData({ ...formData, commandInput: e.target.value })} placeholder="e.g. goosed mcp example" className="w-full" required />
)} {formData.type === 'sse' && (
setFormData({ ...formData, uri: e.target.value })} className="w-full" required />
)}
setEnvKey(e.target.value)} placeholder="Environment variable name" className="flex-1" /> setEnvValue(e.target.value)} placeholder="Value" className="flex-1" />
{envVars.length > 0 && (
{envVars.map((envVar) => (
{envVar.key} = {envVar.value}
))}
)}
setFormData({ ...formData, timeout: parseInt(e.target.value) })} className="w-full" required />
); }