@plitzi/sdk-mcp 0.37.2 → 0.37.3

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 (94) hide show
  1. package/CHANGELOG.md +1156 -0
  2. package/README.md +6 -2
  3. package/dist/createServer.js +1 -1
  4. package/dist/index.d.ts +2 -2
  5. package/dist/index.js +2 -1
  6. package/dist/modules/mcp/accessibilityOutline.d.ts +38 -0
  7. package/dist/modules/mcp/accessibilityOutline.js +75 -0
  8. package/dist/modules/mcp/accessibilityOutline.test.d.ts +1 -0
  9. package/dist/modules/mcp/catalogs/index.d.ts +2 -2
  10. package/dist/modules/mcp/catalogs/index.js +2 -2
  11. package/dist/modules/mcp/catalogs/observed.js +2 -2
  12. package/dist/modules/mcp/catalogs/pluginCatalog.d.ts +6 -3
  13. package/dist/modules/mcp/catalogs/pluginCatalog.js +12 -7
  14. package/dist/modules/mcp/helpers/agentPrompt.js +22 -5
  15. package/dist/modules/mcp/helpers/guide.d.ts +2 -2
  16. package/dist/modules/mcp/helpers/guide.js +212 -48
  17. package/dist/modules/mcp/helpers/interactions.d.ts +2 -1
  18. package/dist/modules/mcp/helpers/interactions.js +2 -0
  19. package/dist/modules/mcp/helpers/space.d.ts +6 -4
  20. package/dist/modules/mcp/helpers/space.js +7 -20
  21. package/dist/modules/mcp/helpers/uris.d.ts +3 -1
  22. package/dist/modules/mcp/helpers/uris.js +4 -1
  23. package/dist/modules/mcp/index.d.ts +2 -0
  24. package/dist/modules/mcp/localScreenshotClient.d.ts +11 -0
  25. package/dist/modules/mcp/localScreenshotClient.js +24 -9
  26. package/dist/modules/mcp/proxy/guard.d.ts +6 -0
  27. package/dist/modules/mcp/proxy/guard.js +8 -12
  28. package/dist/modules/mcp/resources/functions.d.ts +8 -0
  29. package/dist/modules/mcp/resources/functions.js +35 -0
  30. package/dist/modules/mcp/resources/register.d.ts +2 -1
  31. package/dist/modules/mcp/resources/register.js +49 -2
  32. package/dist/modules/mcp/resources/renderGuide.js +3 -2
  33. package/dist/modules/mcp/resources/router.js +3 -1
  34. package/dist/modules/mcp/screenshotClient.js +12 -4
  35. package/dist/modules/mcp/server.js +20 -10
  36. package/dist/modules/mcp/tests/guide.test.d.ts +1 -0
  37. package/dist/modules/mcp/tools/apply/dispatch.js +9 -1
  38. package/dist/modules/mcp/tools/apply/index.d.ts +34 -0
  39. package/dist/modules/mcp/tools/apply/index.js +35 -70
  40. package/dist/modules/mcp/tools/index.js +4 -2
  41. package/dist/modules/mcp/tools/operations/connectors/manifest.d.ts +0 -1
  42. package/dist/modules/mcp/tools/operations/functions/functions.test.d.ts +1 -0
  43. package/dist/modules/mcp/tools/operations/functions/index.d.ts +31 -0
  44. package/dist/modules/mcp/tools/operations/functions/index.js +48 -0
  45. package/dist/modules/mcp/tools/operations/index.d.ts +122 -2
  46. package/dist/modules/mcp/tools/operations/index.js +4 -3
  47. package/dist/modules/mcp/tools/operations/schema/elements/deleteElement.js +5 -6
  48. package/dist/modules/mcp/tools/operations/schema/elements/moveElement.js +3 -11
  49. package/dist/modules/mcp/tools/operations/schema/elements/upsertElement.js +6 -9
  50. package/dist/modules/mcp/tools/operations/schema/interactions/patchInteractionNode.d.ts +5 -0
  51. package/dist/modules/mcp/tools/operations/schema/interactions/patchInteractionNode.js +3 -1
  52. package/dist/modules/mcp/tools/operations/schema/interactions/upsertInteractionFlow.d.ts +5 -0
  53. package/dist/modules/mcp/tools/operations/schema/operations.d.ts +27 -0
  54. package/dist/modules/mcp/tools/operations/schema/pages/deleteLayout.js +6 -5
  55. package/dist/modules/mcp/tools/operations/schema/pages/deletePage.js +6 -5
  56. package/dist/modules/mcp/tools/operations/schema/settings/patchSettings.d.ts +17 -0
  57. package/dist/modules/mcp/tools/operations/schema/settings/patchSettings.js +34 -5
  58. package/dist/modules/mcp/tools/operations/schema/shared.d.ts +6 -0
  59. package/dist/modules/mcp/tools/operations/schema/shared.js +5 -0
  60. package/dist/modules/mcp/tools/operations/schema/translator.js +3 -2
  61. package/dist/modules/mcp/tools/operations/schema/write.d.ts +10 -5
  62. package/dist/modules/mcp/tools/operations/schema/write.js +16 -16
  63. package/dist/modules/mcp/tools/preview.d.ts +27 -0
  64. package/dist/modules/mcp/tools/render.d.ts +27 -0
  65. package/dist/modules/mcp/tools/render.js +7 -43
  66. package/dist/modules/mcp/tools/screenshot.d.ts +39 -0
  67. package/dist/modules/mcp/tools/screenshot.js +40 -5
  68. package/dist/modules/mcp/tools/shared/draftBatch.d.ts +25 -0
  69. package/dist/modules/mcp/tools/shared/draftBatch.js +58 -0
  70. package/dist/modules/mcp/tools/shared/draftBatch.test.d.ts +1 -0
  71. package/dist/modules/mcp/tools/shared/lintDraft.d.ts +30 -0
  72. package/dist/modules/mcp/tools/shared/lintDraft.js +142 -0
  73. package/dist/modules/mcp/tools/shared/lintDraft.test.d.ts +1 -0
  74. package/dist/modules/mcp/tools/shared/tool.d.ts +4 -0
  75. package/dist/modules/mcp/tools/shared/validator/bindings.d.ts +0 -4
  76. package/dist/modules/mcp/tools/shared/validator/bindings.js +1 -60
  77. package/dist/modules/mcp/tools/shared/validator/elements.js +3 -15
  78. package/dist/modules/mcp/tools/shared/validator/index.d.ts +1 -4
  79. package/dist/modules/mcp/tools/shared/validator/index.js +25 -20
  80. package/dist/modules/mcp/tools/shared/validator/interactions.js +3 -58
  81. package/dist/modules/mcp/tools/tryFunction.d.ts +18 -0
  82. package/dist/modules/mcp/tools/tryFunction.js +43 -0
  83. package/dist/modules/mcp/tools/validate.d.ts +34 -0
  84. package/dist/modules/mcp/tools/validate.js +9 -26
  85. package/dist/modules/mcp/types/aiSchema.d.ts +8 -1
  86. package/dist/modules/mcp/types/previewTypes.d.ts +6 -0
  87. package/dist/modules/mcp/types/screenshotTypes.d.ts +16 -0
  88. package/dist/modules/mcp/types/toolTypes.d.ts +5 -1
  89. package/dist/preview/createPreview.js +5 -4
  90. package/dist/stages/preview.js +15 -1
  91. package/package.json +11 -10
  92. package/skills/plitzi-render/SKILL.md +1 -2
  93. package/dist/modules/mcp/tools/shared/validator/audit.d.ts +0 -4
  94. package/dist/modules/mcp/tools/shared/validator/audit.js +0 -127
