mountly-mcp 4.2.0 → 4.4.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 (46) hide show
  1. package/GLOSSARY.md +1 -0
  2. package/README.md +165 -0
  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-SXF23BZD.js → chunk-HIAIQVK5.js} +4 -7
  10. package/dist/chunk-HIAIQVK5.js.map +1 -0
  11. package/dist/{chunk-MTJDRWGD.js → chunk-IDGTP2NI.js} +4 -2
  12. package/dist/chunk-IDGTP2NI.js.map +1 -0
  13. package/dist/cli.js +1 -1
  14. package/dist/dev/host-entry.js +6 -6
  15. package/dist/{doctor-B54H6CRN.js → doctor-NB3DE4CG.js} +45 -3
  16. package/dist/doctor-NB3DE4CG.js.map +1 -0
  17. package/dist/events/index.d.ts +97 -0
  18. package/dist/events/index.js +215 -0
  19. package/dist/events/index.js.map +1 -0
  20. package/dist/json-render/index.js +2 -1
  21. package/dist/json-render/index.js.map +1 -1
  22. package/dist/openai/server.d.ts +16 -0
  23. package/dist/openai/server.js +20 -0
  24. package/dist/openai/server.js.map +1 -0
  25. package/dist/react/index.js +4 -2
  26. package/dist/react/openai-extensions.d.ts +6 -0
  27. package/dist/react/openai-extensions.js +25 -0
  28. package/dist/react/openai-extensions.js.map +1 -0
  29. package/dist/server/index.js +11 -6
  30. package/dist/server/index.js.map +1 -1
  31. package/dist/types.d.ts +3 -0
  32. package/dist/vite/index.js +1 -1
  33. package/package.json +20 -3
  34. package/templates/react/README.md.tmpl +3 -0
  35. package/templates/react/package.json.tmpl +6 -3
  36. package/templates/svelte/README.md.tmpl +2 -0
  37. package/templates/svelte/package.json.tmpl +5 -3
  38. package/templates/svelte/src/env.d.ts.tmpl +10 -0
  39. package/templates/vanilla/README.md.tmpl +2 -0
  40. package/templates/vanilla/package.json.tmpl +5 -2
  41. package/templates/vue/README.md.tmpl +2 -0
  42. package/templates/vue/package.json.tmpl +6 -4
  43. package/templates/vue/src/env.d.ts.tmpl +10 -0
  44. package/dist/chunk-MTJDRWGD.js.map +0 -1
  45. package/dist/chunk-SXF23BZD.js.map +0 -1
  46. 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,168 @@ 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
