@plitzi/sdk-server 0.32.6 → 0.32.8

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.
@@ -0,0 +1,116 @@
1
+ import { emptySpace } from "../helpers/space.js";
2
+ import { RENDER_APP_URI } from "../resources/renderApp.js";
3
+ import { operations } from "./operations/index.js";
4
+ import { applyOperations } from "./apply/dispatch.js";
5
+ import { defineTool } from "./shared/tool.js";
6
+ import { validateOperations } from "./shared/validator/index.js";
7
+ import { auditResources } from "./shared/validator/audit.js";
8
+ import { validateSchema } from "@plitzi/sdk-schema";
9
+ import { generateCache } from "@plitzi/sdk-style";
10
+ //#region src/modules/mcp/tools/render.ts
11
+ var HOST_PAGE_REF = "render";
12
+ var seedSpace = () => {
13
+ const space = emptySpace();
14
+ space.schema.definition.name = "Widget";
15
+ space.schema.flat[HOST_PAGE_REF] = {
16
+ id: HOST_PAGE_REF,
17
+ idRef: HOST_PAGE_REF,
18
+ attributes: {
19
+ slug: "",
20
+ name: "Render",
21
+ default: true
22
+ },
23
+ definition: {
24
+ rootId: HOST_PAGE_REF,
25
+ label: "Page",
26
+ type: "page",
27
+ items: [],
28
+ styleSelectors: { base: "" }
29
+ }
30
+ };
31
+ space.schema.pages = [HOST_PAGE_REF];
32
+ return space;
33
+ };
34
+ var noWarnings = (warnings) => warnings.length > 0 ? warnings : void 0;
35
+ var renderShape = { operations };
36
+ var render = (input) => {
37
+ const space = seedSpace();
38
+ const validation = validateOperations(space, input.operations);
39
+ if (!validation.valid) return {
40
+ rendered: false,
41
+ errors: validation.errors,
42
+ warnings: noWarnings(validation.warnings)
43
+ };
44
+ const outcome = applyOperations(space, "main", input.operations);
45
+ if (outcome.errors.length > 0) return {
46
+ rendered: false,
47
+ errors: outcome.errors,
48
+ warnings: noWarnings(validation.warnings)
49
+ };
50
+ const integrity = validateSchema(space.schema);
51
+ if (!integrity.valid) return {
52
+ rendered: false,
53
+ errors: integrity.errors.map((error) => ({
54
+ path: error.elementId ? `schema.${error.elementId}` : "schema",
55
+ message: error.message,
56
+ hint: "The authored widget is structurally inconsistent (broken parent/child link or a cycle)."
57
+ })),
58
+ warnings: noWarnings(validation.warnings)
59
+ };
60
+ const audit = auditResources(space, input.operations);
61
+ const warnings = [...validation.warnings, ...audit.warnings];
62
+ if (audit.errors.length > 0) return {
63
+ rendered: false,
64
+ errors: audit.errors,
65
+ warnings: noWarnings(warnings)
66
+ };
67
+ space.style.cache = generateCache(space.style);
68
+ return {
69
+ rendered: true,
70
+ rootRef: HOST_PAGE_REF,
71
+ elementCount: Object.keys(space.schema.flat).length - 1,
72
+ offlineData: {
73
+ schema: space.schema,
74
+ style: space.style
75
+ },
76
+ warnings: noWarnings(warnings)
77
+ };
78
+ };
79
+ var toRenderResult = (res) => {
80
+ if (!res.rendered) return { content: [{
81
+ type: "text",
82
+ text: JSON.stringify({
83
+ rendered: false,
84
+ errors: res.errors,
85
+ warnings: res.warnings
86
+ })
87
+ }] };
88
+ const summary = {
89
+ rendered: true,
90
+ rootRef: res.rootRef,
91
+ elementCount: res.elementCount,
92
+ warnings: res.warnings
93
+ };
94
+ return {
95
+ content: [{
96
+ type: "text",
97
+ text: JSON.stringify(summary)
98
+ }],
99
+ structuredContent: {
100
+ ...summary,
101
+ offlineData: res.offlineData
102
+ }
103
+ };
104
+ };
105
+ var renderTool = defineTool({
106
+ name: "plitzi_render",
107
+ title: "Render widget",
108
+ description: "Show the user a real, rendered UI widget instead of describing one — cards, hero sections, pricing tables, forms, menus, checklists, profiles, galleries. It runs the Plitzi SDK fully offline: no backend, account, or setup. Reach for it whenever a visual layout beats prose: the user asks you to design/build/show something, OR your answer is naturally visual (a recipe → a card, a comparison → a table, steps → a checklist). Prefer showing over telling.\n\nAuthor the widget as an ordered list of `operations` that build an element tree under the pre-seeded root page \"render\". Three rules:\n1. STRUCTURE — one upsertElement builds the whole tree: set pageRef:\"render\" and give element a nested `children` array. Each element is { ref (unique), type, subType?, props?, style?, children? }; children render in order. (To attach to something you already made, use a top-level parentRef:\"<existing ref>\" instead.)\n2. STYLE — declare reusable classes with upsertDefinition { ref, desktop:{ …CSS props in kebab-case… } }, then attach via the element style:{ base:[\"<class ref>\"] }. Lay containers out with flex/grid.\n3. CONTENT — visible copy goes in props.content (text, heading, paragraph, button); heading level is the element subType (\"h1\"..\"h6\"); image/video take props.src. An unknown prop comes back as a warning naming the right one.\n\nCommon types: container, heading, paragraph, text, button, link, image, video, list, listItem, markdown (plitzi://render/types lists every built-in type with descriptions). Widgets can also be data-driven and interactive — an apiContainer fetches at runtime, upsertBinding wires data into elements, and upsertInteractionFlow makes them react to clicks (see the guide).\nREAD the resource plitzi://render/guide first — it has the element/prop table, the style model and a full worked example, and following it is the difference between a widget that renders and repeated failed calls.\nReturns a compact summary (the widget is shown to the user); on failure it returns teachable errors (path + hint) — read them and retry.",
109
+ inputShape: renderShape,
110
+ access: "read",
111
+ spaceless: true,
112
+ ui: { resourceUri: RENDER_APP_URI },
113
+ run: (input) => toRenderResult(render(input))
114
+ });
115
+ //#endregion
116
+ export { render, renderShape, renderTool };
@@ -20,6 +20,8 @@ var defineTool = (spec) => ({
20
20
  inputShape: spec.inputShape,
21
21
  access: spec.access,
22
22
  requires: spec.requires,
23
+ spaceless: spec.spaceless,
24
+ ui: spec.ui,
23
25
  execute: (args, ctx) => spec.run(z.object(spec.inputShape).parse(args), ctx)
24
26
  });