@@ -1,34 +1,14 @@
1
- import { getElementCallback, getGlobalCallback, getUtility, hiddenParams, invalidParams, missingRequiredParams, reconcileParams } from "../../../catalogs/index.js";
1
+ import { getElementCallback, getGlobalCallback, getUtility, hiddenParams, reconcileParams } from "../../../catalogs/index.js";
2
2
  import { NULLISH_ELEMENT_IDS } from "../../../helpers/interactions.js";
3
3
  import { checkObservedName, warnOnce } from "./context.js";
4
4
  //#region src/modules/mcp/tools/shared/validator/interactions.ts
5
5
  var ELEMENT_FIELD_ACTIONS = /* @__PURE__ */ new Set(["setState", "toggleState"]);
6
- var describeType = (type) => {
7
- switch (type) {
8
- case "boolean": return "a boolean";
9
- case "number": return "a number";
10
- case "select": return "one of its allowed values";
11
- case "scalar": return "a string, number or boolean";
12
- default: return "a string";
13
- }
14
- };
15
6
  var checkParams = (label, action, params, spec, base, ctx) => {
16
7
  const unknown = Object.keys(params).filter((key) => !(key in spec));
17
8
  if (unknown.length > 0) warnOnce(ctx, `${label} "${action}" at ${base} got unknown param(s) ${unknown.map((k) => `"${k}"`).join(", ")} — these are dropped on apply. Valid params: ${Object.keys(spec).join(", ")}.`);
18
9
  const effective = reconcileParams(params, spec, true);
19
- const missing = missingRequiredParams(params, effective, spec);
20
- if (missing.length > 0) warnOnce(ctx, `${label} "${action}" at ${base} is missing required param(s) ${missing.map((k) => `"${k}"`).join(", ")} — the step is malformed without them (see plitzi://interactions for what each does).`);
21
10
  const hidden = hiddenParams(params, effective, spec);
22
11
  if (hidden.length > 0) warnOnce(ctx, `${label} "${action}" at ${base}: param(s) ${hidden.map((k) => `"${k}"`).join(", ")} are ignored under the current params (they only apply when a companion param is set — see plitzi://interactions). Set the companion param or drop them.`);
23
- for (const { key, expected, got, options } of invalidParams(params, effective, spec)) {
24
- const detail = options ? ` Allowed values: ${options.join(", ")}.` : "";
25
- ctx.errors.push({
26
- path: `${base}.params.${key}`,
27
- message: `${label} "${action}" at ${base}: param "${key}" must be ${describeType(expected)} but got ${got}.${detail}`,
28
- hint: `Set "${key}" to ${describeType(expected)}.`,
29
- ...options ? { validValues: options } : {}
30
- });
31
- }
32
12
  };
