Skip to content

WIP: Add MCP Apps interactive UI extensions#920

Draft
manusa wants to merge 12 commits intocontainers:mainfrom
marcnuri-forks:feat/mcp-apps
Draft

WIP: Add MCP Apps interactive UI extensions#920
manusa wants to merge 12 commits intocontainers:mainfrom
marcnuri-forks:feat/mcp-apps

Conversation

@manusa
Copy link
Copy Markdown
Member

@manusa manusa commented Mar 13, 2026

Summary

Add MCP Apps support as an opt-in feature (--apps flag / apps_enabled config) that enables tools to return interactive HTML-based UIs rendered in sandboxed iframes by MCP hosts (VS Code, Claude Desktop, ChatGPT, etc.).

Viewer built with Preact + HTM + Chart.js + Prism.js (~230 KB, no build step), served via embed.FS with per-tool ui:// resource URIs.

Closes #753

What's done

  • Infrastructure: Config, CLI flag, server capability negotiation, per-tool resource registration
  • Viewer: Dual-flow (spec-compliant tool-result + fallback tool-input), SortableTable, MetricsTable (Chart.js), YamlView (Prism.js), GenericView, theming with light-dark() CSS fallbacks
  • Output layer: PrintObjStructured on Output interface, tableToStructured for Kubernetes Table API, ensureStructuredObject array wrapping
  • Tools with structured content: pods_list, pods_list_in_namespace, pods_top, nodes_top, namespaces_list, projects_list, resources_list
  • Compatibility: VS Code pre-fetch race (resources registered before tools), both nested and flat _meta.ui keys
  • Tests: Unit tests (mcpapps, output, API) + browser integration tests (Rod)

What's pending

  • Structured content for remaining non-list tool handlers (YAML tools)
  • Refinement of table output rendering
  • Reanalyze overall effect of structured outputs on LLM performance
  • Refresh via callServerTool(), height management, filtering/pagination
  • Edge case handling (empty results, large datasets)
  • Documentation updates (docs/configuration.md for apps_enabled)

@manusa manusa force-pushed the feat/mcp-apps branch 2 times, most recently from ef72fed to 324beec Compare March 13, 2026 11:24
@manusa manusa force-pushed the feat/mcp-apps branch 4 times, most recently from 7da0b28 to 4a8603d Compare March 24, 2026 14:45
manusa and others added 11 commits March 25, 2026 09:50
Add comprehensive living specification for the MCP Apps integration
(issue containers#753). Covers architecture, configuration, frontend stack
decisions, MCP postMessage protocol, output layer design, per-tool
resource URIs, VS Code compatibility, and YAML syntax highlighting.

Signed-off-by: Marc Nuri <marc@marcnuri.com>
Add MCP Apps support as an opt-in feature (--apps flag, apps_enabled
config). When enabled, tools return structuredContent alongside text
and expose per-tool ui:// resource URIs for interactive HTML viewers.

Infrastructure:
- AppsEnabled config field with CLI flag and TOML support
- Server capability negotiation (io.modelcontextprotocol/ui extension)
- Per-tool resource registration with VS Code pre-fetch compatibility
- WithAppsMeta() tool mutator for centralized _meta.ui injection

Viewer (Preact + HTM + Chart.js, no build step):
- MCP postMessage protocol implementation (~80 lines)
- Dual-flow viewer: spec-compliant tool-result + fallback tool-input
- SortableTable, TableView, MetricsTable, GenericView components
- CSS theming with light-dark() fallbacks for host integration

Output layer:
- PrintObjStructured() on Output interface for generic extraction
- NewToolCallResultFull() constructor for text + structured content
- ensureStructuredObject() wraps arrays in {"items": [...]} per spec
- tableToStructured() converts Kubernetes Table API to []map[string]any

Signed-off-by: Marc Nuri <marc@marcnuri.com>
Add self-describing structured content for metrics tools and browser
integration tests for the MCP Apps viewer.

Metrics:
- extractNodesTopStructured() with CPU/memory and percentage utilization
- Self-describing format with columns, chart config, and items

Browser tests (Rod + wrapper iframe):
- Protocol handshake, table rendering, sorting, metrics charts
- YAML highlighting, theme application, data routing
- Items envelope unwrapping, error handling

Signed-off-by: Marc Nuri <marc@marcnuri.com>
Add Prism.js (core + YAML grammar, ~9.4 KB) for syntax-highlighted
YAML output in the MCP Apps viewer. Tools returning Kubernetes resource
YAML now render with color-coded keys, strings, numbers, and comments.

- YamlView component with Prism.highlight() and useMemo
- looksLikeYaml() heuristic for Kubernetes resource detection
- Custom theme CSS using light-dark() for host theme integration
- Prism.manual = true to prevent auto-highlighting

Signed-off-by: Marc Nuri <marc@marcnuri.com>
Add Section 12 documenting the content vs structuredContent model
context visibility contract. structuredContent is for UI rendering
only — the model sees only the content field. References ext-apps
spec, SEP-1624, and PR #2200. Includes current client behavior
matrix, eval accountability steps, and open questions.

Signed-off-by: Marc Nuri <marc@marcnuri.com>
- Guard ensureStructuredObject against invalid reflect.Value on nil
- Fix stale closure in tool-input handler by using useRef for toolName
- Use LoadOrStore in ViewerHTMLForTool to avoid redundant replacement
- Unify nodes_top/pods_top to use NewToolCallResultFull consistently
- Document postMessage wildcard origin and items unwrapping heuristic
- Update spec code snippets to match implementation

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Marc Nuri <marc@marcnuri.com>
Replace Prism.highlight() with Prism.tokenize() + custom renderTokens()
that explicitly HTML-escapes all text segments. This removes reliance on
Prism's internal encode() as the sole XSS barrier for
dangerouslySetInnerHTML. Add browser tests verifying script injection,
attribute injection, and span-breaking payloads are escaped.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Marc Nuri <marc@marcnuri.com>
Add TestScreenshots to the browser suite capturing PNGs of every view
(table, metrics, YAML, generic) in light and dark themes. Screenshots
are saved to _output/screenshots/ for offline review and diffing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Marc Nuri <marc@marcnuri.com>
…d softer headers

Wrap tables in a card container matching chart/pre visual treatment.
Add zebra striping for horizontal scanning, softer uppercase headers
to shift emphasis to data, and move item count outside the card as a
right-aligned caption. Fix pre blocks to use overflow-wrap: break-word
and increase line-height for readability.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Marc Nuri <marc@marcnuri.com>
- Clean up stale ui:// resources on toolset reload via RemoveResources
- Merge _meta.ui into existing tool Meta instead of skipping
- Guard ensureStructuredObject against typed nil slices
- Prevent duplicate Namespace key in tableToStructured
- Remove redundant loop variable capture (Go 1.22+ semantics)
- Use plain object instead of ES6 Map in protocol.js for consistency

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Marc Nuri <marc@marcnuri.com>
…rge, namespace dedup)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Marc Nuri <marc@marcnuri.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Signed-off-by: Marc Nuri <marc@marcnuri.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

PoC: MCP Apps integration for interactive UI extensions

1 participant