import { useState, useEffect } from 'react'; import type { McpServerConfig } from '../types'; import { t } from '../i18n'; interface McpEntry { name: string; config: McpServerConfig; enabled: boolean; } const KNOWN_SERVERS: Record = { 'trend-pulse': { tools: 11, tier: '1', description: 'Trending topics from 20 free sources' }, 'claude-101': { tools: 27, tier: '1', description: '24 use-case templates (email, code, analysis)' }, 'cf-browser': { tools: 15, tier: '2', description: 'Headless Chrome (JS rendering, screenshots)' }, 'notebooklm': { tools: 13, tier: '2', description: 'AI podcasts, slides, reports, research' }, }; const PRESETS: Record = { 'trend-pulse': { type: 'stdio', command: 'uvx', args: ['--from', 'trend-pulse[mcp]', 'trend-pulse-server'] }, 'claude-101': { type: 'stdio', command: 'uvx', args: ['--from', 'claude-101[mcp]', 'claude-101-server'] }, 'cf-browser': { type: 'stdio', command: 'uvx', args: ['--from', 'cf-browser-mcp', 'cf-browser-mcp'], env: { CF_ACCOUNT_ID: '${CF_ACCOUNT_ID}', CF_API_TOKEN: '${CF_API_TOKEN}' } }, 'notebooklm': { type: 'stdio', command: 'uvx', args: ['--from', 'notebooklm-skill', 'notebooklm-mcp'] }, }; export default function McpPage() { const [servers, setServers] = useState([]); const [loading, setLoading] = useState(true); const [showAdd, setShowAdd] = useState(false); const [newName, setNewName] = useState(''); const [newCommand, setNewCommand] = useState(''); const [newArgs, setNewArgs] = useState(''); const [newEnvKey, setNewEnvKey] = useState(''); const [newEnvVal, setNewEnvVal] = useState(''); const [status, setStatus] = useState(''); const fetchServers = async () => { try { const res = await fetch('/api/mcp'); const data = await res.json(); const entries: McpEntry[] = Object.entries(data.mcpServers || {}).map( ([name, config]) => ({ name, config: config as McpServerConfig, enabled: true }) ); setServers(entries); } catch { setServers([]); } finally { setLoading(false); } }; useEffect(() => { fetchServers(); }, []); const handleDelete = async (name: string) => { try { await fetch(`/api/mcp/${name}`, { method: 'DELETE' }); setStatus(`${t('mcp.deleted') || 'Removed'}: ${name}`); fetchServers(); setTimeout(() => setStatus(''), 3000); } catch (err) { setStatus(`Error: ${(err as Error).message}`); } }; const handleAddPreset = async (name: string) => { try { await fetch(`/api/mcp/${name}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(PRESETS[name]), }); setStatus(`${t('mcp.added') || 'Added'}: ${name}`); fetchServers(); setTimeout(() => setStatus(''), 3000); } catch (err) { setStatus(`Error: ${(err as Error).message}`); } }; const handleAddCustom = async () => { if (!newName.trim() || !newCommand.trim()) return; const config: McpServerConfig = { type: 'stdio', command: newCommand.trim(), args: newArgs.trim() ? newArgs.split(/\s+/) : [], }; if (newEnvKey.trim()) { config.env = { [newEnvKey.trim()]: newEnvVal.trim() }; } try { await fetch(`/api/mcp/${newName.trim()}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(config), }); setStatus(`${t('mcp.added') || 'Added'}: ${newName}`); setShowAdd(false); setNewName(''); setNewCommand(''); setNewArgs(''); setNewEnvKey(''); setNewEnvVal(''); fetchServers(); setTimeout(() => setStatus(''), 3000); } catch (err) { setStatus(`Error: ${(err as Error).message}`); } }; const activeNames = new Set(servers.map(s => s.name)); const availablePresets = Object.keys(PRESETS).filter(n => !activeNames.has(n)); return (

{t('mcp.title') || 'MCP Servers'}

{t('mcp.subtitle') || 'Manage Model Context Protocol servers that provide tools to Claude'}

{status && (

{status}

)} {showAdd && (

{t('mcp.addCustomTitle') || 'Add Custom MCP Server'}

setNewName(e.target.value)} placeholder="Server name" className="input-base" /> setNewCommand(e.target.value)} placeholder="Command (e.g. uvx, npx, python)" className="input-base" /> setNewArgs(e.target.value)} placeholder="Arguments (space-separated)" className="input-base col-span-2" /> setNewEnvKey(e.target.value)} placeholder="Env var name (optional)" className="input-base" /> setNewEnvVal(e.target.value)} placeholder="Env var value" className="input-base" />
)}
{loading ? (
{t('mcp.loading') || 'Loading...'}
) : ( <> {/* Active servers */}

{t('mcp.active') || 'Active'} ({servers.length})

{servers.length === 0 ? (

{t('mcp.noServers') || 'No MCP servers configured.'}

) : (
{servers.map(({ name, config }) => { const info = KNOWN_SERVERS[name]; return (

{name}

{info && {info.tools} tools} {info && ( Tier {info.tier} )}

{info?.description || `${config.command} ${(config.args || []).join(' ')}`}

{config.command} {(config.args || []).join(' ')}

{config.env && Object.keys(config.env).length > 0 && (

env: {Object.keys(config.env).join(', ')}

)}
); })}
)}
{/* Available presets */} {availablePresets.length > 0 && (

{t('mcp.available') || 'Available to Add'}

{availablePresets.map(name => { const info = KNOWN_SERVERS[name]; return (

{name}

{info && {info.tools} tools} {info && ( Tier {info.tier} )}

{info?.description}

); })}
)} {/* Setup guide */}

{t('mcp.setupHint') || 'Setup Notes:'}

Tier 1 (trend-pulse, claude-101): {t('mcp.tier1') || 'Zero auth — works out of the box.'}

cf-browser: {t('mcp.cfSetup') || 'Set CF_ACCOUNT_ID + CF_API_TOKEN in your shell profile.'}

notebooklm: {t('mcp.nlmSetup') || 'Run `uvx notebooklm login` once (opens browser for Google login).'}

{t('mcp.uvxNote') || 'All servers require `uvx`. Install: curl -LsSf https://astral.sh/uv/install.sh | sh'}

)}
); }