+ **Entrypoint types** place one View on several ChatGPT surfaces. Other hosts
211
+ ignore `openai/ui` and render the View inline:
212
+
213
+ | `type` | ChatGPT surface | Extra fields |
214
+ | ---------- | ----------------------------------------------- | -------------------------------- |
215
+ | `global` | Persistent sidebar panel | `quickAction` (sidebar shortcut) |
216
+ | `thread` | Conversation panel beside the chat | none |
217
+ | `file` | File viewer/editor for matching extensions | `extensions` |
218
+ | `settings` | Plugin settings page | `searchTerms` |
219
+
220
+ A file viewer receives the opened file as tool input. Parse it with the SDK schema:
221
+
222
+ ```ts
223
+ import { OpenAIFileEntrypointInputSchema } from "@openai/mcp-extensions/server";
224
+
225
+ {
226
+ name: "parts.open_cad",
227
+ resourceUri: "ui://parts/cad-viewer",
228
+ config: {
229
+ inputSchema: OpenAIFileEntrypointInputSchema.shape,
230
+ _meta: openaiUiToolMeta({
231
+ entrypoints: [{ type: "file", extensions: [".step", ".stl"] }],
232
+ }),
233
+ },
234
+ handler: async ({ file }) => ({ structuredContent: { name: file.name, uri: file.resourceUri } }),
235
+ }
236
+ ```
237
+
238
+ Smoke in ChatGPT: serve Streamable HTTP at `/mcp`, enable Developer mode, add the
239
+ connector, then open a `global` entrypoint from the Desktop sidebar. See the
240
+ [host matrix](https://mountly.dev/mcp-apps/host-matrix/).
241
+
242
+ ## MCP Events (experimental)
243
+
244
+ `mountly-mcp/events` lets your server wake a ChatGPT conversation when something
245
+ changes, such as a finished deploy or a new comment. It implements webhook
246
+ delivery from the draft MCP Events spec: Standard Webhooks signing, callback
247
+ challenge verification, retries with backoff, and `410` cleanup. The API follows
248
+ the draft spec.
249
+
250
+ You need an **MCP 2.0** server (`@modelcontextprotocol/server` v2, protocol
251
+ `2026-07-28`). Register the helpers as custom methods:
252
+
253
+ ```ts
254
+ import { McpServer, createMcpHandler } from "@modelcontextprotocol/server";
255
+ import { defineEvents } from "mountly-mcp/events";
256
+ import { z } from "zod";
257
+
258
+ const events = defineEvents({
259
+ events: {
260
+ "deploy.finished": {
261
+ description: "A deploy for the app finished.",
262
+ input: z.object({ app: z.string() }),
263
+ payload: z.object({ app: z.string(), id: z.string(), status: z.enum(["ok", "failed"]) }),
264
+ },
265
+ },
266
+ store: subscriptionsTable, // get/set/delete/values; a Map is fine for dev
267
+ authorize: ({ arguments: args, authInfo }) => canSeeApp(authInfo, args.app),
268
+ });
269
+
270
+ const handler = createMcpHandler(() => {
271
+ const server = new McpServer({ name: "deploys", version: "1.0.0" });
272
+ server.server.registerCapabilities({ events: {} });
273
+ const params = z.looseObject({});
274
+ server.server.setRequestHandler("events/list", { params }, () => events.list());
275
+ server.server.setRequestHandler("events/subscribe", { params }, (p, ctx) =>
276
+ events.subscribe(p, { authInfo: ctx.http?.authInfo }));
277
+ server.server.setRequestHandler("events/unsubscribe", { params }, (p, ctx) =>
278
+ events.unsubscribe(p, { authInfo: ctx.http?.authInfo }));
279
+ return server;
280
+ });
281
+
282
+ // Later, when it happens:
283
+ await events.emit(
284
+ "deploy.finished",
285
+ { app: "web", id: "d_42", status: "ok" },
286
+ (args) => args.app === "web",
287
+ );
288
+ ```
289
+
290
+ Give the events server at least one tool. ChatGPT creates a connector once
291
+ `tools/list` succeeds.
292
+
293
+ `authorize` runs on subscribe and again before each delivery, using the principal
294
+ stored at subscribe time, so revoking access stops delivery. Callback URLs use
295
+ `https:`; pass `allowCallbackUrl` to restrict them further. Keep `authInfo`
296
+ serializable when your store persists subscriptions.
297
+
298
+ ChatGPT subscribes from Work chats and processes events asynchronously. See the
299
+ [`mcp-chatgpt-plugin` example](https://github.com/jagreehal/mountly/tree/main/docs/examples/mcp-chatgpt-plugin) for a
300
+ full walkthrough.
301
+
140
302
  ## Verify
141
303
 
142
304
  ```bash
@@ -200,11 +362,14 @@ the view sends the agent its next turn with the current page. The
200
362
  | ------------------------- | --------------------------------------- |
201
363
  | `mountly-mcp` | `runBridge`, `publishMcpView`, types |
202
364
  | `mountly-mcp/react` | `createMcpView` + hooks |
365
+ | `mountly-mcp/react/openai` | `useOpenAIExtensions` (ChatGPT peer) |
203
366
  | `mountly-mcp/vue` | `createMcpView` + composables |
204
367
  | `mountly-mcp/svelte` | `createMcpView` (props) |
205
368
  | `mountly-mcp/vite` | `mountlyMcpViews()` |
206
369
  | `mountly-mcp/artifact` | Manifest APIs |
207
370
  | `mountly-mcp/server` | `registerMcpApps` |
371
+ | `mountly-mcp/openai/server` | ChatGPT helpers (`enableOpenAiExtensions`, `openaiUiToolMeta`) |
372
+ | `mountly-mcp/events` | Experimental MCP Events (`defineEvents`, `signWebhook`) |
208
373
  | `mountly-mcp/dev` | Local host helpers |
209
374
  | `mountly-mcp/testing` | `verifyMcpApps` |
210
375
  | `mountly-mcp/json-render` | Generative path |
@@ -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":[]}
@@ -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
@@ -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-NB3DE4CG.js");
353
353
  return doctorCommand();
354
354
  }
355
355
  if (command === "dev") return dev(parseArgs(rest));
@@ -15186,7 +15186,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
15186
15186
  }
15187
15187
  });
15188
15188
 
15189
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.30.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/types.js
15189
+ // ../../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
15190
15190
  init_v4();
15191
15191
  var RELATED_TASK_META_KEY = "io.modelcontextprotocol/related-task";
15192
15192
  var JSONRPC_VERSION = "2.0";
@@ -16707,7 +16707,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16707
16707
  // ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/mini/parse.js
16708
16708
  init_core2();
16709
16709
 
16710
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.30.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-compat.js
16710
+ // ../../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
16711
16711
  function isZ4Schema(s2) {
16712
16712
  const schema = s2;
16713
16713
  return !!schema._zod;
@@ -16770,7 +16770,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16770
16770
  return void 0;
16771
16771
  }
16772
16772
 
16773
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.30.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/experimental/tasks/interfaces.js
16773
+ // ../../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
16774
16774
  function isTerminal(status) {
16775
16775
  return status === "completed" || status === "failed" || status === "cancelled";
16776
16776
  }
@@ -16778,7 +16778,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16778
16778
  // ../../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
16779
16779
  var ALPHA_NUMERIC = new Set("ABCDEFGHIJKLMNOPQRSTUVXYZabcdefghijklmnopqrstuvxyz0123456789");
16780
16780
 
16781
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.30.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/server/zod-json-schema-compat.js
16781
+ // ../../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
16782
16782
  function getMethodLiteral(schema) {
16783
16783
  const shape = getObjectShape(schema);
16784
16784
  const methodSchema = shape?.method;
@@ -16799,7 +16799,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
16799
16799
  return result.data;
16800
16800
  }
16801
16801
 
16802
- // ../../node_modules/.pnpm/@modelcontextprotocol+sdk@1.30.0_zod@4.4.3/node_modules/@modelcontextprotocol/sdk/dist/esm/shared/protocol.js
16802
+ // ../../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
16803
16803
  var DEFAULT_REQUEST_TIMEOUT_MSEC = 6e4;
16804
16804
  var Protocol = class {
16805
16805
  constructor(_options) {
@@ -17734,7 +17734,7 @@ Set the \`cycles\` parameter to \`"ref"\` to resolve cyclical schemas with defs.
17734
17734
  }
17735
17735
  };
17736
17736
 
17737
- // ../../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-bridge.js
17737
+ // ../../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-bridge.js
17738
17738
  init_v4();
17739
17739
  var ZQ = ((X) => typeof __require < "u" ? __require : typeof Proxy < "u" ? new Proxy(X, { get: (Y, Z) => (typeof __require < "u" ? __require : Y)[Z] }) : X)(function(X) {
17740
17740
  if (typeof __require < "u") return __require.apply(this, arguments);
@@ -4,9 +4,9 @@ import {
4
4
  } from "./chunk-EOWAWZH2.js";
5
5
 
6
6
  // src/doctor.ts
7
- import { existsSync, readFileSync } from "fs";
7
+ import { existsSync, readFileSync, readdirSync } from "fs";
8
8
  import { createRequire } from "module";
9
- import { resolve } from "path";
9
+ import { dirname, join, resolve } from "path";
10
10
  function nodeMajor() {
11
11
  return Number(process.versions.node.split(".")[0] ?? 0);
12
12
  }
@@ -18,6 +18,40 @@ function tryResolve(specifier, fromDir) {
18
18
  return null;
19
19
  }
20
20
  }
21
+ function findInstalled(pkg, fromDir) {
22
+ for (let dir = resolve(fromDir); ; dir = dirname(dir)) {
23
+ const path = join(dir, "node_modules", pkg, "package.json");
24
+ if (existsSync(path)) return path;
25
+ if (dirname(dir) === dir) return null;
26
+ }
27
+ }
28
+ var OPENAI_USAGE = /openai\/ui|useOpenAIExtensions|mountly-mcp\/openai|mountly-mcp\/react\/openai|@openai\/mcp-extensions|openaiUi\b|openaiUiToolMeta|enableOpenAiExtensions/;
29
+ function projectSourcesMentionOpenAi(cwd, viteConfig) {
30
+ const candidates = [];
31
+ if (viteConfig) candidates.push(viteConfig);
32
+ for (const name of ["server.ts", "server.mjs", "server.js", "src/server.ts", "src/view.tsx"]) {
33
+ const path = resolve(cwd, name);
34
+ if (existsSync(path)) candidates.push(path);
35
+ }
36
+ const srcDir = resolve(cwd, "src");
37
+ if (existsSync(srcDir)) {
38
+ try {
39
+ for (const entry of readdirSync(srcDir, { withFileTypes: true })) {
40
+ if (entry.isFile() && /\.(tsx?|jsx?|mjs|cjs)$/.test(entry.name)) {
41
+ candidates.push(join(srcDir, entry.name));
42
+ }
43
+ }
44
+ } catch {
45
+ }
46
+ }
47
+ for (const path of candidates) {
48
+ try {
49
+ if (OPENAI_USAGE.test(readFileSync(path, "utf8"))) return true;
50
+ } catch {
51
+ }
52
+ }
53
+ return false;
54
+ }
21
55
  function runDoctor(cwd = process.cwd()) {
22
56
  const checks = [];
23
57
  const major = nodeMajor();
@@ -88,6 +122,14 @@ function runDoctor(cwd = process.cwd()) {
88
122
  }
89
123
  }
90
124
  }
125
+ if (projectSourcesMentionOpenAi(cwd, viteConfig)) {
126
+ const openai = findInstalled("@openai/mcp-extensions", cwd);
127
+ checks.push({
128
+ id: "openai-extensions",
129
+ ok: Boolean(openai),
130
+ detail: openai ? `@openai/mcp-extensions resolvable (${openai})` : "Project references OpenAI MCP extensions but @openai/mcp-extensions is not installed. Run: pnpm add @openai/mcp-extensions"
131
+ });
132
+ }
91
133
  const hard = checks.filter((c) => c.id !== "manifest" && c.id !== "playwright");
92
134
  const ok = hard.every((c) => c.ok);
93
135
  return { ok, checks };
@@ -119,4 +161,4 @@ export {
119
161
  formatDoctorReport,
120
162
  runDoctor
121
163
  };
122
- //# sourceMappingURL=doctor-B54H6CRN.js.map
164
+ //# sourceMappingURL=doctor-NB3DE4CG.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/doctor.ts"],"sourcesContent":["import { existsSync, readFileSync, readdirSync } from \"node:fs\";\nimport { createRequire } from \"node:module\";\nimport { dirname, join, resolve } from \"node:path\";\nimport { CLI_ERROR_CODES, cliError } from \"./errors.js\";\n\nexport interface DoctorCheck {\n id: string;\n ok: boolean;\n detail: string;\n}\n\nexport interface DoctorReport {\n ok: boolean;\n checks: DoctorCheck[];\n}\n\nfunction nodeMajor(): number {\n return Number(process.versions.node.split(\".\")[0] ?? 0);\n}\n\nfunction tryResolve(specifier: string, fromDir: string): string | null {\n try {\n const require = createRequire(resolve(fromDir, \"package.json\"));\n return require.resolve(specifier);\n } catch {\n return null;\n }\n}\n\n/**\n * Path to `pkg` in the project's node_modules, walking up from `fromDir`. Reads the\n * filesystem, so packages whose `exports` omit `./package.json` resolve too.\n */\nfunction findInstalled(pkg: string, fromDir: string): string | null {\n for (let dir = resolve(fromDir); ; dir = dirname(dir)) {\n const path = join(dir, \"node_modules\", pkg, \"package.json\");\n if (existsSync(path)) return path;\n if (dirname(dir) === dir) return null;\n }\n}\n\nconst OPENAI_USAGE =\n /openai\\/ui|useOpenAIExtensions|mountly-mcp\\/openai|mountly-mcp\\/react\\/openai|@openai\\/mcp-extensions|openaiUi\\b|openaiUiToolMeta|enableOpenAiExtensions/;\n\nfunction projectSourcesMentionOpenAi(cwd: string, viteConfig?: string): boolean {\n const candidates: string[] = [];\n if (viteConfig) candidates.push(viteConfig);\n for (const name of [\"server.ts\", \"server.mjs\", \"server.js\", \"src/server.ts\", \"src/view.tsx\"]) {\n const path = resolve(cwd, name);\n if (existsSync(path)) candidates.push(path);\n }\n const srcDir = resolve(cwd, \"src\");\n if (existsSync(srcDir)) {\n try {\n for (const entry of readdirSync(srcDir, { withFileTypes: true })) {\n if (entry.isFile() && /\\.(tsx?|jsx?|mjs|cjs)$/.test(entry.name)) {\n candidates.push(join(srcDir, entry.name));\n }\n }\n } catch {\n // ignore unreadable src\n }\n }\n for (const path of candidates) {\n try {\n if (OPENAI_USAGE.test(readFileSync(path, \"utf8\"))) return true;\n } catch {\n // skip\n }\n }\n return false;\n}\n\n/**\n * Check the local project for Mountly MCP readiness. Exits nonzero via the\n * CLI when `ok` is false.\n */\nexport function runDoctor(cwd = process.cwd()): DoctorReport {\n const checks: DoctorCheck[] = [];\n\n const major = nodeMajor();\n checks.push({\n id: \"node\",\n ok: major >= 20,\n detail:\n major >= 20\n ? `Node ${process.versions.node} (ok; 20+ required, 22+ recommended)`\n : `Node ${process.versions.node} is below 20. Upgrade Node, then re-run mountly-mcp doctor.`,\n });\n\n const viteConfigCandidates = [\n \"vite.config.ts\",\n \"vite.config.mts\",\n \"vite.config.js\",\n \"vite.config.mjs\",\n ].map((name) => resolve(cwd, name));\n const viteConfig = viteConfigCandidates.find((path) => existsSync(path));\n checks.push({\n id: \"vite-config\",\n ok: Boolean(viteConfig),\n detail: viteConfig\n ? `Found ${viteConfig}`\n : \"No vite.config.* in this directory. Run from the View project, or: npx mountly-mcp create my-app --framework react\",\n });\n\n if (viteConfig) {\n const source = readFileSync(viteConfig, \"utf8\");\n const hasPlugin = /mountlyMcpViews\\s*\\(/.test(source);\n checks.push({\n id: \"vite-plugin\",\n ok: hasPlugin,\n detail: hasPlugin\n ? \"vite config imports mountlyMcpViews()\"\n : `Add mountlyMcpViews() from \"mountly-mcp/vite\" to ${viteConfig}`,\n });\n }\n\n const manifest = resolve(cwd, \"dist/mountly-mcp.manifest.json\");\n checks.push({\n id: \"manifest\",\n ok: existsSync(manifest),\n detail: existsSync(manifest)\n ? `Found ${manifest}`\n : \"No dist/mountly-mcp.manifest.json yet. Run: npx mountly-mcp build\",\n });\n\n const playwright = tryResolve(\"playwright\", cwd) ?? tryResolve(\"@playwright/test\", cwd);\n checks.push({\n id: \"playwright\",\n ok: Boolean(playwright),\n detail: playwright\n ? `Playwright resolvable for verify --render (${playwright})`\n : \"Playwright not installed. For CI render checks: pnpm add -D playwright && npx playwright install chromium\",\n });\n\n const pkgPath = resolve(cwd, \"package.json\");\n if (existsSync(pkgPath)) {\n const pkg = JSON.parse(readFileSync(pkgPath, \"utf8\")) as {\n dependencies?: Record<string, string>;\n peerDependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n };\n const deps = { ...pkg.dependencies, ...pkg.devDependencies };\n if (deps.react) {\n const reactPath = tryResolve(\"react/package.json\", cwd);\n if (reactPath) {\n const reactPkg = JSON.parse(readFileSync(reactPath, \"utf8\")) as { version?: string };\n const reactMajor = Number((reactPkg.version ?? \"0\").split(\".\")[0]);\n checks.push({\n id: \"react\",\n ok: reactMajor >= 19,\n detail:\n reactMajor >= 19\n ? `React ${reactPkg.version} (ok)`\n : `React ${reactPkg.version} is below 19. mountly-mcp/react and mountly-react require React 19.`,\n });\n }\n }\n if (deps.vite) {\n const vitePath = tryResolve(\"vite/package.json\", cwd);\n if (vitePath) {\n const vitePkg = JSON.parse(readFileSync(vitePath, \"utf8\")) as { version?: string };\n const viteMajor = Number((vitePkg.version ?? \"0\").split(\".\")[0]);\n checks.push({\n id: \"vite\",\n ok: viteMajor >= 8,\n detail:\n viteMajor >= 8\n ? `Vite ${vitePkg.version} (ok)`\n : `Vite ${vitePkg.version} is below 8. mountly-mcp/vite is built and tested against Vite 8.`,\n });\n }\n }\n }\n\n if (projectSourcesMentionOpenAi(cwd, viteConfig)) {\n const openai = findInstalled(\"@openai/mcp-extensions\", cwd);\n checks.push({\n id: \"openai-extensions\",\n ok: Boolean(openai),\n detail: openai\n ? `@openai/mcp-extensions resolvable (${openai})`\n : 'Project references OpenAI MCP extensions but @openai/mcp-extensions is not installed. Run: pnpm add @openai/mcp-extensions',\n });\n }\n\n // Manifest missing is advisory for a brand-new scaffold before first build.\n const hard = checks.filter((c) => c.id !== \"manifest\" && c.id !== \"playwright\");\n const ok = hard.every((c) => c.ok);\n\n return { ok, checks };\n}\n\nexport function formatDoctorReport(report: DoctorReport): string {\n const lines = [\"mountly-mcp doctor\", \"\"];\n for (const check of report.checks) {\n lines.push(`${check.ok ? \"ok\" : \"FAIL\"} ${check.id.padEnd(14)} ${check.detail}`);\n }\n lines.push(\"\");\n lines.push(report.ok ? \"Ready for mountly-mcp build / dev / verify.\" : \"Fix FAIL items above.\");\n lines.push(\"\");\n return `${lines.join(\"\\n\")}\\n`;\n}\n\nexport function doctorCommand(cwd = process.cwd()): void {\n const report = runDoctor(cwd);\n process.stdout.write(formatDoctorReport(report));\n if (!report.ok) {\n throw cliError(\n CLI_ERROR_CODES.DOCTOR_FAILED,\n \"one or more doctor checks failed\",\n \"Fix the FAIL lines above, then re-run: mountly-mcp doctor\",\n );\n }\n}\n"],"mappings":"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","names":["require"]}