mountly-mcp 4.3.0 → 4.5.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 (58) hide show
  1. package/README.md +89 -0
  2. package/dist/artifact/declaration.d.ts +61 -0
  3. package/dist/artifact/index.d.ts +2 -42
  4. package/dist/artifact/index.js +9 -5
  5. package/dist/chunk-6T5EIQGE.js +104 -0
  6. package/dist/chunk-6T5EIQGE.js.map +1 -0
  7. package/dist/chunk-BLT2MK32.js +35 -0
  8. package/dist/chunk-BLT2MK32.js.map +1 -0
  9. package/dist/{chunk-HIAIQVK5.js → chunk-QIA76O6M.js} +9 -1
  10. package/dist/chunk-QIA76O6M.js.map +1 -0
  11. package/dist/{chunk-DKK26TRS.js → chunk-RLDSMU5M.js} +9 -70
  12. package/dist/chunk-RLDSMU5M.js.map +1 -0
  13. package/dist/chunk-UMQ5OULD.js +243 -0
  14. package/dist/chunk-UMQ5OULD.js.map +1 -0
  15. package/dist/cli.js +1 -1
  16. package/dist/dev/host-entry.js +23 -0
  17. package/dist/{doctor-4MTC6Y4X.js → doctor-OTOROLPI.js} +118 -9
  18. package/dist/doctor-OTOROLPI.js.map +1 -0
  19. package/dist/doctor.d.ts +4 -0
  20. package/dist/download.d.ts +17 -0
  21. package/dist/events/index.d.ts +97 -0
  22. package/dist/events/index.js +215 -0
  23. package/dist/events/index.js.map +1 -0
  24. package/dist/index.d.ts +1 -0
  25. package/dist/index.js +7 -3
  26. package/dist/json-render/index.js +2 -1
  27. package/dist/json-render/index.js.map +1 -1
  28. package/dist/react/hooks.d.ts +7 -0
  29. package/dist/react/index.js +4 -1
  30. package/dist/server/core.d.ts +84 -0
  31. package/dist/server/index.d.ts +2 -72
  32. package/dist/server/index.js +19 -219
  33. package/dist/server/index.js.map +1 -1
  34. package/dist/server/workers.d.ts +7 -0
  35. package/dist/server/workers.js +14 -0
  36. package/dist/server/workers.js.map +1 -0
  37. package/dist/testing/index.js +5 -3
  38. package/dist/testing/index.js.map +1 -1
  39. package/dist/vite/index.d.ts +15 -1
  40. package/dist/vite/index.js +133 -11
  41. package/dist/vite/index.js.map +1 -1
  42. package/dist/vue/index.d.ts +6 -0
  43. package/dist/vue/index.js +8 -0
  44. package/dist/vue/index.js.map +1 -1
  45. package/package.json +18 -6
  46. package/templates/react/README.md.tmpl +3 -0
  47. package/templates/react/package.json.tmpl +6 -3
  48. package/templates/svelte/README.md.tmpl +2 -0
  49. package/templates/svelte/package.json.tmpl +5 -3
  50. package/templates/svelte/src/env.d.ts.tmpl +10 -0
  51. package/templates/vanilla/README.md.tmpl +2 -0
  52. package/templates/vanilla/package.json.tmpl +5 -2
  53. package/templates/vue/README.md.tmpl +2 -0
  54. package/templates/vue/package.json.tmpl +6 -4
  55. package/templates/vue/src/env.d.ts.tmpl +10 -0
  56. package/dist/chunk-DKK26TRS.js.map +0 -1
  57. package/dist/chunk-HIAIQVK5.js.map +0 -1
  58. package/dist/doctor-4MTC6Y4X.js.map +0 -1
@@ -1,73 +1,3 @@
1
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
- import type { Transport } from "@modelcontextprotocol/sdk/shared/transport.js";
4
- import { EXTENSION_ID, RESOURCE_MIME_TYPE, type McpUiAppToolConfig } from "@modelcontextprotocol/ext-apps/server";
5
- import type { McpUiToolVisibility } from "@modelcontextprotocol/ext-apps";
6
- import { type McpAppArtifact } from "../artifact/index.js";
7
- export interface McpAppToolResult {
8
- structuredContent?: unknown;
9
- content?: ReadonlyArray<{
10
- type: string;
11
- [key: string]: unknown;
12
- }>;
13
- isError?: boolean;
14
- }
15
- /** Complete official tool configuration, plus JSON Schema support at Mountly's seam. */
16
- export interface McpAppToolConfig extends Omit<McpUiAppToolConfig, "_meta" | "inputSchema" | "outputSchema"> {
17
- inputSchema?: object;
18
- outputSchema?: object;
19
- /** Additional metadata is preserved; Mountly only merges `_meta.ui`. */
20
- _meta?: Record<string, unknown>;
21
- }
22
- /** A View is independent of the tools that reference it. */
23
- export interface McpAppViewRegistration {
24
- /** Built artifact or the path to its self-contained HTML. */
25
- artifact: McpAppArtifact | string;
26
- /** Optional assertion used by migration adapters; artifacts already own identity. */
27
- uri?: string;
28
- }
29
- /** A tool links to a separately declared View through the protocol resource URI. */
30
- export interface McpAppToolRegistration<Args = unknown> {
31
- name: string;
32
- resourceUri: string;
33
- config?: McpAppToolConfig;
34
- /** Convenience override for `_meta.ui.visibility`. */
35
- visibility?: McpUiToolVisibility | ReadonlyArray<McpUiToolVisibility>;
36
- handler: (args: Args, extra?: unknown) => Promise<McpAppToolResult>;
37
- }
38
- export interface RegisterMcpAppsOptions {
39
- views: ReadonlyArray<McpAppViewRegistration>;
40
- tools?: ReadonlyArray<McpAppToolRegistration>;
41
- }
42
- /**
43
- * `createMcpAppServer` declares Views and tools exactly as `registerMcpApps`
44
- * does; it only owns the `McpServer` for you. One vocabulary, two entry points.
45
- */
46
- export interface CreateMcpAppServerOptions extends RegisterMcpAppsOptions {
47
- name: string;
48
- version: string;
49
- }
50
- export interface RegisteredMcpApps {
51
- artifacts: ReadonlyArray<McpAppArtifact>;
52
- /** Remove every resource and tool installed by this registration. */
53
- remove(): void;
54
- }
55
- /**
56
- * Install Mountly Views and their independently declared tools into an existing,
57
- * unconnected MCP server. The caller retains ownership of identity, auth,
58
- * transports, prompts, ordinary tools, and deployment.
59
- */
60
- export declare function registerMcpApps(server: McpServer, options: RegisterMcpAppsOptions): Promise<RegisteredMcpApps>;
61
- export interface RunningMcpAppServer {
62
- connectInProcess(options?: {
63
- ui?: boolean;
64
- }): Promise<{
65
- client: Client;
66
- }>;
67
- listen(transport: Transport): Promise<void>;
68
- close(): Promise<void>;
69
- }
70
- /** Convenience adapter that retains the original one-call server experience. */
71
- export declare function createMcpAppServer(options: CreateMcpAppServerOptions): RunningMcpAppServer;
1
+ import { type CreateMcpAppServerOptions, type RunningMcpAppServer } from "./core.js";
2
+ export { EXTENSION_ID, RESOURCE_MIME_TYPE, createMcpAppServer, registerMcpApps, type CreateMcpAppServerOptions, type McpAppToolConfig, type McpAppToolRegistration, type McpAppToolResult, type McpAppViewRegistration, type RegisterMcpAppsOptions, type RegisteredMcpApps, type RunningMcpAppServer, } from "./core.js";
72
3
  export declare function serveStdio(options: CreateMcpAppServerOptions): Promise<RunningMcpAppServer>;
73
- export { EXTENSION_ID, RESOURCE_MIME_TYPE };
@@ -1,229 +1,29 @@
1
+ import {
2
+ EXTENSION_ID,
3
+ RESOURCE_MIME_TYPE,
4
+ createMcpAppServer,
5
+ installDiskViewLoader,
6
+ registerMcpApps
7
+ } from "../chunk-UMQ5OULD.js";
1
8
  import {
2
9
  readMcpAppArtifact
3
- } from "../chunk-DKK26TRS.js";
10
+ } from "../chunk-RLDSMU5M.js";
11
+ import "../chunk-6T5EIQGE.js";
4
12
 
5
13
  // src/server/index.ts
6
14
  import { readFile } from "fs/promises";
