'use client'; import React, { useState, useCallback, useRef } from 'react'; interface Tool { name: string; description: string; method: string; endpoint: string; groupName?: string; inputSchema?: object; enabled: boolean; } interface GeneratedFile { path: string; content: string; preview?: string; } type Step = 'input' | 'analyzing' | 'review' | 'generating' | 'done'; export default function BuildPage() { const [step, setStep] = useState('input'); const [specInput, setSpecInput] = useState(''); const [serviceName, setServiceName] = useState(''); const [tools, setTools] = useState([]); const [analysis, setAnalysis] = useState(null); const [files, setFiles] = useState([]); const [progress, setProgress] = useState(''); const [error, setError] = useState(''); const [selectedFile, setSelectedFile] = useState(null); const eventSourceRef = useRef(null); const handleAnalyze = useCallback(async () => { setError(''); setStep('analyzing'); setTools([]); setProgress('Starting analysis...'); try { const isUrl = specInput.startsWith('http'); const res = await fetch('/api/analyze', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(isUrl ? { specUrl: specInput } : { specContent: specInput }), }); if (!res.body) throw new Error('No response body'); const reader = res.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || ''; let eventType = ''; for (const line of lines) { if (line.startsWith('event: ')) { eventType = line.slice(7); } else if (line.startsWith('data: ') && eventType) { try { const data = JSON.parse(line.slice(6)); if (eventType === 'progress') { setProgress(data.step); } else if (eventType === 'tool_found') { setTools(prev => [...prev, { ...data, enabled: true }]); } else if (eventType === 'complete') { setAnalysis(data.analysis); if (data.analysis?.toolGroups) { const allTools: Tool[] = []; for (const group of data.analysis.toolGroups) { for (const tool of group.tools || []) { allTools.push({ ...tool, groupName: group.name, enabled: true }); } } if (allTools.length > 0) setTools(allTools); } setStep('review'); } else if (eventType === 'error') { setError(data.message); setStep('input'); } } catch {} eventType = ''; } } } } catch (err: unknown) { setError(err instanceof Error ? err.message : 'Analysis failed'); setStep('input'); } }, [specInput]); const handleGenerate = useCallback(async () => { setError(''); setStep('generating'); setFiles([]); setProgress('Generating MCP server...'); try { const enabledTools = tools.filter(t => t.enabled); const res = await fetch('/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ analysis, tools: enabledTools, serviceName: serviceName || analysis?.service || 'custom', }), }); if (!res.body) throw new Error('No response body'); const reader = res.body.getReader(); const decoder = new TextDecoder(); let buffer = ''; while (true) { const { done, value } = await reader.read(); if (done) break; buffer += decoder.decode(value, { stream: true }); const lines = buffer.split('\n'); buffer = lines.pop() || ''; let eventType = ''; for (const line of lines) { if (line.startsWith('event: ')) { eventType = line.slice(7); } else if (line.startsWith('data: ') && eventType) { try { const data = JSON.parse(line.slice(6)); if (eventType === 'progress') { setProgress(data.step); } else if (eventType === 'file_ready') { setFiles(prev => [...prev, data]); } else if (eventType === 'complete') { setFiles(data.files || []); if (data.files?.length > 0) { setSelectedFile(data.files[0].path); } setStep('done'); } else if (eventType === 'error') { setError(data.message); setStep('review'); } } catch {} eventType = ''; } } } } catch (err: unknown) { setError(err instanceof Error ? err.message : 'Generation failed'); setStep('review'); } }, [tools, analysis, serviceName]); const toggleTool = (index: number) => { setTools(prev => prev.map((t, i) => i === index ? { ...t, enabled: !t.enabled } : t)); }; const downloadFiles = () => { const content = files.map(f => `// === ${f.path} ===\n\n${f.content}`).join('\n\n\n'); const blob = new Blob([content], { type: 'text/plain' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `${serviceName || 'mcp-server'}.txt`; a.click(); URL.revokeObjectURL(url); }; const methodColors: Record = { GET: 'bg-emerald-500/20 text-emerald-400', POST: 'bg-blue-500/20 text-blue-400', PUT: 'bg-amber-500/20 text-amber-400', PATCH: 'bg-amber-500/20 text-amber-400', DELETE: 'bg-red-500/20 text-red-400', }; return (
{/* Header */}
MCP Engine Studio
{step === 'input' && 'Ready'} {step === 'analyzing' && 'Analyzing...'} {step === 'review' && `${tools.filter(t => t.enabled).length} tools found`} {step === 'generating' && 'Generating...'} {step === 'done' && `${files.length} files generated`}
{/* Step 1: Input */} {step === 'input' && (

Build an MCP Server

Paste an OpenAPI spec or URL — we'll analyze it and generate a production MCP server.

setServiceName(e.target.value)} placeholder="e.g., trello, stripe, my-api" className="w-full bg-gray-800 border border-gray-600 rounded-lg px-4 py-3 text-gray-100 placeholder:text-gray-500 focus:border-indigo-500 focus:ring-2 focus:ring-indigo-500/20 outline-none" />