mirror of
https://github.com/vercel-labs/json-render.git
synced 2026-10-03 20:38:21 +08:00
Compare commits
4
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f15faaae0 | ||
|
|
63c339b4bb | ||
|
|
1cc87310c9 | ||
|
|
5b929dffa6 |
@@ -17,7 +17,9 @@
|
|||||||
"@json-render/jotai",
|
"@json-render/jotai",
|
||||||
"@json-render/vue",
|
"@json-render/vue",
|
||||||
"@json-render/xstate",
|
"@json-render/xstate",
|
||||||
"@json-render/image"
|
"@json-render/image",
|
||||||
|
"@json-render/mcp",
|
||||||
|
"@json-render/svelte"
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
"linked": [],
|
"linked": [],
|
||||||
|
|||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"json-render": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["tsx", "examples/mcp/server.ts", "--stdio"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+9
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"servers": {
|
||||||
|
"json-render": {
|
||||||
|
"type": "stdio",
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["tsx", "examples/mcp/server.ts", "--stdio"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,8 @@ npm install @json-render/core @json-render/react-pdf
|
|||||||
npm install @json-render/core @json-render/react-email @react-email/components @react-email/render
|
npm install @json-render/core @json-render/react-email @react-email/components @react-email/render
|
||||||
# or for Vue
|
# or for Vue
|
||||||
npm install @json-render/core @json-render/vue
|
npm install @json-render/core @json-render/vue
|
||||||
|
# or for Svelte
|
||||||
|
npm install @json-render/core @json-render/svelte
|
||||||
```
|
```
|
||||||
|
|
||||||
## Why json-render?
|
## Why json-render?
|
||||||
@@ -28,7 +30,7 @@ json-render is a **Generative UI** framework: AI generates interfaces from natur
|
|||||||
- **Guardrailed** - AI can only use components in your catalog
|
- **Guardrailed** - AI can only use components in your catalog
|
||||||
- **Predictable** - JSON output matches your schema, every time
|
- **Predictable** - JSON output matches your schema, every time
|
||||||
- **Fast** - Stream and render progressively as the model responds
|
- **Fast** - Stream and render progressively as the model responds
|
||||||
- **Cross-Platform** - React, Vue (web), React Native (mobile) from the same catalog
|
- **Cross-Platform** - React, Vue, Svelte (web), React Native (mobile) from the same catalog
|
||||||
- **Batteries Included** - 36 pre-built shadcn/ui components ready to use
|
- **Batteries Included** - 36 pre-built shadcn/ui components ready to use
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
@@ -116,16 +118,19 @@ function Dashboard({ spec }) {
|
|||||||
| `@json-render/core` | Schemas, catalogs, AI prompts, dynamic props, SpecStream utilities |
|
| `@json-render/core` | Schemas, catalogs, AI prompts, dynamic props, SpecStream utilities |
|
||||||
| `@json-render/react` | React renderer, contexts, hooks |
|
| `@json-render/react` | React renderer, contexts, hooks |
|
||||||
| `@json-render/vue` | Vue 3 renderer, composables, providers |
|
| `@json-render/vue` | Vue 3 renderer, composables, providers |
|
||||||
|
| `@json-render/svelte` | Svelte 5 renderer with runes-based reactivity |
|
||||||
| `@json-render/shadcn` | 36 pre-built shadcn/ui components (Radix UI + Tailwind CSS) |
|
| `@json-render/shadcn` | 36 pre-built shadcn/ui components (Radix UI + Tailwind CSS) |
|
||||||
| `@json-render/react-native` | React Native renderer with standard mobile components |
|
| `@json-render/react-native` | React Native renderer with standard mobile components |
|
||||||
| `@json-render/remotion` | Remotion video renderer, timeline schema |
|
| `@json-render/remotion` | Remotion video renderer, timeline schema |
|
||||||
| `@json-render/react-pdf` | React PDF renderer for generating PDF documents from specs |
|
| `@json-render/react-pdf` | React PDF renderer for generating PDF documents from specs |
|
||||||
| `@json-render/react-email` | React Email renderer for HTML/plain-text emails from specs |
|
| `@json-render/react-email` | React Email renderer for HTML/plain-text emails from specs |
|
||||||
| `@json-render/image` | Image renderer for SVG/PNG output (OG images, social cards) via Satori |
|
| `@json-render/image` | Image renderer for SVG/PNG output (OG images, social cards) via Satori |
|
||||||
|
| `@json-render/codegen` | Utilities for generating code from json-render UI trees |
|
||||||
| `@json-render/redux` | Redux / Redux Toolkit adapter for `StateStore` |
|
| `@json-render/redux` | Redux / Redux Toolkit adapter for `StateStore` |
|
||||||
| `@json-render/zustand` | Zustand adapter for `StateStore` |
|
| `@json-render/zustand` | Zustand adapter for `StateStore` |
|
||||||
| `@json-render/jotai` | Jotai adapter for `StateStore` |
|
| `@json-render/jotai` | Jotai adapter for `StateStore` |
|
||||||
| `@json-render/xstate` | XState Store (atom) adapter for `StateStore` |
|
| `@json-render/xstate` | XState Store (atom) adapter for `StateStore` |
|
||||||
|
| `@json-render/mcp` | MCP Apps integration for Claude, ChatGPT, Cursor, VS Code |
|
||||||
|
|
||||||
## Renderers
|
## Renderers
|
||||||
|
|
||||||
@@ -177,6 +182,23 @@ const { registry } = defineRegistry(catalog, {
|
|||||||
// <Renderer :spec="spec" :registry="registry" />
|
// <Renderer :spec="spec" :registry="registry" />
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Svelte (UI)
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineRegistry, Renderer } from "@json-render/svelte";
|
||||||
|
import { schema } from "@json-render/svelte/schema";
|
||||||
|
|
||||||
|
const { registry } = defineRegistry(catalog, {
|
||||||
|
components: {
|
||||||
|
Card: ({ props, children }) => /* Svelte 5 snippet */,
|
||||||
|
Button: ({ props, emit }) => /* Svelte 5 snippet */,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// In your Svelte component:
|
||||||
|
// <Renderer spec={spec} registry={registry} />
|
||||||
|
```
|
||||||
|
|
||||||
### shadcn/ui (Web)
|
### shadcn/ui (Web)
|
||||||
|
|
||||||
```tsx
|
```tsx
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
# web
|
# web
|
||||||
|
|
||||||
|
## 0.1.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
- @json-render/codegen@0.12.0
|
||||||
|
- @json-render/react@0.12.0
|
||||||
|
|
||||||
## 0.1.4
|
## 0.1.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { pageMetadata } from "@/lib/page-metadata"
|
||||||
|
export const metadata = pageMetadata("docs/api/jotai")
|
||||||
|
|
||||||
|
# @json-render/jotai
|
||||||
|
|
||||||
|
Jotai adapter for json-render's `StateStore` interface.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @json-render/jotai @json-render/core @json-render/react jotai
|
||||||
|
```
|
||||||
|
|
||||||
|
## jotaiStateStore
|
||||||
|
|
||||||
|
Create a `StateStore` backed by a Jotai atom.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { jotaiStateStore } from "@json-render/jotai";
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Option</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Required</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>atom</code></td>
|
||||||
|
<td><code>{'WritableAtom<StateModel, [StateModel], void>'}</code></td>
|
||||||
|
<td>Yes</td>
|
||||||
|
<td>A writable atom holding the state model.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>store</code></td>
|
||||||
|
<td>Jotai <code>Store</code></td>
|
||||||
|
<td>No</td>
|
||||||
|
<td>The Jotai store instance. Defaults to a new store created internally. Pass your own to share state with <code>{'<Provider>'}</code>.</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { atom } from "jotai";
|
||||||
|
import { jotaiStateStore } from "@json-render/jotai";
|
||||||
|
import { StateProvider } from "@json-render/react";
|
||||||
|
|
||||||
|
const uiAtom = atom<Record<string, unknown>>({ count: 0 });
|
||||||
|
const store = jotaiStateStore({ atom: uiAtom });
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<StateProvider store={store}>
|
||||||
|
{/* json-render reads/writes go through Jotai */}
|
||||||
|
</StateProvider>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Shared Jotai Store
|
||||||
|
|
||||||
|
If your app already uses a Jotai `<Provider>` with a custom store, pass it so both json-render and your components share the same state:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { atom, createStore } from "jotai";
|
||||||
|
import { Provider as JotaiProvider } from "jotai/react";
|
||||||
|
import { jotaiStateStore } from "@json-render/jotai";
|
||||||
|
import { StateProvider } from "@json-render/react";
|
||||||
|
|
||||||
|
const jStore = createStore();
|
||||||
|
const uiAtom = atom<Record<string, unknown>>({ count: 0 });
|
||||||
|
const store = jotaiStateStore({ atom: uiAtom, store: jStore });
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<JotaiProvider store={jStore}>
|
||||||
|
<StateProvider store={store}>
|
||||||
|
{/* Both json-render and useAtom() see the same state */}
|
||||||
|
</StateProvider>
|
||||||
|
</JotaiProvider>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Re-exports
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Export</th>
|
||||||
|
<th>Source</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>StateStore</code></td>
|
||||||
|
<td><code>@json-render/core</code></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
@@ -0,0 +1,247 @@
|
|||||||
|
import { pageMetadata } from "@/lib/page-metadata"
|
||||||
|
export const metadata = pageMetadata("docs/api/mcp")
|
||||||
|
|
||||||
|
# @json-render/mcp
|
||||||
|
|
||||||
|
MCP Apps integration for json-render. Serve json-render UIs as interactive [MCP Apps](https://modelcontextprotocol.io/docs/extensions/apps) inside Claude, ChatGPT, Cursor, VS Code, and other MCP-capable clients.
|
||||||
|
|
||||||
|
## Install
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @json-render/mcp @json-render/core @modelcontextprotocol/sdk
|
||||||
|
```
|
||||||
|
|
||||||
|
For the iframe-side React UI, also install:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @json-render/react react react-dom
|
||||||
|
```
|
||||||
|
|
||||||
|
See the [MCP example](https://github.com/vercel-labs/json-render/tree/main/examples/mcp) for a full working example.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
MCP Apps let MCP servers return interactive HTML UIs that render directly inside chat conversations. `@json-render/mcp` bridges json-render catalogs with the MCP Apps protocol:
|
||||||
|
|
||||||
|
1. Your **catalog** defines which components and actions the AI can use
|
||||||
|
2. The **MCP server** exposes the catalog as a tool with the spec schema
|
||||||
|
3. The **bundled HTML** renders json-render specs inside the host's sandboxed iframe
|
||||||
|
4. The AI generates a spec, the host renders it, and users interact with the live UI
|
||||||
|
|
||||||
|
## Server API
|
||||||
|
|
||||||
|
### createMcpApp
|
||||||
|
|
||||||
|
Create a fully-configured MCP server. This is the main entry point.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { createMcpApp } from "@json-render/mcp";
|
||||||
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||||
|
import fs from "node:fs";
|
||||||
|
|
||||||
|
const server = createMcpApp({
|
||||||
|
name: "My Dashboard",
|
||||||
|
version: "1.0.0",
|
||||||
|
catalog: myCatalog,
|
||||||
|
html: fs.readFileSync("dist/index.html", "utf-8"),
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.connect(new StdioServerTransport());
|
||||||
|
```
|
||||||
|
|
||||||
|
#### CreateMcpAppOptions
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Option</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>name</code></td>
|
||||||
|
<td><code>string</code></td>
|
||||||
|
<td>Server name shown in client UIs</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>version</code></td>
|
||||||
|
<td><code>string</code></td>
|
||||||
|
<td>Server version</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>catalog</code></td>
|
||||||
|
<td><code>Catalog</code></td>
|
||||||
|
<td>json-render catalog defining available components</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>html</code></td>
|
||||||
|
<td><code>string</code></td>
|
||||||
|
<td>Self-contained HTML for the iframe UI</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>tool</code></td>
|
||||||
|
<td><code>McpToolOptions</code></td>
|
||||||
|
<td>Optional tool name/title/description overrides</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
### registerJsonRenderTool
|
||||||
|
|
||||||
|
Register a json-render tool on an existing `McpServer`. Use this when you need to add json-render to a server that has other tools.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { registerJsonRenderTool } from "@json-render/mcp";
|
||||||
|
|
||||||
|
registerJsonRenderTool(server, {
|
||||||
|
catalog,
|
||||||
|
name: "render-ui",
|
||||||
|
title: "Render UI",
|
||||||
|
description: "Render an interactive UI",
|
||||||
|
resourceUri: "ui://render-ui/view.html",
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### registerJsonRenderResource
|
||||||
|
|
||||||
|
Register the UI resource that serves the bundled HTML.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { registerJsonRenderResource } from "@json-render/mcp";
|
||||||
|
|
||||||
|
registerJsonRenderResource(server, {
|
||||||
|
resourceUri: "ui://render-ui/view.html",
|
||||||
|
html: bundledHtml,
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Client API (`@json-render/mcp/app`)
|
||||||
|
|
||||||
|
These exports run inside the sandboxed iframe rendered by the MCP host.
|
||||||
|
|
||||||
|
### useJsonRenderApp
|
||||||
|
|
||||||
|
React hook that connects to the MCP host, listens for tool results, and maintains the current json-render spec.
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useJsonRenderApp } from "@json-render/mcp/app";
|
||||||
|
import { JSONUIProvider, Renderer } from "@json-render/react";
|
||||||
|
|
||||||
|
function McpAppView({ registry }) {
|
||||||
|
const { spec, loading, connected, error } = useJsonRenderApp({
|
||||||
|
name: "my-app",
|
||||||
|
version: "1.0.0",
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) return <div>Error: {error.message}</div>;
|
||||||
|
if (!spec) return <div>Waiting...</div>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<JSONUIProvider registry={registry} initialState={spec.state ?? {}}>
|
||||||
|
<Renderer spec={spec} registry={registry} loading={loading} />
|
||||||
|
</JSONUIProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### UseJsonRenderAppReturn
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Field</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>spec</code></td>
|
||||||
|
<td><code>{'Spec | null'}</code></td>
|
||||||
|
<td>Current json-render spec</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>loading</code></td>
|
||||||
|
<td><code>boolean</code></td>
|
||||||
|
<td>Whether the spec is still being received</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>connected</code></td>
|
||||||
|
<td><code>boolean</code></td>
|
||||||
|
<td>Whether connected to the host</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>connecting</code></td>
|
||||||
|
<td><code>boolean</code></td>
|
||||||
|
<td>Whether currently connecting</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>error</code></td>
|
||||||
|
<td><code>{'Error | null'}</code></td>
|
||||||
|
<td>Connection error, if any</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>app</code></td>
|
||||||
|
<td><code>{'App | null'}</code></td>
|
||||||
|
<td>The underlying MCP App instance</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>callServerTool</code></td>
|
||||||
|
<td><code>{'(name, args?) => Promise<void>'}</code></td>
|
||||||
|
<td>Call an MCP server tool and update spec from result</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
### buildAppHtml
|
||||||
|
|
||||||
|
Generate a self-contained HTML page from bundled JavaScript and CSS.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { buildAppHtml } from "@json-render/mcp/app";
|
||||||
|
import fs from "node:fs";
|
||||||
|
|
||||||
|
const html = buildAppHtml({
|
||||||
|
title: "Dashboard",
|
||||||
|
js: fs.readFileSync("dist/app.js", "utf-8"),
|
||||||
|
css: fs.readFileSync("dist/app.css", "utf-8"),
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Client Configuration
|
||||||
|
|
||||||
|
### Cursor
|
||||||
|
|
||||||
|
Add to `.cursor/mcp.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"json-render": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["tsx", "path/to/server.ts", "--stdio"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Claude Desktop
|
||||||
|
|
||||||
|
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"json-render": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["tsx", "/absolute/path/to/server.ts", "--stdio"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Supported Clients
|
||||||
|
|
||||||
|
MCP Apps are supported by Claude (web and desktop), ChatGPT, VS Code (GitHub Copilot), Cursor, Goose, and Postman.
|
||||||
@@ -0,0 +1,104 @@
|
|||||||
|
import { pageMetadata } from "@/lib/page-metadata"
|
||||||
|
export const metadata = pageMetadata("docs/api/redux")
|
||||||
|
|
||||||
|
# @json-render/redux
|
||||||
|
|
||||||
|
Redux / Redux Toolkit adapter for json-render's `StateStore` interface.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @json-render/redux @json-render/core @json-render/react redux
|
||||||
|
# or with Redux Toolkit (recommended):
|
||||||
|
npm install @json-render/redux @json-render/core @json-render/react @reduxjs/toolkit
|
||||||
|
```
|
||||||
|
|
||||||
|
## reduxStateStore
|
||||||
|
|
||||||
|
Create a `StateStore` backed by a Redux store.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { reduxStateStore } from "@json-render/redux";
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Option</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Required</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>store</code></td>
|
||||||
|
<td><code>Store</code></td>
|
||||||
|
<td>Yes</td>
|
||||||
|
<td>The Redux store instance.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>selector</code></td>
|
||||||
|
<td><code>{'(state: S) => StateModel'}</code></td>
|
||||||
|
<td>No</td>
|
||||||
|
<td>Select the json-render slice from the Redux state tree. Defaults to <code>{'(state) => state'}</code>.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>dispatch</code></td>
|
||||||
|
<td><code>{'(nextState: StateModel, store: Store) => void'}</code></td>
|
||||||
|
<td>Yes</td>
|
||||||
|
<td>Dispatch an action that replaces the selected slice with the next state.</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { configureStore, createSlice } from "@reduxjs/toolkit";
|
||||||
|
import { reduxStateStore } from "@json-render/redux";
|
||||||
|
import { StateProvider } from "@json-render/react";
|
||||||
|
|
||||||
|
const uiSlice = createSlice({
|
||||||
|
name: "ui",
|
||||||
|
initialState: { count: 0 } as Record<string, unknown>,
|
||||||
|
reducers: {
|
||||||
|
replaceUiState: (_state, action) => action.payload,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const reduxStore = configureStore({
|
||||||
|
reducer: { ui: uiSlice.reducer },
|
||||||
|
});
|
||||||
|
|
||||||
|
const store = reduxStateStore({
|
||||||
|
store: reduxStore,
|
||||||
|
selector: (state) => state.ui,
|
||||||
|
dispatch: (next, s) => s.dispatch(uiSlice.actions.replaceUiState(next)),
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<StateProvider store={store}>
|
||||||
|
{/* json-render reads/writes go through Redux */}
|
||||||
|
</StateProvider>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Re-exports
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Export</th>
|
||||||
|
<th>Source</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>StateStore</code></td>
|
||||||
|
<td><code>@json-render/core</code></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
import { pageMetadata } from "@/lib/page-metadata"
|
||||||
|
export const metadata = pageMetadata("docs/api/xstate")
|
||||||
|
|
||||||
|
# @json-render/xstate
|
||||||
|
|
||||||
|
[XState Store](https://stately.ai/docs/xstate-store) adapter for json-render's `StateStore` interface.
|
||||||
|
|
||||||
|
Requires `@xstate/store` v3+.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @json-render/xstate @json-render/core @json-render/react @xstate/store
|
||||||
|
```
|
||||||
|
|
||||||
|
## xstateStoreStateStore
|
||||||
|
|
||||||
|
Create a `StateStore` backed by an `@xstate/store` atom.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { xstateStoreStateStore } from "@json-render/xstate";
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Option</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Required</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>atom</code></td>
|
||||||
|
<td><code>{'Atom<StateModel>'}</code></td>
|
||||||
|
<td>Yes</td>
|
||||||
|
<td>An <code>@xstate/store</code> atom (from <code>createAtom</code>) holding the json-render state model.</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { createAtom } from "@xstate/store";
|
||||||
|
import { xstateStoreStateStore } from "@json-render/xstate";
|
||||||
|
import { StateProvider } from "@json-render/react";
|
||||||
|
|
||||||
|
const uiAtom = createAtom({ count: 0 });
|
||||||
|
const store = xstateStoreStateStore({ atom: uiAtom });
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<StateProvider store={store}>
|
||||||
|
{/* json-render reads/writes go through @xstate/store */}
|
||||||
|
</StateProvider>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Re-exports
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Export</th>
|
||||||
|
<th>Source</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>StateStore</code></td>
|
||||||
|
<td><code>@json-render/core</code></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
import { pageMetadata } from "@/lib/page-metadata"
|
||||||
|
export const metadata = pageMetadata("docs/api/zustand")
|
||||||
|
|
||||||
|
# @json-render/zustand
|
||||||
|
|
||||||
|
Zustand adapter for json-render's `StateStore` interface.
|
||||||
|
|
||||||
|
Requires Zustand v5+. Zustand v4 is not supported due to breaking API changes in the vanilla store interface.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @json-render/zustand @json-render/core @json-render/react zustand
|
||||||
|
```
|
||||||
|
|
||||||
|
## zustandStateStore
|
||||||
|
|
||||||
|
Create a `StateStore` backed by a Zustand vanilla store.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { zustandStateStore } from "@json-render/zustand";
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Option</th>
|
||||||
|
<th>Type</th>
|
||||||
|
<th>Required</th>
|
||||||
|
<th>Description</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>store</code></td>
|
||||||
|
<td><code>{'StoreApi<S>'}</code></td>
|
||||||
|
<td>Yes</td>
|
||||||
|
<td>A Zustand vanilla store (from <code>createStore</code> in <code>zustand/vanilla</code>).</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>selector</code></td>
|
||||||
|
<td><code>{'(state: S) => StateModel'}</code></td>
|
||||||
|
<td>No</td>
|
||||||
|
<td>Select the json-render slice from the store state. Defaults to the entire state.</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td><code>updater</code></td>
|
||||||
|
<td><code>{'(nextState: StateModel, store: StoreApi<S>) => void'}</code></td>
|
||||||
|
<td>No</td>
|
||||||
|
<td>Apply a state change back to the store. Defaults to a shallow merge.</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { createStore } from "zustand/vanilla";
|
||||||
|
import { zustandStateStore } from "@json-render/zustand";
|
||||||
|
import { StateProvider } from "@json-render/react";
|
||||||
|
|
||||||
|
const bearStore = createStore(() => ({
|
||||||
|
count: 0,
|
||||||
|
name: "Bear",
|
||||||
|
}));
|
||||||
|
|
||||||
|
const store = zustandStateStore({ store: bearStore });
|
||||||
|
```
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
<StateProvider store={store}>
|
||||||
|
{/* json-render reads/writes go through Zustand */}
|
||||||
|
</StateProvider>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Nested Slice
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
const appStore = createStore(() => ({
|
||||||
|
ui: { count: 0 },
|
||||||
|
auth: { token: null },
|
||||||
|
}));
|
||||||
|
|
||||||
|
const store = zustandStateStore({
|
||||||
|
store: appStore,
|
||||||
|
selector: (s) => s.ui,
|
||||||
|
updater: (next, s) => s.setState({ ui: next }),
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Re-exports
|
||||||
|
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Export</th>
|
||||||
|
<th>Source</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td><code>StateStore</code></td>
|
||||||
|
<td><code>@json-render/core</code></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
@@ -16,7 +16,7 @@ const SYSTEM_PROMPT = `You are a helpful documentation assistant for json-render
|
|||||||
|
|
||||||
GitHub repository: https://github.com/vercel-labs/json-render
|
GitHub repository: https://github.com/vercel-labs/json-render
|
||||||
Documentation: https://json-render.dev/docs
|
Documentation: https://json-render.dev/docs
|
||||||
npm packages: @json-render/core, @json-render/react, @json-render/react-email, @json-render/react-pdf, @json-render/image, @json-render/remotion, @json-render/codegen
|
npm packages: @json-render/core, @json-render/react, @json-render/vue, @json-render/svelte, @json-render/shadcn, @json-render/react-native, @json-render/react-email, @json-render/react-pdf, @json-render/image, @json-render/remotion, @json-render/codegen, @json-render/mcp, @json-render/redux, @json-render/zustand, @json-render/jotai, @json-render/xstate
|
||||||
|
|
||||||
You have access to the full json-render documentation via the bash and readFile tools. The docs are available as markdown files in the /workspace/docs/ directory.
|
You have access to the full json-render documentation via the bash and readFile tools. The docs are available as markdown files in the /workspace/docs/ directory.
|
||||||
|
|
||||||
|
|||||||
@@ -95,6 +95,11 @@ export const docsNavigation: NavSection[] = [
|
|||||||
href: "https://github.com/vercel-labs/json-render/tree/main/examples/vite-renderers",
|
href: "https://github.com/vercel-labs/json-render/tree/main/examples/vite-renderers",
|
||||||
external: true,
|
external: true,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
title: "MCP App",
|
||||||
|
href: "https://github.com/vercel-labs/json-render/tree/main/examples/mcp",
|
||||||
|
external: true,
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -128,6 +133,11 @@ export const docsNavigation: NavSection[] = [
|
|||||||
{ title: "@json-render/vue", href: "/docs/api/vue" },
|
{ title: "@json-render/vue", href: "/docs/api/vue" },
|
||||||
{ title: "@json-render/svelte", href: "/docs/api/svelte" },
|
{ title: "@json-render/svelte", href: "/docs/api/svelte" },
|
||||||
{ title: "@json-render/codegen", href: "/docs/api/codegen" },
|
{ title: "@json-render/codegen", href: "/docs/api/codegen" },
|
||||||
|
{ title: "@json-render/mcp", href: "/docs/api/mcp" },
|
||||||
|
{ title: "@json-render/redux", href: "/docs/api/redux" },
|
||||||
|
{ title: "@json-render/zustand", href: "/docs/api/zustand" },
|
||||||
|
{ title: "@json-render/jotai", href: "/docs/api/jotai" },
|
||||||
|
{ title: "@json-render/xstate", href: "/docs/api/xstate" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ export const PAGE_TITLES: Record<string, string> = {
|
|||||||
"docs/api/image": "@json-render/image API",
|
"docs/api/image": "@json-render/image API",
|
||||||
"docs/api/remotion": "@json-render/remotion API",
|
"docs/api/remotion": "@json-render/remotion API",
|
||||||
"docs/api/shadcn": "@json-render/shadcn API",
|
"docs/api/shadcn": "@json-render/shadcn API",
|
||||||
|
"docs/api/mcp": "@json-render/mcp API",
|
||||||
|
"docs/api/redux": "@json-render/redux API",
|
||||||
|
"docs/api/zustand": "@json-render/zustand API",
|
||||||
|
"docs/api/jotai": "@json-render/jotai API",
|
||||||
|
"docs/api/xstate": "@json-render/xstate API",
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "web",
|
"name": "web",
|
||||||
"version": "0.1.4",
|
"version": "0.1.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
# example-chat
|
# example-chat
|
||||||
|
|
||||||
|
## 0.1.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
- @json-render/react@0.12.0
|
||||||
|
- @json-render/shadcn@0.12.0
|
||||||
|
|
||||||
## 0.1.4
|
## 0.1.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "example-chat",
|
"name": "example-chat",
|
||||||
"version": "0.1.4",
|
"version": "0.1.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,5 +1,14 @@
|
|||||||
# example-dashboard
|
# example-dashboard
|
||||||
|
|
||||||
|
## 0.1.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
- @json-render/codegen@0.12.0
|
||||||
|
- @json-render/react@0.12.0
|
||||||
|
|
||||||
## 0.1.4
|
## 0.1.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "example-dashboard",
|
"name": "example-dashboard",
|
||||||
"version": "0.1.4",
|
"version": "0.1.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# example-image
|
# example-image
|
||||||
|
|
||||||
|
## 0.1.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
- @json-render/image@0.12.0
|
||||||
|
|
||||||
## 0.1.1
|
## 0.1.1
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "example-image",
|
"name": "example-image",
|
||||||
"version": "0.1.1",
|
"version": "0.1.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -0,0 +1,11 @@
|
|||||||
|
# example-mcp
|
||||||
|
|
||||||
|
## 0.1.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
- @json-render/mcp@0.12.0
|
||||||
|
- @json-render/react@0.12.0
|
||||||
|
- @json-render/shadcn@0.12.0
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
# MCP App Example
|
||||||
|
|
||||||
|
A json-render MCP App that serves interactive shadcn/ui-based UIs directly inside Claude, ChatGPT, Cursor, VS Code, and other MCP-capable clients.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
pnpm build
|
||||||
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### With Cursor
|
||||||
|
|
||||||
|
Add to `.cursor/mcp.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"json-render": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["tsx", "examples/mcp/server.ts", "--stdio"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Claude Desktop
|
||||||
|
|
||||||
|
Add to `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"json-render": {
|
||||||
|
"command": "npx",
|
||||||
|
"args": ["tsx", "/absolute/path/to/examples/mcp/server.ts", "--stdio"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### HTTP Transport
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm start
|
||||||
|
# Server listens on http://localhost:3001/mcp
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stdio Transport
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm start:stdio
|
||||||
|
```
|
||||||
|
|
||||||
|
## How It Works
|
||||||
|
|
||||||
|
1. The Vite build bundles the React app (with shadcn components and `useJsonRenderApp` hook) into a single self-contained HTML file
|
||||||
|
2. The MCP server registers a `render-ui` tool with the catalog prompt as its description
|
||||||
|
3. When the LLM calls the tool, it generates a json-render spec constrained to the catalog
|
||||||
|
4. The host renders the bundled HTML in a sandboxed iframe
|
||||||
|
5. The iframe receives the spec via the MCP Apps protocol and renders it with json-render
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<meta http-equiv="Content-Security-Policy" content="default-src 'self' 'unsafe-inline' 'unsafe-eval' data: blob:; img-src * data: blob:; font-src * data:; connect-src *; media-src *;" />
|
||||||
|
<title>json-render MCP App</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="/src/main.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
{
|
||||||
|
"name": "example-mcp",
|
||||||
|
"version": "0.1.1",
|
||||||
|
"type": "module",
|
||||||
|
"private": true,
|
||||||
|
"scripts": {
|
||||||
|
"build": "vite build",
|
||||||
|
"start": "tsx server.ts",
|
||||||
|
"start:stdio": "tsx server.ts --stdio",
|
||||||
|
"dev": "tsx watch server.ts"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@json-render/core": "workspace:*",
|
||||||
|
"@json-render/mcp": "workspace:*",
|
||||||
|
"@json-render/react": "workspace:*",
|
||||||
|
"@json-render/shadcn": "workspace:*",
|
||||||
|
"@modelcontextprotocol/ext-apps": "^1.2.0",
|
||||||
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
||||||
|
"react": "19.2.3",
|
||||||
|
"react-dom": "19.2.3",
|
||||||
|
"zod": "^4.3.6"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/react": "19.2.3",
|
||||||
|
"@types/react-dom": "19.2.3",
|
||||||
|
"@tailwindcss/vite": "^4.2.1",
|
||||||
|
"@vitejs/plugin-react": "^5.1.4",
|
||||||
|
"tailwindcss": "^4.2.1",
|
||||||
|
"tsx": "^4.21.0",
|
||||||
|
"typescript": "^5.7.2",
|
||||||
|
"vite": "^6.3.5",
|
||||||
|
"vite-plugin-singlefile": "^2.3.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
import { createMcpApp } from "@json-render/mcp";
|
||||||
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||||
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
||||||
|
import { createMcpExpressApp } from "@modelcontextprotocol/sdk/server/express.js";
|
||||||
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { catalog } from "./src/catalog.js";
|
||||||
|
|
||||||
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||||
|
|
||||||
|
function loadHtml(): string {
|
||||||
|
const htmlPath = path.join(__dirname, "dist", "index.html");
|
||||||
|
if (!fs.existsSync(htmlPath)) {
|
||||||
|
throw new Error(
|
||||||
|
`Built HTML not found at ${htmlPath}. Run 'pnpm build' first.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return fs.readFileSync(htmlPath, "utf-8");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startStdio() {
|
||||||
|
const html = loadHtml();
|
||||||
|
const server = await createMcpApp({
|
||||||
|
name: "json-render Example",
|
||||||
|
version: "1.0.0",
|
||||||
|
catalog,
|
||||||
|
html,
|
||||||
|
});
|
||||||
|
await server.connect(new StdioServerTransport());
|
||||||
|
}
|
||||||
|
|
||||||
|
async function startHttp() {
|
||||||
|
const html = loadHtml();
|
||||||
|
const port = parseInt(process.env.PORT ?? "3001", 10);
|
||||||
|
|
||||||
|
const expressApp = createMcpExpressApp({ host: "0.0.0.0" });
|
||||||
|
|
||||||
|
expressApp.all("/mcp", async (req, res) => {
|
||||||
|
const server = await createMcpApp({
|
||||||
|
name: "json-render Example",
|
||||||
|
version: "1.0.0",
|
||||||
|
catalog,
|
||||||
|
html,
|
||||||
|
});
|
||||||
|
|
||||||
|
const transport = new StreamableHTTPServerTransport({
|
||||||
|
sessionIdGenerator: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
|
res.on("close", () => {
|
||||||
|
transport.close().catch(() => {});
|
||||||
|
server.close().catch(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
await server.connect(transport);
|
||||||
|
await transport.handleRequest(req, res, req.body);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("MCP error:", error);
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.status(500).json({
|
||||||
|
jsonrpc: "2.0",
|
||||||
|
error: { code: -32603, message: "Internal server error" },
|
||||||
|
id: null,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expressApp.listen(port, () => {
|
||||||
|
console.log(`MCP server listening on http://localhost:${port}/mcp`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.argv.includes("--stdio")) {
|
||||||
|
startStdio().catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
startHttp().catch((e) => {
|
||||||
|
console.error(e);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import { defineCatalog } from "@json-render/core";
|
||||||
|
import { schema } from "@json-render/react/schema";
|
||||||
|
import { shadcnComponentDefinitions } from "@json-render/shadcn/catalog";
|
||||||
|
|
||||||
|
export const catalog = defineCatalog(schema, {
|
||||||
|
components: {
|
||||||
|
...shadcnComponentDefinitions,
|
||||||
|
},
|
||||||
|
actions: {},
|
||||||
|
});
|
||||||
@@ -0,0 +1,106 @@
|
|||||||
|
@import "tailwindcss";
|
||||||
|
@source "../../../packages/shadcn/src";
|
||||||
|
@source "../../../packages/react/src";
|
||||||
|
|
||||||
|
@custom-variant dark (&:is([data-theme="dark"] *));
|
||||||
|
|
||||||
|
@theme inline {
|
||||||
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
|
--radius-lg: var(--radius);
|
||||||
|
--radius-xl: calc(var(--radius) + 4px);
|
||||||
|
--radius-2xl: calc(var(--radius) + 8px);
|
||||||
|
--color-background: var(--background);
|
||||||
|
--color-foreground: var(--foreground);
|
||||||
|
--color-card: var(--card);
|
||||||
|
--color-card-foreground: var(--card-foreground);
|
||||||
|
--color-popover: var(--popover);
|
||||||
|
--color-popover-foreground: var(--popover-foreground);
|
||||||
|
--color-primary: var(--primary);
|
||||||
|
--color-primary-foreground: var(--primary-foreground);
|
||||||
|
--color-secondary: var(--secondary);
|
||||||
|
--color-secondary-foreground: var(--secondary-foreground);
|
||||||
|
--color-muted: var(--muted);
|
||||||
|
--color-muted-foreground: var(--muted-foreground);
|
||||||
|
--color-accent: var(--accent);
|
||||||
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
|
--color-destructive: var(--destructive);
|
||||||
|
--color-border: var(--border);
|
||||||
|
--color-input: var(--input);
|
||||||
|
--color-ring: var(--ring);
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--radius: 0.625rem;
|
||||||
|
--background: oklch(1 0 0);
|
||||||
|
--foreground: oklch(0.145 0 0);
|
||||||
|
--card: oklch(1 0 0);
|
||||||
|
--card-foreground: oklch(0.145 0 0);
|
||||||
|
--popover: oklch(1 0 0);
|
||||||
|
--popover-foreground: oklch(0.145 0 0);
|
||||||
|
--primary: oklch(0.205 0 0);
|
||||||
|
--primary-foreground: oklch(0.985 0 0);
|
||||||
|
--secondary: oklch(0.97 0 0);
|
||||||
|
--secondary-foreground: oklch(0.205 0 0);
|
||||||
|
--muted: oklch(0.97 0 0);
|
||||||
|
--muted-foreground: oklch(0.556 0 0);
|
||||||
|
--accent: oklch(0.97 0 0);
|
||||||
|
--accent-foreground: oklch(0.205 0 0);
|
||||||
|
--destructive: oklch(0.577 0.245 27.325);
|
||||||
|
--border: oklch(0.922 0 0);
|
||||||
|
--input: oklch(0.922 0 0);
|
||||||
|
--ring: oklch(0.708 0 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] {
|
||||||
|
--background: var(--color-background-secondary, var(--vscode-editor-background, oklch(0.145 0 0)));
|
||||||
|
--foreground: var(--color-text-primary, var(--vscode-foreground, oklch(0.985 0 0)));
|
||||||
|
--card: var(--color-background-primary, var(--vscode-sideBar-background, oklch(0.205 0 0)));
|
||||||
|
--card-foreground: var(--color-text-primary, var(--vscode-foreground, oklch(0.985 0 0)));
|
||||||
|
--popover: var(--color-background-primary, var(--vscode-sideBar-background, oklch(0.205 0 0)));
|
||||||
|
--popover-foreground: var(--color-text-primary, var(--vscode-foreground, oklch(0.985 0 0)));
|
||||||
|
--primary: var(--color-text-primary, var(--vscode-foreground, oklch(0.922 0 0)));
|
||||||
|
--primary-foreground: var(--color-background-primary, var(--vscode-sideBar-background, oklch(0.205 0 0)));
|
||||||
|
--secondary: var(--color-background-tertiary, var(--vscode-activityBar-background, oklch(0.269 0 0)));
|
||||||
|
--secondary-foreground: var(--color-text-primary, var(--vscode-foreground, oklch(0.985 0 0)));
|
||||||
|
--muted: var(--color-background-tertiary, var(--vscode-activityBar-background, oklch(0.269 0 0)));
|
||||||
|
--muted-foreground: var(--color-text-secondary, var(--vscode-descriptionForeground, oklch(0.708 0 0)));
|
||||||
|
--accent: var(--color-background-tertiary, var(--vscode-activityBar-background, oklch(0.269 0 0)));
|
||||||
|
--accent-foreground: var(--color-text-primary, var(--vscode-foreground, oklch(0.985 0 0)));
|
||||||
|
--destructive: var(--color-text-danger, var(--vscode-errorForeground, oklch(0.704 0.191 22.216)));
|
||||||
|
--border: var(--color-border-primary, var(--vscode-widget-border, oklch(1 0 0 / 10%)));
|
||||||
|
--input: var(--color-border-secondary, var(--vscode-editorWidget-border, oklch(1 0 0 / 15%)));
|
||||||
|
--ring: var(--color-ring-primary, var(--vscode-focusBorder, oklch(0.556 0 0)));
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-color: var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body, #root {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--color-background);
|
||||||
|
color: var(--color-foreground);
|
||||||
|
font-family: var(--vscode-font-family, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
|
||||||
|
}
|
||||||
|
|
||||||
|
@layer base {
|
||||||
|
* {
|
||||||
|
@apply border-border outline-ring/50;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
@apply text-foreground;
|
||||||
|
background-color: var(--color-background) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
@@ -0,0 +1,269 @@
|
|||||||
|
import "./globals.css";
|
||||||
|
import {
|
||||||
|
Component,
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useCallback,
|
||||||
|
type ReactNode,
|
||||||
|
} from "react";
|
||||||
|
import { createRoot } from "react-dom/client";
|
||||||
|
import { App as McpApp } from "@modelcontextprotocol/ext-apps";
|
||||||
|
import { JSONUIProvider, Renderer, defineRegistry } from "@json-render/react";
|
||||||
|
import { shadcnComponents } from "@json-render/shadcn";
|
||||||
|
import type { Spec } from "@json-render/core";
|
||||||
|
import { catalog } from "./catalog";
|
||||||
|
|
||||||
|
const { registry } = defineRegistry(catalog, {
|
||||||
|
components: {
|
||||||
|
...shadcnComponents,
|
||||||
|
Avatar: ({ props }: { props: Record<string, unknown> }) => {
|
||||||
|
const name = (props.name as string) || (props.alt as string) || "?";
|
||||||
|
const src = props.src as string | undefined;
|
||||||
|
const initials = name
|
||||||
|
.split(" ")
|
||||||
|
.map((n) => n[0])
|
||||||
|
.join("")
|
||||||
|
.slice(0, 2)
|
||||||
|
.toUpperCase();
|
||||||
|
const size = props.size === "lg" ? 48 : props.size === "sm" ? 32 : 40;
|
||||||
|
const [imgFailed, setImgFailed] = useState(false);
|
||||||
|
const onError = useCallback(() => setImgFailed(true), []);
|
||||||
|
const showImg = src && !imgFailed;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
width: size,
|
||||||
|
height: size,
|
||||||
|
borderRadius: "50%",
|
||||||
|
overflow: "hidden",
|
||||||
|
backgroundColor: "var(--color-background-tertiary, #27272a)",
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "center",
|
||||||
|
flexShrink: 0,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{showImg ? (
|
||||||
|
<img
|
||||||
|
src={src}
|
||||||
|
alt={name}
|
||||||
|
referrerPolicy="no-referrer"
|
||||||
|
crossOrigin="anonymous"
|
||||||
|
width={size}
|
||||||
|
height={size}
|
||||||
|
onError={onError}
|
||||||
|
style={{ objectFit: "cover", width: size, height: size }}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<span
|
||||||
|
style={{
|
||||||
|
fontSize: size * 0.4,
|
||||||
|
color: "var(--color-text-secondary, #a1a1aa)",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{initials}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
class ErrorBoundary extends Component<
|
||||||
|
{ children: ReactNode },
|
||||||
|
{ error: Error | null }
|
||||||
|
> {
|
||||||
|
state = { error: null as Error | null };
|
||||||
|
static getDerivedStateFromError(error: Error) {
|
||||||
|
return { error };
|
||||||
|
}
|
||||||
|
render() {
|
||||||
|
if (this.state.error) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: 16,
|
||||||
|
color: "#dc2626",
|
||||||
|
fontFamily: "monospace",
|
||||||
|
fontSize: 13,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Error: {this.state.error.message}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return this.props.children;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function forceFullWidth(spec: Spec): Spec {
|
||||||
|
if (!spec.elements) return spec;
|
||||||
|
const elements = { ...spec.elements };
|
||||||
|
for (const [key, el] of Object.entries(elements)) {
|
||||||
|
if (el.type === "Card" && el.props) {
|
||||||
|
elements[key] = {
|
||||||
|
...el,
|
||||||
|
props: { ...el.props, maxWidth: "full", centered: false },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { ...spec, elements };
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSpec(
|
||||||
|
result: { content?: Array<{ type: string; text?: string }> } | undefined,
|
||||||
|
): Spec | null {
|
||||||
|
const text = result?.content?.find((c) => c.type === "text")?.text;
|
||||||
|
if (!text) return null;
|
||||||
|
try {
|
||||||
|
return forceFullWidth(JSON.parse(text) as Spec);
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyHostContext(ctx: {
|
||||||
|
theme?: string;
|
||||||
|
styles?: { variables?: Record<string, string> };
|
||||||
|
}) {
|
||||||
|
if (ctx.theme) {
|
||||||
|
document.documentElement.setAttribute("data-theme", ctx.theme);
|
||||||
|
document.documentElement.style.colorScheme = ctx.theme;
|
||||||
|
}
|
||||||
|
if (ctx.styles?.variables) {
|
||||||
|
const root = document.documentElement;
|
||||||
|
for (const [key, value] of Object.entries(ctx.styles.variables)) {
|
||||||
|
root.style.setProperty(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function McpAppView() {
|
||||||
|
const [spec, setSpec] = useState<Spec | null>(null);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let specReceived = false;
|
||||||
|
|
||||||
|
function handleSpec(s: Spec) {
|
||||||
|
if (specReceived) return;
|
||||||
|
specReceived = true;
|
||||||
|
setSpec(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
function onMessage(event: MessageEvent) {
|
||||||
|
if (specReceived) return;
|
||||||
|
const data = event.data as Record<string, unknown> | undefined;
|
||||||
|
if (!data || typeof data !== "object") return;
|
||||||
|
const method = data.method as string | undefined;
|
||||||
|
const params = data.params as Record<string, unknown> | undefined;
|
||||||
|
|
||||||
|
// Cursor sends the spec via tool-input
|
||||||
|
if (method === "ui/notifications/tool-input" && params?.arguments) {
|
||||||
|
const args = params.arguments as Record<string, unknown>;
|
||||||
|
const rawSpec = args.spec;
|
||||||
|
if (
|
||||||
|
rawSpec &&
|
||||||
|
typeof rawSpec === "object" &&
|
||||||
|
"root" in rawSpec &&
|
||||||
|
"elements" in rawSpec
|
||||||
|
) {
|
||||||
|
handleSpec(forceFullWidth(rawSpec as Spec));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
window.addEventListener("message", onMessage);
|
||||||
|
|
||||||
|
const app = new McpApp({ name: "json-render", version: "1.0.0" });
|
||||||
|
|
||||||
|
app.ontoolresult = (result) => {
|
||||||
|
const parsed = parseSpec(
|
||||||
|
result as { content?: Array<{ type: string; text?: string }> },
|
||||||
|
);
|
||||||
|
if (parsed) handleSpec(parsed);
|
||||||
|
};
|
||||||
|
|
||||||
|
app.onhostcontextchanged = (ctx) =>
|
||||||
|
applyHostContext(ctx as Parameters<typeof applyHostContext>[0]);
|
||||||
|
|
||||||
|
app.onerror = (err: unknown) => {
|
||||||
|
setError(err instanceof Error ? err.message : String(err));
|
||||||
|
};
|
||||||
|
|
||||||
|
app
|
||||||
|
.connect()
|
||||||
|
.then(() => {
|
||||||
|
const ctx = app.getHostContext?.();
|
||||||
|
if (ctx)
|
||||||
|
applyHostContext(ctx as Parameters<typeof applyHostContext>[0]);
|
||||||
|
|
||||||
|
// Fallback: if host didn't provide theme, detect via media query
|
||||||
|
if (!ctx || !(ctx as Record<string, unknown>).theme) {
|
||||||
|
const prefersDark = window.matchMedia(
|
||||||
|
"(prefers-color-scheme: dark)",
|
||||||
|
).matches;
|
||||||
|
document.documentElement.setAttribute(
|
||||||
|
"data-theme",
|
||||||
|
prefersDark ? "dark" : "light",
|
||||||
|
);
|
||||||
|
document.documentElement.style.colorScheme = prefersDark
|
||||||
|
? "dark"
|
||||||
|
: "light";
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err: unknown) =>
|
||||||
|
setError(err instanceof Error ? err.message : String(err)),
|
||||||
|
);
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
window.removeEventListener("message", onMessage);
|
||||||
|
app.close().catch(() => {});
|
||||||
|
};
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: 16,
|
||||||
|
color: "#dc2626",
|
||||||
|
fontFamily: "monospace",
|
||||||
|
fontSize: 13,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{error}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!spec) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
padding: 16,
|
||||||
|
color: "#6b7280",
|
||||||
|
fontFamily: "sans-serif",
|
||||||
|
fontSize: 14,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Loading...
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<JSONUIProvider registry={registry} initialState={spec.state ?? {}}>
|
||||||
|
<div className="w-full">
|
||||||
|
<Renderer spec={spec} registry={registry} />
|
||||||
|
</div>
|
||||||
|
</JSONUIProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
createRoot(document.getElementById("root")!).render(
|
||||||
|
<ErrorBoundary>
|
||||||
|
<McpAppView />
|
||||||
|
</ErrorBoundary>,
|
||||||
|
);
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
import { JSONUIProvider, Renderer } from "@json-render/react";
|
||||||
|
import { shadcnComponents } from "@json-render/shadcn";
|
||||||
|
import { defineRegistry } from "@json-render/react";
|
||||||
|
import { useJsonRenderApp } from "@json-render/mcp/app";
|
||||||
|
import { catalog } from "./catalog";
|
||||||
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
const { registry } = defineRegistry(catalog, {
|
||||||
|
components: {
|
||||||
|
...shadcnComponents,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const debugStyle = {
|
||||||
|
padding: 12,
|
||||||
|
fontFamily: "monospace",
|
||||||
|
fontSize: 12,
|
||||||
|
color: "#000",
|
||||||
|
background: "#fffbe6",
|
||||||
|
border: "1px solid #faad14",
|
||||||
|
borderRadius: 4,
|
||||||
|
margin: 8,
|
||||||
|
whiteSpace: "pre-wrap" as const,
|
||||||
|
wordBreak: "break-all" as const,
|
||||||
|
};
|
||||||
|
|
||||||
|
export function McpAppView() {
|
||||||
|
const [logs, setLogs] = useState<string[]>(["App mounted"]);
|
||||||
|
|
||||||
|
const addLog = (msg: string) => {
|
||||||
|
setLogs((prev) => [
|
||||||
|
...prev,
|
||||||
|
`${new Date().toISOString().slice(11, 23)} ${msg}`,
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
const { spec, loading, connected, connecting, error } = useJsonRenderApp({
|
||||||
|
name: "json-render-mcp-example",
|
||||||
|
version: "1.0.0",
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
addLog(
|
||||||
|
`State: connecting=${connecting} connected=${connected} error=${error?.message ?? "none"} loading=${loading} spec=${spec ? "yes" : "null"}`,
|
||||||
|
);
|
||||||
|
}, [connecting, connected, error, loading, spec]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (spec) {
|
||||||
|
addLog(
|
||||||
|
`Spec received: root=${spec.root}, elements=${Object.keys(spec.elements ?? {}).join(",")}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}, [spec]);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return (
|
||||||
|
<div style={debugStyle}>
|
||||||
|
<div style={{ color: "#ef4444", fontWeight: "bold" }}>
|
||||||
|
Connection error: {error.message}
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: 8 }}>{logs.join("\n")}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!spec) {
|
||||||
|
return (
|
||||||
|
<div style={debugStyle}>
|
||||||
|
<div>
|
||||||
|
{connecting
|
||||||
|
? "Connecting to host..."
|
||||||
|
: loading
|
||||||
|
? "Waiting for UI spec..."
|
||||||
|
: "No spec received."}
|
||||||
|
</div>
|
||||||
|
<div style={{ marginTop: 8 }}>{logs.join("\n")}</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<JSONUIProvider registry={registry} initialState={spec.state ?? {}}>
|
||||||
|
<Renderer spec={spec} registry={registry} loading={loading} />
|
||||||
|
</JSONUIProvider>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "bundler",
|
||||||
|
"allowImportingTsExtensions": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"verbatimModuleSyntax": true,
|
||||||
|
"noEmit": true,
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"jsx": "react-jsx"
|
||||||
|
},
|
||||||
|
"include": ["src", "server.ts"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import { defineConfig } from "vite";
|
||||||
|
import react from "@vitejs/plugin-react";
|
||||||
|
import { viteSingleFile } from "vite-plugin-singlefile";
|
||||||
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
plugins: [react(), tailwindcss(), viteSingleFile()],
|
||||||
|
build: {
|
||||||
|
outDir: "dist",
|
||||||
|
emptyOutDir: false,
|
||||||
|
rollupOptions: {
|
||||||
|
input: "index.html",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -1,5 +1,14 @@
|
|||||||
# example-no-ai
|
# example-no-ai
|
||||||
|
|
||||||
|
## 0.1.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
- @json-render/react@0.12.0
|
||||||
|
- @json-render/shadcn@0.12.0
|
||||||
|
|
||||||
## 0.1.4
|
## 0.1.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "example-no-ai",
|
"name": "example-no-ai",
|
||||||
"version": "0.1.4",
|
"version": "0.1.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# example-react-email
|
||||||
|
|
||||||
|
## 0.1.1
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
- @json-render/react-email@0.12.0
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "example-react-email",
|
"name": "example-react-email",
|
||||||
"version": "0.1.0",
|
"version": "0.1.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# example-react-native
|
# example-react-native
|
||||||
|
|
||||||
|
## 0.1.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
- @json-render/react-native@0.12.0
|
||||||
|
|
||||||
## 0.1.4
|
## 0.1.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "example-react-native",
|
"name": "example-react-native",
|
||||||
"version": "0.1.4",
|
"version": "0.1.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"main": "expo-router/entry",
|
"main": "expo-router/entry",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# example-react-pdf
|
# example-react-pdf
|
||||||
|
|
||||||
|
## 0.1.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
- @json-render/react-pdf@0.12.0
|
||||||
|
|
||||||
## 0.1.4
|
## 0.1.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "example-react-pdf",
|
"name": "example-react-pdf",
|
||||||
"version": "0.1.4",
|
"version": "0.1.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# example-remotion
|
# example-remotion
|
||||||
|
|
||||||
|
## 0.1.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
- @json-render/remotion@0.12.0
|
||||||
|
|
||||||
## 0.1.4
|
## 0.1.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "example-remotion",
|
"name": "example-remotion",
|
||||||
"version": "0.1.4",
|
"version": "0.1.5",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# com.example.json-render-demo
|
# com.example.json-render-demo
|
||||||
|
|
||||||
|
## 0.0.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
- @json-render/react@0.12.0
|
||||||
|
|
||||||
## 0.0.5
|
## 0.0.5
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "com.example.json-render-demo",
|
"name": "com.example.json-render-demo",
|
||||||
"version": "0.0.5",
|
"version": "0.0.6",
|
||||||
"description": "Test",
|
"description": "Test",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "~~proprietary~~",
|
"license": "~~proprietary~~",
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# com.example.json-render-fullpage-demo
|
# com.example.json-render-fullpage-demo
|
||||||
|
|
||||||
|
## 0.0.6
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
- @json-render/react@0.12.0
|
||||||
|
|
||||||
## 0.0.5
|
## 0.0.5
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "com.example.json-render-fullpage-demo",
|
"name": "com.example.json-render-fullpage-demo",
|
||||||
"version": "0.0.5",
|
"version": "0.0.6",
|
||||||
"description": "Full-page Stripe App example (alpha)",
|
"description": "Full-page Stripe App example (alpha)",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "~~proprietary~~",
|
"license": "~~proprietary~~",
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# svelte-chat
|
||||||
|
|
||||||
|
## 0.0.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
- @json-render/svelte@0.12.0
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "svelte-chat",
|
"name": "svelte-chat",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.0.1",
|
"version": "0.0.2",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite dev",
|
"dev": "vite dev",
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import { defineRegistry, type ComponentRegistry } from "@json-render/svelte";
|
import { defineRegistry } from "@json-render/svelte";
|
||||||
import { explorerCatalog } from "./catalog";
|
import { explorerCatalog } from "./catalog";
|
||||||
|
|
||||||
// Import render components
|
|
||||||
import StackComponent from "./components/Stack.svelte";
|
import StackComponent from "./components/Stack.svelte";
|
||||||
import CardComponent from "./components/Card.svelte";
|
import CardComponent from "./components/Card.svelte";
|
||||||
import GridComponent from "./components/Grid.svelte";
|
import GridComponent from "./components/Grid.svelte";
|
||||||
@@ -28,7 +27,7 @@ import SelectInputComponent from "./components/SelectInput.svelte";
|
|||||||
import TextInputComponent from "./components/TextInput.svelte";
|
import TextInputComponent from "./components/TextInput.svelte";
|
||||||
import ButtonComponent from "./components/Button.svelte";
|
import ButtonComponent from "./components/Button.svelte";
|
||||||
|
|
||||||
const components: ComponentRegistry = {
|
const components = {
|
||||||
Stack: StackComponent,
|
Stack: StackComponent,
|
||||||
Card: CardComponent,
|
Card: CardComponent,
|
||||||
Grid: GridComponent,
|
Grid: GridComponent,
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# example-svelte
|
||||||
|
|
||||||
|
## 0.1.2
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
- @json-render/svelte@0.12.0
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "example-svelte",
|
"name": "example-svelte",
|
||||||
"version": "0.1.1",
|
"version": "0.1.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,5 +1,15 @@
|
|||||||
# vite-renderers
|
# vite-renderers
|
||||||
|
|
||||||
|
## 0.1.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
- @json-render/svelte@0.12.0
|
||||||
|
- @json-render/react@0.12.0
|
||||||
|
- @json-render/vue@0.12.0
|
||||||
|
|
||||||
## 0.1.2
|
## 0.1.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "vite-renderers",
|
"name": "vite-renderers",
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# example-vue
|
# example-vue
|
||||||
|
|
||||||
|
## 0.1.3
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
- @json-render/vue@0.12.0
|
||||||
|
|
||||||
## 0.1.2
|
## 0.1.2
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "example-vue",
|
"name": "example-vue",
|
||||||
"version": "0.1.2",
|
"version": "0.1.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"predev": "command -v portless >/dev/null 2>&1 || (echo '\\nportless is required but not installed. Run: npm i -g portless\\nSee: https://github.com/vercel-labs/portless\\n' && exit 1)",
|
"predev": "command -v portless >/dev/null 2>&1 || (echo '\\nportless is required but not installed. Run: npm i -g portless\\nSee: https://github.com/vercel-labs/portless\\n' && exit 1)",
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @json-render/codegen
|
# @json-render/codegen
|
||||||
|
|
||||||
|
## 0.12.0
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
|
||||||
## 0.11.0
|
## 0.11.0
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/codegen",
|
"name": "@json-render/codegen",
|
||||||
"version": "0.11.0",
|
"version": "0.12.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "Utilities for generating code from json-render UI trees",
|
"description": "Utilities for generating code from json-render UI trees",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,19 @@
|
|||||||
# @json-render/core
|
# @json-render/core
|
||||||
|
|
||||||
|
## 0.12.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 63c339b: Add Svelte renderer, React Email renderer, and MCP Apps integration.
|
||||||
|
|
||||||
|
### New:
|
||||||
|
- **`@json-render/svelte`** — Svelte 5 renderer with runes-based reactivity. Full support for data binding, visibility, actions, validation, watchers, streaming, and repeat scopes. Includes `defineRegistry`, `Renderer`, `schema`, composables, and context providers.
|
||||||
|
- **`@json-render/react-email`** — React Email renderer for generating HTML and plain-text emails from JSON specs. 17 standard components (Html, Head, Body, Container, Section, Row, Column, Heading, Text, Link, Button, Image, Hr, Preview, Markdown). Server-side `renderToHtml` / `renderToPlainText` APIs. Custom catalog and registry support.
|
||||||
|
- **`@json-render/mcp`** — MCP Apps integration that serves json-render UIs as interactive apps inside Claude, ChatGPT, Cursor, VS Code, and other MCP-capable clients. `createMcpApp` server factory, `useJsonRenderApp` React hook for iframes, and `buildAppHtml` utility.
|
||||||
|
|
||||||
|
### Fixed:
|
||||||
|
- **`@json-render/svelte`** — Corrected JSDoc comment and added missing `zod` peer dependency.
|
||||||
|
|
||||||
## 0.11.0
|
## 0.11.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/core",
|
"name": "@json-render/core",
|
||||||
"version": "0.11.0",
|
"version": "0.12.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "JSON becomes real things. Define your catalog, register your components, let AI generate.",
|
"description": "JSON becomes real things. Define your catalog, register your components, let AI generate.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @json-render/image
|
# @json-render/image
|
||||||
|
|
||||||
|
## 0.12.0
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
|
||||||
## 0.11.0
|
## 0.11.0
|
||||||
|
|
||||||
### Minor Changes
|
### Minor Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/image",
|
"name": "@json-render/image",
|
||||||
"version": "0.11.0",
|
"version": "0.12.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "Image renderer for @json-render/core. JSON becomes SVG and PNG images via Satori.",
|
"description": "Image renderer for @json-render/core. JSON becomes SVG and PNG images via Satori.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @json-render/jotai
|
# @json-render/jotai
|
||||||
|
|
||||||
|
## 0.12.0
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
|
||||||
## 0.11.0
|
## 0.11.0
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/jotai",
|
"name": "@json-render/jotai",
|
||||||
"version": "0.11.0",
|
"version": "0.12.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "Jotai adapter for json-render StateStore",
|
"description": "Jotai adapter for json-render StateStore",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# @json-render/mcp
|
||||||
|
|
||||||
|
## 0.12.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 63c339b: Add Svelte renderer, React Email renderer, and MCP Apps integration.
|
||||||
|
|
||||||
|
### New:
|
||||||
|
- **`@json-render/svelte`** — Svelte 5 renderer with runes-based reactivity. Full support for data binding, visibility, actions, validation, watchers, streaming, and repeat scopes. Includes `defineRegistry`, `Renderer`, `schema`, composables, and context providers.
|
||||||
|
- **`@json-render/react-email`** — React Email renderer for generating HTML and plain-text emails from JSON specs. 17 standard components (Html, Head, Body, Container, Section, Row, Column, Heading, Text, Link, Button, Image, Hr, Preview, Markdown). Server-side `renderToHtml` / `renderToPlainText` APIs. Custom catalog and registry support.
|
||||||
|
- **`@json-render/mcp`** — MCP Apps integration that serves json-render UIs as interactive apps inside Claude, ChatGPT, Cursor, VS Code, and other MCP-capable clients. `createMcpApp` server factory, `useJsonRenderApp` React hook for iframes, and `buildAppHtml` utility.
|
||||||
|
|
||||||
|
### Fixed:
|
||||||
|
- **`@json-render/svelte`** — Corrected JSDoc comment and added missing `zod` peer dependency.
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
@@ -0,0 +1,124 @@
|
|||||||
|
# @json-render/mcp
|
||||||
|
|
||||||
|
MCP Apps integration for [json-render](https://github.com/vercel-labs/json-render). Serve json-render UIs as interactive MCP Apps inside Claude, ChatGPT, Cursor, VS Code, and other MCP-capable clients.
|
||||||
|
|
||||||
|
## What are MCP Apps?
|
||||||
|
|
||||||
|
[MCP Apps](https://modelcontextprotocol.io/docs/extensions/apps) is an extension to the Model Context Protocol that lets MCP servers return interactive HTML UIs rendered directly inside chat conversations. Instead of text-only tool responses, users get full interactive interfaces -- dashboards, forms, data visualizations -- embedded inline.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @json-render/mcp @json-render/core @modelcontextprotocol/sdk
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Define your catalog
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { defineCatalog } from "@json-render/core";
|
||||||
|
import { schema } from "@json-render/react/schema";
|
||||||
|
import { shadcnComponentDefinitions } from "@json-render/shadcn/catalog";
|
||||||
|
|
||||||
|
const catalog = defineCatalog(schema, {
|
||||||
|
components: { ...shadcnComponentDefinitions },
|
||||||
|
actions: {},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Create the MCP server
|
||||||
|
|
||||||
|
```ts
|
||||||
|
import { createMcpApp } from "@json-render/mcp";
|
||||||
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||||
|
import fs from "node:fs";
|
||||||
|
|
||||||
|
const server = createMcpApp({
|
||||||
|
name: "My Dashboard",
|
||||||
|
version: "1.0.0",
|
||||||
|
catalog,
|
||||||
|
html: fs.readFileSync("dist/index.html", "utf-8"),
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.connect(new StdioServerTransport());
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Build the UI (iframe)
|
||||||
|
|
||||||
|
Create a React app that uses `useJsonRenderApp` from `@json-render/mcp/app`:
|
||||||
|
|
||||||
|
```tsx
|
||||||
|
import { useJsonRenderApp } from "@json-render/mcp/app";
|
||||||
|
import { JSONUIProvider, Renderer } from "@json-render/react";
|
||||||
|
|
||||||
|
function McpAppView({ registry }) {
|
||||||
|
const { spec, loading, connected, error } = useJsonRenderApp();
|
||||||
|
|
||||||
|
if (error) return <div>Error: {error.message}</div>;
|
||||||
|
if (!spec) return <div>Waiting for spec...</div>;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<JSONUIProvider registry={registry} initialState={spec.state ?? {}}>
|
||||||
|
<Renderer spec={spec} registry={registry} loading={loading} />
|
||||||
|
</JSONUIProvider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Bundle with Vite + `vite-plugin-singlefile` into a single HTML file, then pass it to `createMcpApp` as the `html` option.
|
||||||
|
|
||||||
|
### 4. Connect to a client
|
||||||
|
|
||||||
|
Add to `.cursor/mcp.json` or Claude Desktop config:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"mcpServers": {
|
||||||
|
"my-app": {
|
||||||
|
"command": "node",
|
||||||
|
"args": ["./server.js", "--stdio"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
### Server Side (main export)
|
||||||
|
|
||||||
|
#### `createMcpApp(options)`
|
||||||
|
|
||||||
|
Creates a fully-configured `McpServer` with a json-render tool and UI resource.
|
||||||
|
|
||||||
|
| Option | Type | Description |
|
||||||
|
|--------|------|-------------|
|
||||||
|
| `name` | `string` | Server name shown in client UIs |
|
||||||
|
| `version` | `string` | Server version |
|
||||||
|
| `catalog` | `Catalog` | json-render catalog defining available components |
|
||||||
|
| `html` | `string` | Bundled HTML for the iframe UI |
|
||||||
|
| `tool` | `McpToolOptions` | Optional tool name/title/description overrides |
|
||||||
|
|
||||||
|
#### `registerJsonRenderTool(server, options)`
|
||||||
|
|
||||||
|
Register a json-render tool on an existing `McpServer`.
|
||||||
|
|
||||||
|
#### `registerJsonRenderResource(server, options)`
|
||||||
|
|
||||||
|
Register a json-render UI resource on an existing `McpServer`.
|
||||||
|
|
||||||
|
### Client Side (`@json-render/mcp/app`)
|
||||||
|
|
||||||
|
#### `useJsonRenderApp(options?)`
|
||||||
|
|
||||||
|
React hook for the iframe-side app. Connects to the MCP host, receives tool results, and maintains the current json-render spec.
|
||||||
|
|
||||||
|
Returns `{ spec, loading, connected, connecting, error, app, callServerTool }`.
|
||||||
|
|
||||||
|
#### `buildAppHtml(options)`
|
||||||
|
|
||||||
|
Generate a self-contained HTML string from bundled JS/CSS for use as a UI resource.
|
||||||
|
|
||||||
|
## Client Support
|
||||||
|
|
||||||
|
MCP Apps are supported by Claude, ChatGPT, VS Code (Copilot), Cursor, Goose, and Postman.
|
||||||
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"name": "@json-render/mcp",
|
||||||
|
"version": "0.12.0",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"description": "MCP Apps integration for @json-render/core. Serve json-render UIs as interactive MCP Apps in Claude, ChatGPT, Cursor, and VS Code.",
|
||||||
|
"keywords": [
|
||||||
|
"json",
|
||||||
|
"ui",
|
||||||
|
"mcp",
|
||||||
|
"model-context-protocol",
|
||||||
|
"mcp-apps",
|
||||||
|
"ai",
|
||||||
|
"generative-ui",
|
||||||
|
"llm",
|
||||||
|
"renderer",
|
||||||
|
"claude",
|
||||||
|
"chatgpt",
|
||||||
|
"cursor"
|
||||||
|
],
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||||
|
"directory": "packages/mcp"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||||
|
"bugs": {
|
||||||
|
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||||
|
},
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
},
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"module": "./dist/index.mjs",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/index.mjs",
|
||||||
|
"require": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"./app": {
|
||||||
|
"types": "./dist/app.d.ts",
|
||||||
|
"import": "./dist/app.mjs",
|
||||||
|
"require": "./dist/app.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsup",
|
||||||
|
"dev": "tsup --watch",
|
||||||
|
"typecheck": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@json-render/core": "workspace:*",
|
||||||
|
"@modelcontextprotocol/ext-apps": "^1.2.0",
|
||||||
|
"@modelcontextprotocol/sdk": "^1.27.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@internal/typescript-config": "workspace:*",
|
||||||
|
"@types/react": "19.2.3",
|
||||||
|
"tsup": "^8.0.2",
|
||||||
|
"typescript": "^5.4.5"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^19.0.0",
|
||||||
|
"react-dom": "^19.0.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
/**
|
||||||
|
* Client-side (iframe) utilities for rendering json-render specs
|
||||||
|
* inside an MCP App view.
|
||||||
|
*
|
||||||
|
* This module is intended to run **inside the sandboxed iframe** that
|
||||||
|
* MCP hosts render. It connects to the host via the MCP Apps protocol,
|
||||||
|
* receives tool results containing json-render specs, and provides
|
||||||
|
* React hooks / helpers to render them.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```tsx
|
||||||
|
* import { useJsonRenderApp } from "@json-render/mcp/app";
|
||||||
|
* import { Renderer } from "@json-render/react";
|
||||||
|
*
|
||||||
|
* function McpAppView({ registry }) {
|
||||||
|
* const { spec, loading } = useJsonRenderApp();
|
||||||
|
* return <Renderer spec={spec} registry={registry} loading={loading} />;
|
||||||
|
* }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @packageDocumentation
|
||||||
|
*/
|
||||||
|
|
||||||
|
export { useJsonRenderApp } from "./use-json-render-app.js";
|
||||||
|
export type {
|
||||||
|
UseJsonRenderAppOptions,
|
||||||
|
UseJsonRenderAppReturn,
|
||||||
|
} from "./use-json-render-app.js";
|
||||||
|
export { buildAppHtml } from "./build-app-html.js";
|
||||||
|
export type { BuildAppHtmlOptions } from "./build-app-html.js";
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* Options for `buildAppHtml`.
|
||||||
|
*/
|
||||||
|
export interface BuildAppHtmlOptions {
|
||||||
|
/** Title for the HTML page. Defaults to `"json-render"`. */
|
||||||
|
title?: string;
|
||||||
|
/**
|
||||||
|
* Inline CSS to inject into the page `<style>` tag.
|
||||||
|
* Use this to include Tailwind output or custom styles.
|
||||||
|
*/
|
||||||
|
css?: string;
|
||||||
|
/**
|
||||||
|
* Bundled JavaScript for the app entry point.
|
||||||
|
* This should be the output of a bundler (Vite, esbuild, etc.)
|
||||||
|
* that bundles your React app, registry, and the
|
||||||
|
* `useJsonRenderApp` hook into a single script.
|
||||||
|
*/
|
||||||
|
js: string;
|
||||||
|
/**
|
||||||
|
* Additional `<head>` content (meta tags, font links, etc.).
|
||||||
|
*/
|
||||||
|
head?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build a self-contained HTML string for an MCP App UI resource.
|
||||||
|
*
|
||||||
|
* The resulting HTML is designed to be served as a `ui://` resource
|
||||||
|
* via `registerJsonRenderResource` or `createMcpApp`.
|
||||||
|
*
|
||||||
|
* Typically you'd use a bundler (Vite + `vite-plugin-singlefile`, or
|
||||||
|
* esbuild) to produce the `js` and `css` strings, then pass them here.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* import { buildAppHtml } from "@json-render/mcp/app";
|
||||||
|
* import { readFileSync } from "node:fs";
|
||||||
|
*
|
||||||
|
* const html = buildAppHtml({
|
||||||
|
* title: "Dashboard",
|
||||||
|
* js: readFileSync("dist/app.js", "utf-8"),
|
||||||
|
* css: readFileSync("dist/app.css", "utf-8"),
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export function buildAppHtml(options: BuildAppHtmlOptions): string {
|
||||||
|
const { title = "json-render", css = "", js, head = "" } = options;
|
||||||
|
|
||||||
|
return `<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>${escapeHtml(title)}</title>
|
||||||
|
${head}
|
||||||
|
<style>${css}</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module">${js}</script>
|
||||||
|
</body>
|
||||||
|
</html>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeHtml(str: string): string {
|
||||||
|
return str
|
||||||
|
.replace(/&/g, "&")
|
||||||
|
.replace(/</g, "<")
|
||||||
|
.replace(/>/g, ">")
|
||||||
|
.replace(/"/g, """);
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
export type {
|
||||||
|
CreateMcpAppOptions,
|
||||||
|
McpToolOptions,
|
||||||
|
RegisterToolOptions,
|
||||||
|
RegisterResourceOptions,
|
||||||
|
} from "./types.js";
|
||||||
|
|
||||||
|
export {
|
||||||
|
createMcpApp,
|
||||||
|
registerJsonRenderTool,
|
||||||
|
registerJsonRenderResource,
|
||||||
|
} from "./server.js";
|
||||||
@@ -0,0 +1,158 @@
|
|||||||
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
||||||
|
import type {
|
||||||
|
CreateMcpAppOptions,
|
||||||
|
RegisterToolOptions,
|
||||||
|
RegisterResourceOptions,
|
||||||
|
} from "./types.js";
|
||||||
|
|
||||||
|
const RESOURCE_MIME_TYPE = "text/html;profile=mcp-app";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dynamically import the ext-apps server helpers to avoid CJS/ESM type
|
||||||
|
* mismatches at compile time while still getting the proper runtime
|
||||||
|
* `_meta.ui` normalization that hosts require.
|
||||||
|
*/
|
||||||
|
async function getExtApps() {
|
||||||
|
const mod = await import("@modelcontextprotocol/ext-apps/server");
|
||||||
|
return mod;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a json-render tool on an existing MCP server.
|
||||||
|
*
|
||||||
|
* Uses `registerAppTool` from `@modelcontextprotocol/ext-apps/server`
|
||||||
|
* so that MCP Apps-capable hosts (Claude, VS Code, Cursor, ChatGPT)
|
||||||
|
* see the `_meta.ui.resourceUri` in the tool listing and know to
|
||||||
|
* fetch and render the `ui://` resource as an interactive iframe.
|
||||||
|
*
|
||||||
|
* The tool accepts a json-render spec as input and returns it as text
|
||||||
|
* content, which the iframe receives via `ontoolresult`.
|
||||||
|
*/
|
||||||
|
export async function registerJsonRenderTool(
|
||||||
|
server: McpServer,
|
||||||
|
options: RegisterToolOptions,
|
||||||
|
): Promise<void> {
|
||||||
|
const { catalog, name, title, description, resourceUri } = options;
|
||||||
|
const { registerAppTool } = await getExtApps();
|
||||||
|
|
||||||
|
const specZodSchema = catalog.zodSchema();
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(registerAppTool as any)(
|
||||||
|
server,
|
||||||
|
name,
|
||||||
|
{
|
||||||
|
title,
|
||||||
|
description,
|
||||||
|
inputSchema: { spec: specZodSchema },
|
||||||
|
_meta: { ui: { resourceUri } },
|
||||||
|
},
|
||||||
|
async (args: { spec?: unknown }) => {
|
||||||
|
const spec = args.spec;
|
||||||
|
const validation = catalog.validate(spec);
|
||||||
|
const validSpec = validation.success ? validation.data : spec;
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: [
|
||||||
|
{
|
||||||
|
type: "text" as const,
|
||||||
|
text: JSON.stringify(validSpec),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a json-render UI resource on an existing MCP server.
|
||||||
|
*
|
||||||
|
* The resource serves the self-contained HTML page that renders
|
||||||
|
* json-render specs received from tool results.
|
||||||
|
*/
|
||||||
|
export async function registerJsonRenderResource(
|
||||||
|
server: McpServer,
|
||||||
|
options: RegisterResourceOptions,
|
||||||
|
): Promise<void> {
|
||||||
|
const { resourceUri, html } = options;
|
||||||
|
const { registerAppResource, RESOURCE_MIME_TYPE: mimeType } =
|
||||||
|
await getExtApps();
|
||||||
|
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
(registerAppResource as any)(
|
||||||
|
server,
|
||||||
|
resourceUri,
|
||||||
|
resourceUri,
|
||||||
|
{ mimeType },
|
||||||
|
async () => ({
|
||||||
|
contents: [
|
||||||
|
{
|
||||||
|
uri: resourceUri,
|
||||||
|
mimeType,
|
||||||
|
text: html,
|
||||||
|
_meta: {
|
||||||
|
ui: {
|
||||||
|
csp: {
|
||||||
|
resourceDomains: ["https:"],
|
||||||
|
connectDomains: ["https:"],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a fully-configured MCP server that serves a json-render catalog
|
||||||
|
* as an MCP App.
|
||||||
|
*
|
||||||
|
* This is the main entry point for most users. It creates an `McpServer`,
|
||||||
|
* registers the render tool and UI resource, and returns the server
|
||||||
|
* ready for transport connection.
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* ```ts
|
||||||
|
* import { createMcpApp } from "@json-render/mcp";
|
||||||
|
*
|
||||||
|
* const server = createMcpApp({
|
||||||
|
* name: "My Dashboard",
|
||||||
|
* version: "1.0.0",
|
||||||
|
* catalog: myCatalog,
|
||||||
|
* html: myBundledHtml,
|
||||||
|
* });
|
||||||
|
*
|
||||||
|
* // Connect via stdio
|
||||||
|
* import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
||||||
|
* await server.connect(new StdioServerTransport());
|
||||||
|
* ```
|
||||||
|
*/
|
||||||
|
export async function createMcpApp(
|
||||||
|
options: CreateMcpAppOptions,
|
||||||
|
): Promise<McpServer> {
|
||||||
|
const { name, version, catalog, html, tool } = options;
|
||||||
|
|
||||||
|
const toolName = tool?.name ?? "render-ui";
|
||||||
|
const toolTitle = tool?.title ?? "Render UI";
|
||||||
|
const resourceUri = `ui://${toolName}/view.html`;
|
||||||
|
|
||||||
|
const catalogPrompt = catalog.prompt();
|
||||||
|
const toolDescription =
|
||||||
|
tool?.description ??
|
||||||
|
`Render an interactive UI. The spec argument must be a json-render spec conforming to the catalog.\n\n${catalogPrompt}`;
|
||||||
|
|
||||||
|
const server = new McpServer({ name, version });
|
||||||
|
|
||||||
|
await registerJsonRenderTool(server, {
|
||||||
|
catalog,
|
||||||
|
name: toolName,
|
||||||
|
title: toolTitle,
|
||||||
|
description: toolDescription,
|
||||||
|
resourceUri,
|
||||||
|
});
|
||||||
|
|
||||||
|
await registerJsonRenderResource(server, { resourceUri, html });
|
||||||
|
|
||||||
|
return server;
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import type { Catalog } from "@json-render/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for creating an MCP App server backed by a json-render catalog.
|
||||||
|
*/
|
||||||
|
export interface CreateMcpAppOptions {
|
||||||
|
/** Display name for the MCP server shown in client UIs. */
|
||||||
|
name: string;
|
||||||
|
/** Semantic version of the MCP server. */
|
||||||
|
version: string;
|
||||||
|
/** The json-render catalog defining available components and actions. */
|
||||||
|
catalog: Catalog;
|
||||||
|
/**
|
||||||
|
* Pre-built HTML string for the UI resource.
|
||||||
|
* Generate this with `buildAppHtml` from `@json-render/mcp/app` or
|
||||||
|
* provide your own self-contained HTML page.
|
||||||
|
*/
|
||||||
|
html: string;
|
||||||
|
/**
|
||||||
|
* Optional tool configuration overrides.
|
||||||
|
*/
|
||||||
|
tool?: McpToolOptions;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for configuring the MCP tool that renders json-render specs.
|
||||||
|
*/
|
||||||
|
export interface McpToolOptions {
|
||||||
|
/** Tool name exposed to the LLM. Defaults to `"render-ui"`. */
|
||||||
|
name?: string;
|
||||||
|
/** Human-readable title. Defaults to `"Render UI"`. */
|
||||||
|
title?: string;
|
||||||
|
/** Tool description shown to the LLM. When omitted, a description is
|
||||||
|
* auto-generated from the catalog prompt. */
|
||||||
|
description?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for registering the MCP App tool.
|
||||||
|
*/
|
||||||
|
export interface RegisterToolOptions {
|
||||||
|
/** The json-render catalog. */
|
||||||
|
catalog: Catalog;
|
||||||
|
/** Tool name. */
|
||||||
|
name: string;
|
||||||
|
/** Tool title. */
|
||||||
|
title: string;
|
||||||
|
/** Tool description. */
|
||||||
|
description: string;
|
||||||
|
/** The `ui://` resource URI this tool's view is served from. */
|
||||||
|
resourceUri: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for registering the MCP App UI resource.
|
||||||
|
*/
|
||||||
|
export interface RegisterResourceOptions {
|
||||||
|
/** The `ui://` resource URI. */
|
||||||
|
resourceUri: string;
|
||||||
|
/** Self-contained HTML string for the view. */
|
||||||
|
html: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,139 @@
|
|||||||
|
import { useState, useEffect, useCallback, useRef } from "react";
|
||||||
|
import type { Spec } from "@json-render/core";
|
||||||
|
import { App } from "@modelcontextprotocol/ext-apps";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Options for the `useJsonRenderApp` hook.
|
||||||
|
*/
|
||||||
|
export interface UseJsonRenderAppOptions {
|
||||||
|
/** App name shown during initialization. Defaults to `"json-render"`. */
|
||||||
|
name?: string;
|
||||||
|
/** App version. Defaults to `"1.0.0"`. */
|
||||||
|
version?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return value of `useJsonRenderApp`.
|
||||||
|
*/
|
||||||
|
export interface UseJsonRenderAppReturn {
|
||||||
|
/** The current json-render spec (null until the first tool result). */
|
||||||
|
spec: Spec | null;
|
||||||
|
/** Whether the app is still connecting to the host. */
|
||||||
|
connecting: boolean;
|
||||||
|
/** Whether the app is connected to the host. */
|
||||||
|
connected: boolean;
|
||||||
|
/** Connection error, if any. */
|
||||||
|
error: Error | null;
|
||||||
|
/** Whether the spec is still being received / parsed. */
|
||||||
|
loading: boolean;
|
||||||
|
/** The underlying MCP App instance. */
|
||||||
|
app: App | null;
|
||||||
|
/**
|
||||||
|
* Call a tool on the MCP server and update the spec from the result.
|
||||||
|
* Useful for refresh / drill-down interactions.
|
||||||
|
*/
|
||||||
|
callServerTool: (
|
||||||
|
name: string,
|
||||||
|
args?: Record<string, unknown>,
|
||||||
|
) => Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ToolResultContent {
|
||||||
|
type: string;
|
||||||
|
text?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
function parseSpecFromToolResult(result: {
|
||||||
|
content?: ToolResultContent[];
|
||||||
|
}): Spec | null {
|
||||||
|
const textContent = result.content?.find(
|
||||||
|
(c: ToolResultContent) => c.type === "text",
|
||||||
|
);
|
||||||
|
if (!textContent?.text) return null;
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(textContent.text);
|
||||||
|
if (parsed && typeof parsed === "object" && "spec" in parsed) {
|
||||||
|
return parsed.spec as Spec;
|
||||||
|
}
|
||||||
|
return parsed as Spec;
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* React hook that connects to the MCP host, listens for tool results,
|
||||||
|
* and maintains the current json-render spec.
|
||||||
|
*
|
||||||
|
* Follows the official MCP Apps pattern: create an `App` instance,
|
||||||
|
* register the `ontoolresult` handler, then call `app.connect()`
|
||||||
|
* which internally creates a PostMessageTransport to the host.
|
||||||
|
*/
|
||||||
|
export function useJsonRenderApp(
|
||||||
|
options: UseJsonRenderAppOptions = {},
|
||||||
|
): UseJsonRenderAppReturn {
|
||||||
|
const { name = "json-render", version = "1.0.0" } = options;
|
||||||
|
|
||||||
|
const [spec, setSpec] = useState<Spec | null>(null);
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [connected, setConnected] = useState(false);
|
||||||
|
const [error, setError] = useState<Error | null>(null);
|
||||||
|
const appRef = useRef<App | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const app = new App({ name, version });
|
||||||
|
appRef.current = app;
|
||||||
|
|
||||||
|
app.ontoolresult = (result: { content?: ToolResultContent[] }) => {
|
||||||
|
const parsed = parseSpecFromToolResult(result);
|
||||||
|
if (parsed) {
|
||||||
|
setSpec(parsed);
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Let the App class handle transport creation internally,
|
||||||
|
// matching the official MCP Apps quickstart pattern.
|
||||||
|
app
|
||||||
|
.connect()
|
||||||
|
.then(() => {
|
||||||
|
setConnected(true);
|
||||||
|
})
|
||||||
|
.catch((err: unknown) => {
|
||||||
|
setError(err instanceof Error ? err : new Error(String(err)));
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
app.close().catch(() => {});
|
||||||
|
};
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const callServerTool = useCallback(
|
||||||
|
async (toolName: string, args: Record<string, unknown> = {}) => {
|
||||||
|
if (!appRef.current) return;
|
||||||
|
setLoading(true);
|
||||||
|
try {
|
||||||
|
const result = await appRef.current.callServerTool({
|
||||||
|
name: toolName,
|
||||||
|
arguments: args,
|
||||||
|
});
|
||||||
|
const parsed = parseSpecFromToolResult(result);
|
||||||
|
if (parsed) setSpec(parsed);
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
spec,
|
||||||
|
connecting: !connected && !error,
|
||||||
|
connected,
|
||||||
|
error,
|
||||||
|
loading,
|
||||||
|
app: appRef.current,
|
||||||
|
callServerTool,
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "@internal/typescript-config/react-library.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src"
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
import { defineConfig } from "tsup";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
entry: ["src/index.ts", "src/app.ts"],
|
||||||
|
format: ["cjs", "esm"],
|
||||||
|
dts: true,
|
||||||
|
sourcemap: true,
|
||||||
|
clean: true,
|
||||||
|
external: [
|
||||||
|
"react",
|
||||||
|
"react-dom",
|
||||||
|
"@json-render/core",
|
||||||
|
"@json-render/react",
|
||||||
|
"@modelcontextprotocol/sdk",
|
||||||
|
"@modelcontextprotocol/ext-apps",
|
||||||
|
],
|
||||||
|
});
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# @json-render/react-email
|
||||||
|
|
||||||
|
## 0.12.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 63c339b: Add Svelte renderer, React Email renderer, and MCP Apps integration.
|
||||||
|
|
||||||
|
### New:
|
||||||
|
- **`@json-render/svelte`** — Svelte 5 renderer with runes-based reactivity. Full support for data binding, visibility, actions, validation, watchers, streaming, and repeat scopes. Includes `defineRegistry`, `Renderer`, `schema`, composables, and context providers.
|
||||||
|
- **`@json-render/react-email`** — React Email renderer for generating HTML and plain-text emails from JSON specs. 17 standard components (Html, Head, Body, Container, Section, Row, Column, Heading, Text, Link, Button, Image, Hr, Preview, Markdown). Server-side `renderToHtml` / `renderToPlainText` APIs. Custom catalog and registry support.
|
||||||
|
- **`@json-render/mcp`** — MCP Apps integration that serves json-render UIs as interactive apps inside Claude, ChatGPT, Cursor, VS Code, and other MCP-capable clients. `createMcpApp` server factory, `useJsonRenderApp` React hook for iframes, and `buildAppHtml` utility.
|
||||||
|
|
||||||
|
### Fixed:
|
||||||
|
- **`@json-render/svelte`** — Corrected JSDoc comment and added missing `zod` peer dependency.
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/react-email",
|
"name": "@json-render/react-email",
|
||||||
"version": "0.8.0",
|
"version": "0.12.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "React Email renderer for @json-render/core. JSON becomes HTML emails.",
|
"description": "React Email renderer for @json-render/core. JSON becomes HTML emails.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @json-render/react-native
|
# @json-render/react-native
|
||||||
|
|
||||||
|
## 0.12.0
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
|
||||||
## 0.11.0
|
## 0.11.0
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/react-native",
|
"name": "@json-render/react-native",
|
||||||
"version": "0.11.0",
|
"version": "0.12.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "React Native renderer for @json-render/core. JSON becomes React Native components.",
|
"description": "React Native renderer for @json-render/core. JSON becomes React Native components.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @json-render/react-pdf
|
# @json-render/react-pdf
|
||||||
|
|
||||||
|
## 0.12.0
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
|
||||||
## 0.11.0
|
## 0.11.0
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/react-pdf",
|
"name": "@json-render/react-pdf",
|
||||||
"version": "0.11.0",
|
"version": "0.12.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "React PDF renderer for @json-render/core. JSON becomes PDF documents.",
|
"description": "React PDF renderer for @json-render/core. JSON becomes PDF documents.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @internal/react-state
|
# @internal/react-state
|
||||||
|
|
||||||
|
## 0.8.5
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
|
||||||
## 0.8.4
|
## 0.8.4
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@internal/react-state",
|
"name": "@internal/react-state",
|
||||||
"version": "0.8.4",
|
"version": "0.8.5",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "Shared React state context for json-render renderer packages",
|
"description": "Shared React state context for json-render renderer packages",
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @json-render/react
|
# @json-render/react
|
||||||
|
|
||||||
|
## 0.12.0
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
|
||||||
## 0.11.0
|
## 0.11.0
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/react",
|
"name": "@json-render/react",
|
||||||
"version": "0.11.0",
|
"version": "0.12.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "React renderer for @json-render/core. JSON becomes React components.",
|
"description": "React renderer for @json-render/core. JSON becomes React components.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @json-render/redux
|
# @json-render/redux
|
||||||
|
|
||||||
|
## 0.12.0
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
|
||||||
## 0.11.0
|
## 0.11.0
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/redux",
|
"name": "@json-render/redux",
|
||||||
"version": "0.11.0",
|
"version": "0.12.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "Redux adapter for json-render StateStore",
|
"description": "Redux adapter for json-render StateStore",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @json-render/remotion
|
# @json-render/remotion
|
||||||
|
|
||||||
|
## 0.12.0
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
|
||||||
## 0.11.0
|
## 0.11.0
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/remotion",
|
"name": "@json-render/remotion",
|
||||||
"version": "0.11.0",
|
"version": "0.12.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "Remotion renderer for @json-render/core. JSON becomes video compositions.",
|
"description": "Remotion renderer for @json-render/core. JSON becomes video compositions.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
# @json-render/shadcn
|
# @json-render/shadcn
|
||||||
|
|
||||||
|
## 0.12.0
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
- @json-render/react@0.12.0
|
||||||
|
|
||||||
## 0.11.0
|
## 0.11.0
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/shadcn",
|
"name": "@json-render/shadcn",
|
||||||
"version": "0.11.0",
|
"version": "0.12.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "shadcn/ui component library for @json-render/core. JSON becomes beautiful Tailwind-styled React components.",
|
"description": "shadcn/ui component library for @json-render/core. JSON becomes beautiful Tailwind-styled React components.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# @json-render/svelte
|
||||||
|
|
||||||
|
## 0.12.0
|
||||||
|
|
||||||
|
### Minor Changes
|
||||||
|
|
||||||
|
- 63c339b: Add Svelte renderer, React Email renderer, and MCP Apps integration.
|
||||||
|
|
||||||
|
### New:
|
||||||
|
- **`@json-render/svelte`** — Svelte 5 renderer with runes-based reactivity. Full support for data binding, visibility, actions, validation, watchers, streaming, and repeat scopes. Includes `defineRegistry`, `Renderer`, `schema`, composables, and context providers.
|
||||||
|
- **`@json-render/react-email`** — React Email renderer for generating HTML and plain-text emails from JSON specs. 17 standard components (Html, Head, Body, Container, Section, Row, Column, Heading, Text, Link, Button, Image, Hr, Preview, Markdown). Server-side `renderToHtml` / `renderToPlainText` APIs. Custom catalog and registry support.
|
||||||
|
- **`@json-render/mcp`** — MCP Apps integration that serves json-render UIs as interactive apps inside Claude, ChatGPT, Cursor, VS Code, and other MCP-capable clients. `createMcpApp` server factory, `useJsonRenderApp` React hook for iframes, and `buildAppHtml` utility.
|
||||||
|
|
||||||
|
### Fixed:
|
||||||
|
- **`@json-render/svelte`** — Corrected JSDoc comment and added missing `zod` peer dependency.
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
@@ -0,0 +1,155 @@
|
|||||||
|
# @json-render/svelte
|
||||||
|
|
||||||
|
Svelte 5 renderer for `@json-render/core`. Turn JSON specs into Svelte components with runes-based reactivity.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
npm install @json-render/core @json-render/svelte
|
||||||
|
```
|
||||||
|
|
||||||
|
Peer dependencies: `svelte ^5.0.0` and `zod ^4.0.0`.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
### 1. Create a Catalog
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineCatalog } from "@json-render/core";
|
||||||
|
import { schema } from "@json-render/svelte/schema";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export const catalog = defineCatalog(schema, {
|
||||||
|
components: {
|
||||||
|
Card: {
|
||||||
|
props: z.object({
|
||||||
|
title: z.string(),
|
||||||
|
description: z.string().nullable(),
|
||||||
|
}),
|
||||||
|
description: "A card container",
|
||||||
|
},
|
||||||
|
Button: {
|
||||||
|
props: z.object({
|
||||||
|
label: z.string(),
|
||||||
|
action: z.string(),
|
||||||
|
}),
|
||||||
|
description: "A clickable button",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
submit: { description: "Submit the form" },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Define Component Implementations
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { defineRegistry } from "@json-render/svelte";
|
||||||
|
import { catalog } from "./catalog";
|
||||||
|
|
||||||
|
export const { registry } = defineRegistry(catalog, {
|
||||||
|
components: {
|
||||||
|
Card: ({ props, children }) => /* Svelte 5 snippet */,
|
||||||
|
Button: ({ props, emit }) => /* Svelte 5 snippet */,
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
submit: async (params) => { /* handle submit */ },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Render Specs
|
||||||
|
|
||||||
|
```svelte
|
||||||
|
<script>
|
||||||
|
import { Renderer, StateProvider, ActionProvider } from "@json-render/svelte";
|
||||||
|
import { registry } from "./registry";
|
||||||
|
|
||||||
|
const spec = { /* ... */ };
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<StateProvider initialState={{ form: { name: "" } }}>
|
||||||
|
<ActionProvider handlers={{ submit: handleSubmit }}>
|
||||||
|
<Renderer {spec} {registry} />
|
||||||
|
</ActionProvider>
|
||||||
|
</StateProvider>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Providers
|
||||||
|
|
||||||
|
| Provider | Purpose |
|
||||||
|
|----------|---------|
|
||||||
|
| `StateProvider` | Share state across components (JSON Pointer paths). Accepts optional `store` prop for controlled mode. |
|
||||||
|
| `ActionProvider` | Handle actions dispatched via the event system |
|
||||||
|
| `VisibilityProvider` | Enable conditional rendering based on state |
|
||||||
|
| `ValidationProvider` | Form field validation |
|
||||||
|
| `RepeatScopeProvider` | Repeat scope for list rendering |
|
||||||
|
| `FunctionsContextProvider` | Register `$computed` functions |
|
||||||
|
| `JsonUIProvider` | All-in-one provider combining state, actions, visibility, validation, and functions |
|
||||||
|
|
||||||
|
## Context Accessors
|
||||||
|
|
||||||
|
Svelte 5 uses `getContext`-based accessors instead of hooks:
|
||||||
|
|
||||||
|
| Accessor | Purpose |
|
||||||
|
|----------|---------|
|
||||||
|
| `getStateContext()` | Access state context (`state`, `get`, `set`) |
|
||||||
|
| `getStateValue(path)` | Get single value from state |
|
||||||
|
| `getBoundProp(value, binding)` | Two-way binding for `$bindState`/`$bindItem` |
|
||||||
|
| `getVisibilityContext()` | Access visibility context |
|
||||||
|
| `isVisible(condition)` | Check if a visibility condition is met |
|
||||||
|
| `getActionContext()` | Access action context |
|
||||||
|
| `getAction(binding)` | Get a single action dispatch function |
|
||||||
|
| `getValidationContext()` | Access validation context |
|
||||||
|
| `getFieldValidation(path, config)` | Field validation state |
|
||||||
|
| `getRepeatScope()` | Access current repeat scope |
|
||||||
|
| `getFunctions()` | Access registered computed functions |
|
||||||
|
|
||||||
|
## Streaming
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { createUIStream, createChatUI } from "@json-render/svelte";
|
||||||
|
|
||||||
|
const stream = createUIStream({ endpoint: "/api/generate" });
|
||||||
|
|
||||||
|
// Or for chat-style UI:
|
||||||
|
const chat = createChatUI({ endpoint: "/api/chat" });
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Exports
|
||||||
|
|
||||||
|
| Export | Purpose |
|
||||||
|
|--------|---------|
|
||||||
|
| `defineRegistry` | Create a type-safe component registry from a catalog |
|
||||||
|
| `createRenderer` | Create a renderer function from a registry |
|
||||||
|
| `Renderer` | Render a spec using a registry |
|
||||||
|
| `CatalogRenderer` | Render with automatic provider wiring |
|
||||||
|
| `JsonUIProvider` | All-in-one provider |
|
||||||
|
| `schema` | Element tree schema (includes built-in state actions) |
|
||||||
|
| `createUIStream` | Stream specs from an API endpoint |
|
||||||
|
| `createChatUI` | Chat-style streaming interface |
|
||||||
|
| `flatToTree` | Convert flat spec to tree format |
|
||||||
|
| `ConfirmDialog` | Confirmation dialog component |
|
||||||
|
| `ConfirmDialogManager` | Manage multiple confirmation dialogs |
|
||||||
|
|
||||||
|
### Types
|
||||||
|
|
||||||
|
| Export | Purpose |
|
||||||
|
|--------|---------|
|
||||||
|
| `ComponentContext` | Typed component render function context (catalog-aware) |
|
||||||
|
| `BaseComponentProps` | Catalog-agnostic base type for reusable component libraries |
|
||||||
|
| `EventHandle` | Event handle with `emit()`, `shouldPreventDefault`, `bound` |
|
||||||
|
| `ComponentFn` | Component render function type |
|
||||||
|
| `SetState` | State setter type |
|
||||||
|
| `StateModel` | State model type |
|
||||||
|
| `SvelteSchema` | Schema type for the Svelte renderer |
|
||||||
|
| `SvelteSpec` | Spec type for the Svelte renderer |
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Full API reference: [json-render.dev/docs/api/svelte](https://json-render.dev/docs/api/svelte).
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Apache-2.0
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/svelte",
|
"name": "@json-render/svelte",
|
||||||
"version": "0.6.1",
|
"version": "0.12.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "Svelte 5 renderer for @json-render/core. JSON becomes Svelte components.",
|
"description": "Svelte 5 renderer for @json-render/core. JSON becomes Svelte components.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -65,6 +65,7 @@
|
|||||||
"typescript": "^5.4.5"
|
"typescript": "^5.4.5"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"svelte": "^5.0.0"
|
"svelte": "^5.0.0",
|
||||||
|
"zod": "^4.0.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,8 @@
|
|||||||
* Props for JSONUIProvider
|
* Props for JSONUIProvider
|
||||||
*/
|
*/
|
||||||
export interface JSONUIProviderProps {
|
export interface JSONUIProviderProps {
|
||||||
/** Component registry */
|
/** Component registry (passed through for convenience; not used internally) */
|
||||||
registry: ComponentRegistry;
|
registry?: ComponentRegistry;
|
||||||
/**
|
/**
|
||||||
* External store (controlled mode). When provided, `initialState` and
|
* External store (controlled mode). When provided, `initialState` and
|
||||||
* `onStateChange` are ignored.
|
* `onStateChange` are ignored.
|
||||||
|
|||||||
@@ -211,7 +211,7 @@ export interface CreateRendererProps {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component map type — maps component names to Vue components
|
* Component map type — maps component names to Svelte components
|
||||||
*/
|
*/
|
||||||
export type ComponentMap<
|
export type ComponentMap<
|
||||||
TComponents extends Record<string, { props: unknown }>,
|
TComponents extends Record<string, { props: unknown }>,
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @json-render/vue
|
# @json-render/vue
|
||||||
|
|
||||||
|
## 0.12.0
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
|
||||||
## 0.11.0
|
## 0.11.0
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/vue",
|
"name": "@json-render/vue",
|
||||||
"version": "0.11.0",
|
"version": "0.12.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "Vue renderer for @json-render/core. JSON becomes Vue components.",
|
"description": "Vue renderer for @json-render/core. JSON becomes Vue components.",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @json-render/xstate
|
# @json-render/xstate
|
||||||
|
|
||||||
|
## 0.12.0
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
|
||||||
## 0.11.0
|
## 0.11.0
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/xstate",
|
"name": "@json-render/xstate",
|
||||||
"version": "0.11.0",
|
"version": "0.12.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "XState Store adapter for json-render StateStore",
|
"description": "XState Store adapter for json-render StateStore",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
@@ -1,5 +1,12 @@
|
|||||||
# @json-render/zustand
|
# @json-render/zustand
|
||||||
|
|
||||||
|
## 0.12.0
|
||||||
|
|
||||||
|
### Patch Changes
|
||||||
|
|
||||||
|
- Updated dependencies [63c339b]
|
||||||
|
- @json-render/core@0.12.0
|
||||||
|
|
||||||
## 0.11.0
|
## 0.11.0
|
||||||
|
|
||||||
### Patch Changes
|
### Patch Changes
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@json-render/zustand",
|
"name": "@json-render/zustand",
|
||||||
"version": "0.11.0",
|
"version": "0.12.0",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"description": "Zustand adapter for json-render StateStore",
|
"description": "Zustand adapter for json-render StateStore",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user