33
13
  var checkGlobalCallback = (node, base, ctx) => {
34
14
  const builtin = getGlobalCallback(node.action);
@@ -46,50 +26,15 @@ var checkElementFieldKey = (node, base, ctx, hostRef) => {
46
26
  const targetRef = typeof node.elementId === "string" && node.elementId !== "" ? node.elementId : hostRef;
47
27
  const type = ctx.elementType(targetRef);
48
28
  const meta = type ? ctx.typeMeta.get(type) : void 0;
49
- if (!meta) return;
29
+ if (!meta?.custom) return;
50
30
  const state = node.params?.category === "state";
51
31
  const validKeys = state ? /* @__PURE__ */ new Set(["visibility", ...[...meta.styleSelectors].map((selector) => `styleSelectors.${selector}`)]) : meta.attributes;
52
32
  if (validKeys.size === 0 || validKeys.has(key)) return;
53
33
  const kind = state ? "state" : "attribute";
54
34
  const valid = [...validKeys].sort();
55
- const detail = `Element ${node.action} at ${base} names ${kind} "${key}" on type "${type}", which has no such ${kind} key`;
56
- if (!meta.custom) ctx.errors.push({
57
- path: base,
58
- message: detail,
59
- hint: `Use one of: ${valid.join(", ")}`,
60
- validValues: valid
61
- });
62
- else warnOnce(ctx, `${detail} (${valid.join(", ")}). It may still be valid — verify against plitzi://types.`);
63
- };
64
- /**
65
- * The step names another element to act on — and that element does not exist.
66
- *
67
- * This is the failure with NO symptom: the runtime resolves a callback as `callbacksAvailables[elementId][action]`,
68
- * so a dangling target finds nothing and the step does nothing, while the flow, the render and the apply all
69
- * report success. Nothing else in the pipeline catches it — the field-key check below needs the element's type to
70
- * say anything, so an unresolvable ref used to make it return in silence.
71
- *
72
- * A `widget` batch is the whole world (plitzi_render seeds an empty space), so a target that is neither in it nor
73
- * created by it is certainly wrong: a hard error. A space may carry legacy flows pointing at elements deleted long
74
- * ago, and the post-apply audit re-checks the stored interactions of every element the batch touches — erroring
75
- * there would block edits on debris the agent did not write, so in `space` mode this warns.
76
- */
77
- var checkCallbackTarget = (node, base, ctx) => {
78
- const target = node.elementId;
79
- if (target === void 0 || target === "" || NULLISH_ELEMENT_IDS.has(target) || ctx.elementExists(target)) return;
80
- const detail = `Element callback "${node.action}" at ${base} targets elementId "${target}", which is not an element in this widget`;
81
- if (ctx.mode === "widget") {
82
- ctx.errors.push({
83
- path: `${base}.elementId`,
84
- message: detail,
85
- hint: "The runtime looks the callback up on that element, so the step silently does nothing. Use the ref of an element the batch authors, or omit elementId to act on the flow host."
86
- });
87
- return;
88
- }
89
- warnOnce(ctx, `Element callback "${node.action}" at ${base} targets elementId "${target}", which is not an element in this space — the step resolves to nothing and does nothing. Point it at a real element ref, or omit elementId to act on the flow host.`);
35
+ warnOnce(ctx, `Element ${node.action} at ${base} names ${kind} "${key}" on type "${type}", which has no such ${kind} key (${valid.join(", ")}). It may still be valid — verify against plitzi://types.`);
90
36
  };
91
37
  var checkElementCallback = (node, base, ctx, hostRef) => {
92
- checkCallbackTarget(node, base, ctx);
93
38
  const builtin = getElementCallback(node.action);
94
39
  if (!builtin) {
95
40
  if (getUtility(node.action)) warnOnce(ctx, `"${node.action}" at ${base} is a utility, not an element callback — use nodeType "utility".`);
@@ -0,0 +1,18 @@
1
+ import { z } from 'zod';
2
+ import { ToolContext } from './shared/tool';
3
+ export declare const tryFunctionShape: {
4
+ task: z.ZodString;
5
+ params: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
6
+ dryRun: z.ZodOptional<z.ZodBoolean>;
7
+ };
8
+ /**
9
+ * Runs one task of the space's functions — the draft, in the sandbox, with the limits and the record any run has — and
10
+ * answers what it returned, what it logged and where it failed. How an agent checks the code it wrote before a page
11
+ * relies on it: the live site only runs what the space was last published with.
12
+ */
13
+ export declare const tryFunction: (input: {
14
+ task: string;
15
+ params?: Record<string, unknown>;
16
+ dryRun?: boolean;
17
+ }, ctx: ToolContext) => Promise<unknown>;
18
+ export declare const tryFunctionTool: import('.').ToolDef;
@@ -0,0 +1,43 @@
1
+ import { defineTool } from "./shared/tool.js";
2
+ import { z } from "zod";
3
+ //#region src/modules/mcp/tools/tryFunction.ts
4
+ var tryFunctionShape = {
5
+ task: z.string().describe("`<namespace>.<action>`"),
6
+ params: z.record(z.string(), z.unknown()).optional(),
7
+ dryRun: z.boolean().optional()
8
+ };
9
+ /**
10
+ * Runs one task of the space's functions — the draft, in the sandbox, with the limits and the record any run has — and
11
+ * answers what it returned, what it logged and where it failed. How an agent checks the code it wrote before a page
12
+ * relies on it: the live site only runs what the space was last published with.
13
+ */
14
+ var tryFunction = async (input, ctx) => {
15
+ if (input.dryRun) return {
16
+ ran: false,
17
+ reason: "Trying a function runs its code for real — its fetches and its writes — so not in plan mode"
18
+ };
19
+ if (!ctx.tryFunction) return {
20
+ ran: false,
21
+ reason: "This deployment runs no space functions"
22
+ };
23
+ const report = await ctx.tryFunction(input.task, input.params ?? {});
24
+ const step = report.steps.find((entry) => entry.action === input.task);
25
+ return {
26
+ ran: true,
27
+ status: report.status,
28
+ value: report.output.value,
29
+ ...step?.error ? { error: step.error } : {},
30
+ ...step?.logs ? { logs: step.logs } : {},
31
+ ...step ? { ms: step.endTime - step.startTime } : {}
32
+ };
33
+ };
34
+ var tryFunctionTool = defineTool({
35
+ name: "plitzi_try_function",
36
+ title: "Try function",
37
+ description: "Run one task of the space’s functions against the draft, in the sandbox: its value, logs and error. Its writes are real.",
38
+ inputShape: tryFunctionShape,
39
+ access: "write",
40
+ run: (input, ctx) => tryFunction(input, ctx)
41
+ });
42
+ //#endregion
43
+ export { tryFunction, tryFunctionShape, tryFunctionTool };
@@ -188,6 +188,11 @@ export declare const validateShape: {
188
188
  params: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnknown>>;
189
189
  enabled: import('zod').ZodOptional<import('zod').ZodBoolean>;
190
190
  when: import('zod').ZodOptional<import('zod').ZodType<import('@plitzi/sdk-shared').RuleGroup, unknown, import('zod/v4/core').$ZodTypeInternals<import('@plitzi/sdk-shared').RuleGroup, unknown>>>;
191
+ whileRunning: import('zod').ZodOptional<import('zod').ZodEnum<{
192
+ skip: "skip";
193
+ parallel: "parallel";
194
+ queue: "queue";
195
+ }>>;
191
196
  elementId: import('zod').ZodOptional<import('zod').ZodString>;
192
197
  preview: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnknown>>;
193
198
  }, import('zod/v4/core').$strip>>;
@@ -201,6 +206,11 @@ export declare const validateShape: {
201
206
  params: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnknown>>;
202
207
  enabled: import('zod').ZodOptional<import('zod').ZodBoolean>;
203
208
  when: import('zod').ZodOptional<import('zod').ZodType<import('@plitzi/sdk-shared').RuleGroup, unknown, import('zod/v4/core').$ZodTypeInternals<import('@plitzi/sdk-shared').RuleGroup, unknown>>>;
209
+ whileRunning: import('zod').ZodOptional<import('zod').ZodEnum<{
210
+ skip: "skip";
211
+ parallel: "parallel";
212
+ queue: "queue";
213
+ }>>;
204
214
  elementId: import('zod').ZodOptional<import('zod').ZodString>;
205
215
  preview: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnknown>>;
206
216
  }, import('zod/v4/core').$strip>, import('zod').ZodObject<{
@@ -217,6 +227,8 @@ export declare const validateShape: {
217
227
  localStorage: "localStorage";
218
228
  sessionStorage: "sessionStorage";
219
229
  }>>;
230
+ transientState: import('zod').ZodOptional<import('zod').ZodArray<import('zod').ZodString>>;
231
+ paintedState: import('zod').ZodOptional<import('zod').ZodArray<import('zod').ZodString>>;
220
232
  userProvider: import('zod').ZodOptional<import('zod').ZodString>;
221
233
  tokenStorage: import('zod').ZodOptional<import('zod').ZodEnum<{
222
234
  "": "";
@@ -239,6 +251,21 @@ export declare const validateShape: {
239
251
  strict: "strict";
240
252
  }>>;
241
253
  sessionRevalidateSeconds: import('zod').ZodOptional<import('zod').ZodNumber>;
254
+ channels: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodNullable<import('zod').ZodObject<{
255
+ access: import('zod').ZodObject<{
256
+ mode: import('zod').ZodEnum<{
257
+ public: "public";
258
+ session: "session";
259
+ role: "role";
260
+ }>;
261
+ permissions: import('zod').ZodOptional<import('zod').ZodArray<import('zod').ZodString>>;
262
+ }, import('zod/v4/core').$strip>;
263
+ publish: import('zod').ZodOptional<import('zod').ZodEnum<{
264
+ server: "server";
265
+ clients: "clients";
266
+ }>>;
267
+ presence: import('zod').ZodOptional<import('zod').ZodBoolean>;
268
+ }, import('zod/v4/core').$strip>>>>;
242
269
  }, import('zod/v4/core').$strip>, import('zod').ZodObject<{
243
270
  desktop: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
244
271
  tablet: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
@@ -991,6 +1018,13 @@ export declare const validateShape: {
991
1018
  }, import('zod/v4/core').$strip>, import('zod').ZodObject<{
992
1019
  type: import('zod').ZodLiteral<"deleteAction">;
993
1020
  ref: import('zod').ZodString;
1021
+ }, import('zod/v4/core').$strip>, import('zod').ZodObject<{
1022
+ type: import('zod').ZodLiteral<"upsertFunctionFile">;
1023
+ path: import('zod').ZodString;
1024
+ content: import('zod').ZodString;
1025
+ }, import('zod/v4/core').$strip>, import('zod').ZodObject<{
1026
+ type: import('zod').ZodLiteral<"deleteFunctionFile">;
1027
+ path: import('zod').ZodString;
994
1028
  }, import('zod/v4/core').$strip>], "type">>;
995
1029
  };
996
1030
  export declare const validate: (input: ValidateInput, space: Space) => ValidationResult;
@@ -1,38 +1,21 @@
1
- import { cloneSpace } from "../helpers/space.js";
2
1
  import { environment, operations } from "./operations/index.js";
3
- import { applyOperations } from "./apply/dispatch.js";
4
- import { expandOperations } from "./shared/expandOperations.js";
2
+ import { draftBatch } from "./shared/draftBatch.js";
5
3
  import { defineTool } from "./shared/tool.js";
6
- import { validateOperations } from "./shared/validator/index.js";
7
- import { auditResources } from "./shared/validator/audit.js";
8
4
  //#region src/modules/mcp/tools/validate.ts
9
5
  var validateShape = {
10
6
  environment,
11
7
  operations
12
8
  };
13
9
  var validate = (input, space) => {
14
- const expansion = expandOperations(input.operations);
15
- if (expansion.errors.length > 0) return {
10
+ const result = draftBatch(space, input.environment ?? "main", input.operations);
11
+ return result.ok ? {
12
+ valid: true,
13
+ errors: [],
14
+ warnings: result.warnings
15
+ } : {
16
16
  valid: false,
17
- errors: expansion.errors,
18
- warnings: []
19
- };
20
- const ops = expansion.operations;
21
- const validation = validateOperations(space, ops);
22
- if (!validation.valid) return validation;
23
- const env = input.environment ?? "main";
24
- const draft = cloneSpace(space);
25
- const outcome = applyOperations(draft, env, ops);
26
- if (outcome.errors.length > 0) return {
27
- valid: false,
28
- errors: outcome.errors,
29
- warnings: validation.warnings
30
- };
31
- const audit = auditResources(draft, ops);
32
- return {
33
- valid: audit.errors.length === 0,
34
- errors: audit.errors,
35
- warnings: [...validation.warnings, ...audit.warnings]
17
+ errors: result.errors,
18
+ warnings: result.warnings
36
19
  };
37
20
  };
38
21
  var validateTool = defineTool({
@@ -1,5 +1,5 @@
1
1
  import { CssProps } from '@plitzi/sdk-authoring';
2
- import { ElementRuntime, Environment } from '@plitzi/sdk-shared';
2
+ import { ElementRuntime, Environment, WhileRunning } from '@plitzi/sdk-shared';
3
3
  export type Env = Environment;
4
4
  /** CSS declarations for one selector state. Keys are kebab-case CSS properties (see plitzi://css-properties).
5
5
  * Owned by the style engine, which is also what expands the shorthands an agent is allowed to write. */
@@ -138,6 +138,8 @@ export interface AIInteractionNode {
138
138
  params?: Record<string, unknown>;
139
139
  enabled?: boolean;
140
140
  when?: unknown;
141
+ /** On a trigger: what firing it again does while its flow still runs. `skip` when absent. */
142
+ whileRunning?: WhileRunning;
141
143
  /** Source element the callback targets (globalCallback/utility). Defaults to this element on write. */
142
144
  elementId?: string;
143
145
  preview?: Record<string, unknown>;
@@ -234,8 +236,13 @@ export interface AISchemaVariable {
234
236
  * configuration. Every field is optional — a patch changes only the keys it sends. */
235
237
  export interface AISettings {
236
238
  customCss?: string;
239
+ /** Keep `runtime.state` — what `setState` writes — across reloads, filed under whoever is signed in. */
237
240
  keepState?: boolean;
238
241
  stateStorage?: 'localStorage' | 'sessionStorage';
242
+ /** Top-level state keys never kept, even with `keepState` on. */
243
+ transientState?: string[];
244
+ /** Kept keys the first paint shows, kept in a cookie too so the server renders with them. Small values only. */
245
+ paintedState?: string[];
239
246
  /** `basic` covers any HTTP+JSON backend by configuration; anything else is a name someone registered. */
240
247
  userProvider?: 'basic' | 'custom' | '' | (string & {});
241
248
  tokenStorage?: 'localStorage' | 'sessionStorage' | '';
@@ -3,6 +3,12 @@ import { Operation } from '../tools/operations';
3
3
  export type PreviewRequestBody = {
4
4
  spaceId: number;
5
5
  env?: string;
6
+ /**
7
+ * A published revision of `env` to render instead of its latest. Ignored for `main`, which has no revisions — it is
8
+ * the draft. Asked for when what is shown has to be one exact version: a reviewed one, rather than whatever was
9
+ * published after the review.
10
+ */
11
+ revision?: number;
6
12
  pageRef?: string;
7
13
  operations?: Operation[];
8
14
  /**
@@ -9,9 +9,20 @@ export type ScreenshotImage = {
9
9
  mimeType: string;
10
10
  data: string;
11
11
  };
12
+ /**
13
+ * What a capture brings back of a page: how it looks, and how assistive technology reads it — the accessibility tree a
14
+ * screen reader walks, and the one a browser agent such as Claude in Chrome finds the page's controls in.
15
+ */
16
+ export type CaptureView = 'image' | 'accessibility';
17
+ /** One viewport's accessibility tree, as an indented outline: `- button "Close"`, `- heading "Plans" [level=1]`. */
18
+ export type AccessibilityOutline = {
19
+ label: string;
20
+ outline: string;
21
+ };
12
22
  export type ScreenshotResult = {
13
23
  ok: true;
14
24
  images: ScreenshotImage[];
25
+ accessibility?: AccessibilityOutline[];
15
26
  } | {
16
27
  ok: false;
17
28
  error: string;
@@ -27,6 +38,11 @@ export type ScreenshotInput = {
27
38
  * forces a theme keeps its own; left out, the browser's default applies, which is light.
28
39
  */
29
40
  colorScheme?: ColorScheme;
41
+ /**
42
+ * What to bring back; `['image']` when left out. A browser that cannot read the accessibility tree answers without
43
+ * `accessibility`, and the caller says so rather than failing.
44
+ */
45
+ views?: CaptureView[];
30
46
  };
31
47
  /** How plitzi_screenshot reaches the headless-browser service. The consumer injects an implementation (an HTTP
32
48
  * client to the dedicated browser pod); absent → the tool is not registered. A capture that fails (pod down)
@@ -1,6 +1,6 @@
1
1
  import { AIDefinition, AIElementDetail, ValidationError } from './aiSchema';
2
2
  import { Operation } from '../tools/operations';
3
- import { ActionEntry, ConnectorEntry, Schema, Style } from '@plitzi/sdk-shared';
3
+ import { ActionEntry, ConnectorEntry, FunctionsSaveResult, Schema, Style } from '@plitzi/sdk-shared';
4
4
  /** The element schema, the style schema and the connectors persist independently (Space model / Style model / one
5
5
  * row per connector), so each has its own optional persister. When one is absent, that store is applied in memory
6
6
  * but reported unsaved. Connectors save and delete a row at a time — a batch touching two of them saves those two
@@ -12,6 +12,8 @@ export interface Persisters {
12
12
  deleteConnector?: (connectorId: string) => Promise<void>;
13
13
  saveAction?: (entry: ActionEntry) => Promise<void>;
14
14
  deleteAction?: (actionId: string) => Promise<void>;
15
+ /** Saves the functions' files whole, against the version the batch read; answers what is wrong where it is. */
16
+ saveFunctions?: (files: Record<string, string>, base: string) => Promise<FunctionsSaveResult>;
15
17
  }
16
18
  export interface Conflict {
17
19
  resourceUri: string;
@@ -66,6 +68,8 @@ export interface MutationOutcome {
66
68
  /** The same, for actions: one row each, so a batch writes exactly the ones it touched. */
67
69
  changedActions: string[];
68
70
  deletedActions: string[];
71
+ /** The functions' files changed: they are saved whole, as one store. */
72
+ changedFunctions: boolean;
69
73
  }
70
74
  export interface ApplyInput {
71
75
  environment?: string;
@@ -1,7 +1,7 @@
1
1
  import { computeVersion } from "../modules/mcp/helpers/computeVersion.js";
2
2
  import { cloneSpace, findPageByRef, getPageElements } from "../modules/mcp/helpers/space.js";
3
- import { applyOperations } from "../modules/mcp/tools/apply/dispatch.js";
4
3
  import { validateOperations } from "../modules/mcp/tools/shared/validator/index.js";
4
+ import { applyOperations } from "../modules/mcp/tools/apply/dispatch.js";
5
5
  import { randomUUID } from "node:crypto";
6
6
  import { buildBody } from "@plitzi/sdk-server/ssr";
7
7
  //#region src/preview/createPreview.ts
@@ -32,12 +32,12 @@ var syntheticRequest = (pagePath) => ({
32
32
  * building any: this runs as a stage INSIDE an SSR server, not in the MCP process that calls it over HTTP. */
33
33
  var createPreview = async (body, config, renderFn, pluginManager, caches) => {
34
34
  const env = body.env ?? "main";
35
- const revision = 0;
35
+ const revision = env === "main" ? 0 : body.revision ?? 0;
36
36
  const offlineData = await config.adapters.getOfflineData(body.spaceId, env, revision);
37
37
  if (!offlineData) return {
38
38
  ok: false,
39
39
  error: "NO_DATA",
40
- message: `No offline data for space ${body.spaceId} (${env}).`
40
+ message: `No offline data for space ${body.spaceId} (${env}${revision ? ` revision ${revision}` : ""}).`
41
41
  };
42
42
  let draftOffline = offlineData;
43
43
  if (body.operations && body.operations.length > 0) {
@@ -77,7 +77,8 @@ var createPreview = async (body, config, renderFn, pluginManager, caches) => {
77
77
  token = randomUUID();
78
78
  await config.draftStore.put(token, draftOffline, {
79
79
  ttlMs,
80
- reusable
80
+ reusable,
81
+ spaceId: body.spaceId
81
82
  });
82
83
  }
83
84
  return {
@@ -1,7 +1,17 @@
1
1
  import { createPreview } from "../preview/createPreview.js";
2
2
  import { readRawBody } from "@plitzi/sdk-server/kernel";
3
+ import { createHash, timingSafeEqual } from "node:crypto";
3
4
  //#region src/stages/preview.ts
4
5
  var PREVIEW_PATH_DEFAULT = "/__preview";
6
+ var digest = (value) => createHash("sha256").update(value).digest();
7
+ /**
8
+ * Whether the request carries the configured secret — and there has to BE one.
9
+ *
10
+ * This endpoint lives on the page server, which is what the public reaches on every published host. Treating a
11
+ * missing secret as "no check" turned an internal endpoint into a public one that renders any space's draft, so an
12
+ * enabled endpoint with no secret refuses everybody. Compared as digests, in constant time.
13
+ */
14
+ var presentsSecret = (header, secret) => !!secret && typeof header === "string" && timingSafeEqual(digest(header), digest(secret));
5
15
  var json = (ctx, status, payload) => {
6
16
  ctx.res.setHeader("Content-Type", "application/json");
7
17
  ctx.res.setStatus(status);
@@ -13,7 +23,7 @@ var previewStage = async (ctx) => {
13
23
  const path = preview?.path ?? PREVIEW_PATH_DEFAULT;
14
24
  const ending = ctx.req.path === `${path}/end`;
15
25
  if (!preview?.enabled || ctx.req.method !== "POST" || ctx.req.path !== path && !ending) return false;
16
- if (preview.secret && ctx.req.headers["x-preview-secret"] !== preview.secret) return json(ctx, 403, {
26
+ if (!presentsSecret(ctx.req.headers["x-preview-secret"], preview.secret)) return json(ctx, 403, {
17
27
  error: "FORBIDDEN",
18
28
  message: "Invalid or missing preview secret."
19
29
  });
@@ -49,6 +59,10 @@ var previewStage = async (ctx) => {
49
59
  error: "BAD_REQUEST",
50
60
  message: "spaceId (number) is required."
51
61
  });
62
+ if (body.revision !== void 0 && (!Number.isInteger(body.revision) || body.revision < 1)) return json(ctx, 400, {
63
+ error: "BAD_REQUEST",
64
+ message: "revision must be a positive integer when given."
65
+ });
52
66
  const result = await createPreview(body, ctx.config, ctx.renderFn, ctx.pluginManager, ctx.caches);
53
67
  return json(ctx, result.ok ? 200 : 422, result);
54
68
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@plitzi/sdk-mcp",
3
- "version": "0.37.2",
3
+ "version": "0.37.3",
4
4
  "license": "AGPL-3.0",
5
5
  "files": [
6
6
  "dist",
@@ -29,14 +29,15 @@
29
29
  "sideEffects": false,
30
30
  "dependencies": {
31
31
  "@modelcontextprotocol/ext-apps": "^1.7.5",
32
- "@modelcontextprotocol/sdk": "^1.30.0",
33
- "@plitzi/plitzi-sdk": "0.37.2",
34
- "@plitzi/sdk-elements": "0.37.2",
35
- "@plitzi/sdk-interactions": "0.37.2",
36
- "@plitzi/sdk-schema": "0.37.2",
37
- "@plitzi/sdk-server": "0.37.2",
38
- "@plitzi/sdk-shared": "0.37.2",
39
- "@plitzi/sdk-style": "0.37.2",
32
+ "@modelcontextprotocol/sdk": "^1.30.1",
33
+ "@plitzi/plitzi-sdk": "0.37.3",
34
+ "@plitzi/sdk-authoring": "0.37.3",
35
+ "@plitzi/sdk-elements": "0.37.3",
36
+ "@plitzi/sdk-interactions": "0.37.3",
37
+ "@plitzi/sdk-schema": "0.37.3",
38
+ "@plitzi/sdk-server": "0.37.3",
39
+ "@plitzi/sdk-shared": "0.37.3",
40
+ "@plitzi/sdk-style": "0.37.3",
40
41
  "ejs": "^6.0.1",
41
42
  "esbuild": "^0.28.2",
42
43
  "zod": "^4.6.5"
@@ -59,7 +60,7 @@
59
60
  "react-dom": "^19.3.0",
60
61
  "tsx": "^4.23.15",
61
62
  "typescript": "^6.0.3",
62
- "vite": "^8.3.0",
63
+ "vite": "^8.3.1",
63
64
  "vite-plugin-dts": "^5.1.1",
64
65
  "vitest": "^5.0.1"
65
66
  },
@@ -52,12 +52,11 @@ Everything the widget needs travels in ONE call, as `operations`. Three ops carr
52
52
  "flex-direction": "column",
53
53
  "gap": "12px",
54
54
  "padding": "16px",
55
- "min-width": "0",
56
55
  "color": "var(--color-text-primary, light-dark(#0f172a, #e8eaed))"
57
56
  }
58
57
  },
59
58
  "row": {
60
- "desktop": { "display": "flex", "flex-wrap": "wrap", "gap": "12px", "min-width": "0" },
59
+ "desktop": { "display": "flex", "flex-wrap": "wrap", "gap": "12px" },
61
60
  "mobile": { "flex-direction": "column" }
62
61
  },
63
62
  "tile": {
@@ -1,4 +0,0 @@
1
- import { Space } from '../../../helpers';
2
- import { ValidationResult } from '../../../types';
3
- import { Operation } from '../../operations';
4
- export declare const auditResources: (space: Space, ops: Operation[]) => ValidationResult;
@@ -1,127 +0,0 @@
1
- import { findElementByRef } from "../../../helpers/space.js";
2
- import { definitionToAI, globalStyleToAI, idStyleToAI } from "../../operations/style/translator.js";
3
- import { checkBindingSourceScope, checkBindingTarget, checkBindingTransformers } from "./bindings.js";
4
- import { checkSlotCss } from "./css.js";
5
- import { checkVariantApplication } from "./elements.js";
6
- import { checkInteractionNode } from "./interactions.js";
7
- import { buildValidationCtx } from "./index.js";
8
- import { styleWithoutTag } from "@plitzi/sdk-schema/helpers/styleWithoutTag";
9
- //#region src/modules/mcp/tools/shared/validator/audit.ts
10
- var harvest = (ctx, into, label, run) => {
11
- const sub = {
12
- ...ctx,
13
- errors: [],
14
- warnings: [],
15
- warned: /* @__PURE__ */ new Set()
16
- };
17
- run(sub);
18
- for (const e of sub.errors) into.errors.push({
19
- path: e.path,
20
- message: `Pre-existing malformation in ${label}: ${e.message}`,
21
- hint: `${e.hint ? `${e.hint} ` : ""}This issue already exists in the space (NOT caused by your change), but the save is blocked until you fix it too, in this same batch.`,
22
- ...e.validValues ? { validValues: e.validValues } : {}
23
- });
24
- for (const w of sub.warnings) {
25
- const message = `Pre-existing issue in ${label} (not caused by your change, but worth fixing too): ${w}`;
26
- if (!into.warnings.includes(message)) into.warnings.push(message);
27
- }
28
- };
29
- var toNode = (id, node) => ({
30
- id,
31
- title: node.title,
32
- nodeType: node.type,
33
- action: node.action,
34
- params: node.params,
35
- enabled: node.enabled,
36
- when: node.when,
37
- elementId: node.elementId ?? void 0,
38
- preview: node.preview
39
- });
40
- var auditElement = (space, sub, el) => {
41
- const hostRef = el.id;
42
- const base = `element "${hostRef}"`;
43
- for (const [category, list] of Object.entries(el.definition.bindings ?? {})) for (const binding of list) {
44
- const path = `${base}.bindings.${category}[to=${binding.to}]`;
45
- checkBindingTransformers(binding.transformers, `${path}.transformers`, sub);
46
- checkBindingSourceScope(space, sub, hostRef, binding.source, `${path}.source`);
47
- checkBindingTarget(hostRef, category, binding.to, path, sub);
48
- }
49
- for (const [id, node] of Object.entries(el.definition.interactions ?? {})) checkInteractionNode(toNode(id, node), `${base}.interactions.${id}`, sub, hostRef);
50
- checkVariantApplication(el.definition.initialState, `${base}.initialState`, sub);
51
- };
52
- var auditDefinitionCss = (sub, label, def) => {
53
- const { ref, slots, ...slot } = def;
54
- checkSlotCss(slot, label, sub);
55
- for (const [name, slotDef] of Object.entries(slots ?? {})) checkSlotCss(slotDef, `${label}.slots.${name}`, sub);
56
- };
57
- var collectTouched = (ops) => {
58
- const touched = {
59
- elementRefs: /* @__PURE__ */ new Set(),
60
- definitions: /* @__PURE__ */ new Set(),
61
- globalStyles: /* @__PURE__ */ new Set(),
62
- idStyles: /* @__PURE__ */ new Set()
63
- };
64
- for (const op of ops) switch (op.type) {
65
- case "upsertElement":
66
- touched.elementRefs.add(op.element.ref);
67
- break;
68
- case "patchElement":
69
- case "moveElement":
70
- case "upsertBinding":
71
- case "patchBinding":
72
- case "deleteBinding":
73
- case "upsertInteractionFlow":
74
- case "patchInteractionNode":
75
- case "deleteInteraction":
76
- touched.elementRefs.add(op.ref);
77
- break;
78
- case "upsertDefinition":
79
- case "patchDefinition":
80
- touched.definitions.add(op.ref);
81
- break;
82
- case "upsertDefinitions":
83
- for (const ref of Object.keys(op.definitions)) touched.definitions.add(ref);
84
- break;
85
- case "upsertGlobalStyle":
86
- case "patchGlobalStyle":
87
- touched.globalStyles.add(op.componentType);
88
- break;
89
- case "upsertIdStyle":
90
- case "patchIdStyle": touched.idStyles.add(op.targetId);
91
- }
92
- return touched;
93
- };
94
- var auditResources = (space, ops) => {
95
- const ctx = buildValidationCtx(space, ops);
96
- const result = {
97
- valid: true,
98
- errors: [],
99
- warnings: []
100
- };
101
- const touched = collectTouched(ops);
102
- const seenElements = /* @__PURE__ */ new Set();
103
- for (const ref of touched.elementRefs) {
104
- const el = findElementByRef(space.schema, ref);
105
- if (!el || seenElements.has(el.id)) continue;
106
- seenElements.add(el.id);
107
- harvest(ctx, result, `element "${el.id}"`, (sub) => auditElement(space, sub, el));
108
- const tagless = styleWithoutTag(el, space.style);
109
- if (tagless) result.warnings.push(`element "${el.id}" ${tagless}`);
110
- }
111
- for (const ref of touched.definitions) {
112
- const def = definitionToAI(space.style, ref);
113
- if (def) harvest(ctx, result, `definition "${ref}"`, (sub) => auditDefinitionCss(sub, `definition "${ref}"`, def));
114
- }
115
- for (const componentType of touched.globalStyles) {
116
- const def = globalStyleToAI(space.style, componentType);
117
- if (def) harvest(ctx, result, `global style "${componentType}"`, (sub) => auditDefinitionCss(sub, `global style "${componentType}"`, def));
118
- }
119
- for (const targetId of touched.idStyles) {
120
- const def = idStyleToAI(space.style, targetId);
121
- if (def) harvest(ctx, result, `id style "#${targetId}"`, (sub) => auditDefinitionCss(sub, `id style "#${targetId}"`, def));
122
- }
123
- result.valid = result.errors.length === 0;
124
- return result;
125
- };
126
- //#endregion
127
- export { auditResources };