# App Build Agent Prompt Build ALL React MCP Apps for the {{NAME}} MCP server at {{DIR}}. ## Foundation + Tools Already Exist DO NOT modify any existing files. Only ADD app files under `src/ui/react-app/`. ## Apps to Build {{APP_LIST}} ## Quality Requirements — 2026 Standards ### Each App Directory Structure ``` src/ui/react-app/{app-name}/ ├── App.tsx # Main component ├── index.html # Entry point ├── main.tsx # React mount with ErrorBoundary ├── styles.css # Dark theme styles └── vite.config.ts # Build config ``` ### main.tsx — With Error Boundary + Suspense ```tsx import React, { Suspense } from 'react'; import ReactDOM from 'react-dom/client'; import './styles.css'; // Lazy load the main app component const App = React.lazy(() => import('./App')); // Error Boundary class ErrorBoundary extends React.Component< { children: React.ReactNode }, { hasError: boolean; error?: Error } > { constructor(props: { children: React.ReactNode }) { super(props); this.state = { hasError: false }; } static getDerivedStateFromError(error: Error) { return { hasError: true, error }; } render() { if (this.state.hasError) { return (

Something went wrong

{this.state.error?.message}
); } return this.props.children; } } // Loading skeleton function LoadingSkeleton() { return (
); } ReactDOM.createRoot(document.getElementById('root')!).render( }> ); ``` ### App.tsx — Modern React Patterns ```tsx import React, { useState, useMemo, useCallback, useTransition } from 'react'; // Debounce hook for search function useDebounce(value: T, delay: number): T { const [debounced, setDebounced] = React.useState(value); React.useEffect(() => { const t = setTimeout(() => setDebounced(value), delay); return () => clearTimeout(t); }, [value, delay]); return debounced; } // Toast notification system function useToast() { const [toasts, setToasts] = useState>([]); const show = useCallback((message: string, type: 'success' | 'error' = 'success') => { const id = Date.now(); setToasts(prev => [...prev, { id, message, type }]); setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), 3000); }, []); return { toasts, show }; } export default function App() { const [search, setSearch] = useState(''); const [isPending, startTransition] = useTransition(); const debouncedSearch = useDebounce(search, 300); const { toasts, show: showToast } = useToast(); // Mock data — replace with MCP tool calls const [data] = useState(MOCK_DATA); // Client-side filtering with useMemo const filtered = useMemo(() => data.filter(item => item.name.toLowerCase().includes(debouncedSearch.toLowerCase()) ), [data, debouncedSearch]); return (
{/* Header with search */}

{{App Title}}

startTransition(() => setSearch(e.target.value))} className="search-input" /> {isPending && }
{/* Stats cards */}
{/* Stat cards with numbers */}
{/* Data grid or dashboard content */}
{filtered.length === 0 ? (

No results found

Try adjusting your search
) : (
{filtered.map(item => (
{/* Card content */}
))}
)}
{/* Toast notifications */}
{toasts.map(t => (
{t.message}
))}
); } ``` ### styles.css — Dark Theme with CSS Variables + Skeleton Animation ```css :root { --bg-primary: #0f172a; --bg-secondary: #1e293b; --bg-tertiary: #334155; --text-primary: #f1f5f9; --text-secondary: #94a3b8; --text-muted: #64748b; --accent: #3b82f6; --accent-hover: #2563eb; --success: #22c55e; --warning: #f59e0b; --error: #ef4444; --border: #334155; --radius: 8px; --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.3); } * { margin: 0; padding: 0; box-sizing: border-box; } body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: var(--bg-primary); color: var(--text-primary); min-height: 100vh; } /* Skeleton loading animation */ .skeleton { background: linear-gradient(90deg, var(--bg-secondary) 25%, var(--bg-tertiary) 50%, var(--bg-secondary) 75%); background-size: 200% 100%; animation: shimmer 1.5s infinite; border-radius: var(--radius); } @keyframes shimmer { 0% { background-position: 200% 0; } 100% { background-position: -200% 0; } } /* Search input */ .search-input { background: var(--bg-secondary); border: 1px solid var(--border); color: var(--text-primary); padding: 10px 16px; border-radius: var(--radius); width: 280px; transition: border-color 0.2s; } .search-input:focus { outline: none; border-color: var(--accent); } .search-input::placeholder { color: var(--text-muted); } /* Stats grid */ .stats-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; margin-bottom: 24px; } /* Cards */ .card { background: var(--bg-secondary); border: 1px solid var(--border); border-radius: var(--radius); padding: 20px; transition: transform 0.15s, box-shadow 0.15s; } .card:hover { transform: translateY(-2px); box-shadow: var(--shadow); } /* Empty state */ .empty-state { text-align: center; padding: 60px 20px; color: var(--text-secondary); } .empty-hint { font-size: 14px; color: var(--text-muted); } /* Toast notifications */ .toast-container { position: fixed; bottom: 20px; right: 20px; display: flex; flex-direction: column; gap: 8px; z-index: 1000; } .toast { padding: 12px 20px; border-radius: var(--radius); color: white; font-weight: 500; animation: slideIn 0.3s ease; box-shadow: var(--shadow); } .toast-success { background: var(--success); } .toast-error { background: var(--error); } @keyframes slideIn { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } /* Responsive */ @media (max-width: 768px) { .stats-grid { grid-template-columns: repeat(2, 1fr); } .search-input { width: 100%; } } @media (max-width: 480px) { .stats-grid { grid-template-columns: 1fr; } } ``` ## Rules - Each app MUST have: ErrorBoundary, Suspense, loading skeleton, empty state, toast system - Debounced search on all grids/lists - CSS custom properties (not hardcoded colors) - Mobile responsive - DO NOT modify existing files - Commit when done: `git add src/ui/ && git commit -m "{{name}}: Add {{count}} React MCP apps"`