# MCP UI Kit — React Rewrite Plan ## Vision Build a **generic, reusable MCP UI component library** using React + ext-apps SDK. Any MCP server (GHL, HubSpot, Salesforce, etc.) can use this component kit to render AI-generated interactive UIs. GHL is the first implementation. The library is CRM-agnostic — interactive components accept tool names as props so each server configures its own tool mappings. --- ## Architecture ``` ┌─────────────────────────────────────────────────────────┐ │ Goose / Claude Desktop (MCP Host) │ │ │ │ tools/call → GHL MCP Server → GHL API │ │ ↑ ↓ │ │ tools/call structuredContent (JSON UI tree) │ │ ↑ ↓ │ │ ┌─────────────────────────────────────────────┐ │ │ │ React App (iframe via ext-apps SDK) │ │ │ │ │ │ │ │ useApp() hook ← ontoolresult (UI tree) │ │ │ │ ↓ │ │ │ │ MCPAppProvider (React Context) │ │ │ │ - uiTree state │ │ │ │ - formState (inputs, selections) │ │ │ │ - callTool(name, args) → MCP server │ │ │ │ ↓ │ │ │ │ │ │ │ │ - Looks up component by type │ │ │ │ - Renders React component with props │ │ │ │ - Recursively renders children │ │ │ │ ↓ │ │ │ │ 42 Display Components (pure, CRM-agnostic) │ │ │ │ + 8 Interactive Components (tool-configurable)│ │ │ │ - ContactPicker(searchTool="search_contacts")│ │ │ │ - InvoiceBuilder(createTool="create_invoice")│ │ │ │ - KanbanBoard(onMoveTool="update_opportunity")│ │ │ │ - EditableField(saveTool=props.saveTool) │ │ │ └─────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────┘ ``` ## File Structure (New) ``` src/ui/react-app/ # GENERIC MCP UI KIT (no GHL references) ├── package.json # React + ext-apps SDK deps ├── vite.config.ts # Vite + singlefile + React ├── tsconfig.json ├── index.html # Entry point ├── src/ │ ├── App.tsx # Root — useApp hook, MCPAppProvider │ ├── types.ts # UITree, UIElement, component prop interfaces │ ├── context/ │ │ └── MCPAppContext.tsx # React Context — uiTree, formState, callTool │ ├── hooks/ │ │ ├── useCallTool.ts # Hook wrapping app.callServerTool │ │ ├── useFormState.ts # Shared form state management │ │ └── useSizeReporter.ts # Auto-report content size to host │ ├── renderer/ │ │ ├── UITreeRenderer.tsx # Recursive tree → React component resolver │ │ └── registry.ts # Component name → React component map │ ├── components/ │ │ ├── layout/ # PageHeader, Card, SplitLayout, Section, StatsGrid │ │ ├── data/ # DataTable, KanbanBoard, MetricCard, StatusBadge, │ │ │ # Timeline, ProgressBar, KeyValueList, etc. │ │ ├── charts/ # BarChart, LineChart, PieChart, FunnelChart, Sparkline │ │ ├── comms/ # ChatThread, EmailPreview, TranscriptView, etc. │ │ ├── viz/ # CalendarView, FlowDiagram, TreeView, MediaGallery, etc. │ │ ├── interactive/ # ContactPicker, InvoiceBuilder, EditableField, etc. │ │ │ # All accept tool names as PROPS (CRM-agnostic) │ │ └── shared/ # ActionButton, Toast, Modal (React portals) │ └── styles/ │ ├── base.css # Reset, variables, typography │ ├── components.css # Component-specific styles (compact for chat) │ └── interactive.css # Drag/drop, modals, toasts, form styles ``` ### CRM-Agnostic Design Principle - NO component imports GHL types or references GHL tool names - Interactive components receive tool names via props: - `ContactPicker` → `searchTool="search_contacts"` (GHL) or `"hubspot_search_contacts"` (HubSpot) - `KanbanBoard` → `moveTool="update_opportunity"` (GHL) or `"move_deal"` (Pipedrive) - `InvoiceBuilder` → `createTool="create_invoice"` (any billing system) - The MCP server's AI prompt tells Claude which tool names to use in the UI tree - Components call `callTool(props.toolName, args)` — they don't know or care what CRM is behind it ## Component Inventory ### Existing 42 (string → React conversion) **Layout (5):** PageHeader, Card, StatsGrid, SplitLayout, Section **Data Display (10):** DataTable, KanbanBoard, MetricCard, StatusBadge, Timeline, ProgressBar, DetailHeader, KeyValueList, LineItemsTable, InfoBlock **Navigation (3):** SearchBar, FilterChips, TabGroup **Actions (2):** ActionButton, ActionBar **Extended Data (6):** CurrencyDisplay, TagList, CardGrid, AvatarGroup, StarRating, StockIndicator **Communications (6):** ChatThread, EmailPreview, ContentPreview, TranscriptView, AudioPlayer, ChecklistView **Visualization (5):** CalendarView, FlowDiagram, TreeView, MediaGallery, DuplicateCompare **Charts (5):** BarChart, LineChart, PieChart, FunnelChart, SparklineChart ### New Interactive Components (8) | Component | Purpose | MCP Tools Used | |-----------|---------|----------------| | **ContactPicker** | Searchable dropdown, fetches contacts on type | `search_contacts` | | **InvoiceBuilder** | Line items, totals, contact auto-fill | `create_invoice`, `get_contact` | | **OpportunityEditor** | Inline edit deal name/value/status/stage | `update_opportunity` | | **AppointmentBooker** | Calendar slot picker + booking form | `get_calendar`, `create_appointment` | | **EditableField** | Click-to-edit any text/number field | varies (generic) | | **SelectDropdown** | Generic select with async option loading | varies | | **FormGroup** | Group of form fields with validation | varies | | **AmountInput** | Currency-formatted number input | — (local state) | --- ## Agent Team Plan ### Phase 1: Foundation (Sequential — 1 agent) **Agent Alpha — Project Scaffold + App Shell** - Create `src/ui/react-app/` with package.json, vite.config, tsconfig - Install deps: react, react-dom, @modelcontextprotocol/ext-apps, @vitejs/plugin-react, vite-plugin-singlefile - Build `App.tsx` with `useApp` hook — handles ontoolresult, ontoolinput, host context - Build `GHLContext.tsx` — React context providing uiTree, formState, callTool - Build `useCallTool.ts` — wrapper around `app.callServerTool` with loading/error states - Build `useFormState.ts` — shared form state hook - Build `useSizeReporter.ts` — auto-measures content, sends `ui/notifications/size-changed` - Build `UITreeRenderer.tsx` — recursive renderer that resolves component types from registry - Build `registry.ts` — component map (stubs for now, filled by other agents) - Build `types.ts` — UITree, UIElement, all component prop interfaces - Build base CSS (`base.css`) — reset, variables, compact typography - Update outer build pipeline in GoHighLevel-MCP `package.json` to build React app - **Output:** Working scaffold that renders a loading state, connects to host via ext-apps ### Phase 2: Components (Parallel — 4 agents) **Agent Bravo — Layout + Core Data Components (15)** Files: `components/layout/`, `components/data/` (first half) - PageHeader, Card, StatsGrid, SplitLayout, Section - DataTable (with clickable rows, sortable columns) - KanbanBoard (with FULL drag-and-drop via React state — no DOM hacking) - MetricCard, StatusBadge, Timeline - Register all in registry.ts - Component CSS in `components.css` **Agent Charlie — Data Display + Navigation + Actions (15)** Files: `components/data/` (second half), `components/shared/` - ProgressBar, DetailHeader, KeyValueList, LineItemsTable, InfoBlock - SearchBar, FilterChips, TabGroup - ActionButton, ActionBar - CurrencyDisplay, TagList, CardGrid, AvatarGroup, StarRating, StockIndicator - Register all in registry.ts **Agent Delta — Comms + Viz + Charts (16)** Files: `components/comms/`, `components/viz/`, `components/charts/` - ChatThread, EmailPreview, ContentPreview, TranscriptView, AudioPlayer, ChecklistView - CalendarView, FlowDiagram, TreeView, MediaGallery, DuplicateCompare - BarChart, LineChart, PieChart, FunnelChart, SparklineChart - All chart components use inline SVG (same approach, just JSX) - Register all in registry.ts **Agent Echo — Interactive Components + Forms (8)** Files: `components/interactive/`, `hooks/` - ContactPicker — searchable dropdown, calls `search_contacts` on keystroke with debounce - InvoiceBuilder — line items table + contact selection + auto-total - OpportunityEditor — inline edit form for deal fields, saves via `update_opportunity` - AppointmentBooker — date/time picker + contact + calendar selection - EditableField — click-to-edit wrapper for any field - SelectDropdown — generic async select - FormGroup — form layout with labels + validation - AmountInput — formatted currency input - Shared: Toast component, Modal component (proper React portals) - Integrate with GHLContext for tool calling ### Phase 3: Integration (Sequential — 1 agent) **Agent Foxtrot — Wire Everything Together** - Merge all component registrations into `registry.ts` - Update `src/apps/index.ts`: - Add new tool definitions for interactive components (`create_invoice`, `create_appointment`) - Update resource handler for `ui://ghl/dynamic-view` to serve React build - Add new resource URIs if needed - Update `src/server.ts` if new tools need routing - Update system prompt: add new interactive component catalog entries - Update Goose config `available_tools` if needed - Full build: React app → singlefile HTML → server TypeScript - Test: verify JSON UI trees render correctly, interactive components call tools, drag-and-drop works - Write brief README for the new architecture --- ## Key Technical Decisions ### State Management - **React Context** (not Redux) — app is small enough, context + useReducer is perfect - `GHLContext` holds: current UITree, form values, loading states, selected entities - Any component can `const { callTool } = useGHL()` to interact with the MCP server ### Tool Calling Pattern ```tsx // Any component can call MCP tools: const { callTool, isLoading } = useCallTool(); const handleDrop = async (cardId: string, newStageId: string) => { await callTool('update_opportunity', { opportunityId: cardId, pipelineStageId: newStageId, }); }; ``` ### Drag & Drop (KanbanBoard) - Pure React state — no global DOM event handlers - `onDragStart`, `onDragOver`, `onDrop` on React elements - Optimistic UI update (move card immediately, revert on error) ### Dynamic Sizing - `useSizeReporter` hook — ResizeObserver on `#app` - Sends `ui/notifications/size-changed` on every size change - Caps at 600px height ### CSS Strategy - Plain CSS files (not CSS modules, not Tailwind) — keeps bundle simple - Same compact sizing as current (12px base, tight padding) - All in `styles/` directory, imported in App.tsx - Interactive styles (drag states, modals, toasts) in separate file ### Build Pipeline ```bash # In src/ui/react-app/ npm run build # → Vite builds React app → vite-plugin-singlefile → single HTML file # → Output: ../../dist/app-ui/dynamic-view.html # In GoHighLevel-MCP root npm run build # → Builds React UI first, then compiles TypeScript server ``` --- ## Timeline Estimate | Phase | Agents | Est. Time | Depends On | |-------|--------|-----------|------------| | Phase 1: Foundation | Alpha (1) | ~20 min | — | | Phase 2: Components | Bravo, Charlie, Delta, Echo (4 parallel) | ~25 min | Phase 1 | | Phase 3: Integration | Foxtrot (1) | ~15 min | Phase 2 | | **Total** | **6 agents** | **~60 min** | | --- ## Success Criteria 1. ✅ All 42 existing components render identically to current version 2. ✅ JSON UI trees from Claude work without any format changes 3. ✅ KanbanBoard drag-and-drop moves deals and persists via `update_opportunity` 4. ✅ ContactPicker fetches real contacts from GHL on keystroke 5. ✅ InvoiceBuilder creates invoices with real contact data 6. ✅ EditableField saves changes via appropriate MCP tool 7. ✅ Dynamic sizing works — views fit in chat 8. ✅ Single HTML file output (vite-plugin-singlefile) 9. ✅ ext-apps handshake completes with Goose 10. ✅ All existing `view_*` tools still work alongside `generate_ghl_view`