mountly-mcp 4.1.0 → 4.3.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.
Files changed (41) hide show
  1. package/GLOSSARY.md +1 -0
  2. package/README.md +123 -12
  3. package/dist/bridge/iframe-entry.js +7 -7
  4. package/dist/build/emit-meta.d.ts +3 -0
  5. package/dist/build/index.d.ts +3 -0
  6. package/dist/build/index.js +1 -1
  7. package/dist/chunk-4SZHJ4MK.js +9 -0
  8. package/dist/chunk-4SZHJ4MK.js.map +1 -0
  9. package/dist/{chunk-KWVGW5Q7.js → chunk-GN2IQYV3.js} +1 -1
  10. package/dist/chunk-GN2IQYV3.js.map +1 -0
  11. package/dist/{chunk-SXF23BZD.js → chunk-HIAIQVK5.js} +4 -7
  12. package/dist/chunk-HIAIQVK5.js.map +1 -0
  13. package/dist/{chunk-MTJDRWGD.js → chunk-IDGTP2NI.js} +4 -2
  14. package/dist/chunk-IDGTP2NI.js.map +1 -0
  15. package/dist/cli.js +2 -2
  16. package/dist/compose/index.d.ts +48 -0
  17. package/dist/compose/index.js +141 -0
  18. package/dist/compose/index.js.map +1 -0
  19. package/dist/dev/host-entry.js +13 -6
  20. package/dist/dev/index.js +1 -1
  21. package/dist/{doctor-B54H6CRN.js → doctor-4MTC6Y4X.js} +38 -3
  22. package/dist/doctor-4MTC6Y4X.js.map +1 -0
  23. package/dist/index.js +3 -3
  24. package/dist/json-render/index.js +6 -5
  25. package/dist/json-render/index.js.map +1 -1
  26. package/dist/openai/server.d.ts +16 -0
  27. package/dist/openai/server.js +20 -0
  28. package/dist/openai/server.js.map +1 -0
  29. package/dist/react/index.js +4 -2
  30. package/dist/react/openai-extensions.d.ts +6 -0
  31. package/dist/react/openai-extensions.js +25 -0
  32. package/dist/react/openai-extensions.js.map +1 -0
  33. package/dist/server/index.js +11 -6
  34. package/dist/server/index.js.map +1 -1
  35. package/dist/types.d.ts +3 -0
  36. package/dist/vite/index.js +1 -1
  37. package/package.json +25 -3
  38. package/dist/chunk-KWVGW5Q7.js.map +0 -1
  39. package/dist/chunk-MTJDRWGD.js.map +0 -1
  40. package/dist/chunk-SXF23BZD.js.map +0 -1
  41. package/dist/doctor-B54H6CRN.js.map +0 -1
package/GLOSSARY.md CHANGED
@@ -13,6 +13,7 @@ docs must use these names.
13
13
  | **`registerMcpApps`** | Install Views and UI-linked tools on an unconnected `McpServer`. |
14
14
  | **`readMcpAppManifest`** | Load `dist/mountly-mcp.manifest.json` from `mountly-mcp/artifact`. |
15
15
  | **`mcp.fixtures.json`** | Named samples for `mountly-mcp dev` (tool args with `--server`, else `structuredContent`). |
16
+ | **OpenAI extensions** | ChatGPT-only extras via `@openai/mcp-extensions`. Helpers: `mountly-mcp/openai/server`, `mountly-mcp/react/openai`. |
16
17
  | **`create`** | `mountly-mcp create`: scaffold a greenfield App. |
17
18
  | **`add`** | `mountly-mcp add`: add a second View to an existing Mountly Vite project. |
18
19
  | **`build`** | `mountly-mcp build`: build Views via Vite. |
