@mosvera/mcp 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +165 -0
  3. package/dist/context.d.ts +18 -0
  4. package/dist/context.js +179 -0
  5. package/dist/errors.d.ts +8 -0
  6. package/dist/errors.js +19 -0
  7. package/dist/examples/README.md +16 -0
  8. package/dist/examples/cinematic-editorial/README.md +22 -0
  9. package/dist/examples/cinematic-editorial/composition.json +14 -0
  10. package/dist/examples/cinematic-editorial/manifests/illustrative-image.manifest.json +16 -0
  11. package/dist/examples/cinematic-editorial/merge-strategies.json +3 -0
  12. package/dist/examples/cinematic-editorial/modifier.golden-hour.json +10 -0
  13. package/dist/examples/cinematic-editorial/modifier.high-contrast.json +5 -0
  14. package/dist/examples/cinematic-editorial/palette.editorial-warm.json +10 -0
  15. package/dist/examples/cinematic-editorial/template.base.json +11 -0
  16. package/dist/examples/cinematic-editorial/template.noir.json +7 -0
  17. package/dist/examples/demo-aesthetics/README.md +11 -0
  18. package/dist/examples/demo-aesthetics/composition.cinematic-lab.json +5 -0
  19. package/dist/examples/demo-aesthetics/composition.claymation-playful-builder.json +5 -0
  20. package/dist/examples/demo-aesthetics/composition.quiet-editorial.json +5 -0
  21. package/dist/examples/demo-aesthetics/composition.technical-manual.json +5 -0
  22. package/dist/examples/demo-aesthetics/template.cinematic-lab-base.json +45 -0
  23. package/dist/examples/demo-aesthetics/template.claymation-playful-builder-base.json +45 -0
  24. package/dist/examples/demo-aesthetics/template.quiet-editorial-base.json +45 -0
  25. package/dist/examples/demo-aesthetics/template.technical-manual-base.json +45 -0
  26. package/dist/index.d.ts +4 -0
  27. package/dist/index.js +7 -0
  28. package/dist/mcp-result.d.ts +10 -0
  29. package/dist/mcp-result.js +26 -0
  30. package/dist/project-writes.d.ts +8 -0
  31. package/dist/project-writes.js +70 -0
  32. package/dist/registry/loader.d.ts +4 -0
  33. package/dist/registry/loader.js +85 -0
  34. package/dist/registry/preflight.d.ts +3 -0
  35. package/dist/registry/preflight.js +28 -0
  36. package/dist/registry/strategies.d.ts +9 -0
  37. package/dist/registry/strategies.js +30 -0
  38. package/dist/server.d.ts +6 -0
  39. package/dist/server.js +172 -0
  40. package/dist/tools/aesthetic.d.ts +57 -0
  41. package/dist/tools/aesthetic.js +331 -0
  42. package/dist/tools/compile-generation.d.ts +12 -0
  43. package/dist/tools/compile-generation.js +67 -0
  44. package/dist/tools/get-palette.d.ts +11 -0
  45. package/dist/tools/get-palette.js +23 -0
  46. package/dist/tools/list-templates.d.ts +6 -0
  47. package/dist/tools/list-templates.js +15 -0
  48. package/dist/tools/resolve-composition.d.ts +8 -0
  49. package/dist/tools/resolve-composition.js +38 -0
  50. package/dist/tools/validate-schema.d.ts +6 -0
  51. package/dist/tools/validate-schema.js +17 -0
  52. package/dist/types.d.ts +58 -0
  53. package/dist/types.js +7 -0
  54. package/examples/README.md +16 -0
  55. package/examples/cinematic-editorial/README.md +22 -0
  56. package/examples/cinematic-editorial/composition.json +14 -0
  57. package/examples/cinematic-editorial/manifests/illustrative-image.manifest.json +16 -0
  58. package/examples/cinematic-editorial/merge-strategies.json +3 -0
  59. package/examples/cinematic-editorial/modifier.golden-hour.json +10 -0
  60. package/examples/cinematic-editorial/modifier.high-contrast.json +5 -0
  61. package/examples/cinematic-editorial/palette.editorial-warm.json +10 -0
  62. package/examples/cinematic-editorial/template.base.json +11 -0
  63. package/examples/cinematic-editorial/template.noir.json +7 -0
  64. package/examples/demo-aesthetics/README.md +11 -0
  65. package/examples/demo-aesthetics/composition.cinematic-lab.json +5 -0
  66. package/examples/demo-aesthetics/composition.claymation-playful-builder.json +5 -0
  67. package/examples/demo-aesthetics/composition.quiet-editorial.json +5 -0
  68. package/examples/demo-aesthetics/composition.technical-manual.json +5 -0
  69. package/examples/demo-aesthetics/template.cinematic-lab-base.json +45 -0
  70. package/examples/demo-aesthetics/template.claymation-playful-builder-base.json +45 -0
  71. package/examples/demo-aesthetics/template.quiet-editorial-base.json +45 -0
  72. package/examples/demo-aesthetics/template.technical-manual-base.json +45 -0
  73. package/mcpb/icon.png +0 -0
  74. package/mcpb/manifest.json +73 -0
  75. package/package.json +74 -0
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "https://mosvera.io/schema/0.1/modifier",
3
+ "id": "golden-hour",
4
+ "lighting": { "mood": "warm" },
5
+ "lights": [
6
+ { "name": "fill", "power": 5 },
7
+ { "name": "rim", "power": 2 }
8
+ ],
9
+ "color_temperature": "warm"
10
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://mosvera.io/schema/0.1/modifier",
3
+ "id": "high-contrast",
4
+ "color_grade": { "contrast": "very_high" }
5
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "$schema": "https://mosvera.io/schema/0.1/palette",
3
+ "id": "editorial-warm",
4
+ "roles": {
5
+ "background": "#1a1410",
6
+ "foreground": "#f5e6d3",
7
+ "accent": "#c8943f",
8
+ "shadow": "#0a0805"
9
+ }
10
+ }
@@ -0,0 +1,11 @@
1
+ {
2
+ "$schema": "https://mosvera.io/schema/0.1/template",
3
+ "id": "cinematic-editorial-base",
4
+ "medium": "photographic",
5
+ "lighting": { "scheme": "three_point", "mood": "neutral" },
6
+ "lights": [
7
+ { "name": "key", "power": 6 },
8
+ { "name": "fill", "power": 3 }
9
+ ],
10
+ "color_grade": { "contrast": "medium", "saturation": "natural" }
11
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "https://mosvera.io/schema/0.1/template",
3
+ "id": "noir",
4
+ "$extends": "cinematic-editorial-base",
5
+ "lighting": { "mood": "moody" },
6
+ "color_grade": { "contrast": "high", "saturation": "desaturated" }
7
+ }
@@ -0,0 +1,11 @@
1
+ <!--
2
+ SPDX-License-Identifier: CC-BY-4.0
3
+ -->
4
+
5
+ # Demo Aesthetics
6
+
7
+ Default local registry seed for the Mosvera MCP server. On first run, the
8
+ server copies these public-safe documents into the user's registry directory
9
+ and then reads/writes only that user-owned copy.
10
+
11
+ These examples mirror the four live `mosvera.io` demonstrator aesthetics.
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://mosvera.io/schema/0.1/composition",
3
+ "base": "cinematic-lab-base",
4
+ "id": "cinematic-lab"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://mosvera.io/schema/0.1/composition",
3
+ "base": "claymation-playful-builder-base",
4
+ "id": "claymation-playful-builder"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://mosvera.io/schema/0.1/composition",
3
+ "base": "quiet-editorial-base",
4
+ "id": "quiet-editorial"
5
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://mosvera.io/schema/0.1/composition",
3
+ "base": "technical-manual-base",
4
+ "id": "technical-manual"
5
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "$schema": "https://mosvera.io/schema/0.1/template",
3
+ "id": "cinematic-lab-base",
4
+ "imagery": {
5
+ "alt": "Spotlit high-contrast Mosvera tesserae arranged for a cinematic lab surface.",
6
+ "blend": "screen",
7
+ "contrast": "1.22",
8
+ "saturation": "1.08",
9
+ "src": "/assets/aesthetics/hero-cinematic-lab.webp",
10
+ "treatment": "spotlit"
11
+ },
12
+ "layout": {
13
+ "density": "spacious",
14
+ "max_width": "1240px",
15
+ "radius": "8px",
16
+ "shadow": "0 28px 80px rgba(0, 0, 0, 0.42)"
17
+ },
18
+ "motion": {
19
+ "duration": "320ms",
20
+ "pace": "cinematic"
21
+ },
22
+ "palette": {
23
+ "accent": "#e05b45",
24
+ "accent_2": "#7cc9d8",
25
+ "background": "#12100f",
26
+ "border": "#4c3b39",
27
+ "code_bg": "#080707",
28
+ "code_ink": "#f8e6c8",
29
+ "ink": "#f6eee6",
30
+ "muted": "#c7b8aa",
31
+ "surface": "#1e1a18",
32
+ "surface_alt": "#2f2524"
33
+ },
34
+ "typography": {
35
+ "body": "Hanken Grotesk",
36
+ "display": "Fraunces",
37
+ "mono": "IBM Plex Mono",
38
+ "scale": "large"
39
+ },
40
+ "voice": {
41
+ "body": "Cinematic Lab pushes contrast, scale, and image presence while the same composition, schema, and token panes stay readable.",
42
+ "eyebrow": "Visual research mode",
43
+ "headline": "The standard can carry drama without losing structure."
44
+ }
45
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "$schema": "https://mosvera.io/schema/0.1/template",
3
+ "id": "claymation-playful-builder-base",
4
+ "imagery": {
5
+ "alt": "Tactile clay-like Mosvera tesserae under warm builder-table light.",
6
+ "blend": "normal",
7
+ "contrast": "0.96",
8
+ "saturation": "1.12",
9
+ "src": "/assets/aesthetics/hero-claymation-playful-builder.webp",
10
+ "treatment": "tabletop_model"
11
+ },
12
+ "layout": {
13
+ "density": "roomy",
14
+ "max_width": "1160px",
15
+ "radius": "8px",
16
+ "shadow": "0 18px 0 rgba(96, 63, 39, 0.26)"
17
+ },
18
+ "motion": {
19
+ "duration": "260ms",
20
+ "pace": "bouncy"
21
+ },
22
+ "palette": {
23
+ "accent": "#d45f3f",
24
+ "accent_2": "#2f8f9d",
25
+ "background": "#f6e7cc",
26
+ "border": "#c99a68",
27
+ "code_bg": "#2b2119",
28
+ "code_ink": "#ffe3bc",
29
+ "ink": "#2a2118",
30
+ "muted": "#695946",
31
+ "surface": "#fff3dc",
32
+ "surface_alt": "#ead2ad"
33
+ },
34
+ "typography": {
35
+ "body": "Hanken Grotesk",
36
+ "display": "Fraunces",
37
+ "mono": "IBM Plex Mono",
38
+ "scale": "friendly"
39
+ },
40
+ "voice": {
41
+ "body": "This mode keeps the spec serious while the surface becomes handmade, constructive, and a little mischievous.",
42
+ "eyebrow": "Tactile builder mode",
43
+ "headline": "Same architecture, built out of warm clay and shop light."
44
+ }
45
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "$schema": "https://mosvera.io/schema/0.1/template",
3
+ "id": "quiet-editorial-base",
4
+ "imagery": {
5
+ "alt": "Warm paper-toned Mosvera tesserae arranged as a calm editorial composition.",
6
+ "blend": "normal",
7
+ "contrast": "1.02",
8
+ "saturation": "0.92",
9
+ "src": "/assets/aesthetics/hero-quiet-editorial.webp",
10
+ "treatment": "paper_field"
11
+ },
12
+ "layout": {
13
+ "density": "comfortable",
14
+ "max_width": "1160px",
15
+ "radius": "6px",
16
+ "shadow": "0 18px 55px rgba(58, 38, 21, 0.16)"
17
+ },
18
+ "motion": {
19
+ "duration": "220ms",
20
+ "pace": "steady"
21
+ },
22
+ "palette": {
23
+ "accent": "#bd5838",
24
+ "accent_2": "#2e6b5f",
25
+ "background": "#f7f2e7",
26
+ "border": "#d8c8b6",
27
+ "code_bg": "#181513",
28
+ "code_ink": "#f5ebdd",
29
+ "ink": "#211b16",
30
+ "muted": "#665c50",
31
+ "surface": "#fffaf0",
32
+ "surface_alt": "#eee3d4"
33
+ },
34
+ "typography": {
35
+ "body": "Hanken Grotesk",
36
+ "display": "Fraunces",
37
+ "mono": "IBM Plex Mono",
38
+ "scale": "editorial"
39
+ },
40
+ "voice": {
41
+ "body": "Mosvera turns aesthetic intent into typed, portable, reviewable artifacts that teams can run inside their own platforms.",
42
+ "eyebrow": "Canonical public home",
43
+ "headline": "Aesthetic infrastructure you can inspect."
44
+ }
45
+ }
@@ -0,0 +1,45 @@
1
+ {
2
+ "$schema": "https://mosvera.io/schema/0.1/template",
3
+ "id": "technical-manual-base",
4
+ "imagery": {
5
+ "alt": "High-key schematic Mosvera tesserae rendered like a technical reference plate.",
6
+ "blend": "multiply",
7
+ "contrast": "1.14",
8
+ "saturation": "0.72",
9
+ "src": "/assets/aesthetics/hero-technical-manual.webp",
10
+ "treatment": "schematic"
11
+ },
12
+ "layout": {
13
+ "density": "compact",
14
+ "max_width": "1220px",
15
+ "radius": "2px",
16
+ "shadow": "0 1px 0 rgba(16, 33, 28, 0.24)"
17
+ },
18
+ "motion": {
19
+ "duration": "120ms",
20
+ "pace": "quick"
21
+ },
22
+ "palette": {
23
+ "accent": "#17745f",
24
+ "accent_2": "#5847a6",
25
+ "background": "#f2f5f1",
26
+ "border": "#b9c8c0",
27
+ "code_bg": "#07130f",
28
+ "code_ink": "#d9f7ea",
29
+ "ink": "#10211c",
30
+ "muted": "#50615b",
31
+ "surface": "#ffffff",
32
+ "surface_alt": "#dfe8e2"
33
+ },
34
+ "typography": {
35
+ "body": "Hanken Grotesk",
36
+ "display": "IBM Plex Mono",
37
+ "mono": "IBM Plex Mono",
38
+ "scale": "compact"
39
+ },
40
+ "voice": {
41
+ "body": "This mode compresses the interface, raises structure, and makes the underlying Mosvera artifacts feel like a spec you can build from.",
42
+ "eyebrow": "Implementation reference",
43
+ "headline": "Same site, compiled as a technical surface."
44
+ }
45
+ }
@@ -0,0 +1,4 @@
1
+ export { buildContext, defaultRegistryDir, parseCliOptions, registryDiagnostics, SERVER_VERSION, } from "./context.ts";
2
+ export { createServer } from "./server.ts";
3
+ export { runCompileDesignTokens, runCompileProviderPayload, runDeleteRegistryDocument, runDraftAesthetic, runGetRegistryDocument, runListAesthetics, runResolveAesthetic, runSaveAesthetic, runSaveRegistryDocument, runServerStatus, runValidateDocument, runValidateRegistry, runWriteMergeStrategies, } from "./tools/aesthetic.ts";
4
+ export type { LoadedProject, ToolContext } from "./types.ts";
package/dist/index.js ADDED
@@ -0,0 +1,7 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // @mosvera/mcp public surface. The package primarily ships a stdio MCP server,
4
+ // but the pure handlers stay exported for tests and host integrations.
5
+ export { buildContext, defaultRegistryDir, parseCliOptions, registryDiagnostics, SERVER_VERSION, } from "./context.js";
6
+ export { createServer } from "./server.js";
7
+ export { runCompileDesignTokens, runCompileProviderPayload, runDeleteRegistryDocument, runDraftAesthetic, runGetRegistryDocument, runListAesthetics, runResolveAesthetic, runSaveAesthetic, runSaveRegistryDocument, runServerStatus, runValidateDocument, runValidateRegistry, runWriteMergeStrategies, } from "./tools/aesthetic.js";
@@ -0,0 +1,10 @@
1
+ import type { CallToolResult } from "@modelcontextprotocol/sdk/types.js";
2
+ import type { ToolErrorCode } from "./types.ts";
3
+ export interface ToolFailure {
4
+ error: ToolErrorCode;
5
+ message: string;
6
+ detail?: unknown;
7
+ }
8
+ export declare function ok(message: string, structuredContent: Record<string, unknown>): CallToolResult;
9
+ export declare function fail(error: ToolErrorCode, message: string, detail?: unknown): CallToolResult;
10
+ export declare function isFailure(value: unknown): value is ToolFailure;
@@ -0,0 +1,26 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // Small helpers for MCP-native results: a structured payload for clients, plus
4
+ // a short text summary for hosts that still render tool output as text.
5
+ export function ok(message, structuredContent) {
6
+ return {
7
+ content: [{ type: "text", text: message }],
8
+ structuredContent: { ok: true, message, ...structuredContent },
9
+ };
10
+ }
11
+ export function fail(error, message, detail) {
12
+ const structuredContent = { ok: false, error, message };
13
+ if (detail !== undefined)
14
+ structuredContent.detail = detail;
15
+ return {
16
+ isError: true,
17
+ content: [{ type: "text", text: message }],
18
+ structuredContent,
19
+ };
20
+ }
21
+ export function isFailure(value) {
22
+ return (typeof value === "object" &&
23
+ value !== null &&
24
+ "error" in value &&
25
+ "message" in value);
26
+ }
@@ -0,0 +1,8 @@
1
+ import type { CapabilityManifest } from "@mosvera/runtime";
2
+ export declare class ProjectWriteError extends Error {
3
+ readonly code: "unsafe_filename" | "invalid_document";
4
+ readonly detail?: unknown;
5
+ constructor(code: "unsafe_filename" | "invalid_document", message: string, detail?: unknown);
6
+ }
7
+ export declare function saveCapabilityManifest(directory: string, manifest: CapabilityManifest): string;
8
+ export declare function deleteCapabilityManifest(directory: string, provider: string): boolean;
@@ -0,0 +1,70 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // MCP-only persistence helpers for capability manifests. Registry document
4
+ // persistence is delegated to @mosvera/runtime/node; manifests are the one
5
+ // project artifact that the runtime intentionally does not author yet.
6
+ import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync, } from "node:fs";
7
+ import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
8
+ const SAFE_ID = /^[a-z][a-z0-9_-]*$/;
9
+ export class ProjectWriteError extends Error {
10
+ code;
11
+ detail;
12
+ constructor(code, message, detail) {
13
+ super(message);
14
+ this.name = "ProjectWriteError";
15
+ this.code = code;
16
+ this.detail = detail;
17
+ }
18
+ }
19
+ function ensureSafeId(id) {
20
+ if (!SAFE_ID.test(id) || id.startsWith(".") || id.includes("/") || id.includes("\\") || isAbsolute(id)) {
21
+ throw new ProjectWriteError("unsafe_filename", `unsafe provider id "${id}"`, { id });
22
+ }
23
+ }
24
+ function assertWithin(root, target) {
25
+ const rel = relative(root, target);
26
+ if (rel.startsWith("..") || isAbsolute(rel)) {
27
+ throw new ProjectWriteError("unsafe_filename", `path escapes registry root: ${target}`, { path: target });
28
+ }
29
+ }
30
+ function stable(value) {
31
+ if (Array.isArray(value))
32
+ return value.map(stable);
33
+ if (value !== null && typeof value === "object") {
34
+ const out = {};
35
+ for (const [key, child] of Object.entries(value).sort(([a], [b]) => a.localeCompare(b))) {
36
+ out[key] = stable(child);
37
+ }
38
+ return out;
39
+ }
40
+ return value;
41
+ }
42
+ function atomicWrite(path, body) {
43
+ const temp = join(dirname(path), `${basename(path)}.${process.pid}.${Date.now()}.tmp`);
44
+ writeFileSync(temp, body, "utf8");
45
+ renameSync(temp, path);
46
+ }
47
+ export function saveCapabilityManifest(directory, manifest) {
48
+ const provider = manifest.provider;
49
+ if (typeof provider !== "string") {
50
+ throw new ProjectWriteError("invalid_document", "capability manifest is missing provider", { manifest });
51
+ }
52
+ ensureSafeId(provider);
53
+ const root = resolve(directory);
54
+ const manifestsDir = join(root, "manifests");
55
+ const path = join(manifestsDir, `${provider}.manifest.json`);
56
+ assertWithin(root, path);
57
+ mkdirSync(manifestsDir, { recursive: true });
58
+ atomicWrite(path, `${JSON.stringify(stable(manifest), null, 2)}\n`);
59
+ return path;
60
+ }
61
+ export function deleteCapabilityManifest(directory, provider) {
62
+ ensureSafeId(provider);
63
+ const root = resolve(directory);
64
+ const path = join(root, "manifests", `${provider}.manifest.json`);
65
+ assertWithin(root, path);
66
+ if (!existsSync(path))
67
+ return false;
68
+ rmSync(path);
69
+ return true;
70
+ }
@@ -0,0 +1,4 @@
1
+ import { type Validator } from "@mosvera/runtime";
2
+ import type { LoadedProject } from "../types.ts";
3
+ /** Load an aesthetic-system project directory. */
4
+ export declare function loadRegistry(dir: string, validator?: Validator): LoadedProject;
@@ -0,0 +1,85 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // Project registry loader (boundary module). Reads a directory of Mosvera
4
+ // documents into an in-memory Registry plus capability manifests and the
5
+ // project's merge-strategies. Classifies each document by its $schema id
6
+ // (filename fallback), validates it on load, and indexes by id/provider.
7
+ // A malformed or unclassifiable document fails startup loudly.
8
+ import { existsSync, readdirSync, readFileSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { createValidator, parse, } from "@mosvera/runtime";
11
+ const ID_TO_KIND = {
12
+ "https://mosvera.io/schema/0.1/template": "template",
13
+ "https://mosvera.io/schema/0.1/modifier": "modifier",
14
+ "https://mosvera.io/schema/0.1/palette": "palette",
15
+ "https://mosvera.io/schema/0.1/composition": "composition",
16
+ "https://mosvera.io/schema/0.1/capability-manifest": "capability-manifest",
17
+ };
18
+ const DOC_EXT = /\.(json|ya?ml)$/i;
19
+ function classify(doc, file) {
20
+ const schemaId = doc["$schema"];
21
+ if (typeof schemaId === "string" && schemaId in ID_TO_KIND)
22
+ return ID_TO_KIND[schemaId];
23
+ if (/(^|\/)template\./.test(file))
24
+ return "template";
25
+ if (/(^|\/)modifier\./.test(file))
26
+ return "modifier";
27
+ if (/(^|\/)palette\./.test(file))
28
+ return "palette";
29
+ if (/\.manifest\.(json|ya?ml)$/i.test(file))
30
+ return "capability-manifest";
31
+ if (/(^|\/)composition\b/.test(file))
32
+ return "composition";
33
+ throw new Error(`cannot classify document "${file}" (no recognized $schema or filename convention)`);
34
+ }
35
+ function readDoc(path) {
36
+ return parse(readFileSync(path, "utf8"));
37
+ }
38
+ function validateOrThrow(v, doc, kind, file) {
39
+ const res = v.validate(doc, kind);
40
+ if (!res.valid) {
41
+ const detail = res.errors.map((e) => e.message).join("; ");
42
+ throw new Error(`invalid ${kind} document "${file}": ${detail}`);
43
+ }
44
+ }
45
+ function requireId(doc, file) {
46
+ const id = doc["id"];
47
+ if (typeof id !== "string")
48
+ throw new Error(`document "${file}" is missing a string id`);
49
+ return id;
50
+ }
51
+ /** Load an aesthetic-system project directory. */
52
+ export function loadRegistry(dir, validator = createValidator()) {
53
+ const registry = { templates: {}, modifiers: {}, palettes: {} };
54
+ const manifests = {};
55
+ let strategies = {};
56
+ const stratPath = join(dir, "merge-strategies.json");
57
+ if (existsSync(stratPath)) {
58
+ strategies = readDoc(stratPath);
59
+ }
60
+ const topLevel = readdirSync(dir).filter((f) => DOC_EXT.test(f) && f !== "merge-strategies.json");
61
+ for (const f of topLevel) {
62
+ const doc = readDoc(join(dir, f));
63
+ const kind = classify(doc, f);
64
+ validateOrThrow(validator, doc, kind, f);
65
+ if (kind === "template")
66
+ registry.templates[requireId(doc, f)] = doc;
67
+ else if (kind === "modifier")
68
+ registry.modifiers[requireId(doc, f)] = doc;
69
+ else if (kind === "palette")
70
+ registry.palettes[requireId(doc, f)] = doc;
71
+ else if (kind === "capability-manifest") {
72
+ manifests[String(doc["provider"])] = doc;
73
+ }
74
+ // composition documents are runtime inputs, not registry entries: validated, not stored.
75
+ }
76
+ const manDir = join(dir, "manifests");
77
+ if (existsSync(manDir)) {
78
+ for (const f of readdirSync(manDir).filter((f) => DOC_EXT.test(f))) {
79
+ const doc = readDoc(join(manDir, f));
80
+ validateOrThrow(validator, doc, "capability-manifest", `manifests/${f}`);
81
+ manifests[String(doc["provider"])] = doc;
82
+ }
83
+ }
84
+ return { registry, manifests, strategies };
85
+ }
@@ -0,0 +1,3 @@
1
+ import type { JsonObject, Registry } from "@mosvera/runtime";
2
+ /** Returns the list of references in `composition` absent from `registry`. */
3
+ export declare function collectMissingReferences(composition: JsonObject, registry: Registry): string[];
@@ -0,0 +1,28 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // Reference pre-flight (design spec D2). Verifies a composition's base and
4
+ // modifier references exist in the effective registry BEFORE the runtime is
5
+ // called, so the MCP layer can emit a clear `unknown_reference` instead of the
6
+ // runtime's `reference_cycle` (which it raises for missing refs). This keeps
7
+ // the runtime's normative error set untouched.
8
+ /** Returns the list of references in `composition` absent from `registry`. */
9
+ export function collectMissingReferences(composition, registry) {
10
+ const templates = registry.templates ?? {};
11
+ const modifiers = registry.modifiers ?? {};
12
+ const missing = [];
13
+ const base = composition["base"];
14
+ if (typeof base !== "string") {
15
+ missing.push("<missing base>");
16
+ }
17
+ else if (!(base in templates)) {
18
+ missing.push(base);
19
+ }
20
+ const mods = composition["modifiers"];
21
+ if (Array.isArray(mods)) {
22
+ for (const ref of mods) {
23
+ if (typeof ref === "string" && !(ref in modifiers))
24
+ missing.push(ref);
25
+ }
26
+ }
27
+ return missing;
28
+ }
@@ -0,0 +1,9 @@
1
+ import type { MergeStrategies, Registry } from "@mosvera/runtime";
2
+ /** Compose strategy layers; later layers win per field name. Skips undefined. */
3
+ export declare function composeStrategies(...layers: Array<MergeStrategies | undefined>): MergeStrategies;
4
+ /**
5
+ * Merge an inline registry override over a base registry, per collection, by
6
+ * id. Inline entries shadow same-id base entries; new ids are added; base ids
7
+ * not mentioned remain. Not a wholesale replacement.
8
+ */
9
+ export declare function mergeRegistry(base: Registry, inline: Registry | undefined): Registry;
@@ -0,0 +1,30 @@
1
+ // SPDX-License-Identifier: Apache-2.0
2
+ //
3
+ // Pure helpers for composing merge strategies (three-layer model, MEP-0001 +
4
+ // design spec D3) and merging an inline registry override over the loaded one
5
+ // (per-collection, by id).
6
+ /** Compose strategy layers; later layers win per field name. Skips undefined. */
7
+ export function composeStrategies(...layers) {
8
+ const out = {};
9
+ for (const layer of layers) {
10
+ if (layer === undefined)
11
+ continue;
12
+ for (const [k, v] of Object.entries(layer))
13
+ out[k] = v;
14
+ }
15
+ return out;
16
+ }
17
+ /**
18
+ * Merge an inline registry override over a base registry, per collection, by
19
+ * id. Inline entries shadow same-id base entries; new ids are added; base ids
20
+ * not mentioned remain. Not a wholesale replacement.
21
+ */
22
+ export function mergeRegistry(base, inline) {
23
+ if (inline === undefined)
24
+ return base;
25
+ return {
26
+ templates: { ...(base.templates ?? {}), ...(inline.templates ?? {}) },
27
+ modifiers: { ...(base.modifiers ?? {}), ...(inline.modifiers ?? {}) },
28
+ palettes: { ...(base.palettes ?? {}), ...(inline.palettes ?? {}) },
29
+ };
30
+ }
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env node
2
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { buildContext } from "./context.ts";
4
+ import type { ToolContext } from "./types.ts";
5
+ export { buildContext };
6
+ export declare function createServer(ctx: ToolContext): McpServer;