mountly-mcp 3.1.0 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +54 -29
- package/dist/bridge/iframe-entry.js +24 -16
- package/dist/bridge/index.d.ts +5 -5
- package/dist/bridge/index.js +2 -2
- package/dist/build/index.d.ts +3 -3
- package/dist/build/index.js +2 -2
- package/dist/{chunk-NLQDVNZP.js → chunk-AEVQKFXX.js} +12 -8
- package/dist/chunk-AEVQKFXX.js.map +1 -0
- package/dist/{chunk-SNWNF2XI.js → chunk-JVQZGKBL.js} +13 -13
- package/dist/chunk-JVQZGKBL.js.map +1 -0
- package/dist/chunk-KWVGW5Q7.js +582 -0
- package/dist/chunk-KWVGW5Q7.js.map +1 -0
- package/dist/{chunk-DQLQOMDM.js → chunk-M4WFJR3C.js} +2 -2
- package/dist/{chunk-DQLQOMDM.js.map → chunk-M4WFJR3C.js.map} +1 -1
- package/dist/{chunk-4WAKOYMK.js → chunk-MTJDRWGD.js} +2 -2
- package/dist/chunk-MTJDRWGD.js.map +1 -0
- package/dist/chunk-SADNMGP6.js +16 -0
- package/dist/chunk-SADNMGP6.js.map +1 -0
- package/dist/cli.js +21 -10
- package/dist/cli.js.map +1 -1
- package/dist/create.d.ts +10 -0
- package/dist/create.js +107 -0
- package/dist/create.js.map +1 -0
- package/dist/dev/host-entry.js +2 -2
- package/dist/dev/index.d.ts +1 -1
- package/dist/dev/index.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +10 -2
- package/dist/json-render/{widget.d.ts → generative-view.d.ts} +9 -11
- package/dist/json-render/index.d.ts +1 -1
- package/dist/json-render/index.js +9 -8
- package/dist/json-render/index.js.map +1 -1
- package/dist/publish.d.ts +10 -0
- package/dist/react/context.d.ts +1 -1
- package/dist/react/create-view.d.ts +8 -0
- package/dist/react/hooks.d.ts +1 -1
- package/dist/react/index.d.ts +1 -1
- package/dist/react/index.js +6 -5
- package/dist/schema.d.ts +2 -2
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js.map +1 -1
- package/dist/svelte/index.d.ts +10 -16
- package/dist/svelte/index.js +7 -3
- package/dist/svelte/index.js.map +1 -1
- package/dist/testing/index.d.ts +1 -1
- package/dist/testing/index.js +1 -1
- package/dist/testing/index.js.map +1 -1
- package/dist/types.d.ts +14 -4
- package/dist/vite/index.d.ts +6 -6
- package/dist/vite/index.js +7 -7
- package/dist/vite/index.js.map +1 -1
- package/dist/vue/index.d.ts +12 -17
- package/dist/vue/index.js +10 -6
- package/dist/vue/index.js.map +1 -1
- package/package.json +7 -11
- package/templates/react/.gitignore.tmpl +4 -0
- package/templates/react/README.md.tmpl +15 -0
- package/templates/react/mcp.fixtures.json.tmpl +4 -0
- package/templates/react/package.json.tmpl +27 -0
- package/templates/react/server.mjs.tmpl +70 -0
- package/templates/react/src/view.tsx.tmpl +160 -0
- package/templates/react/tsconfig.json.tmpl +15 -0
- package/templates/react/vite.config.ts.tmpl +22 -0
- package/templates/svelte/.gitignore.tmpl +4 -0
- package/templates/svelte/README.md.tmpl +14 -0
- package/templates/svelte/mcp.fixtures.json.tmpl +4 -0
- package/templates/svelte/package.json.tmpl +26 -0
- package/templates/svelte/server.mjs.tmpl +70 -0
- package/templates/svelte/src/Dashboard.svelte.tmpl +158 -0
- package/templates/svelte/src/view.ts.tmpl +14 -0
- package/templates/svelte/tsconfig.json.tmpl +14 -0
- package/templates/svelte/vite.config.ts.tmpl +22 -0
- package/templates/vue/.gitignore.tmpl +4 -0
- package/templates/vue/README.md.tmpl +14 -0
- package/templates/vue/mcp.fixtures.json.tmpl +4 -0
- package/templates/vue/package.json.tmpl +26 -0
- package/templates/vue/server.mjs.tmpl +70 -0
- package/templates/vue/src/Dashboard.vue.tmpl +159 -0
- package/templates/vue/src/view.ts.tmpl +14 -0
- package/templates/vue/tsconfig.json.tmpl +15 -0
- package/templates/vue/vite.config.ts.tmpl +22 -0
- package/dist/chunk-4WAKOYMK.js.map +0 -1
- package/dist/chunk-KRAS266U.js +0 -260
- package/dist/chunk-KRAS266U.js.map +0 -1
- package/dist/chunk-NLQDVNZP.js.map +0 -1
- package/dist/chunk-SNWNF2XI.js.map +0 -1
- package/dist/react/create-widget.d.ts +0 -11
package/dist/vite/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/vite/index.ts"],"sourcesContent":["import { rm } from \"node:fs/promises\";\nimport { basename, isAbsolute, join, resolve } from \"node:path\";\nimport type { BuildEnvironment, Plugin, UserConfig } from \"vite\";\nimport { MCP_APP_MANIFEST_FILE, writeMcpAppManifest } from \"../artifact/index.js\";\nimport type { McpAppArtifact } from \"../artifact/index.js\";\nimport { buildMcpResourceFromSource } from \"../build/index.js\";\nimport type { BuildSelfContainedOptions } from \"../build/index.js\";\n\nexport interface MountlyMcpWidgetOptions extends Omit<\n BuildSelfContainedOptions,\n \"entry\" | \"output\" | \"cssEntry\"\n> {\n entry: string;\n output?: string;\n cleanIntermediate?: boolean;\n}\n\nexport interface MountlyMcpWidgetsOptions {\n apps: ReadonlyArray<MountlyMcpWidgetOptions>;\n /** Defaults to `<outDir>/mountly-mcp.manifest.json`; false disables it. */\n manifest?: string | false;\n}\n\nexport type MountlyMcpViteOptions = MountlyMcpWidgetOptions | MountlyMcpWidgetsOptions;\n\nexport interface MountlyMcpViteApi {\n apps: ReadonlyArray<MountlyMcpWidgetOptions>;\n manifest?: string | false;\n}\n\nfunction isCollection(options: MountlyMcpViteOptions): options is MountlyMcpWidgetsOptions {\n return \"apps\" in options;\n}\n\nfunction safeBuildName(name: string, index: number): string {\n const safe = name.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n return `mountly_mcp_${index}_${safe || \"app\"}`;\n}\n\nfunction outputPath(root: string, outDir: string, app: MountlyMcpWidgetOptions): string {\n if (!app.output) return join(outDir, `${basename(app.name)}.html`);\n return isAbsolute(app.output) ? app.output : resolve(root, app.output);\n}\n\n/**\n * Build one or many independent MCP Apps Views. Each View is a Vite build\n * environment, so frameworks and CSS remain isolated while callers configure\n * the collection once.\n */\nexport function mountlyMcpWidget(options: MountlyMcpViteOptions): Plugin {\n const configuredApps = isCollection(options) ? [...options.apps] : [options];\n const selectedForDev = process.env.MOUNTLY_MCP_SELECTED_APP;\n const apps = selectedForDev\n ? configuredApps.filter((app) => app.name === selectedForDev)\n : configuredApps;\n const manifestOption = isCollection(options) ? options.manifest : undefined;\n if (apps.length === 0) throw new Error(\"mountly-mcp: apps must contain at least one View\");\n\n const names = new Set<string>();\n const uris = new Set<string>();\n for (const app of apps) {\n if (!app.uri.startsWith(\"ui://\")) {\n throw new Error(\n `mountly-mcp: UI resource uri must use the 'ui://' scheme (received '${app.uri}')`,\n );\n }\n if (names.has(app.name)) throw new Error(`mountly-mcp: duplicate app name '${app.name}'`);\n if (uris.has(app.uri)) throw new Error(`mountly-mcp: duplicate app uri '${app.uri}'`);\n names.add(app.name);\n uris.add(app.uri);\n }\n\n const environments = apps.map((app, index) => ({\n app,\n name: safeBuildName(app.name, index),\n jsFile: `widget-${index}-${basename(app.name)}.js`,\n cssFile: `widget-${index}-${basename(app.name)}.css`,\n }));\n const byEnvironment = new Map(environments.map((environment) => [environment.name, environment]));\n const emitted = new Map<\n string,\n { js?: string; css?: string; jsFile?: string; cssFile?: string; names: string[] }\n >();\n const artifacts = new Map<string, McpAppArtifact>();\n let root = process.cwd();\n let outDir = resolve(root, \"dist\");\n let manifestWritten = false;\n\n function currentEnvironment(context: unknown) {\n const name = (context as { environment?: { name?: string } }).environment?.name;\n return (\n (name ? byEnvironment.get(name) : undefined) ??\n (environments.length === 1 ? environments[0] : undefined)\n );\n }\n\n const plugin: Plugin = {\n name: \"mountly-mcp-widget\",\n api: {\n // The CLI consumes this documented plugin interface instead of scraping\n // Vite's resolved build settings.\n mountlyMcp: { apps: configuredApps, manifest: manifestOption } satisfies MountlyMcpViteApi,\n },\n // After Vite core plugins: css-post writes the stylesheet in its own\n // closeBundle, and this plugin has to see the file to inline it.\n enforce: \"post\",\n // Only meaningful during `vite build`; a dev server has no bundle to emit.\n apply: \"build\",\n\n config(_config, env): UserConfig {\n const first = environments[0];\n if (!first) throw new Error(\"mountly-mcp: configure at least one app\");\n return {\n // Vite's lib mode leaves `process.env.NODE_ENV` for a consuming bundler\n // to replace. There isn't one — the bundle is inlined into HTML and run\n // in a sandboxed iframe, where `process` is undefined and React and Vue\n // throw on their first environment check.\n define: { \"process.env.NODE_ENV\": JSON.stringify(env.mode) },\n // Legacy `vite build` builds one environment. Preserve that zero-config\n // path for single-View projects; multi-View projects use\n // `mountly-mcp build`, which drives the environments below.\n build: {\n lib: {\n entry: first.app.entry,\n // IIFE so the bundle inlines into a single <script> with no import\n // graph left to resolve inside the sandboxed iframe.\n formats: [\"iife\"] as const,\n name: \"__mountlyMcpEntry0\",\n fileName: () => first.jsFile,\n // Named explicitly: otherwise a lib build derives the stylesheet\n // name from package.json and fails outright when there isn't one.\n cssFileName: first.cssFile.replace(/\\.css$/, \"\"),\n },\n // One stylesheet to inline; per-chunk CSS would strand imports.\n cssCodeSplit: false,\n },\n builder: {\n sharedPlugins: true,\n async buildApp(builder) {\n for (const environment of environments) {\n const target = builder.environments[environment.name] as BuildEnvironment | undefined;\n if (!target)\n throw new Error(`mountly-mcp: missing Vite environment '${environment.name}'`);\n await builder.build(target);\n }\n },\n },\n environments: Object.fromEntries(\n environments.map((environment, index) => [\n environment.name,\n {\n // A custom environment defaults to a server consumer, which\n // externalizes node_modules — React and Vue would become bare\n // globals and the view would die on `vue is not defined`. A\n // `ui://` resource has no import graph at runtime, so everything\n // must be bundled in.\n consumer: \"client\" as const,\n resolve: { noExternal: true as const },\n build: {\n emptyOutDir: index === 0,\n lib: {\n entry: environment.app.entry,\n formats: [\"iife\"] as const,\n name: `__mountlyMcpEntry${index}`,\n fileName: () => environment.jsFile,\n cssFileName: environment.cssFile.replace(/\\.css$/, \"\"),\n },\n cssCodeSplit: false,\n },\n },\n ]),\n ),\n };\n },\n\n configResolved(config) {\n root = config.root;\n outDir = resolve(config.root, config.build.outDir);\n },\n\n buildStart() {\n const name = (this as { environment?: { name?: string } }).environment?.name;\n if (apps.length > 1 && !byEnvironment.has(String(name))) {\n this.error(\n \"mountly-mcp: multi-View builds require 'mountly-mcp build' (or 'vite build --app') so Vite builds every environment\",\n );\n }\n },\n\n generateBundle: {\n order: \"post\",\n handler(_outputOptions, bundle) {\n const environment = currentEnvironment(this);\n if (!environment) return;\n const chunk =\n bundle[environment.jsFile] ??\n Object.values(bundle).find((item) => item.type === \"chunk\" && item.isEntry);\n const cssAsset =\n bundle[environment.cssFile] ??\n Object.values(bundle).find(\n (item) => item.type === \"asset\" && item.fileName.endsWith(\".css\"),\n );\n emitted.set(environment.name, {\n js: chunk && \"code\" in chunk ? chunk.code : undefined,\n css: cssAsset && \"source\" in cssAsset ? cssAsset.source.toString() : undefined,\n jsFile: chunk?.fileName,\n cssFile: cssAsset?.fileName,\n names: Object.keys(bundle),\n });\n },\n },\n\n async closeBundle() {\n const environment = currentEnvironment(this);\n if (!environment || artifacts.has(environment.name)) return;\n const source = emitted.get(environment.name);\n if (source?.js === undefined) {\n this.error(\n `mountly-mcp: the build emitted no ${environment.jsFile} for '${environment.app.entry}'. Emitted: ${source?.names.join(\", \") || \"nothing\"}`,\n );\n return;\n }\n const {\n entry: _entry,\n output: _output,\n cleanIntermediate = true,\n ...resourceOptions\n } = environment.app;\n const result = await buildMcpResourceFromSource({\n ...resourceOptions,\n js: source.js,\n css: source.css,\n output: outputPath(root, outDir, environment.app),\n });\n artifacts.set(environment.name, result.artifact);\n if (cleanIntermediate) {\n if (source.jsFile) await rm(join(outDir, source.jsFile), { force: true });\n if (source.cssFile) await rm(join(outDir, source.cssFile), { force: true });\n }\n this.info?.(`mountly-mcp: ${environment.app.uri} → ${result.htmlPath}`);\n\n if (!manifestWritten && artifacts.size === environments.length && manifestOption !== false) {\n manifestWritten = true;\n const path = manifestOption\n ? isAbsolute(manifestOption)\n ? manifestOption\n : resolve(root, manifestOption)\n : join(outDir, MCP_APP_MANIFEST_FILE);\n await writeMcpAppManifest(\n path,\n environments.map((item) => artifacts.get(item.name) as McpAppArtifact),\n );\n this.info?.(`mountly-mcp: manifest → ${path}`);\n }\n },\n };\n\n return plugin;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/vite/index.ts"],"sourcesContent":["import { rm } from \"node:fs/promises\";\nimport { basename, isAbsolute, join, resolve } from \"node:path\";\nimport type { BuildEnvironment, Plugin, UserConfig } from \"vite\";\nimport { MCP_APP_MANIFEST_FILE, writeMcpAppManifest } from \"../artifact/index.js\";\nimport type { McpAppArtifact } from \"../artifact/index.js\";\nimport { buildMcpResourceFromSource } from \"../build/index.js\";\nimport type { BuildSelfContainedOptions } from \"../build/index.js\";\n\nexport interface MountlyMcpViewOptions extends Omit<\n BuildSelfContainedOptions,\n \"entry\" | \"output\" | \"cssEntry\"\n> {\n entry: string;\n output?: string;\n cleanIntermediate?: boolean;\n}\n\nexport interface MountlyMcpViewsOptions {\n apps: ReadonlyArray<MountlyMcpViewOptions>;\n /** Defaults to `<outDir>/mountly-mcp.manifest.json`; false disables it. */\n manifest?: string | false;\n}\n\nexport type MountlyMcpViteOptions = MountlyMcpViewOptions | MountlyMcpViewsOptions;\n\nexport interface MountlyMcpViteApi {\n apps: ReadonlyArray<MountlyMcpViewOptions>;\n manifest?: string | false;\n}\n\nfunction isCollection(options: MountlyMcpViteOptions): options is MountlyMcpViewsOptions {\n return \"apps\" in options;\n}\n\nfunction safeBuildName(name: string, index: number): string {\n const safe = name.replace(/[^a-zA-Z0-9_-]/g, \"_\");\n return `mountly_mcp_${index}_${safe || \"app\"}`;\n}\n\nfunction outputPath(root: string, outDir: string, app: MountlyMcpViewOptions): string {\n if (!app.output) return join(outDir, `${basename(app.name)}.html`);\n return isAbsolute(app.output) ? app.output : resolve(root, app.output);\n}\n\n/**\n * Build one or many independent MCP Apps Views. Each View is a Vite build\n * environment, so frameworks and CSS remain isolated while callers configure\n * the collection once.\n */\nexport function mountlyMcpViews(options: MountlyMcpViteOptions): Plugin {\n const configuredApps = isCollection(options) ? [...options.apps] : [options];\n const selectedForDev = process.env.MOUNTLY_MCP_SELECTED_APP;\n const apps = selectedForDev\n ? configuredApps.filter((app) => app.name === selectedForDev)\n : configuredApps;\n const manifestOption = isCollection(options) ? options.manifest : undefined;\n if (apps.length === 0) throw new Error(\"mountly-mcp: apps must contain at least one View\");\n\n const names = new Set<string>();\n const uris = new Set<string>();\n for (const app of apps) {\n if (!app.uri.startsWith(\"ui://\")) {\n throw new Error(\n `mountly-mcp: UI resource uri must use the 'ui://' scheme (received '${app.uri}')`,\n );\n }\n if (names.has(app.name)) throw new Error(`mountly-mcp: duplicate app name '${app.name}'`);\n if (uris.has(app.uri)) throw new Error(`mountly-mcp: duplicate app uri '${app.uri}'`);\n names.add(app.name);\n uris.add(app.uri);\n }\n\n const environments = apps.map((app, index) => ({\n app,\n name: safeBuildName(app.name, index),\n jsFile: `view-${index}-${basename(app.name)}.js`,\n cssFile: `view-${index}-${basename(app.name)}.css`,\n }));\n const byEnvironment = new Map(environments.map((environment) => [environment.name, environment]));\n const emitted = new Map<\n string,\n { js?: string; css?: string; jsFile?: string; cssFile?: string; names: string[] }\n >();\n const artifacts = new Map<string, McpAppArtifact>();\n let root = process.cwd();\n let outDir = resolve(root, \"dist\");\n let manifestWritten = false;\n\n function currentEnvironment(context: unknown) {\n const name = (context as { environment?: { name?: string } }).environment?.name;\n return (\n (name ? byEnvironment.get(name) : undefined) ??\n (environments.length === 1 ? environments[0] : undefined)\n );\n }\n\n const plugin: Plugin = {\n name: \"mountly-mcp-views\",\n api: {\n // The CLI consumes this documented plugin interface instead of scraping\n // Vite's resolved build settings.\n mountlyMcp: { apps: configuredApps, manifest: manifestOption } satisfies MountlyMcpViteApi,\n },\n // After Vite core plugins: css-post writes the stylesheet in its own\n // closeBundle, and this plugin has to see the file to inline it.\n enforce: \"post\",\n // Only meaningful during `vite build`; a dev server has no bundle to emit.\n apply: \"build\",\n\n config(_config, env): UserConfig {\n const first = environments[0];\n if (!first) throw new Error(\"mountly-mcp: configure at least one app\");\n return {\n // Vite's lib mode leaves `process.env.NODE_ENV` for a consuming bundler\n // to replace. There isn't one — the bundle is inlined into HTML and run\n // in a sandboxed iframe, where `process` is undefined and React and Vue\n // throw on their first environment check.\n define: { \"process.env.NODE_ENV\": JSON.stringify(env.mode) },\n // Legacy `vite build` builds one environment. Preserve that zero-config\n // path for single-View projects; multi-View projects use\n // `mountly-mcp build`, which drives the environments below.\n build: {\n lib: {\n entry: first.app.entry,\n // IIFE so the bundle inlines into a single <script> with no import\n // graph left to resolve inside the sandboxed iframe.\n formats: [\"iife\"] as const,\n name: \"__mountlyMcpEntry0\",\n fileName: () => first.jsFile,\n // Named explicitly: otherwise a lib build derives the stylesheet\n // name from package.json and fails outright when there isn't one.\n cssFileName: first.cssFile.replace(/\\.css$/, \"\"),\n },\n // One stylesheet to inline; per-chunk CSS would strand imports.\n cssCodeSplit: false,\n },\n builder: {\n sharedPlugins: true,\n async buildApp(builder) {\n for (const environment of environments) {\n const target = builder.environments[environment.name] as BuildEnvironment | undefined;\n if (!target)\n throw new Error(`mountly-mcp: missing Vite environment '${environment.name}'`);\n await builder.build(target);\n }\n },\n },\n environments: Object.fromEntries(\n environments.map((environment, index) => [\n environment.name,\n {\n // A custom environment defaults to a server consumer, which\n // externalizes node_modules — React and Vue would become bare\n // globals and the view would die on `vue is not defined`. A\n // `ui://` resource has no import graph at runtime, so everything\n // must be bundled in.\n consumer: \"client\" as const,\n resolve: { noExternal: true as const },\n build: {\n emptyOutDir: index === 0,\n lib: {\n entry: environment.app.entry,\n formats: [\"iife\"] as const,\n name: `__mountlyMcpEntry${index}`,\n fileName: () => environment.jsFile,\n cssFileName: environment.cssFile.replace(/\\.css$/, \"\"),\n },\n cssCodeSplit: false,\n },\n },\n ]),\n ),\n };\n },\n\n configResolved(config) {\n root = config.root;\n outDir = resolve(config.root, config.build.outDir);\n },\n\n buildStart() {\n const name = (this as { environment?: { name?: string } }).environment?.name;\n if (apps.length > 1 && !byEnvironment.has(String(name))) {\n this.error(\n \"mountly-mcp: multi-View builds require 'mountly-mcp build' (or 'vite build --app') so Vite builds every environment\",\n );\n }\n },\n\n generateBundle: {\n order: \"post\",\n handler(_outputOptions, bundle) {\n const environment = currentEnvironment(this);\n if (!environment) return;\n const chunk =\n bundle[environment.jsFile] ??\n Object.values(bundle).find((item) => item.type === \"chunk\" && item.isEntry);\n const cssAsset =\n bundle[environment.cssFile] ??\n Object.values(bundle).find(\n (item) => item.type === \"asset\" && item.fileName.endsWith(\".css\"),\n );\n emitted.set(environment.name, {\n js: chunk && \"code\" in chunk ? chunk.code : undefined,\n css: cssAsset && \"source\" in cssAsset ? cssAsset.source.toString() : undefined,\n jsFile: chunk?.fileName,\n cssFile: cssAsset?.fileName,\n names: Object.keys(bundle),\n });\n },\n },\n\n async closeBundle() {\n const environment = currentEnvironment(this);\n if (!environment || artifacts.has(environment.name)) return;\n const source = emitted.get(environment.name);\n if (source?.js === undefined) {\n this.error(\n `mountly-mcp: the build emitted no ${environment.jsFile} for '${environment.app.entry}'. Emitted: ${source?.names.join(\", \") || \"nothing\"}`,\n );\n return;\n }\n const {\n entry: _entry,\n output: _output,\n cleanIntermediate = true,\n ...resourceOptions\n } = environment.app;\n const result = await buildMcpResourceFromSource({\n ...resourceOptions,\n js: source.js,\n css: source.css,\n output: outputPath(root, outDir, environment.app),\n });\n artifacts.set(environment.name, result.artifact);\n if (cleanIntermediate) {\n if (source.jsFile) await rm(join(outDir, source.jsFile), { force: true });\n if (source.cssFile) await rm(join(outDir, source.cssFile), { force: true });\n }\n this.info?.(`mountly-mcp: ${environment.app.uri} → ${result.htmlPath}`);\n\n if (!manifestWritten && artifacts.size === environments.length && manifestOption !== false) {\n manifestWritten = true;\n const path = manifestOption\n ? isAbsolute(manifestOption)\n ? manifestOption\n : resolve(root, manifestOption)\n : join(outDir, MCP_APP_MANIFEST_FILE);\n await writeMcpAppManifest(\n path,\n environments.map((item) => artifacts.get(item.name) as McpAppArtifact),\n );\n this.info?.(`mountly-mcp: manifest → ${path}`);\n }\n },\n };\n\n return plugin;\n}\n"],"mappings":"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","names":[]}
|
package/dist/vue/index.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Vue entry point: turn a Vue component into an MCP App
|
|
2
|
+
* Vue entry point: turn a Vue component into an MCP App View.
|
|
3
3
|
*
|
|
4
4
|
* ```ts
|
|
5
|
-
* import {
|
|
5
|
+
* import { createMcpView } from "mountly-mcp/vue";
|
|
6
6
|
* import Dashboard from "./Dashboard.vue";
|
|
7
7
|
*
|
|
8
|
-
*
|
|
9
|
-
* (globalThis as { __mountlyMcpWidget__?: unknown }).__mountlyMcpWidget__ = widget;
|
|
8
|
+
* createMcpView(Dashboard);
|
|
10
9
|
* ```
|
|
11
10
|
*
|
|
12
11
|
* The component receives the bridge's props (`mcp`, `toolInput`, `toolResult`,
|
|
@@ -14,26 +13,22 @@
|
|
|
14
13
|
* below, which mirror `mountly-mcp/react`'s hooks.
|
|
15
14
|
*/
|
|
16
15
|
import { type Component, type ComputedRef, type InjectionKey } from "vue";
|
|
17
|
-
import type { AdapterOptions
|
|
16
|
+
import type { AdapterOptions } from "mountly/adapter";
|
|
18
17
|
import type { App, McpUiDisplayMode, McpUiHostContext } from "@modelcontextprotocol/ext-apps";
|
|
19
|
-
import type {
|
|
20
|
-
/** What {@link
|
|
18
|
+
import type { McpView, McpViewProps } from "../types.js";
|
|
19
|
+
/** What {@link createMcpView} provides to descendants. */
|
|
21
20
|
export interface McpVueContext {
|
|
22
21
|
app: App;
|
|
23
|
-
toolInput?:
|
|
24
|
-
toolInputPartial?:
|
|
25
|
-
toolResult?:
|
|
22
|
+
toolInput?: McpViewProps["toolInput"];
|
|
23
|
+
toolInputPartial?: McpViewProps["toolInputPartial"];
|
|
24
|
+
toolResult?: McpViewProps["toolResult"];
|
|
26
25
|
hostContext?: McpUiHostContext;
|
|
27
26
|
}
|
|
28
27
|
export declare const McpInjectionKey: InjectionKey<ComputedRef<McpVueContext>>;
|
|
29
|
-
/**
|
|
30
|
-
|
|
31
|
-
* bridge. The component's own props/attrs pass through unchanged; MCP bridge
|
|
32
|
-
* props are consumed by the wrapper and provided to the subtree.
|
|
33
|
-
*/
|
|
34
|
-
export declare function createMcpWidget<P extends object>(Component: Component<P>, options?: AdapterOptions): WidgetModule;
|
|
28
|
+
/** Wraps a Vue component as an MCP Apps View and publishes it for the bridge. */
|
|
29
|
+
export declare function createMcpView<P extends object>(Component: Component<P>, options?: AdapterOptions): McpView;
|
|
35
30
|
/** The view-side ext-apps App: call server tools, send messages, open links. */
|
|
36
|
-
export declare function
|
|
31
|
+
export declare function useMcpApp(): App;
|
|
37
32
|
/** Complete notification params; read the tool arguments from `.arguments`. */
|
|
38
33
|
export declare function useToolInput<T = unknown>(): ComputedRef<T | undefined>;
|
|
39
34
|
/**
|
package/dist/vue/index.js
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
publishMcpView
|
|
3
|
+
} from "../chunk-SADNMGP6.js";
|
|
4
|
+
|
|
1
5
|
// src/vue/index.ts
|
|
2
6
|
import {
|
|
3
7
|
computed,
|
|
@@ -8,7 +12,7 @@ import {
|
|
|
8
12
|
} from "vue";
|
|
9
13
|
import { createWidget } from "mountly-vue";
|
|
10
14
|
var McpInjectionKey = /* @__PURE__ */ Symbol("mountly-mcp");
|
|
11
|
-
function
|
|
15
|
+
function createMcpView(Component, options) {
|
|
12
16
|
const Wrapped = defineComponent({
|
|
13
17
|
name: "MountlyMcpProvider",
|
|
14
18
|
inheritAttrs: false,
|
|
@@ -31,18 +35,18 @@ function createMcpWidget(Component, options) {
|
|
|
31
35
|
return () => h(Component, attrs);
|
|
32
36
|
}
|
|
33
37
|
});
|
|
34
|
-
return createWidget(Wrapped, options);
|
|
38
|
+
return publishMcpView(createWidget(Wrapped, options));
|
|
35
39
|
}
|
|
36
40
|
function useEnsuredContext() {
|
|
37
41
|
const context = inject(McpInjectionKey, null);
|
|
38
42
|
if (!context) {
|
|
39
43
|
throw new Error(
|
|
40
|
-
"mountly-mcp/vue:
|
|
44
|
+
"mountly-mcp/vue: useMcpApp/useToolResult/etc must be used inside a component wrapped with createMcpView()."
|
|
41
45
|
);
|
|
42
46
|
}
|
|
43
47
|
return context;
|
|
44
48
|
}
|
|
45
|
-
function
|
|
49
|
+
function useMcpApp() {
|
|
46
50
|
return useEnsuredContext().value.app;
|
|
47
51
|
}
|
|
48
52
|
function useToolInput() {
|
|
@@ -77,10 +81,10 @@ function useRequestDisplayMode() {
|
|
|
77
81
|
}
|
|
78
82
|
export {
|
|
79
83
|
McpInjectionKey,
|
|
80
|
-
|
|
84
|
+
createMcpView,
|
|
81
85
|
useDisplayMode,
|
|
82
86
|
useHostContext,
|
|
83
|
-
|
|
87
|
+
useMcpApp,
|
|
84
88
|
useRequestDisplayMode,
|
|
85
89
|
useToolInput,
|
|
86
90
|
useToolInputPartial,
|
package/dist/vue/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/vue/index.ts"],"sourcesContent":["/**\n * Vue entry point: turn a Vue component into an MCP App
|
|
1
|
+
{"version":3,"sources":["../../src/vue/index.ts"],"sourcesContent":["/**\n * Vue entry point: turn a Vue component into an MCP App View.\n *\n * ```ts\n * import { createMcpView } from \"mountly-mcp/vue\";\n * import Dashboard from \"./Dashboard.vue\";\n *\n * createMcpView(Dashboard);\n * ```\n *\n * The component receives the bridge's props (`mcp`, `toolInput`, `toolResult`,\n * `hostContext`) and can also reach them from any depth with the composables\n * below, which mirror `mountly-mcp/react`'s hooks.\n */\nimport {\n computed,\n defineComponent,\n h,\n inject,\n provide,\n type Component,\n type ComputedRef,\n type InjectionKey,\n} from \"vue\";\nimport { createWidget } from \"mountly-vue\";\nimport type { AdapterOptions } from \"mountly/adapter\";\nimport type { App, McpUiDisplayMode, McpUiHostContext } from \"@modelcontextprotocol/ext-apps\";\nimport type { McpView, McpViewProps } from \"../types.js\";\nimport { publishMcpView } from \"../publish.js\";\n\n/** What {@link createMcpView} provides to descendants. */\nexport interface McpVueContext {\n app: App;\n toolInput?: McpViewProps[\"toolInput\"];\n toolInputPartial?: McpViewProps[\"toolInputPartial\"];\n toolResult?: McpViewProps[\"toolResult\"];\n hostContext?: McpUiHostContext;\n}\n\nexport const McpInjectionKey: InjectionKey<ComputedRef<McpVueContext>> = Symbol(\"mountly-mcp\");\n\n/** Wraps a Vue component as an MCP Apps View and publishes it for the bridge. */\nexport function createMcpView<P extends object>(\n Component: Component<P>,\n options?: AdapterOptions,\n): McpView {\n const Wrapped = defineComponent({\n name: \"MountlyMcpProvider\",\n inheritAttrs: false,\n props: {\n mcp: { type: Object, required: true },\n toolInput: { type: Object, default: undefined },\n toolInputPartial: { type: Object, default: undefined },\n toolResult: { type: Object, default: undefined },\n hostContext: { type: Object, default: undefined },\n },\n setup(props, { attrs }) {\n const context = computed<McpVueContext>(() => ({\n app: props.mcp as App,\n toolInput: props.toolInput as McpVueContext[\"toolInput\"],\n toolInputPartial: props.toolInputPartial as McpVueContext[\"toolInputPartial\"],\n toolResult: props.toolResult as McpVueContext[\"toolResult\"],\n hostContext: props.hostContext as McpUiHostContext | undefined,\n }));\n provide(McpInjectionKey, context);\n return () => h(Component as Component, attrs);\n },\n });\n return publishMcpView(createWidget(Wrapped, options));\n}\n\nfunction useEnsuredContext(): ComputedRef<McpVueContext> {\n const context = inject(McpInjectionKey, null);\n if (!context) {\n throw new Error(\n \"mountly-mcp/vue: useMcpApp/useToolResult/etc must be used inside a component wrapped with createMcpView().\",\n );\n }\n return context;\n}\n\n/** The view-side ext-apps App: call server tools, send messages, open links. */\nexport function useMcpApp(): App {\n return useEnsuredContext().value.app;\n}\n\n/** Complete notification params; read the tool arguments from `.arguments`. */\nexport function useToolInput<T = unknown>(): ComputedRef<T | undefined> {\n const context = useEnsuredContext();\n return computed(() => context.value.toolInput as T | undefined);\n}\n\n/**\n * Streaming tool arguments (`ui/notifications/tool-input-partial`), present only\n * when the bridge runs with `streamToolInput`. Best-effort recovered JSON —\n * render loading states from it, never critical operations.\n */\nexport function useToolInputPartial<T = unknown>(): ComputedRef<Partial<T> | undefined> {\n const context = useEnsuredContext();\n return computed(() => context.value.toolInputPartial as Partial<T> | undefined);\n}\n\n/** Complete MCP tool result; structured data is available at `.structuredContent`. */\nexport function useToolResult<T = unknown>(): ComputedRef<T | undefined> {\n const context = useEnsuredContext();\n return computed(() => context.value.toolResult as T | undefined);\n}\n\n/** Latest host context (theme, styles, displayMode, container dimensions). */\nexport function useHostContext(): ComputedRef<McpUiHostContext | undefined> {\n const context = useEnsuredContext();\n return computed(() => context.value.hostContext);\n}\n\n/** Current display mode reported by the host. */\nexport function useDisplayMode(): ComputedRef<McpUiDisplayMode | undefined> {\n const context = useEnsuredContext();\n return computed(() => context.value.hostContext?.displayMode);\n}\n\n/**\n * Request a display mode change, resolving to the mode the host actually\n * applied. Modes the host didn't advertise are never requested — the spec\n * requires views to check `availableDisplayModes` first.\n */\nexport function useRequestDisplayMode(): (\n mode: McpUiDisplayMode,\n) => Promise<McpUiDisplayMode | undefined> {\n const context = useEnsuredContext();\n return async (mode) => {\n const { app, hostContext } = context.value;\n const available = hostContext?.availableDisplayModes;\n if (available && !available.includes(mode)) return hostContext?.displayMode;\n const result = await app.requestDisplayMode({ mode });\n return result.mode;\n };\n}\n"],"mappings":";;;;;AAcA;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;AACP,SAAS,oBAAoB;AAetB,IAAM,kBAA4D,uBAAO,aAAa;AAGtF,SAAS,cACd,WACA,SACS;AACT,QAAM,UAAU,gBAAgB;AAAA,IAC9B,MAAM;AAAA,IACN,cAAc;AAAA,IACd,OAAO;AAAA,MACL,KAAK,EAAE,MAAM,QAAQ,UAAU,KAAK;AAAA,MACpC,WAAW,EAAE,MAAM,QAAQ,SAAS,OAAU;AAAA,MAC9C,kBAAkB,EAAE,MAAM,QAAQ,SAAS,OAAU;AAAA,MACrD,YAAY,EAAE,MAAM,QAAQ,SAAS,OAAU;AAAA,MAC/C,aAAa,EAAE,MAAM,QAAQ,SAAS,OAAU;AAAA,IAClD;AAAA,IACA,MAAM,OAAO,EAAE,MAAM,GAAG;AACtB,YAAM,UAAU,SAAwB,OAAO;AAAA,QAC7C,KAAK,MAAM;AAAA,QACX,WAAW,MAAM;AAAA,QACjB,kBAAkB,MAAM;AAAA,QACxB,YAAY,MAAM;AAAA,QAClB,aAAa,MAAM;AAAA,MACrB,EAAE;AACF,cAAQ,iBAAiB,OAAO;AAChC,aAAO,MAAM,EAAE,WAAwB,KAAK;AAAA,IAC9C;AAAA,EACF,CAAC;AACD,SAAO,eAAe,aAAa,SAAS,OAAO,CAAC;AACtD;AAEA,SAAS,oBAAgD;AACvD,QAAM,UAAU,OAAO,iBAAiB,IAAI;AAC5C,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,YAAiB;AAC/B,SAAO,kBAAkB,EAAE,MAAM;AACnC;AAGO,SAAS,eAAwD;AACtE,QAAM,UAAU,kBAAkB;AAClC,SAAO,SAAS,MAAM,QAAQ,MAAM,SAA0B;AAChE;AAOO,SAAS,sBAAwE;AACtF,QAAM,UAAU,kBAAkB;AAClC,SAAO,SAAS,MAAM,QAAQ,MAAM,gBAA0C;AAChF;AAGO,SAAS,gBAAyD;AACvE,QAAM,UAAU,kBAAkB;AAClC,SAAO,SAAS,MAAM,QAAQ,MAAM,UAA2B;AACjE;AAGO,SAAS,iBAA4D;AAC1E,QAAM,UAAU,kBAAkB;AAClC,SAAO,SAAS,MAAM,QAAQ,MAAM,WAAW;AACjD;AAGO,SAAS,iBAA4D;AAC1E,QAAM,UAAU,kBAAkB;AAClC,SAAO,SAAS,MAAM,QAAQ,MAAM,aAAa,WAAW;AAC9D;AAOO,SAAS,wBAE2B;AACzC,QAAM,UAAU,kBAAkB;AAClC,SAAO,OAAO,SAAS;AACrB,UAAM,EAAE,KAAK,YAAY,IAAI,QAAQ;AACrC,UAAM,YAAY,aAAa;AAC/B,QAAI,aAAa,CAAC,UAAU,SAAS,IAAI,EAAG,QAAO,aAAa;AAChE,UAAM,SAAS,MAAM,IAAI,mBAAmB,EAAE,KAAK,CAAC;AACpD,WAAO,OAAO;AAAA,EAChB;AACF;","names":[]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mountly-mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Build MCP Apps (SEP-1865) views from
|
|
3
|
+
"version": "4.0.0",
|
|
4
|
+
"description": "Build MCP Apps (SEP-1865) views from React, Vue or Svelte components — new or existing. One build emits the ui:// resource, its sidecar, and the MCP server wiring.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"chatgpt",
|
|
7
7
|
"claude",
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
"svelte",
|
|
15
15
|
"vite-plugin",
|
|
16
16
|
"vue",
|
|
17
|
-
"
|
|
17
|
+
"mcp-view"
|
|
18
18
|
],
|
|
19
19
|
"license": "Apache-2.0",
|
|
20
20
|
"repository": {
|
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
"mountly-mcp": "./dist/cli.js"
|
|
27
27
|
},
|
|
28
28
|
"files": [
|
|
29
|
-
"dist"
|
|
29
|
+
"dist",
|
|
30
|
+
"templates"
|
|
30
31
|
],
|
|
31
32
|
"type": "module",
|
|
32
33
|
"main": "./dist/index.js",
|
|
@@ -106,6 +107,8 @@
|
|
|
106
107
|
"dependencies": {
|
|
107
108
|
"@modelcontextprotocol/ext-apps": "^1.7.5",
|
|
108
109
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
110
|
+
"mountly": "^0.4.0",
|
|
111
|
+
"mountly-react": "^2.0.2",
|
|
109
112
|
"zod": "^4.4.3"
|
|
110
113
|
},
|
|
111
114
|
"devDependencies": {
|
|
@@ -117,14 +120,12 @@
|
|
|
117
120
|
"ai": "6.0.208",
|
|
118
121
|
"axe-core": "4.13.0",
|
|
119
122
|
"jsdom": "^30.0.1",
|
|
120
|
-
"mountly": "^0.4.0",
|
|
121
123
|
"react": "^19.2.8",
|
|
122
124
|
"react-dom": "^19.2.8",
|
|
123
125
|
"tsup": "^8.5.1",
|
|
124
126
|
"typescript": "^7.0.2",
|
|
125
127
|
"vite": "^8.1.5",
|
|
126
128
|
"mountly-svelte": "^2.0.3",
|
|
127
|
-
"mountly-react": "^2.0.2",
|
|
128
129
|
"mountly-vue": "^2.0.3"
|
|
129
130
|
},
|
|
130
131
|
"peerDependencies": {
|
|
@@ -132,8 +133,6 @@
|
|
|
132
133
|
"@json-render/react": "^0.20.0",
|
|
133
134
|
"ai": "^7.0.65",
|
|
134
135
|
"axe-core": ">=4.10.0",
|
|
135
|
-
"mountly": "^0.4.0",
|
|
136
|
-
"mountly-react": "^2.0.2",
|
|
137
136
|
"mountly-svelte": "^2.0.3",
|
|
138
137
|
"mountly-vue": "^2.0.3",
|
|
139
138
|
"playwright": ">=1.62.1",
|
|
@@ -151,9 +150,6 @@
|
|
|
151
150
|
"ai": {
|
|
152
151
|
"optional": true
|
|
153
152
|
},
|
|
154
|
-
"mountly-react": {
|
|
155
|
-
"optional": true
|
|
156
|
-
},
|
|
157
153
|
"react": {
|
|
158
154
|
"optional": true
|
|
159
155
|
},
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# {{name}}
|
|
2
|
+
|
|
3
|
+
MCP App scaffolded with `mountly-mcp create`.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pnpm install
|
|
7
|
+
pnpm dev # sandboxed host on :5179
|
|
8
|
+
pnpm verify # conformance
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- View: `src/view.tsx` (`createMcpView`)
|
|
12
|
+
- Server: `server.mjs` (`registerMcpApps`)
|
|
13
|
+
- Fixtures: `mcp.fixtures.json` (tool args when using `--server`)
|
|
14
|
+
|
|
15
|
+
Do not clone the Mountly monorepo for greenfield apps — this project is the canonical path.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{name}}",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"build": "vite build",
|
|
8
|
+
"dev": "mountly-mcp dev --server ./server.mjs",
|
|
9
|
+
"verify": "mountly-mcp verify",
|
|
10
|
+
"verify:strict": "mountly-mcp verify --strict",
|
|
11
|
+
"typecheck": "tsc --noEmit"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
15
|
+
"mountly-mcp": "^3.1.0",
|
|
16
|
+
"react": "^19.2.8",
|
|
17
|
+
"react-dom": "^19.2.8",
|
|
18
|
+
"zod": "^4.4.3"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/react": "^19.2.18",
|
|
22
|
+
"@types/react-dom": "^19.2.4",
|
|
23
|
+
"@vitejs/plugin-react": "^5.0.0",
|
|
24
|
+
"typescript": "^5.9.0",
|
|
25
|
+
"vite": "^8.2.1"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { readMcpAppManifest } from "mountly-mcp/artifact";
|
|
3
|
+
import { registerMcpApps } from "mountly-mcp/server";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
|
|
6
|
+
const URI = "{{uri}}";
|
|
7
|
+
|
|
8
|
+
function sampleCards() {
|
|
9
|
+
return [
|
|
10
|
+
{ label: "Requests", value: Math.floor(1200 + Math.random() * 200) },
|
|
11
|
+
{ label: "Latency", value: `${Math.floor(30 + Math.random() * 20)} ms` },
|
|
12
|
+
{ label: "Uptime", value: "99.9%" },
|
|
13
|
+
];
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** Default export for `mountly-mcp dev --server ./server.mjs`. */
|
|
17
|
+
export default async function createServer() {
|
|
18
|
+
const server = new McpServer(
|
|
19
|
+
{ name: "{{slug}}", version: "0.0.1" },
|
|
20
|
+
{ capabilities: { tools: {}, resources: {} } },
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
const { artifacts } = await readMcpAppManifest("dist/mountly-mcp.manifest.json");
|
|
24
|
+
|
|
25
|
+
await registerMcpApps(server, {
|
|
26
|
+
views: artifacts.map((artifact) => ({ artifact })),
|
|
27
|
+
tools: [
|
|
28
|
+
{
|
|
29
|
+
name: "showDashboard",
|
|
30
|
+
resourceUri: URI,
|
|
31
|
+
config: {
|
|
32
|
+
description: "Show an interactive metrics dashboard",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
topic: z.string().describe("Dashboard topic"),
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
handler: async ({ topic }) => ({
|
|
38
|
+
structuredContent: {
|
|
39
|
+
topic,
|
|
40
|
+
cards: [
|
|
41
|
+
{ label: "Requests", value: 1284 },
|
|
42
|
+
{ label: "Latency", value: "42 ms" },
|
|
43
|
+
{ label: "Uptime", value: "99.9%" },
|
|
44
|
+
],
|
|
45
|
+
},
|
|
46
|
+
}),
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
name: "refreshDashboard",
|
|
50
|
+
resourceUri: URI,
|
|
51
|
+
visibility: ["app"],
|
|
52
|
+
config: {
|
|
53
|
+
description: "Refresh dashboard metrics (app-only)",
|
|
54
|
+
inputSchema: {
|
|
55
|
+
reason: z.string().optional(),
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
handler: async ({ reason }) => ({
|
|
59
|
+
structuredContent: {
|
|
60
|
+
refreshedAt: new Date().toISOString(),
|
|
61
|
+
cards: sampleCards(),
|
|
62
|
+
reason: reason ?? null,
|
|
63
|
+
},
|
|
64
|
+
}),
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
return server;
|
|
70
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import {
|
|
3
|
+
createMcpView,
|
|
4
|
+
useHostStyles,
|
|
5
|
+
useMcpApp,
|
|
6
|
+
useToolResult,
|
|
7
|
+
} from "mountly-mcp/react";
|
|
8
|
+
|
|
9
|
+
interface DashboardCard {
|
|
10
|
+
label: string;
|
|
11
|
+
value: string | number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
interface DashboardData {
|
|
15
|
+
topic?: string;
|
|
16
|
+
refreshedAt?: string;
|
|
17
|
+
cards?: DashboardCard[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface DashboardResult {
|
|
21
|
+
structuredContent?: DashboardData;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const styles = `
|
|
25
|
+
:root {
|
|
26
|
+
color-scheme: light dark;
|
|
27
|
+
font-family: system-ui, -apple-system, sans-serif;
|
|
28
|
+
background: var(--color-background-primary, #ffffff);
|
|
29
|
+
color: var(--color-text-primary, #171717);
|
|
30
|
+
}
|
|
31
|
+
body { margin: 0; padding: 20px; }
|
|
32
|
+
.card {
|
|
33
|
+
border: 1px solid var(--color-border-primary, #e5e5e5);
|
|
34
|
+
border-radius: 12px;
|
|
35
|
+
padding: 20px;
|
|
36
|
+
background: var(--color-background-secondary, #fafafa);
|
|
37
|
+
}
|
|
38
|
+
.eyebrow {
|
|
39
|
+
margin: 0 0 8px;
|
|
40
|
+
color: var(--color-text-secondary, #737373);
|
|
41
|
+
font-size: 11px;
|
|
42
|
+
text-transform: uppercase;
|
|
43
|
+
letter-spacing: .1em;
|
|
44
|
+
}
|
|
45
|
+
h1 { margin: 0 0 4px; font-size: 20px; font-weight: 600; }
|
|
46
|
+
.subtitle {
|
|
47
|
+
color: var(--color-text-secondary, #737373);
|
|
48
|
+
font-size: 14px;
|
|
49
|
+
margin: 0 0 16px;
|
|
50
|
+
}
|
|
51
|
+
.grid {
|
|
52
|
+
display: grid;
|
|
53
|
+
grid-template-columns: repeat(3, 1fr);
|
|
54
|
+
gap: 10px;
|
|
55
|
+
margin-bottom: 16px;
|
|
56
|
+
}
|
|
57
|
+
.metric {
|
|
58
|
+
border: 1px solid var(--color-border-primary, #e5e5e5);
|
|
59
|
+
border-radius: 8px;
|
|
60
|
+
padding: 12px;
|
|
61
|
+
background: var(--color-background-primary, #ffffff);
|
|
62
|
+
}
|
|
63
|
+
.label {
|
|
64
|
+
color: var(--color-text-secondary, #737373);
|
|
65
|
+
font-size: 11px;
|
|
66
|
+
text-transform: uppercase;
|
|
67
|
+
letter-spacing: .05em;
|
|
68
|
+
}
|
|
69
|
+
.value { margin-top: 4px; font-size: 22px; font-weight: 600; }
|
|
70
|
+
.row { display: flex; align-items: center; gap: 10px; }
|
|
71
|
+
button {
|
|
72
|
+
border: 1px solid var(--color-border-primary, #e5e5e5);
|
|
73
|
+
border-radius: 8px;
|
|
74
|
+
padding: 8px 14px;
|
|
75
|
+
background: var(--color-background-primary, #ffffff);
|
|
76
|
+
color: inherit;
|
|
77
|
+
cursor: pointer;
|
|
78
|
+
font-size: 13px;
|
|
79
|
+
}
|
|
80
|
+
button:hover:not(:disabled) { background: var(--color-background-secondary, #f5f5f5); }
|
|
81
|
+
button:disabled { opacity: 0.6; cursor: wait; }
|
|
82
|
+
.status { font-size: 12px; color: var(--color-text-secondary, #737373); }
|
|
83
|
+
`;
|
|
84
|
+
|
|
85
|
+
function Dashboard() {
|
|
86
|
+
useHostStyles();
|
|
87
|
+
const app = useMcpApp();
|
|
88
|
+
const result = useToolResult<DashboardResult>();
|
|
89
|
+
const [data, setData] = useState<DashboardData | undefined>(result?.structuredContent);
|
|
90
|
+
const [busy, setBusy] = useState(false);
|
|
91
|
+
const [status, setStatus] = useState("Connected.");
|
|
92
|
+
|
|
93
|
+
useEffect(() => {
|
|
94
|
+
if (result?.structuredContent) setData(result.structuredContent);
|
|
95
|
+
}, [result]);
|
|
96
|
+
|
|
97
|
+
const cards = data?.cards ?? [];
|
|
98
|
+
const title = data?.topic ? `${data.topic} Dashboard` : "Dashboard";
|
|
99
|
+
|
|
100
|
+
async function refresh() {
|
|
101
|
+
setBusy(true);
|
|
102
|
+
setStatus("Refreshing…");
|
|
103
|
+
try {
|
|
104
|
+
const next = await app.callServerTool({
|
|
105
|
+
name: "refreshDashboard",
|
|
106
|
+
arguments: { reason: "User clicked refresh" },
|
|
107
|
+
});
|
|
108
|
+
const content = (next as DashboardResult).structuredContent;
|
|
109
|
+
if (content) {
|
|
110
|
+
setData((prev) => ({
|
|
111
|
+
...prev,
|
|
112
|
+
...content,
|
|
113
|
+
topic: content.topic ?? prev?.topic,
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
setStatus(
|
|
117
|
+
content?.refreshedAt
|
|
118
|
+
? `Refreshed ${new Date(content.refreshedAt).toLocaleTimeString()}`
|
|
119
|
+
: "Refreshed.",
|
|
120
|
+
);
|
|
121
|
+
} catch (error) {
|
|
122
|
+
setStatus(error instanceof Error ? error.message : "Refresh failed.");
|
|
123
|
+
} finally {
|
|
124
|
+
setBusy(false);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<div className="card">
|
|
130
|
+
<p className="eyebrow">MCP App · Mountly</p>
|
|
131
|
+
<h1>{title}</h1>
|
|
132
|
+
<p className="subtitle">
|
|
133
|
+
Built with <code>mountly-mcp</code> — try Refresh to call an app-only tool.
|
|
134
|
+
</p>
|
|
135
|
+
<div className="grid">
|
|
136
|
+
{cards.length === 0
|
|
137
|
+
? [1, 2, 3].map((i) => (
|
|
138
|
+
<div className="metric" key={i}>
|
|
139
|
+
<div className="label">—</div>
|
|
140
|
+
<div className="value">—</div>
|
|
141
|
+
</div>
|
|
142
|
+
))
|
|
143
|
+
: cards.map((c) => (
|
|
144
|
+
<div className="metric" key={c.label}>
|
|
145
|
+
<div className="label">{c.label}</div>
|
|
146
|
+
<div className="value">{String(c.value)}</div>
|
|
147
|
+
</div>
|
|
148
|
+
))}
|
|
149
|
+
</div>
|
|
150
|
+
<div className="row">
|
|
151
|
+
<button type="button" disabled={busy} onClick={() => void refresh()}>
|
|
152
|
+
Refresh data
|
|
153
|
+
</button>
|
|
154
|
+
<span className="status">{status}</span>
|
|
155
|
+
</div>
|
|
156
|
+
</div>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
createMcpView(Dashboard, { shadow: true, styles });
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"moduleResolution": "bundler",
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"noEmit": true,
|
|
11
|
+
"isolatedModules": true,
|
|
12
|
+
"resolveJsonModule": true
|
|
13
|
+
},
|
|
14
|
+
"include": ["src"]
|
|
15
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { defineConfig } from "vite";
|
|
2
|
+
import react from "@vitejs/plugin-react";
|
|
3
|
+
import { mountlyMcpViews } from "mountly-mcp/vite";
|
|
4
|
+
|
|
5
|
+
export default defineConfig({
|
|
6
|
+
plugins: [
|
|
7
|
+
react(),
|
|
8
|
+
mountlyMcpViews({
|
|
9
|
+
apps: [
|
|
10
|
+
{
|
|
11
|
+
entry: "src/view.tsx",
|
|
12
|
+
uri: "{{uri}}",
|
|
13
|
+
name: "dashboard",
|
|
14
|
+
description: "Interactive metrics dashboard",
|
|
15
|
+
displayModes: ["inline", "fullscreen"],
|
|
16
|
+
prefersBorder: true,
|
|
17
|
+
awaitToolResult: true,
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
}),
|
|
21
|
+
],
|
|
22
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# {{name}}
|
|
2
|
+
|
|
3
|
+
MCP App scaffolded with `mountly-mcp create --framework svelte`.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
pnpm install
|
|
7
|
+
pnpm dev
|
|
8
|
+
pnpm verify
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
- View: `src/view.ts` + `src/Dashboard.svelte`
|
|
12
|
+
- Server: `server.mjs` (`registerMcpApps`)
|
|
13
|
+
|
|
14
|
+
Do not clone the Mountly monorepo for greenfield apps — this project is the canonical path.
|