Compare commits

...
Author SHA1 Message Date
github-actions[bot] 6f15faaae0 chore: version packages (#186)
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2026-03-06 20:02:39 -06:00
Chris Tate 63c339b4bb prepare 0.12.0 (#185) 2026-03-06 19:58:08 -06:00
Chris Tate 1cc87310c9 mcp (#184)
* mcp

* fix lint
2026-03-06 19:41:27 -06:00
108 changed files with 3643 additions and 43 deletions
+3 -1
View File
@@ -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": [],
+8
View File
@@ -0,0 +1,8 @@
{
"mcpServers": {
"json-render": {
"command": "npx",
"args": ["tsx", "examples/mcp/server.ts", "--stdio"]
}
}
}
+9
View File
@@ -0,0 +1,9 @@
{
"servers": {
"json-render": {
"type": "stdio",
"command": "npx",
"args": ["tsx", "examples/mcp/server.ts", "--stdio"]
}
}
}
+23 -1
View File
@@ -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
+9
View File
@@ -1,5 +1,14 @@
# web
## 0.1.5
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
- @json-render/codegen@0.12.0
- @json-render/react@0.12.0
## 0.1.4
### Patch Changes
+104
View File
@@ -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>
+247
View File
@@ -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.
+104
View File
@@ -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>
+1 -1
View File
@@ -16,7 +16,7 @@ const SYSTEM_PROMPT = `You are a helpful documentation assistant for json-render
GitHub repository: https://github.com/vercel-labs/json-render
Documentation: https://json-render.dev/docs
npm packages: @json-render/core, @json-render/react, @json-render/react-email, @json-render/react-pdf, @json-render/image, @json-render/remotion, @json-render/codegen
npm packages: @json-render/core, @json-render/react, @json-render/vue, @json-render/svelte, @json-render/shadcn, @json-render/react-native, @json-render/react-email, @json-render/react-pdf, @json-render/image, @json-render/remotion, @json-render/codegen, @json-render/mcp, @json-render/redux, @json-render/zustand, @json-render/jotai, @json-render/xstate
You have access to the full json-render documentation via the bash and readFile tools. The docs are available as markdown files in the /workspace/docs/ directory.
+10
View File
@@ -95,6 +95,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 +133,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" },
],
},
];
+5
View File
@@ -50,6 +50,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 -1
View File
@@ -1,6 +1,6 @@
{
"name": "web",
"version": "0.1.4",
"version": "0.1.5",
"type": "module",
"private": true,
"license": "Apache-2.0",
+9
View File
@@ -1,5 +1,14 @@
# example-chat
## 0.1.5
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
- @json-render/react@0.12.0
- @json-render/shadcn@0.12.0
## 0.1.4
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "example-chat",
"version": "0.1.4",
"version": "0.1.5",
"type": "module",
"private": true,
"scripts": {
+9
View File
@@ -1,5 +1,14 @@
# example-dashboard
## 0.1.5
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
- @json-render/codegen@0.12.0
- @json-render/react@0.12.0
## 0.1.4
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "example-dashboard",
"version": "0.1.4",
"version": "0.1.5",
"type": "module",
"private": true,
"scripts": {
+8
View File
@@ -1,5 +1,13 @@
# example-image
## 0.1.2
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
- @json-render/image@0.12.0
## 0.1.1
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "example-image",
"version": "0.1.1",
"version": "0.1.2",
"type": "module",
"private": true,
"scripts": {
+11
View File
@@ -0,0 +1,11 @@
# example-mcp
## 0.1.1
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
- @json-render/mcp@0.12.0
- @json-render/react@0.12.0
- @json-render/shadcn@0.12.0
+63
View File
@@ -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
+13
View File
@@ -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>
+34
View File
@@ -0,0 +1,34 @@
{
"name": "example-mcp",
"version": "0.1.1",
"type": "module",
"private": true,
"scripts": {
"build": "vite build",
"start": "tsx server.ts",
"start:stdio": "tsx server.ts --stdio",
"dev": "tsx watch server.ts"
},
"dependencies": {
"@json-render/core": "workspace:*",
"@json-render/mcp": "workspace:*",
"@json-render/react": "workspace:*",
"@json-render/shadcn": "workspace:*",
"@modelcontextprotocol/ext-apps": "^1.2.0",
"@modelcontextprotocol/sdk": "^1.27.1",
"react": "19.2.3",
"react-dom": "19.2.3",
"zod": "^4.3.6"
},
"devDependencies": {
"@types/react": "19.2.3",
"@types/react-dom": "19.2.3",
"@tailwindcss/vite": "^4.2.1",
"@vitejs/plugin-react": "^5.1.4",
"tailwindcss": "^4.2.1",
"tsx": "^4.21.0",
"typescript": "^5.7.2",
"vite": "^6.3.5",
"vite-plugin-singlefile": "^2.3.0"
}
}
+86
View File
@@ -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);
});
}
+10
View File
@@ -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: {},
});
+106
View File
@@ -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;
}
+269
View File
@@ -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>,
);
+89
View File
@@ -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>
);
}
+17
View File
@@ -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"]
}
+15
View File
@@ -0,0 +1,15 @@
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { viteSingleFile } from "vite-plugin-singlefile";
import tailwindcss from "@tailwindcss/vite";
export default defineConfig({
plugins: [react(), tailwindcss(), viteSingleFile()],
build: {
outDir: "dist",
emptyOutDir: false,
rollupOptions: {
input: "index.html",
},
},
});
+9
View File
@@ -1,5 +1,14 @@
# example-no-ai
## 0.1.5
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
- @json-render/react@0.12.0
- @json-render/shadcn@0.12.0
## 0.1.4
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "example-no-ai",
"version": "0.1.4",
"version": "0.1.5",
"type": "module",
"private": true,
"scripts": {
+9
View File
@@ -0,0 +1,9 @@
# example-react-email
## 0.1.1
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
- @json-render/react-email@0.12.0
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "example-react-email",
"version": "0.1.0",
"version": "0.1.1",
"type": "module",
"private": true,
"scripts": {
+8
View File
@@ -1,5 +1,13 @@
# example-react-native
## 0.1.5
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
- @json-render/react-native@0.12.0
## 0.1.4
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "example-react-native",
"version": "0.1.4",
"version": "0.1.5",
"private": true,
"main": "expo-router/entry",
"scripts": {
+8
View File
@@ -1,5 +1,13 @@
# example-react-pdf
## 0.1.5
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
- @json-render/react-pdf@0.12.0
## 0.1.4
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "example-react-pdf",
"version": "0.1.4",
"version": "0.1.5",
"type": "module",
"private": true,
"scripts": {
+8
View File
@@ -1,5 +1,13 @@
# example-remotion
## 0.1.5
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
- @json-render/remotion@0.12.0
## 0.1.4
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "example-remotion",
"version": "0.1.4",
"version": "0.1.5",
"type": "module",
"private": true,
"scripts": {
@@ -1,5 +1,13 @@
# com.example.json-render-demo
## 0.0.6
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
- @json-render/react@0.12.0
## 0.0.5
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "com.example.json-render-demo",
"version": "0.0.5",
"version": "0.0.6",
"description": "Test",
"private": true,
"license": "~~proprietary~~",
@@ -1,5 +1,13 @@
# com.example.json-render-fullpage-demo
## 0.0.6
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
- @json-render/react@0.12.0
## 0.0.5
### Patch Changes
@@ -1,6 +1,6 @@
{
"name": "com.example.json-render-fullpage-demo",
"version": "0.0.5",
"version": "0.0.6",
"description": "Full-page Stripe App example (alpha)",
"private": true,
"license": "~~proprietary~~",
+9
View File
@@ -0,0 +1,9 @@
# svelte-chat
## 0.0.2
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
- @json-render/svelte@0.12.0
+1 -1
View File
@@ -1,7 +1,7 @@
{
"name": "svelte-chat",
"private": true,
"version": "0.0.1",
"version": "0.0.2",
"type": "module",
"scripts": {
"dev": "vite dev",
@@ -1,7 +1,6 @@
import { defineRegistry, type ComponentRegistry } from "@json-render/svelte";
import { defineRegistry } from "@json-render/svelte";
import { explorerCatalog } from "./catalog";
// Import 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,
+9
View File
@@ -0,0 +1,9 @@
# example-svelte
## 0.1.2
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
- @json-render/svelte@0.12.0
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "example-svelte",
"version": "0.1.1",
"version": "0.1.2",
"private": true,
"type": "module",
"scripts": {
+10
View File
@@ -1,5 +1,15 @@
# vite-renderers
## 0.1.3
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
- @json-render/svelte@0.12.0
- @json-render/react@0.12.0
- @json-render/vue@0.12.0
## 0.1.2
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "vite-renderers",
"version": "0.1.2",
"version": "0.1.3",
"private": true,
"type": "module",
"scripts": {
+8
View File
@@ -1,5 +1,13 @@
# example-vue
## 0.1.3
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
- @json-render/vue@0.12.0
## 0.1.2
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "example-vue",
"version": "0.1.2",
"version": "0.1.3",
"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)",
+7
View File
@@ -1,5 +1,12 @@
# @json-render/codegen
## 0.12.0
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
## 0.11.0
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/codegen",
"version": "0.11.0",
"version": "0.12.0",
"license": "Apache-2.0",
"description": "Utilities for generating code from json-render UI trees",
"keywords": [
+14
View File
@@ -1,5 +1,19 @@
# @json-render/core
## 0.12.0
### Minor Changes
- 63c339b: Add Svelte renderer, React Email renderer, and MCP Apps integration.
### New:
- **`@json-render/svelte`** — Svelte 5 renderer with runes-based reactivity. Full support for data binding, visibility, actions, validation, watchers, streaming, and repeat scopes. Includes `defineRegistry`, `Renderer`, `schema`, composables, and context providers.
- **`@json-render/react-email`** — React Email renderer for generating HTML and plain-text emails from JSON specs. 17 standard components (Html, Head, Body, Container, Section, Row, Column, Heading, Text, Link, Button, Image, Hr, Preview, Markdown). Server-side `renderToHtml` / `renderToPlainText` APIs. Custom catalog and registry support.
- **`@json-render/mcp`** — MCP Apps integration that serves json-render UIs as interactive apps inside Claude, ChatGPT, Cursor, VS Code, and other MCP-capable clients. `createMcpApp` server factory, `useJsonRenderApp` React hook for iframes, and `buildAppHtml` utility.
### Fixed:
- **`@json-render/svelte`** — Corrected JSDoc comment and added missing `zod` peer dependency.
## 0.11.0
### Minor Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/core",
"version": "0.11.0",
"version": "0.12.0",
"license": "Apache-2.0",
"description": "JSON becomes real things. Define your catalog, register your components, let AI generate.",
"keywords": [
+7
View File
@@ -1,5 +1,12 @@
# @json-render/image
## 0.12.0
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
## 0.11.0
### Minor Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/image",
"version": "0.11.0",
"version": "0.12.0",
"license": "Apache-2.0",
"description": "Image renderer for @json-render/core. JSON becomes SVG and PNG images via Satori.",
"keywords": [
+7
View File
@@ -1,5 +1,12 @@
# @json-render/jotai
## 0.12.0
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
## 0.11.0
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/jotai",
"version": "0.11.0",
"version": "0.12.0",
"license": "Apache-2.0",
"description": "Jotai adapter for json-render StateStore",
"keywords": [
+20
View File
@@ -0,0 +1,20 @@
# @json-render/mcp
## 0.12.0
### Minor Changes
- 63c339b: Add Svelte renderer, React Email renderer, and MCP Apps integration.
### New:
- **`@json-render/svelte`** — Svelte 5 renderer with runes-based reactivity. Full support for data binding, visibility, actions, validation, watchers, streaming, and repeat scopes. Includes `defineRegistry`, `Renderer`, `schema`, composables, and context providers.
- **`@json-render/react-email`** — React Email renderer for generating HTML and plain-text emails from JSON specs. 17 standard components (Html, Head, Body, Container, Section, Row, Column, Heading, Text, Link, Button, Image, Hr, Preview, Markdown). Server-side `renderToHtml` / `renderToPlainText` APIs. Custom catalog and registry support.
- **`@json-render/mcp`** — MCP Apps integration that serves json-render UIs as interactive apps inside Claude, ChatGPT, Cursor, VS Code, and other MCP-capable clients. `createMcpApp` server factory, `useJsonRenderApp` React hook for iframes, and `buildAppHtml` utility.
### Fixed:
- **`@json-render/svelte`** — Corrected JSDoc comment and added missing `zod` peer dependency.
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
+124
View File
@@ -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.
+78
View File
@@ -0,0 +1,78 @@
{
"name": "@json-render/mcp",
"version": "0.12.0",
"license": "Apache-2.0",
"description": "MCP Apps integration for @json-render/core. Serve json-render UIs as interactive MCP Apps in Claude, ChatGPT, Cursor, and VS Code.",
"keywords": [
"json",
"ui",
"mcp",
"model-context-protocol",
"mcp-apps",
"ai",
"generative-ui",
"llm",
"renderer",
"claude",
"chatgpt",
"cursor"
],
"repository": {
"type": "git",
"url": "git+https://github.com/vercel-labs/json-render.git",
"directory": "packages/mcp"
},
"homepage": "https://github.com/vercel-labs/json-render#readme",
"bugs": {
"url": "https://github.com/vercel-labs/json-render/issues"
},
"publishConfig": {
"access": "public"
},
"main": "./dist/index.js",
"module": "./dist/index.mjs",
"types": "./dist/index.d.ts",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs",
"require": "./dist/index.js"
},
"./app": {
"types": "./dist/app.d.ts",
"import": "./dist/app.mjs",
"require": "./dist/app.js"
}
},
"files": [
"dist"
],
"scripts": {
"build": "tsup",
"dev": "tsup --watch",
"typecheck": "tsc --noEmit"
},
"dependencies": {
"@json-render/core": "workspace:*",
"@modelcontextprotocol/ext-apps": "^1.2.0",
"@modelcontextprotocol/sdk": "^1.27.1"
},
"devDependencies": {
"@internal/typescript-config": "workspace:*",
"@types/react": "19.2.3",
"tsup": "^8.0.2",
"typescript": "^5.4.5"
},
"peerDependencies": {
"react": "^19.0.0",
"react-dom": "^19.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-dom": {
"optional": true
}
}
}
+30
View File
@@ -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";
+71
View File
@@ -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, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;");
}
+12
View File
@@ -0,0 +1,12 @@
export type {
CreateMcpAppOptions,
McpToolOptions,
RegisterToolOptions,
RegisterResourceOptions,
} from "./types.js";
export {
createMcpApp,
registerJsonRenderTool,
registerJsonRenderResource,
} from "./server.js";
+158
View File
@@ -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;
}
+62
View File
@@ -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;
}
+139
View File
@@ -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,
};
}
+9
View File
@@ -0,0 +1,9 @@
{
"extends": "@internal/typescript-config/react-library.json",
"compilerOptions": {
"outDir": "dist",
"rootDir": "src"
},
"include": ["src"],
"exclude": ["node_modules", "dist"]
}
+17
View File
@@ -0,0 +1,17 @@
import { defineConfig } from "tsup";
export default defineConfig({
entry: ["src/index.ts", "src/app.ts"],
format: ["cjs", "esm"],
dts: true,
sourcemap: true,
clean: true,
external: [
"react",
"react-dom",
"@json-render/core",
"@json-render/react",
"@modelcontextprotocol/sdk",
"@modelcontextprotocol/ext-apps",
],
});
+20
View File
@@ -0,0 +1,20 @@
# @json-render/react-email
## 0.12.0
### Minor Changes
- 63c339b: Add Svelte renderer, React Email renderer, and MCP Apps integration.
### New:
- **`@json-render/svelte`** — Svelte 5 renderer with runes-based reactivity. Full support for data binding, visibility, actions, validation, watchers, streaming, and repeat scopes. Includes `defineRegistry`, `Renderer`, `schema`, composables, and context providers.
- **`@json-render/react-email`** — React Email renderer for generating HTML and plain-text emails from JSON specs. 17 standard components (Html, Head, Body, Container, Section, Row, Column, Heading, Text, Link, Button, Image, Hr, Preview, Markdown). Server-side `renderToHtml` / `renderToPlainText` APIs. Custom catalog and registry support.
- **`@json-render/mcp`** — MCP Apps integration that serves json-render UIs as interactive apps inside Claude, ChatGPT, Cursor, VS Code, and other MCP-capable clients. `createMcpApp` server factory, `useJsonRenderApp` React hook for iframes, and `buildAppHtml` utility.
### Fixed:
- **`@json-render/svelte`** — Corrected JSDoc comment and added missing `zod` peer dependency.
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/react-email",
"version": "0.8.0",
"version": "0.12.0",
"license": "Apache-2.0",
"description": "React Email renderer for @json-render/core. JSON becomes HTML emails.",
"keywords": [
+7
View File
@@ -1,5 +1,12 @@
# @json-render/react-native
## 0.12.0
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
## 0.11.0
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/react-native",
"version": "0.11.0",
"version": "0.12.0",
"license": "Apache-2.0",
"description": "React Native renderer for @json-render/core. JSON becomes React Native components.",
"keywords": [
+7
View File
@@ -1,5 +1,12 @@
# @json-render/react-pdf
## 0.12.0
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
## 0.11.0
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/react-pdf",
"version": "0.11.0",
"version": "0.12.0",
"license": "Apache-2.0",
"description": "React PDF renderer for @json-render/core. JSON becomes PDF documents.",
"keywords": [
+7
View File
@@ -1,5 +1,12 @@
# @internal/react-state
## 0.8.5
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
## 0.8.4
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@internal/react-state",
"version": "0.8.4",
"version": "0.8.5",
"private": true,
"license": "Apache-2.0",
"description": "Shared React state context for json-render renderer packages",
+7
View File
@@ -1,5 +1,12 @@
# @json-render/react
## 0.12.0
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
## 0.11.0
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/react",
"version": "0.11.0",
"version": "0.12.0",
"license": "Apache-2.0",
"description": "React renderer for @json-render/core. JSON becomes React components.",
"keywords": [
+7
View File
@@ -1,5 +1,12 @@
# @json-render/redux
## 0.12.0
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
## 0.11.0
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/redux",
"version": "0.11.0",
"version": "0.12.0",
"license": "Apache-2.0",
"description": "Redux adapter for json-render StateStore",
"keywords": [
+7
View File
@@ -1,5 +1,12 @@
# @json-render/remotion
## 0.12.0
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
## 0.11.0
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/remotion",
"version": "0.11.0",
"version": "0.12.0",
"license": "Apache-2.0",
"description": "Remotion renderer for @json-render/core. JSON becomes video compositions.",
"keywords": [
+8
View File
@@ -1,5 +1,13 @@
# @json-render/shadcn
## 0.12.0
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
- @json-render/react@0.12.0
## 0.11.0
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/shadcn",
"version": "0.11.0",
"version": "0.12.0",
"license": "Apache-2.0",
"description": "shadcn/ui component library for @json-render/core. JSON becomes beautiful Tailwind-styled React components.",
"keywords": [
+20
View File
@@ -0,0 +1,20 @@
# @json-render/svelte
## 0.12.0
### Minor Changes
- 63c339b: Add Svelte renderer, React Email renderer, and MCP Apps integration.
### New:
- **`@json-render/svelte`** — Svelte 5 renderer with runes-based reactivity. Full support for data binding, visibility, actions, validation, watchers, streaming, and repeat scopes. Includes `defineRegistry`, `Renderer`, `schema`, composables, and context providers.
- **`@json-render/react-email`** — React Email renderer for generating HTML and plain-text emails from JSON specs. 17 standard components (Html, Head, Body, Container, Section, Row, Column, Heading, Text, Link, Button, Image, Hr, Preview, Markdown). Server-side `renderToHtml` / `renderToPlainText` APIs. Custom catalog and registry support.
- **`@json-render/mcp`** — MCP Apps integration that serves json-render UIs as interactive apps inside Claude, ChatGPT, Cursor, VS Code, and other MCP-capable clients. `createMcpApp` server factory, `useJsonRenderApp` React hook for iframes, and `buildAppHtml` utility.
### Fixed:
- **`@json-render/svelte`** — Corrected JSDoc comment and added missing `zod` peer dependency.
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
+155
View File
@@ -0,0 +1,155 @@
# @json-render/svelte
Svelte 5 renderer for `@json-render/core`. Turn JSON specs into Svelte components with runes-based reactivity.
## Installation
```bash
npm install @json-render/core @json-render/svelte
```
Peer dependencies: `svelte ^5.0.0` and `zod ^4.0.0`.
## Quick Start
### 1. Create a Catalog
```typescript
import { defineCatalog } from "@json-render/core";
import { schema } from "@json-render/svelte/schema";
import { z } from "zod";
export const catalog = defineCatalog(schema, {
components: {
Card: {
props: z.object({
title: z.string(),
description: z.string().nullable(),
}),
description: "A card container",
},
Button: {
props: z.object({
label: z.string(),
action: z.string(),
}),
description: "A clickable button",
},
},
actions: {
submit: { description: "Submit the form" },
},
});
```
### 2. Define Component Implementations
```typescript
import { defineRegistry } from "@json-render/svelte";
import { catalog } from "./catalog";
export const { registry } = defineRegistry(catalog, {
components: {
Card: ({ props, children }) => /* Svelte 5 snippet */,
Button: ({ props, emit }) => /* Svelte 5 snippet */,
},
actions: {
submit: async (params) => { /* handle submit */ },
},
});
```
### 3. Render Specs
```svelte
<script>
import { Renderer, StateProvider, ActionProvider } from "@json-render/svelte";
import { registry } from "./registry";
const spec = { /* ... */ };
</script>
<StateProvider initialState={{ form: { name: "" } }}>
<ActionProvider handlers={{ submit: handleSubmit }}>
<Renderer {spec} {registry} />
</ActionProvider>
</StateProvider>
```
## Providers
| Provider | Purpose |
|----------|---------|
| `StateProvider` | Share state across components (JSON Pointer paths). Accepts optional `store` prop for controlled mode. |
| `ActionProvider` | Handle actions dispatched via the event system |
| `VisibilityProvider` | Enable conditional rendering based on state |
| `ValidationProvider` | Form field validation |
| `RepeatScopeProvider` | Repeat scope for list rendering |
| `FunctionsContextProvider` | Register `$computed` functions |
| `JsonUIProvider` | All-in-one provider combining state, actions, visibility, validation, and functions |
## Context Accessors
Svelte 5 uses `getContext`-based accessors instead of hooks:
| Accessor | Purpose |
|----------|---------|
| `getStateContext()` | Access state context (`state`, `get`, `set`) |
| `getStateValue(path)` | Get single value from state |
| `getBoundProp(value, binding)` | Two-way binding for `$bindState`/`$bindItem` |
| `getVisibilityContext()` | Access visibility context |
| `isVisible(condition)` | Check if a visibility condition is met |
| `getActionContext()` | Access action context |
| `getAction(binding)` | Get a single action dispatch function |
| `getValidationContext()` | Access validation context |
| `getFieldValidation(path, config)` | Field validation state |
| `getRepeatScope()` | Access current repeat scope |
| `getFunctions()` | Access registered computed functions |
## Streaming
```typescript
import { createUIStream, createChatUI } from "@json-render/svelte";
const stream = createUIStream({ endpoint: "/api/generate" });
// Or for chat-style UI:
const chat = createChatUI({ endpoint: "/api/chat" });
```
## Key Exports
| Export | Purpose |
|--------|---------|
| `defineRegistry` | Create a type-safe component registry from a catalog |
| `createRenderer` | Create a renderer function from a registry |
| `Renderer` | Render a spec using a registry |
| `CatalogRenderer` | Render with automatic provider wiring |
| `JsonUIProvider` | All-in-one provider |
| `schema` | Element tree schema (includes built-in state actions) |
| `createUIStream` | Stream specs from an API endpoint |
| `createChatUI` | Chat-style streaming interface |
| `flatToTree` | Convert flat spec to tree format |
| `ConfirmDialog` | Confirmation dialog component |
| `ConfirmDialogManager` | Manage multiple confirmation dialogs |
### Types
| Export | Purpose |
|--------|---------|
| `ComponentContext` | Typed component render function context (catalog-aware) |
| `BaseComponentProps` | Catalog-agnostic base type for reusable component libraries |
| `EventHandle` | Event handle with `emit()`, `shouldPreventDefault`, `bound` |
| `ComponentFn` | Component render function type |
| `SetState` | State setter type |
| `StateModel` | State model type |
| `SvelteSchema` | Schema type for the Svelte renderer |
| `SvelteSpec` | Spec type for the Svelte renderer |
## Documentation
Full API reference: [json-render.dev/docs/api/svelte](https://json-render.dev/docs/api/svelte).
## License
Apache-2.0
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/svelte",
"version": "0.6.1",
"version": "0.12.0",
"license": "Apache-2.0",
"description": "Svelte 5 renderer for @json-render/core. JSON becomes Svelte components.",
"keywords": [
+2 -2
View File
@@ -4,8 +4,8 @@
* Props for JSONUIProvider
*/
export interface JSONUIProviderProps {
/** Component registry */
registry: ComponentRegistry;
/** Component registry (passed through for convenience; not used internally) */
registry?: ComponentRegistry;
/**
* External store (controlled mode). When provided, `initialState` and
* `onStateChange` are ignored.
+7
View File
@@ -1,5 +1,12 @@
# @json-render/vue
## 0.12.0
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
## 0.11.0
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/vue",
"version": "0.11.0",
"version": "0.12.0",
"license": "Apache-2.0",
"description": "Vue renderer for @json-render/core. JSON becomes Vue components.",
"keywords": [
+7
View File
@@ -1,5 +1,12 @@
# @json-render/xstate
## 0.12.0
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
## 0.11.0
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/xstate",
"version": "0.11.0",
"version": "0.12.0",
"license": "Apache-2.0",
"description": "XState Store adapter for json-render StateStore",
"keywords": [
+7
View File
@@ -1,5 +1,12 @@
# @json-render/zustand
## 0.12.0
### Patch Changes
- Updated dependencies [63c339b]
- @json-render/core@0.12.0
## 0.11.0
### Patch Changes
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@json-render/zustand",
"version": "0.11.0",
"version": "0.12.0",
"license": "Apache-2.0",
"description": "Zustand adapter for json-render StateStore",
"keywords": [
+247 -4
View File
@@ -520,6 +520,64 @@ importers:
specifier: ^5.7.2
version: 5.9.3
examples/mcp:
dependencies:
'@json-render/core':
specifier: workspace:*
version: link:../../packages/core
'@json-render/mcp':
specifier: workspace:*
version: link:../../packages/mcp
'@json-render/react':
specifier: workspace:*
version: link:../../packages/react
'@json-render/shadcn':
specifier: workspace:*
version: link:../../packages/shadcn
'@modelcontextprotocol/ext-apps':
specifier: ^1.2.0
version: 1.2.0(@modelcontextprotocol/sdk@1.27.1(zod@4.3.6))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@4.3.6)
'@modelcontextprotocol/sdk':
specifier: ^1.27.1
version: 1.27.1(zod@4.3.6)
react:
specifier: 19.2.3
version: 19.2.3
react-dom:
specifier: 19.2.3
version: 19.2.3(react@19.2.3)
zod:
specifier: ^4.3.6
version: 4.3.6
devDependencies:
'@tailwindcss/vite':
specifier: ^4.2.1
version: 4.2.1(vite@6.4.1(@types/node@22.19.6)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
'@types/react':
specifier: 19.2.3
version: 19.2.3
'@types/react-dom':
specifier: 19.2.3
version: 19.2.3(@types/react@19.2.3)
'@vitejs/plugin-react':
specifier: ^5.1.4
version: 5.1.4(vite@6.4.1(@types/node@22.19.6)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
tailwindcss:
specifier: ^4.2.1
version: 4.2.1
tsx:
specifier: ^4.21.0
version: 4.21.0
typescript:
specifier: ^5.7.2
version: 5.9.3
vite:
specifier: ^6.3.5
version: 6.4.1(@types/node@22.19.6)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
vite-plugin-singlefile:
specifier: ^2.3.0
version: 2.3.0(rollup@4.55.1)(vite@6.4.1(@types/node@22.19.6)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))
examples/no-ai:
dependencies:
'@json-render/core':
@@ -621,7 +679,7 @@ importers:
version: 0.575.0(react@19.2.4)
next:
specifier: 16.1.6
version: 16.1.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
radix-ui:
specifier: ^1.4.3
version: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -755,7 +813,7 @@ importers:
version: 0.575.0(react@19.2.4)
next:
specifier: 16.1.6
version: 16.1.6(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
version: 16.1.6(@babel/core@7.29.0)(@opentelemetry/api@1.9.0)(babel-plugin-react-compiler@1.0.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
radix-ui:
specifier: ^1.4.3
version: 1.4.3(@types/react-dom@19.2.3(@types/react@19.2.3))(@types/react@19.2.3)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
@@ -1259,6 +1317,37 @@ importers:
specifier: ^5.4.5
version: 5.9.3
packages/mcp:
dependencies:
'@json-render/core':
specifier: workspace:*
version: link:../core
'@modelcontextprotocol/ext-apps':
specifier: ^1.2.0
version: 1.2.0(@modelcontextprotocol/sdk@1.27.1(zod@4.3.6))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6)
'@modelcontextprotocol/sdk':
specifier: ^1.27.1
version: 1.27.1(zod@4.3.6)
react:
specifier: ^19.0.0
version: 19.2.4
react-dom:
specifier: ^19.0.0
version: 19.2.4(react@19.2.4)
devDependencies:
'@internal/typescript-config':
specifier: workspace:*
version: link:../typescript-config
'@types/react':
specifier: 19.2.3
version: 19.2.3
tsup:
specifier: ^8.0.2
version: 8.5.1(jiti@2.6.1)(postcss@8.5.6)(tsx@4.21.0)(typescript@5.9.3)(yaml@2.8.2)
typescript:
specifier: ^5.4.5
version: 5.9.3
packages/react:
dependencies:
'@json-render/core':
@@ -3630,6 +3719,19 @@ packages:
'@mixmark-io/domino@2.2.0':
resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==}
'@modelcontextprotocol/ext-apps@1.2.0':
resolution: {integrity: sha512-ijUQJX/FmNq8PWgOLzph/BAfy84sUZxoIRuHzr+F37wYtWjhdl8pliBJybapYolppY+XJ8oqjFZmTOuMqxwbWQ==}
peerDependencies:
'@modelcontextprotocol/sdk': ^1.24.0
react: ^17.0.0 || ^18.0.0 || ^19.0.0
react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0
zod: ^3.25.0 || ^4.0.0
peerDependenciesMeta:
react:
optional: true
react-dom:
optional: true
'@modelcontextprotocol/sdk@1.26.0':
resolution: {integrity: sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==}
engines: {node: '>=18'}
@@ -3640,6 +3742,16 @@ packages:
'@cfworker/json-schema':
optional: true
'@modelcontextprotocol/sdk@1.27.1':
resolution: {integrity: sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==}
engines: {node: '>=18'}
peerDependencies:
'@cfworker/json-schema': ^4.1.1
zod: ^3.25 || ^4.0
peerDependenciesMeta:
'@cfworker/json-schema':
optional: true
'@mongodb-js/zstd@7.0.0':
resolution: {integrity: sha512-mQ2s0pYYiav+tzCDR05Zptem8Ey2v8s11lri5RKGhTtL4COVCvVCk5vtyRYNT+9L8qSfyOqqefF9UtnW8mC5jA==}
engines: {node: '>= 20.19.0'}
@@ -12106,6 +12218,53 @@ packages:
resolution: {integrity: sha512-t20zYkrSf868+j/p31cRIGN28Phrjm3nRSLR2fyc2tiWi4cZGVdv68yNlwnIINTkMTmPoMiSlc0OadaO7DXZaQ==}
engines: {node: '>= 6'}
vite-plugin-singlefile@2.3.0:
resolution: {integrity: sha512-DAcHzYypM0CasNLSz/WG0VdKOCxGHErfrjOoyIPiNxTPTGmO6rRD/te93n1YL/s+miXq66ipF1brMBikf99c6A==}
engines: {node: '>18.0.0'}
peerDependencies:
rollup: ^4.44.1
vite: ^5.4.11 || ^6.0.0 || ^7.0.0
vite@6.4.1:
resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==}
engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0}
hasBin: true
peerDependencies:
'@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0
jiti: '>=1.21.0'
less: '*'
lightningcss: ^1.21.0
sass: '*'
sass-embedded: '*'
stylus: '*'
sugarss: '*'
terser: ^5.16.0
tsx: ^4.8.1
yaml: ^2.4.2
peerDependenciesMeta:
'@types/node':
optional: true
jiti:
optional: true
less:
optional: true
lightningcss:
optional: true
sass:
optional: true
sass-embedded:
optional: true
stylus:
optional: true
sugarss:
optional: true
terser:
optional: true
tsx:
optional: true
yaml:
optional: true
vite@7.3.1:
resolution: {integrity: sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==}
engines: {node: ^20.19.0 || >=22.12.0}
@@ -14860,6 +15019,22 @@ snapshots:
'@mixmark-io/domino@2.2.0': {}
'@modelcontextprotocol/ext-apps@1.2.0(@modelcontextprotocol/sdk@1.27.1(zod@4.3.6))(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(zod@4.3.6)':
dependencies:
'@modelcontextprotocol/sdk': 1.27.1(zod@4.3.6)
zod: 4.3.6
optionalDependencies:
react: 19.2.3
react-dom: 19.2.3(react@19.2.3)
'@modelcontextprotocol/ext-apps@1.2.0(@modelcontextprotocol/sdk@1.27.1(zod@4.3.6))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(zod@4.3.6)':
dependencies:
'@modelcontextprotocol/sdk': 1.27.1(zod@4.3.6)
zod: 4.3.6
optionalDependencies:
react: 19.2.4
react-dom: 19.2.4(react@19.2.4)
'@modelcontextprotocol/sdk@1.26.0(zod@3.25.76)':
dependencies:
'@hono/node-server': 1.19.9(hono@4.12.0)
@@ -14882,6 +15057,28 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@modelcontextprotocol/sdk@1.27.1(zod@4.3.6)':
dependencies:
'@hono/node-server': 1.19.9(hono@4.12.0)
ajv: 8.17.1
ajv-formats: 3.0.1(ajv@8.17.1)
content-type: 1.0.5
cors: 2.8.6
cross-spawn: 7.0.6
eventsource: 3.0.7
eventsource-parser: 3.0.6
express: 5.2.1
express-rate-limit: 8.2.1(express@5.2.1)
hono: 4.12.0
jose: 6.1.3
json-schema-typed: 8.0.2
pkce-challenge: 5.0.1
raw-body: 3.0.2
zod: 4.3.6
zod-to-json-schema: 3.25.1(zod@4.3.6)
transitivePeerDependencies:
- supports-color
'@mongodb-js/zstd@7.0.0':
dependencies:
node-addon-api: 8.5.0
@@ -17744,7 +17941,7 @@ snapshots:
'@stripe/ui-extension-tools@0.0.1(@babel/core@7.29.0)(babel-jest@27.5.1(@babel/core@7.29.0))':
dependencies:
'@types/jest': 28.1.8
'@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)
'@typescript-eslint/eslint-plugin': 5.62.0(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)
'@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
eslint: 8.57.1
eslint-plugin-react: 7.37.5(eslint@8.57.1)
@@ -17977,6 +18174,13 @@ snapshots:
postcss: 8.5.6
tailwindcss: 4.1.18
'@tailwindcss/vite@4.2.1(vite@6.4.1(@types/node@22.19.6)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))':
dependencies:
'@tailwindcss/node': 4.2.1
'@tailwindcss/oxide': 4.2.1
tailwindcss: 4.2.1
vite: 6.4.1(@types/node@22.19.6)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
'@tailwindcss/vite@4.2.1(vite@7.3.1(@types/node@22.19.6)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))':
dependencies:
'@tailwindcss/node': 4.2.1
@@ -18263,7 +18467,7 @@ snapshots:
'@types/node': 22.19.6
optional: true
'@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.1)(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)':
'@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@9.39.2(jiti@2.6.1))(typescript@4.9.5))(eslint@8.57.1)(typescript@4.9.5)':
dependencies:
'@eslint-community/regexpp': 4.12.2
'@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@4.9.5)
@@ -18497,6 +18701,18 @@ snapshots:
'@visual-json/core': 0.1.1
react: 19.2.3
'@vitejs/plugin-react@5.1.4(vite@6.4.1(@types/node@22.19.6)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))':
dependencies:
'@babel/core': 7.29.0
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.29.0)
'@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.29.0)
'@rolldown/pluginutils': 1.0.0-rc.3
'@types/babel__core': 7.20.5
react-refresh: 0.18.0
vite: 6.4.1(@types/node@22.19.6)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
transitivePeerDependencies:
- supports-color
'@vitejs/plugin-react@5.1.4(vite@7.3.1(@types/node@22.19.6)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2))':
dependencies:
'@babel/core': 7.29.0
@@ -26448,6 +26664,29 @@ snapshots:
string_decoder: 1.3.0
util-deprecate: 1.0.2
vite-plugin-singlefile@2.3.0(rollup@4.55.1)(vite@6.4.1(@types/node@22.19.6)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)):
dependencies:
micromatch: 4.0.8
rollup: 4.55.1
vite: 6.4.1(@types/node@22.19.6)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2)
vite@6.4.1(@types/node@22.19.6)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2):
dependencies:
esbuild: 0.25.12
fdir: 6.5.0(picomatch@4.0.3)
picomatch: 4.0.3
postcss: 8.5.6
rollup: 4.55.1
tinyglobby: 0.2.15
optionalDependencies:
'@types/node': 22.19.6
fsevents: 2.3.3
jiti: 2.6.1
lightningcss: 1.31.1
terser: 5.46.0
tsx: 4.21.0
yaml: 2.8.2
vite@7.3.1(@types/node@22.19.6)(jiti@2.6.1)(lightningcss@1.31.1)(terser@5.46.0)(tsx@4.21.0)(yaml@2.8.2):
dependencies:
esbuild: 0.27.2
@@ -26903,6 +27142,10 @@ snapshots:
dependencies:
zod: 3.25.76
zod-to-json-schema@3.25.1(zod@4.3.6):
dependencies:
zod: 4.3.6
zod@3.22.3: {}
zod@3.25.76: {}

Some files were not shown because too many files have changed in this diff Show More