25
27
  //#endregion
@@ -5,5 +5,8 @@ import { SSRAdapters, SSRRequest, McpLogger } from '@plitzi/sdk-shared';
5
5
  import { IncomingMessage, ServerResponse } from 'node:http';
6
6
  export declare const readMcpBody: (req: IncomingMessage) => Promise<unknown>;
7
7
  export declare const serveMcp: (raw: IncomingMessage, res: ServerResponse, server: McpServer) => Promise<void>;
8
- export declare const handleMcp: (raw: IncomingMessage, res: ServerResponse, req: SSRRequest, adapters: SSRAdapters, preview?: PreviewClient, screenshot?: ScreenshotClient, logger?: McpLogger) => Promise<void>;
8
+ export declare const handleMcp: (raw: IncomingMessage, res: ServerResponse, req: SSRRequest, adapters: SSRAdapters, preview?: PreviewClient, screenshot?: ScreenshotClient, logger?: McpLogger, renderApp?: {
9
+ sdkBase: string;
10
+ devMode?: boolean;
11
+ }) => Promise<void>;
9
12
  export { createMcpServer };
@@ -9,6 +9,7 @@ export interface Space {
9
9
  catalog?: ComponentCatalog;
10
10
  }
11
11
  export declare const cloneSpace: (space: Space) => Space;
12
+ export declare const emptySpace: () => Space;
12
13
  export declare const slugify: (value: string) => string;
13
14
  /** A value when it is a string, otherwise undefined — for the many attributes typed as `unknown` that are strings
14
15
  * in practice (name, slug, subType, dom id…). */
@@ -0,0 +1,3 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
2
+ export declare const RENDER_APP_URI = "ui://plitzi/render.html";
3
+ export declare const registerRenderApp: (server: McpServer, sdkBase: string, devMode: boolean) => void;
@@ -0,0 +1,4 @@
1
+ import { McpServer } from '@modelcontextprotocol/sdk/server/mcp';
2
+ export declare const RENDER_GUIDE_URI = "plitzi://render/guide";
3
+ export declare const RENDER_TYPES_URI = "plitzi://render/types";
4
+ export declare const registerRenderResources: (server: McpServer) => void;
@@ -18,5 +18,13 @@ export interface McpServerContext {
18
18
  /** Structured request-log sink. When set, every tool call and resource read emits an McpLogEvent to it (the
19
19
  * consumer renders them); otherwise logging falls back to the console when MCP_DEBUG=1. */
20
20
  logger?: McpLogger;
21
+ /** MCP Apps: the interactive render view for plitzi_render. `sdkBase` is this server's absolute origin, which
22
+ * serves the Plitzi SDK bundle under /sdk-assets; the iframe imports it and renders the widget client-side.
23
+ * `devMode` picks the SDK vendor bundle name (dev vs prod split) to match the served bundle. Absent → the
24
+ * ui:// view is not registered (the tool still returns its text summary + offlineData). */
25
+ renderApp?: {
26
+ sdkBase: string;
27
+ devMode?: boolean;
28
+ };
21
29
  }
22
- export declare const createMcpServer: ({ adapters, getSpaceId, preview, screenshot, logger }: McpServerContext) => McpServer;
30
+ export declare const createMcpServer: ({ adapters, getSpaceId, preview, screenshot, logger, renderApp }: McpServerContext) => McpServer;
@@ -4,6 +4,7 @@ export { validate, validateShape } from './validate';
4
4
  export { search, searchShape } from './search';
5
5
  export { read, readShape } from './read';
6
6
  export { previewShape } from './preview';
7
+ export { render, renderShape } from './render';
7
8
  export { screenshotShape } from './screenshot';
8
9
  export { applyOperations } from './apply/dispatch';
9
10
  export { validateOperations } from './shared/validator';