From 3418d42a434e9336878fb8802452d2a626e94855 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 9 Mar 2026 01:18:31 -0500 Subject: [PATCH 1/3] svelte/vue clients for mcp --- apps/web/app/(main)/docs/api/mcp/page.mdx | 75 ++++++++-- examples/mcp/src/mcp-app-view.tsx | 2 +- packages/mcp/README.md | 64 ++++++++- packages/mcp/package.json | 29 +++- packages/mcp/src/app.ts | 16 ++- .../{use-json-render-app.ts => app/react.ts} | 37 +---- packages/mcp/src/app/shared.ts | 61 +++++++++ packages/mcp/src/app/svelte.ts | 129 ++++++++++++++++++ packages/mcp/src/app/vue.ts | 124 +++++++++++++++++ packages/mcp/tsup.config.ts | 11 +- pnpm-lock.yaml | 6 + skills/mcp/SKILL.md | 76 +++++++++-- 12 files changed, 565 insertions(+), 65 deletions(-) rename packages/mcp/src/{use-json-render-app.ts => app/react.ts} (77%) create mode 100644 packages/mcp/src/app/shared.ts create mode 100644 packages/mcp/src/app/svelte.ts create mode 100644 packages/mcp/src/app/vue.ts diff --git a/apps/web/app/(main)/docs/api/mcp/page.mdx b/apps/web/app/(main)/docs/api/mcp/page.mdx index 2bb49487..a6949c2d 100644 --- a/apps/web/app/(main)/docs/api/mcp/page.mdx +++ b/apps/web/app/(main)/docs/api/mcp/page.mdx @@ -11,10 +11,17 @@ MCP Apps integration for json-render. Serve json-render UIs as interactive [MCP npm install @json-render/mcp @json-render/core @modelcontextprotocol/sdk ``` -For the iframe-side React UI, also install: +For the iframe-side UI, install the renderer for your framework: ```bash +# React npm install @json-render/react react react-dom + +# Vue +npm install @json-render/vue vue + +# Svelte +npm install @json-render/svelte svelte ``` See the [MCP example](https://github.com/vercel-labs/json-render/tree/main/examples/mcp) for a full working example. @@ -117,16 +124,22 @@ registerJsonRenderResource(server, { }); ``` -## Client API (`@json-render/mcp/app`) +## Client API + +These exports run inside the sandboxed iframe rendered by the MCP host. Framework-specific adapters are available at: -These exports run inside the sandboxed iframe rendered by the MCP host. +- `@json-render/mcp/app/react` -- React hook +- `@json-render/mcp/app/vue` -- Vue composable +- `@json-render/mcp/app/svelte` -- Svelte stores -### useJsonRenderApp +The React hook is also re-exported from `@json-render/mcp/app` for backward compatibility. + +### React -- useJsonRenderApp (`@json-render/mcp/app/react`) 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 { useJsonRenderApp } from "@json-render/mcp/app/react"; import { JSONUIProvider, Renderer } from "@json-render/react"; function McpAppView({ registry }) { @@ -146,7 +159,51 @@ function McpAppView({ registry }) { } ``` -#### UseJsonRenderAppReturn +### Vue -- useJsonRenderApp (`@json-render/mcp/app/vue`) + +Vue composable with the same API shape. Values are Vue refs and computed properties. + +```vue + + + +``` + +### Svelte -- createJsonRenderApp (`@json-render/mcp/app/svelte`) + +Creates a json-render MCP App client using Svelte stores. Call `destroy()` in `onDestroy` to clean up. + +```svelte + + +{#if $error} +
Error: {$error.message}
+{:else if !$spec} +
Waiting...
+{:else} + +{/if} +``` + +### Return values + +All three adapters return the same logical state: @@ -195,9 +252,11 @@ function McpAppView({ registry }) {
-### buildAppHtml +In React, values are plain primitives. In Vue, they are `Ref`/`ShallowRef`/`ComputedRef`. In Svelte, they are readable stores (use `$` prefix to subscribe). The Svelte adapter also returns a `destroy` function for cleanup. + +### buildAppHtml (`@json-render/mcp/app`) -Generate a self-contained HTML page from bundled JavaScript and CSS. +Generate a self-contained HTML page from bundled JavaScript and CSS. Framework-agnostic. ```typescript import { buildAppHtml } from "@json-render/mcp/app"; diff --git a/examples/mcp/src/mcp-app-view.tsx b/examples/mcp/src/mcp-app-view.tsx index 7e853da7..7f170314 100644 --- a/examples/mcp/src/mcp-app-view.tsx +++ b/examples/mcp/src/mcp-app-view.tsx @@ -1,7 +1,7 @@ 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 { useJsonRenderApp } from "@json-render/mcp/app/react"; import { catalog } from "./catalog"; import { useState, useEffect } from "react"; diff --git a/packages/mcp/README.md b/packages/mcp/README.md index c2b79d34..41d037e6 100644 --- a/packages/mcp/README.md +++ b/packages/mcp/README.md @@ -46,10 +46,12 @@ await server.connect(new StdioServerTransport()); ### 3. Build the UI (iframe) -Create a React app that uses `useJsonRenderApp` from `@json-render/mcp/app`: +Create an app that connects to the MCP host and renders specs. Framework-specific adapters are available for React, Vue, and Svelte. + +#### React ```tsx -import { useJsonRenderApp } from "@json-render/mcp/app"; +import { useJsonRenderApp } from "@json-render/mcp/app/react"; import { JSONUIProvider, Renderer } from "@json-render/react"; function McpAppView({ registry }) { @@ -66,6 +68,44 @@ function McpAppView({ registry }) { } ``` +#### Vue + +```vue + + + +``` + +#### Svelte + +```svelte + + +{#if $error} +
Error: {$error.message}
+{:else if !$spec} +
Waiting for spec...
+{:else} + +{/if} +``` + 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 @@ -107,17 +147,29 @@ Register a json-render tool on an existing `McpServer`. Register a json-render UI resource on an existing `McpServer`. -### Client Side (`@json-render/mcp/app`) +### Client Side -#### `useJsonRenderApp(options?)` +#### `buildAppHtml(options)` — `@json-render/mcp/app` + +Generate a self-contained HTML string from bundled JS/CSS for use as a UI resource. + +#### `useJsonRenderApp(options?)` — `@json-render/mcp/app/react` 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)` +#### `useJsonRenderApp(options?)` — `@json-render/mcp/app/vue` -Generate a self-contained HTML string from bundled JS/CSS for use as a UI resource. +Vue composable with the same API shape. Values are Vue refs/computed properties. + +Returns `{ spec, loading, connected, connecting, error, app, callServerTool }`. + +#### `createJsonRenderApp(options?)` — `@json-render/mcp/app/svelte` + +Creates a json-render MCP App client using Svelte stores. Values are readable stores. + +Returns `{ spec, loading, connected, connecting, error, app, callServerTool, destroy }`. ## Client Support diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 2ff9eb89..3eb721e0 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -42,6 +42,21 @@ "types": "./dist/app.d.ts", "import": "./dist/app.mjs", "require": "./dist/app.js" + }, + "./app/react": { + "types": "./dist/app/react.d.ts", + "import": "./dist/app/react.mjs", + "require": "./dist/app/react.js" + }, + "./app/vue": { + "types": "./dist/app/vue.d.ts", + "import": "./dist/app/vue.mjs", + "require": "./dist/app/vue.js" + }, + "./app/svelte": { + "types": "./dist/app/svelte.d.ts", + "import": "./dist/app/svelte.mjs", + "require": "./dist/app/svelte.js" } }, "files": [ @@ -60,12 +75,16 @@ "devDependencies": { "@internal/typescript-config": "workspace:*", "@types/react": "19.2.3", + "svelte": "^5.0.0", "tsup": "^8.0.2", - "typescript": "^5.4.5" + "typescript": "^5.4.5", + "vue": "^3.5.0" }, "peerDependencies": { "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "svelte": "^5.0.0", + "vue": "^3.5.0" }, "peerDependenciesMeta": { "react": { @@ -73,6 +92,12 @@ }, "react-dom": { "optional": true + }, + "svelte": { + "optional": true + }, + "vue": { + "optional": true } } } diff --git a/packages/mcp/src/app.ts b/packages/mcp/src/app.ts index c00b0345..443701f0 100644 --- a/packages/mcp/src/app.ts +++ b/packages/mcp/src/app.ts @@ -5,11 +5,18 @@ * 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. + * framework-specific hooks / helpers to render them. + * + * Framework-specific adapters are available at: + * - `@json-render/mcp/app/react` — React hook + * - `@json-render/mcp/app/vue` — Vue composable + * - `@json-render/mcp/app/svelte` — Svelte stores + * + * The React hook is also re-exported here for backward compatibility. * * @example * ```tsx - * import { useJsonRenderApp } from "@json-render/mcp/app"; + * import { useJsonRenderApp } from "@json-render/mcp/app/react"; * import { Renderer } from "@json-render/react"; * * function McpAppView({ registry }) { @@ -21,10 +28,11 @@ * @packageDocumentation */ -export { useJsonRenderApp } from "./use-json-render-app.js"; +export { useJsonRenderApp } from "./app/react.js"; export type { UseJsonRenderAppOptions, UseJsonRenderAppReturn, -} from "./use-json-render-app.js"; +} from "./app/react.js"; export { buildAppHtml } from "./build-app-html.js"; export type { BuildAppHtmlOptions } from "./build-app-html.js"; +export type { JsonRenderAppOptions, JsonRenderAppState } from "./app/shared.js"; diff --git a/packages/mcp/src/use-json-render-app.ts b/packages/mcp/src/app/react.ts similarity index 77% rename from packages/mcp/src/use-json-render-app.ts rename to packages/mcp/src/app/react.ts index c85a9294..cb9e6650 100644 --- a/packages/mcp/src/use-json-render-app.ts +++ b/packages/mcp/src/app/react.ts @@ -1,16 +1,16 @@ import { useState, useEffect, useCallback, useRef } from "react"; import type { Spec } from "@json-render/core"; import { App } from "@modelcontextprotocol/ext-apps"; +import { + parseSpecFromToolResult, + type JsonRenderAppOptions, + type ToolResultContent, +} from "./shared.js"; /** * 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; -} +export type UseJsonRenderAppOptions = JsonRenderAppOptions; /** * Return value of `useJsonRenderApp`. @@ -38,29 +38,6 @@ export interface UseJsonRenderAppReturn { ) => Promise; } -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. @@ -92,8 +69,6 @@ export function useJsonRenderApp( } }; - // Let the App class handle transport creation internally, - // matching the official MCP Apps quickstart pattern. app .connect() .then(() => { diff --git a/packages/mcp/src/app/shared.ts b/packages/mcp/src/app/shared.ts new file mode 100644 index 00000000..16035d48 --- /dev/null +++ b/packages/mcp/src/app/shared.ts @@ -0,0 +1,61 @@ +import type { Spec } from "@json-render/core"; +import type { App } from "@modelcontextprotocol/ext-apps"; + +/** + * Options for creating a json-render MCP App client. + */ +export interface JsonRenderAppOptions { + /** App name shown during initialization. Defaults to `"json-render"`. */ + name?: string; + /** App version. Defaults to `"1.0.0"`. */ + version?: string; +} + +/** + * State returned by the json-render MCP App client across all frameworks. + */ +export interface JsonRenderAppState { + /** 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, + ) => Promise; +} + +export interface ToolResultContent { + type: string; + text?: string; +} + +export 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; + } +} diff --git a/packages/mcp/src/app/svelte.ts b/packages/mcp/src/app/svelte.ts new file mode 100644 index 00000000..85eee287 --- /dev/null +++ b/packages/mcp/src/app/svelte.ts @@ -0,0 +1,129 @@ +import { writable, derived, type Readable, type Writable } from "svelte/store"; +import type { Spec } from "@json-render/core"; +import { App } from "@modelcontextprotocol/ext-apps"; +import { + parseSpecFromToolResult, + type JsonRenderAppOptions, + type ToolResultContent, +} from "./shared.js"; + +/** + * Options for `createJsonRenderApp`. + */ +export type CreateJsonRenderAppOptions = JsonRenderAppOptions; + +/** + * Return value of `createJsonRenderApp`. + */ +export interface CreateJsonRenderAppReturn { + /** The current json-render spec (null until the first tool result). */ + spec: Readable; + /** Whether the app is still connecting to the host. */ + connecting: Readable; + /** Whether the app is connected to the host. */ + connected: Readable; + /** Connection error, if any. */ + error: Readable; + /** Whether the spec is still being received / parsed. */ + loading: Readable; + /** The underlying MCP App instance. */ + app: Readable; + /** + * 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, + ) => Promise; + /** Clean up the MCP App connection. Call in `onDestroy`. */ + destroy: () => void; +} + +/** + * Create a json-render MCP App client using Svelte stores. + * + * Connects to the MCP host, listens for tool results, and maintains + * the current json-render spec as a readable store. + * + * Call `destroy()` in your component's `onDestroy` to clean up. + * + * @example + * ```svelte + * + * ``` + */ +export function createJsonRenderApp( + options: CreateJsonRenderAppOptions = {}, +): CreateJsonRenderAppReturn { + const { name = "json-render", version = "1.0.0" } = options; + + const spec: Writable = writable(null); + const loading: Writable = writable(true); + const connected: Writable = writable(false); + const error: Writable = writable(null); + const appStore: Writable = writable(null); + + const connecting: Readable = derived( + [connected, error], + ([$connected, $error]) => !$connected && !$error, + ); + + const app = new App({ name, version }); + appStore.set(app); + + app.ontoolresult = (result: { content?: ToolResultContent[] }) => { + const parsed = parseSpecFromToolResult(result); + if (parsed) { + spec.set(parsed); + loading.set(false); + } + }; + + app + .connect() + .then(() => { + connected.set(true); + }) + .catch((err: unknown) => { + error.set(err instanceof Error ? err : new Error(String(err))); + }); + + async function callServerTool( + toolName: string, + args: Record = {}, + ): Promise { + loading.set(true); + try { + const result = await app.callServerTool({ + name: toolName, + arguments: args, + }); + const parsed = parseSpecFromToolResult(result); + if (parsed) spec.set(parsed); + } finally { + loading.set(false); + } + } + + function destroy(): void { + app.close().catch(() => {}); + } + + return { + spec, + connecting, + connected, + error, + loading, + app: appStore, + callServerTool, + destroy, + }; +} diff --git a/packages/mcp/src/app/vue.ts b/packages/mcp/src/app/vue.ts new file mode 100644 index 00000000..c4f2af85 --- /dev/null +++ b/packages/mcp/src/app/vue.ts @@ -0,0 +1,124 @@ +import { + shallowRef, + ref, + computed, + onMounted, + onUnmounted, + type ShallowRef, + type Ref, + type ComputedRef, +} from "vue"; +import type { Spec } from "@json-render/core"; +import { App } from "@modelcontextprotocol/ext-apps"; +import { + parseSpecFromToolResult, + type JsonRenderAppOptions, + type ToolResultContent, +} from "./shared.js"; + +/** + * Options for the `useJsonRenderApp` composable. + */ +export type UseJsonRenderAppOptions = JsonRenderAppOptions; + +/** + * Return value of `useJsonRenderApp`. + */ +export interface UseJsonRenderAppReturn { + /** The current json-render spec (null until the first tool result). */ + spec: ShallowRef; + /** Whether the app is still connecting to the host. */ + connecting: ComputedRef; + /** Whether the app is connected to the host. */ + connected: Ref; + /** Connection error, if any. */ + error: Ref; + /** Whether the spec is still being received / parsed. */ + loading: Ref; + /** The underlying MCP App instance. */ + app: ShallowRef; + /** + * 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, + ) => Promise; +} + +/** + * Vue composable that connects to the MCP host, listens for tool results, + * and maintains the current json-render spec. + * + * Must be called inside a component's `setup` function or ` + + +``` + +#### Svelte + +```svelte + + +{#if $error} +
Error: {$error.message}
+{:else if !$spec} +
Waiting...
+{:else} + +{/if} +``` + ## Architecture 1. `createMcpApp()` creates an `McpServer` that registers a `render-ui` tool and a `ui://` HTML resource 2. The tool description includes the catalog prompt so the LLM knows how to generate valid specs -3. The HTML resource is a Vite-bundled single-file React app with json-render renderers -4. Inside the iframe, `useJsonRenderApp()` connects to the host via `postMessage` and renders specs +3. The HTML resource is a Vite-bundled single-file app (React, Vue, or Svelte) with json-render renderers +4. Inside the iframe, the framework adapter connects to the host via `postMessage` and renders specs ## Server API @@ -65,17 +111,21 @@ function McpAppView({ registry }) { - `registerJsonRenderTool(server, options)` - register a json-render tool on an existing server - `registerJsonRenderResource(server, options)` - register the UI resource -## Client API (`@json-render/mcp/app`) +## Client API + +- `buildAppHtml(options)` (`@json-render/mcp/app`) - generate HTML from bundled JS/CSS +- `useJsonRenderApp(options?)` (`@json-render/mcp/app/react`) - React hook +- `useJsonRenderApp(options?)` (`@json-render/mcp/app/vue`) - Vue composable +- `createJsonRenderApp(options?)` (`@json-render/mcp/app/svelte`) - Svelte stores + `destroy()` -- `useJsonRenderApp(options?)` - React hook, returns `{ spec, loading, connected, error, callServerTool }` -- `buildAppHtml(options)` - generate HTML from bundled JS/CSS +All adapters return `{ spec, loading, connected, connecting, error, app, callServerTool }`. ## Building the iframe HTML -Bundle the React app into a single self-contained HTML file using Vite + `vite-plugin-singlefile`: +Bundle your app into a single self-contained HTML file using Vite + `vite-plugin-singlefile`. Add the appropriate framework plugin: ```typescript -// vite.config.ts +// vite.config.ts (React example) import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import { viteSingleFile } from "vite-plugin-singlefile"; @@ -120,9 +170,11 @@ export default defineConfig({ # Server npm install @json-render/mcp @json-render/core @modelcontextprotocol/sdk -# Client (iframe) -npm install @json-render/react @json-render/shadcn react react-dom +# Client (iframe) -- pick your framework +npm install @json-render/react react react-dom # React +npm install @json-render/vue vue # Vue +npm install @json-render/svelte svelte # Svelte # Build tools -npm install -D vite @vitejs/plugin-react vite-plugin-singlefile +npm install -D vite vite-plugin-singlefile ``` From 5b51204960d9682293e53970b595bee240182021 Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 9 Mar 2026 01:34:12 -0500 Subject: [PATCH 2/3] vue --- .cursor/mcp.json | 12 +- README.md | 1 + examples/chat/next-env.d.ts | 2 +- examples/dashboard/next-env.d.ts | 2 +- examples/mcp-svelte/index.html | 13 ++ examples/mcp-svelte/package.json | 26 ++++ examples/mcp-svelte/server.ts | 34 +++++ examples/mcp-svelte/src/App.svelte | 26 ++++ examples/mcp-svelte/src/catalog.ts | 54 +++++++ .../mcp-svelte/src/components/Badge.svelte | 24 ++++ .../mcp-svelte/src/components/Button.svelte | 43 ++++++ .../mcp-svelte/src/components/Card.svelte | 45 ++++++ .../mcp-svelte/src/components/Stack.svelte | 35 +++++ .../mcp-svelte/src/components/Text.svelte | 34 +++++ examples/mcp-svelte/src/main.ts | 4 + examples/mcp-svelte/src/registry.ts | 18 +++ examples/mcp-svelte/svelte.config.js | 1 + examples/mcp-svelte/tsconfig.json | 16 +++ examples/mcp-svelte/vite.config.ts | 14 ++ examples/mcp-vue/index.html | 13 ++ examples/mcp-vue/package.json | 27 ++++ examples/mcp-vue/server.ts | 34 +++++ examples/mcp-vue/src/App.vue | 25 ++++ examples/mcp-vue/src/catalog.ts | 56 ++++++++ examples/mcp-vue/src/main.ts | 4 + examples/mcp-vue/src/registry.ts | 136 ++++++++++++++++++ examples/mcp-vue/tsconfig.json | 17 +++ examples/mcp-vue/vite.config.ts | 14 ++ examples/no-ai/next-env.d.ts | 2 +- examples/react-pdf/next-env.d.ts | 2 +- examples/remotion/next-env.d.ts | 2 +- examples/stripe-app/api/next-env.d.ts | 2 +- pnpm-lock.yaml | 104 ++++++++++++-- 33 files changed, 820 insertions(+), 22 deletions(-) create mode 100644 examples/mcp-svelte/index.html create mode 100644 examples/mcp-svelte/package.json create mode 100644 examples/mcp-svelte/server.ts create mode 100644 examples/mcp-svelte/src/App.svelte create mode 100644 examples/mcp-svelte/src/catalog.ts create mode 100644 examples/mcp-svelte/src/components/Badge.svelte create mode 100644 examples/mcp-svelte/src/components/Button.svelte create mode 100644 examples/mcp-svelte/src/components/Card.svelte create mode 100644 examples/mcp-svelte/src/components/Stack.svelte create mode 100644 examples/mcp-svelte/src/components/Text.svelte create mode 100644 examples/mcp-svelte/src/main.ts create mode 100644 examples/mcp-svelte/src/registry.ts create mode 100644 examples/mcp-svelte/svelte.config.js create mode 100644 examples/mcp-svelte/tsconfig.json create mode 100644 examples/mcp-svelte/vite.config.ts create mode 100644 examples/mcp-vue/index.html create mode 100644 examples/mcp-vue/package.json create mode 100644 examples/mcp-vue/server.ts create mode 100644 examples/mcp-vue/src/App.vue create mode 100644 examples/mcp-vue/src/catalog.ts create mode 100644 examples/mcp-vue/src/main.ts create mode 100644 examples/mcp-vue/src/registry.ts create mode 100644 examples/mcp-vue/tsconfig.json create mode 100644 examples/mcp-vue/vite.config.ts diff --git a/.cursor/mcp.json b/.cursor/mcp.json index 5f211d4f..902d4562 100644 --- a/.cursor/mcp.json +++ b/.cursor/mcp.json @@ -1,8 +1,16 @@ { "mcpServers": { - "json-render": { + "json-render-react": { "command": "npx", "args": ["tsx", "examples/mcp/server.ts", "--stdio"] + }, + "json-render-vue": { + "command": "npx", + "args": ["tsx", "examples/mcp-vue/server.ts", "--stdio"] + }, + "json-render-svelte": { + "command": "npx", + "args": ["tsx", "examples/mcp-svelte/server.ts", "--stdio"] } } -} +} \ No newline at end of file diff --git a/README.md b/README.md index 91b9a006..404f917d 100644 --- a/README.md +++ b/README.md @@ -486,6 +486,7 @@ pnpm dev - Chat Example: run `pnpm dev` in `examples/chat` - Svelte Example: run `pnpm dev` in `examples/svelte` or `examples/svelte-chat` - Vue Example: run `pnpm dev` in `examples/vue` +- MCP Vue Example: run `pnpm build && pnpm start:stdio` in `examples/mcp-vue` - Vite Renderers (React + Vue + Svelte): run `pnpm dev` in `examples/vite-renderers` - React Native example: run `npx expo start` in `examples/react-native` diff --git a/examples/chat/next-env.d.ts b/examples/chat/next-env.d.ts index c4b7818f..9edff1c7 100644 --- a/examples/chat/next-env.d.ts +++ b/examples/chat/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -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. diff --git a/examples/dashboard/next-env.d.ts b/examples/dashboard/next-env.d.ts index c4b7818f..9edff1c7 100644 --- a/examples/dashboard/next-env.d.ts +++ b/examples/dashboard/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -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. diff --git a/examples/mcp-svelte/index.html b/examples/mcp-svelte/index.html new file mode 100644 index 00000000..6dc94b78 --- /dev/null +++ b/examples/mcp-svelte/index.html @@ -0,0 +1,13 @@ + + + + + + + json-render MCP App (Svelte) + + +
+ + + diff --git a/examples/mcp-svelte/package.json b/examples/mcp-svelte/package.json new file mode 100644 index 00000000..ded2d865 --- /dev/null +++ b/examples/mcp-svelte/package.json @@ -0,0 +1,26 @@ +{ + "name": "example-mcp-svelte", + "version": "0.1.0", + "type": "module", + "private": true, + "scripts": { + "build": "vite build", + "start": "tsx server.ts --stdio" + }, + "dependencies": { + "@json-render/core": "workspace:*", + "@json-render/mcp": "workspace:*", + "@json-render/svelte": "workspace:*", + "@modelcontextprotocol/ext-apps": "^1.2.0", + "@modelcontextprotocol/sdk": "^1.27.1", + "svelte": "^5.49.2", + "zod": "^4.3.6" + }, + "devDependencies": { + "@sveltejs/vite-plugin-svelte": "^6.2.4", + "tsx": "^4.21.0", + "typescript": "^5.9.3", + "vite": "^7.3.1", + "vite-plugin-singlefile": "^2.3.0" + } +} diff --git a/examples/mcp-svelte/server.ts b/examples/mcp-svelte/server.ts new file mode 100644 index 00000000..4bbb2536 --- /dev/null +++ b/examples/mcp-svelte/server.ts @@ -0,0 +1,34 @@ +import { createMcpApp } from "@json-render/mcp"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.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 main() { + const html = loadHtml(); + const server = await createMcpApp({ + name: "json-render Svelte Example", + version: "1.0.0", + catalog, + html, + }); + await server.connect(new StdioServerTransport()); +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/examples/mcp-svelte/src/App.svelte b/examples/mcp-svelte/src/App.svelte new file mode 100644 index 00000000..9e468655 --- /dev/null +++ b/examples/mcp-svelte/src/App.svelte @@ -0,0 +1,26 @@ + + +{#if $error} +
+ {$error.message} +
+{:else if !$spec} +
+ {$connecting ? "Connecting to host..." : "Waiting for UI spec..."} +
+{:else} + + + +{/if} diff --git a/examples/mcp-svelte/src/catalog.ts b/examples/mcp-svelte/src/catalog.ts new file mode 100644 index 00000000..51d641d2 --- /dev/null +++ b/examples/mcp-svelte/src/catalog.ts @@ -0,0 +1,54 @@ +import { schema } from "@json-render/svelte/schema"; +import { z } from "zod"; + +export const catalog = schema.createCatalog({ + components: { + Stack: { + props: z.object({ + gap: z.number().optional(), + padding: z.number().optional(), + direction: z.enum(["vertical", "horizontal"]).optional(), + align: z.enum(["start", "center", "end"]).optional(), + }), + slots: ["default"], + description: + "Layout container that stacks children vertically or horizontally", + }, + Card: { + props: z.object({ + title: z.string().optional(), + subtitle: z.string().optional(), + }), + slots: ["default"], + description: "A card container with optional title and subtitle", + }, + Text: { + props: z.object({ + content: z.string(), + size: z.enum(["sm", "md", "lg", "xl"]).optional(), + weight: z.enum(["normal", "medium", "bold"]).optional(), + color: z.string().optional(), + }), + slots: [], + description: "Displays a text string", + }, + Button: { + props: z.object({ + label: z.string(), + variant: z.enum(["primary", "secondary", "danger"]).optional(), + disabled: z.boolean().optional(), + }), + slots: [], + description: "A clickable button that emits a 'press' event", + }, + Badge: { + props: z.object({ + label: z.string(), + color: z.string().optional(), + }), + slots: [], + description: "A small badge/tag label", + }, + }, + actions: {}, +}); diff --git a/examples/mcp-svelte/src/components/Badge.svelte b/examples/mcp-svelte/src/components/Badge.svelte new file mode 100644 index 00000000..a1169973 --- /dev/null +++ b/examples/mcp-svelte/src/components/Badge.svelte @@ -0,0 +1,24 @@ + + + + {props.label} + diff --git a/examples/mcp-svelte/src/components/Button.svelte b/examples/mcp-svelte/src/components/Button.svelte new file mode 100644 index 00000000..4a9dd222 --- /dev/null +++ b/examples/mcp-svelte/src/components/Button.svelte @@ -0,0 +1,43 @@ + + + diff --git a/examples/mcp-svelte/src/components/Card.svelte b/examples/mcp-svelte/src/components/Card.svelte new file mode 100644 index 00000000..30d5de46 --- /dev/null +++ b/examples/mcp-svelte/src/components/Card.svelte @@ -0,0 +1,45 @@ + + +
+ {#if props.title} +

+ {props.title} +

+ {/if} + {#if props.subtitle} +

+ {props.subtitle} +

+ {/if} + {#if children} + {@render children()} + {/if} +
diff --git a/examples/mcp-svelte/src/components/Stack.svelte b/examples/mcp-svelte/src/components/Stack.svelte new file mode 100644 index 00000000..ffe8fd7c --- /dev/null +++ b/examples/mcp-svelte/src/components/Stack.svelte @@ -0,0 +1,35 @@ + + +
+ {#if children} + {@render children()} + {/if} +
diff --git a/examples/mcp-svelte/src/components/Text.svelte b/examples/mcp-svelte/src/components/Text.svelte new file mode 100644 index 00000000..53516d35 --- /dev/null +++ b/examples/mcp-svelte/src/components/Text.svelte @@ -0,0 +1,34 @@ + + + + {String(props.content ?? "")} + diff --git a/examples/mcp-svelte/src/main.ts b/examples/mcp-svelte/src/main.ts new file mode 100644 index 00000000..99fc6a4a --- /dev/null +++ b/examples/mcp-svelte/src/main.ts @@ -0,0 +1,4 @@ +import { mount } from "svelte"; +import App from "./App.svelte"; + +mount(App, { target: document.getElementById("app")! }); diff --git a/examples/mcp-svelte/src/registry.ts b/examples/mcp-svelte/src/registry.ts new file mode 100644 index 00000000..f7086448 --- /dev/null +++ b/examples/mcp-svelte/src/registry.ts @@ -0,0 +1,18 @@ +import { type Components, defineRegistry } from "@json-render/svelte"; +import { catalog } from "./catalog"; + +import Stack from "./components/Stack.svelte"; +import Card from "./components/Card.svelte"; +import Text from "./components/Text.svelte"; +import Button from "./components/Button.svelte"; +import Badge from "./components/Badge.svelte"; + +const components: Components = { + Stack, + Card, + Text, + Button, + Badge, +}; + +export const { registry } = defineRegistry(catalog, { components }); diff --git a/examples/mcp-svelte/svelte.config.js b/examples/mcp-svelte/svelte.config.js new file mode 100644 index 00000000..ff8b4c56 --- /dev/null +++ b/examples/mcp-svelte/svelte.config.js @@ -0,0 +1 @@ +export default {}; diff --git a/examples/mcp-svelte/tsconfig.json b/examples/mcp-svelte/tsconfig.json new file mode 100644 index 00000000..5155a5e2 --- /dev/null +++ b/examples/mcp-svelte/tsconfig.json @@ -0,0 +1,16 @@ +{ + "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 + }, + "include": ["src", "server.ts"] +} diff --git a/examples/mcp-svelte/vite.config.ts b/examples/mcp-svelte/vite.config.ts new file mode 100644 index 00000000..0ca0a97b --- /dev/null +++ b/examples/mcp-svelte/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from "vite"; +import { svelte } from "@sveltejs/vite-plugin-svelte"; +import { viteSingleFile } from "vite-plugin-singlefile"; + +export default defineConfig({ + plugins: [svelte(), viteSingleFile()], + build: { + outDir: "dist", + emptyOutDir: false, + rollupOptions: { + input: "index.html", + }, + }, +}); diff --git a/examples/mcp-vue/index.html b/examples/mcp-vue/index.html new file mode 100644 index 00000000..5e254659 --- /dev/null +++ b/examples/mcp-vue/index.html @@ -0,0 +1,13 @@ + + + + + + + json-render MCP App (Vue) + + +
+ + + diff --git a/examples/mcp-vue/package.json b/examples/mcp-vue/package.json new file mode 100644 index 00000000..7b2fb14b --- /dev/null +++ b/examples/mcp-vue/package.json @@ -0,0 +1,27 @@ +{ + "name": "example-mcp-vue", + "version": "0.1.0", + "type": "module", + "private": true, + "scripts": { + "build": "vite build", + "start": "tsx server.ts", + "start:stdio": "tsx server.ts --stdio" + }, + "dependencies": { + "@json-render/core": "workspace:*", + "@json-render/mcp": "workspace:*", + "@json-render/vue": "workspace:*", + "@modelcontextprotocol/ext-apps": "^1.2.0", + "@modelcontextprotocol/sdk": "^1.27.1", + "vue": "^3.5.0", + "zod": "^4.3.6" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^6.0.4", + "tsx": "^4.21.0", + "typescript": "^5.9.3", + "vite": "^7.3.1", + "vite-plugin-singlefile": "^2.3.0" + } +} diff --git a/examples/mcp-vue/server.ts b/examples/mcp-vue/server.ts new file mode 100644 index 00000000..72e512d9 --- /dev/null +++ b/examples/mcp-vue/server.ts @@ -0,0 +1,34 @@ +import { createMcpApp } from "@json-render/mcp"; +import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.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 main() { + const html = loadHtml(); + const server = await createMcpApp({ + name: "json-render Vue Example", + version: "1.0.0", + catalog, + html, + }); + await server.connect(new StdioServerTransport()); +} + +main().catch((e) => { + console.error(e); + process.exit(1); +}); diff --git a/examples/mcp-vue/src/App.vue b/examples/mcp-vue/src/App.vue new file mode 100644 index 00000000..ec347078 --- /dev/null +++ b/examples/mcp-vue/src/App.vue @@ -0,0 +1,25 @@ + + + diff --git a/examples/mcp-vue/src/catalog.ts b/examples/mcp-vue/src/catalog.ts new file mode 100644 index 00000000..c61a7b54 --- /dev/null +++ b/examples/mcp-vue/src/catalog.ts @@ -0,0 +1,56 @@ +import { schema } from "@json-render/vue/schema"; +import { z } from "zod"; + +export const catalog = schema.createCatalog({ + components: { + Stack: { + props: z.object({ + gap: z.number().optional(), + padding: z.number().optional(), + direction: z.enum(["vertical", "horizontal"]).optional(), + align: z.enum(["start", "center", "end"]).optional(), + }), + slots: ["default"], + description: + "Layout container that stacks children vertically or horizontally", + }, + Card: { + props: z.object({ + title: z.string().optional(), + subtitle: z.string().optional(), + }), + slots: ["default"], + description: "A card container with optional title and subtitle", + }, + Text: { + props: z.object({ + content: z.string(), + size: z.enum(["sm", "md", "lg", "xl"]).optional(), + weight: z.enum(["normal", "medium", "bold"]).optional(), + color: z.string().optional(), + }), + slots: [], + description: "Displays a text string", + }, + Button: { + props: z.object({ + label: z.string(), + variant: z.enum(["primary", "secondary", "danger"]).optional(), + disabled: z.boolean().optional(), + }), + slots: [], + description: "A clickable button that emits a 'press' event", + }, + Badge: { + props: z.object({ + label: z.string(), + color: z.string().optional(), + }), + slots: [], + description: "A small badge/tag label", + }, + }, + actions: {}, +}); + +export type AppCatalog = typeof catalog; diff --git a/examples/mcp-vue/src/main.ts b/examples/mcp-vue/src/main.ts new file mode 100644 index 00000000..b670de8b --- /dev/null +++ b/examples/mcp-vue/src/main.ts @@ -0,0 +1,4 @@ +import { createApp } from "vue"; +import App from "./App.vue"; + +createApp(App).mount("#app"); diff --git a/examples/mcp-vue/src/registry.ts b/examples/mcp-vue/src/registry.ts new file mode 100644 index 00000000..fc4222fb --- /dev/null +++ b/examples/mcp-vue/src/registry.ts @@ -0,0 +1,136 @@ +import { h } from "vue"; +import type { Components } from "@json-render/vue"; +import type { AppCatalog } from "./catalog"; + +export const components: Components = { + Stack: ({ props, children }) => { + const horizontal = props.direction === "horizontal"; + return h( + "div", + { + style: { + display: "flex", + flexDirection: horizontal ? "row" : "column", + gap: props.gap ? `${props.gap}px` : undefined, + padding: props.padding ? `${props.padding}px` : undefined, + alignItems: props.align ?? (horizontal ? "center" : "stretch"), + }, + }, + children, + ); + }, + + Card: ({ props, children }) => + h( + "div", + { + style: { + borderRadius: "12px", + border: "1px solid #e5e7eb", + padding: "20px", + boxShadow: "0 1px 3px rgba(0,0,0,0.05)", + }, + }, + [ + props.title && + h( + "h2", + { + style: { + fontSize: "16px", + fontWeight: "600", + margin: "0 0 4px 0", + }, + }, + props.title, + ), + props.subtitle && + h( + "p", + { + style: { + fontSize: "13px", + color: "#6b7280", + margin: "0 0 12px 0", + }, + }, + props.subtitle, + ), + children, + ], + ), + + Text: ({ props }) => { + const sizeMap: Record = { + sm: "12px", + md: "14px", + lg: "16px", + xl: "24px", + }; + const weightMap: Record = { + normal: "400", + medium: "500", + bold: "700", + }; + return h( + "span", + { + style: { + fontSize: sizeMap[props.size ?? "md"] ?? "14px", + fontWeight: weightMap[props.weight ?? "normal"] ?? "400", + color: props.color ?? "inherit", + }, + }, + String(props.content ?? ""), + ); + }, + + Button: ({ props, emit }) => + h( + "button", + { + disabled: props.disabled, + onClick: () => emit("press"), + style: { + padding: "8px 16px", + borderRadius: "8px", + border: "none", + cursor: props.disabled ? "not-allowed" : "pointer", + fontWeight: "500", + fontSize: "14px", + opacity: props.disabled ? "0.5" : "1", + backgroundColor: + props.variant === "danger" + ? "#fee2e2" + : props.variant === "secondary" + ? "#f3f4f6" + : "#3b82f6", + color: + props.variant === "danger" + ? "#dc2626" + : props.variant === "secondary" + ? "#374151" + : "white", + }, + }, + props.label, + ), + + Badge: ({ props }) => + h( + "span", + { + style: { + display: "inline-block", + padding: "4px 12px", + borderRadius: "999px", + fontSize: "13px", + fontWeight: "500", + backgroundColor: props.color ? `${props.color}20` : "#e0f2fe", + color: props.color ?? "#0369a1", + border: `1px solid ${props.color ? `${props.color}40` : "#bae6fd"}`, + }, + }, + props.label, + ), +}; diff --git a/examples/mcp-vue/tsconfig.json b/examples/mcp-vue/tsconfig.json new file mode 100644 index 00000000..596f0b3a --- /dev/null +++ b/examples/mcp-vue/tsconfig.json @@ -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": "preserve" + }, + "include": ["src", "server.ts"] +} diff --git a/examples/mcp-vue/vite.config.ts b/examples/mcp-vue/vite.config.ts new file mode 100644 index 00000000..e8598002 --- /dev/null +++ b/examples/mcp-vue/vite.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from "vite"; +import vue from "@vitejs/plugin-vue"; +import { viteSingleFile } from "vite-plugin-singlefile"; + +export default defineConfig({ + plugins: [vue(), viteSingleFile()], + build: { + outDir: "dist", + emptyOutDir: false, + rollupOptions: { + input: "index.html", + }, + }, +}); diff --git a/examples/no-ai/next-env.d.ts b/examples/no-ai/next-env.d.ts index c4b7818f..9edff1c7 100644 --- a/examples/no-ai/next-env.d.ts +++ b/examples/no-ai/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -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. diff --git a/examples/react-pdf/next-env.d.ts b/examples/react-pdf/next-env.d.ts index c4b7818f..9edff1c7 100644 --- a/examples/react-pdf/next-env.d.ts +++ b/examples/react-pdf/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -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. diff --git a/examples/remotion/next-env.d.ts b/examples/remotion/next-env.d.ts index c4b7818f..9edff1c7 100644 --- a/examples/remotion/next-env.d.ts +++ b/examples/remotion/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -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. diff --git a/examples/stripe-app/api/next-env.d.ts b/examples/stripe-app/api/next-env.d.ts index c4b7818f..9edff1c7 100644 --- a/examples/stripe-app/api/next-env.d.ts +++ b/examples/stripe-app/api/next-env.d.ts @@ -1,6 +1,6 @@ /// /// -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. diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7822f0cf..6d60b08b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -578,6 +578,86 @@ importers: 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/mcp-svelte: + dependencies: + '@json-render/core': + specifier: workspace:* + version: link:../../packages/core + '@json-render/mcp': + specifier: workspace:* + version: link:../../packages/mcp + '@json-render/svelte': + specifier: workspace:* + version: link:../../packages/svelte + '@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) + svelte: + specifier: ^5.49.2 + version: 5.53.5 + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@sveltejs/vite-plugin-svelte': + specifier: ^6.2.4 + version: 6.2.4(svelte@5.53.5)(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)) + tsx: + specifier: ^4.21.0 + version: 4.21.0 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vite: + specifier: ^7.3.1 + version: 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) + vite-plugin-singlefile: + specifier: ^2.3.0 + version: 2.3.0(rollup@4.55.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)) + + examples/mcp-vue: + dependencies: + '@json-render/core': + specifier: workspace:* + version: link:../../packages/core + '@json-render/mcp': + specifier: workspace:* + version: link:../../packages/mcp + '@json-render/vue': + specifier: workspace:* + version: link:../../packages/vue + '@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) + vue: + specifier: ^3.5.0 + version: 3.5.29(typescript@5.9.3) + zod: + specifier: ^4.3.6 + version: 4.3.6 + devDependencies: + '@vitejs/plugin-vue': + specifier: ^6.0.4 + version: 6.0.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))(vue@3.5.29(typescript@5.9.3)) + tsx: + specifier: ^4.21.0 + version: 4.21.0 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vite: + specifier: ^7.3.1 + version: 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) + vite-plugin-singlefile: + specifier: ^2.3.0 + version: 2.3.0(rollup@4.55.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)) + examples/no-ai: dependencies: '@json-render/core': @@ -679,7 +759,7 @@ importers: version: 0.575.0(react@19.2.4) next: specifier: 16.1.6 - 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) + 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) 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) @@ -813,7 +893,7 @@ importers: version: 0.575.0(react@19.2.4) next: specifier: 16.1.6 - 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) + 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) 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) @@ -3738,16 +3818,6 @@ packages: react-dom: optional: true - '@modelcontextprotocol/sdk@1.26.0': - resolution: {integrity: sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==} - engines: {node: '>=18'} - peerDependencies: - '@cfworker/json-schema': ^4.1.1 - zod: ^3.25 || ^4.0 - peerDependenciesMeta: - '@cfworker/json-schema': - optional: true - '@modelcontextprotocol/sdk@1.27.1': resolution: {integrity: sha512-sr6GbP+4edBwFndLbM60gf07z0FQ79gaExpnsjMGePXqFcSSb7t6iscpjk9DhFhwd+mTEQrzNafGP8/iGGFYaA==} engines: {node: '>=18'} @@ -15041,7 +15111,7 @@ snapshots: react: 19.2.4 react-dom: 19.2.4(react@19.2.4) - '@modelcontextprotocol/sdk@1.26.0(zod@3.25.76)': + '@modelcontextprotocol/sdk@1.27.1(zod@3.25.76)': dependencies: '@hono/node-server': 1.19.9(hono@4.12.0) ajv: 8.17.1 @@ -25471,7 +25541,7 @@ snapshots: '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) '@dotenvx/dotenvx': 1.52.0 - '@modelcontextprotocol/sdk': 1.26.0(zod@3.25.76) + '@modelcontextprotocol/sdk': 1.27.1(zod@3.25.76) '@types/validate-npm-package-name': 4.0.2 browserslist: 4.28.1 commander: 14.0.2 @@ -26676,6 +26746,12 @@ snapshots: 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-plugin-singlefile@2.3.0(rollup@4.55.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: + micromatch: 4.0.8 + rollup: 4.55.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) + 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 From b2020fad6ff323f83868c6fda976446ade75f63f Mon Sep 17 00:00:00 2001 From: Chris Tate Date: Mon, 9 Mar 2026 02:06:22 -0500 Subject: [PATCH 3/3] cleanup --- .cursor/mcp.json | 2 +- .gitignore | 1 + .vscode/mcp.json | 2 +- apps/web/app/(main)/docs/api/mcp/page.mdx | 2 +- apps/web/lib/docs-navigation.ts | 2 +- examples/{mcp => mcp-react}/CHANGELOG.md | 0 examples/{mcp => mcp-react}/README.md | 4 +- examples/{mcp => mcp-react}/index.html | 0 examples/{mcp => mcp-react}/package.json | 2 +- examples/{mcp => mcp-react}/server.ts | 0 examples/{mcp => mcp-react}/src/catalog.ts | 0 examples/{mcp => mcp-react}/src/globals.css | 0 examples/{mcp => mcp-react}/src/main.tsx | 0 .../{mcp => mcp-react}/src/mcp-app-view.tsx | 0 examples/{mcp => mcp-react}/tsconfig.json | 0 examples/{mcp => mcp-react}/vite.config.ts | 0 examples/mcp-svelte/package.json | 2 + examples/mcp-svelte/src/App.svelte | 4 +- .../mcp-svelte/src/components/Badge.svelte | 14 +-- .../mcp-svelte/src/components/Button.svelte | 33 ++---- .../mcp-svelte/src/components/Card.svelte | 22 +--- .../mcp-svelte/src/components/Stack.svelte | 22 ++-- .../mcp-svelte/src/components/Text.svelte | 25 ++-- examples/mcp-svelte/src/globals.css | 98 ++++++++++++++++ examples/mcp-svelte/src/main.ts | 8 ++ examples/mcp-vue/package.json | 2 + examples/mcp-vue/src/App.vue | 12 +- examples/mcp-vue/src/globals.css | 98 ++++++++++++++++ examples/mcp-vue/src/main.ts | 9 ++ examples/mcp-vue/src/registry.ts | 110 +++++++----------- examples/mcp-vue/vite.config.ts | 3 +- pnpm-lock.yaml | 18 ++- 32 files changed, 334 insertions(+), 161 deletions(-) rename examples/{mcp => mcp-react}/CHANGELOG.md (100%) rename examples/{mcp => mcp-react}/README.md (88%) rename examples/{mcp => mcp-react}/index.html (100%) rename examples/{mcp => mcp-react}/package.json (96%) rename examples/{mcp => mcp-react}/server.ts (100%) rename examples/{mcp => mcp-react}/src/catalog.ts (100%) rename examples/{mcp => mcp-react}/src/globals.css (100%) rename examples/{mcp => mcp-react}/src/main.tsx (100%) rename examples/{mcp => mcp-react}/src/mcp-app-view.tsx (100%) rename examples/{mcp => mcp-react}/tsconfig.json (100%) rename examples/{mcp => mcp-react}/vite.config.ts (100%) create mode 100644 examples/mcp-svelte/src/globals.css create mode 100644 examples/mcp-vue/src/globals.css diff --git a/.cursor/mcp.json b/.cursor/mcp.json index 902d4562..87da2a23 100644 --- a/.cursor/mcp.json +++ b/.cursor/mcp.json @@ -2,7 +2,7 @@ "mcpServers": { "json-render-react": { "command": "npx", - "args": ["tsx", "examples/mcp/server.ts", "--stdio"] + "args": ["tsx", "examples/mcp-react/server.ts", "--stdio"] }, "json-render-vue": { "command": "npx", diff --git a/.gitignore b/.gitignore index cdd528c4..32979a7f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ node_modules .pnp .pnp.js +.pnpm-store/ # Local env files .env* diff --git a/.vscode/mcp.json b/.vscode/mcp.json index b2876a02..98831672 100644 --- a/.vscode/mcp.json +++ b/.vscode/mcp.json @@ -3,7 +3,7 @@ "json-render": { "type": "stdio", "command": "npx", - "args": ["tsx", "examples/mcp/server.ts", "--stdio"] + "args": ["tsx", "examples/mcp-react/server.ts", "--stdio"] } } } \ No newline at end of file diff --git a/apps/web/app/(main)/docs/api/mcp/page.mdx b/apps/web/app/(main)/docs/api/mcp/page.mdx index a6949c2d..d9cfecc5 100644 --- a/apps/web/app/(main)/docs/api/mcp/page.mdx +++ b/apps/web/app/(main)/docs/api/mcp/page.mdx @@ -24,7 +24,7 @@ npm install @json-render/vue vue npm install @json-render/svelte svelte ``` -See the [MCP example](https://github.com/vercel-labs/json-render/tree/main/examples/mcp) for a full working example. +See the [MCP React example](https://github.com/vercel-labs/json-render/tree/main/examples/mcp-react) for a full working example. ## Overview diff --git a/apps/web/lib/docs-navigation.ts b/apps/web/lib/docs-navigation.ts index c5d77395..f15f6ead 100644 --- a/apps/web/lib/docs-navigation.ts +++ b/apps/web/lib/docs-navigation.ts @@ -98,7 +98,7 @@ export const docsNavigation: NavSection[] = [ }, { title: "MCP App", - href: "https://github.com/vercel-labs/json-render/tree/main/examples/mcp", + href: "https://github.com/vercel-labs/json-render/tree/main/examples/mcp-react", external: true, }, ], diff --git a/examples/mcp/CHANGELOG.md b/examples/mcp-react/CHANGELOG.md similarity index 100% rename from examples/mcp/CHANGELOG.md rename to examples/mcp-react/CHANGELOG.md diff --git a/examples/mcp/README.md b/examples/mcp-react/README.md similarity index 88% rename from examples/mcp/README.md rename to examples/mcp-react/README.md index 8f637362..4cb8d1c1 100644 --- a/examples/mcp/README.md +++ b/examples/mcp-react/README.md @@ -20,7 +20,7 @@ Add to `.cursor/mcp.json`: "mcpServers": { "json-render": { "command": "npx", - "args": ["tsx", "examples/mcp/server.ts", "--stdio"] + "args": ["tsx", "examples/mcp-react/server.ts", "--stdio"] } } } @@ -35,7 +35,7 @@ Add to `~/Library/Application Support/Claude/claude_desktop_config.json`: "mcpServers": { "json-render": { "command": "npx", - "args": ["tsx", "/absolute/path/to/examples/mcp/server.ts", "--stdio"] + "args": ["tsx", "/absolute/path/to/examples/mcp-react/server.ts", "--stdio"] } } } diff --git a/examples/mcp/index.html b/examples/mcp-react/index.html similarity index 100% rename from examples/mcp/index.html rename to examples/mcp-react/index.html diff --git a/examples/mcp/package.json b/examples/mcp-react/package.json similarity index 96% rename from examples/mcp/package.json rename to examples/mcp-react/package.json index 310100d4..adace60f 100644 --- a/examples/mcp/package.json +++ b/examples/mcp-react/package.json @@ -1,5 +1,5 @@ { - "name": "example-mcp", + "name": "example-mcp-react", "version": "0.1.1", "type": "module", "private": true, diff --git a/examples/mcp/server.ts b/examples/mcp-react/server.ts similarity index 100% rename from examples/mcp/server.ts rename to examples/mcp-react/server.ts diff --git a/examples/mcp/src/catalog.ts b/examples/mcp-react/src/catalog.ts similarity index 100% rename from examples/mcp/src/catalog.ts rename to examples/mcp-react/src/catalog.ts diff --git a/examples/mcp/src/globals.css b/examples/mcp-react/src/globals.css similarity index 100% rename from examples/mcp/src/globals.css rename to examples/mcp-react/src/globals.css diff --git a/examples/mcp/src/main.tsx b/examples/mcp-react/src/main.tsx similarity index 100% rename from examples/mcp/src/main.tsx rename to examples/mcp-react/src/main.tsx diff --git a/examples/mcp/src/mcp-app-view.tsx b/examples/mcp-react/src/mcp-app-view.tsx similarity index 100% rename from examples/mcp/src/mcp-app-view.tsx rename to examples/mcp-react/src/mcp-app-view.tsx diff --git a/examples/mcp/tsconfig.json b/examples/mcp-react/tsconfig.json similarity index 100% rename from examples/mcp/tsconfig.json rename to examples/mcp-react/tsconfig.json diff --git a/examples/mcp/vite.config.ts b/examples/mcp-react/vite.config.ts similarity index 100% rename from examples/mcp/vite.config.ts rename to examples/mcp-react/vite.config.ts diff --git a/examples/mcp-svelte/package.json b/examples/mcp-svelte/package.json index ded2d865..5296fa82 100644 --- a/examples/mcp-svelte/package.json +++ b/examples/mcp-svelte/package.json @@ -18,6 +18,8 @@ }, "devDependencies": { "@sveltejs/vite-plugin-svelte": "^6.2.4", + "@tailwindcss/vite": "^4.2.1", + "tailwindcss": "^4.2.1", "tsx": "^4.21.0", "typescript": "^5.9.3", "vite": "^7.3.1", diff --git a/examples/mcp-svelte/src/App.svelte b/examples/mcp-svelte/src/App.svelte index 9e468655..f918d30d 100644 --- a/examples/mcp-svelte/src/App.svelte +++ b/examples/mcp-svelte/src/App.svelte @@ -12,11 +12,11 @@ {#if $error} -
+
{$error.message}
{:else if !$spec} -
+
{$connecting ? "Connecting to host..." : "Waiting for UI spec..."}
{:else} diff --git a/examples/mcp-svelte/src/components/Badge.svelte b/examples/mcp-svelte/src/components/Badge.svelte index a1169973..b3a28ee5 100644 --- a/examples/mcp-svelte/src/components/Badge.svelte +++ b/examples/mcp-svelte/src/components/Badge.svelte @@ -10,15 +10,9 @@ + class="inline-flex items-center rounded-full px-3 py-1 text-xs font-medium bg-secondary text-secondary-foreground border border-border" + style:background-color={props.color ? `${props.color}20` : undefined} + style:color={props.color ?? undefined} + style:border-color={props.color ? `${props.color}40` : undefined}> {props.label} diff --git a/examples/mcp-svelte/src/components/Button.svelte b/examples/mcp-svelte/src/components/Button.svelte index 4a9dd222..492f0068 100644 --- a/examples/mcp-svelte/src/components/Button.svelte +++ b/examples/mcp-svelte/src/components/Button.svelte @@ -9,35 +9,18 @@ let { props, emit }: Props = $props(); - let backgroundColor = $derived( - props.variant === "danger" - ? "#fee2e2" - : props.variant === "secondary" - ? "#f3f4f6" - : "#3b82f6", - ); - let textColor = $derived( - props.variant === "danger" - ? "#dc2626" - : props.variant === "secondary" - ? "#374151" - : "white", - ); + const variantClasses: Record = { + primary: "bg-primary text-primary-foreground hover:bg-primary/90", + secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80", + danger: "bg-destructive/10 text-destructive hover:bg-destructive/20 border border-destructive/20", + }; + + let variant = $derived(props.variant ?? "primary"); diff --git a/examples/mcp-svelte/src/components/Card.svelte b/examples/mcp-svelte/src/components/Card.svelte index 30d5de46..edd949f7 100644 --- a/examples/mcp-svelte/src/components/Card.svelte +++ b/examples/mcp-svelte/src/components/Card.svelte @@ -12,30 +12,14 @@ let { props, children }: Props = $props(); -
+
{#if props.title} -

+

{props.title}

{/if} {#if props.subtitle} -

+

{props.subtitle}

{/if} diff --git a/examples/mcp-svelte/src/components/Stack.svelte b/examples/mcp-svelte/src/components/Stack.svelte index ffe8fd7c..05049226 100644 --- a/examples/mcp-svelte/src/components/Stack.svelte +++ b/examples/mcp-svelte/src/components/Stack.svelte @@ -16,19 +16,17 @@
+ ? "center" + : "stretch"}> {#if children} {@render children()} {/if} diff --git a/examples/mcp-svelte/src/components/Text.svelte b/examples/mcp-svelte/src/components/Text.svelte index 53516d35..06c72b19 100644 --- a/examples/mcp-svelte/src/components/Text.svelte +++ b/examples/mcp-svelte/src/components/Text.svelte @@ -10,25 +10,22 @@ let { props }: Props = $props(); - const sizeMap: Record = { - sm: "12px", - md: "14px", - lg: "16px", - xl: "24px", + const sizeClasses: Record = { + sm: "text-xs", + md: "text-sm", + lg: "text-base", + xl: "text-2xl", }; - const weightMap: Record = { - normal: "400", - medium: "500", - bold: "700", + const weightClasses: Record = { + normal: "font-normal", + medium: "font-medium", + bold: "font-bold", }; + class="{sizeClasses[props.size ?? 'md'] ?? 'text-sm'} {weightClasses[props.weight ?? 'normal'] ?? 'font-normal'} text-foreground" + style:color={props.color ?? undefined}> {String(props.content ?? "")} diff --git a/examples/mcp-svelte/src/globals.css b/examples/mcp-svelte/src/globals.css new file mode 100644 index 00000000..1162f970 --- /dev/null +++ b/examples/mcp-svelte/src/globals.css @@ -0,0 +1,98 @@ +@import "tailwindcss"; + +@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-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); + --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))); + --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, #app { + 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; +} diff --git a/examples/mcp-svelte/src/main.ts b/examples/mcp-svelte/src/main.ts index 99fc6a4a..4b13ec06 100644 --- a/examples/mcp-svelte/src/main.ts +++ b/examples/mcp-svelte/src/main.ts @@ -1,4 +1,12 @@ +import "./globals.css"; import { mount } from "svelte"; import App from "./App.svelte"; +const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches; +document.documentElement.setAttribute( + "data-theme", + prefersDark ? "dark" : "light", +); +document.documentElement.style.colorScheme = prefersDark ? "dark" : "light"; + mount(App, { target: document.getElementById("app")! }); diff --git a/examples/mcp-vue/package.json b/examples/mcp-vue/package.json index 7b2fb14b..76298814 100644 --- a/examples/mcp-vue/package.json +++ b/examples/mcp-vue/package.json @@ -18,7 +18,9 @@ "zod": "^4.3.6" }, "devDependencies": { + "@tailwindcss/vite": "^4.2.1", "@vitejs/plugin-vue": "^6.0.4", + "tailwindcss": "^4.2.1", "tsx": "^4.21.0", "typescript": "^5.9.3", "vite": "^7.3.1", diff --git a/examples/mcp-vue/src/App.vue b/examples/mcp-vue/src/App.vue index ec347078..363023bc 100644 --- a/examples/mcp-vue/src/App.vue +++ b/examples/mcp-vue/src/App.vue @@ -6,17 +6,23 @@ import { components } from "./registry"; const { registry } = defineRegistry(catalog, { components }); -const { spec, loading, connected, connecting, error } = useJsonRenderApp({ +const { spec, loading, connecting, error } = useJsonRenderApp({ name: "json-render-mcp-vue", version: "1.0.0", });