@plitzi/sdk-server 0.32.17 → 0.32.19
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/CHANGELOG.md +20 -0
- package/README.md +18 -0
- package/dist/modules/mcp/apps/render/index.js +2 -1
- package/dist/modules/mcp/apps/render/view/heldBatch.ts +107 -0
- package/dist/modules/mcp/apps/render/view/index.tsx +212 -0
- package/dist/modules/mcp/apps/shared/assets.js +10 -0
- package/dist/modules/mcp/apps/shared/bundle.js +15 -14
- package/dist/modules/mcp/handler.js +1 -1
- package/dist/modules/mcp/helpers/guide.js +11 -3
- package/dist/modules/mcp/helpers/space.js +13 -1
- package/dist/modules/mcp/resources/register.js +8 -3
- package/dist/modules/mcp/resources/renderGuide.js +138 -24
- package/dist/modules/mcp/resources/router.js +13 -1
- package/dist/modules/mcp/server.js +31 -19
- package/dist/modules/mcp/tools/apply/dispatch.js +2 -0
- package/dist/modules/mcp/tools/apply/index.js +17 -3
- package/dist/modules/mcp/tools/operations/index.js +4 -0
- package/dist/modules/mcp/tools/operations/schema/elements/patchElement.js +1 -1
- package/dist/modules/mcp/tools/operations/schema/elements/repeatElement.js +148 -0
- package/dist/modules/mcp/tools/operations/schema/interactions/deleteInteraction.js +1 -1
- package/dist/modules/mcp/tools/operations/schema/interactions/patchInteractionNode.js +1 -1
- package/dist/modules/mcp/tools/operations/schema/interactions/upsertInteractionFlow.js +1 -1
- package/dist/modules/mcp/tools/operations/schema/operations.js +2 -0
- package/dist/modules/mcp/tools/operations/schema/pages/upsertPage.js +1 -1
- package/dist/modules/mcp/tools/operations/schema/shared.js +8 -5
- package/dist/modules/mcp/tools/operations/schemaIds.js +54 -0
- package/dist/modules/mcp/tools/operations/style/definitions/patchDefinition.js +1 -1
- package/dist/modules/mcp/tools/operations/style/definitions/upsertDefinition.js +1 -1
- package/dist/modules/mcp/tools/operations/style/definitions/upsertDefinitions.js +40 -0
- package/dist/modules/mcp/tools/operations/style/globalStyles/patchGlobalStyle.js +1 -1
- package/dist/modules/mcp/tools/operations/style/globalStyles/upsertGlobalStyle.js +1 -1
- package/dist/modules/mcp/tools/operations/style/idStyles/patchIdStyle.js +1 -1
- package/dist/modules/mcp/tools/operations/style/idStyles/upsertIdStyle.js +1 -1
- package/dist/modules/mcp/tools/operations/style/operations.js +2 -0
- package/dist/modules/mcp/tools/operations/style/shared.js +1 -1
- package/dist/modules/mcp/tools/read.js +21 -3
- package/dist/modules/mcp/tools/render.js +70 -8
- package/dist/modules/mcp/tools/shared/expandOperations.js +44 -0
- package/dist/modules/mcp/tools/shared/tool.js +16 -11
- package/dist/modules/mcp/tools/shared/validator/audit.js +3 -0
- package/dist/modules/mcp/tools/shared/validator/batch.js +13 -4
- package/dist/modules/mcp/tools/shared/validator/index.js +15 -0
- package/dist/modules/mcp/tools/validate.js +11 -3
- package/dist/src/modules/mcp/apps/shared/assets.d.ts +10 -0
- package/dist/src/modules/mcp/apps/shared/bundle.d.ts +3 -0
- package/dist/src/modules/mcp/apps/shared/index.d.ts +1 -0
- package/dist/src/modules/mcp/e2e/index.d.ts +2 -2
- package/dist/src/modules/mcp/e2e/mcpEndpoint.d.ts +6 -1
- package/dist/src/modules/mcp/e2e/renderingHost.d.ts +16 -1
- package/dist/src/modules/mcp/helpers/guide.d.ts +2 -1
- package/dist/src/modules/mcp/helpers/space.d.ts +9 -0
- package/dist/src/modules/mcp/resources/index.d.ts +2 -1
- package/dist/src/modules/mcp/resources/register.d.ts +6 -2
- package/dist/src/modules/mcp/resources/renderGuide.d.ts +5 -0
- package/dist/src/modules/mcp/resources/router.d.ts +4 -0
- package/dist/src/modules/mcp/server.d.ts +6 -5
- package/dist/src/modules/mcp/tools/apply/index.d.ts +50 -0
- package/dist/src/modules/mcp/tools/operations/index.d.ts +100 -0
- package/dist/src/modules/mcp/tools/operations/schema/elements/repeatElement.d.ts +55 -0
- package/dist/src/modules/mcp/tools/operations/schema/index.d.ts +1 -0
- package/dist/src/modules/mcp/tools/operations/schema/operations.d.ts +19 -0
- package/dist/src/modules/mcp/tools/operations/schema/shared.d.ts +15 -0
- package/dist/src/modules/mcp/tools/operations/schemaIds.d.ts +6 -0
- package/dist/src/modules/mcp/tools/operations/style/definitions/upsertDefinitions.d.ts +41 -0
- package/dist/src/modules/mcp/tools/operations/style/index.d.ts +1 -0
- package/dist/src/modules/mcp/tools/operations/style/operations.d.ts +33 -0
- package/dist/src/modules/mcp/tools/operations/style/shared.d.ts +4 -3
- package/dist/src/modules/mcp/tools/preview.d.ts +50 -0
- package/dist/src/modules/mcp/tools/read.d.ts +1 -0
- package/dist/src/modules/mcp/tools/render.d.ts +421 -363
- package/dist/src/modules/mcp/tools/screenshot.d.ts +50 -0
- package/dist/src/modules/mcp/tools/shared/expandOperations.d.ts +13 -0
- package/dist/src/modules/mcp/tools/shared/tool.d.ts +8 -0
- package/dist/src/modules/mcp/tools/validate.d.ts +50 -0
- package/package.json +6 -5
- package/skills/plitzi-render/SKILL.md +170 -0
- package/dist/modules/mcp/apps/render/view.tsx +0 -108
- /package/dist/modules/mcp/apps/example/{view.tsx → view/index.tsx} +0 -0
- /package/dist/src/modules/mcp/{apps/example/view.d.ts → tests/schemaIds.test.d.ts} +0 -0
- /package/dist/src/modules/mcp/{apps/render/view.d.ts → tests/skill.test.d.ts} +0 -0
|
@@ -11,6 +11,24 @@ export declare const screenshotShape: {
|
|
|
11
11
|
before: "before";
|
|
12
12
|
after: "after";
|
|
13
13
|
}>>;
|
|
14
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
15
|
+
type: z.ZodLiteral<"repeatElement">;
|
|
16
|
+
pageRef: z.ZodString;
|
|
17
|
+
ref: z.ZodString;
|
|
18
|
+
elementType: z.ZodOptional<z.ZodString>;
|
|
19
|
+
label: z.ZodOptional<z.ZodString>;
|
|
20
|
+
style: z.ZodOptional<z.ZodObject<{
|
|
21
|
+
base: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
22
|
+
slots: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodArray<z.ZodString>>>;
|
|
23
|
+
}, z.core.$strip>>;
|
|
24
|
+
parentRef: z.ZodOptional<z.ZodString>;
|
|
25
|
+
position: z.ZodOptional<z.ZodEnum<{
|
|
26
|
+
inside: "inside";
|
|
27
|
+
before: "before";
|
|
28
|
+
after: "after";
|
|
29
|
+
}>>;
|
|
30
|
+
template: z.ZodType<import('./operations/schema').TemplateElement, unknown, z.core.$ZodTypeInternals<import('./operations/schema').TemplateElement, unknown>>;
|
|
31
|
+
items: z.ZodArray<z.ZodRecord<z.ZodString, z.ZodUnknown>>;
|
|
14
32
|
}, z.core.$strip>, z.ZodObject<{
|
|
15
33
|
type: z.ZodLiteral<"patchElement">;
|
|
16
34
|
pageRef: z.ZodString;
|
|
@@ -223,6 +241,38 @@ export declare const screenshotShape: {
|
|
|
223
241
|
}, z.core.$strip>>>;
|
|
224
242
|
type: z.ZodLiteral<"upsertDefinition">;
|
|
225
243
|
ref: z.ZodString;
|
|
244
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
245
|
+
type: z.ZodLiteral<"upsertDefinitions">;
|
|
246
|
+
definitions: z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
247
|
+
desktop: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
248
|
+
tablet: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
249
|
+
mobile: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
250
|
+
states: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
251
|
+
desktop: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
252
|
+
tablet: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
253
|
+
mobile: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
254
|
+
}, z.core.$strip>>>;
|
|
255
|
+
variants: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
256
|
+
desktop: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
257
|
+
tablet: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
258
|
+
mobile: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
259
|
+
}, z.core.$strip>>>;
|
|
260
|
+
slots: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
261
|
+
desktop: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
262
|
+
tablet: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
263
|
+
mobile: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
264
|
+
states: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
265
|
+
desktop: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
266
|
+
tablet: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
267
|
+
mobile: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
268
|
+
}, z.core.$strip>>>;
|
|
269
|
+
variants: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
270
|
+
desktop: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
271
|
+
tablet: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
272
|
+
mobile: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>>>;
|
|
273
|
+
}, z.core.$strip>>>;
|
|
274
|
+
}, z.core.$strip>>>;
|
|
275
|
+
}, z.core.$strip>>;
|
|
226
276
|
}, z.core.$strip>, z.ZodObject<{
|
|
227
277
|
desktop: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodNull]>>>;
|
|
228
278
|
tablet: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodUnion<readonly [z.ZodString, z.ZodNumber, z.ZodNull]>>>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ValidationError } from '../../types';
|
|
2
|
+
import { Operation } from '../operations';
|
|
3
|
+
/** Rewrites the sugar ops into the vocabulary the rest of the pipeline knows, BEFORE validation — today that is
|
|
4
|
+
* `repeatElement`, which stands for the `upsertElement` its template + rows expand to. Everything downstream
|
|
5
|
+
* (validator, dispatch, audit, versions) therefore stays unaware of it, and a row that renders a bad element is
|
|
6
|
+
* reported by the ordinary element checks.
|
|
7
|
+
*
|
|
8
|
+
* One op in, one op out: the indices the agent sent are the indices every error path names, so `operations[3]`
|
|
9
|
+
* still means the fourth operation it wrote. */
|
|
10
|
+
export declare const expandOperations: (ops: Operation[]) => {
|
|
11
|
+
operations: Operation[];
|
|
12
|
+
errors: ValidationError[];
|
|
13
|
+
};
|
|
@@ -39,6 +39,11 @@ export interface ToolSpec<Shape extends ZodRawShape> {
|
|
|
39
39
|
* iframe, receiving the tool result). Registered as the tool's `_meta.ui` — see registerAppTool. */
|
|
40
40
|
ui?: McpUiToolMeta;
|
|
41
41
|
run: (input: z.infer<ZodObject<Shape>>, ctx: ToolContext) => unknown;
|
|
42
|
+
/** What the tool still does on a connection that carries NO space (a guest / widgets-only grant): it keeps only
|
|
43
|
+
* the part that needs none — plitzi_read serves the public documents and says so for the rest. A tool that
|
|
44
|
+
* defines this stays advertised there; one that does not is not registered at all, so the agent never calls
|
|
45
|
+
* something that cannot work. */
|
|
46
|
+
runPublic?: (input: z.infer<ZodObject<Shape>>, env: Env) => unknown;
|
|
42
47
|
}
|
|
43
48
|
/** What the registry holds and the hosts register from: the same metadata plus a type-erased `execute` that
|
|
44
49
|
* validates raw args against the shape and delegates to the typed `run`. Produced by `defineTool`. */
|
|
@@ -52,6 +57,9 @@ export interface ToolDef {
|
|
|
52
57
|
spaceless?: boolean;
|
|
53
58
|
ui?: McpUiToolMeta;
|
|
54
59
|
execute: (args: unknown, ctx: ToolContext) => unknown;
|
|
60
|
+
/** Present when the tool degrades to a space-less connection (see ToolSpec.runPublic); its absence is what tells
|
|
61
|
+
* the host not to advertise the tool there. */
|
|
62
|
+
executePublic?: (args: unknown, env: Env) => unknown;
|
|
55
63
|
}
|
|
56
64
|
/** Author a tool: give it its metadata, its input shape and a typed `run`. The returned descriptor parses the
|
|
57
65
|
* raw args against the shape before handing them to `run`, so `run` is fully typed and no cast is needed.
|
|
@@ -12,6 +12,24 @@ export declare const validateShape: {
|
|
|
12
12
|
before: "before";
|
|
13
13
|
after: "after";
|
|
14
14
|
}>>;
|
|
15
|
+
}, import('zod/v4/core').$strip>, import('zod').ZodObject<{
|
|
16
|
+
type: import('zod').ZodLiteral<"repeatElement">;
|
|
17
|
+
pageRef: import('zod').ZodString;
|
|
18
|
+
ref: import('zod').ZodString;
|
|
19
|
+
elementType: import('zod').ZodOptional<import('zod').ZodString>;
|
|
20
|
+
label: import('zod').ZodOptional<import('zod').ZodString>;
|
|
21
|
+
style: import('zod').ZodOptional<import('zod').ZodObject<{
|
|
22
|
+
base: import('zod').ZodOptional<import('zod').ZodArray<import('zod').ZodString>>;
|
|
23
|
+
slots: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodArray<import('zod').ZodString>>>;
|
|
24
|
+
}, import('zod/v4/core').$strip>>;
|
|
25
|
+
parentRef: import('zod').ZodOptional<import('zod').ZodString>;
|
|
26
|
+
position: import('zod').ZodOptional<import('zod').ZodEnum<{
|
|
27
|
+
inside: "inside";
|
|
28
|
+
before: "before";
|
|
29
|
+
after: "after";
|
|
30
|
+
}>>;
|
|
31
|
+
template: import('zod').ZodType<import('./operations/schema').TemplateElement, unknown, import('zod/v4/core').$ZodTypeInternals<import('./operations/schema').TemplateElement, unknown>>;
|
|
32
|
+
items: import('zod').ZodArray<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnknown>>;
|
|
15
33
|
}, import('zod/v4/core').$strip>, import('zod').ZodObject<{
|
|
16
34
|
type: import('zod').ZodLiteral<"patchElement">;
|
|
17
35
|
pageRef: import('zod').ZodString;
|
|
@@ -224,6 +242,38 @@ export declare const validateShape: {
|
|
|
224
242
|
}, import('zod/v4/core').$strip>>>;
|
|
225
243
|
type: import('zod').ZodLiteral<"upsertDefinition">;
|
|
226
244
|
ref: import('zod').ZodString;
|
|
245
|
+
}, import('zod/v4/core').$strip>, import('zod').ZodObject<{
|
|
246
|
+
type: import('zod').ZodLiteral<"upsertDefinitions">;
|
|
247
|
+
definitions: import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodObject<{
|
|
248
|
+
desktop: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
|
|
249
|
+
tablet: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
|
|
250
|
+
mobile: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
|
|
251
|
+
states: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodObject<{
|
|
252
|
+
desktop: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
|
|
253
|
+
tablet: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
|
|
254
|
+
mobile: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
|
|
255
|
+
}, import('zod/v4/core').$strip>>>;
|
|
256
|
+
variants: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodObject<{
|
|
257
|
+
desktop: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
|
|
258
|
+
tablet: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
|
|
259
|
+
mobile: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
|
|
260
|
+
}, import('zod/v4/core').$strip>>>;
|
|
261
|
+
slots: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodObject<{
|
|
262
|
+
desktop: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
|
|
263
|
+
tablet: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
|
|
264
|
+
mobile: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
|
|
265
|
+
states: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodObject<{
|
|
266
|
+
desktop: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
|
|
267
|
+
tablet: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
|
|
268
|
+
mobile: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
|
|
269
|
+
}, import('zod/v4/core').$strip>>>;
|
|
270
|
+
variants: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodObject<{
|
|
271
|
+
desktop: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
|
|
272
|
+
tablet: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
|
|
273
|
+
mobile: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber]>>>;
|
|
274
|
+
}, import('zod/v4/core').$strip>>>;
|
|
275
|
+
}, import('zod/v4/core').$strip>>>;
|
|
276
|
+
}, import('zod/v4/core').$strip>>;
|
|
227
277
|
}, import('zod/v4/core').$strip>, import('zod').ZodObject<{
|
|
228
278
|
desktop: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber, import('zod').ZodNull]>>>;
|
|
229
279
|
tablet: import('zod').ZodOptional<import('zod').ZodRecord<import('zod').ZodString, import('zod').ZodUnion<readonly [import('zod').ZodString, import('zod').ZodNumber, import('zod').ZodNull]>>>;
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@plitzi/sdk-server",
|
|
3
|
-
"version": "0.32.
|
|
3
|
+
"version": "0.32.19",
|
|
4
4
|
"license": "AGPL-3.0",
|
|
5
5
|
"files": [
|
|
6
|
-
"dist"
|
|
6
|
+
"dist",
|
|
7
|
+
"skills"
|
|
7
8
|
],
|
|
8
9
|
"main": "dist/index.js",
|
|
9
10
|
"module": "dist/index.js",
|
|
@@ -29,9 +30,9 @@
|
|
|
29
30
|
"dependencies": {
|
|
30
31
|
"@modelcontextprotocol/ext-apps": "^1.7.5",
|
|
31
32
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
32
|
-
"@plitzi/plitzi-sdk": "0.32.
|
|
33
|
-
"@plitzi/sdk-schema": "0.32.
|
|
34
|
-
"@plitzi/sdk-shared": "0.32.
|
|
33
|
+
"@plitzi/plitzi-sdk": "0.32.19",
|
|
34
|
+
"@plitzi/sdk-schema": "0.32.19",
|
|
35
|
+
"@plitzi/sdk-shared": "0.32.19",
|
|
35
36
|
"ejs": "^6.0.1",
|
|
36
37
|
"esbuild": "^0.28.1",
|
|
37
38
|
"zod": "^4.4.3"
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: plitzi-render
|
|
3
|
+
description: >-
|
|
4
|
+
Show answers as a rendered visual widget instead of plain text, using the Plitzi MCP's plitzi_render tool.
|
|
5
|
+
Use whenever the reply is naturally visual or structured — a recipe, a comparison, pricing tiers, a profile,
|
|
6
|
+
a menu, steps/checklist, a product card, a dashboard-like summary — or whenever the user asks to design,
|
|
7
|
+
build, show, or "make it look nice". Requires the Plitzi MCP server (the plitzi_render tool) to be connected.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Rendering answers as Plitzi widgets
|
|
11
|
+
|
|
12
|
+
`plitzi_render` renders a small, self-contained UI widget the user can SEE. It runs the Plitzi SDK offline — no
|
|
13
|
+
backend, no account, no space, so it works even on a connection with no credentials. Prefer **showing** a widget
|
|
14
|
+
over a long text answer whenever the content is naturally visual.
|
|
15
|
+
|
|
16
|
+
## When to reach for it
|
|
17
|
+
|
|
18
|
+
- **Something to design or build**: a card, hero, banner, form, pricing table — anything the user asks you to
|
|
19
|
+
"create / design / build / show / make look nice".
|
|
20
|
+
- **A naturally visual answer**: a recipe → a card with photo + ingredients; a comparison → side-by-side tiles;
|
|
21
|
+
steps → a checklist; a product, place or person → a profile card; options → a tiled menu.
|
|
22
|
+
|
|
23
|
+
Keep writing plain text when the answer is genuinely textual — an explanation, code, a discussion. One good widget
|
|
24
|
+
beats a wall of text, but a widget wrapping a paragraph helps no one.
|
|
25
|
+
|
|
26
|
+
## Before the first call
|
|
27
|
+
|
|
28
|
+
Read the MCP resource **`plitzi://render/guide`** once per conversation. It is the authoritative authoring model:
|
|
29
|
+
every operation, the element types and their props, the styling system, worked examples. `plitzi://render/types`
|
|
30
|
+
lists every element type with its description. Don't reconstruct the schema from memory — read the guide.
|
|
31
|
+
|
|
32
|
+
## The shape of a good call
|
|
33
|
+
|
|
34
|
+
Everything the widget needs travels in ONE call, as `operations`. Three ops carry almost every widget:
|
|
35
|
+
|
|
36
|
+
- **`upsertDefinitions`** — declare ALL the CSS classes at once, one class per *look* (not per property).
|
|
37
|
+
- **`upsertElement`** — build the whole tree in a single op, nesting with `children` under `pageRef: "render"`.
|
|
38
|
+
- **`repeatElement`** — the moment two siblings differ only in data, write the row ONCE as a template with
|
|
39
|
+
`{{item.field}}` placeholders and pass `items`. Rows come out numbered (`tile-1`, `tile-2`…). A list inside each
|
|
40
|
+
row is the same op: give a template node `repeat: { items: "{{item.<field>}}", template: … }`.
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"operations": [
|
|
45
|
+
{
|
|
46
|
+
"type": "upsertDefinitions",
|
|
47
|
+
"definitions": {
|
|
48
|
+
"panel": {
|
|
49
|
+
"desktop": {
|
|
50
|
+
"display": "flex",
|
|
51
|
+
"flex-direction": "column",
|
|
52
|
+
"gap": "12px",
|
|
53
|
+
"padding": "16px",
|
|
54
|
+
"min-width": "0",
|
|
55
|
+
"color": "var(--color-text-primary, light-dark(#0f172a, #e8eaed))"
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"row": {
|
|
59
|
+
"desktop": { "display": "flex", "flex-wrap": "wrap", "gap": "12px", "min-width": "0" },
|
|
60
|
+
"mobile": { "flex-direction": "column" }
|
|
61
|
+
},
|
|
62
|
+
"tile": {
|
|
63
|
+
"desktop": {
|
|
64
|
+
"flex": "1 1 0%",
|
|
65
|
+
"min-width": "160px",
|
|
66
|
+
"padding": "12px",
|
|
67
|
+
"border-radius": "10px",
|
|
68
|
+
"border": "1px solid var(--color-border-primary, light-dark(#e2e8f0, #333a48))",
|
|
69
|
+
"background-color": "var(--color-background-secondary, light-dark(#ffffff, #1f2430))"
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"tile-title": { "desktop": { "margin": "0", "font-size": "15px" } },
|
|
73
|
+
"tile-price": { "desktop": { "margin": "0", "font-size": "13px", "opacity": "0.75" } }
|
|
74
|
+
}
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"type": "upsertElement",
|
|
78
|
+
"pageRef": "render",
|
|
79
|
+
"element": {
|
|
80
|
+
"ref": "panel",
|
|
81
|
+
"type": "container",
|
|
82
|
+
"style": { "base": ["panel"] },
|
|
83
|
+
"children": [{ "ref": "title", "type": "heading", "subType": "h3", "props": { "content": "Plans" } }]
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"type": "repeatElement",
|
|
88
|
+
"pageRef": "render",
|
|
89
|
+
"parentRef": "panel",
|
|
90
|
+
"ref": "plans",
|
|
91
|
+
"style": { "base": ["row"] },
|
|
92
|
+
"template": {
|
|
93
|
+
"ref": "tile",
|
|
94
|
+
"type": "container",
|
|
95
|
+
"style": { "base": ["tile"] },
|
|
96
|
+
"children": [
|
|
97
|
+
{
|
|
98
|
+
"ref": "name",
|
|
99
|
+
"type": "heading",
|
|
100
|
+
"subType": "h4",
|
|
101
|
+
"props": { "content": "{{item.name}}" },
|
|
102
|
+
"style": { "base": ["tile-title"] }
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"ref": "price",
|
|
106
|
+
"type": "paragraph",
|
|
107
|
+
"props": { "content": "{{item.price}}" },
|
|
108
|
+
"style": { "base": ["tile-price"] }
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
"items": [
|
|
113
|
+
{ "name": "Starter", "price": "$0 / month" },
|
|
114
|
+
{ "name": "Team", "price": "$19 / month" },
|
|
115
|
+
{ "name": "Business", "price": "$49 / month" }
|
|
116
|
+
]
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
## The five things that decide whether it looks good
|
|
123
|
+
|
|
124
|
+
1. **Width is free, height is scarce.** The widget renders in a side panel. A plain container stacks its children
|
|
125
|
+
vertically — that is the tall, half-empty default to avoid. Put peers (metrics, plans, options, image + text) in
|
|
126
|
+
a wrapping row (`display:flex` + `flex-wrap:wrap`, children `flex:1 1 0%` with a `min-width` as the wrap
|
|
127
|
+
threshold) or a grid with `grid-template-columns: repeat(auto-fit, minmax(160px, 1fr))`. Stack only what reads in
|
|
128
|
+
order: heading over paragraph, forms, steps, prose.
|
|
129
|
+
2. **The host may be in dark mode.** Never hardcode a light palette. Take colours from the host variables with a
|
|
130
|
+
`light-dark()` fallback, and always set `color` wherever you set `background-color`.
|
|
131
|
+
3. **Watch the SDK defaults.** Every container has `min-width`/`min-height: 50px` — set them to `0` for rails,
|
|
132
|
+
dividers, dots and any flex child that must shrink. Headings and paragraphs keep the browser's margins; zero
|
|
133
|
+
them and space with the parent's `gap`.
|
|
134
|
+
4. **Never hand-draw a scene in a `data:` URI.** An inline SVG illustration costs more tokens than the entire rest
|
|
135
|
+
of the widget. Use an `https` image, a flat colour, or a CSS gradient.
|
|
136
|
+
5. **Write CSS plainly.** Kebab-case properties, shorthands welcome (`padding: 8px 16px`, `border: 1px solid red`,
|
|
137
|
+
`font: bold 16px/1.5 Arial`) — they are expanded and stored as longhands, so a breakpoint or state can override
|
|
138
|
+
one property on its own.
|
|
139
|
+
|
|
140
|
+
## Iterating on a widget you already rendered
|
|
141
|
+
|
|
142
|
+
Every render answers with a **`renderId`**. To change that widget, do NOT rebuild it: call again with
|
|
143
|
+
`patch: true`, that `renderId`, and only the operations that differ (`patchElement`, `patchDefinition`,
|
|
144
|
+
`deleteElement`, a new `repeatElement`). Address rows by the refs you already know (`tile-1`, `tile-2`). The widget
|
|
145
|
+
merges the delta into the batch it was built from and reports back what it applied, errors included.
|
|
146
|
+
|
|
147
|
+
Patch **only** to modify that widget. A different subject, or a different kind of widget, is a fresh render with no
|
|
148
|
+
`patch` — the delta is merged into the previous batch, so patching a new idea leaves the user looking at both at
|
|
149
|
+
once. Rebuilding when you could have patched only costs tokens; patching when you should have rebuilt costs the
|
|
150
|
+
user a wrong widget.
|
|
151
|
+
|
|
152
|
+
If the answer says the widget could not be recovered (a surface that renders no widgets, a host that keeps no
|
|
153
|
+
storage, a conversation resumed elsewhere), send the whole batch again without `patch`.
|
|
154
|
+
|
|
155
|
+
## When a call fails
|
|
156
|
+
|
|
157
|
+
`plitzi_render` answers with `rendered: false` and `errors: [{ path, message, hint }]`, plus `warnings` for smaller
|
|
158
|
+
issues. The `path` names the operation, so fix that one and call again — you never lose the rest of the batch. An
|
|
159
|
+
unknown prop comes back as a warning naming the right one, so probing is safe.
|
|
160
|
+
|
|
161
|
+
## After it renders
|
|
162
|
+
|
|
163
|
+
The widget is shown to the user; you get a compact summary. Don't re-describe what they can already see — a short
|
|
164
|
+
caption or a follow-up question is enough.
|
|
165
|
+
|
|
166
|
+
## Going further
|
|
167
|
+
|
|
168
|
+
Widgets can be data-driven and interactive, not only static: an `apiContainer` fetches at runtime, `upsertBinding`
|
|
169
|
+
wires that data into elements, and `upsertInteractionFlow` makes them react to clicks. See the "Data &
|
|
170
|
+
interactivity" section of `plitzi://render/guide`.
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/* eslint-disable react-refresh/only-export-components -- one bundled entry: components cannot move out. */
|
|
2
|
-
import { useApp, useHostStyles } from '@modelcontextprotocol/ext-apps/react';
|
|
3
|
-
import PlitziSdk from '@plitzi/plitzi-sdk';
|
|
4
|
-
import { Component, useEffect, useState } from 'react';
|
|
5
|
-
import { createRoot } from 'react-dom/client';
|
|
6
|
-
|
|
7
|
-
import type { McpUiHostContext } from '@modelcontextprotocol/ext-apps';
|
|
8
|
-
import type { CallToolResult } from '@modelcontextprotocol/sdk/types.js';
|
|
9
|
-
import type { OfflineDataRaw } from '@plitzi/sdk-shared';
|
|
10
|
-
import type { ReactNode } from 'react';
|
|
11
|
-
|
|
12
|
-
/** The view for plitzi_render: the entry the server bundles (React, the MCP Apps runtime and the Plitzi SDK
|
|
13
|
-
* included) and inlines in the shared ui:// page shell — which is what lets it run in a sandbox that can fetch
|
|
14
|
-
* nothing. It mounts on `#app`, the shell's root. */
|
|
15
|
-
|
|
16
|
-
const panelStyle = { padding: 16, font: '13px/1.5 system-ui, sans-serif', color: '#b91c1c' } as const;
|
|
17
|
-
|
|
18
|
-
// Every failure ends up here: an iframe left on its placeholder would hide the reason in the sandbox's console.
|
|
19
|
-
const ErrorPanel = ({ title, details }: { title: string; details: string }) => (
|
|
20
|
-
<div style={panelStyle}>
|
|
21
|
-
<strong>{title}</strong>
|
|
22
|
-
<pre style={{ margin: '8px 0 0', whiteSpace: 'pre-wrap', color: '#7f1d1d', fontSize: 12 }}>{details}</pre>
|
|
23
|
-
</div>
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
class RenderBoundary extends Component<{ children: ReactNode }, { error?: Error }> {
|
|
27
|
-
state: { error?: Error } = {};
|
|
28
|
-
|
|
29
|
-
static getDerivedStateFromError(error: Error) {
|
|
30
|
-
return { error };
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
render() {
|
|
34
|
-
if (this.state.error) {
|
|
35
|
-
return <ErrorPanel title="Widget failed to render" details={this.state.error.message} />;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return this.props.children;
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
const RenderApp = () => {
|
|
43
|
-
const [result, setResult] = useState<CallToolResult | null>(null);
|
|
44
|
-
const [cancelled, setCancelled] = useState<string | undefined>(undefined);
|
|
45
|
-
const [context, setContext] = useState<McpUiHostContext | undefined>(undefined);
|
|
46
|
-
|
|
47
|
-
// useApp creates the App, runs onAppCreated so every handler is in place BEFORE the handshake, and connects.
|
|
48
|
-
const { app, error } = useApp({
|
|
49
|
-
appInfo: { name: 'Plitzi Widget', version: '1.0.0' },
|
|
50
|
-
capabilities: {},
|
|
51
|
-
onAppCreated: instance => {
|
|
52
|
-
instance.ontoolresult = setResult;
|
|
53
|
-
instance.ontoolcancelled = params => setCancelled(params.reason ?? 'The host cancelled the render.');
|
|
54
|
-
instance.onhostcontextchanged = params => setContext(previous => ({ ...previous, ...params }));
|
|
55
|
-
instance.onteardown = () => ({});
|
|
56
|
-
}
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
// The context the host sent in its initialize result predates any change notification.
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
if (app) {
|
|
62
|
-
setContext(app.getHostContext());
|
|
63
|
-
}
|
|
64
|
-
}, [app]);
|
|
65
|
-
|
|
66
|
-
useHostStyles(app, context);
|
|
67
|
-
|
|
68
|
-
const failure = error?.message ?? cancelled;
|
|
69
|
-
if (failure) {
|
|
70
|
-
return <ErrorPanel title="Could not render the widget" details={failure} />;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Still connecting, or connected and waiting for the result: the page's CSS placeholder covers both.
|
|
74
|
-
if (!result) {
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// The payload rides in structuredContent, which the protocol types as unknown-valued; plitzi_render is the one
|
|
79
|
-
// guaranteeing the shape, and the SDK validates it again when it renders.
|
|
80
|
-
const offlineData = result.structuredContent?.offlineData as OfflineDataRaw | undefined;
|
|
81
|
-
if (!offlineData) {
|
|
82
|
-
// A failed render answers with its (already compact) reasons as the tool's JSON text summary.
|
|
83
|
-
const text = result.content.find(entry => entry.type === 'text');
|
|
84
|
-
|
|
85
|
-
return <ErrorPanel title="Render failed" details={text?.text ?? 'The tool returned no widget data.'} />;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const insets = context?.safeAreaInsets;
|
|
89
|
-
|
|
90
|
-
return (
|
|
91
|
-
<div
|
|
92
|
-
style={{
|
|
93
|
-
padding: `${insets?.top ?? 0}px ${insets?.right ?? 0}px ${insets?.bottom ?? 0}px ${insets?.left ?? 0}px`
|
|
94
|
-
}}
|
|
95
|
-
>
|
|
96
|
-
<RenderBoundary>
|
|
97
|
-
<PlitziSdk offlineData={offlineData} offlineMode environment="main" renderMode="raw" />
|
|
98
|
-
</RenderBoundary>
|
|
99
|
-
</div>
|
|
100
|
-
);
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const root = document.getElementById('app');
|
|
104
|
-
if (root) {
|
|
105
|
-
// The SDK styles its tree from this container class down.
|
|
106
|
-
root.className = 'plitzi-root-container';
|
|
107
|
-
createRoot(root).render(<RenderApp />);
|
|
108
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|