=== DONE === - MCP Apps UI system added (11 apps with _meta.ui.resourceUri) - 19 new tool modules added - Tool count: 269 → 461 across 38 categories - Upstream changes merged - All tools tagged with _meta labels - Package lock updated === TO-DO === - [ ] Fix 42 failing edge case tests (BLOCKER — Stage 11) - [ ] Live API testing with GHL credentials - [ ] App design approval for Stage 7→8
14 KiB
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 │ │
│ │ ↓ │ │
│ │ <UITreeRenderer tree={uiTree} /> │ │
│ │ - 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.tsxwithuseApphook — handles ontoolresult, ontoolinput, host context - Build
GHLContext.tsx— React context providing uiTree, formState, callTool - Build
useCallTool.ts— wrapper aroundapp.callServerToolwith loading/error states - Build
useFormState.ts— shared form state hook - Build
useSizeReporter.ts— auto-measures content, sendsui/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.jsonto 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_contactson 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-viewto serve React build - Add new resource URIs if needed
- Add new tool definitions for interactive components (
- Update
src/server.tsif new tools need routing - Update system prompt: add new interactive component catalog entries
- Update Goose config
available_toolsif 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
GHLContextholds: current UITree, form values, loading states, selected entities- Any component can
const { callTool } = useGHL()to interact with the MCP server
Tool Calling Pattern
// 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,onDropon React elements- Optimistic UI update (move card immediately, revert on error)
Dynamic Sizing
useSizeReporterhook — ResizeObserver on#app- Sends
ui/notifications/size-changedon 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
# 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
- ✅ All 42 existing components render identically to current version
- ✅ JSON UI trees from Claude work without any format changes
- ✅ KanbanBoard drag-and-drop moves deals and persists via
update_opportunity - ✅ ContactPicker fetches real contacts from GHL on keystroke
- ✅ InvoiceBuilder creates invoices with real contact data
- ✅ EditableField saves changes via appropriate MCP tool
- ✅ Dynamic sizing works — views fit in chat
- ✅ Single HTML file output (vite-plugin-singlefile)
- ✅ ext-apps handshake completes with Goose
- ✅ All existing
view_*tools still work alongsidegenerate_ghl_view