# GHL × json-render Spike Proof-of-concept: replace hand-coded HTML MCP apps with AI-generated UIs using Vercel's [json-render](https://github.com/vercel-labs/json-render) library. ## What This Is We have 11 GHL MCP apps built as self-contained HTML files (~400-540 lines each, ~5,000 lines total). Each is hand-coded with bespoke markup. This spike tests whether we can: 1. **Define a component catalog** (Zod schemas) that constrains what AI can generate 2. **Build React components** matching the existing visual quality 3. **Have AI produce JSON trees** that the Renderer turns into polished UIs 4. **Serve via MCP Apps SDK** (`@modelcontextprotocol/ext-apps`) for rendering in AI chat ## How It Works ``` User Prompt → AI + Catalog (guardrailed) → JSON Tree → React Renderer → Polished UI ┌──────────────┐ ┌───────────────┐ ┌──────────────┐ ┌──────────────┐ │ "Show me the │────▶│ Claude + GHL │────▶│ JSON Tree │────▶│ React with │ │ pipeline" │ │ Catalog │ │ (validated) │ │ Tailwind UI │ └──────────────┘ └───────────────┘ └──────────────┘ └──────────────┘ ``` **Key insight:** One HTML app bundle serves ALL views. The JSON tree in `structuredContent` determines what renders. ## Quick Start ```bash cd ghl-json-render-spike npm install npm run dev # Open http://localhost:3000 ``` ## Demo Pages | Page | URL | Components Used | |------|-----|-----------------| | Landing | `/` | Overview + links to demos | | Contact Grid | `/contact-grid` | PageHeader · SearchBar · FilterChips · DataTable | | Pipeline Board | `/pipeline` | PageHeader (gradient) · KanbanBoard | | Campaign Stats | `/campaign` | PageHeader · StatsGrid · MetricCard · ProgressBar · Card | | Invoice Preview | `/invoice` | DetailHeader · SplitLayout · InfoBlock · LineItemsTable · KeyValueList · ActionBar | | Playground | `/playground` | Paste any JSON tree → see it render live | ## Project Structure ``` src/ ├── catalog/ │ └── ghl-catalog.ts # Component catalog (Zod schemas + action defs) ├── components/ # React implementations of each catalog component │ ├── registry.tsx # Maps type names → React components │ ├── PageHeader.tsx │ ├── DataTable.tsx │ ├── KanbanBoard.tsx │ ├── MetricCard.tsx │ ├── LineItemsTable.tsx │ └── ... (20 components total) ├── examples/ # JSON trees showing what AI would generate │ ├── contact-grid.json │ ├── pipeline-board.json │ ├── campaign-stats.json │ └── invoice-preview.json ├── lib/ │ └── GHLRenderer.tsx # Wraps DataProvider + ActionProvider + Renderer ├── mcp/ │ ├── generate-ui.ts # System prompts + catalog → prompt generation │ └── mcp-tool-handler.ts # Full MCP Apps SDK integration example └── app/ # Next.js pages (demo harness) ``` ## Component Catalog (20 Components) ### Layout - **PageHeader** — title, subtitle, status badge, meta stats, gradient variant - **Card** — container with optional header, padding variants - **StatsGrid** — responsive grid of metric cards - **SplitLayout** — two-column layout (50/50, 33/67, 67/33) - **Section** — titled section wrapper ### Data Display - **DataTable** — sortable table with column formats, row selection, pagination - **KanbanBoard** — columns with draggable cards (pipeline view) - **MetricCard** — big number + label + trend indicator - **StatusBadge** — colored badge by status variant - **Timeline** — chronological event list - **ProgressBar** — percentage bar with benchmark markers ### Detail Views - **DetailHeader** — entity name, ID, status badge - **KeyValueList** — label/value pairs (totals, metadata) - **LineItemsTable** — invoice-style table with quantities and totals - **InfoBlock** — labeled block of info (From/To on invoices) ### Interactive - **SearchBar** — search input with focus ring - **FilterChips** — toggleable filter tags - **TabGroup** — tab navigation ### Actions - **ActionButton** — primary/secondary/danger/ghost variants - **ActionBar** — row of action buttons ### Action Definitions (12 Actions) `view_contact`, `edit_contact`, `delete_contact`, `move_opportunity`, `send_invoice`, `mark_paid`, `void_invoice`, `download_pdf`, `pause_campaign`, `resume_campaign`, `export_data`, `refresh_data` ## MCP Apps Integration The spike includes a complete example of how this integrates with the official MCP Apps SDK: ``` registerAppTool → returns structuredContent with UITree registerAppResource → serves single-file HTML bundle HTML app receives UITree via ontoolresult → feeds to json-render Renderer ``` **Key pattern:** One `registerAppResource` call serves one HTML bundle. ALL GHL views render through it. The `structuredContent.uiTree` JSON tree determines the UI. See `src/mcp/mcp-tool-handler.ts` for the full server example using: - `registerAppTool` from `@modelcontextprotocol/ext-apps/server` - `registerAppResource` with `RESOURCE_MIME_TYPE` - `ui://` scheme resource URIs - `structuredContent` for passing data to the UI - Action tools (no UI) called from the app via `app.callServerTool()` ## Comparison: Old vs New ### Old Approach (hand-coded HTML) - 11 separate HTML files, each 400-540 lines - **~5,000 lines** of bespoke HTML/CSS/JS - Each app has duplicated styles, markup patterns - Changes to design require editing all 11 files - No validation — errors surface at runtime ### New Approach (json-render) - 1 component catalog: **~270 lines** (Zod schemas) - 20 React components: **~800 lines** total - 4 JSON examples: **~400 lines** total (what AI generates) - **~1,470 lines** total for equivalent coverage - Changes to design: update one component, all views update - Catalog-enforced validation — AI can only use defined components ### Line Count Reduction | | Old | New | Savings | |---|---|---|---| | Per app | ~470 lines | ~100 lines JSON | **~79%** | | Total (4 apps) | ~1,880 lines | ~400 lines JSON + ~1,070 shared | **~22% fewer total, 79% less per-app work** | | Adding a new app | ~470 lines from scratch | ~100 lines JSON (AI-generated) | **AI does it** | The real win: **adding new views costs ~0 developer effort** — Claude generates the JSON tree from the catalog. ## Next Steps 1. **Build the single-file HTML app** — Vite + vite-plugin-singlefile bundle containing React, json-render, and all GHL components 2. **Wire up to real MCP server** — Replace the Next.js demo with `registerAppResource` serving the bundle 3. **Add streaming** — Use `useUIStream` from json-render for progressive rendering as Claude generates 4. **Expand the catalog** — Add calendar, workflow, timeline components for remaining 7 GHL apps 5. **AI generation testing** — Test Claude generating JSON trees from the catalog prompt with real GHL data 6. **Action integration** — Wire `app.callServerTool()` to actual GHL API endpoints