mountly-mcp 0.0.0 → 4.0.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 +283 -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 +270 -220
- package/dist/bridge/index.d.ts +20 -15
- 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 +33 -48
- 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-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-AEVQKFXX.js +119 -0
- package/dist/chunk-AEVQKFXX.js.map +1 -0
- package/dist/chunk-DKK26TRS.js +199 -0
- package/dist/chunk-DKK26TRS.js.map +1 -0
- package/dist/chunk-JVQZGKBL.js +171 -0
- package/dist/chunk-JVQZGKBL.js.map +1 -0
- package/dist/chunk-KWVGW5Q7.js +582 -0
- package/dist/chunk-KWVGW5Q7.js.map +1 -0
- package/dist/{chunk-A64ZILAS.js → chunk-M4WFJR3C.js} +3 -2
- package/dist/{chunk-A64ZILAS.js.map → chunk-M4WFJR3C.js.map} +1 -1
- package/dist/chunk-MTJDRWGD.js +196 -0
- package/dist/chunk-MTJDRWGD.js.map +1 -0
- package/dist/chunk-SADNMGP6.js +16 -0
- package/dist/chunk-SADNMGP6.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 +297 -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/create.d.ts +10 -0
- package/dist/create.js +107 -0
- package/dist/create.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 +7 -26
- package/dist/index.js +16 -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/generative-view.d.ts +57 -0
- package/dist/json-render/index.d.ts +10 -0
- package/dist/json-render/index.js +120 -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/publish.d.ts +10 -0
- package/dist/react/context.d.ts +14 -0
- package/dist/react/create-view.d.ts +8 -0
- package/dist/react/hooks.d.ts +46 -0
- package/dist/react/index.d.ts +3 -0
- package/dist/react/index.js +36 -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 +29 -0
- package/dist/svelte/index.js +13 -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 +65 -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 +51 -0
- package/dist/vue/index.js +93 -0
- package/dist/vue/index.js.map +1 -0
- package/package.json +162 -20
- package/templates/react/.gitignore.tmpl +4 -0
- package/templates/react/README.md.tmpl +15 -0
- package/templates/react/mcp.fixtures.json.tmpl +4 -0
- package/templates/react/package.json.tmpl +27 -0
- package/templates/react/server.mjs.tmpl +70 -0
- package/templates/react/src/view.tsx.tmpl +160 -0
- package/templates/react/tsconfig.json.tmpl +15 -0
- package/templates/react/vite.config.ts.tmpl +22 -0
- package/templates/svelte/.gitignore.tmpl +4 -0
- package/templates/svelte/README.md.tmpl +14 -0
- package/templates/svelte/mcp.fixtures.json.tmpl +4 -0
- package/templates/svelte/package.json.tmpl +26 -0
- package/templates/svelte/server.mjs.tmpl +70 -0
- package/templates/svelte/src/Dashboard.svelte.tmpl +158 -0
- package/templates/svelte/src/view.ts.tmpl +14 -0
- package/templates/svelte/tsconfig.json.tmpl +14 -0
- package/templates/svelte/vite.config.ts.tmpl +22 -0
- package/templates/vue/.gitignore.tmpl +4 -0
- package/templates/vue/README.md.tmpl +14 -0
- package/templates/vue/mcp.fixtures.json.tmpl +4 -0
- package/templates/vue/package.json.tmpl +26 -0
- package/templates/vue/server.mjs.tmpl +70 -0
- package/templates/vue/src/Dashboard.vue.tmpl +159 -0
- package/templates/vue/src/view.ts.tmpl +14 -0
- package/templates/vue/tsconfig.json.tmpl +15 -0
- package/templates/vue/vite.config.ts.tmpl +22 -0
- 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,98 @@
|
|
|
1
|
+
import {
|
|
2
|
+
compileTextStreamToSpecs
|
|
3
|
+
} from "../chunk-5EHNJCMF.js";
|
|
4
|
+
|
|
5
|
+
// src/json-render/server.ts
|
|
6
|
+
import { streamText } from "ai";
|
|
7
|
+
import {
|
|
8
|
+
autoFixSpec,
|
|
9
|
+
buildUserPrompt,
|
|
10
|
+
validateSpec
|
|
11
|
+
} from "@json-render/core";
|
|
12
|
+
function streamSpec(options) {
|
|
13
|
+
const { catalog, model, prompt, state, temperature = 0.2 } = options;
|
|
14
|
+
const queue = [];
|
|
15
|
+
let done = false;
|
|
16
|
+
let failure = null;
|
|
17
|
+
let wake = null;
|
|
18
|
+
const ping = () => {
|
|
19
|
+
wake?.();
|
|
20
|
+
wake = null;
|
|
21
|
+
};
|
|
22
|
+
let resolveResult;
|
|
23
|
+
let rejectResult;
|
|
24
|
+
const result = new Promise((res, rej) => {
|
|
25
|
+
resolveResult = res;
|
|
26
|
+
rejectResult = rej;
|
|
27
|
+
});
|
|
28
|
+
void result.catch(() => {
|
|
29
|
+
});
|
|
30
|
+
void (async () => {
|
|
31
|
+
let raw = "";
|
|
32
|
+
try {
|
|
33
|
+
const stream = streamText({
|
|
34
|
+
model,
|
|
35
|
+
system: catalog.prompt({ mode: "standalone" }),
|
|
36
|
+
prompt: buildUserPrompt({ prompt, state }),
|
|
37
|
+
temperature
|
|
38
|
+
});
|
|
39
|
+
const text = (async function* () {
|
|
40
|
+
for await (const delta of stream.textStream) {
|
|
41
|
+
raw += delta;
|
|
42
|
+
yield delta;
|
|
43
|
+
}
|
|
44
|
+
})();
|
|
45
|
+
const driver = compileTextStreamToSpecs(text);
|
|
46
|
+
let step = await driver.next();
|
|
47
|
+
while (!step.done) {
|
|
48
|
+
queue.push(step.value);
|
|
49
|
+
ping();
|
|
50
|
+
step = await driver.next();
|
|
51
|
+
}
|
|
52
|
+
const compiled = step.value ?? {};
|
|
53
|
+
if (typeof compiled.root !== "string" || !compiled.elements || !compiled.elements[compiled.root]) {
|
|
54
|
+
throw new Error(
|
|
55
|
+
"mountly-mcp/json-render: model did not produce a valid spec (no resolvable root element)"
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
const { spec, fixes } = autoFixSpec(compiled);
|
|
59
|
+
queue.push(spec);
|
|
60
|
+
resolveResult({ spec, raw, fixes, issues: validateSpec(spec) });
|
|
61
|
+
} catch (error) {
|
|
62
|
+
failure = error;
|
|
63
|
+
rejectResult(error);
|
|
64
|
+
} finally {
|
|
65
|
+
done = true;
|
|
66
|
+
ping();
|
|
67
|
+
}
|
|
68
|
+
})();
|
|
69
|
+
async function* partialSpecStream() {
|
|
70
|
+
let i = 0;
|
|
71
|
+
while (true) {
|
|
72
|
+
while (i < queue.length) yield queue[i++];
|
|
73
|
+
if (done) {
|
|
74
|
+
if (failure) throw failure;
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
await new Promise((res) => {
|
|
78
|
+
wake = res;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
partialSpecStream: { [Symbol.asyncIterator]: partialSpecStream },
|
|
84
|
+
result,
|
|
85
|
+
// Getters so an unread convenience promise can't trip unhandledRejection.
|
|
86
|
+
get spec() {
|
|
87
|
+
return result.then((r) => r.spec);
|
|
88
|
+
},
|
|
89
|
+
get raw() {
|
|
90
|
+
return result.then((r) => r.raw);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
export {
|
|
95
|
+
compileTextStreamToSpecs,
|
|
96
|
+
streamSpec
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/json-render/server.ts"],"sourcesContent":["import { type LanguageModel, streamText } from \"ai\";\nimport {\n type Catalog,\n type Spec,\n type SpecValidationIssues,\n autoFixSpec,\n buildUserPrompt,\n validateSpec,\n} from \"@json-render/core\";\nimport { compileTextStreamToSpecs } from \"./spec-stream.js\";\n\n// The pure stream driver lives in `./spec-stream` (no AI-SDK coupling); re-export\n// it here so it sits next to `streamSpec` for server-side consumers.\nexport { compileTextStreamToSpecs } from \"./spec-stream.js\";\n\nexport interface StreamSpecOptions {\n /** The catalog the spec must conform to (drives the system prompt). */\n catalog: Catalog;\n /** Any AI SDK model — `ollama(...)`, `google(...)`, `groq(...)`, etc. */\n model: LanguageModel;\n /** The natural-language UI request. */\n prompt: string;\n /** Optional runtime state to ground the generation. */\n state?: Record<string, unknown>;\n /** Sampling temperature. Default 0.2 — low for consistent JSONL output. */\n temperature?: number;\n}\n\nexport interface SpecResult {\n /** The compiled, repaired spec. */\n spec: Spec;\n /** Raw model output (JSONL patches). */\n raw: string;\n /** Repairs applied by `autoFixSpec`: props-vs-element fields, and dangling child references. */\n fixes: string[];\n /** Validation issues against the catalog. */\n issues: SpecValidationIssues;\n}\n\n/**\n * The handle {@link streamSpec} returns — one object, two ways to consume it\n * (mirrors the AI SDK's `streamText` result):\n *\n * - **iterate** `partialSpecStream` to watch the UI build itself, element by\n * element, as the model emits patches;\n * - **await** `spec` (or `result` for everything) for the final, repaired spec\n * — no need to touch the stream.\n *\n * The model request starts immediately; the promises resolve when it completes,\n * whether or not you iterate the stream.\n */\nexport interface SpecStream {\n /** Live: each value is the progressively-built spec (the UI assembling itself). */\n partialSpecStream: AsyncIterable<Spec>;\n /** The final repaired + validated result. */\n result: Promise<SpecResult>;\n /** Convenience: the final repaired spec only. */\n spec: Promise<Spec>;\n /** Convenience: the raw JSONL the model emitted. */\n raw: Promise<string>;\n}\n\n/**\n * Generate a catalog-constrained json-render spec from any AI SDK model — the\n * one entry point for turning a prompt into UI.\n *\n * Uses `streamText` + json-render's stream compiler (no JSON-mode or\n * tool-calling, so a small local model can drive it). The returned\n * {@link SpecStream} is both **awaitable** for the final spec and **iterable**\n * for the live build:\n *\n * ```ts\n * import { streamSpec } from \"mountly-mcp/json-render/server\";\n * import { ollama } from \"ai-sdk-ollama\";\n *\n * // Blocking (e.g. an MCP tool returning structuredContent):\n * const { spec } = await streamSpec({ catalog, model: ollama(\"granite4.1:3b\"), prompt }).result;\n *\n * // Live (watch it build):\n * const ui = streamSpec({ catalog, model, prompt });\n * for await (const partial of ui.partialSpecStream) render(partial);\n * const { spec, issues } = await ui.result;\n * ```\n *\n * The promise rejects if the model never produces a spec with a resolvable root.\n */\nexport function streamSpec(options: StreamSpecOptions): SpecStream {\n const { catalog, model, prompt, state, temperature = 0.2 } = options;\n\n // Async queue: the eager pump pushes partial specs; the iterator drains them.\n const queue: Spec[] = [];\n let done = false;\n let failure: unknown = null;\n let wake: (() => void) | null = null;\n const ping = () => {\n wake?.();\n wake = null;\n };\n\n let resolveResult!: (r: SpecResult) => void;\n let rejectResult!: (e: unknown) => void;\n const result = new Promise<SpecResult>((res, rej) => {\n resolveResult = res;\n rejectResult = rej;\n });\n // The error also surfaces through the stream / `result`; don't let the base\n // promise trip an unhandledRejection if a caller reads neither.\n void result.catch(() => {});\n\n // Start the model request immediately (AI SDK semantics), pumping partials\n // into the queue and resolving the promises when it settles.\n void (async () => {\n let raw = \"\";\n try {\n const stream = streamText({\n model,\n system: catalog.prompt({ mode: \"standalone\" }),\n prompt: buildUserPrompt({ prompt, state }),\n temperature,\n });\n const text = (async function* () {\n for await (const delta of stream.textStream) {\n raw += delta;\n yield delta;\n }\n })();\n\n const driver = compileTextStreamToSpecs(text);\n let step = await driver.next();\n while (!step.done) {\n queue.push(step.value);\n ping();\n step = await driver.next();\n }\n const compiled = (step.value ?? {}) as Spec;\n\n // Guard before autoFixSpec: a small model can emit patches that never\n // scaffold a resolvable root. autoFixSpec would throw a cryptic\n // `Object.entries(undefined)`; fail with a clear message instead.\n if (\n typeof compiled.root !== \"string\" ||\n !compiled.elements ||\n !compiled.elements[compiled.root]\n ) {\n throw new Error(\n \"mountly-mcp/json-render: model did not produce a valid spec (no resolvable root element)\",\n );\n }\n\n const { spec, fixes } = autoFixSpec(compiled as unknown as Spec);\n // A final snapshot with repairs applied, so a live renderer settles on the\n // same spec the awaiting caller receives.\n queue.push(spec);\n resolveResult({ spec, raw, fixes, issues: validateSpec(spec) });\n } catch (error) {\n failure = error;\n rejectResult(error);\n } finally {\n done = true;\n ping();\n }\n })();\n\n async function* partialSpecStream(): AsyncGenerator<Spec> {\n let i = 0;\n while (true) {\n // Bounded by queue.length above, so the index is always populated.\n while (i < queue.length) yield queue[i++] as Spec;\n if (done) {\n if (failure) throw failure;\n return;\n }\n await new Promise<void>((res) => {\n wake = res;\n });\n }\n }\n\n return {\n partialSpecStream: { [Symbol.asyncIterator]: partialSpecStream },\n result,\n // Getters so an unread convenience promise can't trip unhandledRejection.\n get spec() {\n return result.then((r) => r.spec);\n },\n get raw() {\n return result.then((r) => r.raw);\n },\n };\n}\n"],"mappings":";;;;;AAAA,SAA6B,kBAAkB;AAC/C;AAAA,EAIE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AA8EA,SAAS,WAAW,SAAwC;AACjE,QAAM,EAAE,SAAS,OAAO,QAAQ,OAAO,cAAc,IAAI,IAAI;AAG7D,QAAM,QAAgB,CAAC;AACvB,MAAI,OAAO;AACX,MAAI,UAAmB;AACvB,MAAI,OAA4B;AAChC,QAAM,OAAO,MAAM;AACjB,WAAO;AACP,WAAO;AAAA,EACT;AAEA,MAAI;AACJ,MAAI;AACJ,QAAM,SAAS,IAAI,QAAoB,CAAC,KAAK,QAAQ;AACnD,oBAAgB;AAChB,mBAAe;AAAA,EACjB,CAAC;AAGD,OAAK,OAAO,MAAM,MAAM;AAAA,EAAC,CAAC;AAI1B,QAAM,YAAY;AAChB,QAAI,MAAM;AACV,QAAI;AACF,YAAM,SAAS,WAAW;AAAA,QACxB;AAAA,QACA,QAAQ,QAAQ,OAAO,EAAE,MAAM,aAAa,CAAC;AAAA,QAC7C,QAAQ,gBAAgB,EAAE,QAAQ,MAAM,CAAC;AAAA,QACzC;AAAA,MACF,CAAC;AACD,YAAM,QAAQ,mBAAmB;AAC/B,yBAAiB,SAAS,OAAO,YAAY;AAC3C,iBAAO;AACP,gBAAM;AAAA,QACR;AAAA,MACF,GAAG;AAEH,YAAM,SAAS,yBAAyB,IAAI;AAC5C,UAAI,OAAO,MAAM,OAAO,KAAK;AAC7B,aAAO,CAAC,KAAK,MAAM;AACjB,cAAM,KAAK,KAAK,KAAK;AACrB,aAAK;AACL,eAAO,MAAM,OAAO,KAAK;AAAA,MAC3B;AACA,YAAM,WAAY,KAAK,SAAS,CAAC;AAKjC,UACE,OAAO,SAAS,SAAS,YACzB,CAAC,SAAS,YACV,CAAC,SAAS,SAAS,SAAS,IAAI,GAChC;AACA,cAAM,IAAI;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAEA,YAAM,EAAE,MAAM,MAAM,IAAI,YAAY,QAA2B;AAG/D,YAAM,KAAK,IAAI;AACf,oBAAc,EAAE,MAAM,KAAK,OAAO,QAAQ,aAAa,IAAI,EAAE,CAAC;AAAA,IAChE,SAAS,OAAO;AACd,gBAAU;AACV,mBAAa,KAAK;AAAA,IACpB,UAAE;AACA,aAAO;AACP,WAAK;AAAA,IACP;AAAA,EACF,GAAG;AAEH,kBAAgB,oBAA0C;AACxD,QAAI,IAAI;AACR,WAAO,MAAM;AAEX,aAAO,IAAI,MAAM,OAAQ,OAAM,MAAM,GAAG;AACxC,UAAI,MAAM;AACR,YAAI,QAAS,OAAM;AACnB;AAAA,MACF;AACA,YAAM,IAAI,QAAc,CAAC,QAAQ;AAC/B,eAAO;AAAA,MACT,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AAAA,IACL,mBAAmB,EAAE,CAAC,OAAO,aAAa,GAAG,kBAAkB;AAAA,IAC/D;AAAA;AAAA,IAEA,IAAI,OAAO;AACT,aAAO,OAAO,KAAK,CAAC,MAAM,EAAE,IAAI;AAAA,IAClC;AAAA,IACA,IAAI,MAAM;AACR,aAAO,OAAO,KAAK,CAAC,MAAM,EAAE,GAAG;AAAA,IACjC;AAAA,EACF;AACF;","names":[]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Spec } from "@json-render/core";
|
|
2
|
+
export { compileSpecStream, createJsonRenderTransform, createSpecStreamCompiler, parseSpecStreamLine, } from "@json-render/core";
|
|
3
|
+
/**
|
|
4
|
+
* Drive json-render's stream compiler from a raw text stream of JSONL patches,
|
|
5
|
+
* yielding the progressively-built spec after each complete line. This is the
|
|
6
|
+
* live "model token → patch → spec" path, decoupled from any AI SDK so it can
|
|
7
|
+
* drive a real model (see `streamSpec` in `mountly-mcp/json-render/server`), replay
|
|
8
|
+
* a saved transcript, bridge a custom transport, or be unit-tested with a
|
|
9
|
+
* hand-written stream. Buffers partial lines across chunk boundaries. The
|
|
10
|
+
* generator's *return* value is the final compiled spec (before `autoFixSpec`).
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* for await (const partial of compileTextStreamToSpecs(result.textStream)) {
|
|
14
|
+
* render(partial); // each yield is one more element built
|
|
15
|
+
* }
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare function compileTextStreamToSpecs(textStream: AsyncIterable<string>): AsyncGenerator<Spec, Spec, void>;
|
|
19
|
+
/**
|
|
20
|
+
* Turn a finished spec into json-render's streaming patch format — ordered
|
|
21
|
+
* RFC-6902 JSONL lines (`/elements` scaffold, `/state`, `/root`, then each
|
|
22
|
+
* element parent-before-child). Feed these to a `createSpecStreamCompiler` to
|
|
23
|
+
* replay the build progressively, so a known spec streams in the same way a
|
|
24
|
+
* live model would emit it. Pure spec work — no AI SDK dependency.
|
|
25
|
+
*/
|
|
26
|
+
export declare function specToPatchLines(spec: Spec): string[];
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Spec } from "@json-render/core";
|
|
2
|
+
export interface UseSpecStreamOptions {
|
|
3
|
+
/** Delay between patches, ms. Default 90. Set 0 to render instantly. */
|
|
4
|
+
intervalMs?: number;
|
|
5
|
+
}
|
|
6
|
+
export interface SpecStreamState {
|
|
7
|
+
/** The progressively-built spec (partial while streaming, full when done). */
|
|
8
|
+
spec: Spec | null;
|
|
9
|
+
/** Convenience: `spec?.state`, to pass straight to the renderer. */
|
|
10
|
+
state: Record<string, unknown> | undefined;
|
|
11
|
+
/** True while patches are still being applied. */
|
|
12
|
+
loading: boolean;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Replay a finished spec as a live json-render stream — and own all the
|
|
16
|
+
* ceremony. Give it the target spec; it scaffolds a `createSpecStreamCompiler`,
|
|
17
|
+
* replays the patches with a delay, exposes the partial spec + `loading` after
|
|
18
|
+
* each step, and cancels cleanly when the target changes (so a self-driving UI
|
|
19
|
+
* that swaps specs mid-stream just works). Re-streams whenever `target` changes.
|
|
20
|
+
*
|
|
21
|
+
* ```tsx
|
|
22
|
+
* const { spec, state, loading } = useSpecStream(specs[view]);
|
|
23
|
+
* return <Renderer spec={spec} state={state} loading={loading} onAction={...} />;
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export declare function useSpecStream(target: Spec | null | undefined, options?: UseSpecStreamOptions): SpecStreamState;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { McpView } from "./types.js";
|
|
2
|
+
/** Internal bridge key — prefer {@link publishMcpView} / `createMcpView`. */
|
|
3
|
+
export declare const MCP_VIEW_GLOBAL_KEY: "__mountlyMcpView__";
|
|
4
|
+
/**
|
|
5
|
+
* Publish a View module for the iframe bridge. Call once from your View entry
|
|
6
|
+
* (or rely on `createMcpView`, which does this for you).
|
|
7
|
+
*/
|
|
8
|
+
export declare function publishMcpView(view: McpView): McpView;
|
|
9
|
+
/** Read the published View module, if any. */
|
|
10
|
+
export declare function getPublishedMcpView(): McpView | undefined;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { App, McpUiHostContext, McpUiToolInputNotification, McpUiToolInputPartialNotification, McpUiToolResultNotification } from "@modelcontextprotocol/ext-apps";
|
|
2
|
+
/**
|
|
3
|
+
* Shared per-View context. The bridge populates this on each mount/update,
|
|
4
|
+
* so consumers can synchronously read the initial values (no event subscription
|
|
5
|
+
* race) and the React tree re-renders when the bridge calls `update()`.
|
|
6
|
+
*/
|
|
7
|
+
export interface McpContextValue {
|
|
8
|
+
app: App;
|
|
9
|
+
toolInput?: McpUiToolInputNotification["params"];
|
|
10
|
+
toolInputPartial?: McpUiToolInputPartialNotification["params"];
|
|
11
|
+
toolResult?: McpUiToolResultNotification["params"];
|
|
12
|
+
hostContext?: McpUiHostContext;
|
|
13
|
+
}
|
|
14
|
+
export declare const McpContext: import("react").Context<McpContextValue | null>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Wraps a React component as an MCP Apps View and publishes it for the bridge.
|
|
3
|
+
* One call is enough — no manual global assign.
|
|
4
|
+
*/
|
|
5
|
+
import { type ComponentType } from "react";
|
|
6
|
+
import type { AdapterOptions } from "mountly/adapter";
|
|
7
|
+
import type { McpView } from "../types.js";
|
|
8
|
+
export declare function createMcpView<P extends object>(Component: ComponentType<P>, options?: AdapterOptions): McpView;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { useAutoResize as useAutoResizeBase } from "@modelcontextprotocol/ext-apps/react";
|
|
2
|
+
import type { App, McpUiDisplayMode, McpUiHostContext, McpUiUpdateModelContextRequest } from "@modelcontextprotocol/ext-apps";
|
|
3
|
+
/** The view-side ext-apps App. Use this to call server tools, send messages, open links, etc. */
|
|
4
|
+
export declare function useMcpApp(): App;
|
|
5
|
+
/** Complete notification params; read the tool arguments from `.arguments`. */
|
|
6
|
+
export declare function useToolInput<T = unknown>(): T | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* Streaming tool arguments (`ui/notifications/tool-input-partial`), present only
|
|
9
|
+
* when the bridge runs with `streamToolInput`. Best-effort recovered JSON —
|
|
10
|
+
* fields may be missing or change between notifications, so render loading
|
|
11
|
+
* states from it, never critical operations. Undefined once the complete
|
|
12
|
+
* {@link useToolInput} arrives.
|
|
13
|
+
*/
|
|
14
|
+
export declare function useToolInputPartial<T = unknown>(): Partial<T> | undefined;
|
|
15
|
+
/** Complete MCP tool result; structured data is available at `.structuredContent`. */
|
|
16
|
+
export declare function useToolResult<T = unknown>(): T | undefined;
|
|
17
|
+
/** Latest host context snapshot (theme, styles, displayMode, container dimensions, etc.). */
|
|
18
|
+
export declare function useHostContext(): McpUiHostContext | undefined;
|
|
19
|
+
/** Current display mode reported by the host. */
|
|
20
|
+
export declare function useDisplayMode(): McpUiDisplayMode | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Request a display mode change, resolving to the mode the host actually
|
|
23
|
+
* applied — which the spec allows to differ from the one asked for.
|
|
24
|
+
*
|
|
25
|
+
* Modes the host didn't list in `availableDisplayModes` are never requested
|
|
26
|
+
* (the spec requires views to check first); the current mode comes back
|
|
27
|
+
* unchanged instead.
|
|
28
|
+
*/
|
|
29
|
+
export declare function useRequestDisplayMode(): (mode: McpUiDisplayMode) => Promise<McpUiDisplayMode | undefined>;
|
|
30
|
+
/** Apply host CSS variables (and react to host-context changes). */
|
|
31
|
+
export declare function useHostStyleVariables(): void;
|
|
32
|
+
/** Apply host-provided `@font-face`/`@import` rules. */
|
|
33
|
+
export declare function useHostFonts(): void;
|
|
34
|
+
/** One-shot helper: apply both host style variables and host fonts. */
|
|
35
|
+
export declare function useHostStyles(): void;
|
|
36
|
+
/** Track the document theme reactively. */
|
|
37
|
+
export declare function useDocumentTheme(): import("@modelcontextprotocol/ext-apps").McpUiTheme;
|
|
38
|
+
/**
|
|
39
|
+
* Return a function that calls `app.updateModelContext(params)`.
|
|
40
|
+
*
|
|
41
|
+
* Context updates are available to the model in future turns without
|
|
42
|
+
* triggering an immediate response. Only the last update is kept.
|
|
43
|
+
*/
|
|
44
|
+
export declare function useUpdateModelContext(): (params: McpUiUpdateModelContextRequest["params"]) => Promise<void>;
|
|
45
|
+
/** Manually control auto-resize (rarely needed — the bridge calls setupSizeChangedNotifications by default). */
|
|
46
|
+
export declare function useAutoResize(...args: Parameters<typeof useAutoResizeBase>): void;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
McpContext,
|
|
3
|
+
createMcpView,
|
|
4
|
+
useAutoResize,
|
|
5
|
+
useDisplayMode,
|
|
6
|
+
useDocumentTheme,
|
|
7
|
+
useHostContext,
|
|
8
|
+
useHostFonts,
|
|
9
|
+
useHostStyleVariables,
|
|
10
|
+
useHostStyles,
|
|
11
|
+
useMcpApp,
|
|
12
|
+
useRequestDisplayMode,
|
|
13
|
+
useToolInput,
|
|
14
|
+
useToolInputPartial,
|
|
15
|
+
useToolResult,
|
|
16
|
+
useUpdateModelContext
|
|
17
|
+
} from "../chunk-AEVQKFXX.js";
|
|
18
|
+
import "../chunk-SADNMGP6.js";
|
|
19
|
+
export {
|
|
20
|
+
McpContext,
|
|
21
|
+
createMcpView,
|
|
22
|
+
useAutoResize,
|
|
23
|
+
useDisplayMode,
|
|
24
|
+
useDocumentTheme,
|
|
25
|
+
useHostContext,
|
|
26
|
+
useHostFonts,
|
|
27
|
+
useHostStyleVariables,
|
|
28
|
+
useHostStyles,
|
|
29
|
+
useMcpApp,
|
|
30
|
+
useRequestDisplayMode,
|
|
31
|
+
useToolInput,
|
|
32
|
+
useToolInputPartial,
|
|
33
|
+
useToolResult,
|
|
34
|
+
useUpdateModelContext
|
|
35
|
+
};
|
|
36
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export interface SandboxProxyHtmlOptions {
|
|
2
|
+
/**
|
|
3
|
+
* The compiled sandbox entry IIFE to inline. When omitted the page emits an
|
|
4
|
+
* empty `<script>` — the caller must arrange for the runtime to load another
|
|
5
|
+
* way (e.g. a `<script src>` tag).
|
|
6
|
+
*/
|
|
7
|
+
sandboxEntryScript?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Return the complete sandbox proxy HTML document for the given host origin.
|
|
11
|
+
*
|
|
12
|
+
* This is the security boundary described in MCP Apps §8.4: it runs on an
|
|
13
|
+
* origin distinct from the host, receives the view HTML over postMessage,
|
|
14
|
+
* injects the CSP the sidecar declared, and relays messages.
|
|
15
|
+
*/
|
|
16
|
+
export declare function sandboxProxyHtml(hostOrigin: string, options?: SandboxProxyHtmlOptions): string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|
package/dist/schema.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Spec constants — re-exported from the canonical SDK so they cannot drift.
|
|
3
|
+
* See `@modelcontextprotocol/ext-apps` for the source of truth.
|
|
4
|
+
*/
|
|
5
|
+
export { LATEST_PROTOCOL_VERSION as MCP_APPS_PROTOCOL_VERSION, RESOURCE_MIME_TYPE as MCP_APPS_MIME, RESOURCE_URI_META_KEY, } from "@modelcontextprotocol/ext-apps";
|
|
6
|
+
export { EXTENSION_ID } from "@modelcontextprotocol/ext-apps/server";
|
|
7
|
+
export declare const MCP_APPS_URI_SCHEME: "ui://";
|
|
8
|
+
/**
|
|
9
|
+
* Implementation-specific error codes for mountly-mcp's host/bridge layer.
|
|
10
|
+
* The spec doesn't standardize these; they appear in `notifications/message`
|
|
11
|
+
* params so hosts can categorize View failures.
|
|
12
|
+
*/
|
|
13
|
+
export declare const MCP_ERROR_CODES: {
|
|
14
|
+
readonly INITIALIZE_FAILED: "mountly-mcp/initialize-failed";
|
|
15
|
+
readonly INITIALIZE_TIMEOUT: "mountly-mcp/initialize-timeout";
|
|
16
|
+
readonly TOOL_CALL_FAILED: "mountly-mcp/tool-call-failed";
|
|
17
|
+
readonly VIEW_MOUNT_THREW: "mountly-mcp/view-mount-threw";
|
|
18
|
+
readonly INVALID_ORIGIN: "mountly-mcp/invalid-origin";
|
|
19
|
+
};
|
|
20
|
+
export type McpErrorCode = (typeof MCP_ERROR_CODES)[keyof typeof MCP_ERROR_CODES];
|
|
@@ -0,0 +1,73 @@
|
|
|
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;
|
|
72
|
+
export declare function serveStdio(options: CreateMcpAppServerOptions): Promise<RunningMcpAppServer>;
|
|
73
|
+
export { EXTENSION_ID, RESOURCE_MIME_TYPE };
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
import {
|
|
2
|
+
readMcpAppArtifact
|
|
3
|
+
} from "../chunk-DKK26TRS.js";
|
|
4
|
+
|
|
5
|
+
// src/server/index.ts
|
|
6
|
+
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(
|
|
120
|
+
({ artifact, html }) => registerAppResource(
|
|
121
|
+
server,
|
|
122
|
+
artifact.name,
|
|
123
|
+
artifact.uri,
|
|
124
|
+
{
|
|
125
|
+
mimeType: artifact.declaration.mimeType,
|
|
126
|
+
description: artifact.declaration.description,
|
|
127
|
+
_meta: { ui: artifact.declaration._meta.ui }
|
|
128
|
+
},
|
|
129
|
+
async () => ({
|
|
130
|
+
contents: [
|
|
131
|
+
{
|
|
132
|
+
uri: artifact.uri,
|
|
133
|
+
mimeType: artifact.declaration.mimeType,
|
|
134
|
+
text: html,
|
|
135
|
+
_meta: { ui: artifact.declaration._meta.ui }
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
})
|
|
139
|
+
)
|
|
140
|
+
);
|
|
141
|
+
const tools = (options.tools ?? []).map((registration) => {
|
|
142
|
+
const { meta, withoutUi, visibility } = mergedToolMetadata(registration);
|
|
143
|
+
const config = registration.config ?? {};
|
|
144
|
+
const registered = registerAppTool(
|
|
145
|
+
server,
|
|
146
|
+
registration.name,
|
|
147
|
+
{
|
|
148
|
+
...config,
|
|
149
|
+
inputSchema: normalizeToolSchema(config.inputSchema),
|
|
150
|
+
outputSchema: normalizeToolSchema(config.outputSchema),
|
|
151
|
+
_meta: meta
|
|
152
|
+
},
|
|
153
|
+
(async (args, extra) => withTextFallback(await registration.handler(args, extra)))
|
|
154
|
+
);
|
|
155
|
+
return { registration, registered, metadataWithoutUi: withoutUi, visibility };
|
|
156
|
+
});
|
|
157
|
+
const previousInitialized = server.server.oninitialized;
|
|
158
|
+
const handleInitialized = () => {
|
|
159
|
+
previousInitialized?.();
|
|
160
|
+
const ui = getUiCapability(server.server.getClientCapabilities());
|
|
161
|
+
if (ui?.mimeTypes?.includes(RESOURCE_MIME_TYPE)) return;
|
|
162
|
+
for (const resource of resources) resource.remove();
|
|
163
|
+
for (const tool of tools) {
|
|
164
|
+
if (tool.visibility.includes("model")) {
|
|
165
|
+
tool.registered.update({ _meta: tool.metadataWithoutUi });
|
|
166
|
+
} else {
|
|
167
|
+
tool.registered.remove();
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
server.server.oninitialized = handleInitialized;
|
|
172
|
+
return {
|
|
173
|
+
artifacts: views.map((view) => view.artifact),
|
|
174
|
+
remove() {
|
|
175
|
+
for (const resource of resources) resource.remove();
|
|
176
|
+
for (const tool of tools) tool.registered.remove();
|
|
177
|
+
if (server.server.oninitialized === handleInitialized) {
|
|
178
|
+
server.server.oninitialized = previousInitialized;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function createConfiguredServer(options) {
|
|
184
|
+
return new McpServer(
|
|
185
|
+
{ name: options.name, version: options.version },
|
|
186
|
+
{ capabilities: { resources: {}, tools: {} } }
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
function createMcpAppServer(options) {
|
|
190
|
+
let server;
|
|
191
|
+
async function build() {
|
|
192
|
+
const next = createConfiguredServer(options);
|
|
193
|
+
await registerMcpApps(next, { views: options.views, tools: options.tools });
|
|
194
|
+
server = next;
|
|
195
|
+
return next;
|
|
196
|
+
}
|
|
197
|
+
return {
|
|
198
|
+
async connectInProcess(connectOptions) {
|
|
199
|
+
const next = await build();
|
|
200
|
+
const [clientTransport, serverTransport] = InMemoryTransport.createLinkedPair();
|
|
201
|
+
await next.connect(serverTransport);
|
|
202
|
+
const client = new Client(
|
|
203
|
+
{ name: `${options.name}-inprocess-client`, version: options.version },
|
|
204
|
+
connectOptions?.ui ?? true ? {
|
|
205
|
+
capabilities: {
|
|
206
|
+
extensions: { [EXTENSION_ID]: { mimeTypes: [RESOURCE_MIME_TYPE] } }
|
|
207
|
+
}
|
|
208
|
+
} : void 0
|
|
209
|
+
);
|
|
210
|
+
await client.connect(clientTransport);
|
|
211
|
+
return { client };
|
|
212
|
+
},
|
|
213
|
+
async listen(transport) {
|
|
214
|
+
const next = await build();
|
|
215
|
+
await next.connect(transport);
|
|
216
|
+
},
|
|
217
|
+
async close() {
|
|
218
|
+
await server?.close();
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
async function serveStdio(options) {
|
|
223
|
+
const { StdioServerTransport } = await import("@modelcontextprotocol/sdk/server/stdio.js");
|
|
224
|
+
const server = createMcpAppServer(options);
|
|
225
|
+
await server.listen(new StdioServerTransport());
|
|
226
|
+
return server;
|
|
227
|
+
}
|
|
228
|
+
export {
|
|
229
|
+
EXTENSION_ID,
|
|
230
|
+
RESOURCE_MIME_TYPE,
|
|
231
|
+
createMcpAppServer,
|
|
232
|
+
registerMcpApps,
|
|
233
|
+
serveStdio
|
|
234
|
+
};
|
|
235
|
+
//# sourceMappingURL=index.js.map
|