mountly-mcp 0.0.0 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +258 -20
- package/dist/artifact/index.d.ts +49 -0
- package/dist/artifact/index.js +21 -0
- package/dist/artifact/index.js.map +1 -0
- package/dist/bridge/iframe-entry.d.ts +1 -0
- package/dist/bridge/iframe-entry.js +251 -209
- package/dist/bridge/index.d.ts +17 -12
- package/dist/bridge/index.js +2 -2
- package/dist/build/emit-html.d.ts +30 -0
- package/dist/build/emit-meta.d.ts +17 -0
- package/dist/build/index.d.ts +32 -47
- package/dist/build/index.js +9 -135
- package/dist/build/index.js.map +1 -1
- package/dist/chunk-33DMFKKD.js +111 -0
- package/dist/chunk-33DMFKKD.js.map +1 -0
- package/dist/chunk-4HC3OWAQ.js +185 -0
- package/dist/chunk-4HC3OWAQ.js.map +1 -0
- package/dist/chunk-4WAKOYMK.js +196 -0
- package/dist/chunk-4WAKOYMK.js.map +1 -0
- package/dist/chunk-5EHNJCMF.js +58 -0
- package/dist/chunk-5EHNJCMF.js.map +1 -0
- package/dist/chunk-6QHKKFO3.js +30 -0
- package/dist/chunk-6QHKKFO3.js.map +1 -0
- package/dist/chunk-DKK26TRS.js +199 -0
- package/dist/chunk-DKK26TRS.js.map +1 -0
- package/dist/{chunk-A64ZILAS.js → chunk-DQLQOMDM.js} +2 -1
- package/dist/{chunk-A64ZILAS.js.map → chunk-DQLQOMDM.js.map} +1 -1
- package/dist/chunk-KRAS266U.js +260 -0
- package/dist/chunk-KRAS266U.js.map +1 -0
- package/dist/chunk-NLQDVNZP.js +115 -0
- package/dist/chunk-NLQDVNZP.js.map +1 -0
- package/dist/chunk-SNWNF2XI.js +171 -0
- package/dist/chunk-SNWNF2XI.js.map +1 -0
- package/dist/chunk-UECZA34Q.js +52 -0
- package/dist/chunk-UECZA34Q.js.map +1 -0
- package/dist/chunk-YJA4H6IB.js +13 -0
- package/dist/chunk-YJA4H6IB.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +286 -0
- package/dist/cli.js.map +1 -0
- package/dist/connect-server-7L4HGKKL.js +7 -0
- package/dist/connect-server-7L4HGKKL.js.map +1 -0
- package/dist/dev/connect-server.d.ts +13 -0
- package/dist/dev/host-entry.d.ts +13 -0
- package/dist/dev/host-entry.js +18330 -0
- package/dist/dev/index.d.ts +45 -0
- package/dist/dev/index.js +14 -0
- package/dist/dev/index.js.map +1 -0
- package/dist/dev/sandbox-entry.d.ts +7 -0
- package/dist/dev/sandbox-entry.js +80 -0
- package/dist/html.d.ts +4 -0
- package/dist/index.d.ts +6 -26
- package/dist/index.js +8 -3
- package/dist/json-render/app.d.ts +22 -0
- package/dist/json-render/app.js +10 -0
- package/dist/json-render/app.js.map +1 -0
- package/dist/json-render/index.d.ts +10 -0
- package/dist/json-render/index.js +119 -0
- package/dist/json-render/index.js.map +1 -0
- package/dist/json-render/mcp.d.ts +49 -0
- package/dist/json-render/mcp.js +11 -0
- package/dist/json-render/mcp.js.map +1 -0
- package/dist/json-render/server.d.ts +72 -0
- package/dist/json-render/server.js +98 -0
- package/dist/json-render/server.js.map +1 -0
- package/dist/json-render/spec-stream.d.ts +26 -0
- package/dist/json-render/use-spec-stream.d.ts +26 -0
- package/dist/json-render/widget.d.ts +59 -0
- package/dist/react/context.d.ts +14 -0
- package/dist/react/create-widget.d.ts +11 -0
- package/dist/react/hooks.d.ts +46 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.js +35 -0
- package/dist/react/index.js.map +1 -0
- package/dist/sandbox/index.d.ts +16 -0
- package/dist/sandbox/index.js +8 -0
- package/dist/sandbox/index.js.map +1 -0
- package/dist/schema.d.ts +20 -0
- package/dist/server/index.d.ts +73 -0
- package/dist/server/index.js +235 -0
- package/dist/server/index.js.map +1 -0
- package/dist/svelte/index.d.ts +35 -0
- package/dist/svelte/index.js +9 -0
- package/dist/svelte/index.js.map +1 -0
- package/dist/testing/index.d.ts +27 -0
- package/dist/testing/index.js +312 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/render-artifact.d.ts +35 -0
- package/dist/types.d.ts +55 -0
- package/dist/vite/index.d.ts +23 -0
- package/dist/vite/index.js +211 -0
- package/dist/vite/index.js.map +1 -0
- package/dist/vue/index.d.ts +56 -0
- package/dist/vue/index.js +89 -0
- package/dist/vue/index.js.map +1 -0
- package/package.json +166 -20
- package/dist/chunk-BUGGLMNA.js +0 -132
- package/dist/chunk-BUGGLMNA.js.map +0 -1
- package/dist/types-B9JAWm4W.d.ts +0 -62
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// src/json-render/mcp.ts
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import {
|
|
5
|
+
EXTENSION_ID,
|
|
6
|
+
RESOURCE_MIME_TYPE,
|
|
7
|
+
getUiCapability,
|
|
8
|
+
registerAppResource,
|
|
9
|
+
registerAppTool
|
|
10
|
+
} from "@modelcontextprotocol/ext-apps/server";
|
|
11
|
+
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
12
|
+
import { InMemoryTransport } from "@modelcontextprotocol/sdk/inMemory.js";
|
|
13
|
+
var DEFAULT_RESOURCE_URI = "ui://json-render/view.html";
|
|
14
|
+
var DEFAULT_TOOL_NAME = "render_ui";
|
|
15
|
+
var LOOSE_SPEC_SCHEMA = z.looseObject({
|
|
16
|
+
root: z.string(),
|
|
17
|
+
elements: z.record(z.string(), z.unknown()),
|
|
18
|
+
state: z.unknown().optional()
|
|
19
|
+
});
|
|
20
|
+
function resolveInputSchema(options) {
|
|
21
|
+
if (options.inputSchema) return options.inputSchema;
|
|
22
|
+
const { catalog } = options;
|
|
23
|
+
if (typeof catalog.zodSchema !== "function") {
|
|
24
|
+
return { spec: LOOSE_SPEC_SCHEMA };
|
|
25
|
+
}
|
|
26
|
+
return { spec: z.union([catalog.zodSchema(), LOOSE_SPEC_SCHEMA]) };
|
|
27
|
+
}
|
|
28
|
+
function resolveDescription(options) {
|
|
29
|
+
if (options.description) return options.description;
|
|
30
|
+
const base = "Render an interactive UI. The `spec` argument must be a json-render spec conforming to the catalog.";
|
|
31
|
+
const prompt = typeof options.catalog.prompt === "function" ? options.catalog.prompt() : "";
|
|
32
|
+
return prompt ? `${base}
|
|
33
|
+
|
|
34
|
+
${prompt}` : base;
|
|
35
|
+
}
|
|
36
|
+
function pickToolInputSpec(args) {
|
|
37
|
+
if (!args || typeof args !== "object") return args;
|
|
38
|
+
const record = args;
|
|
39
|
+
if (record.spec !== void 0) return record.spec;
|
|
40
|
+
return record;
|
|
41
|
+
}
|
|
42
|
+
function assertUiUri(uri) {
|
|
43
|
+
if (!uri.startsWith("ui://")) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`mountly-mcp/json-render: resourceUri must use 'ui://' scheme (received '${uri}')`
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function registerJsonRenderResource(server, options) {
|
|
50
|
+
const uri = options.resourceUri ?? DEFAULT_RESOURCE_URI;
|
|
51
|
+
assertUiUri(uri);
|
|
52
|
+
const name = options.name ?? "json_render_ui";
|
|
53
|
+
const description = options.description ?? "Interactive json-render MCP App view.";
|
|
54
|
+
const registered = registerAppResource(
|
|
55
|
+
server,
|
|
56
|
+
name,
|
|
57
|
+
uri,
|
|
58
|
+
{
|
|
59
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
60
|
+
description,
|
|
61
|
+
_meta: {
|
|
62
|
+
ui: {
|
|
63
|
+
prefersBorder: true,
|
|
64
|
+
csp: { connectDomains: [], resourceDomains: [], frameDomains: [] }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
async () => ({
|
|
69
|
+
contents: [
|
|
70
|
+
{
|
|
71
|
+
uri,
|
|
72
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
73
|
+
text: options.html,
|
|
74
|
+
_meta: {
|
|
75
|
+
ui: {
|
|
76
|
+
prefersBorder: true,
|
|
77
|
+
csp: { connectDomains: [], resourceDomains: [], frameDomains: [] }
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
})
|
|
83
|
+
);
|
|
84
|
+
return { uri, remove: () => registered.remove() };
|
|
85
|
+
}
|
|
86
|
+
function registerJsonRenderTool(server, options) {
|
|
87
|
+
const name = options.name ?? DEFAULT_TOOL_NAME;
|
|
88
|
+
const resourceUri = options.resourceUri ?? DEFAULT_RESOURCE_URI;
|
|
89
|
+
assertUiUri(resourceUri);
|
|
90
|
+
const description = resolveDescription(options);
|
|
91
|
+
const visibility = options.visibility === void 0 ? ["model", "app"] : typeof options.visibility === "string" ? [options.visibility] : [...options.visibility];
|
|
92
|
+
const registered = registerAppTool(
|
|
93
|
+
server,
|
|
94
|
+
name,
|
|
95
|
+
{
|
|
96
|
+
title: options.title,
|
|
97
|
+
description,
|
|
98
|
+
inputSchema: resolveInputSchema(options),
|
|
99
|
+
_meta: {
|
|
100
|
+
ui: {
|
|
101
|
+
resourceUri,
|
|
102
|
+
visibility
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
(async (args) => {
|
|
107
|
+
const spec = pickToolInputSpec(args);
|
|
108
|
+
const validation = options.catalog.validate?.(spec);
|
|
109
|
+
const validSpec = validation?.success ? validation.data : spec;
|
|
110
|
+
return {
|
|
111
|
+
// Two shapes on purpose. `content[0].text` is the wire format
|
|
112
|
+
// `@json-render/mcp` emits and its iframe hook reads, so an app built
|
|
113
|
+
// against that package renders a Mountly server unchanged.
|
|
114
|
+
// `structuredContent` is what Mountly's own hosts and tests read.
|
|
115
|
+
content: [{ type: "text", text: JSON.stringify(validSpec) }],
|
|
116
|
+
structuredContent: { spec: validSpec }
|
|
117
|
+
};
|
|
118
|
+
})
|
|
119
|
+
);
|
|
120
|
+
return { name, remove: () => registered.remove() };
|
|
121
|
+
}
|
|
122
|
+
function createJsonRenderMcpApp(options) {
|
|
123
|
+
const configured = new McpServer(
|
|
124
|
+
{ name: options.name, version: options.version },
|
|
125
|
+
{ capabilities: { resources: {}, tools: {} } }
|
|
126
|
+
);
|
|
127
|
+
configured.server.registerCapabilities({
|
|
128
|
+
extensions: { [EXTENSION_ID]: { mimeTypes: [RESOURCE_MIME_TYPE] } }
|
|
129
|
+
});
|
|
130
|
+
const resourceUri = options.tool?.resourceUri ?? DEFAULT_RESOURCE_URI;
|
|
131
|
+
assertUiUri(resourceUri);
|
|
132
|
+
const toolName = options.tool?.name ?? DEFAULT_TOOL_NAME;
|
|
133
|
+
const resource = registerJsonRenderResource(configured, {
|
|
134
|
+
resourceUri,
|
|
135
|
+
html: options.html,
|
|
136
|
+
name: "json_render_ui"
|
|
137
|
+
});
|
|
138
|
+
const tool = registerJsonRenderTool(configured, {
|
|
139
|
+
catalog: options.catalog,
|
|
140
|
+
name: toolName,
|
|
141
|
+
title: options.tool?.title,
|
|
142
|
+
description: options.tool?.description,
|
|
143
|
+
resourceUri,
|
|
144
|
+
visibility: options.tool?.visibility,
|
|
145
|
+
inputSchema: options.tool?.inputSchema
|
|
146
|
+
});
|
|
147
|
+
const previousInitialized = configured.server.oninitialized;
|
|
148
|
+
const handleInitialized = () => {
|
|
149
|
+
previousInitialized?.();
|
|
150
|
+
const ui = getUiCapability(configured.server.getClientCapabilities());
|
|
151
|
+
if (ui?.mimeTypes?.includes(RESOURCE_MIME_TYPE)) return;
|
|
152
|
+
resource.remove();
|
|
153
|
+
tool.remove();
|
|
154
|
+
};
|
|
155
|
+
configured.server.oninitialized = handleInitialized;
|
|
156
|
+
return {
|
|
157
|
+
async connectInProcess(connectOptions) {
|
|
158
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
159
|
+
await configured.connect(serverTransport);
|
|
160
|
+
const client = new Client(
|
|
161
|
+
{ name: `${options.name}-inprocess-client`, version: options.version },
|
|
162
|
+
connectOptions?.ui ?? true ? {
|
|
163
|
+
capabilities: {
|
|
164
|
+
extensions: { [EXTENSION_ID]: { mimeTypes: [RESOURCE_MIME_TYPE] } }
|
|
165
|
+
}
|
|
166
|
+
} : void 0
|
|
167
|
+
);
|
|
168
|
+
await client.connect(clientTransport);
|
|
169
|
+
return { client };
|
|
170
|
+
},
|
|
171
|
+
async listen(transport) {
|
|
172
|
+
await configured.connect(transport);
|
|
173
|
+
},
|
|
174
|
+
async close() {
|
|
175
|
+
await configured.close();
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export {
|
|
181
|
+
registerJsonRenderResource,
|
|
182
|
+
registerJsonRenderTool,
|
|
183
|
+
createJsonRenderMcpApp
|
|
184
|
+
};
|
|
185
|
+
//# sourceMappingURL=chunk-4HC3OWAQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/json-render/mcp.ts"],"sourcesContent":["import { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { z } from \"zod\";\nimport {\n EXTENSION_ID,\n RESOURCE_MIME_TYPE,\n getUiCapability,\n registerAppResource,\n registerAppTool,\n} from \"@modelcontextprotocol/ext-apps/server\";\nimport type { McpUiToolVisibility } from \"@modelcontextprotocol/ext-apps\";\nimport type { Catalog } from \"@json-render/core\";\nimport { Client } from \"@modelcontextprotocol/sdk/client/index.js\";\nimport { InMemoryTransport } from \"@modelcontextprotocol/sdk/inMemory.js\";\nimport type { Transport } from \"@modelcontextprotocol/sdk/shared/transport.js\";\nimport type { RunningMcpAppServer } from \"../server/index.js\";\n\nexport interface JsonRenderToolOptions {\n /** MCP tool name. */\n name?: string;\n /** Human-readable tool title. */\n title?: string;\n /** Tool description for model selection. */\n description?: string;\n /** ui:// resource linked to this tool. */\n resourceUri?: string;\n /** Tool visibility in MCP Apps hosts. */\n visibility?: McpUiToolVisibility | ReadonlyArray<McpUiToolVisibility>;\n /** Optional explicit input schema override. */\n inputSchema?: object;\n}\n\nexport interface RegisterJsonRenderResourceOptions {\n resourceUri?: string;\n name?: string;\n description?: string;\n html: string;\n}\n\nexport interface RegisterJsonRenderToolOptions {\n catalog: Catalog;\n name?: string;\n title?: string;\n description?: string;\n resourceUri?: string;\n visibility?: McpUiToolVisibility | ReadonlyArray<McpUiToolVisibility>;\n inputSchema?: object;\n}\n\nexport interface CreateJsonRenderMcpAppOptions {\n name: string;\n version: string;\n catalog: Catalog;\n html: string;\n tool?: JsonRenderToolOptions;\n}\n\nconst DEFAULT_RESOURCE_URI = \"ui://json-render/view.html\";\nconst DEFAULT_TOOL_NAME = \"render_ui\";\n\n/**\n * A spec shape loose enough to survive real model output. `@json-render/core`\n * generates a *strict* catalog schema: as of 0.20 `visible` is optional, but\n * `children` is still required on every element — the omission models make on\n * roughly a third of first attempts. Gating tool input on the strict schema\n * alone turns that into an SDK-level rejection before the handler can repair\n * anything. (`@json-render/mcp` passes the strict schema straight through and\n * inherits that failure.) Delete this branch once `children` is optional too.\n */\nconst LOOSE_SPEC_SCHEMA = z.looseObject({\n root: z.string(),\n elements: z.record(z.string(), z.unknown()),\n state: z.unknown().optional(),\n});\n\n/**\n * The tool's input shape, matching `@json-render/mcp`: a single `spec`\n * argument typed by the catalog. Must be a Zod *raw shape* (a plain object of\n * Zod types) — handing the SDK a bare `ZodType` yields an empty\n * `{ type: \"object\", properties: {} }` schema, which tells the model nothing.\n */\nfunction resolveInputSchema(options: RegisterJsonRenderToolOptions): object {\n if (options.inputSchema) return options.inputSchema;\n const { catalog } = options;\n if (typeof catalog.zodSchema !== \"function\") {\n return { spec: LOOSE_SPEC_SCHEMA };\n }\n // The catalog branch documents the exact component vocabulary to the model;\n // the loose branch keeps a near-miss spec from being rejected outright. The\n // handler still validates against the catalog and reports what it repaired.\n return { spec: z.union([catalog.zodSchema(), LOOSE_SPEC_SCHEMA]) };\n}\n\n/**\n * Catalogs carry their own model-facing prompt (component list, prop shapes,\n * authoring rules). Appending it is what makes the tool usable by a model —\n * a bare one-line description leaves it guessing at the component vocabulary.\n */\nfunction resolveDescription(options: RegisterJsonRenderToolOptions): string {\n if (options.description) return options.description;\n const base =\n \"Render an interactive UI. The `spec` argument must be a json-render spec conforming to the catalog.\";\n const prompt = typeof options.catalog.prompt === \"function\" ? options.catalog.prompt() : \"\";\n return prompt ? `${base}\\n\\n${prompt}` : base;\n}\n\n/**\n * Unwrap the tool arguments. The declared shape is `{ spec }`, but a spec\n * passed at the top level is accepted too — hosts and hand-written clients do\n * both, and a spec is identifiable by its `root`/`elements` pair.\n */\nfunction pickToolInputSpec(args: unknown): unknown {\n if (!args || typeof args !== \"object\") return args;\n const record = args as Record<string, unknown>;\n if (record.spec !== undefined) return record.spec;\n return record;\n}\n\nfunction assertUiUri(uri: string): void {\n if (!uri.startsWith(\"ui://\")) {\n throw new Error(\n `mountly-mcp/json-render: resourceUri must use 'ui://' scheme (received '${uri}')`,\n );\n }\n}\n\nexport function registerJsonRenderResource(\n server: McpServer,\n options: RegisterJsonRenderResourceOptions,\n): { uri: string; remove(): void } {\n const uri = options.resourceUri ?? DEFAULT_RESOURCE_URI;\n assertUiUri(uri);\n const name = options.name ?? \"json_render_ui\";\n const description = options.description ?? \"Interactive json-render MCP App view.\";\n const registered = registerAppResource(\n server,\n name,\n uri,\n {\n mimeType: RESOURCE_MIME_TYPE,\n description,\n _meta: {\n ui: {\n prefersBorder: true,\n csp: { connectDomains: [], resourceDomains: [], frameDomains: [] },\n },\n },\n },\n async () => ({\n contents: [\n {\n uri,\n mimeType: RESOURCE_MIME_TYPE,\n text: options.html,\n _meta: {\n ui: {\n prefersBorder: true,\n csp: { connectDomains: [], resourceDomains: [], frameDomains: [] },\n },\n },\n },\n ],\n }),\n );\n return { uri, remove: () => registered.remove() };\n}\n\nexport function registerJsonRenderTool(\n server: McpServer,\n options: RegisterJsonRenderToolOptions,\n): { name: string; remove(): void } {\n const name = options.name ?? DEFAULT_TOOL_NAME;\n const resourceUri = options.resourceUri ?? DEFAULT_RESOURCE_URI;\n assertUiUri(resourceUri);\n const description = resolveDescription(options);\n const visibility =\n options.visibility === undefined\n ? [\"model\", \"app\"]\n : typeof options.visibility === \"string\"\n ? [options.visibility]\n : [...options.visibility];\n\n const registered = registerAppTool(\n server,\n name,\n {\n title: options.title,\n description,\n inputSchema: resolveInputSchema(options) as never,\n _meta: {\n ui: {\n resourceUri,\n visibility,\n },\n },\n } as never,\n (async (args: unknown) => {\n const spec = pickToolInputSpec(args);\n // Repair-tolerant, like `@json-render/mcp`: a spec that fails catalog\n // validation is still rendered rather than dropped — the renderer skips\n // what it cannot resolve, and a partial UI beats a blank iframe.\n const validation = options.catalog.validate?.(spec);\n const validSpec = validation?.success ? validation.data : spec;\n return {\n // Two shapes on purpose. `content[0].text` is the wire format\n // `@json-render/mcp` emits and its iframe hook reads, so an app built\n // against that package renders a Mountly server unchanged.\n // `structuredContent` is what Mountly's own hosts and tests read.\n content: [{ type: \"text\", text: JSON.stringify(validSpec) }],\n structuredContent: { spec: validSpec },\n };\n }) as never,\n );\n return { name, remove: () => registered.remove() };\n}\n\nexport function createJsonRenderMcpApp(\n options: CreateJsonRenderMcpAppOptions,\n): RunningMcpAppServer {\n const configured = new McpServer(\n { name: options.name, version: options.version },\n { capabilities: { resources: {}, tools: {} } },\n );\n configured.server.registerCapabilities({\n extensions: { [EXTENSION_ID]: { mimeTypes: [RESOURCE_MIME_TYPE] } },\n } as never);\n\n const resourceUri = options.tool?.resourceUri ?? DEFAULT_RESOURCE_URI;\n assertUiUri(resourceUri);\n const toolName = options.tool?.name ?? DEFAULT_TOOL_NAME;\n const resource = registerJsonRenderResource(configured, {\n resourceUri,\n html: options.html,\n name: \"json_render_ui\",\n });\n const tool = registerJsonRenderTool(configured, {\n catalog: options.catalog,\n name: toolName,\n title: options.tool?.title,\n description: options.tool?.description,\n resourceUri,\n visibility: options.tool?.visibility,\n inputSchema: options.tool?.inputSchema,\n });\n const previousInitialized = configured.server.oninitialized;\n const handleInitialized = () => {\n previousInitialized?.();\n const ui = getUiCapability(configured.server.getClientCapabilities());\n if (ui?.mimeTypes?.includes(RESOURCE_MIME_TYPE)) return;\n resource.remove();\n tool.remove();\n };\n configured.server.oninitialized = handleInitialized;\n\n return {\n async connectInProcess(connectOptions) {\n const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();\n await configured.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: Transport) {\n await configured.connect(transport);\n },\n async close() {\n await configured.close();\n },\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import {
|
|
2
|
+
LATEST_PROTOCOL_VERSION,
|
|
3
|
+
RESOURCE_MIME_TYPE
|
|
4
|
+
} from "./chunk-DQLQOMDM.js";
|
|
5
|
+
import {
|
|
6
|
+
escapeInlineScript,
|
|
7
|
+
serializeInlineScriptValue
|
|
8
|
+
} from "./chunk-YJA4H6IB.js";
|
|
9
|
+
|
|
10
|
+
// src/build/index.ts
|
|
11
|
+
import { readFile, writeFile } from "fs/promises";
|
|
12
|
+
import { fileURLToPath } from "url";
|
|
13
|
+
import { resolve } from "path";
|
|
14
|
+
|
|
15
|
+
// src/build/emit-html.ts
|
|
16
|
+
function escapeAttr(value) {
|
|
17
|
+
return value.replace(/&/g, "&").replace(/"/g, """).replace(/</g, "<").replace(/>/g, ">");
|
|
18
|
+
}
|
|
19
|
+
function escapeStyle(value) {
|
|
20
|
+
return value.replace(/<\/style/gi, "<\\/style");
|
|
21
|
+
}
|
|
22
|
+
function configScript(config) {
|
|
23
|
+
if (!config) return [];
|
|
24
|
+
const assignments = [];
|
|
25
|
+
if (config.awaitToolResult !== void 0) {
|
|
26
|
+
assignments.push(
|
|
27
|
+
`globalThis.__mountlyMcpAwaitToolResult__=${serializeInlineScriptValue(config.awaitToolResult)};`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
if (config.displayModes !== void 0) {
|
|
31
|
+
assignments.push(
|
|
32
|
+
`globalThis.__mountlyMcpAvailableDisplayModes__=${serializeInlineScriptValue(config.displayModes)};`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
if (config.streamToolInput !== void 0) {
|
|
36
|
+
assignments.push(
|
|
37
|
+
`globalThis.__mountlyMcpStreamToolInput__=${serializeInlineScriptValue(config.streamToolInput)};`
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
if (assignments.length === 0) return [];
|
|
41
|
+
return [`<script>${assignments.join("")}</script>`];
|
|
42
|
+
}
|
|
43
|
+
function emitHtml(input) {
|
|
44
|
+
const head = [
|
|
45
|
+
`<meta charset="utf-8">`,
|
|
46
|
+
`<meta name="mountly-mcp-protocol" content="${LATEST_PROTOCOL_VERSION}">`,
|
|
47
|
+
`<meta name="mountly-mcp-uri" content="${escapeAttr(input.uri)}">`
|
|
48
|
+
];
|
|
49
|
+
if (input.mode === "self-contained") {
|
|
50
|
+
head.push(`<style>${escapeStyle(input.css)}</style>`);
|
|
51
|
+
const body2 = [
|
|
52
|
+
`<div id="mountly-mcp-root"></div>`,
|
|
53
|
+
...configScript(input.config),
|
|
54
|
+
`<script type="module">${escapeInlineScript(input.js)}
|
|
55
|
+
${escapeInlineScript(input.bridgeRuntimeJs)}</script>`
|
|
56
|
+
];
|
|
57
|
+
return template(head, body2);
|
|
58
|
+
}
|
|
59
|
+
head.push(`<link rel="stylesheet" href="${escapeAttr(input.cssUrl)}">`);
|
|
60
|
+
const body = [
|
|
61
|
+
`<div id="mountly-mcp-root"></div>`,
|
|
62
|
+
...configScript(input.config),
|
|
63
|
+
`<script type="module" src="${escapeAttr(input.jsUrl)}"></script>`,
|
|
64
|
+
`<script type="module">${escapeInlineScript(input.bridgeRuntimeJs)}</script>`
|
|
65
|
+
];
|
|
66
|
+
return template(head, body);
|
|
67
|
+
}
|
|
68
|
+
function template(head, body) {
|
|
69
|
+
return `<!doctype html>
|
|
70
|
+
<html data-mountly-mcp>
|
|
71
|
+
<head>
|
|
72
|
+
${head.join("\n")}
|
|
73
|
+
</head>
|
|
74
|
+
<body>
|
|
75
|
+
${body.join("\n")}
|
|
76
|
+
</body>
|
|
77
|
+
</html>`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// src/build/emit-meta.ts
|
|
81
|
+
function assertUiUri(uri) {
|
|
82
|
+
if (!uri.startsWith("ui://")) {
|
|
83
|
+
throw new Error(`mountly-mcp: UI resource uri must use the 'ui://' scheme (received '${uri}')`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function emitMeta(input) {
|
|
87
|
+
assertUiUri(input.uri);
|
|
88
|
+
const ui = {};
|
|
89
|
+
if (input.csp) ui.csp = input.csp;
|
|
90
|
+
if (input.permissions) ui.permissions = input.permissions;
|
|
91
|
+
if (input.domain !== void 0) ui.domain = input.domain;
|
|
92
|
+
if (input.prefersBorder !== void 0) ui.prefersBorder = input.prefersBorder;
|
|
93
|
+
const decl = {
|
|
94
|
+
protocolVersion: LATEST_PROTOCOL_VERSION,
|
|
95
|
+
uri: input.uri,
|
|
96
|
+
name: input.name,
|
|
97
|
+
mimeType: RESOURCE_MIME_TYPE,
|
|
98
|
+
awaitToolResult: input.awaitToolResult ?? true,
|
|
99
|
+
displayModes: input.displayModes ?? ["inline"],
|
|
100
|
+
_meta: { ui }
|
|
101
|
+
};
|
|
102
|
+
if (input.description !== void 0) decl.description = input.description;
|
|
103
|
+
return decl;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// src/build/index.ts
|
|
107
|
+
function getBridgeRuntimePath() {
|
|
108
|
+
return fileURLToPath(import.meta.resolve("mountly-mcp/bridge/iframe-entry.js"));
|
|
109
|
+
}
|
|
110
|
+
function defaultBridgeRuntimePath() {
|
|
111
|
+
return getBridgeRuntimePath();
|
|
112
|
+
}
|
|
113
|
+
async function buildMcpResourceFromSource(options) {
|
|
114
|
+
const { js, css, ...rest } = options;
|
|
115
|
+
return writeResource({ ...rest, mode: "self-contained", js, css: css ?? "" });
|
|
116
|
+
}
|
|
117
|
+
async function buildMcpResource(options) {
|
|
118
|
+
if (!("cdn" in options)) {
|
|
119
|
+
const js = await readFile(options.entry, "utf8");
|
|
120
|
+
const css = options.cssEntry ? await readFile(options.cssEntry, "utf8") : "";
|
|
121
|
+
return writeResource({ ...options, mode: "self-contained", js, css });
|
|
122
|
+
}
|
|
123
|
+
return writeResource({ ...options, mode: "cdn" });
|
|
124
|
+
}
|
|
125
|
+
async function writeResource(options) {
|
|
126
|
+
const bridgeRuntimeJs = await readFile(
|
|
127
|
+
options.bridgeRuntimePath ?? defaultBridgeRuntimePath(),
|
|
128
|
+
"utf8"
|
|
129
|
+
);
|
|
130
|
+
const declaration = emitMeta({
|
|
131
|
+
uri: options.uri,
|
|
132
|
+
name: options.name,
|
|
133
|
+
description: options.description,
|
|
134
|
+
awaitToolResult: options.awaitToolResult,
|
|
135
|
+
displayModes: options.displayModes,
|
|
136
|
+
csp: options.mode === "cdn" ? mergeCdnIntoCsp(options.csp, options.cdn) : options.csp,
|
|
137
|
+
permissions: options.permissions,
|
|
138
|
+
domain: options.domain,
|
|
139
|
+
prefersBorder: options.prefersBorder
|
|
140
|
+
});
|
|
141
|
+
const config = {
|
|
142
|
+
awaitToolResult: declaration.awaitToolResult,
|
|
143
|
+
displayModes: declaration.displayModes,
|
|
144
|
+
streamToolInput: options.streamToolInput ?? false
|
|
145
|
+
};
|
|
146
|
+
const html = options.mode === "cdn" ? emitHtml({
|
|
147
|
+
mode: "cdn",
|
|
148
|
+
uri: options.uri,
|
|
149
|
+
jsUrl: options.cdn.jsUrl,
|
|
150
|
+
cssUrl: options.cdn.cssUrl,
|
|
151
|
+
bridgeRuntimeJs,
|
|
152
|
+
config
|
|
153
|
+
}) : emitHtml({
|
|
154
|
+
mode: "self-contained",
|
|
155
|
+
uri: options.uri,
|
|
156
|
+
js: options.js,
|
|
157
|
+
css: options.css,
|
|
158
|
+
bridgeRuntimeJs,
|
|
159
|
+
config
|
|
160
|
+
});
|
|
161
|
+
const metaPath = `${options.output}.meta.json`;
|
|
162
|
+
await writeFile(options.output, html, "utf8");
|
|
163
|
+
await writeFile(metaPath, JSON.stringify(declaration, null, 2), "utf8");
|
|
164
|
+
const htmlPath = resolve(options.output);
|
|
165
|
+
return {
|
|
166
|
+
htmlPath,
|
|
167
|
+
metaPath: resolve(metaPath),
|
|
168
|
+
declaration,
|
|
169
|
+
artifact: {
|
|
170
|
+
name: declaration.name,
|
|
171
|
+
uri: declaration.uri,
|
|
172
|
+
htmlPath,
|
|
173
|
+
metaPath: resolve(metaPath),
|
|
174
|
+
declaration
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function mergeCdnIntoCsp(csp, cdn) {
|
|
179
|
+
const jsOrigin = new URL(cdn.jsUrl).origin;
|
|
180
|
+
const cssOrigin = new URL(cdn.cssUrl).origin;
|
|
181
|
+
const merged = { ...csp ?? {} };
|
|
182
|
+
const existing = new Set(merged.resourceDomains ?? []);
|
|
183
|
+
existing.add(jsOrigin);
|
|
184
|
+
existing.add(cssOrigin);
|
|
185
|
+
merged.resourceDomains = Array.from(existing);
|
|
186
|
+
return merged;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export {
|
|
190
|
+
emitHtml,
|
|
191
|
+
emitMeta,
|
|
192
|
+
getBridgeRuntimePath,
|
|
193
|
+
buildMcpResourceFromSource,
|
|
194
|
+
buildMcpResource
|
|
195
|
+
};
|
|
196
|
+
//# sourceMappingURL=chunk-4WAKOYMK.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 { 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 widget 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 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 widget JS. */\n js: string;\n /** The widget'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 });\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, \"&\")\n .replace(/\"/g, \""\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\");\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 widget bundle MUST load before the bridge: module scripts execute in\n // document order, and the bridge reads `__mountlyMcpWidget__` 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 { 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}\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.description !== undefined) decl.description = input.description;\n return decl;\n}\n"],"mappings":"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","names":["body"]}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// src/json-render/spec-stream.ts
|
|
2
|
+
import { createSpecStreamCompiler } from "@json-render/core";
|
|
3
|
+
import {
|
|
4
|
+
compileSpecStream,
|
|
5
|
+
createJsonRenderTransform,
|
|
6
|
+
createSpecStreamCompiler as createSpecStreamCompiler2,
|
|
7
|
+
parseSpecStreamLine
|
|
8
|
+
} from "@json-render/core";
|
|
9
|
+
async function* compileTextStreamToSpecs(textStream) {
|
|
10
|
+
const compiler = createSpecStreamCompiler();
|
|
11
|
+
let buffer = "";
|
|
12
|
+
for await (const delta of textStream) {
|
|
13
|
+
buffer += delta;
|
|
14
|
+
const lines = buffer.split("\n");
|
|
15
|
+
buffer = lines.pop() ?? "";
|
|
16
|
+
for (const line of lines) {
|
|
17
|
+
if (!line.trim()) continue;
|
|
18
|
+
const { result } = compiler.push(`${line}
|
|
19
|
+
`);
|
|
20
|
+
yield { ...result };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
if (buffer.trim()) {
|
|
24
|
+
const { result } = compiler.push(`${buffer}
|
|
25
|
+
`);
|
|
26
|
+
yield { ...result };
|
|
27
|
+
}
|
|
28
|
+
return compiler.getResult();
|
|
29
|
+
}
|
|
30
|
+
function specToPatchLines(spec) {
|
|
31
|
+
const lines = [JSON.stringify({ op: "add", path: "/elements", value: {} })];
|
|
32
|
+
if (spec.state !== void 0) {
|
|
33
|
+
lines.push(JSON.stringify({ op: "add", path: "/state", value: spec.state }));
|
|
34
|
+
}
|
|
35
|
+
lines.push(JSON.stringify({ op: "add", path: "/root", value: spec.root }));
|
|
36
|
+
const seen = /* @__PURE__ */ new Set();
|
|
37
|
+
const queue = [spec.root];
|
|
38
|
+
while (queue.length) {
|
|
39
|
+
const key = queue.shift();
|
|
40
|
+
if (seen.has(key) || !spec.elements[key]) continue;
|
|
41
|
+
seen.add(key);
|
|
42
|
+
const el = spec.elements[key];
|
|
43
|
+
const path = `/elements/${key.replace(/~/g, "~0").replace(/\//g, "~1")}`;
|
|
44
|
+
lines.push(JSON.stringify({ op: "add", path, value: el }));
|
|
45
|
+
for (const child of el.children ?? []) queue.push(child);
|
|
46
|
+
}
|
|
47
|
+
return lines;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export {
|
|
51
|
+
compileTextStreamToSpecs,
|
|
52
|
+
specToPatchLines,
|
|
53
|
+
compileSpecStream,
|
|
54
|
+
createJsonRenderTransform,
|
|
55
|
+
createSpecStreamCompiler2 as createSpecStreamCompiler,
|
|
56
|
+
parseSpecStreamLine
|
|
57
|
+
};
|
|
58
|
+
//# sourceMappingURL=chunk-5EHNJCMF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/json-render/spec-stream.ts"],"sourcesContent":["import { createSpecStreamCompiler } from \"@json-render/core\";\nimport type { Spec } from \"@json-render/core\";\n\n// json-render's streaming primitives, re-exported so the consumer side\n// (compile patches → progressive spec) is one import away.\nexport {\n compileSpecStream,\n createJsonRenderTransform,\n createSpecStreamCompiler,\n parseSpecStreamLine,\n} from \"@json-render/core\";\n\n/**\n * Drive json-render's stream compiler from a raw text stream of JSONL patches,\n * yielding the progressively-built spec after each complete line. This is the\n * live \"model token → patch → spec\" path, decoupled from any AI SDK so it can\n * drive a real model (see `streamSpec` in `mountly-mcp/json-render/server`), replay\n * a saved transcript, bridge a custom transport, or be unit-tested with a\n * hand-written stream. Buffers partial lines across chunk boundaries. The\n * generator's *return* value is the final compiled spec (before `autoFixSpec`).\n *\n * ```ts\n * for await (const partial of compileTextStreamToSpecs(result.textStream)) {\n * render(partial); // each yield is one more element built\n * }\n * ```\n */\nexport async function* compileTextStreamToSpecs(\n textStream: AsyncIterable<string>,\n): AsyncGenerator<Spec, Spec, void> {\n const compiler = createSpecStreamCompiler<Spec>();\n let buffer = \"\";\n for await (const delta of textStream) {\n buffer += delta;\n const lines = buffer.split(\"\\n\");\n // Keep the trailing partial line for the next chunk.\n buffer = lines.pop() ?? \"\";\n for (const line of lines) {\n if (!line.trim()) continue;\n const { result } = compiler.push(`${line}\\n`);\n yield { ...result };\n }\n }\n if (buffer.trim()) {\n const { result } = compiler.push(`${buffer}\\n`);\n yield { ...result };\n }\n return compiler.getResult();\n}\n\n/**\n * Turn a finished spec into json-render's streaming patch format — ordered\n * RFC-6902 JSONL lines (`/elements` scaffold, `/state`, `/root`, then each\n * element parent-before-child). Feed these to a `createSpecStreamCompiler` to\n * replay the build progressively, so a known spec streams in the same way a\n * live model would emit it. Pure spec work — no AI SDK dependency.\n */\nexport function specToPatchLines(spec: Spec): string[] {\n const lines: string[] = [JSON.stringify({ op: \"add\", path: \"/elements\", value: {} })];\n if (spec.state !== undefined) {\n lines.push(JSON.stringify({ op: \"add\", path: \"/state\", value: spec.state }));\n }\n lines.push(JSON.stringify({ op: \"add\", path: \"/root\", value: spec.root }));\n\n // Breadth-first from the root so parents stream before their children.\n const seen = new Set<string>();\n const queue = [spec.root];\n while (queue.length) {\n const key = queue.shift() as string;\n if (seen.has(key) || !spec.elements[key]) continue;\n seen.add(key);\n const el = spec.elements[key];\n const path = `/elements/${key.replace(/~/g, \"~0\").replace(/\\//g, \"~1\")}`;\n lines.push(JSON.stringify({ op: \"add\", path, value: el }));\n for (const child of el.children ?? []) queue.push(child);\n }\n return lines;\n}\n"],"mappings":";AAAA,SAAS,gCAAgC;AAKzC;AAAA,EACE;AAAA,EACA;AAAA,EACA,4BAAAA;AAAA,EACA;AAAA,OACK;AAiBP,gBAAuB,yBACrB,YACkC;AAClC,QAAM,WAAW,yBAA+B;AAChD,MAAI,SAAS;AACb,mBAAiB,SAAS,YAAY;AACpC,cAAU;AACV,UAAM,QAAQ,OAAO,MAAM,IAAI;AAE/B,aAAS,MAAM,IAAI,KAAK;AACxB,eAAW,QAAQ,OAAO;AACxB,UAAI,CAAC,KAAK,KAAK,EAAG;AAClB,YAAM,EAAE,OAAO,IAAI,SAAS,KAAK,GAAG,IAAI;AAAA,CAAI;AAC5C,YAAM,EAAE,GAAG,OAAO;AAAA,IACpB;AAAA,EACF;AACA,MAAI,OAAO,KAAK,GAAG;AACjB,UAAM,EAAE,OAAO,IAAI,SAAS,KAAK,GAAG,MAAM;AAAA,CAAI;AAC9C,UAAM,EAAE,GAAG,OAAO;AAAA,EACpB;AACA,SAAO,SAAS,UAAU;AAC5B;AASO,SAAS,iBAAiB,MAAsB;AACrD,QAAM,QAAkB,CAAC,KAAK,UAAU,EAAE,IAAI,OAAO,MAAM,aAAa,OAAO,CAAC,EAAE,CAAC,CAAC;AACpF,MAAI,KAAK,UAAU,QAAW;AAC5B,UAAM,KAAK,KAAK,UAAU,EAAE,IAAI,OAAO,MAAM,UAAU,OAAO,KAAK,MAAM,CAAC,CAAC;AAAA,EAC7E;AACA,QAAM,KAAK,KAAK,UAAU,EAAE,IAAI,OAAO,MAAM,SAAS,OAAO,KAAK,KAAK,CAAC,CAAC;AAGzE,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,QAAQ,CAAC,KAAK,IAAI;AACxB,SAAO,MAAM,QAAQ;AACnB,UAAM,MAAM,MAAM,MAAM;AACxB,QAAI,KAAK,IAAI,GAAG,KAAK,CAAC,KAAK,SAAS,GAAG,EAAG;AAC1C,SAAK,IAAI,GAAG;AACZ,UAAM,KAAK,KAAK,SAAS,GAAG;AAC5B,UAAM,OAAO,aAAa,IAAI,QAAQ,MAAM,IAAI,EAAE,QAAQ,OAAO,IAAI,CAAC;AACtE,UAAM,KAAK,KAAK,UAAU,EAAE,IAAI,OAAO,MAAM,OAAO,GAAG,CAAC,CAAC;AACzD,eAAW,SAAS,GAAG,YAAY,CAAC,EAAG,OAAM,KAAK,KAAK;AAAA,EACzD;AACA,SAAO;AACT;","names":["createSpecStreamCompiler"]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
escapeInlineScript,
|
|
3
|
+
serializeInlineScriptValue
|
|
4
|
+
} from "./chunk-YJA4H6IB.js";
|
|
5
|
+
|
|
6
|
+
// src/sandbox/index.ts
|
|
7
|
+
function sandboxProxyHtml(hostOrigin, options = {}) {
|
|
8
|
+
const runtime = options.sandboxEntryScript ?? "";
|
|
9
|
+
return `<!doctype html>
|
|
10
|
+
<html lang="en">
|
|
11
|
+
<head>
|
|
12
|
+
<meta charset="utf-8">
|
|
13
|
+
<title>mountly-mcp sandbox proxy</title>
|
|
14
|
+
<style>
|
|
15
|
+
html, body { margin: 0; padding: 0; width: 100%; height: 100%; background: transparent; }
|
|
16
|
+
#inner { width: 100%; height: 100%; border: 0; background: white; }
|
|
17
|
+
</style>
|
|
18
|
+
</head>
|
|
19
|
+
<body>
|
|
20
|
+
<script>globalThis.__mountlyMcpSandbox__ = ${serializeInlineScriptValue({ hostOrigin })};</script>
|
|
21
|
+
<script>${escapeInlineScript(runtime)}</script>
|
|
22
|
+
</body>
|
|
23
|
+
</html>
|
|
24
|
+
`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export {
|
|
28
|
+
sandboxProxyHtml
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=chunk-6QHKKFO3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/sandbox/index.ts"],"sourcesContent":["/**\n * Standalone sandbox proxy HTML for external hosts (Vercel AI SDK, etc.).\n *\n * The dev host already builds this internally, but external hosts need it too —\n * every host has to serve the proxy page on a separate origin (spec §8.4), and\n * hand-writing it is error-prone and drifts from the canonical CSP/permissions\n * logic in `sandbox-entry`.\n *\n * The proxy page is a thin HTML shell: it sets `__mountlyMcpSandbox__` with the\n * host origin and then runs the sandbox entry script (an IIFE). Hosts provide\n * the entry script themselves because loading strategy varies — some inline it,\n * others serve it as a separate `<script src>`.\n */\nimport { escapeInlineScript, serializeInlineScriptValue } from \"../html.js\";\n\nexport interface SandboxProxyHtmlOptions {\n /**\n * The compiled sandbox entry IIFE to inline. When omitted the page emits an\n * empty `<script>` — the caller must arrange for the runtime to load another\n * way (e.g. a `<script src>` tag).\n */\n sandboxEntryScript?: string;\n}\n\n/**\n * Return the complete sandbox proxy HTML document for the given host origin.\n *\n * This is the security boundary described in MCP Apps §8.4: it runs on an\n * origin distinct from the host, receives the view HTML over postMessage,\n * injects the CSP the sidecar declared, and relays messages.\n */\nexport function sandboxProxyHtml(\n hostOrigin: string,\n options: SandboxProxyHtmlOptions = {},\n): string {\n const runtime = options.sandboxEntryScript ?? \"\";\n return `<!doctype html>\n<html lang=\"en\">\n<head>\n<meta charset=\"utf-8\">\n<title>mountly-mcp sandbox proxy</title>\n<style>\n html, body { margin: 0; padding: 0; width: 100%; height: 100%; background: transparent; }\n #inner { width: 100%; height: 100%; border: 0; background: white; }\n</style>\n</head>\n<body>\n<script>globalThis.__mountlyMcpSandbox__ = ${serializeInlineScriptValue({ hostOrigin })};</script>\n<script>${escapeInlineScript(runtime)}</script>\n</body>\n</html>\n`;\n}\n"],"mappings":";;;;;;AA+BO,SAAS,iBACd,YACA,UAAmC,CAAC,GAC5B;AACR,QAAM,UAAU,QAAQ,sBAAsB;AAC9C,SAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,6CAWoC,2BAA2B,EAAE,WAAW,CAAC,CAAC;AAAA,UAC7E,mBAAmB,OAAO,CAAC;AAAA;AAAA;AAAA;AAIrC;","names":[]}
|