From ork
Upgrades plain MCP tool JSON responses to interactive dashboards rendered in sandboxed iframes for Claude, Cursor, ChatGPT using @json-render/mcp. Covers createMcpApp, registerJsonRenderTool, CSP, streaming, dashboard patterns for MCP servers.
npx claudepluginhub yonatangross/orchestkit --plugin orkThis skill uses the workspace's default tool permissions.
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.
Integrates json-render with MCP to build servers rendering interactive UIs as MCP Apps in Claude, ChatGPT, Cursor, VS Code. Provides Node.js server factory and React client hook.
Guides building MCP Apps for rendering interactive HTML UIs in sandboxed iframes inside MCP hosts like Claude Desktop, ChatGPT, VS Code Copilot.
Builds MCP apps adding interactive UI widgets like forms, pickers, dashboards, and confirmation dialogs to MCP servers for inline rendering in Claude and ChatGPT chats.
Share bugs, ideas, or general feedback.
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-patternsfor 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-catalogfor all available components, props, and composition patterns.
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
| 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
defineCatalog() + ZodcreateMcpApp() for new servers or registerJsonRenderTool() for existing onesuseJsonRenderApp() + <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.
import { createMcpApp } from '@json-render/mcp'
import { catalog } from './catalog'
import bundledHtml from './app.html'
// 1. Create the MCP app (wraps McpServer + registers the render tool)
const app = createMcpApp({
catalog, // component schemas the AI can use
html: bundledHtml, // pre-built iframe app (single HTML file)
})
// 2. Start -- works with stdio, Streamable HTTP, or any MCP transport
app.start()
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { registerJsonRenderTool } from '@json-render/mcp'
import { catalog } from './catalog'
import bundledHtml from './app.html'
const server = new McpServer({ name: 'my-server', version: '1.0.0' })
// Add visual output capability alongside existing tools
registerJsonRenderTool(server, {
catalog,
html: bundledHtml,
})
The iframe app receives specs from the MCP host and renders them:
import { useJsonRenderApp } from '@json-render/mcp/app'
import { Renderer } from '@json-render/react'
import { registry } from './registry'
function App() {
const { spec, loading } = useJsonRenderApp()
if (loading) return <Skeleton />
return <Renderer spec={spec} registry={registry} />
}
Catalogs define what components the AI can use. Each component has typed props via Zod:
import { defineCatalog } from '@json-render/core'
import { z } from 'zod'
export const dashboardCatalog = defineCatalog({
StatGrid: {
props: z.object({
items: z.array(z.object({
label: z.string(),
value: z.string(),
trend: z.enum(['up', 'down', 'flat']).optional(),
color: z.enum(['green', 'red', 'yellow', 'blue']).optional(),
})),
}),
children: false,
},
StatusBadge: {
props: z.object({
label: z.string(),
status: z.enum(['success', 'warning', 'error', 'info', 'pending']),
}),
children: false,
},
DataTable: {
props: z.object({
columns: z.array(z.object({ key: z.string(), label: z.string() })),
rows: z.array(z.record(z.string())),
}),
children: false,
},
})
The AI generates a spec like this -- flat element map, no nesting beyond 2 levels:
{
"root": "dashboard",
"elements": {
"dashboard": {
"type": "Card",
"props": { "title": "Eval Results -- v7.21.1" },
"children": ["stats", "table"]
},
"stats": {
"type": "StatGrid",
"props": {
"items": [
{ "label": "Skills Evaluated", "value": "94", "trend": "flat" },
{ "label": "Pass Rate", "value": "97.8%", "trend": "up", "color": "green" },
{ "label": "Avg Score", "value": "8.2/10", "trend": "up" }
]
}
},
"table": {
"type": "DataTable",
"props": {
"columns": [
{ "key": "skill", "label": "Skill" },
{ "key": "score", "label": "Score" },
{ "key": "status", "label": "Status" }
],
"rows": [
{ "skill": "implement", "score": "9.1", "status": "pass" },
{ "skill": "verify", "score": "8.7", "status": "pass" }
]
}
}
}
}
| Decision | Recommendation |
|---|---|
| New vs existing server | 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 |
| 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 |
script-src 'unsafe-inline' in CSP declarations (security risk, unnecessary)html bundle in createMcpApp() config (iframe has nothing to render)ork:mcp-patterns -- MCP server building, transport, securityork:json-render-catalog -- Full component catalog and composition patternsork:multi-surface-render -- Rendering across Claude, Cursor, ChatGPT, webork:ai-ui-generation -- GenUI patterns for AI-generated interfaces