7.3 KiB
Raw Blame History

GHL × json-render Spike

Proof-of-concept: replace hand-coded HTML MCP apps with AI-generated UIs using Vercel's 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

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