7
- import { Client } from "@modelcontextprotocol/sdk/client/index.js";
8
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
9
- import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
10
- import {
11
- EXTENSION_ID,
12
- RESOURCE_MIME_TYPE,
13
- getUiCapability,
14
- registerAppResource,
15
- registerAppTool
16
- } from "@modelcontextprotocol/ext-apps/server";
17
- import { z } from "zod";
18
- function normalizeVisibility(visibility) {
19
- if (visibility === void 0) return void 0;
20
- return typeof visibility === "string" ? [visibility] : [...visibility];
21
- }
22
- function isSchemaLibraryValue(value) {
23
- return typeof value === "object" && value !== null && ("~standard" in value || "_def" in value);
24
- }
25
- function normalizeToolSchema(schema) {
26
- if (!schema || Object.keys(schema).length === 0) return schema;
27
- const candidate = schema;
28
- if (isSchemaLibraryValue(candidate) || Object.values(candidate).some(isSchemaLibraryValue)) {
29
- return schema;
30
- }
31
- const jsonSchemaKeys = [
32
- "$schema",
33
- "$ref",
34
- "$defs",
35
- "type",
36
- "properties",
37
- "required",
38
- "additionalProperties",
39
- "allOf",
40
- "anyOf",
41
- "oneOf"
42
- ];
43
- return jsonSchemaKeys.some((key) => key in candidate) ? z.fromJSONSchema(schema) : schema;
44
- }
45
- function withTextFallback(result) {
46
- if (result.content && result.content.length > 0) return result;
47
- if (result.structuredContent === void 0) return result;
48
- return {
49
- ...result,
50
- content: [{ type: "text", text: JSON.stringify(result.structuredContent) }]
51
- };
52
- }
53
- function withoutUiMetadata(meta) {
54
- const copy = { ...meta };
55
- delete copy.ui;
56
- delete copy["ui/resourceUri"];
57
- return copy;
58
- }
59
- function mergedToolMetadata(tool) {
60
- const base = { ...tool.config?._meta ?? {} };
61
- const existingUi = typeof base.ui === "object" && base.ui !== null ? base.ui : {};
62
- const visibility = normalizeVisibility(tool.visibility) ?? [
63
- ...existingUi.visibility ?? ["model", "app"]
64
- ];
65
- const meta = {
66
- ...base,
67
- ui: { ...existingUi, resourceUri: tool.resourceUri, visibility }
68
- };
69
- return { meta, withoutUi: withoutUiMetadata(base), visibility };
70
- }
71
- async function loadViews(registrations) {
72
- const loaded = [];
73
- const names = /* @__PURE__ */ new Set();
74
- const uris = /* @__PURE__ */ new Set();
75
- for (const registration of registrations) {
76
- if (registration.uri !== void 0 && !registration.uri.startsWith("ui://")) {
77
- throw new Error(
78
- `mountly-mcp/server: registration uri must use the 'ui://' scheme (received '${registration.uri}')`
79
- );
80
- }
81
- const supplied = typeof registration.artifact === "string" ? void 0 : registration.artifact;
82
- const artifact = await readMcpAppArtifact(
83
- typeof registration.artifact === "string" ? registration.artifact : registration.artifact.htmlPath,
84
- registration.uri ?? supplied?.uri
85
- );
86
- if (supplied && supplied.name !== artifact.name) {
87
- throw new Error(
88
- `mountly-mcp: supplied artifact name '${supplied.name}' does not match file artifact name '${artifact.name}'`
89
- );
90
- }
91
- if (names.has(artifact.name))
92
- throw new Error(`mountly-mcp: duplicate app name '${artifact.name}'`);
93
- if (uris.has(artifact.uri)) throw new Error(`mountly-mcp: duplicate app uri '${artifact.uri}'`);
94
- names.add(artifact.name);
95
- uris.add(artifact.uri);
96
- loaded.push({ artifact, html: await readFile(artifact.htmlPath, "utf8") });
97
- }
98
- return loaded;
99
- }
100
- async function registerMcpApps(server, options) {
101
- if (server.isConnected()) {
102
- throw new Error("mountly-mcp: registerMcpApps() must run before the server connects");
103
- }
104
- if (options.views.length === 0) {
105
- throw new Error("mountly-mcp: registerMcpApps() requires at least one View");
106
- }
107
- const views = await loadViews(options.views);
108
- const byUri = new Map(views.map((view) => [view.artifact.uri, view]));
109
- for (const tool of options.tools ?? []) {
110
- if (!byUri.has(tool.resourceUri)) {
111
- throw new Error(
112
- `mountly-mcp: tool '${tool.name}' references undeclared View '${tool.resourceUri}'`
113
- );
114
- }
115
- }
116
- server.server.registerCapabilities({
117
- extensions: { [EXTENSION_ID]: { mimeTypes: [RESOURCE_MIME_TYPE] } }
118
- });
119
- const resources = views.map(({ artifact, html }) => {
120
- const resourceMeta = {
121
- ui: artifact.declaration._meta.ui
122
- };
123
- const openaiUi = artifact.declaration._meta["openai/ui"];
124
- if (openaiUi !== void 0) resourceMeta["openai/ui"] = openaiUi;
125
- return registerAppResource(
126
- server,
127
- artifact.name,
128
- artifact.uri,
129
- {
130
- mimeType: artifact.declaration.mimeType,
131
- description: artifact.declaration.description,
132
- _meta: resourceMeta
133
- },
134
- async () => ({
135
- contents: [
136
- {
137
- uri: artifact.uri,
138
- mimeType: artifact.declaration.mimeType,
139
- text: html,
140
- _meta: resourceMeta
141
- }
142
- ]
143
- })
144
- );
145
- });
146
- const tools = (options.tools ?? []).map((registration) => {
147
- const { meta, withoutUi, visibility } = mergedToolMetadata(registration);
148
- const config = registration.config ?? {};
149
- const registered = registerAppTool(
150
- server,
151
- registration.name,
152
- {
153
- ...config,
154
- inputSchema: normalizeToolSchema(config.inputSchema),
155
- outputSchema: normalizeToolSchema(config.outputSchema),
156
- _meta: meta
157
- },
158
- (async (args, extra) => withTextFallback(await registration.handler(args, extra)))
159
- );
160
- return { registration, registered, metadataWithoutUi: withoutUi, visibility };
161
- });
162
- const previousInitialized = server.server.oninitialized;
163
- const handleInitialized = () => {
164
- previousInitialized?.();
165
- const ui = getUiCapability(server.server.getClientCapabilities());
166
- if (ui?.mimeTypes?.includes(RESOURCE_MIME_TYPE)) return;
167
- for (const resource of resources) resource.remove();
168
- for (const tool of tools) {
169
- if (tool.visibility.includes("model")) {
170
- tool.registered.update({ _meta: tool.metadataWithoutUi });
171
- } else {
172
- tool.registered.remove();
173
- }
174
- }
175
- };
176
- server.server.oninitialized = handleInitialized;
177
- return {
178
- artifacts: views.map((view) => view.artifact),
179
- remove() {
180
- for (const resource of resources) resource.remove();
181
- for (const tool of tools) tool.registered.remove();
182
- if (server.server.oninitialized === handleInitialized) {
183
- server.server.oninitialized = previousInitialized;
184
- }
185
- }
186
- };
187
- }
188
- function createConfiguredServer(options) {
189
- return new McpServer(
190
- { name: options.name, version: options.version },
191
- { capabilities: { resources: {}, tools: {} } }
15
+ installDiskViewLoader(async (supplied, uri) => {
16
+ const artifact = await readMcpAppArtifact(
17
+ typeof supplied === "string" ? supplied : supplied.htmlPath,
18
+ uri ?? (typeof supplied === "string" ? void 0 : supplied.uri)
192
19
  );
193
- }
194
- function createMcpAppServer(options) {
195
- let server;
196
- async function build() {
197
- const next = createConfiguredServer(options);
198
- await registerMcpApps(next, { views: options.views, tools: options.tools });
199
- server = next;
200
- return next;
20
+ if (typeof supplied !== "string" && supplied.name !== artifact.name) {
21
+ throw new Error(
22
+ `mountly-mcp: supplied artifact name '${supplied.name}' does not match file artifact name '${artifact.name}'`
23
+ );
201
24
  }
202
- return {
203
- async connectInProcess(connectOptions) {
204
- const next = await build();
205
- const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
206
- await next.connect(serverTransport);
207
- const client = new Client(
208
- { name: `${options.name}-inprocess-client`, version: options.version },
209
- connectOptions?.ui ?? true ? {
210
- capabilities: {
211
- extensions: { [EXTENSION_ID]: { mimeTypes: [RESOURCE_MIME_TYPE] } }
212
- }
213
- } : void 0
214
- );
215
- await client.connect(clientTransport);
216
- return { client };
217
- },
218
- async listen(transport) {
219
- const next = await build();
220
- await next.connect(transport);
221
- },
222
- async close() {
223
- await server?.close();
224
- }
225
- };
226
- }
25
+ return { artifact, html: await readFile(artifact.htmlPath, "utf8") };
26
+ });
227
27
  async function serveStdio(options) {
228
28
  const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
229
29
  const server = createMcpAppServer(options);
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/server/index.ts"],"sourcesContent":["import { readFile } from \"node:fs/promises\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { InMemoryTransport } from \"@modelcontextprotocol/sdk/inMemory.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport {\n EXTENSION_ID,\n RESOURCE_MIME_TYPE,\n getUiCapability,\n registerAppResource,\n registerAppTool,\n type McpUiAppToolConfig,\n} from \"@modelcontextprotocol/ext-apps/server\";\nimport type { McpUiToolMeta, McpUiToolVisibility } from \"@modelcontextprotocol/ext-apps\";\nimport { z } from \"zod\";\nimport { readMcpAppArtifact, type McpAppArtifact } from \"../artifact/index.js\";\n\nexport interface McpAppToolResult {\n structuredContent?: unknown;\n content?: ReadonlyArray<{ type: string; [key: string]: unknown }>;\n isError?: boolean;\n}\n\n/** Complete official tool configuration, plus JSON Schema support at Mountly's seam. */\nexport interface McpAppToolConfig extends Omit<\n McpUiAppToolConfig,\n \"_meta\" | \"inputSchema\" | \"outputSchema\"\n> {\n inputSchema?: object;\n outputSchema?: object;\n /** Additional metadata is preserved; Mountly only merges `_meta.ui`. */\n _meta?: Record<string, unknown>;\n}\n\n/** A View is independent of the tools that reference it. */\nexport interface McpAppViewRegistration {\n /** Built artifact or the path to its self-contained HTML. */\n artifact: McpAppArtifact | string;\n /** Optional assertion used by migration adapters; artifacts already own identity. */\n uri?: string;\n}\n\n/** A tool links to a separately declared View through the protocol resource URI. */\nexport interface McpAppToolRegistration<Args = unknown> {\n name: string;\n resourceUri: string;\n config?: McpAppToolConfig;\n /** Convenience override for `_meta.ui.visibility`. */\n visibility?: McpUiToolVisibility | ReadonlyArray<McpUiToolVisibility>;\n handler: (args: Args, extra?: unknown) => Promise<McpAppToolResult>;\n}\n\nexport interface RegisterMcpAppsOptions {\n views: ReadonlyArray<McpAppViewRegistration>;\n tools?: ReadonlyArray<McpAppToolRegistration>;\n}\n\n/**\n * `createMcpAppServer` declares Views and tools exactly as `registerMcpApps`\n * does; it only owns the `McpServer` for you. One vocabulary, two entry points.\n */\nexport interface CreateMcpAppServerOptions extends RegisterMcpAppsOptions {\n name: string;\n version: string;\n}\n\ninterface LoadedView {\n artifact: McpAppArtifact;\n html: string;\n}\n\ninterface InstalledTool {\n registration: McpAppToolRegistration;\n registered: { remove(): void; update(u: { _meta?: object }): void };\n metadataWithoutUi: Record<string, unknown>;\n visibility: ReadonlyArray<McpUiToolVisibility>;\n}\n\nexport interface RegisteredMcpApps {\n artifacts: ReadonlyArray<McpAppArtifact>;\n /** Remove every resource and tool installed by this registration. */\n remove(): void;\n}\n\nfunction normalizeVisibility(\n visibility: McpAppToolRegistration[\"visibility\"],\n): McpUiToolVisibility[] | undefined {\n if (visibility === undefined) return undefined;\n return typeof visibility === \"string\" ? [visibility] : [...visibility];\n}\n\nfunction isSchemaLibraryValue(value: unknown): boolean {\n return typeof value === \"object\" && value !== null && (\"~standard\" in value || \"_def\" in value);\n}\n\n/** Convert documented JSON Schema while preserving Zod and Standard Schema values. */\nfunction normalizeToolSchema(schema: object | undefined): object | undefined {\n if (!schema || Object.keys(schema).length === 0) return schema;\n const candidate = schema as Record<string, unknown>;\n if (isSchemaLibraryValue(candidate) || Object.values(candidate).some(isSchemaLibraryValue)) {\n return schema;\n }\n const jsonSchemaKeys = [\n \"$schema\",\n \"$ref\",\n \"$defs\",\n \"type\",\n \"properties\",\n \"required\",\n \"additionalProperties\",\n \"allOf\",\n \"anyOf\",\n \"oneOf\",\n ];\n return jsonSchemaKeys.some((key) => key in candidate)\n ? z.fromJSONSchema(schema as never)\n : schema;\n}\n\nfunction withTextFallback(result: McpAppToolResult): McpAppToolResult {\n if (result.content && result.content.length > 0) return result;\n if (result.structuredContent === undefined) return result;\n return {\n ...result,\n content: [{ type: \"text\", text: JSON.stringify(result.structuredContent) }],\n };\n}\n\nfunction withoutUiMetadata(meta: Record<string, unknown>): Record<string, unknown> {\n const copy = { ...meta };\n delete copy.ui;\n delete copy[\"ui/resourceUri\"];\n return copy;\n}\n\nfunction mergedToolMetadata(tool: McpAppToolRegistration): {\n meta: Record<string, unknown> & { ui: McpUiToolMeta };\n withoutUi: Record<string, unknown>;\n visibility: McpUiToolVisibility[];\n} {\n const base = { ...(tool.config?._meta ?? {}) };\n const existingUi =\n typeof base.ui === \"object\" && base.ui !== null ? (base.ui as Partial<McpUiToolMeta>) : {};\n const visibility = normalizeVisibility(tool.visibility) ?? [\n ...(existingUi.visibility ?? [\"model\", \"app\"]),\n ];\n const meta = {\n ...base,\n ui: { ...existingUi, resourceUri: tool.resourceUri, visibility },\n };\n return { meta, withoutUi: withoutUiMetadata(base), visibility };\n}\n\nasync function loadViews(\n registrations: ReadonlyArray<McpAppViewRegistration>,\n): Promise<ReadonlyArray<LoadedView>> {\n const loaded: LoadedView[] = [];\n const names = new Set<string>();\n const uris = new Set<string>();\n for (const registration of registrations) {\n if (registration.uri !== undefined && !registration.uri.startsWith(\"ui://\")) {\n throw new Error(\n `mountly-mcp/server: registration uri must use the 'ui://' scheme (received '${registration.uri}')`,\n );\n }\n const supplied = typeof registration.artifact === \"string\" ? undefined : registration.artifact;\n const artifact = await readMcpAppArtifact(\n typeof registration.artifact === \"string\"\n ? registration.artifact\n : registration.artifact.htmlPath,\n registration.uri ?? supplied?.uri,\n );\n if (supplied && supplied.name !== artifact.name) {\n throw new Error(\n `mountly-mcp: supplied artifact name '${supplied.name}' does not match file artifact name '${artifact.name}'`,\n );\n }\n if (names.has(artifact.name))\n throw new Error(`mountly-mcp: duplicate app name '${artifact.name}'`);\n if (uris.has(artifact.uri)) throw new Error(`mountly-mcp: duplicate app uri '${artifact.uri}'`);\n names.add(artifact.name);\n uris.add(artifact.uri);\n loaded.push({ artifact, html: await readFile(artifact.htmlPath, \"utf8\") });\n }\n return loaded;\n}\n\n/**\n * Install Mountly Views and their independently declared tools into an existing,\n * unconnected MCP server. The caller retains ownership of identity, auth,\n * transports, prompts, ordinary tools, and deployment.\n */\nexport async function registerMcpApps(\n server: McpServer,\n options: RegisterMcpAppsOptions,\n): Promise<RegisteredMcpApps> {\n if (server.isConnected()) {\n throw new Error(\"mountly-mcp: registerMcpApps() must run before the server connects\");\n }\n if (options.views.length === 0) {\n throw new Error(\"mountly-mcp: registerMcpApps() requires at least one View\");\n }\n const views = await loadViews(options.views);\n const byUri = new Map(views.map((view) => [view.artifact.uri, view]));\n for (const tool of options.tools ?? []) {\n if (!byUri.has(tool.resourceUri)) {\n throw new Error(\n `mountly-mcp: tool '${tool.name}' references undeclared View '${tool.resourceUri}'`,\n );\n }\n }\n\n server.server.registerCapabilities({\n extensions: { [EXTENSION_ID]: { mimeTypes: [RESOURCE_MIME_TYPE] } },\n } as never);\n\n const resources = views.map(({ artifact, html }) => {\n const resourceMeta: Record<string, unknown> = {\n ui: artifact.declaration._meta.ui,\n };\n const openaiUi = artifact.declaration._meta[\"openai/ui\"];\n if (openaiUi !== undefined) resourceMeta[\"openai/ui\"] = openaiUi;\n return registerAppResource(\n server,\n artifact.name,\n artifact.uri,\n {\n mimeType: artifact.declaration.mimeType,\n description: artifact.declaration.description,\n _meta: resourceMeta,\n },\n async () => ({\n contents: [\n {\n uri: artifact.uri,\n mimeType: artifact.declaration.mimeType,\n text: html,\n _meta: resourceMeta,\n },\n ],\n }),\n );\n });\n\n const tools: InstalledTool[] = (options.tools ?? []).map((registration) => {\n const { meta, withoutUi, visibility } = mergedToolMetadata(registration);\n const config = registration.config ?? {};\n const registered = registerAppTool(\n server,\n registration.name,\n {\n ...config,\n inputSchema: normalizeToolSchema(config.inputSchema) as never,\n outputSchema: normalizeToolSchema(config.outputSchema) as never,\n _meta: meta,\n },\n (async (args: unknown, extra: unknown) =>\n withTextFallback(await registration.handler(args, extra))) as never,\n );\n return { registration, registered, metadataWithoutUi: withoutUi, visibility };\n });\n\n const previousInitialized = server.server.oninitialized;\n const handleInitialized = () => {\n previousInitialized?.();\n const ui = getUiCapability(server.server.getClientCapabilities());\n if (ui?.mimeTypes?.includes(RESOURCE_MIME_TYPE)) return;\n for (const resource of resources) resource.remove();\n for (const tool of tools) {\n if (tool.visibility.includes(\"model\")) {\n tool.registered.update({ _meta: tool.metadataWithoutUi });\n } else {\n tool.registered.remove();\n }\n }\n };\n server.server.oninitialized = handleInitialized;\n\n return {\n artifacts: views.map((view) => view.artifact),\n remove() {\n for (const resource of resources) resource.remove();\n for (const tool of tools) tool.registered.remove();\n if (server.server.oninitialized === handleInitialized) {\n server.server.oninitialized = previousInitialized;\n }\n },\n };\n}\n\nexport interface RunningMcpAppServer {\n connectInProcess(options?: { ui?: boolean }): Promise<{ client: Client }>;\n listen(transport: Transport): Promise<void>;\n close(): Promise<void>;\n}\n\nfunction createConfiguredServer(options: CreateMcpAppServerOptions): McpServer {\n return new McpServer(\n { name: options.name, version: options.version },\n { capabilities: { resources: {}, tools: {} } },\n );\n}\n\n/** Convenience adapter that retains the original one-call server experience. */\nexport function createMcpAppServer(options: CreateMcpAppServerOptions): RunningMcpAppServer {\n let server: McpServer | undefined;\n async function build(): Promise<McpServer> {\n const next = createConfiguredServer(options);\n await registerMcpApps(next, { views: options.views, tools: options.tools });\n server = next;\n return next;\n }\n return {\n async connectInProcess(connectOptions) {\n const next = await build();\n const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();\n await next.connect(serverTransport);\n const client = new Client(\n { name: `${options.name}-inprocess-client`, version: options.version },\n (connectOptions?.ui ?? true)\n ? ({\n capabilities: {\n extensions: { [EXTENSION_ID]: { mimeTypes: [RESOURCE_MIME_TYPE] } },\n },\n } as never)\n : undefined,\n );\n await client.connect(clientTransport);\n return { client };\n },\n async listen(transport) {\n const next = await build();\n await next.connect(transport);\n },\n async close() {\n await server?.close();\n },\n };\n}\n\nexport async function serveStdio(options: CreateMcpAppServerOptions): Promise<RunningMcpAppServer> {\n const { StdioServerTransport } = await import(\"@modelcontextprotocol/sdk/server/stdio.js\");\n const server = createMcpAppServer(options);\n await server.listen(new StdioServerTransport());\n return server;\n}\n\nexport { EXTENSION_ID, RESOURCE_MIME_TYPE };\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/server/index.ts"],"sourcesContent":["/**\n * The Node entry: Views may be paths on disk. Runtimes without a filesystem\n * (Cloudflare Workers) resolve `mountly-mcp/server` to `./workers.js` instead,\n * through the `workerd` and `worker` export conditions.\n */\nimport { readFile } from \"node:fs/promises\";\nimport { readMcpAppArtifact } from \"../artifact/index.js\";\nimport {\n createMcpAppServer,\n installDiskViewLoader,\n type CreateMcpAppServerOptions,\n type RunningMcpAppServer,\n} from \"./core.js\";\n\ninstallDiskViewLoader(async (supplied, uri) => {\n const artifact = await readMcpAppArtifact(\n typeof supplied === \"string\" ? supplied : supplied.htmlPath,\n uri ?? (typeof supplied === \"string\" ? undefined : supplied.uri),\n );\n if (typeof supplied !== \"string\" && supplied.name !== artifact.name) {\n throw new Error(\n `mountly-mcp: supplied artifact name '${supplied.name}' does not match file artifact name '${artifact.name}'`,\n );\n }\n return { artifact, html: await readFile(artifact.htmlPath, \"utf8\") };\n});\n\nexport {\n EXTENSION_ID,\n RESOURCE_MIME_TYPE,\n createMcpAppServer,\n registerMcpApps,\n type CreateMcpAppServerOptions,\n type McpAppToolConfig,\n type McpAppToolRegistration,\n type McpAppToolResult,\n type McpAppViewRegistration,\n type RegisterMcpAppsOptions,\n type RegisteredMcpApps,\n type RunningMcpAppServer,\n} from \"./core.js\";\n\nexport async function serveStdio(options: CreateMcpAppServerOptions): Promise<RunningMcpAppServer> {\n const { StdioServerTransport } = await import(\"@modelcontextprotocol/sdk/server/stdio.js\");\n const server = createMcpAppServer(options);\n await server.listen(new StdioServerTransport());\n return server;\n}\n"],"mappings":";;;;;;;;;;;;;AAKA,SAAS,gBAAgB;AASzB,sBAAsB,OAAO,UAAU,QAAQ;AAC7C,QAAM,WAAW,MAAM;AAAA,IACrB,OAAO,aAAa,WAAW,WAAW,SAAS;AAAA,IACnD,QAAQ,OAAO,aAAa,WAAW,SAAY,SAAS;AAAA,EAC9D;AACA,MAAI,OAAO,aAAa,YAAY,SAAS,SAAS,SAAS,MAAM;AACnE,UAAM,IAAI;AAAA,MACR,wCAAwC,SAAS,IAAI,wCAAwC,SAAS,IAAI;AAAA,IAC5G;AAAA,EACF;AACA,SAAO,EAAE,UAAU,MAAM,MAAM,SAAS,SAAS,UAAU,MAAM,EAAE;AACrE,CAAC;AAiBD,eAAsB,WAAW,SAAkE;AACjG,QAAM,EAAE,qBAAqB,IAAI,MAAM,OAAO,2CAA2C;AACzF,QAAM,SAAS,mBAAmB,OAAO;AACzC,QAAM,OAAO,OAAO,IAAI,qBAAqB,CAAC;AAC9C,SAAO;AACT;","names":[]}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * `mountly-mcp/server` on a runtime with no filesystem — Cloudflare Workers,
3
+ * via the `workerd` and `worker` export conditions. Same API as the Node entry,
4
+ * except Views come from `mountly-mcp.views.js` rather than paths, and there is
5
+ * no `serveStdio`: pair it with the SDK's `WebStandardStreamableHTTPServerTransport`.
6
+ */
7
+ export { EXTENSION_ID, RESOURCE_MIME_TYPE, createMcpAppServer, registerMcpApps, type CreateMcpAppServerOptions, type McpAppToolConfig, type McpAppToolRegistration, type McpAppToolResult, type McpAppViewRegistration, type RegisterMcpAppsOptions, type RegisteredMcpApps, type RunningMcpAppServer, } from "./core.js";
@@ -0,0 +1,14 @@
1
+ import {
2
+ EXTENSION_ID,
3
+ RESOURCE_MIME_TYPE,
4
+ createMcpAppServer,
5
+ registerMcpApps
6
+ } from "../chunk-UMQ5OULD.js";
7
+ import "../chunk-6T5EIQGE.js";
8
+ export {
9
+ EXTENSION_ID,
10
+ RESOURCE_MIME_TYPE,
11
+ createMcpAppServer,
12
+ registerMcpApps
13
+ };
14
+ //# sourceMappingURL=workers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
@@ -1,9 +1,11 @@
1
1
  import {
2
- MCP_APP_MANIFEST_FILE,
3
- McpAppConformanceError,
4
2
  readMcpAppArtifact,
5
3
  readMcpAppManifest
6
- } from "../chunk-DKK26TRS.js";
4
+ } from "../chunk-RLDSMU5M.js";
5
+ import {
6
+ MCP_APP_MANIFEST_FILE,
7
+ McpAppConformanceError
8
+ } from "../chunk-6T5EIQGE.js";
7
9
 
