diff --git a/README.md b/README.md
index 0ba5bc6..14149f9 100644
--- a/README.md
+++ b/README.md
@@ -42,14 +42,60 @@ make dev # Start all services
You can also use `pnpm` directly (`pnpm dev`, `pnpm dev:app`, `pnpm dev:agent`, etc.).
+## MCP Server (Self-Hosted)
+
+The repo includes a standalone [Model Context Protocol](https://modelcontextprotocol.io) server that exposes the design system, skill instructions, and an HTML document assembler to any MCP-compatible client — including Claude Desktop, Claude Code, and Cursor.
+
+### What it provides
+
+- **`assemble_document` tool** — wraps HTML fragments with the full design system CSS and bridge JS, returning an iframe-ready document
+- **Skill resources** — browse and read skill instruction documents (`skills://list`, `skills://{name}`)
+- **Prompt templates** — pre-composed prompts for widgets, SVG diagrams, and advanced visualizations
+
+### Claude Desktop (stdio)
+
+Add to your Claude Desktop config (`claude_desktop_config.json`):
+
+```json
+{
+ "mcpServers": {
+ "open-generative-ui": {
+ "command": "node",
+ "args": ["dist/stdio.js"],
+ "cwd": "/path/to/apps/mcp"
+ }
+ }
+}
+```
+
+### Claude Code / HTTP clients
+
+```bash
+# Start the HTTP server
+cd apps/mcp && pnpm dev
+```
+
+Add to `.mcp.json`:
+
+```json
+{
+ "openGenerativeUI": {
+ "url": "http://localhost:3100/mcp"
+ }
+}
+```
+
+See [apps/mcp/README.md](apps/mcp/README.md) for full configuration, Docker deployment, and API reference.
+
## Architecture
-Turborepo monorepo with two apps:
+Turborepo monorepo with three packages:
```
apps/
├── app/ Next.js 16 frontend (CopilotKit v2, React 19, Tailwind 4)
-└── agent/ LangGraph Python agent (GPT-5.4, CopilotKit middleware)
+├── agent/ LangGraph Python agent (GPT-5.4, CopilotKit middleware)
+└── mcp/ Standalone MCP server (design system + skills + document assembler)
```
### How It Works
diff --git a/apps/mcp/.dockerignore b/apps/mcp/.dockerignore
new file mode 100644
index 0000000..7c53328
--- /dev/null
+++ b/apps/mcp/.dockerignore
@@ -0,0 +1,9 @@
+node_modules/
+npm-debug.log
+.git
+.gitignore
+README.md
+.env.example
+.DS_Store
+dist/
+.turbo/
diff --git a/apps/mcp/.env.example b/apps/mcp/.env.example
new file mode 100644
index 0000000..a7ffead
--- /dev/null
+++ b/apps/mcp/.env.example
@@ -0,0 +1,12 @@
+# Server Configuration
+MCP_PORT=3100
+NODE_ENV=development
+
+# CORS (comma-separated origins, default: * for development)
+ALLOWED_ORIGINS=*
+
+# Skills directory (default: ./skills relative to package root)
+SKILLS_DIR=./skills
+
+# Logging
+LOG_LEVEL=info
diff --git a/apps/mcp/.gitignore b/apps/mcp/.gitignore
new file mode 100644
index 0000000..049d6c8
--- /dev/null
+++ b/apps/mcp/.gitignore
@@ -0,0 +1,11 @@
+node_modules/
+dist/
+.env
+.env.local
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+.DS_Store
+.turbo/
+.venv/
diff --git a/apps/mcp/Dockerfile b/apps/mcp/Dockerfile
new file mode 100644
index 0000000..60042e5
--- /dev/null
+++ b/apps/mcp/Dockerfile
@@ -0,0 +1,53 @@
+# Multi-stage build for production
+FROM node:20-alpine AS builder
+
+WORKDIR /app
+
+# Install build dependencies
+RUN apk add --no-cache python3 make g++
+
+# Copy package files
+COPY package.json pnpm-lock.yaml ./
+
+# Install pnpm
+RUN npm install -g pnpm@9
+
+# Install dependencies
+RUN pnpm install --frozen-lockfile
+
+# Copy source
+COPY tsconfig.json ./
+COPY src ./src
+COPY skills ./skills
+
+# Build
+RUN pnpm build
+
+# Production image
+FROM node:20-alpine
+
+WORKDIR /app
+
+# Install dumb-init for proper signal handling
+RUN apk add --no-cache dumb-init
+
+# Copy built application from builder
+COPY --from=builder /app/dist ./dist
+COPY --from=builder /app/skills ./skills
+COPY --from=builder /app/node_modules ./node_modules
+COPY package.json ./
+
+# Non-root user
+RUN addgroup -g 1001 -S nodejs && \
+ adduser -S nodejs -u 1001
+
+USER nodejs
+
+# Health check
+HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
+ CMD wget --no-verbose --tries=1 --spider http://localhost:${MCP_PORT:-3100}/health || exit 1
+
+EXPOSE 3100
+
+ENTRYPOINT ["/sbin/dumb-init", "--"]
+CMD ["node", "dist/index.js"]
diff --git a/apps/mcp/README.md b/apps/mcp/README.md
new file mode 100644
index 0000000..c5d0089
--- /dev/null
+++ b/apps/mcp/README.md
@@ -0,0 +1,297 @@
+# OpenGenerativeUI MCP Server
+
+A standalone, independently deployable [Model Context Protocol](https://modelcontextprotocol.io) server that exposes OpenGenerativeUI's design system, skills, and document renderer to any MCP-compatible client.
+
+## Features
+
+- **Design System Tool** — `assemble_document` wraps HTML fragments with OpenGenerativeUI's complete CSS design system and bridge JavaScript
+- **Skill Resources** — Browse and read skill instruction documents via `skills://list` and `skills://{name}`
+- **Prompt Templates** — Pre-composed prompts for common visualization tasks: `create_widget`, `create_svg_diagram`, `create_visualization`
+- **Standalone** — No dependencies on other packages; can be deployed independently
+- **Configurable** — Environment variables for port, CORS origins, skills directory, and logging
+
+## Quick Start
+
+### Prerequisites
+- Node.js >= 18
+- pnpm or npm
+
+### Installation
+
+```bash
+# Navigate to the MCP package
+cd apps/mcp
+
+# Install dependencies
+pnpm install
+
+# Start the development server
+pnpm dev
+# → MCP server running on http://localhost:3100/mcp
+
+# Health check
+curl http://localhost:3100/health
+# → {"status":"ok"}
+```
+
+### Build for Production
+
+```bash
+pnpm build
+pnpm start
+```
+
+## Configuration
+
+Create a `.env` file in the package root (copy from `.env.example`):
+
+```bash
+# Server port (default: 3100)
+MCP_PORT=3100
+
+# CORS origins, comma-separated (default: * for development)
+ALLOWED_ORIGINS=*
+
+# Skills directory path (default: ./skills)
+SKILLS_DIR=./skills
+
+# Log level (default: info)
+LOG_LEVEL=info
+```
+
+## Usage
+
+### Claude Desktop (stdio)
+
+Claude Desktop uses stdio transport. Build first, then add to your `claude_desktop_config.json`:
+
+```bash
+pnpm build
+```
+
+```json
+{
+ "mcpServers": {
+ "open-generative-ui": {
+ "command": "node",
+ "args": ["dist/stdio.js"],
+ "cwd": "/absolute/path/to/apps/mcp"
+ }
+ }
+}
+```
+
+For development, you can use `tsx` directly:
+
+```json
+{
+ "mcpServers": {
+ "open-generative-ui": {
+ "command": "npx",
+ "args": ["tsx", "src/stdio.ts"],
+ "cwd": "/absolute/path/to/apps/mcp"
+ }
+ }
+}
+```
+
+### Claude Code (HTTP)
+
+Add to `.mcp.json`:
+
+```json
+{
+ "openGenerativeUI": {
+ "url": "http://localhost:3100/mcp"
+ }
+}
+```
+
+Then start the HTTP server with `pnpm dev`.
+
+### Any MCP Client
+
+- **stdio**: Run `node dist/stdio.js` (or `pnpm start:stdio`)
+- **HTTP**: Connect to `http://localhost:3100/mcp` (start with `pnpm dev` or `pnpm start`)
+
+## API Reference
+
+### Tool: `assemble_document`
+
+Wraps an HTML fragment with the complete OpenGenerativeUI design system.
+
+**Input:**
+```typescript
+{
+ title: string; // Short title, e.g., "Binary Search Visualization"
+ description: string; // One-sentence explanation
+ html: string; // Self-contained HTML fragment (inline
+
+
+
+
+ Play / Pause
+ Speed
+
+
+ Reset
+
+
+
+```
+
+### Math Visualizations
+For plotting functions, showing geometric relationships, or exploring equations.
+
+**Pattern: Function Plotter with SVG**
+```html
+
+
+
+
+
+ x
+ y
+
+
+
+
+
+ f(x) = sin(
+ x)
+
+ Amplitude
+
+
+
+
+
+```
+
+### Sortable / Filterable Data Tables
+```html
+
+
+
+
+
+
+
+ Name
+ Value
+ Status
+
+
+
+
+
+
+
+
+```
+
+---
+
+## Part 4: Chart.js — Advanced Patterns
+
+### Dark Mode Awareness
+```javascript
+const isDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
+const textColor = isDark ? '#c2c0b6' : '#3d3d3a';
+const gridColor = isDark ? 'rgba(255,255,255,0.08)' : 'rgba(0,0,0,0.06)';
+const tooltipBg = isDark ? '#2C2C2A' : '#fff';
+```
+
+Canvas cannot read CSS variables — always detect dark mode and use
+hardcoded hex values.
+
+### Wrapper Pattern (Critical for Sizing)
+```html
+
+
+
+```
+- Height goes on the wrapper div ONLY, never on canvas.
+- Always set `responsive: true, maintainAspectRatio: false`.
+- For horizontal bar charts: height = (bars × 40) + 80 pixels.
+
+### Custom Legend (Always Use This)
+Disable Chart.js default legend and build HTML:
+```javascript
+plugins: { legend: { display: false } }
+```
+```html
+
+
+ Series A — 65%
+
+
+ Series B — 35%
+
+
+```
+
+### Dashboard Layout
+Metric cards on top → chart below → sendPrompt for drill-down:
+```html
+
+
+
+
+
+
+
+
+
+```
+
+### Chart Type Selection Guide
+| Data pattern | Chart type |
+|----------------------------|---------------------|
+| Trend over time | Line |
+| Category comparison | Vertical bar |
+| Ranking (few items) | Horizontal bar |
+| Part of whole | Doughnut |
+| Distribution | Histogram (bar) |
+| Correlation (2 variables) | Scatter |
+| Multi-variable comparison | Radar |
+| Range / uncertainty | Line with fill area |
+
+---
+
+## Part 5: Generative Art and Illustration
+
+For when the user asks for something creative, decorative, or aesthetic.
+
+### When to Use
+- "Draw me a sunset" / "Create a pattern"
+- Decorative headers or visual breaks
+- Mood illustrations for creative writing
+- Abstract visualizations of data or music
+
+### Rules (Different from Diagrams)
+- Fill the canvas — art should feel rich, not sparse
+- Bold colors are encouraged. You can use custom hex freely.
+- Layered overlapping shapes create depth
+- Organic forms with `` curves, ``, ``
+- Texture via repetition (hatching, dots, parallel lines)
+- Geometric patterns with ``
+- NO gradients, shadows, blur, or glow (still flat aesthetic)
+
+### Pattern: Geometric Art
+```svg
+
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### Pattern: Radial Symmetry
+```svg
+
+
+
+
+
+
+
+
+
+
+
+
+```
+
+### Pattern: Landscape with Layered Shapes
+For physical scenes, use ALL hardcoded hex (no theme classes):
+```svg
+
+
+
+
+
+
+
+
+
+
+
+```
+
+---
+
+## Part 6: Advanced Patterns
+
+### Tabbed / Multi-View Interfaces
+Since content streams top-down, don't use `display: none` during streaming.
+Instead, render all content stacked, then use post-stream JS to create tabs:
+
+```html
+
+ Overview
+ Details
+ Code
+
+
+
+
+
+
+
+```
+
+### sendPrompt() — Chat-Driven Interactivity
+A global function that sends a message as if the user typed it.
+Use it when the user's next action benefits from AI thinking:
+
+```html
+
+ Drill into Q4 ↗
+
+
+ Learn about shear ↗
+
+```
+
+**Use for**: drill-downs, follow-up questions, "explain this part".
+**Don't use for**: filtering, sorting, toggling — handle those in JS.
+Append ` ↗` to button text when it triggers sendPrompt.
+
+### Responsive Grid Pattern
+```css
+display: grid;
+grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
+gap: 12px;
+```
+Use `minmax(0, 1fr)` if children have large min-content that could overflow.
+
+### CSS Animations (Subtle and Purposeful)
+```css
+/* Only animate transform and opacity for performance */
+@keyframes fadeSlideIn {
+ from { opacity: 0; transform: translateY(8px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+/* Always respect user preferences */
+@media (prefers-reduced-motion: reduce) {
+ *, *::before, *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ }
+}
+
+/* Flowing particles / convection currents */
+@keyframes flow { to { stroke-dashoffset: -20; } }
+.flowing {
+ stroke-dasharray: 5 5;
+ animation: flow 1.6s linear infinite;
+}
+
+/* Pulsing for active elements */
+@keyframes pulse {
+ 0%, 100% { opacity: 0.3; }
+ 50% { opacity: 0.7; }
+}
+```
+
+---
+
+## Part 7: External Libraries (CDN Allowlist)
+
+Only these CDN origins work (CSP-enforced):
+- `cdnjs.cloudflare.com`
+- `esm.sh`
+- `cdn.jsdelivr.net`
+- `unpkg.com`
+
+### Useful Libraries
+
+**Chart.js** (data visualization):
+```html
+
+```
+
+**Three.js** (3D graphics):
+```html
+
+```
+
+**D3.js** (advanced data viz, force layouts, geographic maps):
+```html
+
+```
+
+**Mermaid** (ERDs, sequence diagrams, class diagrams):
+```html
+
+```
+
+**Tone.js** (audio synthesis):
+```html
+
+```
+
+---
+
+## Part 8: Quality Checklist
+
+Before producing any visual, run through this:
+
+### Functional
+- [ ] Does it work without JavaScript during streaming? (Content visible)
+- [ ] Do all interactive controls have event handlers?
+- [ ] Are all displayed numbers rounded properly?
+- [ ] Does the canvas/SVG fit within the container width?
+
+### Visual
+- [ ] Dark mode test: would every element be readable on near-black?
+- [ ] No hardcoded text colors in HTML (use CSS variables)
+- [ ] No gradients, shadows, blur, or glow
+- [ ] Borders are 0.5px (except 2px for featured item accent)
+- [ ] Font weights are only 400 or 500
+- [ ] All text is sentence case
+
+### Content
+- [ ] Explanatory text is in the response, not inside the widget
+- [ ] No titles or headings embedded in the HTML output
+- [ ] Visual is self-explanatory without reading the narration
+- [ ] Narration adds value beyond what the visual shows
+- [ ] Offered a clear "go deeper" path
+
+### Accessibility
+- [ ] `@media (prefers-reduced-motion: reduce)` for all animations
+- [ ] Text contrast is sufficient (dark text on light fills, vice versa)
+- [ ] Interactive elements are large enough to click (min 44px touch target)
+- [ ] No information conveyed by color alone
+
+---
+
+## Part 9: Decision Matrix — Picking the Right Visual
+
+| User asks about... | Output type | Technology |
+|-----------------------------|--------------------------|---------------------|
+| How X works (physical) | Illustrative diagram | SVG |
+| How X works (abstract) | Interactive explainer | HTML + inline SVG |
+| Process / steps | Flowchart | SVG |
+| Architecture / containment | Structural diagram | SVG |
+| Database schema / ERD | Relationship diagram | Mermaid |
+| Trends over time | Line chart | Chart.js |
+| Category comparison | Bar chart | Chart.js |
+| Part of whole | Doughnut chart | Chart.js |
+| KPIs / metrics | Dashboard | HTML metric cards |
+| Design a UI | Mockup | HTML |
+| Choose between options | Comparison cards | HTML grid |
+| Cyclic process | Step-through | HTML stepper |
+| Physics / math | Simulation | Canvas + JS |
+| Function / equation | Plotter | SVG + JS |
+| Data exploration | Sortable table | HTML + JS |
+| Creative / decorative | Art / illustration | SVG |
+| 3D visualization | 3D scene | Three.js |
+| Music / audio | Synthesizer | Tone.js |
+| Network / graph | Force layout | D3.js |
+| Quick factual answer | Plain text | None |
+| Code solution | Code block | None |
+| Emotional support | Warm text | None |
diff --git a/apps/mcp/skills/master-agent-playbook.txt b/apps/mcp/skills/master-agent-playbook.txt
new file mode 100644
index 0000000..e27ff25
--- /dev/null
+++ b/apps/mcp/skills/master-agent-playbook.txt
@@ -0,0 +1,432 @@
+# Master Agent Playbook: Making AI Responses Extraordinary
+
+This playbook teaches an AI coding agent how to go beyond plain text and deliver
+responses that are visual, interactive, and deeply educational. It covers the
+philosophy, decision-making, and technical skills needed.
+
+---
+
+## Part 1: The Core Philosophy
+
+### Think Like a Teacher, Not a Search Engine
+
+Bad: "A load path is the route that forces take through a structure to the ground."
+Good: [draws an interactive building cross-section with loads flowing downward]
+
+The principle: **Show, don't just tell.** Before writing any response, ask:
+- Would a diagram make this click faster than a paragraph?
+- Would an interactive widget let the user explore the concept themselves?
+- Would a worked example teach better than a definition?
+
+### The Response Decision Tree
+
+```
+User asks a question
+ │
+ ├─ Is it a quick factual answer? → Answer in 1-2 sentences.
+ │
+ ├─ Is it conceptual / "how does X work"?
+ │ ├─ Is it spatial or visual? → SVG illustrative diagram
+ │ ├─ Is it a process/flow? → SVG flowchart or HTML stepper
+ │ ├─ Is it data-driven? → Interactive chart (Chart.js / Recharts)
+ │ └─ Is it abstract but explorable? → Interactive HTML widget with controls
+ │
+ ├─ Is it "build me X"? → Working code artifact, fully functional
+ │
+ ├─ Is it a comparison? → Side-by-side table or comparative visual
+ │
+ └─ Is it emotional/personal? → Warm text response. No visuals needed.
+```
+
+### The 3-Layer Response Pattern
+
+Great responses layer information:
+
+1. **Hook** (1-2 sentences): Validate the question, set context.
+2. **Visual** (diagram/widget): The core explanation, rendered visually.
+3. **Narration** (2-4 paragraphs): Walk through the visual, add nuance,
+ connect to what the user already knows. Offer to go deeper.
+
+Never dump a visual without narration. Never narrate without visuals
+when visuals would help.
+
+---
+
+## Part 2: Skill — Interactive HTML Widgets
+
+For concepts that benefit from user exploration. More powerful than
+static SVGs — users can manipulate parameters and see results.
+
+### When to Use
+- The concept has a variable the user could tweak (temperature, rate, count)
+- The system has states the user could toggle (on/off, mode A/B)
+- The explanation benefits from stepping through stages
+- Data exploration or filtering is involved
+
+### Template: Interactive Widget with Controls
+
+```html
+
+
+
+
+
+
+
+
+
+
+
+ Parameter
+
+ 50
+
+
+
+
+```
+
+### Template: Step-Through Explainer
+
+For cyclic or staged processes (event loops, biological cycles, pipelines).
+
+```html
+
+
+
+
+
+
+
+
Previous
+
+
Next
+
Step 1 of 4
+
+
+
+```
+
+### CSS Animation Patterns (for live diagrams)
+
+```css
+/* Flowing particles along a path */
+@keyframes flow {
+ to { stroke-dashoffset: -20; }
+}
+.flowing {
+ stroke-dasharray: 5 5;
+ animation: flow 1.6s linear infinite;
+}
+
+/* Pulsing glow for active elements */
+@keyframes pulse {
+ 0%, 100% { opacity: 0.3; }
+ 50% { opacity: 0.7; }
+}
+.pulsing { animation: pulse 2s ease-in-out infinite; }
+
+/* Flickering (for flames, sparks) */
+@keyframes flicker {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.8; }
+}
+
+/* Always respect reduced motion preferences */
+@media (prefers-reduced-motion: reduce) {
+ *, *::before, *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ }
+}
+```
+
+---
+
+## Part 3: Skill — Data Visualization
+
+### When to Use
+- Comparing quantities
+- Showing trends over time
+- Displaying distributions or proportions
+- Making data explorable
+
+### Approach: Inline SVG Charts (No Dependencies)
+
+For simple charts, hand-draw in SVG. No library needed.
+
+```svg
+
+
+
+
+
+
+
+
+
+ Q1
+ $42k
+
+
+```
+
+### Approach: Chart.js (For Complex/Interactive Charts)
+
+When you need tooltips, responsive legends, animations:
+
+```html
+
+
+
+```
+
+---
+
+## Part 4: Skill — Mermaid Diagrams
+
+For relationship diagrams (ERDs, class diagrams, sequence diagrams) where
+precise layout math isn't worth doing by hand.
+
+```html
+
+
+```
+
+Use Mermaid for: ERDs, class diagrams, sequence diagrams, Gantt charts.
+Use hand-drawn SVG for: everything else (flowcharts, architecture,
+illustrative diagrams) — you get much better control.
+
+---
+
+## Part 5: Skill — Explanatory Writing Between Visuals
+
+### Narration Patterns
+
+**The Walk-Through**: Point at parts of the visual and explain them.
+> "Starting at the top, the roof deck collects distributed loads across
+> its surface. These get channeled into the rafters below, which act
+> like one-way bridges..."
+
+**The "Why It Matters"**: Connect the visual to real consequences.
+> "This is why lower columns are always larger — they're carrying the
+> accumulated weight of every floor above."
+
+**The "Common Mistake"**: Anticipate misconceptions.
+> "One thing that trips people up: removing a single column doesn't just
+> lose that member — it breaks the entire load chain."
+
+**The "Go Deeper" Offer**: End with expansion paths.
+> "Want me to show how lateral loads (wind, seismic) take a completely
+> different path?"
+
+### Tone Rules
+- Warm and direct. Not academic, not dumbed-down.
+- Use "you" and "we" freely.
+- Analogies and metaphors are powerful. Use them.
+- Short paragraphs (2-4 sentences). No walls of text.
+- Bold key terms on first introduction, then don't re-bold.
+- Never use bullet points for explanations. Prose only.
+- Ask at most one question per response.
+
+---
+
+## Part 6: Skill — Knowing What NOT to Visualize
+
+Not everything needs a diagram. Skip visuals when:
+
+- The answer is a single fact or number
+- The user is venting or emotional (empathy, not charts)
+- The topic is purely textual (writing, editing, drafting)
+- A code snippet is the answer (just show the code)
+- The user explicitly asked for brief/concise
+
+### The "Would They Screenshot This?" Test
+
+If the user would likely screenshot or save the visual to reference
+later, it was worth making. If not, just use text.
+
+---
+
+## Part 7: Putting It All Together
+
+### Example Response Structure (Complex Technical Question)
+
+```
+[1-2 sentence hook validating the question]
+
+[Visual: SVG diagram or interactive widget]
+
+[Walk-through narration: 3-4 paragraphs explaining the visual,
+ pointing at specific parts, noting key insights]
+
+[One "go deeper" offer with 2-3 specific directions]
+```
+
+### Example Response Structure (Simple Question with Visual Aid)
+
+```
+[Direct answer in 1-2 sentences]
+
+[Small supporting visual if it adds value]
+
+[One additional insight or context sentence]
+```
+
+### Quality Checklist Before Responding
+
+- [ ] Did I pick the right format? (text vs SVG vs interactive vs chart)
+- [ ] Is the visual self-explanatory even without the narration?
+- [ ] Does the narration add value beyond what the visual shows?
+- [ ] Are colors meaningful, not decorative?
+- [ ] Does it work in dark mode?
+- [ ] Is the response concise? (Cut anything that doesn't teach)
+- [ ] Did I offer a clear next step?
+
+---
+
+## Appendix: Quick Reference
+
+| Concept Type | Best Format |
+|-------------------------|------------------------------|
+| How X works (physical) | Illustrative SVG diagram |
+| How X works (abstract) | Interactive HTML + SVG |
+| Process / workflow | SVG flowchart |
+| Architecture | SVG structural diagram |
+| Data relationships | Mermaid ERD |
+| Trends / comparisons | Chart.js or SVG bar chart |
+| Cyclic process | HTML step-through widget |
+| System states | Interactive widget + toggles |
+| Quick answer | Plain text |
+| Code solution | Code block / artifact |
+| Emotional support | Warm text only |
diff --git a/apps/mcp/skills/svg-diagram-skill.txt b/apps/mcp/skills/svg-diagram-skill.txt
new file mode 100644
index 0000000..2a28385
--- /dev/null
+++ b/apps/mcp/skills/svg-diagram-skill.txt
@@ -0,0 +1,245 @@
+# SVG Diagram Generation Skill
+
+You can generate rich, inline SVG diagrams to visually explain concepts. Use this skill whenever a visual would help the user understand a system, process, architecture, or mechanism better than text alone.
+
+---
+
+## When to Use
+
+- Explaining how something works (load paths, circuits, pipelines, algorithms)
+- Showing architecture or structure (system diagrams, component layouts)
+- Illustrating processes or flows (flowcharts, data flow, decision trees)
+- Building intuition for abstract concepts (attention mechanisms, gradient descent, recursion)
+
+## SVG Setup
+
+Always use this template:
+
+```svg
+
+
+
+
+
+
+
+
+```
+
+- **Width is always 680px** via viewBox. Set `width="100%"` so it scales responsively.
+- **H (height)** = bottom-most element's y + height + 40px padding. Don't guess — compute it.
+- **Safe content area**: x=40 to x=640, y=40 to y=(H-40).
+- **No wrapping divs**, no ``, ``, ``, or DOCTYPE.
+- **Background is transparent** — the host provides the background.
+
+---
+
+## Core Design Rules
+
+### Typography
+- **Two sizes only**: 14px for titles/labels, 12px for subtitles/descriptions.
+- **Two weights only**: 400 (regular), 500 (medium/bold). Never use 600 or 700.
+- **Font**: Use `font-family="system-ui, -apple-system, sans-serif"` or inherit from host.
+- **Always set** `text-anchor="middle"` and `dominant-baseline="central"` for centered text in boxes.
+- **Sentence case always**. Never Title Case or ALL CAPS.
+
+### Text Width Estimation
+At 14px, each character ≈ 8px wide. At 12px, each character ≈ 7px wide.
+- "Load Balancer" (13 chars) at 14px ≈ 104px → needs rect ≈ 140px wide (with padding).
+- Always compute: `rect_width = max(title_chars × 8, subtitle_chars × 7) + 48px padding`.
+
+### Colors (Light/Dark Mode Safe)
+Use these semantic color sets that work in both modes:
+
+```
+Teal: fill="#E1F5EE" stroke="#0F6E56" text="#085041" (dark: fill="#085041" stroke="#5DCAA5" text="#9FE1CB")
+Purple: fill="#EEEDFE" stroke="#534AB7" text="#3C3489" (dark: fill="#3C3489" stroke="#AFA9EC" text="#CECBF6")
+Coral: fill="#FAECE7" stroke="#993C1D" text="#712B13" (dark: fill="#712B13" stroke="#F0997B" text="#F5C4B3")
+Amber: fill="#FAEEDA" stroke="#854F0B" text="#633806" (dark: fill="#633806" stroke="#EF9F27" text="#FAC775")
+Blue: fill="#E6F1FB" stroke="#185FA5" text="#0C447C" (dark: fill="#0C447C" stroke="#85B7EB" text="#B5D4F4")
+Gray: fill="#F1EFE8" stroke="#5F5E5A" text="#444441" (dark: fill="#444441" stroke="#B4B2A9" text="#D3D1C7")
+Red: fill="#FCEBEB" stroke="#A32D2D" text="#791F1F" (dark: fill="#791F1F" stroke="#F09595" text="#F7C1C1")
+Green: fill="#EAF3DE" stroke="#3B6D11" text="#27500A" (dark: fill="#27500A" stroke="#97C459" text="#C0DD97")
+Pink: fill="#FBEAF0" stroke="#993556" text="#72243E" (dark: fill="#72243E" stroke="#ED93B1" text="#F4C0D1")
+```
+
+**Color meaning, not sequence**: Don't rainbow-cycle. Use 2-3 colors per diagram. Map colors to categories or physical properties (warm = heat/energy, cool = calm/cold, gray = structural/neutral).
+
+If you're rendering inside a system that supports CSS variables, prefer:
+- `var(--color-text-primary)` for primary text
+- `var(--color-text-secondary)` for muted text
+- `var(--color-border-tertiary)` for light borders
+
+### Shapes & Layout
+- **Stroke width**: 0.5px for borders, 1.5px for arrows/connectors.
+- **Corner radius**: `rx="4"` for subtle rounding, `rx="8"` for emphasized. `rx="20"` for large containers.
+- **Spacing**: 60px minimum between boxes, 24px padding inside boxes, 12px text-to-edge clearance.
+- **Single-line box**: 44px tall. **Two-line box**: 56px tall.
+- **Max 4-5 nodes per row** at 680px width. If more, split into multiple diagrams.
+- **All connectors need `fill="none"`** — SVG defaults fill to black, which turns paths into black blobs.
+
+---
+
+## Component Patterns
+
+### Single-Line Node
+```svg
+
+
+ Node title
+
+```
+
+### Two-Line Node
+```svg
+
+
+ Title
+ Short subtitle
+
+```
+
+### Arrow Connector
+```svg
+
+```
+
+### Dashed Flow Indicator
+```svg
+
+```
+
+### Leader Line with Label (for annotations)
+```svg
+
+
+Annotation text
+```
+
+### Large Container (for structural diagrams)
+```svg
+
+Container name
+```
+
+---
+
+## Diagram Types & When to Use Each
+
+### 1. Flowchart
+**When**: Sequential processes, decision trees, pipelines.
+**Layout**: Top-to-bottom or left-to-right. Single direction only.
+**Rules**:
+- Arrows must never cross unrelated boxes. Route around with L-bends if needed.
+- Keep all same-type boxes the same height.
+- Max 4-5 nodes per diagram. Break complex flows into multiple diagrams.
+
+### 2. Structural Diagram
+**When**: Containment matters — things inside other things (architecture, org charts, system components).
+**Layout**: Nested rectangles. Outer = container, inner = regions.
+**Rules**:
+- Max 2-3 nesting levels.
+- 20px minimum padding inside every container.
+- Use different color ramps for parent vs child to show hierarchy.
+
+### 3. Illustrative Diagram
+**When**: Building intuition. "How does X actually work?"
+**Layout**: Freeform — follows the subject's natural geometry.
+**Rules**:
+- Shapes can be freeform (paths, ellipses, polygons), not just rects.
+- Color encodes intensity, not category (warm = active, cool = dormant).
+- Overlap shapes for depth, but never let strokes cross text.
+- Labels go in margins with leader lines pointing to the relevant part.
+
+---
+
+## Critical Checks Before Finalizing
+
+1. **ViewBox height**: Find your lowest element (max y + height). Set H = that + 40px.
+2. **No content past x=640 or below y=(H-40)**.
+3. **Text fits in boxes**: `(char_count × 8) + 48 < rect_width` for 14px text.
+4. **No arrows through boxes**: Trace every line's path — if it crosses a rect, reroute.
+5. **All `` connectors have `fill="none"`**.
+6. **All text has appropriate fill color** — never rely on inheritance (SVG defaults to black).
+7. **Colors work in dark mode**: If using hardcoded colors, provide both light and dark variants. If using CSS variables, you're fine.
+
+---
+
+## Multi-Diagram Approach
+
+For complex topics, use multiple smaller SVGs instead of one dense one:
+- Each SVG should have 3-5 nodes max.
+- Write explanatory text between diagrams.
+- First diagram = overview, subsequent = zoom into subsections.
+- Never promise diagrams you don't deliver.
+
+---
+
+## Example: Simple 3-Step Flow
+
+```svg
+
+
+
+
+
+
+
+
+
+ User request
+ HTTP POST /api/data
+
+
+
+
+
+
+ Server processing
+ Validate and transform
+
+
+
+
+
+
+ Database write
+ INSERT into table
+
+```
+
+---
+
+## Tips for Great Diagrams
+
+- **Less is more**: A clean 4-node diagram teaches better than a cramped 12-node one.
+- **Color = meaning**: Warm colors for active/hot/important, cool for passive/cold/secondary, gray for structural.
+- **Streaming effect**: Since SVGs render top-to-bottom as tokens arrive, structure your elements top-down for a natural build-up animation.
+- **Annotations on the side**: Put explanatory labels in the right margin (x > 560) with leader lines pointing to the relevant element.
+- **Consistent heights**: All boxes of the same type should be the same height.
+- **Whitespace is your friend**: Don't fill every pixel. Breathing room makes diagrams readable.
diff --git a/apps/mcp/src/index.ts b/apps/mcp/src/index.ts
new file mode 100644
index 0000000..8e74307
--- /dev/null
+++ b/apps/mcp/src/index.ts
@@ -0,0 +1,59 @@
+import { serve } from "@hono/node-server";
+import { Hono } from "hono";
+import { cors } from "hono/cors";
+import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
+import { createMcpServer } from "./server.js";
+
+const PORT = Number(process.env.MCP_PORT) || 3100;
+const ALLOWED_ORIGINS = process.env.ALLOWED_ORIGINS?.split(",") || ["*"];
+
+const server = createMcpServer();
+const sessions = new Map();
+
+const app = new Hono();
+
+app.use(
+ "*",
+ cors({
+ origin:
+ ALLOWED_ORIGINS.length === 1 && ALLOWED_ORIGINS[0] === "*"
+ ? "*"
+ : ALLOWED_ORIGINS,
+ allowMethods: ["GET", "POST", "DELETE", "OPTIONS"],
+ allowHeaders: [
+ "Content-Type",
+ "mcp-session-id",
+ "Last-Event-ID",
+ "mcp-protocol-version",
+ ],
+ exposeHeaders: ["mcp-session-id", "mcp-protocol-version"],
+ })
+);
+
+app.get("/health", (c) => c.json({ status: "ok" }));
+
+app.all("/mcp", async (c) => {
+ const sessionId = c.req.header("mcp-session-id");
+
+ if (sessionId && sessions.has(sessionId)) {
+ return sessions.get(sessionId)!.handleRequest(c.req.raw);
+ }
+
+ const transport = new WebStandardStreamableHTTPServerTransport();
+ await server.connect(transport);
+
+ const response = await transport.handleRequest(c.req.raw);
+
+ const newSessionId = response.headers.get("mcp-session-id");
+ if (newSessionId) {
+ sessions.set(newSessionId, transport);
+ transport.onclose = () => {
+ sessions.delete(newSessionId);
+ };
+ }
+
+ return response;
+});
+
+serve({ fetch: app.fetch, port: PORT });
+console.log(`MCP server running on http://localhost:${PORT}/mcp`);
diff --git a/apps/mcp/src/renderer.ts b/apps/mcp/src/renderer.ts
new file mode 100644
index 0000000..8ae8238
--- /dev/null
+++ b/apps/mcp/src/renderer.ts
@@ -0,0 +1,351 @@
+// OpenGenerativeUI Design System CSS and Bridge JS
+// Forked from apps/app/src/components/generative-ui/widget-renderer.tsx
+// WARNING: Keep in sync with the source widget-renderer.tsx when the design system changes.
+
+const THEME_CSS = `
+:root {
+ --color-background-primary: #ffffff;
+ --color-background-secondary: #f7f6f3;
+ --color-background-tertiary: #efeee9;
+ --color-background-info: #E6F1FB;
+ --color-background-danger: #FCEBEB;
+ --color-background-success: #EAF3DE;
+ --color-background-warning: #FAEEDA;
+
+ --color-text-primary: #1a1a1a;
+ --color-text-secondary: #73726c;
+ --color-text-tertiary: #9c9a92;
+ --color-text-info: #185FA5;
+ --color-text-danger: #A32D2D;
+ --color-text-success: #3B6D11;
+ --color-text-warning: #854F0B;
+
+ --color-border-primary: rgba(0, 0, 0, 0.4);
+ --color-border-secondary: rgba(0, 0, 0, 0.3);
+ --color-border-tertiary: rgba(0, 0, 0, 0.15);
+ --color-border-info: #185FA5;
+ --color-border-danger: #A32D2D;
+ --color-border-success: #3B6D11;
+ --color-border-warning: #854F0B;
+
+ --font-sans: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
+ --font-serif: Georgia, "Times New Roman", serif;
+ --font-mono: "SF Mono", "Fira Code", "Fira Mono", monospace;
+
+ --border-radius-md: 8px;
+ --border-radius-lg: 12px;
+ --border-radius-xl: 16px;
+
+ --p: var(--color-text-primary);
+ --s: var(--color-text-secondary);
+ --t: var(--color-text-tertiary);
+ --bg2: var(--color-background-secondary);
+ --b: var(--color-border-tertiary);
+}
+
+@media (prefers-color-scheme: dark) {
+ :root {
+ --color-background-primary: #1a1a18;
+ --color-background-secondary: #2c2c2a;
+ --color-background-tertiary: #222220;
+ --color-background-info: #0C447C;
+ --color-background-danger: #501313;
+ --color-background-success: #173404;
+ --color-background-warning: #412402;
+
+ --color-text-primary: #e8e6de;
+ --color-text-secondary: #9c9a92;
+ --color-text-tertiary: #73726c;
+ --color-text-info: #85B7EB;
+ --color-text-danger: #F09595;
+ --color-text-success: #97C459;
+ --color-text-warning: #EF9F27;
+
+ --color-border-primary: rgba(255, 255, 255, 0.4);
+ --color-border-secondary: rgba(255, 255, 255, 0.3);
+ --color-border-tertiary: rgba(255, 255, 255, 0.15);
+ --color-border-info: #85B7EB;
+ --color-border-danger: #F09595;
+ --color-border-success: #97C459;
+ --color-border-warning: #EF9F27;
+ }
+}
+`;
+
+const SVG_CLASSES_CSS = `
+svg text.t { font: 400 14px var(--font-sans); fill: var(--p); }
+svg text.ts { font: 400 12px var(--font-sans); fill: var(--s); }
+svg text.th { font: 500 14px var(--font-sans); fill: var(--p); }
+
+svg .box > rect, svg .box > circle, svg .box > ellipse { fill: var(--bg2); stroke: var(--b); }
+svg .node { cursor: pointer; }
+svg .node:hover { opacity: 0.8; }
+svg .arr { stroke: var(--s); stroke-width: 1.5; fill: none; }
+svg .leader { stroke: var(--t); stroke-width: 0.5; stroke-dasharray: 4 4; fill: none; }
+
+/* Purple */
+svg .c-purple > rect, svg .c-purple > circle, svg .c-purple > ellipse,
+svg rect.c-purple, svg circle.c-purple, svg ellipse.c-purple { fill: #EEEDFE; stroke: #534AB7; }
+svg .c-purple text.th, svg .c-purple text.t { fill: #3C3489; }
+svg .c-purple text.ts { fill: #534AB7; }
+
+/* Teal */
+svg .c-teal > rect, svg .c-teal > circle, svg .c-teal > ellipse,
+svg rect.c-teal, svg circle.c-teal, svg ellipse.c-teal { fill: #E1F5EE; stroke: #0F6E56; }
+svg .c-teal text.th, svg .c-teal text.t { fill: #085041; }
+svg .c-teal text.ts { fill: #0F6E56; }
+
+/* Coral */
+svg .c-coral > rect, svg .c-coral > circle, svg .c-coral > ellipse,
+svg rect.c-coral, svg circle.c-coral, svg ellipse.c-coral { fill: #FAECE7; stroke: #993C1D; }
+svg .c-coral text.th, svg .c-coral text.t { fill: #712B13; }
+svg .c-coral text.ts { fill: #993C1D; }
+
+/* Pink */
+svg .c-pink > rect, svg .c-pink > circle, svg .c-pink > ellipse,
+svg rect.c-pink, svg circle.c-pink, svg ellipse.c-pink { fill: #FBEAF0; stroke: #993556; }
+svg .c-pink text.th, svg .c-pink text.t { fill: #72243E; }
+svg .c-pink text.ts { fill: #993556; }
+
+/* Gray */
+svg .c-gray > rect, svg .c-gray > circle, svg .c-gray > ellipse,
+svg rect.c-gray, svg circle.c-gray, svg ellipse.c-gray { fill: #F1EFE8; stroke: #5F5E5A; }
+svg .c-gray text.th, svg .c-gray text.t { fill: #444441; }
+svg .c-gray text.ts { fill: #5F5E5A; }
+
+/* Blue */
+svg .c-blue > rect, svg .c-blue > circle, svg .c-blue > ellipse,
+svg rect.c-blue, svg circle.c-blue, svg ellipse.c-blue { fill: #E6F1FB; stroke: #185FA5; }
+svg .c-blue text.th, svg .c-blue text.t { fill: #0C447C; }
+svg .c-blue text.ts { fill: #185FA5; }
+
+/* Green */
+svg .c-green > rect, svg .c-green > circle, svg .c-green > ellipse,
+svg rect.c-green, svg circle.c-green, svg ellipse.c-green { fill: #EAF3DE; stroke: #3B6D11; }
+svg .c-green text.th, svg .c-green text.t { fill: #27500A; }
+svg .c-green text.ts { fill: #3B6D11; }
+
+/* Amber */
+svg .c-amber > rect, svg .c-amber > circle, svg .c-amber > ellipse,
+svg rect.c-amber, svg circle.c-amber, svg ellipse.c-amber { fill: #FAEEDA; stroke: #854F0B; }
+svg .c-amber text.th, svg .c-amber text.t { fill: #633806; }
+svg .c-amber text.ts { fill: #854F0B; }
+
+/* Red */
+svg .c-red > rect, svg .c-red > circle, svg .c-red > ellipse,
+svg rect.c-red, svg circle.c-red, svg ellipse.c-red { fill: #FCEBEB; stroke: #A32D2D; }
+svg .c-red text.th, svg .c-red text.t { fill: #791F1F; }
+svg .c-red text.ts { fill: #A32D2D; }
+
+@media (prefers-color-scheme: dark) {
+ svg text.t { fill: #e8e6de; }
+ svg text.ts { fill: #9c9a92; }
+ svg text.th { fill: #e8e6de; }
+
+ svg .c-purple > rect, svg .c-purple > circle, svg .c-purple > ellipse,
+ svg rect.c-purple, svg circle.c-purple, svg ellipse.c-purple { fill: #3C3489; stroke: #AFA9EC; }
+ svg .c-purple text.th, svg .c-purple text.t { fill: #CECBF6; }
+ svg .c-purple text.ts { fill: #AFA9EC; }
+
+ svg .c-teal > rect, svg .c-teal > circle, svg .c-teal > ellipse,
+ svg rect.c-teal, svg circle.c-teal, svg ellipse.c-teal { fill: #085041; stroke: #5DCAA5; }
+ svg .c-teal text.th, svg .c-teal text.t { fill: #9FE1CB; }
+ svg .c-teal text.ts { fill: #5DCAA5; }
+
+ svg .c-coral > rect, svg .c-coral > circle, svg .c-coral > ellipse,
+ svg rect.c-coral, svg circle.c-coral, svg ellipse.c-coral { fill: #712B13; stroke: #F0997B; }
+ svg .c-coral text.th, svg .c-coral text.t { fill: #F5C4B3; }
+ svg .c-coral text.ts { fill: #F0997B; }
+
+ svg .c-pink > rect, svg .c-pink > circle, svg .c-pink > ellipse,
+ svg rect.c-pink, svg circle.c-pink, svg ellipse.c-pink { fill: #72243E; stroke: #ED93B1; }
+ svg .c-pink text.th, svg .c-pink text.t { fill: #F4C0D1; }
+ svg .c-pink text.ts { fill: #ED93B1; }
+
+ svg .c-gray > rect, svg .c-gray > circle, svg .c-gray > ellipse,
+ svg rect.c-gray, svg circle.c-gray, svg ellipse.c-gray { fill: #444441; stroke: #B4B2A9; }
+ svg .c-gray text.th, svg .c-gray text.t { fill: #D3D1C7; }
+ svg .c-gray text.ts { fill: #B4B2A9; }
+
+ svg .c-blue > rect, svg .c-blue > circle, svg .c-blue > ellipse,
+ svg rect.c-blue, svg circle.c-blue, svg ellipse.c-blue { fill: #0C447C; stroke: #85B7EB; }
+ svg .c-blue text.th, svg .c-blue text.t { fill: #B5D4F4; }
+ svg .c-blue text.ts { fill: #85B7EB; }
+
+ svg .c-green > rect, svg .c-green > circle, svg .c-green > ellipse,
+ svg rect.c-green, svg circle.c-green, svg ellipse.c-green { fill: #27500A; stroke: #97C459; }
+ svg .c-green text.th, svg .c-green text.t { fill: #C0DD97; }
+ svg .c-green text.ts { fill: #97C459; }
+
+ svg .c-amber > rect, svg .c-amber > circle, svg .c-amber > ellipse,
+ svg rect.c-amber, svg circle.c-amber, svg ellipse.c-amber { fill: #633806; stroke: #EF9F27; }
+ svg .c-amber text.th, svg .c-amber text.t { fill: #FAC775; }
+ svg .c-amber text.ts { fill: #EF9F27; }
+
+ svg .c-red > rect, svg .c-red > circle, svg .c-red > ellipse,
+ svg rect.c-red, svg circle.c-red, svg ellipse.c-red { fill: #791F1F; stroke: #F09595; }
+ svg .c-red text.th, svg .c-red text.t { fill: #F7C1C1; }
+ svg .c-red text.ts { fill: #F09595; }
+}
+`;
+
+const FORM_STYLES_CSS = `
+* { box-sizing: border-box; margin: 0; }
+html { background: transparent; }
+body {
+ font-family: var(--font-sans);
+ font-size: 16px;
+ line-height: 1.7;
+ color: var(--color-text-primary);
+ background: transparent;
+ -webkit-font-smoothing: antialiased;
+}
+button {
+ font-family: inherit;
+ font-size: 14px;
+ padding: 6px 16px;
+ border: 0.5px solid var(--color-border-secondary);
+ border-radius: var(--border-radius-md);
+ background: transparent;
+ color: var(--color-text-primary);
+ cursor: pointer;
+ transition: background 0.15s, transform 0.1s;
+}
+button:hover { background: var(--color-background-secondary); }
+button:active { transform: scale(0.98); }
+input[type="text"],
+input[type="number"],
+input[type="email"],
+input[type="search"],
+textarea,
+select {
+ font-family: inherit;
+ font-size: 14px;
+ padding: 6px 12px;
+ height: 36px;
+ border: 0.5px solid var(--color-border-tertiary);
+ border-radius: var(--border-radius-md);
+ background: var(--color-background-primary);
+ color: var(--color-text-primary);
+ transition: border-color 0.15s;
+}
+input:hover, textarea:hover, select:hover { border-color: var(--color-border-secondary); }
+input:focus, textarea:focus, select:focus {
+ outline: none;
+ border-color: var(--color-border-primary);
+ box-shadow: 0 0 0 3px rgba(0, 0, 0, 0.06);
+}
+textarea { height: auto; min-height: 80px; resize: vertical; }
+input::placeholder, textarea::placeholder { color: var(--color-text-tertiary); }
+input[type="range"] {
+ -webkit-appearance: none;
+ appearance: none;
+ height: 4px;
+ background: var(--color-border-tertiary);
+ border-radius: 2px;
+ border: none;
+ outline: none;
+}
+input[type="range"]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ width: 18px; height: 18px;
+ border-radius: 50%;
+ background: var(--color-background-primary);
+ border: 0.5px solid var(--color-border-secondary);
+ cursor: pointer;
+ box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
+}
+input[type="range"]::-moz-range-thumb {
+ width: 18px; height: 18px;
+ border-radius: 50%;
+ background: var(--color-background-primary);
+ border: 0.5px solid var(--color-border-secondary);
+ cursor: pointer;
+}
+input[type="checkbox"], input[type="radio"] {
+ width: 16px; height: 16px;
+ accent-color: var(--color-text-info);
+}
+a { color: var(--color-text-info); text-decoration: none; }
+a:hover { text-decoration: underline; }
+#content > * {
+ animation: fadeSlideIn 0.4s ease-out both;
+}
+#content > *:nth-child(1) { animation-delay: 0s; }
+#content > *:nth-child(2) { animation-delay: 0.06s; }
+#content > *:nth-child(3) { animation-delay: 0.12s; }
+#content > *:nth-child(4) { animation-delay: 0.18s; }
+#content > *:nth-child(5) { animation-delay: 0.24s; }
+#content > *:nth-child(n+6) { animation-delay: 0.3s; }
+@keyframes fadeSlideIn {
+ from { opacity: 0; transform: translateY(8px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+@media (prefers-reduced-motion: reduce) {
+ *, *::before, *::after {
+ animation-duration: 0.01ms !important;
+ animation-iteration-count: 1 !important;
+ }
+}
+`;
+
+const BRIDGE_JS = `
+window.sendPrompt = function(text) {
+ window.parent.postMessage({ type: 'send-prompt', text: text }, '*');
+};
+window.openLink = function(url) {
+ window.parent.postMessage({ type: 'open-link', url: url }, '*');
+};
+document.addEventListener('click', function(e) {
+ var a = e.target.closest('a[href]');
+ if (a && a.href.startsWith('http')) {
+ e.preventDefault();
+ window.parent.postMessage({ type: 'open-link', url: a.href }, '*');
+ }
+});
+function reportHeight() {
+ var content = document.getElementById('content');
+ var h = content ? content.offsetHeight : document.documentElement.scrollHeight;
+ window.parent.postMessage({ type: 'widget-resize', height: h }, '*');
+}
+var ro = new ResizeObserver(reportHeight);
+ro.observe(document.getElementById('content') || document.body);
+window.addEventListener('load', reportHeight);
+var _resizeInterval = setInterval(reportHeight, 200);
+setTimeout(function() { clearInterval(_resizeInterval); }, 15000);
+`;
+
+export function assembleDocument(html: string): string {
+ return `
+
+
+
+
+
+
+
+
+
+ ${html}
+
+
+
+`;
+}
diff --git a/apps/mcp/src/server.ts b/apps/mcp/src/server.ts
new file mode 100644
index 0000000..b7328ae
--- /dev/null
+++ b/apps/mcp/src/server.ts
@@ -0,0 +1,110 @@
+import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
+import * as z from "zod";
+import { listSkills, loadSkill } from "./skills.js";
+import { assembleDocument } from "./renderer.js";
+
+export function createMcpServer(): McpServer {
+ const server = new McpServer({
+ name: "open-generative-ui",
+ version: "0.1.0",
+ });
+
+ // Resources: list and get skills
+ server.registerResource(
+ "skills-list",
+ "skills://list",
+ { description: "JSON array of available skill names", mimeType: "application/json" },
+ async () => ({
+ contents: [
+ {
+ uri: "skills://list",
+ mimeType: "application/json",
+ text: JSON.stringify(listSkills()),
+ },
+ ],
+ })
+ );
+
+ server.registerResource(
+ "skill",
+ new ResourceTemplate("skills://{name}", {
+ list: async () => ({
+ resources: listSkills().map((name) => ({
+ uri: `skills://${name}`,
+ name,
+ mimeType: "text/plain",
+ })),
+ }),
+ }),
+ { description: "Full text of a skill instruction document", mimeType: "text/plain" },
+ async (uri, { name }) => ({
+ contents: [
+ {
+ uri: uri.href,
+ mimeType: "text/plain",
+ text: loadSkill(name as string),
+ },
+ ],
+ })
+ );
+
+ // Prompts: pre-composed skill prompts
+ server.registerPrompt(
+ "create_widget",
+ { description: "Instructions for creating interactive HTML widgets" },
+ async () => ({
+ messages: [
+ {
+ role: "user",
+ content: { type: "text", text: loadSkill("master-agent-playbook") },
+ },
+ ],
+ })
+ );
+
+ server.registerPrompt(
+ "create_svg_diagram",
+ { description: "Instructions for creating inline SVG diagrams" },
+ async () => ({
+ messages: [
+ {
+ role: "user",
+ content: { type: "text", text: loadSkill("svg-diagram-skill") },
+ },
+ ],
+ })
+ );
+
+ server.registerPrompt(
+ "create_visualization",
+ { description: "Advanced visualization instructions" },
+ async () => ({
+ messages: [
+ {
+ role: "user",
+ content: { type: "text", text: loadSkill("agent-skills-vol2") },
+ },
+ ],
+ })
+ );
+
+ // Tool: assemble complete HTML document with design system
+ server.registerTool(
+ "assemble_document",
+ {
+ description:
+ "Wraps HTML with OpenGenerativeUI theme CSS, SVG classes, form styles, and bridge JS. " +
+ "Returns a complete iframe-ready HTML document.",
+ inputSchema: {
+ title: z.string().describe("Short title for the visualization"),
+ description: z.string().describe("One-sentence explanation of what this shows"),
+ html: z.string().describe("Self-contained HTML fragment with inline