mirror of
https://github.com/vercel-labs/json-render.git
synced 2026-10-02 12:00:58 +08:00
Compare commits
9
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
caa90b8b84 | ||
|
|
54a1ecf817 | ||
|
|
dc9e9d17f3 | ||
|
|
1977fb3a11 | ||
|
|
4606c01085 | ||
|
|
c1a700d719 | ||
|
|
6f15faaae0 | ||
|
|
63c339b4bb | ||
|
|
1cc87310c9 |
@@ -17,7 +17,9 @@
|
||||
"@json-render/jotai",
|
||||
"@json-render/vue",
|
||||
"@json-render/xstate",
|
||||
"@json-render/image"
|
||||
"@json-render/image",
|
||||
"@json-render/mcp",
|
||||
"@json-render/svelte"
|
||||
]
|
||||
],
|
||||
"linked": [],
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"mcpServers": {
|
||||
"json-render": {
|
||||
"command": "npx",
|
||||
"args": ["tsx", "examples/mcp/server.ts", "--stdio"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@
|
||||
node_modules
|
||||
.pnp
|
||||
.pnp.js
|
||||
.pnpm-store/
|
||||
|
||||
# Local env files
|
||||
.env*
|
||||
|
||||
Vendored
+9
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"servers": {
|
||||
"json-render": {
|
||||
"type": "stdio",
|
||||
"command": "npx",
|
||||
"args": ["tsx", "examples/mcp/server.ts", "--stdio"]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,7 +98,7 @@ When asked to prepare a release (e.g. "prepare v0.12.0"):
|
||||
- An API reference page at `apps/web/app/(main)/docs/api/<name>/page.mdx`
|
||||
- An entry in `apps/web/lib/page-titles.ts` and `apps/web/lib/docs-navigation.ts`
|
||||
- An entry in the docs-chat system prompt (`apps/web/app/api/docs-chat/route.ts`)
|
||||
- A skill at `skills/json-render-<name>/SKILL.md`
|
||||
- A skill at `skills/<name>/SKILL.md`
|
||||
- A `packages/<name>/README.md`
|
||||
6. **Run `pnpm type-check`** after all changes to verify nothing is broken
|
||||
|
||||
|
||||
@@ -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
|
||||
# or for Vue
|
||||
npm install @json-render/core @json-render/vue
|
||||
# or for Svelte
|
||||
npm install @json-render/core @json-render/svelte
|
||||
```
|
||||
|
||||
## 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
|
||||
- **Predictable** - JSON output matches your schema, every time
|
||||
- **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
|
||||
|
||||
## Quick Start
|
||||
@@ -116,16 +118,19 @@ function Dashboard({ spec }) {
|
||||
| `@json-render/core` | Schemas, catalogs, AI prompts, dynamic props, SpecStream utilities |
|
||||
| `@json-render/react` | React renderer, contexts, hooks |
|
||||
| `@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/react-native` | React Native renderer with standard mobile components |
|
||||
| `@json-render/remotion` | Remotion video renderer, timeline schema |
|
||||
| `@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/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/zustand` | Zustand adapter for `StateStore` |
|
||||
| `@json-render/jotai` | Jotai 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
|
||||
|
||||
@@ -177,6 +182,23 @@ const { registry } = defineRegistry(catalog, {
|
||||
// <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)
|
||||
|
||||
```tsx
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# web
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/codegen@0.12.1
|
||||
- @json-render/react@0.12.1
|
||||
|
||||
## 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
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -3,7 +3,7 @@ export const metadata = pageMetadata("docs/ai-sdk")
|
||||
|
||||
# AI SDK Integration
|
||||
|
||||
Use json-render with the [Vercel AI SDK](https://sdk.vercel.ai) for seamless streaming. json-render supports two modes: **Generate** (standalone UI) and **Chat** (UI embedded in conversation). See [Generation Modes](/docs/generation-modes) for a detailed comparison.
|
||||
Use json-render with the [Vercel AI SDK](https://sdk.vercel.ai) for seamless streaming. json-render supports two modes: **Standalone** (standalone UI) and **Inline** (UI embedded in conversation). See [Generation Modes](/docs/generation-modes) for a detailed comparison.
|
||||
|
||||
## Installation
|
||||
|
||||
@@ -11,9 +11,9 @@ Use json-render with the [Vercel AI SDK](https://sdk.vercel.ai) for seamless str
|
||||
npm install ai @ai-sdk/react
|
||||
```
|
||||
|
||||
## Generate Mode
|
||||
## Standalone Mode
|
||||
|
||||
In generate mode, the AI outputs only JSONL patches. The entire response is a UI spec with no prose. This is the default mode and is ideal for playgrounds, builders, and dashboard generators.
|
||||
In standalone mode, the AI outputs only JSONL patches. The entire response is a UI spec with no prose. This is the default mode and is ideal for playgrounds, builders, and dashboard generators.
|
||||
|
||||
### API Route
|
||||
|
||||
@@ -73,9 +73,9 @@ function GenerativeUI() {
|
||||
}
|
||||
```
|
||||
|
||||
## Chat Mode
|
||||
## Inline Mode
|
||||
|
||||
In chat mode, the AI responds conversationally and includes JSONL patches inline. Text-only replies are allowed when no UI is needed. This is ideal for chatbots, copilots, and educational assistants.
|
||||
In inline mode, the AI responds conversationally and includes JSONL patches inline. Text-only replies are allowed when no UI is needed. This is ideal for chatbots, copilots, and educational assistants.
|
||||
|
||||
### API Route
|
||||
|
||||
@@ -96,7 +96,7 @@ export async function POST(req: Request) {
|
||||
|
||||
const result = streamText({
|
||||
model: yourModel,
|
||||
system: catalog.prompt({ mode: "chat" }),
|
||||
system: catalog.prompt({ mode: "inline" }),
|
||||
messages,
|
||||
});
|
||||
|
||||
@@ -182,13 +182,13 @@ const systemPrompt = catalog.prompt({
|
||||
});
|
||||
```
|
||||
|
||||
### Chat Mode Prompt
|
||||
### Inline Mode Prompt
|
||||
|
||||
```typescript
|
||||
const chatPrompt = catalog.prompt({ mode: "chat" });
|
||||
const inlinePrompt = catalog.prompt({ mode: "inline" });
|
||||
```
|
||||
|
||||
In chat mode, the prompt instructs the AI to respond conversationally first, then include JSONL patches on their own lines when UI is needed. Text-only replies are allowed.
|
||||
In inline mode, the prompt instructs the AI to respond conversationally first, then include JSONL patches on their own lines when UI is needed. Text-only replies are allowed.
|
||||
|
||||
## Which Mode?
|
||||
|
||||
@@ -197,8 +197,8 @@ In chat mode, the prompt instructs the AI to respond conversationally first, the
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Generate</th>
|
||||
<th>Chat</th>
|
||||
<th>Standalone</th>
|
||||
<th>Inline</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -215,7 +215,7 @@ In chat mode, the prompt instructs the AI to respond conversationally first, the
|
||||
<tr>
|
||||
<td>System prompt</td>
|
||||
<td><code>catalog.prompt()</code></td>
|
||||
<td><code>{"catalog.prompt({ mode: \"chat\" })"}</code></td>
|
||||
<td><code>{"catalog.prompt({ mode: \"inline\" })"}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Stream utility</td>
|
||||
|
||||
@@ -75,7 +75,7 @@ interface Catalog {
|
||||
interface PromptOptions {
|
||||
system?: string; // Custom system message intro
|
||||
customRules?: string[]; // Additional rules to append
|
||||
mode?: "generate" | "chat"; // Output mode (default: "generate")
|
||||
mode?: "standalone" | "inline" | "generate" | "chat"; // Output mode (default: "standalone")
|
||||
}
|
||||
|
||||
interface SpecValidationResult<T> {
|
||||
@@ -370,7 +370,7 @@ Most users should use `pipeJsonRender()` instead, which wraps this transform for
|
||||
|
||||
### createMixedStreamParser
|
||||
|
||||
Parse a mixed stream of text and JSONL patches (used for Chat + GenUI mode):
|
||||
Parse a mixed stream of text and JSONL patches (used for Inline mode):
|
||||
|
||||
```typescript
|
||||
import { createMixedStreamParser } from '@json-render/core';
|
||||
@@ -389,7 +389,7 @@ parser.flush();
|
||||
|
||||
### pipeJsonRender
|
||||
|
||||
Pipe an AI SDK `UIMessageStream` through the json-render transform. Lines that parse as JSONL patches are emitted as `data-spec` parts; everything else passes through as text. Used in Chat mode API routes.
|
||||
Pipe an AI SDK `UIMessageStream` through the json-render transform. Lines that parse as JSONL patches are emitted as `data-spec` parts; everything else passes through as text. Used in Inline mode API routes.
|
||||
|
||||
```typescript
|
||||
import { pipeJsonRender } from '@json-render/core';
|
||||
@@ -403,7 +403,7 @@ const stream = createUIMessageStream({
|
||||
return createUIMessageStreamResponse({ stream });
|
||||
```
|
||||
|
||||
See [Generation Modes](/docs/generation-modes) for full Chat mode setup.
|
||||
See [Generation Modes](/docs/generation-modes) for full Inline mode setup.
|
||||
|
||||
### SpecStream Types
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -341,6 +341,8 @@ February 2026
|
||||
|
||||
### New: Chat Mode (Inline GenUI)
|
||||
|
||||
> **Note:** These modes were renamed in v0.12.1 — "Generate" is now "Standalone" and "Chat" is now "Inline". The old names are accepted as deprecated aliases.
|
||||
|
||||
json-render now supports two generation modes: **Generate** (JSONL-only, the default) and **Chat** (text + JSONL inline). Chat mode lets the AI respond conversationally with embedded UI specs, ideal for chatbots and copilot experiences.
|
||||
|
||||
```typescript
|
||||
|
||||
@@ -3,15 +3,15 @@ export const metadata = pageMetadata("docs/generation-modes")
|
||||
|
||||
# Generation Modes
|
||||
|
||||
json-render supports two modes for AI-generated UI: **Generate mode** for standalone UI and **Chat mode** for inline UI within a conversation.
|
||||
json-render supports two modes for AI-generated UI: **Standalone mode** for standalone UI and **Inline mode** for inline UI within a conversation.
|
||||
|
||||
The mode controls how the AI formats its output and how your app processes the stream. The underlying JSONL patch format is the same in both modes.
|
||||
|
||||
<GenerationModesDiagram />
|
||||
|
||||
## Generate Mode (Standalone)
|
||||
## Standalone Mode
|
||||
|
||||
In generate mode, the AI outputs **only JSONL patches** — no prose, no markdown. The entire response is a UI spec.
|
||||
In standalone mode, the AI outputs **only JSONL patches** — no prose, no markdown. The entire response is a UI spec.
|
||||
|
||||
This is the default mode and is ideal for:
|
||||
|
||||
@@ -25,7 +25,7 @@ This is the default mode and is ideal for:
|
||||
```typescript
|
||||
import { streamText } from "ai";
|
||||
|
||||
// Generate mode is the default (no mode option needed)
|
||||
// Standalone mode is the default (no mode option needed)
|
||||
const systemPrompt = catalog.prompt({
|
||||
customRules: [
|
||||
"Use Card as root for forms and small UIs.",
|
||||
@@ -74,9 +74,9 @@ The AI outputs only JSONL — one patch per line, no surrounding text:
|
||||
{"op":"add","path":"/elements/submit","value":{"type":"Button","props":{"label":"Sign In"}}}
|
||||
```
|
||||
|
||||
## Chat Mode (Inline)
|
||||
## Inline Mode
|
||||
|
||||
In chat mode, the AI responds **conversationally first**, then outputs JSONL patches on their own lines. Text-only replies are allowed when no UI is needed (e.g. greetings, clarifying questions).
|
||||
In inline mode, the AI responds **conversationally first**, then outputs JSONL patches on their own lines. Text-only replies are allowed when no UI is needed (e.g. greetings, clarifying questions).
|
||||
|
||||
This is ideal for:
|
||||
|
||||
@@ -92,8 +92,8 @@ import { streamText } from "ai";
|
||||
import { pipeJsonRender } from "@json-render/core";
|
||||
import { createUIMessageStream, createUIMessageStreamResponse } from "ai";
|
||||
|
||||
// Enable chat mode
|
||||
const systemPrompt = catalog.prompt({ mode: "chat" });
|
||||
// Enable inline mode
|
||||
const systemPrompt = catalog.prompt({ mode: "inline" });
|
||||
|
||||
const result = streamText({
|
||||
model: yourModel,
|
||||
@@ -180,8 +180,8 @@ If the user asks a simple question ("what does BTC stand for?"), the AI replies
|
||||
<thead>
|
||||
<tr>
|
||||
<th />
|
||||
<th>Generate</th>
|
||||
<th>Chat</th>
|
||||
<th>Standalone</th>
|
||||
<th>Inline</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -198,7 +198,7 @@ If the user asks a simple question ("what does BTC stand for?"), the AI replies
|
||||
<tr>
|
||||
<td>System prompt</td>
|
||||
<td><code>{"catalog.prompt()"}</code></td>
|
||||
<td><code>{'catalog.prompt({ mode: "chat" })'}</code></td>
|
||||
<td><code>{'catalog.prompt({ mode: "inline" })'}</code></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Stream utility</td>
|
||||
|
||||
@@ -309,10 +309,35 @@ const catalog = defineCatalog(schema, {
|
||||
|
||||
const prompt = catalog.prompt();
|
||||
|
||||
// Chat mode prompt
|
||||
// Inline mode prompt (formerly "chat")
|
||||
const inlinePrompt = catalog.prompt({ mode: "inline" });
|
||||
```
|
||||
|
||||
## Generation Modes
|
||||
|
||||
The generation mode values passed to `catalog.prompt()` have been renamed for clarity:
|
||||
|
||||
- `"generate"` is now `"standalone"`
|
||||
- `"chat"` is now `"inline"`
|
||||
|
||||
The old names are accepted as deprecated aliases, so existing code will continue to work. Update when convenient.
|
||||
|
||||
**Before:**
|
||||
|
||||
```typescript
|
||||
const prompt = catalog.prompt({ mode: "generate" });
|
||||
const chatPrompt = catalog.prompt({ mode: "chat" });
|
||||
```
|
||||
|
||||
**After:**
|
||||
|
||||
```typescript
|
||||
const prompt = catalog.prompt({ mode: "standalone" });
|
||||
const inlinePrompt = catalog.prompt({ mode: "inline" });
|
||||
```
|
||||
|
||||
The default mode (when no `mode` option is provided) is `"standalone"`, which behaves identically to the previous `"generate"` default.
|
||||
|
||||
## Validation
|
||||
|
||||
`ValidationCheck` now uses `type` instead of `fn`, `ValidationProvider` uses `customFunctions` instead of `functions`, and `useFieldValidation` takes a config object instead of a checks array.
|
||||
|
||||
@@ -91,7 +91,7 @@ The result is a native UI built from your own components — not an iframe, not
|
||||
- **[Streaming](/docs/streaming)** — Render progressively as the AI responds. Each JSONL patch adds to the spec and the UI updates in real time.
|
||||
- **[Data Binding](/docs/data-binding)** — Bind props to runtime data with `$state` paths, repeat elements over arrays, and wire two-way input bindings.
|
||||
- **[Visibility](/docs/visibility)** — Show or hide elements based on state conditions. The AI can generate conditional UIs without writing logic.
|
||||
- **[Generation Modes](/docs/generation-modes)** — Generate standalone UI (playground/builder) or inline UI within a chat conversation.
|
||||
- **[Generation Modes](/docs/generation-modes)** — Standalone mode for full-page generated UI or inline mode for UI embedded in a conversation.
|
||||
|
||||
## Next
|
||||
|
||||
|
||||
@@ -0,0 +1,117 @@
|
||||
import { pageMetadata } from "@/lib/page-metadata"
|
||||
|
||||
export const metadata = pageMetadata("docs/skills")
|
||||
|
||||
# Skills
|
||||
|
||||
json-render ships with skills that teach AI coding agents how to use each package. Install a skill and your agent in Cursor, Claude Code, or Codex can generate json-render UIs without manual guidance.
|
||||
|
||||
## Available Skills
|
||||
|
||||
- **core** — Core schemas, catalogs, and AI prompt generation.
|
||||
- **react** — React renderer that turns JSON specs into React component trees.
|
||||
- **react-pdf** — PDF renderer using `@react-pdf/renderer`.
|
||||
- **react-email** — Email renderer that produces HTML or plain-text emails.
|
||||
- **react-native** — React Native renderer for native mobile UIs.
|
||||
- **shadcn** — Pre-built shadcn/ui components (Radix UI + Tailwind).
|
||||
- **image** — Image renderer that turns JSON specs into SVG and PNG via Satori.
|
||||
- **remotion** — Remotion renderer for video generation from JSON timeline specs.
|
||||
- **vue** — Vue 3 renderer for Vue component trees.
|
||||
- **svelte** — Svelte 5 renderer for Svelte component trees.
|
||||
- **codegen** — Code generation utilities for building custom exporters.
|
||||
- **mcp** — MCP Apps integration for Claude, ChatGPT, Cursor, and VS Code.
|
||||
- **redux** — Redux adapter for json-render's `StateStore` interface.
|
||||
- **zustand** — Zustand adapter for json-render's `StateStore` interface.
|
||||
- **jotai** — Jotai adapter for json-render's `StateStore` interface.
|
||||
- **xstate** — XState Store adapter for json-render's `StateStore` interface.
|
||||
|
||||
## Installation
|
||||
|
||||
```bash
|
||||
npx skills add vercel-labs/json-render --skill core
|
||||
npx skills add vercel-labs/json-render --skill react
|
||||
npx skills add vercel-labs/json-render --skill react-pdf
|
||||
npx skills add vercel-labs/json-render --skill react-email
|
||||
npx skills add vercel-labs/json-render --skill react-native
|
||||
npx skills add vercel-labs/json-render --skill shadcn
|
||||
npx skills add vercel-labs/json-render --skill image
|
||||
npx skills add vercel-labs/json-render --skill remotion
|
||||
npx skills add vercel-labs/json-render --skill vue
|
||||
npx skills add vercel-labs/json-render --skill svelte
|
||||
npx skills add vercel-labs/json-render --skill codegen
|
||||
npx skills add vercel-labs/json-render --skill mcp
|
||||
npx skills add vercel-labs/json-render --skill redux
|
||||
npx skills add vercel-labs/json-render --skill zustand
|
||||
npx skills add vercel-labs/json-render --skill jotai
|
||||
npx skills add vercel-labs/json-render --skill xstate
|
||||
```
|
||||
|
||||
After installing, your AI agent will automatically activate the right skill when it encounters a matching request.
|
||||
|
||||
## core
|
||||
|
||||
The foundational skill. Teaches agents how to define catalogs, create schemas, build specs, and generate AI prompts. This is the starting point for any json-render project and covers `defineCatalog`, `defineSchema`, `specSchema`, `toPrompt`, and the full spec format.
|
||||
|
||||
## react
|
||||
|
||||
Teaches agents how to render JSON specs as React component trees using `JsonRender`, `JsonRenderClient`, and `useJsonRender`. Covers custom component registries, client-side interactivity, state management, and streaming integration.
|
||||
|
||||
## react-pdf
|
||||
|
||||
Teaches agents how to generate PDFs from JSON specs using `@react-pdf/renderer`. Covers the PDF-specific component registry, page layout, and styling.
|
||||
|
||||
## react-email
|
||||
|
||||
Teaches agents how to render JSON specs as HTML or plain-text emails using React Email components. Covers the email-specific registry and rendering pipeline.
|
||||
|
||||
## react-native
|
||||
|
||||
Teaches agents how to render JSON specs as native mobile UIs with React Native. Covers the native component registry and platform-specific considerations.
|
||||
|
||||
## shadcn
|
||||
|
||||
Teaches agents how to use the pre-built shadcn/ui component registry with json-render. Includes Radix UI primitives, Tailwind styling, and the full set of available shadcn components.
|
||||
|
||||
## image
|
||||
|
||||
Teaches agents how to turn JSON specs into SVG and PNG images using Satori. Covers the image-specific registry, dimensions, fonts, and rendering options.
|
||||
|
||||
## remotion
|
||||
|
||||
Teaches agents how to generate videos from JSON timeline specs using Remotion. Covers compositions, sequences, timeline structure, and video rendering.
|
||||
|
||||
## vue
|
||||
|
||||
Teaches agents how to render JSON specs as Vue 3 component trees. Covers the Vue renderer API, custom component registries, and reactivity integration.
|
||||
|
||||
## svelte
|
||||
|
||||
Teaches agents how to render JSON specs as Svelte 5 component trees. Covers the Svelte renderer API and component registration.
|
||||
|
||||
## codegen
|
||||
|
||||
Teaches agents how to use code generation utilities to export UI specs as framework-specific source code. Covers the codegen pipeline and custom exporter creation.
|
||||
|
||||
## mcp
|
||||
|
||||
Teaches agents how to build MCP Apps that serve json-render UIs inside AI tools like Claude, ChatGPT, Cursor, and VS Code. Covers MCP server setup, tool definitions, and UI streaming.
|
||||
|
||||
## redux
|
||||
|
||||
Teaches agents how to connect a Redux store to json-render's `StateStore` interface for state-driven UIs.
|
||||
|
||||
## zustand
|
||||
|
||||
Teaches agents how to connect a Zustand store to json-render's `StateStore` interface for lightweight state management.
|
||||
|
||||
## jotai
|
||||
|
||||
Teaches agents how to connect Jotai atoms to json-render's `StateStore` interface for atomic state management.
|
||||
|
||||
## xstate
|
||||
|
||||
Teaches agents how to connect an XState Store to json-render's `StateStore` interface for state-machine-driven UIs.
|
||||
|
||||
## Source
|
||||
|
||||
All skill files are in the [`skills/`](https://github.com/vercel-labs/json-render/tree/main/skills) directory of the repository.
|
||||
@@ -16,7 +16,8 @@ const SYSTEM_PROMPT = `You are a helpful documentation assistant for json-render
|
||||
|
||||
GitHub repository: https://github.com/vercel-labs/json-render
|
||||
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
|
||||
Skills: json-render ships AI agent skills that teach coding agents how to use each package. Install with "npx skills add vercel-labs/json-render --skill <name>". Available skills: core, react, react-pdf, react-email, react-native, shadcn, image, remotion, vue, svelte, codegen, mcp, redux, zustand, jotai, xstate. See /docs/skills for details.
|
||||
|
||||
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.
|
||||
|
||||
|
||||
@@ -19,11 +19,11 @@ function FormUI() {
|
||||
);
|
||||
}
|
||||
|
||||
function ChatModeDiagram() {
|
||||
function InlineModeDiagram() {
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="text-xs font-medium text-muted-foreground mb-3 text-center">
|
||||
Chat Mode
|
||||
Inline Mode
|
||||
</div>
|
||||
<div className="flex-1 border border-border rounded-lg bg-background overflow-hidden flex flex-col">
|
||||
{/* Chat area */}
|
||||
@@ -69,11 +69,11 @@ function ChatModeDiagram() {
|
||||
);
|
||||
}
|
||||
|
||||
function GenerateModeDiagram() {
|
||||
function StandaloneModeDiagram() {
|
||||
return (
|
||||
<div className="flex flex-col h-full">
|
||||
<div className="text-xs font-medium text-muted-foreground mb-3 text-center">
|
||||
Generate Mode
|
||||
Standalone Mode
|
||||
</div>
|
||||
<div className="flex-1 border border-border rounded-lg bg-background overflow-hidden flex flex-row">
|
||||
{/* Left panel - prompt */}
|
||||
@@ -104,10 +104,10 @@ export function GenerationModesDiagram() {
|
||||
<div className="not-prose my-8">
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-6">
|
||||
<div className="h-[280px]">
|
||||
<ChatModeDiagram />
|
||||
<InlineModeDiagram />
|
||||
</div>
|
||||
<div className="h-[280px]">
|
||||
<GenerateModeDiagram />
|
||||
<StandaloneModeDiagram />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -16,6 +16,7 @@ export const docsNavigation: NavSection[] = [
|
||||
{ title: "Introduction", href: "/docs" },
|
||||
{ title: "Installation", href: "/docs/installation" },
|
||||
{ title: "Quick Start", href: "/docs/quick-start" },
|
||||
{ title: "Skills", href: "/docs/skills" },
|
||||
{ title: "Migration Guide", href: "/docs/migration" },
|
||||
{ title: "Changelog", href: "/docs/changelog" },
|
||||
],
|
||||
@@ -95,6 +96,11 @@ export const docsNavigation: NavSection[] = [
|
||||
href: "https://github.com/vercel-labs/json-render/tree/main/examples/vite-renderers",
|
||||
external: true,
|
||||
},
|
||||
{
|
||||
title: "MCP App",
|
||||
href: "https://github.com/vercel-labs/json-render/tree/main/examples/mcp",
|
||||
external: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -128,6 +134,11 @@ export const docsNavigation: NavSection[] = [
|
||||
{ title: "@json-render/vue", href: "/docs/api/vue" },
|
||||
{ title: "@json-render/svelte", href: "/docs/api/svelte" },
|
||||
{ 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" },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
@@ -37,6 +37,7 @@ export const PAGE_TITLES: Record<string, string> = {
|
||||
"docs/ag-ui": "AG-UI Integration",
|
||||
"docs/migration": "Migration Guide",
|
||||
"docs/changelog": "Changelog",
|
||||
"docs/skills": "Skills",
|
||||
|
||||
// API references
|
||||
"docs/api/core": "@json-render/core API",
|
||||
@@ -50,6 +51,11 @@ export const PAGE_TITLES: Record<string, string> = {
|
||||
"docs/api/image": "@json-render/image API",
|
||||
"docs/api/remotion": "@json-render/remotion 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",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.6",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# example-chat
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/react@0.12.1
|
||||
- @json-render/shadcn@0.12.1
|
||||
|
||||
## 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
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -130,7 +130,7 @@ When the user asks for a quiz, test, or Q&A, build an interactive experience:
|
||||
4. You can also add a final score section that becomes visible when all questions are submitted.
|
||||
|
||||
${explorerCatalog.prompt({
|
||||
mode: "chat",
|
||||
mode: "inline",
|
||||
customRules: [
|
||||
"NEVER use viewport height classes (min-h-screen, h-screen) — the UI renders inside a fixed-size container.",
|
||||
"Prefer Grid with columns='2' or columns='3' for side-by-side layouts.",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-chat",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.6",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,23 @@
|
||||
# example-dashboard
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/codegen@0.12.1
|
||||
- @json-render/react@0.12.1
|
||||
|
||||
## 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
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-dashboard",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.6",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# example-image
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/image@0.12.1
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [63c339b]
|
||||
- @json-render/core@0.12.0
|
||||
- @json-render/image@0.12.0
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-image",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.3",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# example-mcp
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/mcp@0.12.1
|
||||
- @json-render/react@0.12.1
|
||||
- @json-render/shadcn@0.12.1
|
||||
|
||||
## 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.2",
|
||||
"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.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"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,18 @@
|
||||
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()],
|
||||
resolve: {
|
||||
dedupe: ["react", "react-dom"],
|
||||
},
|
||||
build: {
|
||||
outDir: "dist",
|
||||
emptyOutDir: false,
|
||||
rollupOptions: {
|
||||
input: "index.html",
|
||||
},
|
||||
},
|
||||
});
|
||||
@@ -1,5 +1,23 @@
|
||||
# example-no-ai
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/react@0.12.1
|
||||
- @json-render/shadcn@0.12.1
|
||||
|
||||
## 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
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-no-ai",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.6",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# example-react-email
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/react-email@0.12.1
|
||||
|
||||
## 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",
|
||||
"version": "0.1.0",
|
||||
"version": "0.1.2",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# example-react-native
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/react-native@0.12.1
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [63c339b]
|
||||
- @json-render/core@0.12.0
|
||||
- @json-render/react-native@0.12.0
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-react-native",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.6",
|
||||
"private": true,
|
||||
"main": "expo-router/entry",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# example-react-pdf
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/react-pdf@0.12.1
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [63c339b]
|
||||
- @json-render/core@0.12.0
|
||||
- @json-render/react-pdf@0.12.0
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-react-pdf",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.6",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# example-remotion
|
||||
|
||||
## 0.1.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/remotion@0.12.1
|
||||
|
||||
## 0.1.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [63c339b]
|
||||
- @json-render/core@0.12.0
|
||||
- @json-render/remotion@0.12.0
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-remotion",
|
||||
"version": "0.1.4",
|
||||
"version": "0.1.6",
|
||||
"type": "module",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
/// <reference types="next" />
|
||||
/// <reference types="next/image-types/global" />
|
||||
import "./.next/dev/types/routes.d.ts";
|
||||
import "./.next/types/routes.d.ts";
|
||||
|
||||
// NOTE: This file should not be edited
|
||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# com.example.json-render-demo
|
||||
|
||||
## 0.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/react@0.12.1
|
||||
|
||||
## 0.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [63c339b]
|
||||
- @json-render/core@0.12.0
|
||||
- @json-render/react@0.12.0
|
||||
|
||||
## 0.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "com.example.json-render-demo",
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.7",
|
||||
"description": "Test",
|
||||
"private": true,
|
||||
"license": "~~proprietary~~",
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# com.example.json-render-fullpage-demo
|
||||
|
||||
## 0.0.7
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/react@0.12.1
|
||||
|
||||
## 0.0.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [63c339b]
|
||||
- @json-render/core@0.12.0
|
||||
- @json-render/react@0.12.0
|
||||
|
||||
## 0.0.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "com.example.json-render-fullpage-demo",
|
||||
"version": "0.0.5",
|
||||
"version": "0.0.7",
|
||||
"description": "Full-page Stripe App example (alpha)",
|
||||
"private": true,
|
||||
"license": "~~proprietary~~",
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# svelte-chat
|
||||
|
||||
## 0.0.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/svelte@0.12.1
|
||||
|
||||
## 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",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"version": "0.0.3",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite dev",
|
||||
|
||||
@@ -62,7 +62,7 @@ INPUT COMPONENTS:
|
||||
- Button: Clickable button. Use on.press to trigger actions.
|
||||
|
||||
${explorerCatalog.prompt({
|
||||
mode: "chat",
|
||||
mode: "inline",
|
||||
customRules: [
|
||||
"NEVER use viewport height classes (min-h-screen, h-screen) — the UI renders inside a fixed-size container.",
|
||||
"Prefer Grid with columns='2' or columns='3' for side-by-side layouts.",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { defineRegistry, type ComponentRegistry } from "@json-render/svelte";
|
||||
import { defineRegistry } from "@json-render/svelte";
|
||||
import { explorerCatalog } from "./catalog";
|
||||
|
||||
// Import render components
|
||||
import StackComponent from "./components/Stack.svelte";
|
||||
import CardComponent from "./components/Card.svelte";
|
||||
import GridComponent from "./components/Grid.svelte";
|
||||
@@ -28,7 +27,7 @@ import SelectInputComponent from "./components/SelectInput.svelte";
|
||||
import TextInputComponent from "./components/TextInput.svelte";
|
||||
import ButtonComponent from "./components/Button.svelte";
|
||||
|
||||
const components: ComponentRegistry = {
|
||||
const components = {
|
||||
Stack: StackComponent,
|
||||
Card: CardComponent,
|
||||
Grid: GridComponent,
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
# example-svelte
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/svelte@0.12.1
|
||||
|
||||
## 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",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.3",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,25 @@
|
||||
# vite-renderers
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/react@0.12.1
|
||||
- @json-render/svelte@0.12.1
|
||||
- @json-render/vue@0.12.1
|
||||
|
||||
## 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
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "vite-renderers",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.4",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
|
||||
@@ -1,5 +1,21 @@
|
||||
# example-vue
|
||||
|
||||
## 0.1.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
- @json-render/vue@0.12.1
|
||||
|
||||
## 0.1.3
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [63c339b]
|
||||
- @json-render/core@0.12.0
|
||||
- @json-render/vue@0.12.0
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "example-vue",
|
||||
"version": "0.1.2",
|
||||
"version": "0.1.4",
|
||||
"private": true,
|
||||
"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)",
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @json-render/codegen
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [63c339b]
|
||||
- @json-render/core@0.12.0
|
||||
|
||||
## 0.11.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/codegen",
|
||||
"version": "0.11.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "Utilities for generating code from json-render UI trees",
|
||||
"keywords": [
|
||||
@@ -15,7 +15,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/codegen"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,36 @@
|
||||
# @json-render/core
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 54a1ecf: Rename generation modes and fix MCP React duplicate module error.
|
||||
|
||||
### Changed:
|
||||
- **`@json-render/core`** — Renamed generation modes from `"generate"` / `"chat"` to `"standalone"` / `"inline"`. The old names still work but emit a deprecation warning.
|
||||
|
||||
### Fixed:
|
||||
- **`@json-render/mcp`** — Resolved React duplicate module error (`useRef` returning null) by adding `resolve.dedupe` Vite configuration. Added `./build-app-html` export entry point.
|
||||
|
||||
### Other:
|
||||
- Updated `homepage` URLs across all packages to point to `https://json-render.dev`.
|
||||
- Reorganized skills directory structure for cleaner naming.
|
||||
- Added skills documentation page to the web app.
|
||||
|
||||
## 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
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -227,7 +227,7 @@ Schema options:
|
||||
| `ActionBinding` | Action binding with `action`, `params`, `confirm`, `preventDefault`, etc. |
|
||||
| `BuiltInAction` | Built-in action definition with `name` and `description` |
|
||||
|
||||
### Chat Mode (Mixed Streams)
|
||||
### Inline Mode (Mixed Streams)
|
||||
|
||||
| Export | Purpose |
|
||||
|--------|---------|
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/core",
|
||||
"version": "0.11.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "JSON becomes real things. Define your catalog, register your components, let AI generate.",
|
||||
"keywords": [
|
||||
@@ -19,7 +19,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/core"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -257,7 +257,7 @@ describe("catalog.prompt", () => {
|
||||
expect(prompt).toContain("Keep UIs simple");
|
||||
});
|
||||
|
||||
it("generates chat mode prompt when mode is chat", () => {
|
||||
it("generates inline mode prompt when mode is inline", () => {
|
||||
const catalog = defineCatalog(testSchema, {
|
||||
components: {
|
||||
Text: {
|
||||
@@ -268,13 +268,13 @@ describe("catalog.prompt", () => {
|
||||
},
|
||||
actions: {},
|
||||
});
|
||||
const prompt = catalog.prompt({ mode: "chat" });
|
||||
const prompt = catalog.prompt({ mode: "inline" });
|
||||
expect(prompt).toContain("```spec");
|
||||
expect(prompt).toContain("conversationally");
|
||||
expect(prompt).toContain("text + JSONL");
|
||||
});
|
||||
|
||||
it("generates generate mode prompt by default", () => {
|
||||
it("generates standalone mode prompt by default", () => {
|
||||
const catalog = defineCatalog(testSchema, {
|
||||
components: {
|
||||
Text: {
|
||||
@@ -290,6 +290,38 @@ describe("catalog.prompt", () => {
|
||||
expect(prompt).not.toContain("conversationally");
|
||||
});
|
||||
|
||||
it("accepts deprecated 'chat' as alias for 'inline'", () => {
|
||||
const catalog = defineCatalog(testSchema, {
|
||||
components: {
|
||||
Text: {
|
||||
props: z.object({}),
|
||||
description: "",
|
||||
slots: [],
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
});
|
||||
const inlinePrompt = catalog.prompt({ mode: "inline" });
|
||||
const chatPrompt = catalog.prompt({ mode: "chat" });
|
||||
expect(chatPrompt).toEqual(inlinePrompt);
|
||||
});
|
||||
|
||||
it("accepts deprecated 'generate' as alias for 'standalone'", () => {
|
||||
const catalog = defineCatalog(testSchema, {
|
||||
components: {
|
||||
Text: {
|
||||
props: z.object({}),
|
||||
description: "",
|
||||
slots: [],
|
||||
},
|
||||
},
|
||||
actions: {},
|
||||
});
|
||||
const standalonePrompt = catalog.prompt({ mode: "standalone" });
|
||||
const generatePrompt = catalog.prompt({ mode: "generate" });
|
||||
expect(generatePrompt).toEqual(standalonePrompt);
|
||||
});
|
||||
|
||||
it("uses actual catalog component names in examples", () => {
|
||||
const catalog = defineCatalog(testSchema, {
|
||||
components: {
|
||||
|
||||
@@ -112,11 +112,14 @@ export interface PromptOptions {
|
||||
/**
|
||||
* Output mode for the generated prompt.
|
||||
*
|
||||
* - `"generate"` (default): The LLM should output only JSONL patches (no prose).
|
||||
* - `"chat"`: The LLM should respond conversationally first, then output JSONL patches.
|
||||
* - `"standalone"` (default): The LLM should output only JSONL patches (no prose).
|
||||
* - `"inline"`: The LLM should respond conversationally first, then output JSONL patches.
|
||||
* Includes rules about interleaving text with JSONL and not wrapping in code fences.
|
||||
*
|
||||
* @deprecated `"generate"` — use `"standalone"` instead.
|
||||
* @deprecated `"chat"` — use `"inline"` instead.
|
||||
*/
|
||||
mode?: "generate" | "chat";
|
||||
mode?: "standalone" | "inline" | "generate" | "chat";
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -563,15 +566,28 @@ function generatePrompt<TDef extends SchemaDefinition, TCatalog>(
|
||||
const {
|
||||
system = "You are a UI generator that outputs JSON.",
|
||||
customRules = [],
|
||||
mode = "generate",
|
||||
mode: rawMode = "standalone",
|
||||
} = options;
|
||||
|
||||
const mode: "standalone" | "inline" =
|
||||
rawMode === "chat"
|
||||
? (console.warn(
|
||||
'[json-render] mode "chat" is deprecated, use "inline" instead',
|
||||
),
|
||||
"inline")
|
||||
: rawMode === "generate"
|
||||
? (console.warn(
|
||||
'[json-render] mode "generate" is deprecated, use "standalone" instead',
|
||||
),
|
||||
"standalone")
|
||||
: rawMode;
|
||||
|
||||
const lines: string[] = [];
|
||||
lines.push(system);
|
||||
lines.push("");
|
||||
|
||||
// Output format section - explain JSONL streaming patch format
|
||||
if (mode === "chat") {
|
||||
if (mode === "inline") {
|
||||
lines.push("OUTPUT FORMAT (text + JSONL, RFC 6902 JSON Patch):");
|
||||
lines.push(
|
||||
"You respond conversationally. When generating UI, first write a brief explanation (1-3 sentences), then output JSONL patch lines wrapped in a ```spec code fence.",
|
||||
@@ -1014,7 +1030,7 @@ Note: state patches appear right after the elements that use them, so the UI fil
|
||||
// Rules
|
||||
lines.push("RULES:");
|
||||
const baseRules =
|
||||
mode === "chat"
|
||||
mode === "inline"
|
||||
? [
|
||||
"When generating UI, wrap all JSONL patches in a ```spec code fence - one JSON object per line inside the fence",
|
||||
"Write a brief conversational response before any JSONL output",
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @json-render/image
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [63c339b]
|
||||
- @json-render/core@0.12.0
|
||||
|
||||
## 0.11.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/image",
|
||||
"version": "0.11.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "Image renderer for @json-render/core. JSON becomes SVG and PNG images via Satori.",
|
||||
"keywords": [
|
||||
@@ -20,7 +20,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/image"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @json-render/jotai
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [63c339b]
|
||||
- @json-render/core@0.12.0
|
||||
|
||||
## 0.11.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/jotai",
|
||||
"version": "0.11.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "Jotai adapter for json-render StateStore",
|
||||
"keywords": [
|
||||
@@ -14,7 +14,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/jotai"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
# @json-render/mcp
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- 54a1ecf: Rename generation modes and fix MCP React duplicate module error.
|
||||
|
||||
### Changed:
|
||||
- **`@json-render/core`** — Renamed generation modes from `"generate"` / `"chat"` to `"standalone"` / `"inline"`. The old names still work but emit a deprecation warning.
|
||||
|
||||
### Fixed:
|
||||
- **`@json-render/mcp`** — Resolved React duplicate module error (`useRef` returning null) by adding `resolve.dedupe` Vite configuration. Added `./build-app-html` export entry point.
|
||||
|
||||
### Other:
|
||||
- Updated `homepage` URLs across all packages to point to `https://json-render.dev`.
|
||||
- Reorganized skills directory structure for cleaner naming.
|
||||
- Added skills documentation page to the web app.
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 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,83 @@
|
||||
{
|
||||
"name": "@json-render/mcp",
|
||||
"version": "0.12.1",
|
||||
"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://json-render.dev",
|
||||
"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"
|
||||
},
|
||||
"./build-app-html": {
|
||||
"types": "./dist/build-app-html-entry.d.ts",
|
||||
"import": "./dist/build-app-html-entry.mjs",
|
||||
"require": "./dist/build-app-html-entry.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,11 @@
|
||||
/**
|
||||
* Server-side utility for building self-contained MCP App HTML.
|
||||
*
|
||||
* This entry point does NOT depend on React and is safe to import
|
||||
* in Node.js / server environments.
|
||||
*
|
||||
* @packageDocumentation
|
||||
*/
|
||||
|
||||
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", "src/build-app-html-entry.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,27 @@
|
||||
# @json-render/react-email
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 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",
|
||||
"version": "0.8.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "React Email renderer for @json-render/core. JSON becomes HTML emails.",
|
||||
"keywords": [
|
||||
@@ -18,7 +18,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/react-email"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @json-render/react-native
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [63c339b]
|
||||
- @json-render/core@0.12.0
|
||||
|
||||
## 0.11.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/react-native",
|
||||
"version": "0.11.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "React Native renderer for @json-render/core. JSON becomes React Native components.",
|
||||
"keywords": [
|
||||
@@ -19,7 +19,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/react-native"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @json-render/react-pdf
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [63c339b]
|
||||
- @json-render/core@0.12.0
|
||||
|
||||
## 0.11.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/react-pdf",
|
||||
"version": "0.11.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "React PDF renderer for @json-render/core. JSON becomes PDF documents.",
|
||||
"keywords": [
|
||||
@@ -18,7 +18,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/react-pdf"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @internal/react-state
|
||||
|
||||
## 0.8.6
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.8.5
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [63c339b]
|
||||
- @json-render/core@0.12.0
|
||||
|
||||
## 0.8.4
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@internal/react-state",
|
||||
"version": "0.8.4",
|
||||
"version": "0.8.6",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"description": "Shared React state context for json-render renderer packages",
|
||||
|
||||
@@ -1,5 +1,19 @@
|
||||
# @json-render/react
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [54a1ecf]
|
||||
- @json-render/core@0.12.1
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [63c339b]
|
||||
- @json-render/core@0.12.0
|
||||
|
||||
## 0.11.0
|
||||
|
||||
### Patch Changes
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@json-render/react",
|
||||
"version": "0.11.0",
|
||||
"version": "0.12.1",
|
||||
"license": "Apache-2.0",
|
||||
"description": "React renderer for @json-render/core. JSON becomes React components.",
|
||||
"keywords": [
|
||||
@@ -19,7 +19,7 @@
|
||||
"url": "git+https://github.com/vercel-labs/json-render.git",
|
||||
"directory": "packages/react"
|
||||
},
|
||||
"homepage": "https://github.com/vercel-labs/json-render#readme",
|
||||
"homepage": "https://json-render.dev",
|
||||
"bugs": {
|
||||
"url": "https://github.com/vercel-labs/json-render/issues"
|
||||
},
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user