8
10
  // src/testing/index.ts
9
11
  import { access, readFile } from "fs/promises";
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/testing/index.ts","../../src/testing/render-artifact.ts"],"sourcesContent":["import { access, readFile } from \"node:fs/promises\";\nimport { isDeepStrictEqual } from \"node:util\";\nimport {\n MCP_APP_MANIFEST_FILE,\n McpAppConformanceError,\n readMcpAppArtifact,\n readMcpAppManifest,\n type ConformanceDiagnostic,\n type McpAppArtifact,\n} from \"../artifact/index.js\";\nimport { renderMcpAppArtifact } from \"./render-artifact.js\";\n\nexport interface VerifyMcpAppsOptions {\n /** Canonical manifest. Defaults to `dist/mountly-mcp.manifest.json`. */\n manifestPath?: string;\n /** Transitional single-View inputs when no manifest exists. */\n htmlPaths?: ReadonlyArray<string>;\n /**\n * Load each View in a real browser and confirm the View mounts with content.\n *\n * Off by default: it needs Playwright and a Chromium download, which the\n * static checks do not. Turn it on in CI. Everything else here inspects\n * files, and a View can satisfy every file-level check while failing to\n * render — a framework left external, a Node global in the bundle, a bridge\n * that never completes the handshake all produce conforming HTML.\n */\n render?: boolean;\n}\n\nexport interface McpAppConformanceReport {\n ok: boolean;\n /** Verification tier selected by the caller. */\n mode: \"static\" | \"browser\";\n artifacts: ReadonlyArray<McpAppArtifact>;\n diagnostics: ReadonlyArray<ConformanceDiagnostic>;\n}\n\nfunction error(code: string, message: string, source?: string): ConformanceDiagnostic {\n return { severity: \"error\", code, message, source };\n}\n\nfunction warning(code: string, message: string, source?: string): ConformanceDiagnostic {\n return { severity: \"warning\", code, message, source };\n}\n\n/** Deterministic verification shared by the CLI and downstream CI. */\nexport async function verifyMcpApps(\n options: VerifyMcpAppsOptions = {},\n): Promise<McpAppConformanceReport> {\n const diagnostics: ConformanceDiagnostic[] = [];\n const artifacts: McpAppArtifact[] = [];\n if (options.htmlPaths && options.htmlPaths.length > 0) {\n for (const path of options.htmlPaths) {\n try {\n artifacts.push(await readMcpAppArtifact(path));\n } catch (caught) {\n if (caught instanceof McpAppConformanceError) diagnostics.push(...caught.diagnostics);\n else\n diagnostics.push(\n error(\"verify/load\", caught instanceof Error ? caught.message : String(caught)),\n );\n }\n }\n } else {\n try {\n const loaded = await readMcpAppManifest(\n options.manifestPath ?? `dist/${MCP_APP_MANIFEST_FILE}`,\n );\n for (const declared of loaded.artifacts) {\n try {\n const actual = await readMcpAppArtifact(declared.htmlPath, declared.uri);\n if (actual.name !== declared.name) {\n diagnostics.push(\n error(\n \"manifest/name-mismatch\",\n `manifest app '${declared.name}' points to artifact '${actual.name}'`,\n loaded.path,\n ),\n );\n }\n if (!isDeepStrictEqual(actual.declaration, declared.declaration)) {\n diagnostics.push(\n error(\n \"manifest/declaration-mismatch\",\n `manifest declaration for '${declared.name}' does not match its metadata file`,\n loaded.path,\n ),\n );\n }\n artifacts.push(actual);\n } catch (caught) {\n if (caught instanceof McpAppConformanceError) diagnostics.push(...caught.diagnostics);\n else\n diagnostics.push(\n error(\"verify/load\", caught instanceof Error ? caught.message : String(caught)),\n );\n }\n }\n } catch (caught) {\n if (caught instanceof McpAppConformanceError) diagnostics.push(...caught.diagnostics);\n else\n diagnostics.push(\n error(\"verify/load\", caught instanceof Error ? caught.message : String(caught)),\n );\n }\n }\n\n const names = new Set<string>();\n const uris = new Set<string>();\n for (const artifact of artifacts) {\n if (names.has(artifact.name)) {\n diagnostics.push(error(\"manifest/duplicate-name\", `duplicate app name '${artifact.name}'`));\n }\n if (uris.has(artifact.uri)) {\n diagnostics.push(error(\"manifest/duplicate-uri\", `duplicate app uri '${artifact.uri}'`));\n }\n names.add(artifact.name);\n uris.add(artifact.uri);\n try {\n await Promise.all([access(artifact.htmlPath), access(artifact.metaPath)]);\n const html = await readFile(artifact.htmlPath, \"utf8\");\n if (!html.includes(\"ui/initialize\") || !html.includes(\"ui/notifications/initialized\")) {\n diagnostics.push(\n error(\n \"artifact/bridge\",\n \"View HTML does not contain the MCP Apps initialization handshake\",\n artifact.htmlPath,\n ),\n );\n }\n if (options.render) diagnostics.push(...(await renderMcpAppArtifact(artifact)));\n if (!artifact.declaration.description) {\n diagnostics.push(\n warning(\n \"artifact/description\",\n `View '${artifact.name}' has no description`,\n artifact.metaPath,\n ),\n );\n }\n } catch (caught) {\n diagnostics.push(\n error(\n \"artifact/files\",\n caught instanceof Error ? caught.message : String(caught),\n artifact.htmlPath,\n ),\n );\n }\n }\n\n return {\n ok: !diagnostics.some((diagnostic) => diagnostic.severity === \"error\"),\n mode: options.render === true ? \"browser\" : \"static\",\n artifacts,\n diagnostics,\n };\n}\n\nexport function formatConformanceReport(report: McpAppConformanceReport): string {\n const lines = report.diagnostics.map((diagnostic) => {\n const marker = diagnostic.severity === \"error\" ? \"ERROR\" : \"WARN\";\n const source = diagnostic.source ? ` (${diagnostic.source})` : \"\";\n return `${marker} ${diagnostic.code}: ${diagnostic.message}${source}`;\n });\n lines.push(\n `${report.ok ? \"PASS\" : \"FAIL\"} ${report.artifacts.length} app artifact${report.artifacts.length === 1 ? \"\" : \"s\"} (${report.mode === \"browser\" ? \"browser checks\" : \"static checks only; pass --render for runtime coverage\"})`,\n );\n return `${lines.join(\"\\n\")}\\n`;\n}\n","import type { ConformanceDiagnostic, McpAppArtifact } from \"../artifact/index.js\";\n\ninterface FrameLike {\n url(): string;\n /**\n * A string expression is evaluated through the debugger protocol rather than\n * as page script, which is how axe reaches a View whose CSP withholds\n * `unsafe-eval` — the same CSP a host enforces in production.\n */\n evaluate<T>(fn: (() => T) | string): Promise<T>;\n}\n\ninterface PageLike {\n on(event: \"pageerror\", handler: (error: Error) => void): void;\n goto(url: string): Promise<unknown>;\n frames(): FrameLike[];\n}\n\nexport interface ConformanceBrowser {\n newPage(): Promise<PageLike>;\n close(): Promise<void>;\n}\n\nexport interface ConformanceHost {\n hostUrl: string;\n close(): Promise<void>;\n}\n\n/** @internal Test seam for browser and host lifecycle failures. */\nexport interface RenderMcpAppArtifactDependencies {\n launchBrowser(): Promise<ConformanceBrowser | undefined>;\n startHost(artifact: McpAppArtifact): Promise<ConformanceHost>;\n}\n\nfunction error(code: string, message: string, source: string): ConformanceDiagnostic {\n return { severity: \"error\", code, message, source };\n}\n\nfunction warning(code: string, message: string, source: string): ConformanceDiagnostic {\n return { severity: \"warning\", code, message, source };\n}\n\ninterface AxeViolation {\n id: string;\n impact: string | null;\n help: string;\n nodes: number;\n target: string;\n}\n\n/** axe-core is an optional peer, like Playwright: absent means the pass is skipped. */\nasync function loadAxeSource(): Promise<string | undefined> {\n const specifier: string = \"axe-core\";\n try {\n const mod = (await import(specifier)) as { source?: string; default?: { source?: string } };\n return mod.source ?? mod.default?.source;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Accessibility of the View as assembled, which component-level checks cannot\n * see: every part can be accessible and the composition still fail, because a\n * missing label or a contrast pairing only exists once the parts are together.\n *\n * Reported as warnings, so a run stays green unless `--strict` is on. They are\n * findings about the View you built, not proof the artifact is malformed.\n */\nasync function auditAccessibility(\n view: FrameLike,\n artifact: McpAppArtifact,\n): Promise<ConformanceDiagnostic[]> {\n const axeSource = await loadAxeSource();\n if (!axeSource) return [];\n\n let violations: AxeViolation[];\n try {\n await view.evaluate(axeSource);\n violations = await view.evaluate<AxeViolation[]>(`\n axe.run(document, { resultTypes: [\"violations\"] }).then((result) =>\n result.violations.map((violation) => ({\n id: violation.id,\n impact: violation.impact ?? null,\n help: violation.help,\n nodes: violation.nodes.length,\n target: String(violation.nodes[0]?.target?.[0] ?? \"\")\n }))\n )\n `);\n } catch (caught) {\n return [\n warning(\n \"render/a11y-unavailable\",\n `accessibility pass did not run: ${caught instanceof Error ? caught.message : String(caught)}`,\n artifact.htmlPath,\n ),\n ];\n }\n\n return violations.map((violation) =>\n warning(\n \"render/a11y\",\n `${violation.id} (${violation.impact ?? \"unknown\"} impact): ${violation.help} — ${violation.nodes} element${violation.nodes === 1 ? \"\" : \"s\"}, first at \\`${violation.target}\\``,\n artifact.htmlPath,\n ),\n );\n}\n\n/** Playwright is an optional peer — absent is a diagnostic, not a crash. */\nasync function launchBrowser(): Promise<ConformanceBrowser | undefined> {\n // Both specifiers are tried: under an isolated `node_modules` a project that\n // installed `@playwright/test` cannot resolve bare `playwright`, even though\n // one depends on the other. Specifiers stay `string`-typed so the optional\n // peer is optional at type-check time as well as at runtime.\n const specifiers: string[] = [\"playwright\", \"@playwright/test\"];\n for (const specifier of specifiers) {\n let mod: { chromium?: { launch(): Promise<ConformanceBrowser> } };\n try {\n mod = (await import(specifier)) as typeof mod;\n } catch {\n continue;\n }\n if (mod.chromium) return mod.chromium.launch();\n }\n return undefined;\n}\n\nasync function startHost(artifact: McpAppArtifact): Promise<ConformanceHost> {\n const { startDevHost } = await import(\"../dev/index.js\");\n return startDevHost({\n htmlPath: artifact.htmlPath,\n fixtures: { conformance: {} },\n hostPort: 5480,\n });\n}\n\n/**\n * Exercise an App artifact through the real two-origin host and browser.\n * Browser and host ownership stays local so every startup path is cleaned up.\n */\nexport async function renderMcpAppArtifactWith(\n artifact: McpAppArtifact,\n dependencies: RenderMcpAppArtifactDependencies,\n): Promise<ConformanceDiagnostic[]> {\n let browser: ConformanceBrowser | undefined;\n try {\n browser = await dependencies.launchBrowser();\n } catch (caught) {\n return [\n error(\n \"render/unavailable\",\n `could not launch Chromium: ${caught instanceof Error ? caught.message : String(caught)}`,\n artifact.htmlPath,\n ),\n ];\n }\n if (!browser) {\n return [\n error(\n \"render/unavailable\",\n \"render requested but Playwright is not installed (npm i -D playwright)\",\n artifact.htmlPath,\n ),\n ];\n }\n\n let host: ConformanceHost | undefined;\n const failures: ConformanceDiagnostic[] = [];\n try {\n host = await dependencies.startHost(artifact);\n const page = await browser.newPage();\n const pageErrors: string[] = [];\n page.on(\"pageerror\", (caught) => pageErrors.push(caught.message));\n await page.goto(host.hostUrl);\n\n const deadline = Date.now() + 20_000;\n let mounted = false;\n let renderedContent = false;\n let mountedAt: number | undefined;\n let boundary: string | undefined;\n while (!boundary && Date.now() < deadline && pageErrors.length === 0) {\n const view = page.frames().find((frame) => frame.url().includes(\"srcdoc\"));\n const state = view\n ? await view\n .evaluate(() => {\n const failed = document.querySelector(\"[data-mountly-mcp-error]\");\n const root = document.querySelector(\"#mountly-mcp-root\");\n const bounds = root?.getBoundingClientRect();\n return {\n state: root?.getAttribute(\"data-mountly-mcp-state\") ?? undefined,\n html: root?.innerHTML ?? \"\",\n shadowHtml: root?.shadowRoot?.innerHTML ?? \"\",\n visibleHeight: bounds?.height ?? 0,\n error: failed\n ? `${failed.getAttribute(\"data-mountly-mcp-error\") ?? \"\"}: ${failed.textContent ?? \"\"}`\n : undefined,\n };\n })\n .catch(() => undefined)\n : undefined;\n boundary = state?.error;\n // Sticky: the bridge's state only moves forward, and a View that mounted\n // stays mounted even if a later poll reads the frame mid-navigation.\n mounted ||= state?.state === \"mounted\";\n if (mounted && mountedAt === undefined) mountedAt = Date.now();\n renderedContent =\n (state?.html ?? \"\").trim().length > 0 ||\n (state?.shadowHtml ?? \"\").trim().length > 0 ||\n (state?.visibleHeight ?? 0) > 0;\n if (mounted && renderedContent) break;\n // Framework renderers may commit after mount() returns. Give them a\n // bounded window while still failing a genuinely empty View quickly.\n if (mountedAt !== undefined && Date.now() - mountedAt >= 2_000) break;\n await new Promise((done) => setTimeout(done, 100));\n }\n\n if (boundary) {\n failures.push(\n error(\"render/error-boundary\", `View rendered an error: ${boundary}`, artifact.htmlPath),\n );\n } else if (pageErrors.length > 0) {\n failures.push(\n error(\"render/threw\", `View threw while loading: ${pageErrors[0]}`, artifact.htmlPath),\n );\n } else if (!mounted) {\n failures.push(\n error(\n \"render/timeout\",\n \"View never mounted: the handshake or View mount did not complete\",\n artifact.htmlPath,\n ),\n );\n } else if (!renderedContent) {\n failures.push(\n error(\"render/blank\", \"View mounted but rendered no content\", artifact.htmlPath),\n );\n } else {\n // Only a View that actually rendered can be audited — anything else has\n // already failed, and axe would just report on an empty document.\n const view = page.frames().find((frame) => frame.url().includes(\"srcdoc\"));\n if (view) failures.push(...(await auditAccessibility(view, artifact)));\n }\n } catch (caught) {\n failures.push(\n error(\n \"render/failed\",\n caught instanceof Error ? caught.message : String(caught),\n artifact.htmlPath,\n ),\n );\n } finally {\n await Promise.allSettled([browser.close(), host?.close() ?? Promise.resolve()]);\n }\n return failures;\n}\n\nexport function renderMcpAppArtifact(artifact: McpAppArtifact): Promise<ConformanceDiagnostic[]> {\n return renderMcpAppArtifactWith(artifact, { launchBrowser, startHost });\n}\n"],"mappings":";;;;;;;;AAAA,SAAS,QAAQ,gBAAgB;AACjC,SAAS,yBAAyB;;;ACiClC,SAAS,MAAM,MAAc,SAAiB,QAAuC;AACnF,SAAO,EAAE,UAAU,SAAS,MAAM,SAAS,OAAO;AACpD;AAEA,SAAS,QAAQ,MAAc,SAAiB,QAAuC;AACrF,SAAO,EAAE,UAAU,WAAW,MAAM,SAAS,OAAO;AACtD;AAWA,eAAe,gBAA6C;AAC1D,QAAM,YAAoB;AAC1B,MAAI;AACF,UAAM,MAAO,MAAM,OAAO;AAC1B,WAAO,IAAI,UAAU,IAAI,SAAS;AAAA,EACpC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAUA,eAAe,mBACb,MACA,UACkC;AAClC,QAAM,YAAY,MAAM,cAAc;AACtC,MAAI,CAAC,UAAW,QAAO,CAAC;AAExB,MAAI;AACJ,MAAI;AACF,UAAM,KAAK,SAAS,SAAS;AAC7B,iBAAa,MAAM,KAAK,SAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAUhD;AAAA,EACH,SAAS,QAAQ;AACf,WAAO;AAAA,MACL;AAAA,QACE;AAAA,QACA,mCAAmC,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM,CAAC;AAAA,QAC5F,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAEA,SAAO,WAAW;AAAA,IAAI,CAAC,cACrB;AAAA,MACE;AAAA,MACA,GAAG,UAAU,EAAE,KAAK,UAAU,UAAU,SAAS,aAAa,UAAU,IAAI,WAAM,UAAU,KAAK,WAAW,UAAU,UAAU,IAAI,KAAK,GAAG,gBAAgB,UAAU,MAAM;AAAA,MAC5K,SAAS;AAAA,IACX;AAAA,EACF;AACF;AAGA,eAAe,gBAAyD;AAKtE,QAAM,aAAuB,CAAC,cAAc,kBAAkB;AAC9D,aAAW,aAAa,YAAY;AAClC,QAAI;AACJ,QAAI;AACF,YAAO,MAAM,OAAO;AAAA,IACtB,QAAQ;AACN;AAAA,IACF;AACA,QAAI,IAAI,SAAU,QAAO,IAAI,SAAS,OAAO;AAAA,EAC/C;AACA,SAAO;AACT;AAEA,eAAe,UAAU,UAAoD;AAC3E,QAAM,EAAE,aAAa,IAAI,MAAM,OAAO,iBAAiB;AACvD,SAAO,aAAa;AAAA,IAClB,UAAU,SAAS;AAAA,IACnB,UAAU,EAAE,aAAa,CAAC,EAAE;AAAA,IAC5B,UAAU;AAAA,EACZ,CAAC;AACH;AAMA,eAAsB,yBACpB,UACA,cACkC;AAClC,MAAI;AACJ,MAAI;AACF,cAAU,MAAM,aAAa,cAAc;AAAA,EAC7C,SAAS,QAAQ;AACf,WAAO;AAAA,MACL;AAAA,QACE;AAAA,QACA,8BAA8B,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM,CAAC;AAAA,QACvF,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,MACL;AAAA,QACE;AAAA,QACA;AAAA,QACA,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACJ,QAAM,WAAoC,CAAC;AAC3C,MAAI;AACF,WAAO,MAAM,aAAa,UAAU,QAAQ;AAC5C,UAAM,OAAO,MAAM,QAAQ,QAAQ;AACnC,UAAM,aAAuB,CAAC;AAC9B,SAAK,GAAG,aAAa,CAAC,WAAW,WAAW,KAAK,OAAO,OAAO,CAAC;AAChE,UAAM,KAAK,KAAK,KAAK,OAAO;AAE5B,UAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,QAAI,UAAU;AACd,QAAI,kBAAkB;AACtB,QAAI;AACJ,QAAI;AACJ,WAAO,CAAC,YAAY,KAAK,IAAI,IAAI,YAAY,WAAW,WAAW,GAAG;AACpE,YAAM,OAAO,KAAK,OAAO,EAAE,KAAK,CAAC,UAAU,MAAM,IAAI,EAAE,SAAS,QAAQ,CAAC;AACzE,YAAM,QAAQ,OACV,MAAM,KACH,SAAS,MAAM;AACd,cAAM,SAAS,SAAS,cAAc,0BAA0B;AAChE,cAAM,OAAO,SAAS,cAAc,mBAAmB;AACvD,cAAM,SAAS,MAAM,sBAAsB;AAC3C,eAAO;AAAA,UACL,OAAO,MAAM,aAAa,wBAAwB,KAAK;AAAA,UACvD,MAAM,MAAM,aAAa;AAAA,UACzB,YAAY,MAAM,YAAY,aAAa;AAAA,UAC3C,eAAe,QAAQ,UAAU;AAAA,UACjC,OAAO,SACH,GAAG,OAAO,aAAa,wBAAwB,KAAK,EAAE,KAAK,OAAO,eAAe,EAAE,KACnF;AAAA,QACN;AAAA,MACF,CAAC,EACA,MAAM,MAAM,MAAS,IACxB;AACJ,iBAAW,OAAO;AAGlB,4BAAY,OAAO,UAAU;AAC7B,UAAI,WAAW,cAAc,OAAW,aAAY,KAAK,IAAI;AAC7D,yBACG,OAAO,QAAQ,IAAI,KAAK,EAAE,SAAS,MACnC,OAAO,cAAc,IAAI,KAAK,EAAE,SAAS,MACzC,OAAO,iBAAiB,KAAK;AAChC,UAAI,WAAW,gBAAiB;AAGhC,UAAI,cAAc,UAAa,KAAK,IAAI,IAAI,aAAa,IAAO;AAChE,YAAM,IAAI,QAAQ,CAAC,SAAS,WAAW,MAAM,GAAG,CAAC;AAAA,IACnD;AAEA,QAAI,UAAU;AACZ,eAAS;AAAA,QACP,MAAM,yBAAyB,2BAA2B,QAAQ,IAAI,SAAS,QAAQ;AAAA,MACzF;AAAA,IACF,WAAW,WAAW,SAAS,GAAG;AAChC,eAAS;AAAA,QACP,MAAM,gBAAgB,6BAA6B,WAAW,CAAC,CAAC,IAAI,SAAS,QAAQ;AAAA,MACvF;AAAA,IACF,WAAW,CAAC,SAAS;AACnB,eAAS;AAAA,QACP;AAAA,UACE;AAAA,UACA;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF,WAAW,CAAC,iBAAiB;AAC3B,eAAS;AAAA,QACP,MAAM,gBAAgB,wCAAwC,SAAS,QAAQ;AAAA,MACjF;AAAA,IACF,OAAO;AAGL,YAAM,OAAO,KAAK,OAAO,EAAE,KAAK,CAAC,UAAU,MAAM,IAAI,EAAE,SAAS,QAAQ,CAAC;AACzE,UAAI,KAAM,UAAS,KAAK,GAAI,MAAM,mBAAmB,MAAM,QAAQ,CAAE;AAAA,IACvE;AAAA,EACF,SAAS,QAAQ;AACf,aAAS;AAAA,MACP;AAAA,QACE;AAAA,QACA,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM;AAAA,QACxD,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF,UAAE;AACA,UAAM,QAAQ,WAAW,CAAC,QAAQ,MAAM,GAAG,MAAM,MAAM,KAAK,QAAQ,QAAQ,CAAC,CAAC;AAAA,EAChF;AACA,SAAO;AACT;AAEO,SAAS,qBAAqB,UAA4D;AAC/F,SAAO,yBAAyB,UAAU,EAAE,eAAe,UAAU,CAAC;AACxE;;;AD9NA,SAASA,OAAM,MAAc,SAAiB,QAAwC;AACpF,SAAO,EAAE,UAAU,SAAS,MAAM,SAAS,OAAO;AACpD;AAEA,SAASC,SAAQ,MAAc,SAAiB,QAAwC;AACtF,SAAO,EAAE,UAAU,WAAW,MAAM,SAAS,OAAO;AACtD;AAGA,eAAsB,cACpB,UAAgC,CAAC,GACC;AAClC,QAAM,cAAuC,CAAC;AAC9C,QAAM,YAA8B,CAAC;AACrC,MAAI,QAAQ,aAAa,QAAQ,UAAU,SAAS,GAAG;AACrD,eAAW,QAAQ,QAAQ,WAAW;AACpC,UAAI;AACF,kBAAU,KAAK,MAAM,mBAAmB,IAAI,CAAC;AAAA,MAC/C,SAAS,QAAQ;AACf,YAAI,kBAAkB,uBAAwB,aAAY,KAAK,GAAG,OAAO,WAAW;AAAA;AAElF,sBAAY;AAAA,YACVD,OAAM,eAAe,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM,CAAC;AAAA,UAChF;AAAA,MACJ;AAAA,IACF;AAAA,EACF,OAAO;AACL,QAAI;AACF,YAAM,SAAS,MAAM;AAAA,QACnB,QAAQ,gBAAgB,QAAQ,qBAAqB;AAAA,MACvD;AACA,iBAAW,YAAY,OAAO,WAAW;AACvC,YAAI;AACF,gBAAM,SAAS,MAAM,mBAAmB,SAAS,UAAU,SAAS,GAAG;AACvE,cAAI,OAAO,SAAS,SAAS,MAAM;AACjC,wBAAY;AAAA,cACVA;AAAA,gBACE;AAAA,gBACA,iBAAiB,SAAS,IAAI,yBAAyB,OAAO,IAAI;AAAA,gBAClE,OAAO;AAAA,cACT;AAAA,YACF;AAAA,UACF;AACA,cAAI,CAAC,kBAAkB,OAAO,aAAa,SAAS,WAAW,GAAG;AAChE,wBAAY;AAAA,cACVA;AAAA,gBACE;AAAA,gBACA,6BAA6B,SAAS,IAAI;AAAA,gBAC1C,OAAO;AAAA,cACT;AAAA,YACF;AAAA,UACF;AACA,oBAAU,KAAK,MAAM;AAAA,QACvB,SAAS,QAAQ;AACf,cAAI,kBAAkB,uBAAwB,aAAY,KAAK,GAAG,OAAO,WAAW;AAAA;AAElF,wBAAY;AAAA,cACVA,OAAM,eAAe,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM,CAAC;AAAA,YAChF;AAAA,QACJ;AAAA,MACF;AAAA,IACF,SAAS,QAAQ;AACf,UAAI,kBAAkB,uBAAwB,aAAY,KAAK,GAAG,OAAO,WAAW;AAAA;AAElF,oBAAY;AAAA,UACVA,OAAM,eAAe,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM,CAAC;AAAA,QAChF;AAAA,IACJ;AAAA,EACF;AAEA,QAAM,QAAQ,oBAAI,IAAY;AAC9B,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,YAAY,WAAW;AAChC,QAAI,MAAM,IAAI,SAAS,IAAI,GAAG;AAC5B,kBAAY,KAAKA,OAAM,2BAA2B,uBAAuB,SAAS,IAAI,GAAG,CAAC;AAAA,IAC5F;AACA,QAAI,KAAK,IAAI,SAAS,GAAG,GAAG;AAC1B,kBAAY,KAAKA,OAAM,0BAA0B,sBAAsB,SAAS,GAAG,GAAG,CAAC;AAAA,IACzF;AACA,UAAM,IAAI,SAAS,IAAI;AACvB,SAAK,IAAI,SAAS,GAAG;AACrB,QAAI;AACF,YAAM,QAAQ,IAAI,CAAC,OAAO,SAAS,QAAQ,GAAG,OAAO,SAAS,QAAQ,CAAC,CAAC;AACxE,YAAM,OAAO,MAAM,SAAS,SAAS,UAAU,MAAM;AACrD,UAAI,CAAC,KAAK,SAAS,eAAe,KAAK,CAAC,KAAK,SAAS,8BAA8B,GAAG;AACrF,oBAAY;AAAA,UACVA;AAAA,YACE;AAAA,YACA;AAAA,YACA,SAAS;AAAA,UACX;AAAA,QACF;AAAA,MACF;AACA,UAAI,QAAQ,OAAQ,aAAY,KAAK,GAAI,MAAM,qBAAqB,QAAQ,CAAE;AAC9E,UAAI,CAAC,SAAS,YAAY,aAAa;AACrC,oBAAY;AAAA,UACVC;AAAA,YACE;AAAA,YACA,SAAS,SAAS,IAAI;AAAA,YACtB,SAAS;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,QAAQ;AACf,kBAAY;AAAA,QACVD;AAAA,UACE;AAAA,UACA,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM;AAAA,UACxD,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAI,CAAC,YAAY,KAAK,CAAC,eAAe,WAAW,aAAa,OAAO;AAAA,IACrE,MAAM,QAAQ,WAAW,OAAO,YAAY;AAAA,IAC5C;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,wBAAwB,QAAyC;AAC/E,QAAM,QAAQ,OAAO,YAAY,IAAI,CAAC,eAAe;AACnD,UAAM,SAAS,WAAW,aAAa,UAAU,UAAU;AAC3D,UAAM,SAAS,WAAW,SAAS,KAAK,WAAW,MAAM,MAAM;AAC/D,WAAO,GAAG,MAAM,IAAI,WAAW,IAAI,KAAK,WAAW,OAAO,GAAG,MAAM;AAAA,EACrE,CAAC;AACD,QAAM;AAAA,IACJ,GAAG,OAAO,KAAK,SAAS,MAAM,IAAI,OAAO,UAAU,MAAM,gBAAgB,OAAO,UAAU,WAAW,IAAI,KAAK,GAAG,KAAK,OAAO,SAAS,YAAY,mBAAmB,wDAAwD;AAAA,EAC/N;AACA,SAAO,GAAG,MAAM,KAAK,IAAI,CAAC;AAAA;AAC5B;","names":["error","warning"]}
1
+ {"version":3,"sources":["../../src/testing/index.ts","../../src/testing/render-artifact.ts"],"sourcesContent":["import { access, readFile } from \"node:fs/promises\";\nimport { isDeepStrictEqual } from \"node:util\";\nimport {\n MCP_APP_MANIFEST_FILE,\n McpAppConformanceError,\n readMcpAppArtifact,\n readMcpAppManifest,\n type ConformanceDiagnostic,\n type McpAppArtifact,\n} from \"../artifact/index.js\";\nimport { renderMcpAppArtifact } from \"./render-artifact.js\";\n\nexport interface VerifyMcpAppsOptions {\n /** Canonical manifest. Defaults to `dist/mountly-mcp.manifest.json`. */\n manifestPath?: string;\n /** Transitional single-View inputs when no manifest exists. */\n htmlPaths?: ReadonlyArray<string>;\n /**\n * Load each View in a real browser and confirm the View mounts with content.\n *\n * Off by default: it needs Playwright and a Chromium download, which the\n * static checks do not. Turn it on in CI. Everything else here inspects\n * files, and a View can satisfy every file-level check while failing to\n * render — a framework left external, a Node global in the bundle, a bridge\n * that never completes the handshake all produce conforming HTML.\n */\n render?: boolean;\n}\n\nexport interface McpAppConformanceReport {\n ok: boolean;\n /** Verification tier selected by the caller. */\n mode: \"static\" | \"browser\";\n artifacts: ReadonlyArray<McpAppArtifact>;\n diagnostics: ReadonlyArray<ConformanceDiagnostic>;\n}\n\nfunction error(code: string, message: string, source?: string): ConformanceDiagnostic {\n return { severity: \"error\", code, message, source };\n}\n\nfunction warning(code: string, message: string, source?: string): ConformanceDiagnostic {\n return { severity: \"warning\", code, message, source };\n}\n\n/** Deterministic verification shared by the CLI and downstream CI. */\nexport async function verifyMcpApps(\n options: VerifyMcpAppsOptions = {},\n): Promise<McpAppConformanceReport> {\n const diagnostics: ConformanceDiagnostic[] = [];\n const artifacts: McpAppArtifact[] = [];\n if (options.htmlPaths && options.htmlPaths.length > 0) {\n for (const path of options.htmlPaths) {\n try {\n artifacts.push(await readMcpAppArtifact(path));\n } catch (caught) {\n if (caught instanceof McpAppConformanceError) diagnostics.push(...caught.diagnostics);\n else\n diagnostics.push(\n error(\"verify/load\", caught instanceof Error ? caught.message : String(caught)),\n );\n }\n }\n } else {\n try {\n const loaded = await readMcpAppManifest(\n options.manifestPath ?? `dist/${MCP_APP_MANIFEST_FILE}`,\n );\n for (const declared of loaded.artifacts) {\n try {\n const actual = await readMcpAppArtifact(declared.htmlPath, declared.uri);\n if (actual.name !== declared.name) {\n diagnostics.push(\n error(\n \"manifest/name-mismatch\",\n `manifest app '${declared.name}' points to artifact '${actual.name}'`,\n loaded.path,\n ),\n );\n }\n if (!isDeepStrictEqual(actual.declaration, declared.declaration)) {\n diagnostics.push(\n error(\n \"manifest/declaration-mismatch\",\n `manifest declaration for '${declared.name}' does not match its metadata file`,\n loaded.path,\n ),\n );\n }\n artifacts.push(actual);\n } catch (caught) {\n if (caught instanceof McpAppConformanceError) diagnostics.push(...caught.diagnostics);\n else\n diagnostics.push(\n error(\"verify/load\", caught instanceof Error ? caught.message : String(caught)),\n );\n }\n }\n } catch (caught) {\n if (caught instanceof McpAppConformanceError) diagnostics.push(...caught.diagnostics);\n else\n diagnostics.push(\n error(\"verify/load\", caught instanceof Error ? caught.message : String(caught)),\n );\n }\n }\n\n const names = new Set<string>();\n const uris = new Set<string>();\n for (const artifact of artifacts) {\n if (names.has(artifact.name)) {\n diagnostics.push(error(\"manifest/duplicate-name\", `duplicate app name '${artifact.name}'`));\n }\n if (uris.has(artifact.uri)) {\n diagnostics.push(error(\"manifest/duplicate-uri\", `duplicate app uri '${artifact.uri}'`));\n }\n names.add(artifact.name);\n uris.add(artifact.uri);\n try {\n await Promise.all([access(artifact.htmlPath), access(artifact.metaPath)]);\n const html = await readFile(artifact.htmlPath, \"utf8\");\n if (!html.includes(\"ui/initialize\") || !html.includes(\"ui/notifications/initialized\")) {\n diagnostics.push(\n error(\n \"artifact/bridge\",\n \"View HTML does not contain the MCP Apps initialization handshake\",\n artifact.htmlPath,\n ),\n );\n }\n if (options.render) diagnostics.push(...(await renderMcpAppArtifact(artifact)));\n if (!artifact.declaration.description) {\n diagnostics.push(\n warning(\n \"artifact/description\",\n `View '${artifact.name}' has no description`,\n artifact.metaPath,\n ),\n );\n }\n } catch (caught) {\n diagnostics.push(\n error(\n \"artifact/files\",\n caught instanceof Error ? caught.message : String(caught),\n artifact.htmlPath,\n ),\n );\n }\n }\n\n return {\n ok: !diagnostics.some((diagnostic) => diagnostic.severity === \"error\"),\n mode: options.render === true ? \"browser\" : \"static\",\n artifacts,\n diagnostics,\n };\n}\n\nexport function formatConformanceReport(report: McpAppConformanceReport): string {\n const lines = report.diagnostics.map((diagnostic) => {\n const marker = diagnostic.severity === \"error\" ? \"ERROR\" : \"WARN\";\n const source = diagnostic.source ? ` (${diagnostic.source})` : \"\";\n return `${marker} ${diagnostic.code}: ${diagnostic.message}${source}`;\n });\n lines.push(\n `${report.ok ? \"PASS\" : \"FAIL\"} ${report.artifacts.length} app artifact${report.artifacts.length === 1 ? \"\" : \"s\"} (${report.mode === \"browser\" ? \"browser checks\" : \"static checks only; pass --render for runtime coverage\"})`,\n );\n return `${lines.join(\"\\n\")}\\n`;\n}\n","import type { ConformanceDiagnostic, McpAppArtifact } from \"../artifact/index.js\";\n\ninterface FrameLike {\n url(): string;\n /**\n * A string expression is evaluated through the debugger protocol rather than\n * as page script, which is how axe reaches a View whose CSP withholds\n * `unsafe-eval` — the same CSP a host enforces in production.\n */\n evaluate<T>(fn: (() => T) | string): Promise<T>;\n}\n\ninterface PageLike {\n on(event: \"pageerror\", handler: (error: Error) => void): void;\n goto(url: string): Promise<unknown>;\n frames(): FrameLike[];\n}\n\nexport interface ConformanceBrowser {\n newPage(): Promise<PageLike>;\n close(): Promise<void>;\n}\n\nexport interface ConformanceHost {\n hostUrl: string;\n close(): Promise<void>;\n}\n\n/** @internal Test seam for browser and host lifecycle failures. */\nexport interface RenderMcpAppArtifactDependencies {\n launchBrowser(): Promise<ConformanceBrowser | undefined>;\n startHost(artifact: McpAppArtifact): Promise<ConformanceHost>;\n}\n\nfunction error(code: string, message: string, source: string): ConformanceDiagnostic {\n return { severity: \"error\", code, message, source };\n}\n\nfunction warning(code: string, message: string, source: string): ConformanceDiagnostic {\n return { severity: \"warning\", code, message, source };\n}\n\ninterface AxeViolation {\n id: string;\n impact: string | null;\n help: string;\n nodes: number;\n target: string;\n}\n\n/** axe-core is an optional peer, like Playwright: absent means the pass is skipped. */\nasync function loadAxeSource(): Promise<string | undefined> {\n const specifier: string = \"axe-core\";\n try {\n const mod = (await import(specifier)) as { source?: string; default?: { source?: string } };\n return mod.source ?? mod.default?.source;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Accessibility of the View as assembled, which component-level checks cannot\n * see: every part can be accessible and the composition still fail, because a\n * missing label or a contrast pairing only exists once the parts are together.\n *\n * Reported as warnings, so a run stays green unless `--strict` is on. They are\n * findings about the View you built, not proof the artifact is malformed.\n */\nasync function auditAccessibility(\n view: FrameLike,\n artifact: McpAppArtifact,\n): Promise<ConformanceDiagnostic[]> {\n const axeSource = await loadAxeSource();\n if (!axeSource) return [];\n\n let violations: AxeViolation[];\n try {\n await view.evaluate(axeSource);\n violations = await view.evaluate<AxeViolation[]>(`\n axe.run(document, { resultTypes: [\"violations\"] }).then((result) =>\n result.violations.map((violation) => ({\n id: violation.id,\n impact: violation.impact ?? null,\n help: violation.help,\n nodes: violation.nodes.length,\n target: String(violation.nodes[0]?.target?.[0] ?? \"\")\n }))\n )\n `);\n } catch (caught) {\n return [\n warning(\n \"render/a11y-unavailable\",\n `accessibility pass did not run: ${caught instanceof Error ? caught.message : String(caught)}`,\n artifact.htmlPath,\n ),\n ];\n }\n\n return violations.map((violation) =>\n warning(\n \"render/a11y\",\n `${violation.id} (${violation.impact ?? \"unknown\"} impact): ${violation.help} — ${violation.nodes} element${violation.nodes === 1 ? \"\" : \"s\"}, first at \\`${violation.target}\\``,\n artifact.htmlPath,\n ),\n );\n}\n\n/** Playwright is an optional peer — absent is a diagnostic, not a crash. */\nasync function launchBrowser(): Promise<ConformanceBrowser | undefined> {\n // Both specifiers are tried: under an isolated `node_modules` a project that\n // installed `@playwright/test` cannot resolve bare `playwright`, even though\n // one depends on the other. Specifiers stay `string`-typed so the optional\n // peer is optional at type-check time as well as at runtime.\n const specifiers: string[] = [\"playwright\", \"@playwright/test\"];\n for (const specifier of specifiers) {\n let mod: { chromium?: { launch(): Promise<ConformanceBrowser> } };\n try {\n mod = (await import(specifier)) as typeof mod;\n } catch {\n continue;\n }\n if (mod.chromium) return mod.chromium.launch();\n }\n return undefined;\n}\n\nasync function startHost(artifact: McpAppArtifact): Promise<ConformanceHost> {\n const { startDevHost } = await import(\"../dev/index.js\");\n return startDevHost({\n htmlPath: artifact.htmlPath,\n fixtures: { conformance: {} },\n hostPort: 5480,\n });\n}\n\n/**\n * Exercise an App artifact through the real two-origin host and browser.\n * Browser and host ownership stays local so every startup path is cleaned up.\n */\nexport async function renderMcpAppArtifactWith(\n artifact: McpAppArtifact,\n dependencies: RenderMcpAppArtifactDependencies,\n): Promise<ConformanceDiagnostic[]> {\n let browser: ConformanceBrowser | undefined;\n try {\n browser = await dependencies.launchBrowser();\n } catch (caught) {\n return [\n error(\n \"render/unavailable\",\n `could not launch Chromium: ${caught instanceof Error ? caught.message : String(caught)}`,\n artifact.htmlPath,\n ),\n ];\n }\n if (!browser) {\n return [\n error(\n \"render/unavailable\",\n \"render requested but Playwright is not installed (npm i -D playwright)\",\n artifact.htmlPath,\n ),\n ];\n }\n\n let host: ConformanceHost | undefined;\n const failures: ConformanceDiagnostic[] = [];\n try {\n host = await dependencies.startHost(artifact);\n const page = await browser.newPage();\n const pageErrors: string[] = [];\n page.on(\"pageerror\", (caught) => pageErrors.push(caught.message));\n await page.goto(host.hostUrl);\n\n const deadline = Date.now() + 20_000;\n let mounted = false;\n let renderedContent = false;\n let mountedAt: number | undefined;\n let boundary: string | undefined;\n while (!boundary && Date.now() < deadline && pageErrors.length === 0) {\n const view = page.frames().find((frame) => frame.url().includes(\"srcdoc\"));\n const state = view\n ? await view\n .evaluate(() => {\n const failed = document.querySelector(\"[data-mountly-mcp-error]\");\n const root = document.querySelector(\"#mountly-mcp-root\");\n const bounds = root?.getBoundingClientRect();\n return {\n state: root?.getAttribute(\"data-mountly-mcp-state\") ?? undefined,\n html: root?.innerHTML ?? \"\",\n shadowHtml: root?.shadowRoot?.innerHTML ?? \"\",\n visibleHeight: bounds?.height ?? 0,\n error: failed\n ? `${failed.getAttribute(\"data-mountly-mcp-error\") ?? \"\"}: ${failed.textContent ?? \"\"}`\n : undefined,\n };\n })\n .catch(() => undefined)\n : undefined;\n boundary = state?.error;\n // Sticky: the bridge's state only moves forward, and a View that mounted\n // stays mounted even if a later poll reads the frame mid-navigation.\n mounted ||= state?.state === \"mounted\";\n if (mounted && mountedAt === undefined) mountedAt = Date.now();\n renderedContent =\n (state?.html ?? \"\").trim().length > 0 ||\n (state?.shadowHtml ?? \"\").trim().length > 0 ||\n (state?.visibleHeight ?? 0) > 0;\n if (mounted && renderedContent) break;\n // Framework renderers may commit after mount() returns. Give them a\n // bounded window while still failing a genuinely empty View quickly.\n if (mountedAt !== undefined && Date.now() - mountedAt >= 2_000) break;\n await new Promise((done) => setTimeout(done, 100));\n }\n\n if (boundary) {\n failures.push(\n error(\"render/error-boundary\", `View rendered an error: ${boundary}`, artifact.htmlPath),\n );\n } else if (pageErrors.length > 0) {\n failures.push(\n error(\"render/threw\", `View threw while loading: ${pageErrors[0]}`, artifact.htmlPath),\n );\n } else if (!mounted) {\n failures.push(\n error(\n \"render/timeout\",\n \"View never mounted: the handshake or View mount did not complete\",\n artifact.htmlPath,\n ),\n );\n } else if (!renderedContent) {\n failures.push(\n error(\"render/blank\", \"View mounted but rendered no content\", artifact.htmlPath),\n );\n } else {\n // Only a View that actually rendered can be audited — anything else has\n // already failed, and axe would just report on an empty document.\n const view = page.frames().find((frame) => frame.url().includes(\"srcdoc\"));\n if (view) failures.push(...(await auditAccessibility(view, artifact)));\n }\n } catch (caught) {\n failures.push(\n error(\n \"render/failed\",\n caught instanceof Error ? caught.message : String(caught),\n artifact.htmlPath,\n ),\n );\n } finally {\n await Promise.allSettled([browser.close(), host?.close() ?? Promise.resolve()]);\n }\n return failures;\n}\n\nexport function renderMcpAppArtifact(artifact: McpAppArtifact): Promise<ConformanceDiagnostic[]> {\n return renderMcpAppArtifactWith(artifact, { launchBrowser, startHost });\n}\n"],"mappings":";;;;;;;;;;AAAA,SAAS,QAAQ,gBAAgB;AACjC,SAAS,yBAAyB;;;ACiClC,SAAS,MAAM,MAAc,SAAiB,QAAuC;AACnF,SAAO,EAAE,UAAU,SAAS,MAAM,SAAS,OAAO;AACpD;AAEA,SAAS,QAAQ,MAAc,SAAiB,QAAuC;AACrF,SAAO,EAAE,UAAU,WAAW,MAAM,SAAS,OAAO;AACtD;AAWA,eAAe,gBAA6C;AAC1D,QAAM,YAAoB;AAC1B,MAAI;AACF,UAAM,MAAO,MAAM,OAAO;AAC1B,WAAO,IAAI,UAAU,IAAI,SAAS;AAAA,EACpC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAUA,eAAe,mBACb,MACA,UACkC;AAClC,QAAM,YAAY,MAAM,cAAc;AACtC,MAAI,CAAC,UAAW,QAAO,CAAC;AAExB,MAAI;AACJ,MAAI;AACF,UAAM,KAAK,SAAS,SAAS;AAC7B,iBAAa,MAAM,KAAK,SAAyB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,KAUhD;AAAA,EACH,SAAS,QAAQ;AACf,WAAO;AAAA,MACL;AAAA,QACE;AAAA,QACA,mCAAmC,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM,CAAC;AAAA,QAC5F,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAEA,SAAO,WAAW;AAAA,IAAI,CAAC,cACrB;AAAA,MACE;AAAA,MACA,GAAG,UAAU,EAAE,KAAK,UAAU,UAAU,SAAS,aAAa,UAAU,IAAI,WAAM,UAAU,KAAK,WAAW,UAAU,UAAU,IAAI,KAAK,GAAG,gBAAgB,UAAU,MAAM;AAAA,MAC5K,SAAS;AAAA,IACX;AAAA,EACF;AACF;AAGA,eAAe,gBAAyD;AAKtE,QAAM,aAAuB,CAAC,cAAc,kBAAkB;AAC9D,aAAW,aAAa,YAAY;AAClC,QAAI;AACJ,QAAI;AACF,YAAO,MAAM,OAAO;AAAA,IACtB,QAAQ;AACN;AAAA,IACF;AACA,QAAI,IAAI,SAAU,QAAO,IAAI,SAAS,OAAO;AAAA,EAC/C;AACA,SAAO;AACT;AAEA,eAAe,UAAU,UAAoD;AAC3E,QAAM,EAAE,aAAa,IAAI,MAAM,OAAO,iBAAiB;AACvD,SAAO,aAAa;AAAA,IAClB,UAAU,SAAS;AAAA,IACnB,UAAU,EAAE,aAAa,CAAC,EAAE;AAAA,IAC5B,UAAU;AAAA,EACZ,CAAC;AACH;AAMA,eAAsB,yBACpB,UACA,cACkC;AAClC,MAAI;AACJ,MAAI;AACF,cAAU,MAAM,aAAa,cAAc;AAAA,EAC7C,SAAS,QAAQ;AACf,WAAO;AAAA,MACL;AAAA,QACE;AAAA,QACA,8BAA8B,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM,CAAC;AAAA,QACvF,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AACA,MAAI,CAAC,SAAS;AACZ,WAAO;AAAA,MACL;AAAA,QACE;AAAA,QACA;AAAA,QACA,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF;AAEA,MAAI;AACJ,QAAM,WAAoC,CAAC;AAC3C,MAAI;AACF,WAAO,MAAM,aAAa,UAAU,QAAQ;AAC5C,UAAM,OAAO,MAAM,QAAQ,QAAQ;AACnC,UAAM,aAAuB,CAAC;AAC9B,SAAK,GAAG,aAAa,CAAC,WAAW,WAAW,KAAK,OAAO,OAAO,CAAC;AAChE,UAAM,KAAK,KAAK,KAAK,OAAO;AAE5B,UAAM,WAAW,KAAK,IAAI,IAAI;AAC9B,QAAI,UAAU;AACd,QAAI,kBAAkB;AACtB,QAAI;AACJ,QAAI;AACJ,WAAO,CAAC,YAAY,KAAK,IAAI,IAAI,YAAY,WAAW,WAAW,GAAG;AACpE,YAAM,OAAO,KAAK,OAAO,EAAE,KAAK,CAAC,UAAU,MAAM,IAAI,EAAE,SAAS,QAAQ,CAAC;AACzE,YAAM,QAAQ,OACV,MAAM,KACH,SAAS,MAAM;AACd,cAAM,SAAS,SAAS,cAAc,0BAA0B;AAChE,cAAM,OAAO,SAAS,cAAc,mBAAmB;AACvD,cAAM,SAAS,MAAM,sBAAsB;AAC3C,eAAO;AAAA,UACL,OAAO,MAAM,aAAa,wBAAwB,KAAK;AAAA,UACvD,MAAM,MAAM,aAAa;AAAA,UACzB,YAAY,MAAM,YAAY,aAAa;AAAA,UAC3C,eAAe,QAAQ,UAAU;AAAA,UACjC,OAAO,SACH,GAAG,OAAO,aAAa,wBAAwB,KAAK,EAAE,KAAK,OAAO,eAAe,EAAE,KACnF;AAAA,QACN;AAAA,MACF,CAAC,EACA,MAAM,MAAM,MAAS,IACxB;AACJ,iBAAW,OAAO;AAGlB,4BAAY,OAAO,UAAU;AAC7B,UAAI,WAAW,cAAc,OAAW,aAAY,KAAK,IAAI;AAC7D,yBACG,OAAO,QAAQ,IAAI,KAAK,EAAE,SAAS,MACnC,OAAO,cAAc,IAAI,KAAK,EAAE,SAAS,MACzC,OAAO,iBAAiB,KAAK;AAChC,UAAI,WAAW,gBAAiB;AAGhC,UAAI,cAAc,UAAa,KAAK,IAAI,IAAI,aAAa,IAAO;AAChE,YAAM,IAAI,QAAQ,CAAC,SAAS,WAAW,MAAM,GAAG,CAAC;AAAA,IACnD;AAEA,QAAI,UAAU;AACZ,eAAS;AAAA,QACP,MAAM,yBAAyB,2BAA2B,QAAQ,IAAI,SAAS,QAAQ;AAAA,MACzF;AAAA,IACF,WAAW,WAAW,SAAS,GAAG;AAChC,eAAS;AAAA,QACP,MAAM,gBAAgB,6BAA6B,WAAW,CAAC,CAAC,IAAI,SAAS,QAAQ;AAAA,MACvF;AAAA,IACF,WAAW,CAAC,SAAS;AACnB,eAAS;AAAA,QACP;AAAA,UACE;AAAA,UACA;AAAA,UACA,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF,WAAW,CAAC,iBAAiB;AAC3B,eAAS;AAAA,QACP,MAAM,gBAAgB,wCAAwC,SAAS,QAAQ;AAAA,MACjF;AAAA,IACF,OAAO;AAGL,YAAM,OAAO,KAAK,OAAO,EAAE,KAAK,CAAC,UAAU,MAAM,IAAI,EAAE,SAAS,QAAQ,CAAC;AACzE,UAAI,KAAM,UAAS,KAAK,GAAI,MAAM,mBAAmB,MAAM,QAAQ,CAAE;AAAA,IACvE;AAAA,EACF,SAAS,QAAQ;AACf,aAAS;AAAA,MACP;AAAA,QACE;AAAA,QACA,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM;AAAA,QACxD,SAAS;AAAA,MACX;AAAA,IACF;AAAA,EACF,UAAE;AACA,UAAM,QAAQ,WAAW,CAAC,QAAQ,MAAM,GAAG,MAAM,MAAM,KAAK,QAAQ,QAAQ,CAAC,CAAC;AAAA,EAChF;AACA,SAAO;AACT;AAEO,SAAS,qBAAqB,UAA4D;AAC/F,SAAO,yBAAyB,UAAU,EAAE,eAAe,UAAU,CAAC;AACxE;;;AD9NA,SAASA,OAAM,MAAc,SAAiB,QAAwC;AACpF,SAAO,EAAE,UAAU,SAAS,MAAM,SAAS,OAAO;AACpD;AAEA,SAASC,SAAQ,MAAc,SAAiB,QAAwC;AACtF,SAAO,EAAE,UAAU,WAAW,MAAM,SAAS,OAAO;AACtD;AAGA,eAAsB,cACpB,UAAgC,CAAC,GACC;AAClC,QAAM,cAAuC,CAAC;AAC9C,QAAM,YAA8B,CAAC;AACrC,MAAI,QAAQ,aAAa,QAAQ,UAAU,SAAS,GAAG;AACrD,eAAW,QAAQ,QAAQ,WAAW;AACpC,UAAI;AACF,kBAAU,KAAK,MAAM,mBAAmB,IAAI,CAAC;AAAA,MAC/C,SAAS,QAAQ;AACf,YAAI,kBAAkB,uBAAwB,aAAY,KAAK,GAAG,OAAO,WAAW;AAAA;AAElF,sBAAY;AAAA,YACVD,OAAM,eAAe,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM,CAAC;AAAA,UAChF;AAAA,MACJ;AAAA,IACF;AAAA,EACF,OAAO;AACL,QAAI;AACF,YAAM,SAAS,MAAM;AAAA,QACnB,QAAQ,gBAAgB,QAAQ,qBAAqB;AAAA,MACvD;AACA,iBAAW,YAAY,OAAO,WAAW;AACvC,YAAI;AACF,gBAAM,SAAS,MAAM,mBAAmB,SAAS,UAAU,SAAS,GAAG;AACvE,cAAI,OAAO,SAAS,SAAS,MAAM;AACjC,wBAAY;AAAA,cACVA;AAAA,gBACE;AAAA,gBACA,iBAAiB,SAAS,IAAI,yBAAyB,OAAO,IAAI;AAAA,gBAClE,OAAO;AAAA,cACT;AAAA,YACF;AAAA,UACF;AACA,cAAI,CAAC,kBAAkB,OAAO,aAAa,SAAS,WAAW,GAAG;AAChE,wBAAY;AAAA,cACVA;AAAA,gBACE;AAAA,gBACA,6BAA6B,SAAS,IAAI;AAAA,gBAC1C,OAAO;AAAA,cACT;AAAA,YACF;AAAA,UACF;AACA,oBAAU,KAAK,MAAM;AAAA,QACvB,SAAS,QAAQ;AACf,cAAI,kBAAkB,uBAAwB,aAAY,KAAK,GAAG,OAAO,WAAW;AAAA;AAElF,wBAAY;AAAA,cACVA,OAAM,eAAe,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM,CAAC;AAAA,YAChF;AAAA,QACJ;AAAA,MACF;AAAA,IACF,SAAS,QAAQ;AACf,UAAI,kBAAkB,uBAAwB,aAAY,KAAK,GAAG,OAAO,WAAW;AAAA;AAElF,oBAAY;AAAA,UACVA,OAAM,eAAe,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM,CAAC;AAAA,QAChF;AAAA,IACJ;AAAA,EACF;AAEA,QAAM,QAAQ,oBAAI,IAAY;AAC9B,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,YAAY,WAAW;AAChC,QAAI,MAAM,IAAI,SAAS,IAAI,GAAG;AAC5B,kBAAY,KAAKA,OAAM,2BAA2B,uBAAuB,SAAS,IAAI,GAAG,CAAC;AAAA,IAC5F;AACA,QAAI,KAAK,IAAI,SAAS,GAAG,GAAG;AAC1B,kBAAY,KAAKA,OAAM,0BAA0B,sBAAsB,SAAS,GAAG,GAAG,CAAC;AAAA,IACzF;AACA,UAAM,IAAI,SAAS,IAAI;AACvB,SAAK,IAAI,SAAS,GAAG;AACrB,QAAI;AACF,YAAM,QAAQ,IAAI,CAAC,OAAO,SAAS,QAAQ,GAAG,OAAO,SAAS,QAAQ,CAAC,CAAC;AACxE,YAAM,OAAO,MAAM,SAAS,SAAS,UAAU,MAAM;AACrD,UAAI,CAAC,KAAK,SAAS,eAAe,KAAK,CAAC,KAAK,SAAS,8BAA8B,GAAG;AACrF,oBAAY;AAAA,UACVA;AAAA,YACE;AAAA,YACA;AAAA,YACA,SAAS;AAAA,UACX;AAAA,QACF;AAAA,MACF;AACA,UAAI,QAAQ,OAAQ,aAAY,KAAK,GAAI,MAAM,qBAAqB,QAAQ,CAAE;AAC9E,UAAI,CAAC,SAAS,YAAY,aAAa;AACrC,oBAAY;AAAA,UACVC;AAAA,YACE;AAAA,YACA,SAAS,SAAS,IAAI;AAAA,YACtB,SAAS;AAAA,UACX;AAAA,QACF;AAAA,MACF;AAAA,IACF,SAAS,QAAQ;AACf,kBAAY;AAAA,QACVD;AAAA,UACE;AAAA,UACA,kBAAkB,QAAQ,OAAO,UAAU,OAAO,MAAM;AAAA,UACxD,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,IAAI,CAAC,YAAY,KAAK,CAAC,eAAe,WAAW,aAAa,OAAO;AAAA,IACrE,MAAM,QAAQ,WAAW,OAAO,YAAY;AAAA,IAC5C;AAAA,IACA;AAAA,EACF;AACF;AAEO,SAAS,wBAAwB,QAAyC;AAC/E,QAAM,QAAQ,OAAO,YAAY,IAAI,CAAC,eAAe;AACnD,UAAM,SAAS,WAAW,aAAa,UAAU,UAAU;AAC3D,UAAM,SAAS,WAAW,SAAS,KAAK,WAAW,MAAM,MAAM;AAC/D,WAAO,GAAG,MAAM,IAAI,WAAW,IAAI,KAAK,WAAW,OAAO,GAAG,MAAM;AAAA,EACrE,CAAC;AACD,QAAM;AAAA,IACJ,GAAG,OAAO,KAAK,SAAS,MAAM,IAAI,OAAO,UAAU,MAAM,gBAAgB,OAAO,UAAU,WAAW,IAAI,KAAK,GAAG,KAAK,OAAO,SAAS,YAAY,mBAAmB,wDAAwD;AAAA,EAC/N;AACA,SAAO,GAAG,MAAM,KAAK,IAAI,CAAC;AAAA;AAC5B;","names":["error","warning"]}
@@ -1,4 +1,4 @@
1
- import type { Plugin } from "vite";
1
+ import { type Plugin } from "vite";
2
2
  import type { BuildSelfContainedOptions } from "../build/index.js";
