Interactive MCP visual output via @json-render/mcp: upgrade plain JSON tool responses to dashboards rendered in sandboxed iframes inside MCP clients like Claude, Cursor, and ChatGPT. Use when a tool result would read better as a stat grid, data table, or status badge than as text. For the server itself (transport, auth, tool handlers, security) reach for ork:mcp-patterns.
Interactive MCP visual output via @json-render/mcp: upgrade plain JSON tool responses to dashboards rendered in sandboxed iframes inside MCP clients like Claude, Cursor, and ChatGPT. Use when a tool result would read better as a stat grid, data table, or status badge than as text. For the server itself (transport, auth, tool handlers, security) reach for ork:mcp-patterns.
Upgrade plain MCP tool responses to interactive dashboards rendered inside AI conversations. Built on @json-render/mcp, which bridges the json-render spec system with MCP's tool/resource model -- the AI generates a typed JSON spec, and a sandboxed iframe renders it as an interactive UI.
Building an MCP server from scratch? Use ork:mcp-patterns for server setup, transport, and security. This skill focuses on the visual output layer after your server is running.
Need the full component catalog? See ork:json-render-catalog for all available components, props, and composition patterns.
Decision Tree -- Which File to Read
What are you doing?
|
+-- Setting up visual output for the first time
| +-- New MCP server -----------> rules/mcp-app-setup.md
| +-- Existing MCP server ------> rules/mcp-app-setup.md (registerJsonRenderTool section)
|
+-- Configuring security / sandbox
| +-- CSP declarations ----------> rules/sandbox-csp.md
| +-- Iframe permissions --------> rules/sandbox-csp.md
|
+-- Rendering strategy
| +-- Progressive streaming -----> rules/streaming-output.md
| +-- Dashboard layouts ----------> rules/dashboard-patterns.md
|
+-- API reference
| +-- Server-side API -----------> references/mcp-integration.md
| +-- Component recipes ----------> references/component-recipes.md
Quick Reference
Category
Rule
Impact
Key Pattern
Setup
mcp-app-setup.md
HIGH
createMcpApp() and registerJsonRenderTool()
Security
sandbox-csp.md
HIGH
CSP declarations, iframe sandboxing
Rendering
streaming-output.md
MEDIUM
Progressive rendering via JSON Patch
Patterns
dashboard-patterns.md
MEDIUM
Stat grids, status badges, data tables
Total: 4 rules across 3 categories
How It Works
Define a catalog -- typed component schemas using defineCatalog() + Zod
Register with MCP -- for new servers or for existing ones
createMcpApp()
registerJsonRenderTool()
AI generates specs -- the model produces a JSON spec conforming to the catalog
Iframe renders it -- a bundled React app inside a sandboxed iframe renders the spec with useJsonRenderApp() + <Renderer />
The AI never writes HTML or CSS. It produces a structured JSON spec that references catalog components by type. The iframe app renders those components using a pre-built registry.
Quick Start -- New MCP Server
import { createMcpApp } from'@json-render/mcp'import { StdioServerTransport } from'@modelcontextprotocol/sdk/server/stdio.js'import { buildAppHtml } from'@json-render/mcp/app'import { catalog } from'./catalog'// Generate the iframe HTML from the bundled JS/CSS (docs-prescribed generator).const bundledHtml = buildAppHtml({ entry: './app.tsx' })
// 1. Create the MCP app (async; returns an McpServer, no .start()/.close()).// name + version are required; tool config nests under `tool`// (default tool name is 'render-ui'). There is no top-level `csp`.const server = awaitcreateMcpApp({
name: 'my-app',
version: '1.0.0',
catalog, // component schemas the AI can usehtml: bundledHtml, // pre-built iframe app (single HTML file)tool: {
name: 'render-dashboard',
description: 'Render an interactive dashboard from a json-render spec',
},
})
// 2. Connect a transport -- stdio, Streamable HTTP, or any MCP transportawait server.connect(newStdioServerTransport())
Quick Start -- Enhance Existing Server with Visual Output
import { McpServer } from'@modelcontextprotocol/sdk/server/mcp.js'import { registerJsonRenderTool, registerJsonRenderResource } from'@json-render/mcp'import { buildAppHtml } from'@json-render/mcp/app'import { catalog } from'./catalog'const server = newMcpServer({ name: 'my-server', version: '1.0.0' })
// Generate the iframe HTML from the bundled JS/CSS (docs-prescribed generator).const bundledHtml = buildAppHtml({ entry: './app.tsx' })
const resourceUri = 'ui://my-server/dashboard'// Register the render tool (lets the model return specs).// name, title, description, and resourceUri are all required.registerJsonRenderTool(server, {
catalog,
name: 'render-dashboard',
title: 'Render Dashboard',
description: 'Render an interactive dashboard from a json-render spec',
resourceUri,
})
// Serve the bundled HTML iframe app as a resource (new in 0.15).// resourceUri must match the tool's resourceUri.registerJsonRenderResource(server, { resourceUri, html: bundledHtml })
registerJsonRenderResource() was added in 0.15 to separate tool registration from UI resource serving — useful when the host caches the bundled HTML (clients: Claude, ChatGPT, Cursor, VS Code Copilot, Goose, Postman). Transports: stdio and Streamable HTTP (Express) both supported. This skill is verified against @json-render/mcp0.19.0.
Client-Side Iframe App
The iframe app receives specs from the MCP host and renders them:
createMcpApp() for new; registerJsonRenderTool() to add to existing
CSP policy
Minimal -- only declare domains you actually need
Streaming
Always enable progressive rendering; never wait for full spec
Dashboard depth
Keep element trees flat (2-3 levels max) for streamability
Component count
3-5 component types per catalog covers most dashboards
Visual vs text
Use visual output for multi-metric views; plain text for single values
CC 2.1.113 fixed MCP concurrent-call timeout handling — hanging tool calls now error cleanly instead of blocking the queue. Parallel tool invocation from dashboards is safer; no workarounds needed.
When to Use Visual Output vs Plain Text
Scenario
Use Visual Output
Use Plain Text
Multiple metrics at a glance
Yes -- StatGrid
No
Tabular data (5+ rows)
Yes -- DataTable
No
Status of multiple systems
Yes -- StatusBadge grid
No
Single value answer
No
Yes
Error message
No
Yes
File content / code
No
Yes
Common Mistakes
Returning raw HTML strings from MCP tools instead of json-render specs (breaks type safety, no streaming)
Deeply nested component trees that cannot stream progressively (keep flat)
Using script-src 'unsafe-inline' in CSP declarations (security risk, unnecessary)
Waiting for the full spec before rendering (defeats progressive rendering)
Defining 20+ component types in a single catalog (increases prompt token cost)
Missing html bundle in createMcpApp() config (iframe has nothing to render)
Related Skills
ork:mcp-patterns -- MCP server building, transport, security
ork:json-render-catalog -- Full component catalog and composition patterns
ork:multi-surface-render -- Rendering across Claude, Cursor, ChatGPT, web
ork:ai-ui-generation -- GenUI patterns for AI-generated interfaces