Skip to main content

implementing-mcp-ui-apps

Guide for adding MCP UI apps — interactive visualizations that render tool results in MCP clients like Claude Desktop. Use when adding a new detail or list view for an MCP tool, creating view components in products/*/mcp/apps/, or linking tools to UI apps via YAML.

Datos de origen

Repositorio
PostHog/posthog
Última actividad en el origen
14 de julio de 2026 a las 09:12
Idioma detectado de SKILL.md
inglés
Estrellas
39.613
Forks
3339

Opciones de instalación

De forma predeterminada está seleccionado el prompt que primero revisa el origen. Puedes cambiar a un comando directo o descargar una copia local.

Revisa los archivos de origen

Lee SKILL.md y los archivos complementarios que muestra SkillsMP antes de decidir si quieres instalarlo.

Mostrando SKILL.md

SKILL.md
Instrucciones de origen · Vista previa de solo lectura
name
implementing-mcp-ui-apps
description
Guide for adding MCP UI apps — interactive visualizations that render tool results in MCP clients like Claude Desktop. Use when adding a new detail or list view for an MCP tool, creating view components in products/*/mcp/apps/, or linking tools to UI apps via YAML.
# Implementing MCP UI apps MCP UI apps are interactive React visualizations that render alongside tool results in MCP clients (e.g. Claude Desktop). They're built with the Mosaic component library and served via Cloudflare Workers Static Assets. Full reference: [services/mcp/CONTRIBUTING.md](../../../services/mcp/CONTRIBUTING.md). ## Quick workflow ```sh # 1. Create view components in your product's mcp/apps/ directory # (see "View components" below) # 2. Add ui_apps entries to your product's mcp/tools.yaml # (see "YAML configuration" below) # 3. Link tools to apps with ui_app: <key> in the tools section # 4. Generate entry points + registry, then build pnpm --filter=@posthog/mcp run generate:ui-apps pnpm --filter=@posthog/mcp run build ``` ## When to add a UI app When an MCP tool returns structured data that benefits from visual presentation — tables, detail views, charts, status badges, etc. Without a UI app, tool results are shown as plain text/JSON in the chat. ## Architecture ```filesystem products/{product}/mcp/ apps/ # React view components (you write these) EntityView.tsx # Detail view EntityListView.tsx # List view (uses ListDetailView from Mosaic) index.ts # Barrel exports tools.yaml # YAML config: ui_apps + tools services/mcp/ src/ui-apps/apps/ generated/ # Auto-generated entry points (don't edit) debug.tsx # Custom/manual entry points query-results.tsx src/resources/ ui-apps.generated.ts # Auto-generated: URI constants, UiAppKey, URI_MAP, UI_APPS ui-apps.ts # Hand-authored: withUiApp(), registerUiAppResources() scripts/ generate-ui-apps.ts # The generator — reads YAML, writes entry points + registry yaml-config-schema.ts # Zod schemas for YAML validation (source of truth for field definitions) ``` ## View components Place view components in `products/{product}/mcp/apps/`. **Detail view** — renders a single entity: ```tsx import { type ReactElement } from 'react' import { DescriptionList } from '@posthog/mcp-ui' import { Card, CardHeader, CardTitle, CardContent } from '@posthog/quill' export interface MyEntityData { id: number name: string // ... fields from the API response } export function MyEntityView({ data }: { data: MyEntityData }): ReactElement { return ( <Card> <CardHeader> <CardTitle>{data.title}</CardTitle> </CardHeader> <CardContent> <DescriptionList items={[{ label: 'ID', value: String(data.id) }]} /> </CardContent> </Card> ) } ``` **List view** — uses `ListDetailView` from Mosaic for the list-to-detail state machine: ```tsx import { type ReactElement, type ReactNode } from 'react' import { DataTable, type DataTableColumn, ListDetailView } from '@posthog/mcp-ui' import { MyEntityView, type MyEntityData } from './MyEntityView' export interface MyEntityListData { results: MyEntityData[] _posthogUrl?: string } export interface MyEntityListViewProps { data: MyEntityListData onMyEntityClick?: (entity: MyEntityData) => Promise<MyEntityData | null> } export function MyEntityListView({ data, onMyEntityClick }: MyEntityListViewProps): ReactElement { return ( <ListDetailView<MyEntityData> onItemClick={onMyEntityClick} backLabel="All entities" getItemName={(e) => e.name} renderDetail={(e) => <MyEntityView data={e} />} renderList={(handleClick) => { const columns: DataTableColumn<MyEntityData>[] = [ { key: 'name', header: 'Name', sortable: true, render: (row): ReactNode => onMyEntityClick ? ( <button onClick={() => handleClick(row)} className="text-link underline ..."> {row.name} </button> ) : ( row.name ), }, ] return ( <div className="p-4"> <div className="flex flex-col gap-2"> <div className="flex items-center justify-between"> <span className="text-sm text-muted-foreground"> {data.results.length} entit{data.results.length === 1 ? 'y' : 'ies'} </span> </div> <DataTable<CohortData> columns={columns} data={data.results} pageSize={10} defaultSort={{ key: 'name', direction: 'asc' }} emptyMessage="No entities found" /> </div> </div> ) }} /> ) } ``` **Barrel export** (`index.ts`): ```ts export { MyEntityView, type MyEntityData } from './MyEntityView' export { MyEntityListView, type MyEntityListData, type MyEntityListViewProps } from './MyEntityListView' ``` ## YAML configuration The `ui_apps` section in `products/{product}/mcp/tools.yaml` defines UI apps. Each key becomes the app identifier (used in URIs, constants, and `withUiApp` calls). There are three app types: `detail`, `list`, and `custom`. ### `type: detail` — single-entity view Renders one entity using a view component wrapped in `AppWrapper`. **Required fields:** | Field | Description | | ----------- | ---------------------------------------------------------------------------------------------------------------------------------------- | | `type` | Must be `'detail'`. | | `view_prop` | The React prop name passed to the view component (e.g. `data`, `action`, `flag`). Cannot be derived — must match your component's props. | **Optional fields** (derived by convention when omitted): | Field | Default | Description | | ------------------ | --------------------------------- | -------------------------------------------------------------------------------------------------------- | | `app_name` | `"PostHog " + titleCase(key)` | Display name shown in the MCP client. Example: key `error-details` → `"PostHog Error Details"`. | | `description` | `titleCase(key) + " detail view"` | Short description for the MCP resource registry. | | `component_import` | `products/{product}/mcp/apps` | Import path for the view component. Auto-derived from the YAML file's location in the product directory. | | `data_type` | `PascalCase(key) + "Data"` | TypeScript type for the tool result. Example: key `error-details` → `ErrorDetailsData`. | | `view_component` | `PascalCase(key) + "View"` | React component name. Example: key `error-details` → `ErrorDetailsView`. | **Minimal example:** ```yaml ui_apps: action: type: detail view_prop: action ``` **Example with overrides** (when conventions don't match the actual code): ```yaml ui_apps: llm-costs: type: detail view_prop: data data_type: LLMCostsData # convention would produce LlmCostsData view_component: LLMCostsView # convention would produce LlmCostsView ``` ### `type: list` — list with drill-down Renders a list component. When an item is clicked, calls a detail tool via `app.callServerTool()` and shows the detail view inline. Falls back to a chat message if the MCP client doesn't support tool calls from apps. **Required fields:** | Field | Description | | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `type` | Must be `'list'`. | | `detail_tool` | Tool name to call when a list item is clicked (e.g. `'action-get'`, `'cohorts-retrieve'`). Must be a valid tool name defined in the `tools` section of any YAML file. | **Optional fields with behavioral defaults:** | Field | Default | Description | | ----------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `detail_args` | `'{ id: item.id }'` | JS expression for arguments passed to `detail_tool`. The variable `item` refers to the clicked list item. Override when the tool uses a different param name, e.g. `'{ flagId: item.id }'`. | | `item_name_field` | `'name'` | Field on the item object used for display in loading states and fallback chat messages. Override when items are identified by something other than `name`, e.g. `key` for feature flags. |
Ver en GitHub
Este SKILL.md es muy grande, por eso SkillsMP muestra aqui solo la primera seccion. Ver en GitHub