+ {/* Header with search */}
+
+
+ {/* 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"`
diff --git a/mcp-factory-v3/templates/AGENT-PROMPT-FOUNDATION.md b/mcp-factory-v3/templates/AGENT-PROMPT-FOUNDATION.md
new file mode 100644
index 0000000..4b7dec6
--- /dev/null
+++ b/mcp-factory-v3/templates/AGENT-PROMPT-FOUNDATION.md
@@ -0,0 +1,156 @@
+# Foundation Build Agent Prompt
+
+Build the foundation for the {{NAME}} MCP server at {{DIR}}.
+
+## What to Build
+
+### 1. `package.json`
+```json
+{
+ "name": "@mcpengine/{{NAME}}",
+ "version": "1.0.0",
+ "type": "module",
+ "main": "dist/main.js",
+ "scripts": {
+ "build": "tsc",
+ "start": "node dist/main.js",
+ "dev": "tsx watch src/main.ts"
+ },
+ "dependencies": {
+ "@modelcontextprotocol/sdk": "^1.12.1",
+ "axios": "^1.7.0",
+ "zod": "^3.23.0"
+ },
+ "devDependencies": {
+ "typescript": "^5.6.0",
+ "tsx": "^4.19.0",
+ "@types/node": "^22.0.0"
+ }
+}
+```
+
+### 2. `tsconfig.json`
+```json
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "Node16",
+ "moduleResolution": "Node16",
+ "outDir": "dist",
+ "rootDir": "src",
+ "strict": true,
+ "esModuleInterop": true,
+ "skipLibCheck": true,
+ "declaration": true,
+ "resolveJsonModule": true,
+ "forceConsistentCasingInFileNames": true,
+ "noUncheckedIndexedAccess": true,
+ "jsx": "react-jsx"
+ },
+ "include": ["src/**/*"],
+ "exclude": ["node_modules", "dist"]
+}
+```
+
+### 3. `src/types/index.ts`
+- Define TypeScript interfaces for ALL API entities
+- Use branded types for IDs: `type CustomerId = string & { __brand: 'CustomerId' }`
+- Use discriminated unions for status fields
+- Export everything
+
+### 4. `src/clients/{{NAME}}.ts`
+API client with:
+- Auth: {{AUTH_TYPE}} (Bearer token / API key / OAuth2)
+- Base URL: {{API_BASE}}
+- Retry with exponential backoff (3 retries, 1s/2s/4s)
+- Rate limit awareness (respect 429 + Retry-After header)
+- Automatic pagination (abstract cursor/offset)
+- Request interceptors for logging
+- Response interceptors for error normalization
+- Timeout: 30s default
+- OAuth token auto-refresh if applicable
+
+```typescript
+import axios, { AxiosInstance, AxiosError } from 'axios';
+
+export class {{PascalName}}Client {
+ private client: AxiosInstance;
+ private rateLimitRemaining = Infinity;
+ private rateLimitReset = 0;
+
+ constructor(config: { apiKey?: string; accessToken?: string; baseUrl?: string }) {
+ this.client = axios.create({
+ baseURL: config.baseUrl || '{{API_BASE}}',
+ timeout: 30000,
+ headers: { /* auth headers */ }
+ });
+
+ // Response interceptor for rate limiting
+ this.client.interceptors.response.use(
+ (res) => {
+ this.rateLimitRemaining = parseInt(res.headers['x-ratelimit-remaining'] || 'Infinity');
+ this.rateLimitReset = parseInt(res.headers['x-ratelimit-reset'] || '0');
+ return res;
+ },
+ async (error: AxiosError) => {
+ if (error.response?.status === 429) {
+ const retryAfter = parseInt(error.response.headers['retry-after'] || '5');
+ await this.sleep(retryAfter * 1000);
+ return this.client.request(error.config!);
+ }
+ throw this.normalizeError(error);
+ }
+ );
+ }
+
+ // Paginated fetch helper
+ async paginate