3
3
  export interface MountlyMcpViewOptions extends Omit<BuildSelfContainedOptions, "entry" | "output" | "cssEntry"> {
4
4
  entry: string;
@@ -9,12 +9,26 @@ export interface MountlyMcpViewsOptions {
9
9
  apps: ReadonlyArray<MountlyMcpViewOptions>;
10
10
  /** Defaults to `<outDir>/mountly-mcp.manifest.json`; false disables it. */
11
11
  manifest?: string | false;
12
+ /**
13
+ * Also write every View, HTML included, as an ES module, for a server that
14
+ * cannot read files — Cloudflare Workers, say: `import views from
15
+ * "./dist/mountly-mcp.views.js"`, then `views.map((artifact) => ({ artifact }))`.
16
+ * `true` writes `<outDir>/mountly-mcp.views.js`; a string names the path.
17
+ */
18
+ module?: string | boolean;
12
19
  }
20
+ export declare const MCP_APP_VIEWS_MODULE_FILE = "mountly-mcp.views.js";
13
21
  export type MountlyMcpViteOptions = MountlyMcpViewOptions | MountlyMcpViewsOptions;
14
22
  export interface MountlyMcpViteApi {
15
23
  apps: ReadonlyArray<MountlyMcpViewOptions>;
16
24
  manifest?: string | false;
17
25
  }
26
+ /**
27
+ * Rewrite a module's worker calls. In a `.vue` or `.svelte` file each
28
+ * `<script>` is parsed on its own, and its imports go inside that block —
29
+ * anything outside it is not script, and the compiler drops it.
30
+ */
31
+ export declare function inlineWorkers(code: string, file?: string): string | null;
18
32
  /**
19
33
  * Build one or many independent MCP Apps Views. Each View is a Vite build
20
34
  * environment, so frameworks and CSS remain isolated while callers configure