diff --git a/specification/screenshots/apps-tab-app.png b/specification/screenshots/apps-tab-app.png new file mode 100644 index 00000000..71a1fec0 Binary files /dev/null and b/specification/screenshots/apps-tab-app.png differ diff --git a/specification/screenshots/apps-tab-form.png b/specification/screenshots/apps-tab-form.png new file mode 100644 index 00000000..484b32f5 Binary files /dev/null and b/specification/screenshots/apps-tab-form.png differ diff --git a/specification/v2_screenshots.md b/specification/v2_screenshots.md index 1fc79545..3277baf1 100644 --- a/specification/v2_screenshots.md +++ b/specification/v2_screenshots.md @@ -51,6 +51,18 @@ Tool list with annotations (user, read-only, destructive, long-running), paramet ![Tools Screen](screenshots/shadcn-tools.png) +### Apps Screen + +The subset of tools that are MCP Apps — tools with `_meta.ui.resourceUri`. Two-panel layout: list of apps on the left, input form (before launch) or embedded app iframe (after launch) on the right. + +**Input form state** (from legacy v1 reference): + +![Apps Screen — Input Form](screenshots/apps-tab-form.png) + +**Running app state** (from legacy v1 reference): + +![Apps Screen — Running App](screenshots/apps-tab-app.png) + ### Resources Screen Accordion layout with Resources, Templates, and Subscriptions sections. diff --git a/specification/v2_ux.md b/specification/v2_ux.md index df050840..0630d110 100644 --- a/specification/v2_ux.md +++ b/specification/v2_ux.md @@ -13,6 +13,7 @@ * [Server Info (Connected)](#server-info-connected) * [Feature Screens](#feature-screens) * [Tools Screen](#tools-screen) + * [Apps Screen](#apps-screen) * [Resources Screen](#resources-screen) * [Prompts Screen](#prompts-screen) * [Logging Screen](#logging-screen) @@ -40,7 +41,7 @@ ``` ┌─────────────────────────────────────────────────────────────────────────────────────────┐ -│ [Server Name ▼] ● Connected (23ms) [Tools] [Resources] [Prompts] [Logs] [Tasks] [History] [Disconnect] │ +│ [Server Name ▼] ● Connected (23ms) [Tools] [Apps] [Resources] [Prompts] [Logs] [Tasks] [History] [Disconnect] │ ├─────────────────────────────────────────────────────────────────────────────────────────┤ │ │ │ Full-width content │ @@ -49,7 +50,7 @@ ``` - **Menu bar** displays server name, connection status with latency, and disconnect button when connected -- **Top navigation** links to Tools, Resources, Prompts, Logs, Tasks, and History sections +- **Top navigation** links to Tools, Apps, Resources, Prompts, Logs, Tasks, and History sections - **Connection indicator** shows latency from periodic `ping` requests - **No sidebar** - content areas use full width @@ -399,6 +400,95 @@ When a tool execution triggers sampling or elicitation requests, they appear inl - Audio player for audio content (base64) - Resource links displayed as clickable references +#### Apps Screen + +A two-panel layout showing the subset of tools that are **MCP Apps** — tools whose +metadata advertises a UI resource via `_meta.ui.resourceUri` (or the deprecated +flat `_meta["ui/resourceUri"]`). The right panel toggles between an input form +(before launch) and the embedded app (after launch). + +**Input Form State:** + +``` +┌──────────────────────────┬──────────────────────────────────────────────┐ +│ MCP Apps (1) 🔍 │ get-cohort-data ✕ │ +│ [Refresh Apps] │ Returns cohort retention heatmap data... │ +├──────────────────────────┼──────────────────────────────────────────────┤ +│ │ │ +│ ▸ get-cohort-data │ ┌──────────────────────────────────────────┐ │ +│ Returns cohort │ │ App Input │ │ +│ retention heatmap... │ │ │ │ +│ │ │ metric │ │ +│ │ │ ┌──────────────────────────────────────┐ │ │ +│ │ │ │ retention ▾ │ │ │ +│ │ │ └──────────────────────────────────────┘ │ │ +│ │ │ │ │ +│ │ │ periodType │ │ +│ │ │ ┌──────────────────────────────────────┐ │ │ +│ │ │ │ monthly ▾ │ │ │ +│ │ │ └──────────────────────────────────────┘ │ │ +│ │ │ │ │ +│ │ │ cohortCount │ │ +│ │ │ ┌──────────────────────────────────────┐ │ │ +│ │ │ │ 12 │ │ │ +│ │ │ └──────────────────────────────────────┘ │ │ +│ │ │ │ │ +│ │ │ maxPeriods │ │ +│ │ │ ┌──────────────────────────────────────┐ │ │ +│ │ │ │ 12 │ │ │ +│ │ │ └──────────────────────────────────────┘ │ │ +│ │ │ │ │ +│ │ │ [▶ Open App] │ │ +│ │ └──────────────────────────────────────────┘ │ +└──────────────────────────┴──────────────────────────────────────────────┘ +``` + +**Running App State:** + +After "Open App" the right panel swaps to the embedded MCP App. The input +form is replaced by the app iframe; a "Back to Input" button (only when the +selected app has input fields) returns to the form. + +``` +┌──────────────────────────┬──────────────────────────────────────────────┐ +│ MCP Apps (1) 🔍 │ get-cohort-data ⛶ ✕ │ +│ [Refresh Apps] │ [Back to Input] │ +├──────────────────────────┼──────────────────────────────────────────────┤ +│ ▸ get-cohort-data │ ┌──────────────────────────────────────────┐ │ +│ Returns cohort │ │ │ │ +│ retention heatmap... │ │ [Embedded MCP App] │ │ +│ │ │ │ │ +│ │ │ Cohort Retention Analysis Metric: ▾ │ │ +│ │ │ │ │ +│ │ │ May 2025 100 85 72 ... │ │ +│ │ │ Jun 2025 100 85 78 ... │ │ +│ │ │ ... │ │ +│ │ │ │ │ +│ │ └──────────────────────────────────────────┘ │ +└──────────────────────────┴──────────────────────────────────────────────┘ +``` + +**Features:** +- **App Detection** — A tool is treated as an App when `getToolUiResourceUri(tool)` + returns a defined `ui://...` URI (helper from `@modelcontextprotocol/ext-apps`). + Filtering happens upstream (wiring layer); the screen receives an already-filtered + `tools: Tool[]` array of apps. +- **List Changed Indicator** — Shows when `notifications/tools/list_changed` received. +- **Refresh Apps** button re-runs `tools/list`. +- Searchable list, mirroring the Tools screen sidebar. +- **App icons** rendered from `tool.icons` when present. +- **App Input form** generated from `tool.inputSchema` (same `SchemaForm` used by + Tools screen). +- **Open App** button: + - If the tool has input fields, the form must be filled before launch. + - If the tool has no input fields, "Open App" runs immediately on selection. +- **Back to Input** returns to the form (only visible when the app has fields). +- **Maximize / Minimize** toggle hides the sidebar to give the app the full viewport. +- **App Renderer** embeds the UI resource in a sandboxed iframe and bridges + it to the active MCP server via `AppBridge`. Tool input and result are sent + to the app via `sendToolInput` / `sendToolResult`. +- **Close (✕)** deselects the app and clears any in-flight launch. + #### Resources Screen ``` diff --git a/specification/v2_ux_features.md b/specification/v2_ux_features.md index 94fa518f..203b0657 100644 --- a/specification/v2_ux_features.md +++ b/specification/v2_ux_features.md @@ -9,6 +9,7 @@ Each feature screen uses a **resizable panel layout** for flexibility. ## Table of Contents * [Tools Screen](#tools-screen) + * [Apps Screen](#apps-screen) * [Resources Screen](#resources-screen) * [Prompts Screen](#prompts-screen) * [Logging Screen](#logging-screen) @@ -124,6 +125,111 @@ When a tool execution triggers sampling or elicitation requests, they appear inl - Audio player for audio content (base64) - Resource links displayed as clickable references +## Apps Screen + +A two-panel layout showing the subset of tools that are **MCP Apps** — tools +whose metadata advertises a UI resource via `_meta.ui.resourceUri` (or the +deprecated flat `_meta["ui/resourceUri"]`). The right panel toggles between an +input form (before launch) and the embedded app (after launch). + +``` ++--------------------------+----------------------------------------------+ +| MCP Apps (1) [Q] | get-cohort-data [x] | +| [Refresh Apps] | Returns cohort retention heatmap data | ++--------------------------+----------------------------------------------+ +| | | +| > get-cohort-data | +------------------------------------------+ | +| Returns cohort | | App Input | | +| retention heatmap... | | | | +| | | metric | | +| | | +--------------------------------------+ | | +| | | | retention v | | | +| | | +--------------------------------------+ | | +| | | | | +| | | periodType | | +| | | +--------------------------------------+ | | +| | | | monthly v | | | +| | | +--------------------------------------+ | | +| | | | | +| | | cohortCount | | +| | | +--------------------------------------+ | | +| | | | 12 | | | +| | | +--------------------------------------+ | | +| | | | | +| | | maxPeriods | | +| | | +--------------------------------------+ | | +| | | | 12 | | | +| | | +--------------------------------------+ | | +| | | | | +| | | [> Open App] | | +| | +------------------------------------------+ | ++--------------------------+----------------------------------------------+ +``` + +**Running App State:** + +After "Open App" the right panel swaps to the embedded MCP App. The input form +is replaced by the app iframe. A "Back to Input" button is shown when the +selected app has input fields; it returns to the form. A Maximize toggle hides +the sidebar so the app takes the full viewport. + +``` ++--------------------------+----------------------------------------------+ +| MCP Apps (1) [Q] | get-cohort-data [^] [x] | +| [Refresh Apps] | [Back to Input] | ++--------------------------+----------------------------------------------+ +| > get-cohort-data | +------------------------------------------+ | +| Returns cohort | | | | +| retention heatmap... | | [Embedded MCP App] | | +| | | | | +| | | Cohort Retention Analysis Metric: [v] | | +| | | | | +| | | May 2025 100 85 72 ... | | +| | | Jun 2025 100 85 78 ... | | +| | | ... | | +| | | | | +| | +------------------------------------------+ | ++--------------------------+----------------------------------------------+ +``` + +**Features:** + +- **App Detection** — A tool is treated as an App when `getToolUiResourceUri(tool)` + returns a defined `ui://...` URI (helper from `@modelcontextprotocol/ext-apps`). + The detection helper is wrapped in `core/mcp/apps.ts` (e.g. `isAppTool(tool)`) + so all clients (web, CLI, TUI) share one definition. Filtering happens upstream + (wiring layer); the screen receives an already-filtered `tools: Tool[]` array. +- **List Changed Indicator** — shows when `notifications/tools/list_changed` + received; same component as the Tools screen. +- **Refresh Apps** re-runs `tools/list`. +- **Searchable list**, mirroring the Tools screen sidebar. +- **App icons** rendered from `tool.icons` (real MCP `Tool.icons`) when present. +- **App Input form** generated from `tool.inputSchema` via the same `SchemaForm` + used by the Tools screen. +- **Open App** button: + - If the tool has input fields, the form is filled before launch. + - If the tool has no input fields, "Open App" runs immediately on selection. +- **Back to Input** returns to the form (visible only when the app has fields). +- **Maximize / Minimize** toggles full-viewport mode by hiding the sidebar. +- **App Renderer** embeds the UI resource in a sandboxed iframe and bridges + it to the active MCP server via the `AppBridge` from + `@modelcontextprotocol/ext-apps`. The host pushes tool input and results to + the app via `sendToolInput` / `sendToolResult` through an imperative ref. +- **Close (x)** deselects the app and clears any in-flight launch. + +**Component contract (presentational):** + +The screen and its sub-components stay "dumb": they accept data + callbacks + +imperative refs and contain only display logic. + +- `AppsScreen` props: `tools: Tool[]` (already filtered to apps), `listChanged`, + `onRefreshList`, `onSelectApp`, `onOpenApp(name, args)`, `onCloseApp`, plus a + ref for the embedded `AppRenderer`. +- `AppRenderer` exposes an imperative ref with `sendToolInput`, + `sendToolResult`, `sendToolCancelled`, `teardown`. The wiring layer creates + the `AppBridge` from the active MCP `Client` (real MCP type) and drives the + ref; the renderer itself only owns the iframe and the bridge handle. + ## Resources Screen Uses an **accordion layout** for the left panel to organize Resources, Templates, and Subscriptions into collapsible sections. diff --git a/specification/v2_ux_storybook.md b/specification/v2_ux_storybook.md index c1c2ae00..f0907d16 100644 --- a/specification/v2_ux_storybook.md +++ b/specification/v2_ux_storybook.md @@ -334,6 +334,24 @@ Single tool entry in the sidebar list with name and annotation badges. --- +### 2.3a AppListItem + +Single MCP App entry in the sidebar list. Mirrors `ToolListItem` but renders the app's `tool.icons` (real MCP `Tool.icons`) when present and surfaces a chevron affordance. + +| Prop | Type | Description | +|------|------|-------------| +| `tool` | `Tool` | App tool (real MCP `Tool` type) | +| `selected` | `boolean` | Whether this app is currently selected | +| `onClick` | `() => void` | Selection callback | + +**Mantine:** `UnstyledButton` with `Group` (Icon + label/description + ChevronRight). Reuses the same active-state background highlight pattern as `ToolListItem`. + +**Stories:** Default, Selected, WithIcon, NoDescription, LongName + +> Alternative: fold icon support into `ToolListItem` and reuse it on the Apps screen. Decide during implementation — see issue "Tool icon support on tool list items". + +--- + ### 2.4 ToolDetailPanel Tool name, description, annotations display, and generated parameter form. @@ -373,6 +391,50 @@ Displays tool execution results with copy/clear actions. --- +### 2.5a AppDetailPanel + +App name, description, optional icon, and generated `App Input` form. Mirrors `ToolDetailPanel` but the action button is **Open App** (no annotations, no progress display, no cancel — execution is delegated to the embedded app). + +| Prop | Type | Description | +|------|------|-------------| +| `tool` | `Tool` | App tool (real MCP `Tool` type) | +| `formValues` | `Record` | Current form values | +| `isOpening` | `boolean` | Whether the app is currently being launched | +| `onFormChange` | `(values: Record) => void` | Form value change | +| `onOpenApp` | `() => void` | Open App callback | + +**Mantine:** `Stack` with icon + `Title` (`tool.title ?? tool.name`), description `Text`, SchemaForm (see 2.12), full-width `Button` with `IconPlayerPlay` for open. Disabled while `isOpening` or while the form has validation errors. + +**Stories:** NoFields, SimpleStringParam, MultipleParams, WithIcon, Opening, ComplexSchema + +--- + +### 2.5b AppRenderer + +Embeds an MCP App in a sandboxed iframe and exposes an imperative ref so the wiring layer can drive the bridge. The component is a thin shell — it owns the iframe element and the `AppBridge` lifecycle but does **not** know about the active MCP `Client`. The wiring layer constructs the bridge (real MCP `Client` + `Implementation` + `McpUiHostCapabilities`) and passes it in via the `bridgeFactory` callback when the iframe mounts. + +| Prop | Type | Description | +|------|------|-------------| +| `sandboxPath` | `string` | URL/path of the sandbox proxy iframe | +| `tool` | `Tool` | The selected app tool (for resource URI lookup) | +| `bridgeFactory` | `(iframe: HTMLIFrameElement) => AppBridge` | Wiring layer creates the bridge from the active MCP client | +| `onError` | `(err: Error) => void` | Error notification callback | + +**Imperative ref (`AppRendererHandle`):** + +| Method | Description | +|--------|-------------| +| `sendToolInput(args)` | Push complete tool arguments to the app | +| `sendToolResult(result)` | Push the `CallToolResult` from the server | +| `sendToolCancelled(reason)` | Notify the app that execution was cancelled | +| `teardown()` | Request graceful shutdown before unmounting | + +**Mantine:** `Box` with `iframe` element. No Mantine controls — display logic only. The renderer treats the bridge handle and iframe element as runtime state, not props, but exposes the bridge actions through `useImperativeHandle`. + +**Stories:** Loading, Loaded (with mock bridge), Error, Maximized + +--- + ### 2.6 ResourceListItem Single resource entry with name and annotation badges. @@ -855,6 +917,28 @@ Three-panel layout: tool list, detail/form, and results. --- +### 3.2a AppsScreen + +Two-panel layout: app list on the left, dynamic right pane that toggles between an input form (before launch) and the embedded `AppRenderer` (after launch). Receives the **already-filtered** subset of tools that are MCP Apps; filtering happens in the wiring layer via the shared `isAppTool` helper in `core/mcp/apps`. + +| Prop | Type | Description | +|------|------|-------------| +| `tools` | `Tool[]` | Apps (already filtered to those with `_meta.ui.resourceUri`) | +| `listChanged` | `boolean` | List changed indicator | +| `sandboxPath` | `string` | Sandbox proxy path passed through to `AppRenderer` | +| `bridgeFactory` | `(iframe) => AppBridge` | Wiring layer creates the bridge from the active MCP client | +| `rendererRef` | `Ref` | Imperative ref forwarded to the embedded `AppRenderer` | +| `onRefreshList` | `() => void` | Refresh apps list | +| `onSelectApp` | `(name: string) => void` | App selection callback | +| `onOpenApp` | `(name: string, args: Record) => void` | Open App callback (wiring runs `tools/call` and pushes result via the ref) | +| `onCloseApp` | `() => void` | Close currently-open app | + +**Mantine:** Two-column layout. Left: `Card` with ListChangedIndicator, search `TextInput`, AppListItem stack, "Refresh Apps" `Button`. Right: `Card` whose content swaps between AppDetailPanel (form state) and AppRenderer (running state) plus a "Back to Input" / Maximize / Close header. Maximize hides the sidebar by widening the right card to full width. + +**Stories:** NoSelection, AppSelected, AppRunning, AppRunningMaximized, NoFieldsApp (auto-launches on select), WithListChanged, Empty (no apps available) + +--- + ### 3.3 ResourcesScreen Two-panel layout: resource list (with accordion sections) and content preview. @@ -1022,7 +1106,8 @@ Depends on Phase 1 elements being complete. 2. **ServerCard** (2.1) - Uses StatusIndicator, TransportBadge, ConnectionToggle, CopyButton, InlineError 3. **AddServerMenu** (2.2) 4. **ToolListItem** (2.3) - Uses AnnotationBadge -5. **ResourceListItem** (2.6) - Uses AnnotationBadge +5. **AppListItem** (2.3a) - Renders `tool.icons` (or fold into ToolListItem) +6. **ResourceListItem** (2.6) - Uses AnnotationBadge 6. **LogControls** (2.11) 7. **ResultPanel** (2.5) - Uses ContentViewer, CopyButton 8. **PromptMessagesDisplay** (2.10) - Uses MessageBubble @@ -1033,17 +1118,19 @@ Depends on Phase 1 elements being complete. Depends on SchemaForm and Phase 2. 1. **ToolDetailPanel** (2.4) - Uses SchemaForm, AnnotationBadge, ProgressDisplay -2. **ResourcePreviewPanel** (2.7) - Uses ContentViewer -3. **ResourceTemplateInput** (2.8) -4. **PromptArgumentsForm** (2.9) -5. **TaskCard** (2.13) - Uses ProgressDisplay -6. **HistoryEntry** (2.14) -7. **SamplingRequestPanel** (2.15) - Uses MessageBubble -8. **ElicitationFormPanel** (2.16) - Uses SchemaForm -9. **ElicitationUrlPanel** (2.17) -10. **ServerSettingsForm** (2.20) -11. **ImportServerJsonPanel** (2.21) -12. **ExperimentalFeaturesPanel** (2.22) +2. **AppDetailPanel** (2.5a) - Uses SchemaForm +3. **AppRenderer** (2.5b) - Imperative ref shell for embedded MCP App; depends on `@modelcontextprotocol/ext-apps` +4. **ResourcePreviewPanel** (2.7) - Uses ContentViewer +5. **ResourceTemplateInput** (2.8) +6. **PromptArgumentsForm** (2.9) +7. **TaskCard** (2.13) - Uses ProgressDisplay +8. **HistoryEntry** (2.14) +9. **SamplingRequestPanel** (2.15) - Uses MessageBubble +10. **ElicitationFormPanel** (2.16) - Uses SchemaForm +11. **ElicitationUrlPanel** (2.17) +12. **ServerSettingsForm** (2.20) +13. **ImportServerJsonPanel** (2.21) +14. **ExperimentalFeaturesPanel** (2.22) ### Phase 4: Inline Handlers Depends on Phase 3 panels. @@ -1057,11 +1144,12 @@ Depends on all groups. 1. **ServerListScreen** (3.1) 2. **ToolsScreen** (3.2) -3. **ResourcesScreen** (3.3) -4. **PromptsScreen** (3.4) -5. **LoggingScreen** (3.5) -6. **TasksScreen** (3.6) -7. **HistoryScreen** (3.7) +3. **AppsScreen** (3.2a) +4. **ResourcesScreen** (3.3) +5. **PromptsScreen** (3.4) +6. **LoggingScreen** (3.5) +7. **TasksScreen** (3.6) +8. **HistoryScreen** (3.7) ### Phase 6: Views Depends on screens.