162 lines
7.3 KiB
Markdown
162 lines
7.3 KiB
Markdown
# 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
|