package/README.md CHANGED
@@ -137,6 +137,80 @@ await registerMcpApps(server, {
137
137
 
138
138
  Call `registerMcpApps` before `connect`. You keep auth, transport, and deploy.
139
139
 
140
+ ## ChatGPT / OpenAI extensions
141
+
142
+ Install the optional peer [`@openai/mcp-extensions`](https://www.npmjs.com/package/@openai/mcp-extensions)
143
+ for ChatGPT entrypoints, mentions, forms, and settings. Mountly helpers import that SDK.
144
+
145
+ ```bash
146
+ pnpm add @openai/mcp-extensions
147
+ ```
148
+
149
+ **Resource metadata** — pass `openaiUi` on the Vite View (or `buildMcpResource`).
150
+ `registerMcpApps` writes it to `_meta["openai/ui"]` on the `ui://` resource:
151
+
152
+ ```ts
153
+ mountlyMcpViews({
154
+ apps: [
155
+ {
156
+ entry: "src/view.tsx",
157
+ uri: "ui://parts/library",
158
+ name: "parts_library",
159
+ openaiUi: {
160
+ preferredDisplayMode: "fullscreen",
161
+ availableDisplayModes: ["inline", "fullscreen"],
162
+ },
163
+ },
164
+ ],
165
+ });
166
+ ```
167
+
168
+ **Tool entrypoints** — merge typed `openai/ui` into the tool `_meta`:
169
+
170
+ ```ts
171
+ import {
172
+ enableOpenAiExtensions,
173
+ openaiUiToolMeta,
174
+ } from "mountly-mcp/openai/server";
175
+
176
+ const openai = enableOpenAiExtensions(server); // mentions, settings, elicitInput
177
+
178
+ await registerMcpApps(server, {
179
+ views: artifacts.map((artifact) => ({ artifact })),
180
+ tools: [
181
+ {
182
+ name: "parts.library",
183
+ resourceUri: "ui://parts/library",
184
+ config: {
185
+ title: "Parts Library",
186
+ inputSchema: {},
187
+ _meta: {
188
+ ...openaiUiToolMeta({
189
+ entrypoints: [{ type: "global" }],
190
+ }),
191
+ },
192
+ },
193
+ handler: async () => ({ structuredContent: {} }),
194
+ },
195
+ ],
196
+ });
197
+ ```
198
+
199
+ **View-side** — import from the OpenAI React entry:
200
+
201
+ ```ts
202
+ import { useOpenAIExtensions } from "mountly-mcp/react/openai";
203
+
204
+ function Library() {
205
+ const openai = useOpenAIExtensions();
206
+ // openai.message, openai.deepLink, openai.files, …
207
+ }
208
+ ```
209
+
210
+ Smoke in ChatGPT: serve Streamable HTTP at `/mcp`, enable Developer mode, add the
211
+ connector, then open a `global` entrypoint from the Desktop sidebar. See the
212
+ [host matrix](https://mountly.dev/mcp-apps/host-matrix/).
213
+
140
214
  ## Verify
141
215
 
142
216
  ```bash
@@ -155,6 +229,40 @@ npx mountly-mcp add settings --framework react
155
229
 
156
230
  [GLOSSARY.md](./GLOSSARY.md). Protocol: [`@modelcontextprotocol/ext-apps`](https://www.npmjs.com/package/@modelcontextprotocol/ext-apps).
157
231
 
232
+ ## Compose from teams' elements
233
+
234
+ `mountly-mcp/compose` lets the agent build a page from many teams' custom
235
+ elements, using a [`mountly-compose`](../mountly-compose/README.md) catalog read
236
+ from their `custom-elements.json`:
237
+
238
+ ```ts
239
+ import { loadCatalog } from "mountly-compose";
240
+ import { registerComposeApp } from "mountly-mcp/compose";
241
+
242
+ registerComposeApp(server, {
243
+ catalog: await loadCatalog("https://cdn.acme.com/registry.json", { actions }),
244
+ imports: {
245
+ "mountly-compose": "https://cdn.acme.com/mountly-compose.js",
246
+ "@modelcontextprotocol/ext-apps": "https://cdn.acme.com/ext-apps/app-with-deps.js",
247
+ react: "…",
248
+ "react/jsx-runtime": "…",
249
+ "react-dom/client": "…",
250
+ "mountly-react": "…",
251
+ "mountly/embed": "…",
252
+ },
253
+ });
254
+ ```
255
+
256
+ The catalog becomes `show_page`'s description, and the agent composes the page.
257
+ The handler validates the page and repairs small mistakes. When a page needs
258
+ more than a repair, the handler returns the reasons as a tool error for the
259
+ agent to fix. The `ui://` view renders with `mountly-compose` and loads each
260
+ team's embed on first use. Its CSP allows the origins in `imports` and the
261
+ teams' embed origins. The view runs at a `null` origin, so the servers behind
262
+ those URLs send `Access-Control-Allow-Origin`. When the user acts in a widget,
263
+ the view sends the agent its next turn with the current page. The
264
+ [`ai-compose`](../../docs/examples/ai-compose) example runs this in `mcp/`.
265
+
158
266
  ## Advanced: json-render
159
267
 
160
268
  `mountly-mcp/json-render` is optional catalog-driven UI. Start with `create` /
@@ -162,18 +270,21 @@ npx mountly-mcp add settings --framework react
162
270
 
163
271
  ## Exports
164
272
 
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 |
273
+ | Entry | Contents |
274
+ | ------------------------- | --------------------------------------- |
275
+ | `mountly-mcp` | `runBridge`, `publishMcpView`, types |
276
+ | `mountly-mcp/react` | `createMcpView` + hooks |
277
+ | `mountly-mcp/react/openai` | `useOpenAIExtensions` (ChatGPT peer) |
278
+ | `mountly-mcp/vue` | `createMcpView` + composables |
279
+ | `mountly-mcp/svelte` | `createMcpView` (props) |
280
+ | `mountly-mcp/vite` | `mountlyMcpViews()` |
281
+ | `mountly-mcp/artifact` | Manifest APIs |
282
+ | `mountly-mcp/server` | `registerMcpApps` |
283
+ | `mountly-mcp/openai/server` | ChatGPT helpers (`enableOpenAiExtensions`, `openaiUiToolMeta`) |
284
+ | `mountly-mcp/dev` | Local host helpers |
285
+ | `mountly-mcp/testing` | `verifyMcpApps` |
286
+ | `mountly-mcp/json-render` | Generative path |
287
+ | `mountly-mcp/compose` | `registerComposeApp`, `composeViewHtml` |
177
288
 
178
289
  ## See also
179
290
 
@@ -15437,7 +15437,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15437
15437
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/mini/parse.js
15438
15438
  init_core2();
15439
15439
 
15440
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.30.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
15440
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.30.0_@cfworker+json-schema@4.1.1_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
15441
15441
  function isZ4Schema(s) {
15442
15442
  const schema = s;
15443
15443
  return !!schema._zod;
@@ -15500,7 +15500,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15500
15500
  return void 0;
15501
15501
  }
15502
15502
 
15503
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.30.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
15503
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.30.0_@cfworker+json-schema@4.1.1_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
15504
15504
  init_v4();
15505
15505
  var RELATED_TASK_META_KEY = "io.modelcontextprotocol/related-task";
15506
15506
  var JSONRPC_VERSION = "2.0";
@@ -17018,7 +17018,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17018
17018
  }
17019
17019
  };
17020
17020
 
17021
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.30.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
17021
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.30.0_@cfworker+json-schema@4.1.1_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
17022
17022
  function isTerminal(status) {
17023
17023
  return status === "completed" || status === "failed" || status === "cancelled";
17024
17024
  }
@@ -17026,7 +17026,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17026
17026
  // ../../node_modules/.pnpm/zod-to-json-schema@3.25.2_zod@4.4.3/node_modules/zod-to-json-schema/dist/esm/parsers/string.js
17027
17027
  var ALPHA_NUMERIC = new Set("ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvxyz0123456789");
17028
17028
 
17029
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.30.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
17029
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.30.0_@cfworker+json-schema@4.1.1_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
17030
17030
  function getMethodLiteral(schema) {
17031
17031
  const shape = getObjectShape(schema);
17032
17032
  const methodSchema = shape?.method;
@@ -17047,7 +17047,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17047
17047
  return result.data;
17048
17048
  }
17049
17049
 
17050
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.30.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
17050
+ // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.30.0_@cfworker+json-schema@4.1.1_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
17051
17051
  var DEFAULT_REQUEST_TIMEOUT_MSEC = 6e4;
17052
17052
  var Protocol = class {
17053
17053
  constructor(_options) {
@@ -18001,7 +18001,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
18001
18001
  return result;
18002
18002
  }
18003
18003
 
18004
- // ../../node_modules/.pnpm/@modelcontextprotocol+ext-apps@1.7.5_@modelcontextprotocol+sdk@1.30.0_zod@4.4.3__react-dom@19_cmaoxxnyjgqtqbxeim6duwrgeu/node_modules/@modelcontextprotocol/ext-apps/dist/src/app.js
18004
+ // ../../node_modules/.pnpm/@modelcontextprotocol+ext-apps@1.7.5_@modelcontextprotocol+sdk@1.30.0_@cfworker+json-schema@4_zach7odob275zkkz2vkisyu32i/node_modules/@modelcontextprotocol/ext-apps/dist/src/app.js
18005
18005
  init_v4();
18006
18006
  init_v4();
18007
18007
  var p = ((X) => typeof __require < "u" ? __require : typeof Proxy < "u" ? new Proxy(X, { get: (Y2, Z) => (typeof __require < "u" ? __require : Y2)[Z] }) : X)(function(X) {
@@ -18467,7 +18467,7 @@ container holding the app. Specify either width or maxWidth, and either height o
18467
18467
  __publicField(_i, "ONE_SHOT_EVENTS", /* @__PURE__ */ new Set(["toolinput", "toolinputpartial", "toolresult", "toolcancelled"]));
18468
18468
  var i = _i;
18469
18469
 
18470
- // ../../node_modules/.pnpm/@modelcontextprotocol+ext-apps@1.7.5_@modelcontextprotocol+sdk@1.30.0_zod@4.4.3__react-dom@19_cmaoxxnyjgqtqbxeim6duwrgeu/node_modules/@modelcontextprotocol/ext-apps/dist/src/server/index.js
18470
+ // ../../node_modules/.pnpm/@modelcontextprotocol+ext-apps@1.7.5_@modelcontextprotocol+sdk@1.30.0_@cfworker+json-schema@4_zach7odob275zkkz2vkisyu32i/node_modules/@modelcontextprotocol/ext-apps/dist/src/server/index.js
18471
18471
  init_v4();
18472
18472
  var r = ((Z) => typeof __require < "u" ? __require : typeof Proxy < "u" ? new Proxy(Z, { get: ($, J) => (typeof __require < "u" ? __require : $)[J] }) : Z)(function(Z) {
18473
18473
  if (typeof __require < "u") return __require.apply(this, arguments);
@@ -1,4 +1,5 @@
1
1
  import type { McpUiDisplayMode, McpUiResourceCsp, McpUiResourcePermissions } from "@modelcontextprotocol/ext-apps";
2
+ import type { OpenAIUiResourceMetadata } from "@openai/mcp-extensions/server";
2
3
  import type { McpResourceDeclaration } from "../types.js";
3
4
  export interface EmitMetaInput {
4
5
  uri: string;
@@ -13,5 +14,7 @@ export interface EmitMetaInput {
13
14
  domain?: string;
14
15
  /** Whether the view requests the host to show a visible border + background. */
15
16
  prefersBorder?: boolean;
17
+ /** ChatGPT resource display metadata written to `_meta["openai/ui"]`. */
18
+ openaiUi?: OpenAIUiResourceMetadata;
16
19
  }
17
20
  export declare function emitMeta(input: EmitMetaInput): McpResourceDeclaration;
@@ -1,4 +1,5 @@
1
1
  import type { McpUiDisplayMode, McpUiResourceCsp, McpUiResourcePermissions } from "@modelcontextprotocol/ext-apps";
2
+ import type { OpenAIUiResourceMetadata } from "@openai/mcp-extensions/server";
2
3
  import type { McpResourceDeclaration } from "../types.js";
3
4
  import type { McpAppArtifact } from "../artifact/index.js";
4
5
  /**
@@ -33,6 +34,8 @@ export interface BuildSelfContainedOptions {
33
34
  domain?: string;
34
35
  /** Whether the view requests the host to show a visible border + background. */
35
36
  prefersBorder?: boolean;
37
+ /** ChatGPT resource display metadata (`_meta["openai/ui"]`). */
38
+ openaiUi?: OpenAIUiResourceMetadata;
36
39
  cssEntry?: string;
37
40
  bridgeRuntimePath?: string;
38
41
  }
@@ -4,7 +4,7 @@ import {
4
4
  emitHtml,
5
5
  emitMeta,
6
6
  getBridgeRuntimePath
7
- } from "../chunk-MTJDRWGD.js";
7
+ } from "../chunk-IDGTP2NI.js";
8
8
  import "../chunk-M4WFJR3C.js";
9
9
  import "../chunk-YJA4H6IB.js";
10
10
  export {
@@ -0,0 +1,9 @@
1
+ // src/react/context.ts
2
+ import { createContext } from "react";
3
+ var McpContext = createContext(null);
4
+ McpContext.displayName = "McpContext";
5
+
6
+ export {
7
+ McpContext
8
+ };
9
+ //# sourceMappingURL=chunk-4SZHJ4MK.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react/context.ts"],"sourcesContent":["import { createContext } from \"react\";\nimport type {\n App,\n McpUiHostContext,\n McpUiToolInputNotification,\n McpUiToolInputPartialNotification,\n McpUiToolResultNotification,\n} from \"@modelcontextprotocol/ext-apps\";\n\n/**\n * Shared per-View context. The bridge populates this on each mount/update,\n * so consumers can synchronously read the initial values (no event subscription\n * race) and the React tree re-renders when the bridge calls `update()`.\n */\nexport interface McpContextValue {\n app: App;\n toolInput?: McpUiToolInputNotification[\"params\"];\n toolInputPartial?: McpUiToolInputPartialNotification[\"params\"];\n toolResult?: McpUiToolResultNotification[\"params\"];\n hostContext?: McpUiHostContext;\n}\n\nexport const McpContext = createContext<McpContextValue | null>(null);\nMcpContext.displayName = \"McpContext\";\n"],"mappings":";AAAA,SAAS,qBAAqB;AAsBvB,IAAM,aAAa,cAAsC,IAAI;AACpE,WAAW,cAAc;","names":[]}
@@ -579,4 +579,4 @@ export {
579
579
  startDevHost,
580
580
  sandboxProxyHtml
581
581
  };
582
- //# sourceMappingURL=chunk-KWVGW5Q7.js.map
582
+ //# sourceMappingURL=chunk-GN2IQYV3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/dev/index.ts"],"sourcesContent":["/**\n * A local MCP Apps host, so a View can be developed without installing a\n * real host and restarting it on every change.\n *\n * It is a genuine host, not a preview shim: two origins as the spec requires\n * (§8.4), a sandbox proxy that injects the sidecar's CSP and permissions, the\n * full `ui/initialize` handshake, and tool-input/tool-result delivery. A View\n * that works here works in Claude, and one that reaches for `eval` fails here\n * for the same reason it would fail there.\n *\n * ```ts\n * const host = await startDevHost({ htmlPath: \"dist/view.html\", fixtures });\n * console.log(host.hostUrl);\n * ```\n */\nimport { existsSync } from \"node:fs\";\nimport { readFile } from \"node:fs/promises\";\nimport { createServer, type Server } from \"node:http\";\nimport { fileURLToPath } from \"node:url\";\nimport type { McpResourceDeclaration } from \"../types.js\";\nimport { escapeInlineScript, serializeInlineScriptValue } from \"../html.js\";\n\nexport interface DevHostOptions {\n /** Built View HTML. Its `.meta.json` sidecar is read alongside. */\n htmlPath: string;\n /**\n * Named samples, each becoming a button in the host chrome.\n *\n * Without `callTool` the value is delivered straight through as\n * `structuredContent`. With `callTool` it is the tool's *arguments*, and\n * what the view receives is whatever the real tool returns.\n */\n fixtures?: Record<string, unknown>;\n /**\n * Run a real tool. Supplied when the developer points `dev` at their server,\n * and used both for the fixture buttons and for `tools/call` requests the\n * view makes itself — so app-only tools behave as they will in production.\n */\n callTool?: (name: string, args: unknown) => Promise<unknown>;\n /** Arguments delivered as `ui/notifications/tool-input`. */\n toolInput?: Record<string, unknown>;\n /** Shown in the host chrome. Defaults to the sidecar's resource name. */\n toolName?: string;\n /** Defaults to 5179, incrementing until a free port is found. */\n hostPort?: number;\n /** Defaults to `hostPort + 1`, incrementing until a free port is found. */\n sandboxPort?: number;\n}\n\nexport interface DevHost {\n hostUrl: string;\n sandboxUrl: string;\n /** Tell connected browsers to reload — call after a rebuild. */\n reload(): void;\n close(): Promise<void>;\n}\n\nfunction escapeHtml(value: string): string {\n return value.replace(\n /[&<>\"']/g,\n (character) =>\n ({ \"&\": \"&amp;\", \"<\": \"&lt;\", \">\": \"&gt;\", '\"': \"&quot;\", \"'\": \"&#39;\" })[character]!,\n );\n}\n\nconst runtimeCache = new Map<string, Promise<string>>();\n\n/**\n * Load one of the browser-side runtimes (`host-entry`, `sandbox-entry`) as a\n * string to inline into a page.\n *\n * Installed, the built IIFE is a declared package export, so ask the module\n * resolver for it. Walking from `import.meta.url` breaks the moment a bundler\n * hoists this module into a shared chunk at a different depth — which is what\n * tsup does, leaving it looking in dist/ while the file sits in dist/dev/.\n */\nfunction loadRuntime(name: \"host-entry\" | \"sandbox-entry\"): Promise<string> {\n const cached = runtimeCache.get(name);\n if (cached) return cached;\n const loading = (async () => {\n try {\n return await readFile(\n fileURLToPath(import.meta.resolve(`mountly-mcp/dev/${name}.js`)),\n \"utf8\",\n );\n } catch {\n // Running from source in this repo, where nothing is built yet.\n }\n const entry = fileURLToPath(new URL(`./${name}.ts`, import.meta.url));\n if (!existsSync(entry)) {\n throw new Error(\n `mountly-mcp dev: ${name} runtime missing. Expected the packaged 'mountly-mcp/dev/${name}.js' export, or ${entry} when running from source.`,\n );\n }\n const { build } = await import(\"vite\");\n const result = await build({\n configFile: false,\n logLevel: \"silent\",\n build: {\n write: false,\n target: \"es2022\",\n minify: false,\n lib: { entry, formats: [\"iife\"], name: \"__mountlyMcpDevRuntime\" },\n },\n });\n const outputs = Array.isArray(result) ? result : [result];\n const chunk = outputs\n .flatMap((output) => (\"output\" in output ? output.output : []))\n .find((item) => item.type === \"chunk\");\n if (!chunk || chunk.type !== \"chunk\")\n throw new Error(`mountly-mcp dev: failed to bundle ${name}`);\n return chunk.code;\n })();\n runtimeCache.set(name, loading);\n return loading;\n}\n\nfunction listenOnFreePort(server: Server, port: number): Promise<number> {\n return new Promise((resolvePort, reject) => {\n const attempt = (candidate: number): void => {\n // Listeners are paired and torn down together: `listen()`'s callback form\n // leaves a stale one attached across retries, which then resolves with\n // the port that was already taken.\n const onError = (error: NodeJS.ErrnoException): void => {\n server.removeListener(\"listening\", onListening);\n // A linear probe is enough for a dev tool on a developer's laptop.\n if (error.code === \"EADDRINUSE\" && candidate < port + 50) attempt(candidate + 1);\n else reject(error);\n };\n const onListening = (): void => {\n server.removeListener(\"error\", onError);\n const address = server.address();\n resolvePort(typeof address === \"object\" && address !== null ? address.port : candidate);\n };\n server.once(\"error\", onError);\n server.once(\"listening\", onListening);\n server.listen(candidate);\n };\n attempt(port);\n });\n}\n\nexport async function startDevHost(options: DevHostOptions): Promise<DevHost> {\n const { htmlPath, fixtures = {}, toolInput = {}, callTool } = options;\n\n const metaPath = `${htmlPath}.meta.json`;\n const initialMeta = JSON.parse(await readFile(metaPath, \"utf8\")) as McpResourceDeclaration;\n const toolName = options.toolName ?? initialMeta.name;\n const [hostRuntime, sandboxRuntime] = await Promise.all([\n loadRuntime(\"host-entry\"),\n loadRuntime(\"sandbox-entry\"),\n ]);\n\n // Bumped on reload(); the browser polls it and reloads when it changes. The\n // View HTML is read per request rather than baked into the page, so a\n // rebuild is picked up with no server restart.\n let version = 1;\n\n // The two servers reference each other's origin, and each only needs the\n // other's once a request arrives — so the sandbox reads this after the host\n // has bound its port.\n let hostOrigin = \"\";\n\n const sandboxServer = createServer((_req, res) => {\n res.writeHead(200, { \"Content-Type\": \"text/html; charset=utf-8\", \"Cache-Control\": \"no-store\" });\n res.end(sandboxProxyShell(hostOrigin, sandboxRuntime));\n });\n const sandboxPort = await listenOnFreePort(\n sandboxServer,\n options.sandboxPort ?? (options.hostPort ?? 5179) + 1,\n );\n const sandboxOrigin = `http://localhost:${sandboxPort}`;\n\n const hostServer = createServer(async (req, res) => {\n const path = new URL(req.url ?? \"/\", \"http://localhost\").pathname;\n const send = (status: number, type: string, body: string | Buffer): void => {\n res.writeHead(status, { \"Content-Type\": type, \"Cache-Control\": \"no-store\" });\n res.end(body);\n };\n try {\n if (path === \"/call\" && req.method === \"POST\") {\n if (!callTool) return send(400, \"text/plain\", \"no server connected\");\n const body = await new Promise<string>((done, fail) => {\n let raw = \"\";\n req.on(\"data\", (chunk) => (raw += chunk));\n req.on(\"end\", () => done(raw));\n req.on(\"error\", fail);\n });\n const { name, arguments: args } = JSON.parse(body) as { name: string; arguments?: unknown };\n // Tool failures are the view's problem to render, not the dev server's\n // to crash on, so they come back as a normal result.\n const result = await callTool(name, args).catch((error: unknown) => ({\n isError: true,\n content: [{ type: \"text\", text: error instanceof Error ? error.message : String(error) }],\n }));\n return send(200, \"application/json\", JSON.stringify(result));\n }\n if (path === \"/\") return send(200, \"text/html; charset=utf-8\", hostPage());\n if (path === \"/host.js\") return send(200, \"text/javascript; charset=utf-8\", hostRuntime);\n if (path === \"/version\") return send(200, \"application/json\", String(version));\n if (path === \"/view.html\")\n return send(200, \"text/plain; charset=utf-8\", await readFile(htmlPath, \"utf8\"));\n if (path === \"/view.meta.json\")\n return send(200, \"application/json\", await readFile(metaPath, \"utf8\"));\n send(404, \"text/plain\", \"not found\");\n } catch (error) {\n // A rebuild in flight can briefly leave the file missing; say so rather\n // than crashing the dev server.\n send(500, \"text/plain\", error instanceof Error ? error.message : String(error));\n }\n });\n let hostPort: number;\n try {\n hostPort = await listenOnFreePort(hostServer, options.hostPort ?? 5179);\n } catch (caught) {\n await new Promise<void>((done) => sandboxServer.close(() => done()));\n throw caught;\n }\n hostOrigin = `http://localhost:${hostPort}`;\n\n function hostPage(): string {\n return devHostHtml({\n toolName,\n sandboxOrigin,\n fixtures,\n toolInput,\n uri: initialMeta.uri,\n hasServer: callTool !== undefined,\n });\n }\n\n return {\n hostUrl: hostOrigin,\n sandboxUrl: sandboxOrigin,\n reload() {\n version += 1;\n },\n async close() {\n await Promise.all([\n new Promise<void>((done) => hostServer.close(() => done())),\n new Promise<void>((done) => sandboxServer.close(() => done())),\n ]);\n },\n };\n}\n\ninterface HostPageOptions {\n toolName: string;\n sandboxOrigin: string;\n fixtures: Record<string, unknown>;\n toolInput: Record<string, unknown>;\n uri: string;\n hasServer: boolean;\n}\n\nfunction devHostHtml(options: HostPageOptions): string {\n const { toolName, sandboxOrigin, fixtures, toolInput, uri, hasServer } = options;\n const buttons = Object.keys(fixtures)\n .map(\n (name, index) =>\n `<button class=\"chip\" type=\"button\" data-fixture=\"${escapeHtml(name)}\"${index === 0 ? ' data-active=\"true\"' : \"\"}>${escapeHtml(name)}</button>`,\n )\n .join(\"\\n\");\n const firstFixture = Object.keys(fixtures)[0] ?? \"sample\";\n\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n<title>${escapeHtml(toolName)} · Mountly MCP</title>\n<style>\n :root {\n color-scheme: light;\n --bg: #f5f5f5;\n --bg-chat: #efefef;\n --fg: #171717;\n --muted: #737373;\n --accent: #0f766e;\n --accent-soft: #ccfbf1;\n --border: #e5e5e5;\n --surface: #ffffff;\n --user-bg: #171717;\n --user-fg: #fafafa;\n --assistant-fg: #171717;\n --composer: #ffffff;\n --shadow: 0 1px 2px rgb(0 0 0 / 4%), 0 8px 24px rgb(0 0 0 / 6%);\n }\n :root[data-theme=\"dark\"] {\n color-scheme: dark;\n --bg: #0a0a0a;\n --bg-chat: #111111;\n --fg: #f5f5f5;\n --muted: #a3a3a3;\n --accent: #2dd4bf;\n --accent-soft: #134e4a;\n --border: #262626;\n --surface: #171717;\n --user-bg: #e5e5e5;\n --user-fg: #0a0a0a;\n --assistant-fg: #f5f5f5;\n --composer: #171717;\n --shadow: 0 1px 2px rgb(0 0 0 / 35%), 0 12px 32px rgb(0 0 0 / 35%);\n }\n * { box-sizing: border-box; }\n html, body { height: 100%; }\n body {\n margin: 0;\n background: var(--bg);\n color: var(--fg);\n font: 15px/1.5 \"Segoe UI\", \"Helvetica Neue\", system-ui, sans-serif;\n }\n .app {\n display: grid;\n grid-template-rows: auto 1fr auto;\n min-height: 100%;\n max-width: 880px;\n margin: 0 auto;\n }\n .topbar {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 12px 18px;\n border-bottom: 1px solid var(--border);\n background: color-mix(in srgb, var(--surface) 92%, transparent);\n backdrop-filter: blur(10px);\n position: sticky;\n top: 0;\n z-index: 3;\n }\n .model {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n min-width: 0;\n }\n .avatar {\n width: 28px;\n height: 28px;\n border-radius: 8px;\n background:\n linear-gradient(135deg, var(--accent-soft), transparent 60%),\n var(--surface);\n border: 1px solid var(--border);\n flex: 0 0 auto;\n }\n .model h1 {\n margin: 0;\n font-size: 14px;\n font-weight: 600;\n letter-spacing: -0.01em;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .model .sub {\n color: var(--muted);\n font-size: 12px;\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .spacer { flex: 1; }\n .icon-btn {\n appearance: none;\n border: 1px solid var(--border);\n background: var(--surface);\n color: var(--fg);\n width: 34px;\n height: 34px;\n border-radius: 10px;\n font: inherit;\n cursor: pointer;\n }\n .icon-btn:hover { border-color: var(--accent); color: var(--accent); }\n .chat {\n background:\n radial-gradient(900px 420px at 50% -8%, var(--accent-soft), transparent 55%),\n var(--bg-chat);\n padding: 28px 18px 18px;\n display: flex;\n flex-direction: column;\n gap: 22px;\n overflow: auto;\n }\n .turn {\n display: grid;\n grid-template-columns: 32px 1fr;\n gap: 12px;\n align-items: start;\n max-width: 720px;\n width: 100%;\n }\n .turn.user {\n margin-left: auto;\n grid-template-columns: 1fr 32px;\n }\n .turn.user .who { order: 2; }\n .turn.user .body { order: 1; text-align: right; }\n .who {\n width: 32px;\n height: 32px;\n border-radius: 999px;\n display: grid;\n place-items: center;\n font-size: 11px;\n font-weight: 700;\n letter-spacing: 0.04em;\n border: 1px solid var(--border);\n background: var(--surface);\n color: var(--muted);\n }\n .turn.user .who {\n background: var(--user-bg);\n color: var(--user-fg);\n border-color: transparent;\n }\n .bubble {\n display: inline-block;\n text-align: left;\n background: var(--surface);\n border: 1px solid var(--border);\n border-radius: 18px;\n padding: 12px 14px;\n box-shadow: var(--shadow);\n color: var(--assistant-fg);\n }\n .turn.user .bubble {\n background: var(--user-bg);\n color: var(--user-fg);\n border-color: transparent;\n }\n .bubble p { margin: 0; }\n .artifact {\n margin-top: 10px;\n border: 1px solid var(--border);\n border-radius: 16px;\n overflow: hidden;\n background: var(--surface);\n box-shadow: var(--shadow);\n text-align: left;\n }\n .artifact-bar {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 12px;\n border-bottom: 1px solid var(--border);\n background: color-mix(in srgb, var(--bg-chat) 55%, var(--surface));\n color: var(--muted);\n font-size: 12px;\n }\n .artifact-bar .dot {\n width: 7px;\n height: 7px;\n border-radius: 50%;\n background: var(--accent);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);\n }\n iframe {\n display: block;\n width: 100%;\n height: 420px;\n border: 0;\n background: #fff;\n }\n .composer {\n border-top: 1px solid var(--border);\n background: color-mix(in srgb, var(--composer) 94%, transparent);\n backdrop-filter: blur(10px);\n padding: 12px 18px 16px;\n position: sticky;\n bottom: 0;\n }\n .composer-box {\n border: 1px solid var(--border);\n background: var(--surface);\n border-radius: 18px;\n padding: 10px 12px 12px;\n box-shadow: var(--shadow);\n }\n .composer-hint {\n color: var(--muted);\n font-size: 13px;\n margin: 0 0 10px;\n }\n .samples {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n align-items: center;\n }\n .samples .label {\n color: var(--muted);\n font-size: 12px;\n margin-right: 2px;\n }\n #fixtures { display: flex; gap: 8px; flex-wrap: wrap; align-items: center; }\n .chip {\n appearance: none;\n border: 1px solid var(--border);\n background: color-mix(in srgb, var(--bg-chat) 40%, var(--surface));\n color: var(--fg);\n padding: 7px 12px;\n border-radius: 999px;\n font: inherit;\n font-size: 13px;\n cursor: pointer;\n }\n .chip:hover { border-color: var(--accent); color: var(--accent); }\n .chip[data-active=\"true\"] {\n background: var(--accent);\n color: #fff;\n border-color: var(--accent);\n }\n :root[data-theme=\"dark\"] .chip[data-active=\"true\"] { color: #042f2e; }\n details.debug {\n margin-top: 10px;\n border: 1px dashed var(--border);\n border-radius: 14px;\n background: transparent;\n overflow: hidden;\n }\n details.debug > summary {\n cursor: pointer;\n list-style: none;\n padding: 10px 12px;\n font-size: 11px;\n font-weight: 600;\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--muted);\n }\n details.debug > summary::-webkit-details-marker { display: none; }\n .debug-body {\n display: grid;\n grid-template-columns: 1fr 1fr;\n gap: 0;\n border-top: 1px solid var(--border);\n }\n .panel { padding: 12px; min-width: 0; }\n .panel + .panel { border-left: 1px solid var(--border); }\n .panel h2 {\n margin: 0 0 8px;\n font-size: 11px;\n text-transform: uppercase;\n letter-spacing: 0.08em;\n color: var(--muted);\n }\n pre {\n margin: 0;\n padding: 10px;\n border-radius: 8px;\n background: color-mix(in srgb, var(--bg-chat) 80%, transparent);\n font: 12px/1.45 ui-monospace, Menlo, Consolas, monospace;\n overflow: auto;\n max-height: 180px;\n }\n .log {\n font: 11px/1.5 ui-monospace, Menlo, Consolas, monospace;\n color: var(--muted);\n white-space: pre-wrap;\n max-height: 180px;\n overflow: auto;\n }\n @media (max-width: 720px) {\n .debug-body { grid-template-columns: 1fr; }\n .panel + .panel { border-left: 0; border-top: 1px solid var(--border); }\n .turn, .turn.user { grid-template-columns: 28px 1fr; }\n .turn.user .who { order: 0; }\n .turn.user .body { order: 0; text-align: left; }\n }\n</style>\n</head>\n<body>\n<div class=\"app\">\n <header class=\"topbar\">\n <div class=\"model\">\n <div class=\"avatar\" aria-hidden=\"true\"></div>\n <div>\n <h1>Mountly · ${escapeHtml(toolName)}</h1>\n <div class=\"sub\">${escapeHtml(uri)}</div>\n </div>\n </div>\n <span class=\"spacer\"></span>\n <button class=\"icon-btn\" id=\"theme\" type=\"button\" title=\"Toggle theme\" aria-label=\"Toggle theme\">◐</button>\n <button class=\"icon-btn\" id=\"teardown\" type=\"button\" title=\"Teardown\" aria-label=\"Teardown\">×</button>\n </header>\n\n <main class=\"chat\">\n <div class=\"turn user\">\n <div class=\"who\" aria-hidden=\"true\">You</div>\n <div class=\"body\">\n <div class=\"bubble\">\n <p>Call <strong>${escapeHtml(toolName)}</strong> with sample “${escapeHtml(firstFixture)}”.</p>\n </div>\n </div>\n </div>\n\n <div class=\"turn assistant\">\n <div class=\"who\" aria-hidden=\"true\">AI</div>\n <div class=\"body\">\n <div class=\"bubble\">\n <p>Here’s the interactive result from that tool.</p>\n <div class=\"artifact\">\n <div class=\"artifact-bar\"><span class=\"dot\" aria-hidden=\"true\"></span> MCP App · inline</div>\n <iframe id=\"sandbox\" sandbox=\"allow-scripts allow-same-origin\" title=\"mountly-mcp sandbox proxy\"></iframe>\n </div>\n </div>\n </div>\n </div>\n </main>\n\n <footer class=\"composer\">\n <div class=\"composer-box\">\n <p class=\"composer-hint\">Ask another sample — same as switching fixtures in a real host.</p>\n <div class=\"samples\">\n <span class=\"label\">Samples</span>\n <div id=\"fixtures\">${buttons || '<span class=\"label\">No fixtures — add mcp.fixtures.json</span>'}</div>\n </div>\n <details class=\"debug\">\n <summary>Protocol debug</summary>\n <div class=\"debug-body\">\n <section class=\"panel\">\n <h2>Last structuredContent</h2>\n <pre id=\"payload\">(none yet)</pre>\n </section>\n <section class=\"panel\">\n <h2>Channel log</h2>\n <div class=\"log\" id=\"log\"></div>\n </section>\n </div>\n </details>\n </div>\n </footer>\n</div>\n<script>\nglobalThis.__mountlyMcpDevHost__ = ${serializeInlineScriptValue({\n toolName,\n sandboxOrigin,\n fixtures,\n toolInput,\n uri,\n hasServer,\n sandboxPath: \"/sandbox-proxy.html\",\n })};\nconst HAS_SERVER = ${hasServer ? \"true\" : \"false\"};\n</script>\n<script src=\"/host.js\"></script>\n</body>\n</html>\n`;\n}\n/**\n * The proxy document: a shell around the compiled sandbox runtime.\n *\n * Kept separate from {@link sandboxProxyHtml} so the dev server can load the\n * runtime once at startup and still stamp in the host origin per request,\n * which it only learns after binding a port.\n */\nfunction sandboxProxyShell(hostOrigin: string, runtime: string): string {\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>mountly-mcp sandbox proxy</title>\n<style>\n html, body { margin: 0; padding: 0; width: 100%; height: 100%; background: transparent; }\n #inner { width: 100%; height: 100%; border: 0; background: white; }\n</style>\n</head>\n<body>\n<script>globalThis.__mountlyMcpSandbox__ = ${serializeInlineScriptValue({ hostOrigin })};</script>\n<script>${escapeInlineScript(runtime)}</script>\n</body>\n</html>\n`;\n}\n\n/**\n * The MCP Apps sandbox proxy document (spec §8.4), ready to serve from an\n * origin distinct from the host page.\n *\n * Exported because this is the security boundary — it builds the CSP from the\n * sidecar and decides what the view may execute and reach. Anything standing\n * up a host, including the example harnesses in this repo, should serve this\n * rather than keep a copy that drifts from it.\n */\nexport async function sandboxProxyHtml(hostOrigin: string): Promise<string> {\n return sandboxProxyShell(hostOrigin, await loadRuntime(\"sandbox-entry\"));\n}\n\nexport { connectMcpServer, type ConnectedMcpServer } from \"./connect-server.js\";\n"],"mappings":"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;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,2BAuTL,WAAW,QAAQ,CAAC;AAAA,2BACjB,WAAW,GAAG,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,4BAad,WAAW,QAAQ,CAAC,+BAA0B,WAAW,YAAY,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6BAwBrE,WAAW,qEAAgE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,qCAmBnE,2BAA2B;AAAA,IAC5D;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa;AAAA,EACf,CAAC,CAAC;AAAA,qBACiB,YAAY,SAAS,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAMjD;AAQA,SAAS,kBAAkB,YAAoB,SAAyB;AACtE,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6CAWoC,2BAA2B,EAAE,WAAW,CAAC,CAAC;AAAA,UAC7E,mBAAmB,OAAO,CAAC;AAAA;AAAA;AAAA;AAIrC;AAWA,eAAsB,iBAAiB,YAAqC;AAC1E,SAAO,kBAAkB,YAAY,MAAM,YAAY,eAAe,CAAC;AACzE;","names":[]}
@@ -1,12 +1,10 @@
1
+ import {
2
+ McpContext
3
+ } from "./chunk-4SZHJ4MK.js";
1
4
  import {
2
5
  publishMcpView
3
6
  } from "./chunk-5L3QHYWK.js";
4
7
 
5
- // src/react/context.ts
6
- import { createContext } from "react";
7
- var McpContext = createContext(null);
8
- McpContext.displayName = "McpContext";
9
-
10
8
  // src/react/hooks.ts
11
9
  import { useContext } from "react";
12
10
  import {
@@ -100,7 +98,6 @@ function createMcpView(Component, options) {
100
98
  }
101
99
 
102
100
  export {
103
- McpContext,
104
101
  useMcpApp,
105
102
  useToolInput,
106
103
  useToolInputPartial,
@@ -116,4 +113,4 @@ export {
116
113
  useAutoResize,
117
114
  createMcpView
118
115
  };
119
- //# sourceMappingURL=chunk-SXF23BZD.js.map
116
+ //# sourceMappingURL=chunk-HIAIQVK5.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/react/hooks.ts","../src/react/create-view.ts"],"sourcesContent":["import { useContext } from \"react\";\nimport {\n useAutoResize as useAutoResizeBase,\n useDocumentTheme as useDocumentThemeBase,\n useHostFonts as useHostFontsBase,\n useHostStyleVariables as useHostStyleVariablesBase,\n useHostStyles as useHostStylesBase,\n} from \"@modelcontextprotocol/ext-apps/react\";\nimport type {\n App,\n McpUiDisplayMode,\n McpUiHostContext,\n McpUiUpdateModelContextRequest,\n} from \"@modelcontextprotocol/ext-apps\";\nimport { McpContext } from \"./context.js\";\n\nfunction useEnsuredContext() {\n const ctx = useContext(McpContext);\n if (!ctx) {\n throw new Error(\n \"mountly-mcp/react: useMcpApp/useToolInput/etc must be used inside a View wrapped with createMcpView().\",\n );\n }\n return ctx;\n}\n\n/** The view-side ext-apps App. Use this to call server tools, send messages, open links, etc. */\nexport function useMcpApp(): App {\n return useEnsuredContext().app;\n}\n\n/** Complete notification params; read the tool arguments from `.arguments`. */\nexport function useToolInput<T = unknown>(): T | undefined {\n return useEnsuredContext().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 * fields may be missing or change between notifications, so render loading\n * states from it, never critical operations. Undefined once the complete\n * {@link useToolInput} arrives.\n */\nexport function useToolInputPartial<T = unknown>(): Partial<T> | undefined {\n return useEnsuredContext().toolInputPartial as Partial<T> | undefined;\n}\n\n/** Complete MCP tool result; structured data is available at `.structuredContent`. */\nexport function useToolResult<T = unknown>(): T | undefined {\n return useEnsuredContext().toolResult as T | undefined;\n}\n\n/** Latest host context snapshot (theme, styles, displayMode, container dimensions, etc.). */\nexport function useHostContext(): McpUiHostContext | undefined {\n return useEnsuredContext().hostContext;\n}\n\n/** Current display mode reported by the host. */\nexport function useDisplayMode(): McpUiDisplayMode | undefined {\n return useHostContext()?.displayMode;\n}\n\n/**\n * Request a display mode change, resolving to the mode the host actually\n * applied — which the spec allows to differ from the one asked for.\n *\n * Modes the host didn't list in `availableDisplayModes` are never requested\n * (the spec requires views to check first); the current mode comes back\n * unchanged instead.\n */\nexport function useRequestDisplayMode(): (\n mode: McpUiDisplayMode,\n) => Promise<McpUiDisplayMode | undefined> {\n const { app, hostContext } = useEnsuredContext();\n return async (mode) => {\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\n/** Apply host CSS variables (and react to host-context changes). */\nexport function useHostStyleVariables(): void {\n const { app, hostContext } = useEnsuredContext();\n useHostStyleVariablesBase(app, hostContext ?? null);\n}\n\n/** Apply host-provided `@font-face`/`@import` rules. */\nexport function useHostFonts(): void {\n const { app, hostContext } = useEnsuredContext();\n useHostFontsBase(app, hostContext ?? null);\n}\n\n/** One-shot helper: apply both host style variables and host fonts. */\nexport function useHostStyles(): void {\n const { app, hostContext } = useEnsuredContext();\n useHostStylesBase(app, hostContext ?? null);\n}\n\n/** Track the document theme reactively. */\nexport function useDocumentTheme() {\n return useDocumentThemeBase();\n}\n\n/**\n * Return a function that calls `app.updateModelContext(params)`.\n *\n * Context updates are available to the model in future turns without\n * triggering an immediate response. Only the last update is kept.\n */\nexport function useUpdateModelContext(): (\n params: McpUiUpdateModelContextRequest[\"params\"],\n) => Promise<void> {\n const { app } = useEnsuredContext();\n return async (params) => {\n await app.updateModelContext(params);\n };\n}\n\n/** Manually control auto-resize (rarely needed — the bridge calls setupSizeChangedNotifications by default). */\nexport function useAutoResize(...args: Parameters<typeof useAutoResizeBase>) {\n return useAutoResizeBase(...args);\n}\n","/**\n * Wraps a React component as an MCP Apps View and publishes it for the bridge.\n * One call is enough — no manual global assign.\n */\nimport { createElement, type ComponentType } from \"react\";\nimport { createWidget } from \"mountly-react\";\nimport type { AdapterOptions } from \"mountly/adapter\";\nimport type { McpView, McpViewProps } from \"../types.js\";\nimport { publishMcpView } from \"../publish.js\";\nimport { McpContext, type McpContextValue } from \"./context.js\";\n\nexport function createMcpView<P extends object>(\n Component: ComponentType<P>,\n options?: AdapterOptions,\n): McpView {\n const Wrapped = (props: P & McpViewProps) => {\n const { mcp, toolInput, toolInputPartial, toolResult, hostContext, ...rest } =\n props as McpViewProps & P;\n const value: McpContextValue = {\n app: mcp,\n toolInput,\n toolInputPartial,\n toolResult,\n hostContext,\n };\n return createElement(\n McpContext.Provider,\n { value },\n createElement(Component as ComponentType, rest as P),\n );\n };\n return publishMcpView(createWidget(Wrapped as ComponentType<P & McpViewProps>, options));\n}\n"],"mappings":";;;;;;;;AAAA,SAAS,kBAAkB;AAC3B;AAAA,EACE,iBAAiB;AAAA,EACjB,oBAAoB;AAAA,EACpB,gBAAgB;AAAA,EAChB,yBAAyB;AAAA,EACzB,iBAAiB;AAAA,OACZ;AASP,SAAS,oBAAoB;AAC3B,QAAM,MAAM,WAAW,UAAU;AACjC,MAAI,CAAC,KAAK;AACR,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,YAAiB;AAC/B,SAAO,kBAAkB,EAAE;AAC7B;AAGO,SAAS,eAA2C;AACzD,SAAO,kBAAkB,EAAE;AAC7B;AASO,SAAS,sBAA2D;AACzE,SAAO,kBAAkB,EAAE;AAC7B;AAGO,SAAS,gBAA4C;AAC1D,SAAO,kBAAkB,EAAE;AAC7B;AAGO,SAAS,iBAA+C;AAC7D,SAAO,kBAAkB,EAAE;AAC7B;AAGO,SAAS,iBAA+C;AAC7D,SAAO,eAAe,GAAG;AAC3B;AAUO,SAAS,wBAE2B;AACzC,QAAM,EAAE,KAAK,YAAY,IAAI,kBAAkB;AAC/C,SAAO,OAAO,SAAS;AACrB,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;AAGO,SAAS,wBAA8B;AAC5C,QAAM,EAAE,KAAK,YAAY,IAAI,kBAAkB;AAC/C,4BAA0B,KAAK,eAAe,IAAI;AACpD;AAGO,SAAS,eAAqB;AACnC,QAAM,EAAE,KAAK,YAAY,IAAI,kBAAkB;AAC/C,mBAAiB,KAAK,eAAe,IAAI;AAC3C;AAGO,SAAS,gBAAsB;AACpC,QAAM,EAAE,KAAK,YAAY,IAAI,kBAAkB;AAC/C,oBAAkB,KAAK,eAAe,IAAI;AAC5C;AAGO,SAAS,mBAAmB;AACjC,SAAO,qBAAqB;AAC9B;AAQO,SAAS,wBAEG;AACjB,QAAM,EAAE,IAAI,IAAI,kBAAkB;AAClC,SAAO,OAAO,WAAW;AACvB,UAAM,IAAI,mBAAmB,MAAM;AAAA,EACrC;AACF;AAGO,SAAS,iBAAiB,MAA4C;AAC3E,SAAO,kBAAkB,GAAG,IAAI;AAClC;;;ACvHA,SAAS,qBAAyC;AAClD,SAAS,oBAAoB;AAMtB,SAAS,cACd,WACA,SACS;AACT,QAAM,UAAU,CAAC,UAA4B;AAC3C,UAAM,EAAE,KAAK,WAAW,kBAAkB,YAAY,aAAa,GAAG,KAAK,IACzE;AACF,UAAM,QAAyB;AAAA,MAC7B,KAAK;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AACA,WAAO;AAAA,MACL,WAAW;AAAA,MACX,EAAE,MAAM;AAAA,MACR,cAAc,WAA4B,IAAS;AAAA,IACrD;AAAA,EACF;AACA,SAAO,eAAe,aAAa,SAA4C,OAAO,CAAC;AACzF;","names":[]}
@@ -99,6 +99,7 @@ function emitMeta(input) {
99
99
  displayModes: input.displayModes ?? ["inline"],
100
100
  _meta: { ui }
101
101
  };
102
+ if (input.openaiUi !== void 0) decl._meta["openai/ui"] = input.openaiUi;
102
103
  if (input.description !== void 0) decl.description = input.description;
103
104
  return decl;
104
105
  }
@@ -136,7 +137,8 @@ async function writeResource(options) {
136
137
  csp: options.mode === "cdn" ? mergeCdnIntoCsp(options.csp, options.cdn) : options.csp,
137
138
  permissions: options.permissions,
138
139
  domain: options.domain,
139
- prefersBorder: options.prefersBorder
140
+ prefersBorder: options.prefersBorder,
141
+ openaiUi: options.openaiUi
140
142
  });
141
143
  const config = {
142
144
  awaitToolResult: declaration.awaitToolResult,
@@ -193,4 +195,4 @@ export {
193
195
  buildMcpResourceFromSource,
194
196
  buildMcpResource
195
197
  };
196
- //# sourceMappingURL=chunk-MTJDRWGD.js.map
198
+ //# sourceMappingURL=chunk-IDGTP2NI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/build/index.ts","../src/build/emit-html.ts","../src/build/emit-meta.ts"],"sourcesContent":["import { readFile, writeFile } from \"node:fs/promises\";\nimport { fileURLToPath } from \"node:url\";\nimport { resolve } from \"node:path\";\nimport type {\n McpUiDisplayMode,\n McpUiResourceCsp,\n McpUiResourcePermissions,\n} from \"@modelcontextprotocol/ext-apps\";\nimport type { OpenAIUiResourceMetadata } from \"@openai/mcp-extensions/server\";\nimport type { McpResourceDeclaration } from \"../types.js\";\nimport type { McpAppArtifact } from \"../artifact/index.js\";\nimport { emitHtml } from \"./emit-html.js\";\nimport { emitMeta } from \"./emit-meta.js\";\n\n/**\n * Absolute path to the iframe bridge runtime shipped with this package.\n *\n * `buildMcpResource` inlines this file's contents into the emitted HTML so\n * the View can speak the MCP Apps postMessage protocol. Use this when you\n * need to point another build tool at the runtime, or when you want a\n * stable, version-pinned alternative to passing `bridgeRuntimePath` yourself.\n *\n * Resolves relative to the installed package, so it works identically in\n * monorepos and in projects that installed mountly-mcp from npm.\n */\nexport function getBridgeRuntimePath(): string {\n // Ask the module resolver rather than walking up from __dirname: the subpath\n // is a declared export, so this holds wherever a bundler puts this file.\n return fileURLToPath(import.meta.resolve(\"mountly-mcp/bridge/iframe-entry.js\"));\n}\n\nfunction defaultBridgeRuntimePath(): string {\n return getBridgeRuntimePath();\n}\n\nexport interface BuildSelfContainedOptions {\n entry: string;\n uri: string;\n name: string;\n output: string;\n description?: string;\n awaitToolResult?: boolean;\n displayModes?: ReadonlyArray<McpUiDisplayMode>;\n /**\n * Render progressive states from `ui/notifications/tool-input-partial`.\n * Off by default — see `RunBridgeOptions.streamToolInput`.\n */\n streamToolInput?: boolean;\n csp?: McpUiResourceCsp;\n /** Sandbox permissions requested by the view (camera/microphone/geolocation/clipboardWrite). */\n permissions?: McpUiResourcePermissions;\n /** Dedicated sandbox origin (host-dependent format). */\n domain?: string;\n /** Whether the view requests the host to show a visible border + background. */\n prefersBorder?: boolean;\n /** ChatGPT resource display metadata (`_meta[\"openai/ui\"]`). */\n openaiUi?: OpenAIUiResourceMetadata;\n cssEntry?: string;\n bridgeRuntimePath?: string;\n}\n\nexport interface BuildCdnOptions extends BuildSelfContainedOptions {\n cdn: { jsUrl: string; cssUrl: string };\n}\n\nexport type BuildOptions = BuildSelfContainedOptions | BuildCdnOptions;\n\nexport interface BuildResult {\n htmlPath: string;\n metaPath: string;\n declaration: McpResourceDeclaration;\n artifact: McpAppArtifact;\n}\n\nexport interface BuildFromSourceOptions extends Omit<\n BuildSelfContainedOptions,\n \"entry\" | \"cssEntry\"\n> {\n /** The bundled View JS. */\n js: string;\n /** The View's CSS, if it has any. */\n css?: string;\n}\n\n/**\n * Same as {@link buildMcpResource}, but taking the bundle in memory instead of\n * from disk — for bundler plugins that already hold the emitted sources and\n * shouldn't have to guess when they land on the filesystem.\n */\nexport async function buildMcpResourceFromSource(\n options: BuildFromSourceOptions,\n): Promise<BuildResult> {\n const { js, css, ...rest } = options;\n return writeResource({ ...rest, mode: \"self-contained\", js, css: css ?? \"\" });\n}\n\nexport async function buildMcpResource(options: BuildOptions): Promise<BuildResult> {\n if (!(\"cdn\" in options)) {\n const js = await readFile(options.entry, \"utf8\");\n const css = options.cssEntry ? await readFile(options.cssEntry, \"utf8\") : \"\";\n return writeResource({ ...options, mode: \"self-contained\", js, css });\n }\n return writeResource({ ...options, mode: \"cdn\" });\n}\n\ntype ResourceMeta = Omit<BuildSelfContainedOptions, \"entry\" | \"cssEntry\">;\n\ntype WriteResourceOptions =\n | (ResourceMeta & { mode: \"self-contained\"; js: string; css: string })\n | (ResourceMeta & { mode: \"cdn\"; cdn: BuildCdnOptions[\"cdn\"] });\n\nasync function writeResource(options: WriteResourceOptions): Promise<BuildResult> {\n const bridgeRuntimeJs = await readFile(\n options.bridgeRuntimePath ?? defaultBridgeRuntimePath(),\n \"utf8\",\n );\n\n const declaration = emitMeta({\n uri: options.uri,\n name: options.name,\n description: options.description,\n awaitToolResult: options.awaitToolResult,\n displayModes: options.displayModes,\n csp: options.mode === \"cdn\" ? mergeCdnIntoCsp(options.csp, options.cdn) : options.csp,\n permissions: options.permissions,\n domain: options.domain,\n prefersBorder: options.prefersBorder,\n openaiUi: options.openaiUi,\n });\n\n // Same values the sidecar declares, so the view's `ui/initialize` matches it.\n const config = {\n awaitToolResult: declaration.awaitToolResult,\n displayModes: declaration.displayModes,\n streamToolInput: options.streamToolInput ?? false,\n };\n\n const html =\n options.mode === \"cdn\"\n ? emitHtml({\n mode: \"cdn\",\n uri: options.uri,\n jsUrl: options.cdn.jsUrl,\n cssUrl: options.cdn.cssUrl,\n bridgeRuntimeJs,\n config,\n })\n : emitHtml({\n mode: \"self-contained\",\n uri: options.uri,\n js: options.js,\n css: options.css,\n bridgeRuntimeJs,\n config,\n });\n\n const metaPath = `${options.output}.meta.json`;\n await writeFile(options.output, html, \"utf8\");\n await writeFile(metaPath, JSON.stringify(declaration, null, 2), \"utf8\");\n\n const htmlPath = resolve(options.output);\n return {\n htmlPath,\n metaPath: resolve(metaPath),\n declaration,\n artifact: {\n name: declaration.name,\n uri: declaration.uri,\n htmlPath,\n metaPath: resolve(metaPath),\n declaration,\n },\n };\n}\n\nfunction mergeCdnIntoCsp(\n csp: McpUiResourceCsp | undefined,\n cdn: { jsUrl: string; cssUrl: string },\n): McpUiResourceCsp {\n const jsOrigin = new URL(cdn.jsUrl).origin;\n const cssOrigin = new URL(cdn.cssUrl).origin;\n const merged: McpUiResourceCsp = { ...(csp ?? {}) };\n const existing = new Set(merged.resourceDomains ?? []);\n existing.add(jsOrigin);\n existing.add(cssOrigin);\n merged.resourceDomains = Array.from(existing);\n return merged;\n}\n\nexport { emitHtml } from \"./emit-html.js\";\nexport { emitMeta } from \"./emit-meta.js\";\n","import { MCP_APPS_PROTOCOL_VERSION } from \"../schema.js\";\nimport type { McpUiDisplayMode } from \"@modelcontextprotocol/ext-apps\";\nimport { escapeInlineScript, serializeInlineScriptValue } from \"../html.js\";\n\n/**\n * Runtime config the emitted HTML hands to the inlined bridge\n * (`bridge/iframe-entry.ts` reads these off `globalThis`). Without it the view\n * falls back to `awaitToolResult: true` / `availableDisplayModes: [\"inline\"]`,\n * regardless of what the build declared in the sidecar.\n */\nexport interface EmitHtmlConfig {\n awaitToolResult?: boolean;\n displayModes?: ReadonlyArray<McpUiDisplayMode>;\n streamToolInput?: boolean;\n}\n\nexport interface EmitHtmlSelfContainedInput {\n mode: \"self-contained\";\n uri: string;\n js: string;\n css: string;\n bridgeRuntimeJs: string;\n config?: EmitHtmlConfig;\n}\n\nexport interface EmitHtmlCdnInput {\n mode: \"cdn\";\n uri: string;\n jsUrl: string;\n cssUrl: string;\n bridgeRuntimeJs: string;\n config?: EmitHtmlConfig;\n}\n\nexport type EmitHtmlInput = EmitHtmlSelfContainedInput | EmitHtmlCdnInput;\n\nfunction escapeAttr(value: string): string {\n return value\n .replace(/&/g, \"&amp;\")\n .replace(/\"/g, \"&quot;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\");\n}\n\nfunction escapeStyle(value: string): string {\n return value.replace(/<\\/style/gi, \"<\\\\/style\");\n}\n\n/**\n * Classic (non-module) script so it runs at parse time — before the deferred\n * module scripts that boot the bridge.\n */\nfunction configScript(config: EmitHtmlConfig | undefined): string[] {\n if (!config) return [];\n const assignments: string[] = [];\n if (config.awaitToolResult !== undefined) {\n assignments.push(\n `globalThis.__mountlyMcpAwaitToolResult__=${serializeInlineScriptValue(config.awaitToolResult)};`,\n );\n }\n if (config.displayModes !== undefined) {\n assignments.push(\n `globalThis.__mountlyMcpAvailableDisplayModes__=${serializeInlineScriptValue(config.displayModes)};`,\n );\n }\n if (config.streamToolInput !== undefined) {\n assignments.push(\n `globalThis.__mountlyMcpStreamToolInput__=${serializeInlineScriptValue(config.streamToolInput)};`,\n );\n }\n if (assignments.length === 0) return [];\n return [`<script>${assignments.join(\"\")}</script>`];\n}\n\nexport function emitHtml(input: EmitHtmlInput): string {\n const head = [\n `<meta charset=\"utf-8\">`,\n `<meta name=\"mountly-mcp-protocol\" content=\"${MCP_APPS_PROTOCOL_VERSION}\">`,\n `<meta name=\"mountly-mcp-uri\" content=\"${escapeAttr(input.uri)}\">`,\n ];\n\n if (input.mode === \"self-contained\") {\n head.push(`<style>${escapeStyle(input.css)}</style>`);\n const body = [\n `<div id=\"mountly-mcp-root\"></div>`,\n ...configScript(input.config),\n `<script type=\"module\">${escapeInlineScript(input.js)}\\n${escapeInlineScript(input.bridgeRuntimeJs)}</script>`,\n ];\n return template(head, body);\n }\n\n head.push(`<link rel=\"stylesheet\" href=\"${escapeAttr(input.cssUrl)}\">`);\n // The View bundle MUST load before the bridge: module scripts execute in\n // document order, and the bridge reads the published View synchronously.\n const body = [\n `<div id=\"mountly-mcp-root\"></div>`,\n ...configScript(input.config),\n `<script type=\"module\" src=\"${escapeAttr(input.jsUrl)}\"></script>`,\n `<script type=\"module\">${escapeInlineScript(input.bridgeRuntimeJs)}</script>`,\n ];\n return template(head, body);\n}\n\nfunction template(head: string[], body: string[]): string {\n return `<!doctype html>\n<html data-mountly-mcp>\n<head>\n${head.join(\"\\n\")}\n</head>\n<body>\n${body.join(\"\\n\")}\n</body>\n</html>`;\n}\n","import { MCP_APPS_MIME, MCP_APPS_PROTOCOL_VERSION } from \"../schema.js\";\nimport type {\n McpUiDisplayMode,\n McpUiResourceCsp,\n McpUiResourcePermissions,\n} from \"@modelcontextprotocol/ext-apps\";\nimport type { OpenAIUiResourceMetadata } from \"@openai/mcp-extensions/server\";\nimport type { McpResourceDeclaration } from \"../types.js\";\n\nexport interface EmitMetaInput {\n uri: string;\n name: string;\n description?: string;\n awaitToolResult?: boolean;\n displayModes?: ReadonlyArray<McpUiDisplayMode>;\n csp?: McpUiResourceCsp;\n /** Sandbox permissions requested by the view (camera/microphone/geolocation/clipboardWrite). */\n permissions?: McpUiResourcePermissions;\n /** Dedicated sandbox origin requested by the view (host-dependent format). */\n domain?: string;\n /** Whether the view requests the host to show a visible border + background. */\n prefersBorder?: boolean;\n /** ChatGPT resource display metadata written to `_meta[\"openai/ui\"]`. */\n openaiUi?: OpenAIUiResourceMetadata;\n}\n\nfunction assertUiUri(uri: string): void {\n if (!uri.startsWith(\"ui://\")) {\n throw new Error(`mountly-mcp: UI resource uri must use the 'ui://' scheme (received '${uri}')`);\n }\n}\n\nexport function emitMeta(input: EmitMetaInput): McpResourceDeclaration {\n assertUiUri(input.uri);\n\n const ui: McpResourceDeclaration[\"_meta\"][\"ui\"] = {};\n if (input.csp) ui.csp = input.csp;\n if (input.permissions) ui.permissions = input.permissions;\n if (input.domain !== undefined) ui.domain = input.domain;\n if (input.prefersBorder !== undefined) ui.prefersBorder = input.prefersBorder;\n\n const decl: McpResourceDeclaration = {\n protocolVersion: MCP_APPS_PROTOCOL_VERSION,\n uri: input.uri,\n name: input.name,\n mimeType: MCP_APPS_MIME,\n awaitToolResult: input.awaitToolResult ?? true,\n displayModes: input.displayModes ?? [\"inline\"],\n _meta: { ui },\n };\n if (input.openaiUi !== undefined) decl._meta[\"openai/ui\"] = input.openaiUi;\n if (input.description !== undefined) decl.description = input.description;\n return decl;\n}\n"],"mappings":"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","names":["body"]}
package/dist/cli.js CHANGED
@@ -5,7 +5,7 @@ import {
5
5
  } from "./chunk-EOWAWZH2.js";
6
6
  import {
7
7
  startDevHost
8
- } from "./chunk-KWVGW5Q7.js";
8
+ } from "./chunk-GN2IQYV3.js";
9
9
  import "./chunk-OWAXBNZA.js";
10
10
  import "./chunk-YJA4H6IB.js";
11
11
 
@@ -349,7 +349,7 @@ async function main() {
349
349
  return addView(parseAddArgs(rest));
350
350
  }
351
351
  if (command === "doctor") {
352
- const { doctorCommand } = await import("./doctor-B54H6CRN.js");
352
+ const { doctorCommand } = await import("./doctor-4MTC6Y4X.js");
353
353
  return doctorCommand();
354
354
  }
355
355
  if (command === "dev") return dev(parseArgs(rest));
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Show a page composed from many teams' custom elements inside an MCP host.
3
+ *
4
+ * The agent is the composer: `show_page` carries the catalog's prompt as its
5
+ * description, the agent calls it with a tree, the server validates (or
6
+ * repairs) it, and the `ui://` view renders it with `mountly-compose` — each
7
+ * team's code loading on first use. When the user acts in a widget, the view
8
+ * sends the agent its next turn, with the current page, so it edits rather than
9
+ * starts over.
10
+ */
11
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
12
+ import type { Catalog } from "mountly-compose";
13
+ export interface ComposeViewOptions {
14
+ /**
15
+ * The view's import map. Must map `mountly-compose` and
16
+ * `@modelcontextprotocol/ext-apps` (its `app-with-deps` build), plus
17
+ * whatever the teams' peer embeds import: `react`, `react/jsx-runtime`,
18
+ * `react-dom/client`, `mountly-react`, `mountly/embed`.
19
+ */
20
+ imports: Record<string, string>;
21
+ /** The page's own CSS: host layout (`ui-stack`, `ui-grid`, `ui-note`) and design tokens. */
22
+ styles?: string;
23
+ /** Document title. */
24
+ title?: string;
25
+ }
26
+ export interface RegisterComposeAppOptions extends ComposeViewOptions {
27
+ catalog: Catalog;
28
+ /** MCP tool name. Default `show_page`. */
29
+ name?: string;
30
+ /** `ui://` resource for the view. */
31
+ resourceUri?: string;
32
+ /** Appended to the tool description: what the agent may fill ids from, house rules. */
33
+ instructions?: string;
34
+ }
35
+ /** The view HTML: an import map, the host's CSS, and a module that renders tool results. */
36
+ export declare function composeViewHtml(options: ComposeViewOptions): string;
37
+ /**
38
+ * Register the `show_page` tool and its `ui://` view on an MCP server.
39
+ *
40
+ * The input is `{ page }`, typed loosely on purpose: a strict schema would make
41
+ * the SDK reject a near-miss before the handler could repair it. The handler
42
+ * validates against the catalog, strips small mistakes, and otherwise returns
43
+ * the reasons as a tool error the agent can act on.
44
+ */
45
+ export declare function registerComposeApp(server: McpServer, options: RegisterComposeAppOptions): {
46
+ tool: string;
47
+ resourceUri: string;
48
+ };