mountly-mcp 4.0.1 → 4.1.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/GLOSSARY.md +37 -0
- package/README.md +75 -197
- package/dist/add-DAFMA4F3.js +263 -0
- package/dist/add-DAFMA4F3.js.map +1 -0
- package/dist/add.d.ts +13 -0
- package/dist/bridge/iframe-entry.js +3 -1
- package/dist/bridge/index.js +1 -1
- package/dist/{chunk-JVQZGKBL.js → chunk-2HNXL5A4.js} +4 -2
- package/dist/chunk-2HNXL5A4.js.map +1 -0
- package/dist/{chunk-SADNMGP6.js → chunk-5L3QHYWK.js} +1 -1
- package/dist/{chunk-SADNMGP6.js.map → chunk-5L3QHYWK.js.map} +1 -1
- package/dist/chunk-EOWAWZH2.js +34 -0
- package/dist/chunk-EOWAWZH2.js.map +1 -0
- package/dist/{chunk-UECZA34Q.js → chunk-OWAXBNZA.js} +10 -5
- package/dist/chunk-OWAXBNZA.js.map +1 -0
- package/dist/{chunk-AEVQKFXX.js → chunk-SXF23BZD.js} +2 -2
- package/dist/cli.js +96 -24
- package/dist/cli.js.map +1 -1
- package/dist/connect-server-P2UZ62YC.js +7 -0
- package/dist/create.js +43 -15
- package/dist/create.js.map +1 -1
- package/dist/dev/index.js +1 -1
- package/dist/doctor-B54H6CRN.js +122 -0
- package/dist/doctor-B54H6CRN.js.map +1 -0
- package/dist/doctor.d.ts +16 -0
- package/dist/errors-4ERW7HZB.js +11 -0
- package/dist/errors-4ERW7HZB.js.map +1 -0
- package/dist/errors.d.ts +25 -0
- package/dist/index.js +2 -2
- package/dist/json-render/index.js +2 -2
- package/dist/react/index.js +2 -2
- package/dist/svelte/index.js +1 -1
- package/dist/vue/index.js +1 -1
- package/package.json +14 -11
- package/templates/react/README.md.tmpl +83 -6
- package/templates/react/package.json.tmpl +2 -0
- package/templates/react/serve-stdio.mjs.tmpl +9 -0
- package/templates/react/server.mjs.tmpl +6 -1
- package/templates/react/src/view.tsx.tmpl +1 -1
- package/templates/svelte/README.md.tmpl +27 -4
- package/templates/svelte/package.json.tmpl +2 -0
- package/templates/svelte/serve-stdio.mjs.tmpl +5 -0
- package/templates/svelte/server.mjs.tmpl +6 -1
- package/templates/svelte/src/Dashboard.svelte.tmpl +1 -1
- package/templates/vanilla/README.md.tmpl +27 -11
- package/templates/vanilla/package.json.tmpl +2 -0
- package/templates/vanilla/serve-stdio.mjs.tmpl +5 -0
- package/templates/vanilla/server.mjs.tmpl +6 -1
- package/templates/vanilla/src/view.ts.tmpl +1 -1
- package/templates/vue/README.md.tmpl +27 -4
- package/templates/vue/package.json.tmpl +2 -0
- package/templates/vue/serve-stdio.mjs.tmpl +5 -0
- package/templates/vue/server.mjs.tmpl +6 -1
- package/templates/vue/src/Dashboard.vue.tmpl +1 -1
- package/dist/chunk-JVQZGKBL.js.map +0 -1
- package/dist/chunk-UECZA34Q.js.map +0 -1
- package/dist/connect-server-7L4HGKKL.js +0 -7
- /package/dist/{chunk-AEVQKFXX.js.map → chunk-SXF23BZD.js.map} +0 -0
- /package/dist/{connect-server-7L4HGKKL.js.map → connect-server-P2UZ62YC.js.map} +0 -0
package/GLOSSARY.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Mountly MCP glossary
|
|
2
|
+
|
|
3
|
+
Public vocabulary for `mountly-mcp`. Skills, templates, CLI help, README, and
|
|
4
|
+
docs must use these names.
|
|
5
|
+
|
|
6
|
+
| Term | Meaning |
|
|
7
|
+
| ------------------------ | -------------------------------------------------------------------------------------------- |
|
|
8
|
+
| **App** | MCP server plus its Views (SEP-1865 product). |
|
|
9
|
+
| **View** | Iframe UI for a tool result (`ui://` resource). |
|
|
10
|
+
| **`ui://`** | Required scheme for View resource URIs. |
|
|
11
|
+
| **`createMcpView`** | Wrap a React / Vue / Svelte component (`mountly-mcp/react` etc.). Vanilla: `publishMcpView`. |
|
|
12
|
+
| **`mountlyMcpViews`** | Vite plugin. Emits View HTML and `dist/mountly-mcp.manifest.json`. |
|
|
13
|
+
| **`registerMcpApps`** | Install Views and UI-linked tools on an unconnected `McpServer`. |
|
|
14
|
+
| **`readMcpAppManifest`** | Load `dist/mountly-mcp.manifest.json` from `mountly-mcp/artifact`. |
|
|
15
|
+
| **`mcp.fixtures.json`** | Named samples for `mountly-mcp dev` (tool args with `--server`, else `structuredContent`). |
|
|
16
|
+
| **`create`** | `mountly-mcp create`: scaffold a greenfield App. |
|
|
17
|
+
| **`add`** | `mountly-mcp add`: add a second View to an existing Mountly Vite project. |
|
|
18
|
+
| **`build`** | `mountly-mcp build`: build Views via Vite. |
|
|
19
|
+
| **`dev`** | `mountly-mcp dev`: local sandboxed AppBridge host with live reload. |
|
|
20
|
+
| **`verify`** | `mountly-mcp verify`: conformance (`--strict`, `--render`). |
|
|
21
|
+
| **`doctor`** | `mountly-mcp doctor`: check Node, Vite plugin, Playwright, peer floors. |
|
|
22
|
+
|
|
23
|
+
## Do not put in MCP docs / skills / templates
|
|
24
|
+
|
|
25
|
+
- Islands vocabulary, HTML island attributes, microfrontends
|
|
26
|
+
- Cloning `jagreehal/mountly` or `modelcontextprotocol/ext-apps` for greenfield Views
|
|
27
|
+
- Hand-writing View HTML or postMessage for Views
|
|
28
|
+
- Calling Mountly the "default view layer" until hosts recommend it
|
|
29
|
+
|
|
30
|
+
## Happy path
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
npx mountly-mcp create my-app --framework react # react | vue | svelte | vanilla
|
|
34
|
+
cd my-app && pnpm install && pnpm dev
|
|
35
|
+
pnpm verify
|
|
36
|
+
pnpm verify --strict --render # CI
|
|
37
|
+
```
|
package/README.md
CHANGED
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
# mountly-mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Build MCP Apps Views from React, Vue, or Svelte. New apps or components you
|
|
4
|
+
already ship.
|
|
4
5
|
|
|
5
|
-
[MCP Apps](https://github.com/modelcontextprotocol/ext-apps) (SEP-1865)
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
[MCP Apps](https://github.com/modelcontextprotocol/ext-apps) (SEP-1865) is the
|
|
7
|
+
protocol. Official packages speak the wire. `mountly-mcp` is the View kit:
|
|
8
|
+
component → `ui://` resource → local host → `registerMcpApps` on your server.
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx mountly-mcp create my-app --framework react
|
|
12
|
+
cd my-app && pnpm install && pnpm dev
|
|
13
|
+
```
|
|
10
14
|
|
|
11
15
|
```ts
|
|
12
16
|
import { createMcpView } from "mountly-mcp/react";
|
|
@@ -15,81 +19,69 @@ import Dashboard from "./Dashboard";
|
|
|
15
19
|
createMcpView(Dashboard);
|
|
16
20
|
```
|
|
17
21
|
|
|
18
|
-
|
|
19
|
-
|
|
22
|
+
`--framework`: `react` | `vue` | `svelte` | `vanilla`. Vanilla calls
|
|
23
|
+
`publishMcpView` with `mount` / `update` / `unmount`. No framework runtime.
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
+
## vs ext-apps
|
|
26
|
+
|
|
27
|
+
| Need | Package |
|
|
28
|
+
| ------------------------------------------------------- | -------------------------------- |
|
|
29
|
+
| Host / bridge protocol | `@modelcontextprotocol/ext-apps` |
|
|
30
|
+
| Component → View → `dev` → `verify` → `registerMcpApps` | `mountly-mcp` |
|
|
25
31
|
|
|
26
|
-
|
|
27
|
-
framework and no adapter: a View is just `mount` / `update` / `unmount`, so
|
|
28
|
-
`publishMcpView` takes the object directly and the bridge drives it the same
|
|
29
|
-
way it drives the others.
|
|
32
|
+
Details: [Mountly vs ext-apps](https://mountly.dev/mcp-apps/vs-ext-apps/).
|
|
30
33
|
|
|
31
|
-
##
|
|
34
|
+
## Agent Skills
|
|
32
35
|
|
|
33
|
-
| Skill
|
|
34
|
-
|
|
|
35
|
-
| [`create-mcp-app`](../../plugins/mountly-mcp/skills/create-mcp-app/SKILL.md)
|
|
36
|
-
| [`add-app-to-server`](../../plugins/mountly-mcp/skills/add-app-to-server/SKILL.md) |
|
|
37
|
-
| [`convert-web-app`](../../plugins/mountly-mcp/skills/convert-web-app/SKILL.md)
|
|
38
|
-
| [`migrate-ext-apps`](../../plugins/mountly-mcp/skills/migrate-ext-apps/SKILL.md)
|
|
36
|
+
| Skill | Ask for |
|
|
37
|
+
| ---------------------------------------------------------------------------------- | ----------------------------------- |
|
|
38
|
+
| [`create-mcp-app`](../../plugins/mountly-mcp/skills/create-mcp-app/SKILL.md) | "Create an MCP App" |
|
|
39
|
+
| [`add-app-to-server`](../../plugins/mountly-mcp/skills/add-app-to-server/SKILL.md) | "Add UI to my MCP server" |
|
|
40
|
+
| [`convert-web-app`](../../plugins/mountly-mcp/skills/convert-web-app/SKILL.md) | "Turn my component into an MCP App" |
|
|
41
|
+
| [`migrate-ext-apps`](../../plugins/mountly-mcp/skills/migrate-ext-apps/SKILL.md) | "Migrate from ext-apps" |
|
|
39
42
|
|
|
40
43
|
```
|
|
41
44
|
/plugin marketplace add jagreehal/mountly
|
|
42
45
|
/plugin install mountly-mcp@mountly
|
|
43
46
|
```
|
|
44
47
|
|
|
45
|
-
Docs: [Agent Skills](https://mountly.dev/mcp-apps/agent-skills/).
|
|
46
|
-
`mountly-mcp build` emits a versioned App manifest; `registerMcpApps` installs
|
|
47
|
-
the Views into the MCP server you already own. Existing website components stay
|
|
48
|
-
reusable as-is.
|
|
49
|
-
|
|
50
|
-
The wire protocol is delegated to the official `@modelcontextprotocol/sdk` and
|
|
51
|
-
`@modelcontextprotocol/ext-apps` packages in full, so server transports,
|
|
52
|
-
handshakes, capabilities and message types track the spec rather than a
|
|
53
|
-
reimplementation of it. Both are runtime dependencies of `mountly-mcp`.
|
|
48
|
+
Or `npx skills add jagreehal/mountly`. Docs: [Agent Skills](https://mountly.dev/mcp-apps/agent-skills/).
|
|
54
49
|
|
|
55
50
|
## Install
|
|
56
51
|
|
|
57
52
|
```bash
|
|
58
|
-
# React (default)
|
|
53
|
+
# React (default). mountly + mountly-react are included.
|
|
59
54
|
npm install mountly-mcp react react-dom
|
|
60
55
|
|
|
61
56
|
# Vue / Svelte
|
|
62
57
|
npm install mountly-mcp mountly-vue vue # or mountly-svelte + svelte
|
|
63
58
|
```
|
|
64
59
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
60
|
+
Add `@modelcontextprotocol/sdk` when you import `McpServer`.
|
|
61
|
+
|
|
62
|
+
Peers: Node 20+ (22 preferred), React 18 or 19. Scaffolds pin Vite 8.
|
|
63
|
+
`npx mountly-mcp doctor` checks a project.
|
|
68
64
|
|
|
69
|
-
##
|
|
65
|
+
## Build
|
|
70
66
|
|
|
71
67
|
```ts
|
|
72
68
|
// vite.config.ts
|
|
73
|
-
import
|
|
69
|
+
import { defineConfig } from "vite";
|
|
70
|
+
import react from "@vitejs/plugin-react";
|
|
74
71
|
import { mountlyMcpViews } from "mountly-mcp/vite";
|
|
75
72
|
|
|
76
73
|
export default defineConfig({
|
|
77
74
|
plugins: [
|
|
78
|
-
|
|
75
|
+
react(),
|
|
79
76
|
mountlyMcpViews({
|
|
80
77
|
apps: [
|
|
81
78
|
{
|
|
82
|
-
entry: "src/
|
|
79
|
+
entry: "src/view.tsx",
|
|
83
80
|
uri: "ui://weather-server/dashboard",
|
|
84
81
|
name: "weather_dashboard",
|
|
85
82
|
displayModes: ["inline", "fullscreen"],
|
|
86
83
|
csp: { connectDomains: ["https://api.weather.com"] },
|
|
87
84
|
},
|
|
88
|
-
{
|
|
89
|
-
entry: "src/settings.ts",
|
|
90
|
-
uri: "ui://weather-server/settings",
|
|
91
|
-
name: "weather_settings",
|
|
92
|
-
},
|
|
93
85
|
],
|
|
94
86
|
}),
|
|
95
87
|
],
|
|
@@ -100,38 +92,19 @@ export default defineConfig({
|
|
|
100
92
|
npx mountly-mcp build
|
|
101
93
|
```
|
|
102
94
|
|
|
103
|
-
|
|
104
|
-
HTML with a transitional `.meta.json` file. The canonical
|
|
105
|
-
`dist/mountly-mcp.manifest.json` gives build, dev, registration, and CI one
|
|
106
|
-
versioned artifact contract. The original single-View plugin shape and
|
|
107
|
-
`vite build` remain supported.
|
|
95
|
+
Writes HTML and `dist/mountly-mcp.manifest.json`.
|
|
108
96
|
|
|
109
|
-
##
|
|
97
|
+
## Develop
|
|
110
98
|
|
|
111
99
|
```bash
|
|
112
|
-
npx mountly-mcp dev
|
|
113
|
-
|
|
114
|
-
npx mountly-mcp dev --app weather_dashboard
|
|
100
|
+
npx mountly-mcp dev --server ./server.mjs
|
|
101
|
+
npx mountly-mcp dev --app weather_dashboard --server ./server.mjs
|
|
115
102
|
```
|
|
116
103
|
|
|
117
|
-
|
|
118
|
-
|
|
104
|
+
Real AppBridge host plus sandbox CSP. Tool arguments come from
|
|
105
|
+
`mcp.fixtures.json`.
|
|
119
106
|
|
|
120
|
-
|
|
121
|
-
handshake and host behavior, while Mountly supplies two origins and a sandbox
|
|
122
|
-
proxy enforcing the CSP and permissions the App artifact declares. Sample tool
|
|
123
|
-
results come from `mcp.fixtures.json`,
|
|
124
|
-
one button each, so clicking between them exercises the update path:
|
|
125
|
-
|
|
126
|
-
```json
|
|
127
|
-
{ "annual": { "total": 99 }, "monthly": { "total": 12 } }
|
|
128
|
-
```
|
|
129
|
-
|
|
130
|
-
Add `--server ./server.js` (a module default-exporting your server) and the
|
|
131
|
-
fixtures become tool _arguments_ instead: the view renders what your handler
|
|
132
|
-
really returns, and the tool calls the view makes itself are routed there too.
|
|
133
|
-
|
|
134
|
-
## Install into your production server
|
|
107
|
+
## Production server
|
|
135
108
|
|
|
136
109
|
```ts
|
|
137
110
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -153,152 +126,57 @@ await registerMcpApps(server, {
|
|
|
153
126
|
config: {
|
|
154
127
|
inputSchema: { type: "object", properties: { location: { type: "string" } } },
|
|
155
128
|
},
|
|
156
|
-
handler: async ({ location }) => ({
|
|
129
|
+
handler: async ({ location }) => ({
|
|
130
|
+
structuredContent: { location, temperature: 72 },
|
|
131
|
+
}),
|
|
157
132
|
},
|
|
158
133
|
],
|
|
159
134
|
});
|
|
135
|
+
// connect with YOUR transport after this
|
|
160
136
|
```
|
|
161
137
|
|
|
162
|
-
Call `registerMcpApps` before
|
|
163
|
-
ownership of authentication, transport, prompts, non-UI tools, lifecycle, and
|
|
164
|
-
deployment. Views and tools are independent, so several tools can link to one
|
|
165
|
-
View and app-only tools use `visibility: "app"`. Mountly negotiates the UI
|
|
166
|
-
extension and degrades to text-only model tools for hosts without MCP Apps.
|
|
167
|
-
|
|
168
|
-
`createMcpAppServer` and `serveStdio` remain as convenience adapters for small
|
|
169
|
-
servers and existing projects.
|
|
138
|
+
Call `registerMcpApps` before `connect`. You keep auth, transport, and deploy.
|
|
170
139
|
|
|
171
|
-
## Verify
|
|
140
|
+
## Verify
|
|
172
141
|
|
|
173
142
|
```bash
|
|
143
|
+
npx mountly-mcp doctor
|
|
174
144
|
npx mountly-mcp verify
|
|
175
|
-
npx mountly-mcp verify --strict
|
|
176
|
-
npx mountly-mcp verify --render --strict
|
|
145
|
+
npx mountly-mcp verify --strict --render
|
|
177
146
|
```
|
|
178
147
|
|
|
179
|
-
|
|
180
|
-
launches Chromium, waits for the explicit bridge mount state, and rejects Views
|
|
181
|
-
that time out, throw, or mount without content. Errors always fail; advisory
|
|
182
|
-
warnings fail only under `--strict`. The same engine is exported as
|
|
183
|
-
`verifyMcpApps()` from `mountly-mcp/testing` for custom test runners. Install
|
|
184
|
-
Playwright and Chromium for the browser tier.
|
|
185
|
-
|
|
186
|
-
## Reading tool data in a view
|
|
187
|
-
|
|
188
|
-
The bridge exposes `mcp`, `toolInput`, `toolResult`, and `hostContext`. React
|
|
189
|
-
and Vue consume those bridge props in their wrapper and expose them through
|
|
190
|
-
hooks/composables, leaving the existing component's props unchanged. Svelte
|
|
191
|
-
receives them directly as component props:
|
|
192
|
-
|
|
193
|
-
```tsx
|
|
194
|
-
// React
|
|
195
|
-
const result = useToolResult<{ structuredContent?: Quote }>();
|
|
196
|
-
const quote = result?.structuredContent;
|
|
197
|
-
useHostStyles(); // adopt the host's theme variables and fonts
|
|
198
|
-
```
|
|
199
|
-
|
|
200
|
-
```ts
|
|
201
|
-
// Vue
|
|
202
|
-
const result = useToolResult<{ structuredContent?: Quote }>(); // a ref
|
|
203
|
-
const quote = computed(() => result.value?.structuredContent);
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
Svelte reads the props directly via `$props()` — see `mountly-mcp/svelte`.
|
|
207
|
-
|
|
208
|
-
## json-render MCP Apps integration
|
|
209
|
-
|
|
210
|
-
Mountly now exposes a first-class json-render MCP facade so catalog-driven UIs
|
|
211
|
-
can be shipped as MCP Apps with minimal protocol code:
|
|
212
|
-
|
|
213
|
-
```ts
|
|
214
|
-
import { createJsonRenderMcpApp } from "mountly-mcp/json-render/mcp";
|
|
215
|
-
import { catalog } from "./catalog.js";
|
|
216
|
-
import { html } from "./dist/app-html.js";
|
|
217
|
-
|
|
218
|
-
const app = createJsonRenderMcpApp({
|
|
219
|
-
name: "json-render-dashboard",
|
|
220
|
-
version: "1.0.0",
|
|
221
|
-
catalog,
|
|
222
|
-
html,
|
|
223
|
-
tool: {
|
|
224
|
-
name: "render_ui",
|
|
225
|
-
title: "Render UI",
|
|
226
|
-
description: "Render an interactive dashboard from a json-render spec.",
|
|
227
|
-
resourceUri: "ui://json-render-dashboard/view.html",
|
|
228
|
-
},
|
|
229
|
-
});
|
|
230
|
-
```
|
|
231
|
-
|
|
232
|
-
For iframe-side React hosts, use `mountly-mcp/json-render/app`:
|
|
233
|
-
|
|
234
|
-
```tsx
|
|
235
|
-
import { useJsonRenderApp } from "mountly-mcp/json-render/app";
|
|
148
|
+
## Second View
|
|
236
149
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
name: "json-render-dashboard",
|
|
240
|
-
version: "1.0.0",
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
if (error) return <div>{error.message}</div>;
|
|
244
|
-
if (!connected || loading || !spec) return <div>Waiting…</div>;
|
|
245
|
-
return <pre>{JSON.stringify(spec, null, 2)}</pre>;
|
|
246
|
-
}
|
|
150
|
+
```bash
|
|
151
|
+
npx mountly-mcp add settings --framework react
|
|
247
152
|
```
|
|
248
153
|
|
|
249
|
-
|
|
250
|
-
`mountly-mcp/json-render/app` to generate a self-contained HTML resource.
|
|
154
|
+
## Glossary
|
|
251
155
|
|
|
252
|
-
|
|
156
|
+
[GLOSSARY.md](./GLOSSARY.md). Protocol: [`@modelcontextprotocol/ext-apps`](https://www.npmjs.com/package/@modelcontextprotocol/ext-apps).
|
|
253
157
|
|
|
254
|
-
|
|
255
|
-
directions, so you can mix the two without a bridge:
|
|
158
|
+
## Advanced: json-render
|
|
256
159
|
|
|
257
|
-
-
|
|
258
|
-
|
|
259
|
-
vocabulary.
|
|
260
|
-
- **Tool output** carries the spec twice: as JSON in `content[0].text` (the
|
|
261
|
-
shape `@json-render/mcp`'s iframe hook reads) and as `structuredContent.spec`
|
|
262
|
-
(what Mountly's own hosts read).
|
|
263
|
-
- **`useJsonRenderApp`** reads either shape, so an iframe built with Mountly
|
|
264
|
-
renders a `@json-render/mcp` server unchanged, and vice versa.
|
|
265
|
-
|
|
266
|
-
One deliberate difference: `@json-render/core` 0.19 generates a _strict_ catalog
|
|
267
|
-
schema that requires `visible` and `children` on every element, which rejects
|
|
268
|
-
most first-attempt model output at the SDK's input gate. Mountly accepts the
|
|
269
|
-
catalog schema **or** a looser spec shape, then validates in the handler and
|
|
270
|
-
renders what it can. Drop the loose branch once core relaxes those fields.
|
|
160
|
+
`mountly-mcp/json-render` is optional catalog-driven UI. Start with `create` /
|
|
161
|
+
`createMcpView`. Docs: [Generative UI](https://mountly.dev/concepts/generative-ui/).
|
|
271
162
|
|
|
272
163
|
## Exports
|
|
273
164
|
|
|
274
|
-
| Entry
|
|
275
|
-
|
|
|
276
|
-
| `mountly-mcp`
|
|
277
|
-
| `mountly-mcp/react`
|
|
278
|
-
| `mountly-mcp/vue`
|
|
279
|
-
| `mountly-mcp/svelte`
|
|
280
|
-
| `mountly-mcp/vite`
|
|
281
|
-
| `mountly-mcp/artifact`
|
|
282
|
-
| `mountly-mcp/
|
|
283
|
-
| `mountly-mcp/
|
|
284
|
-
| `mountly-mcp/
|
|
285
|
-
| `mountly-mcp/
|
|
286
|
-
|
|
287
|
-
Types use the spec's own names (`McpUiHostContext`, `McpUiDisplayMode`, …) —
|
|
288
|
-
one vocabulary, no translation layer.
|
|
289
|
-
|
|
290
|
-
## Agent Skills
|
|
291
|
-
|
|
292
|
-
Install the plugin skills and ask your coding agent to create an MCP App:
|
|
293
|
-
[Agent Skills docs](https://mountly.dev/mcp-apps/agent-skills/) ·
|
|
294
|
-
[`create-mcp-app`](../../plugins/mountly-mcp/skills/create-mcp-app/SKILL.md).
|
|
295
|
-
|
|
296
|
-
Greenfield without an agent: `npx mountly-mcp create my-app --framework react`.
|
|
165
|
+
| Entry | Contents |
|
|
166
|
+
| ------------------------- | ------------------------------------ |
|
|
167
|
+
| `mountly-mcp` | `runBridge`, `publishMcpView`, types |
|
|
168
|
+
| `mountly-mcp/react` | `createMcpView` + hooks |
|
|
169
|
+
| `mountly-mcp/vue` | `createMcpView` + composables |
|
|
170
|
+
| `mountly-mcp/svelte` | `createMcpView` (props) |
|
|
171
|
+
| `mountly-mcp/vite` | `mountlyMcpViews()` |
|
|
172
|
+
| `mountly-mcp/artifact` | Manifest APIs |
|
|
173
|
+
| `mountly-mcp/server` | `registerMcpApps` |
|
|
174
|
+
| `mountly-mcp/dev` | Local host helpers |
|
|
175
|
+
| `mountly-mcp/testing` | `verifyMcpApps` |
|
|
176
|
+
| `mountly-mcp/json-render` | Generative path |
|
|
297
177
|
|
|
298
178
|
## See also
|
|
299
179
|
|
|
300
|
-
- [`
|
|
301
|
-
|
|
302
|
-
- [
|
|
303
|
-
protocol harness (not the greenfield starter)
|
|
304
|
-
- `docs/how-to-test.md`
|
|
180
|
+
- [`mcp-release-readiness`](../../docs/examples/mcp-release-readiness) — production-shaped Vite View
|
|
181
|
+
- [`mcp-app-demo`](../../docs/examples/mcp-app-demo) — protocol harness
|
|
182
|
+
- [Host matrix](https://mountly.dev/mcp-apps/host-matrix/)
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import {
|
|
2
|
+
CLI_ERROR_CODES,
|
|
3
|
+
cliError
|
|
4
|
+
} from "./chunk-EOWAWZH2.js";
|
|
5
|
+
|
|
6
|
+
// src/add.ts
|
|
7
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
8
|
+
import { dirname, join, resolve } from "path";
|
|
9
|
+
function parseAddArgs(argv) {
|
|
10
|
+
const name = argv[0];
|
|
11
|
+
if (!name || name.startsWith("-")) {
|
|
12
|
+
throw cliError(
|
|
13
|
+
CLI_ERROR_CODES.UNKNOWN_OPTION,
|
|
14
|
+
"mountly-mcp add requires a View name",
|
|
15
|
+
"Example: mountly-mcp add settings --framework react"
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
const args = { name };
|
|
19
|
+
for (let i = 1; i < argv.length; i += 1) {
|
|
20
|
+
const arg = argv[i];
|
|
21
|
+
const next = () => {
|
|
22
|
+
const value = argv[i + 1];
|
|
23
|
+
if (value === void 0) {
|
|
24
|
+
throw cliError(
|
|
25
|
+
CLI_ERROR_CODES.UNKNOWN_OPTION,
|
|
26
|
+
`${arg} needs a value`,
|
|
27
|
+
"See: mountly-mcp --help"
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
i += 1;
|
|
31
|
+
return value;
|
|
32
|
+
};
|
|
33
|
+
if (arg === "--entry") args.entry = next();
|
|
34
|
+
else if (arg === "--uri") args.uri = next();
|
|
35
|
+
else if (arg === "--framework" || arg === "-f") {
|
|
36
|
+
const value = next();
|
|
37
|
+
if (!["react", "vue", "svelte", "vanilla"].includes(value)) {
|
|
38
|
+
throw cliError(
|
|
39
|
+
CLI_ERROR_CODES.UNKNOWN_OPTION,
|
|
40
|
+
`framework "${value}" not supported`,
|
|
41
|
+
"Use: react | vue | svelte | vanilla"
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
args.framework = value;
|
|
45
|
+
} else if (arg === "--config" || arg === "-c") args.config = next();
|
|
46
|
+
else {
|
|
47
|
+
throw cliError(
|
|
48
|
+
CLI_ERROR_CODES.UNKNOWN_OPTION,
|
|
49
|
+
`unknown add option '${arg}'`,
|
|
50
|
+
"Options: --entry --uri --framework --config"
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return args;
|
|
55
|
+
}
|
|
56
|
+
function detectFramework(cwd) {
|
|
57
|
+
const pkgPath = join(cwd, "package.json");
|
|
58
|
+
if (!existsSync(pkgPath)) return "react";
|
|
59
|
+
const pkg = JSON.parse(readFileSync(pkgPath, "utf8"));
|
|
60
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
61
|
+
if (deps.vue || deps["mountly-vue"]) return "vue";
|
|
62
|
+
if (deps.svelte || deps["mountly-svelte"]) return "svelte";
|
|
63
|
+
if (deps.react) return "react";
|
|
64
|
+
return "vanilla";
|
|
65
|
+
}
|
|
66
|
+
function findViteConfig(cwd, explicit) {
|
|
67
|
+
if (explicit) {
|
|
68
|
+
const path = resolve(cwd, explicit);
|
|
69
|
+
if (!existsSync(path)) {
|
|
70
|
+
throw cliError(
|
|
71
|
+
CLI_ERROR_CODES.NO_VITE_CONFIG,
|
|
72
|
+
`vite config not found: ${path}`,
|
|
73
|
+
"Pass --config path/to/vite.config.ts"
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
return path;
|
|
77
|
+
}
|
|
78
|
+
for (const name of ["vite.config.ts", "vite.config.mts", "vite.config.js", "vite.config.mjs"]) {
|
|
79
|
+
const path = join(cwd, name);
|
|
80
|
+
if (existsSync(path)) return path;
|
|
81
|
+
}
|
|
82
|
+
throw cliError(
|
|
83
|
+
CLI_ERROR_CODES.NO_VITE_CONFIG,
|
|
84
|
+
"no vite.config.* in this directory",
|
|
85
|
+
"Run from a Mountly MCP project, or: npx mountly-mcp create my-app --framework react"
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
function slugify(name) {
|
|
89
|
+
return name.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
90
|
+
}
|
|
91
|
+
function viewSource(framework, componentName) {
|
|
92
|
+
if (framework === "react") {
|
|
93
|
+
return `import { createMcpView, useToolResult } from "mountly-mcp/react";
|
|
94
|
+
|
|
95
|
+
interface Result {
|
|
96
|
+
structuredContent?: { title?: string };
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function ${componentName}() {
|
|
100
|
+
const result = useToolResult<Result>();
|
|
101
|
+
const title = result?.structuredContent?.title ?? "${componentName}";
|
|
102
|
+
return (
|
|
103
|
+
<main style={{ fontFamily: "system-ui", padding: 20 }}>
|
|
104
|
+
<h1>{title}</h1>
|
|
105
|
+
<p>Edit <code>src/${slugify(componentName)}.tsx</code> \u2014 data comes from useToolResult().</p>
|
|
106
|
+
</main>
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
createMcpView(${componentName});
|
|
111
|
+
`;
|
|
112
|
+
}
|
|
113
|
+
if (framework === "vue") {
|
|
114
|
+
return `import { createMcpView } from "mountly-mcp/vue";
|
|
115
|
+
import ${componentName} from "./${componentName}.vue";
|
|
116
|
+
|
|
117
|
+
createMcpView(${componentName});
|
|
118
|
+
`;
|
|
119
|
+
}
|
|
120
|
+
if (framework === "svelte") {
|
|
121
|
+
return `import { createMcpView } from "mountly-mcp/svelte";
|
|
122
|
+
import ${componentName} from "./${componentName}.svelte";
|
|
123
|
+
|
|
124
|
+
createMcpView(${componentName});
|
|
125
|
+
`;
|
|
126
|
+
}
|
|
127
|
+
return `import { publishMcpView, type McpViewProps } from "mountly-mcp";
|
|
128
|
+
|
|
129
|
+
publishMcpView({
|
|
130
|
+
mount(el, props: McpViewProps) {
|
|
131
|
+
el.innerHTML = "<main style=\\"font-family:system-ui;padding:20px\\"><h1>${componentName}</h1><pre></pre></main>";
|
|
132
|
+
const pre = el.querySelector("pre");
|
|
133
|
+
if (pre) pre.textContent = JSON.stringify(props.toolResult?.structuredContent ?? {}, null, 2);
|
|
134
|
+
},
|
|
135
|
+
update(el, props: McpViewProps) {
|
|
136
|
+
const pre = el.querySelector("pre");
|
|
137
|
+
if (pre) pre.textContent = JSON.stringify(props.toolResult?.structuredContent ?? {}, null, 2);
|
|
138
|
+
},
|
|
139
|
+
unmount(el) {
|
|
140
|
+
el.innerHTML = "";
|
|
141
|
+
},
|
|
142
|
+
});
|
|
143
|
+
`;
|
|
144
|
+
}
|
|
145
|
+
function vueComponent(componentName) {
|
|
146
|
+
return `<script setup lang="ts">
|
|
147
|
+
import { computed } from "vue";
|
|
148
|
+
import { useToolResult } from "mountly-mcp/vue";
|
|
149
|
+
|
|
150
|
+
const result = useToolResult<{ structuredContent?: { title?: string } }>();
|
|
151
|
+
const title = computed(() => result.value?.structuredContent?.title ?? "${componentName}");
|
|
152
|
+
</script>
|
|
153
|
+
|
|
154
|
+
<template>
|
|
155
|
+
<main style="font-family: system-ui; padding: 20px">
|
|
156
|
+
<h1>{{ title }}</h1>
|
|
157
|
+
<p>Edit this View \u2014 data comes from useToolResult().</p>
|
|
158
|
+
</main>
|
|
159
|
+
</template>
|
|
160
|
+
`;
|
|
161
|
+
}
|
|
162
|
+
function svelteComponent(componentName) {
|
|
163
|
+
return `<script lang="ts">
|
|
164
|
+
let { toolResult } = $props();
|
|
165
|
+
const title = $derived(toolResult?.structuredContent?.title ?? "${componentName}");
|
|
166
|
+
</script>
|
|
167
|
+
|
|
168
|
+
<main style="font-family: system-ui; padding: 20px">
|
|
169
|
+
<h1>{title}</h1>
|
|
170
|
+
<p>Edit this View \u2014 toolResult arrives as a prop.</p>
|
|
171
|
+
</main>
|
|
172
|
+
`;
|
|
173
|
+
}
|
|
174
|
+
function addView(args, cwd = process.cwd()) {
|
|
175
|
+
const framework = args.framework ?? detectFramework(cwd);
|
|
176
|
+
const configPath = findViteConfig(cwd, args.config);
|
|
177
|
+
const configSource = readFileSync(configPath, "utf8");
|
|
178
|
+
if (!/mountlyMcpViews\s*\(/.test(configSource)) {
|
|
179
|
+
throw cliError(
|
|
180
|
+
CLI_ERROR_CODES.MISSING_VITE_PLUGIN,
|
|
181
|
+
`no mountlyMcpViews() in ${configPath}`,
|
|
182
|
+
`Add mountlyMcpViews({ apps: [...] }) from "mountly-mcp/vite" to ${configPath}`
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
const slug = slugify(args.name) || "view";
|
|
186
|
+
const pascal = slug.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
187
|
+
const uri = args.uri ?? `ui://app/${slug}`;
|
|
188
|
+
if (!uri.startsWith("ui://")) {
|
|
189
|
+
throw cliError(
|
|
190
|
+
CLI_ERROR_CODES.INVALID_UI_URI,
|
|
191
|
+
`uri must start with ui:// (received '${uri}')`,
|
|
192
|
+
"Example: --uri ui://my-server/settings"
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
const entry = args.entry ?? `src/${slug}.${framework === "react" ? "tsx" : "ts"}`;
|
|
196
|
+
const entryPath = resolve(cwd, entry);
|
|
197
|
+
if (existsSync(entryPath)) {
|
|
198
|
+
throw cliError(
|
|
199
|
+
CLI_ERROR_CODES.TARGET_EXISTS,
|
|
200
|
+
`entry already exists: ${entryPath}`,
|
|
201
|
+
"Pass a different --entry or remove the file first"
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
if (configSource.includes(JSON.stringify(uri)) || configSource.includes(JSON.stringify(slug))) {
|
|
205
|
+
throw cliError(
|
|
206
|
+
CLI_ERROR_CODES.TARGET_EXISTS,
|
|
207
|
+
`${configPath} already declares a View named '${slug}' or uri '${uri}'`,
|
|
208
|
+
"Pick another name, or pass --uri to use a different resource URI"
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
const appBlock = `{
|
|
212
|
+
entry: ${JSON.stringify(entry)},
|
|
213
|
+
uri: ${JSON.stringify(uri)},
|
|
214
|
+
name: ${JSON.stringify(slug)},
|
|
215
|
+
displayModes: ["inline", "fullscreen"],
|
|
216
|
+
prefersBorder: true,
|
|
217
|
+
awaitToolResult: true,
|
|
218
|
+
}`;
|
|
219
|
+
let next;
|
|
220
|
+
if (/apps:\s*\[\s*\]/.test(configSource)) {
|
|
221
|
+
next = configSource.replace(/apps:\s*\[\s*\]/, `apps: [
|
|
222
|
+
${appBlock},
|
|
223
|
+
]`);
|
|
224
|
+
} else if (/apps:\s*\[/.test(configSource)) {
|
|
225
|
+
next = configSource.replace(/apps:\s*\[/, `apps: [
|
|
226
|
+
${appBlock},`);
|
|
227
|
+
} else {
|
|
228
|
+
throw cliError(
|
|
229
|
+
CLI_ERROR_CODES.MISSING_VITE_PLUGIN,
|
|
230
|
+
`could not find apps: [] in ${configPath}`,
|
|
231
|
+
"Add an apps array inside mountlyMcpViews({ ... })"
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
mkdirSync(dirname(entryPath), { recursive: true });
|
|
235
|
+
writeFileSync(entryPath, viewSource(framework, pascal));
|
|
236
|
+
if (framework === "vue") {
|
|
237
|
+
writeFileSync(join(dirname(entryPath), `${pascal}.vue`), vueComponent(pascal));
|
|
238
|
+
}
|
|
239
|
+
if (framework === "svelte") {
|
|
240
|
+
writeFileSync(join(dirname(entryPath), `${pascal}.svelte`), svelteComponent(pascal));
|
|
241
|
+
}
|
|
242
|
+
writeFileSync(configPath, next);
|
|
243
|
+
process.stdout.write(
|
|
244
|
+
[
|
|
245
|
+
"",
|
|
246
|
+
`\u2713 Added View "${slug}"`,
|
|
247
|
+
` entry ${entry}`,
|
|
248
|
+
` uri ${uri}`,
|
|
249
|
+
` config ${configPath}`,
|
|
250
|
+
"",
|
|
251
|
+
"Next:",
|
|
252
|
+
" pnpm exec mountly-mcp build",
|
|
253
|
+
` pnpm exec mountly-mcp dev --app ${slug}`,
|
|
254
|
+
" Register a tool with resourceUri matching the uri above via registerMcpApps.",
|
|
255
|
+
""
|
|
256
|
+
].join("\n")
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
export {
|
|
260
|
+
addView,
|
|
261
|
+
parseAddArgs
|
|
262
|
+
};
|
|
263
|
+
//# sourceMappingURL=add-DAFMA4F3.js.map
|