@plitzi/sdk-mcp 0.38.6 → 0.38.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.
- package/CHANGELOG.md +397 -0
- package/README.md +9 -9
- package/dist/index.d.ts +2 -2
- package/dist/index.js +2 -3
- package/dist/modules/mcp/helpers/guide.js +74 -35
- package/dist/modules/mcp/index.d.ts +2 -2
- package/dist/modules/mcp/server.d.ts +4 -4
- package/dist/modules/mcp/server.js +10 -5
- package/dist/modules/mcp/tests/cheapMistakes.test.d.ts +1 -0
- package/dist/modules/mcp/tests/helpers.d.ts +9 -2
- package/dist/modules/mcp/tests/intents.test.d.ts +1 -0
- package/dist/modules/mcp/tests/look.test.d.ts +1 -0
- package/dist/modules/mcp/tools/apply/index.d.ts +706 -62
- package/dist/modules/mcp/tools/apply/index.js +70 -20
- package/dist/modules/mcp/tools/describeOperation.d.ts +11 -0
- package/dist/modules/mcp/tools/describeOperation.js +40 -0
- package/dist/modules/mcp/tools/describeOperation.test.d.ts +1 -0
- package/dist/modules/mcp/tools/index.d.ts +1 -6
- package/dist/modules/mcp/tools/index.js +12 -9
- package/dist/modules/mcp/tools/intents.d.ts +5 -0
- package/dist/modules/mcp/tools/intents.js +201 -0
- package/dist/modules/mcp/tools/look.d.ts +17 -0
- package/dist/modules/mcp/tools/look.js +32 -0
- package/dist/modules/mcp/tools/operations/closed.d.ts +7 -0
- package/dist/modules/mcp/tools/operations/closed.js +29 -0
- package/dist/modules/mcp/tools/operations/index.d.ts +2928 -359
- package/dist/modules/mcp/tools/operations/index.js +72 -51
- package/dist/modules/mcp/tools/operations/schema/bindings/upsertBinding.d.ts +1 -1
- package/dist/modules/mcp/tools/operations/schema/elements/moveElement.d.ts +1 -1
- package/dist/modules/mcp/tools/operations/schema/elements/repeatElement.d.ts +1 -1
- package/dist/modules/mcp/tools/operations/schema/elements/upsertElement.d.ts +1 -1
- package/dist/modules/mcp/tools/operations/schema/interactions/upsertInteractionFlow.d.ts +1 -1
- package/dist/modules/mcp/tools/operations/schema/operations.d.ts +5 -5
- package/dist/modules/mcp/tools/operations/schema/shared.d.ts +3 -3
- package/dist/modules/mcp/tools/operations/schema/shared.js +7 -6
- package/dist/modules/mcp/tools/operations/schemaIds.js +11 -5
- package/dist/modules/mcp/tools/operations/style/definitions/patchDefinition.d.ts +90 -0
- package/dist/modules/mcp/tools/operations/style/definitions/patchDefinition.js +2 -1
- package/dist/modules/mcp/tools/operations/style/definitions/upsertDefinition.d.ts +90 -0
- package/dist/modules/mcp/tools/operations/style/definitions/upsertDefinition.js +2 -1
- package/dist/modules/mcp/tools/operations/style/definitions/upsertDefinitions.d.ts +90 -0
- package/dist/modules/mcp/tools/operations/style/definitions/upsertDefinitions.js +8 -1
- package/dist/modules/mcp/tools/operations/style/globalStyles/patchGlobalStyle.d.ts +90 -0
- package/dist/modules/mcp/tools/operations/style/globalStyles/patchGlobalStyle.js +2 -1
- package/dist/modules/mcp/tools/operations/style/globalStyles/upsertGlobalStyle.d.ts +90 -0
- package/dist/modules/mcp/tools/operations/style/globalStyles/upsertGlobalStyle.js +2 -1
- package/dist/modules/mcp/tools/operations/style/idStyles/patchIdStyle.d.ts +90 -0
- package/dist/modules/mcp/tools/operations/style/idStyles/patchIdStyle.js +2 -1
- package/dist/modules/mcp/tools/operations/style/idStyles/upsertIdStyle.d.ts +90 -0
- package/dist/modules/mcp/tools/operations/style/idStyles/upsertIdStyle.js +2 -1
- package/dist/modules/mcp/tools/operations/style/operations.d.ts +639 -9
- package/dist/modules/mcp/tools/operations/style/shared.d.ts +394 -0
- package/dist/modules/mcp/tools/operations/style/shared.js +26 -8
- package/dist/modules/mcp/tools/operations/style/translator.js +26 -1
- package/dist/modules/mcp/tools/operations/style/validate.d.ts +4 -0
- package/dist/modules/mcp/tools/operations/style/validate.js +63 -0
- package/dist/modules/mcp/tools/operations/style/write.d.ts +1 -1
- package/dist/modules/mcp/tools/operations/style/write.js +88 -2
- package/dist/modules/mcp/tools/render.d.ts +675 -45
- package/dist/modules/mcp/tools/render.js +5 -1
- package/dist/modules/mcp/tools/shared/draftBatch.d.ts +3 -2
- package/dist/modules/mcp/tools/shared/draftBatch.js +16 -5
- package/dist/modules/mcp/tools/shared/effects.d.ts +2 -0
- package/dist/modules/mcp/tools/shared/effects.js +155 -0
- package/dist/modules/mcp/tools/shared/effects.test.d.ts +1 -0
- package/dist/modules/mcp/tools/shared/look.d.ts +32 -0
- package/dist/modules/mcp/tools/shared/look.js +128 -0
- package/dist/modules/mcp/tools/shared/normalizeOperations.d.ts +6 -0
- package/dist/modules/mcp/tools/shared/normalizeOperations.js +37 -0
- package/dist/modules/mcp/tools/shared/repeats.d.ts +13 -0
- package/dist/modules/mcp/tools/shared/repeats.js +38 -0
- package/dist/modules/mcp/tools/shared/tool.d.ts +9 -6
- package/dist/modules/mcp/tools/shared/tool.js +2 -1
- package/dist/modules/mcp/tools/shared/validator/context.d.ts +0 -1
- package/dist/modules/mcp/tools/shared/validator/elements.js +0 -1
- package/dist/modules/mcp/tools/shared/validator/index.js +25 -2
- package/dist/modules/mcp/types/aiSchema.d.ts +13 -1
- package/dist/modules/mcp/types/screenshotTypes.d.ts +3 -3
- package/dist/modules/mcp/types/toolTypes.d.ts +2 -4
- package/dist/options.d.ts +4 -5
- package/dist/preview/createPreview.js +9 -18
- package/package.json +7 -7
- package/skills/plitzi-render/SKILL.md +8 -184
- package/skills/plitzi-render/reference/good-call.md +88 -0
- package/skills/plitzi-render/reference/interactive.md +64 -0
- package/skills/plitzi-render/reference/looks.md +29 -0
- package/dist/modules/mcp/tools/preview.d.ts +0 -793
- package/dist/modules/mcp/tools/preview.js +0 -37
- package/dist/modules/mcp/tools/screenshot.d.ts +0 -1078
- package/dist/modules/mcp/tools/screenshot.js +0 -127
- package/dist/modules/mcp/tools/validate.d.ts +0 -1069
- package/dist/modules/mcp/tools/validate.js +0 -32
|
@@ -2,7 +2,10 @@ import { dataFileUri, dataUri, functionFileUri, functionsUri } from "../../helpe
|
|
|
2
2
|
import { changedResources, conflictMessage, detectConflicts, resolvedElements } from "./writeResult.js";
|
|
3
3
|
import { environment, operations } from "../operations/index.js";
|
|
4
4
|
import { draftBatch } from "../shared/draftBatch.js";
|
|
5
|
-
import {
|
|
5
|
+
import { effectsOf } from "../shared/effects.js";
|
|
6
|
+
import { fullPage, lookAt, pageRef, viewport } from "../shared/look.js";
|
|
7
|
+
import { AGAIN, REPEATED, batchKey, forgetRefused, noteRefused, timesRefused } from "../shared/repeats.js";
|
|
8
|
+
import { defineTool, imageResult } from "../shared/tool.js";
|
|
6
9
|
import { z } from "zod";
|
|
7
10
|
import { generateCache } from "@plitzi/sdk-style/StyleHelper";
|
|
8
11
|
//#region src/modules/mcp/tools/apply/index.ts
|
|
@@ -10,8 +13,18 @@ var applyShape = {
|
|
|
10
13
|
environment,
|
|
11
14
|
dryRun: z.boolean().optional().describe("Validate and apply in memory only, WITHOUT persisting. Returns the same result (changed versions + full element detail) so you can inspect the outcome and decide on more changes before committing for real."),
|
|
12
15
|
expectedResourceVersions: z.record(z.string(), z.string()).optional().describe("Resource URI → the stateVersion you read; guards against concurrent edits"),
|
|
13
|
-
operations
|
|
16
|
+
operations,
|
|
17
|
+
look: z.enum([
|
|
18
|
+
"html",
|
|
19
|
+
"image",
|
|
20
|
+
"accessibility",
|
|
21
|
+
"both"
|
|
22
|
+
]).optional().describe("Also render the page: with dryRun, as the batch would leave it (nothing saved); without, as it was saved. \"html\" the markup, \"image\" a PNG, \"accessibility\" the outline a screen reader reads (text, far cheaper than an image), \"both\" image and outline. Check, look and then save with the same operations — written once."),
|
|
23
|
+
pageRef,
|
|
24
|
+
viewport,
|
|
25
|
+
fullPage
|
|
14
26
|
};
|
|
27
|
+
var NOTHING_CHANGED = "Nothing changed: every operation left the space as it was.";
|
|
15
28
|
var noWarnings = (warnings) => warnings.length > 0 ? warnings : void 0;
|
|
16
29
|
/** Why the platform did not save the functions, as the batch's errors: each problem where it is, or the refusal. */
|
|
17
30
|
var functionsErrors = (saved, env) => "problems" in saved ? saved.problems.map((problem) => ({
|
|
@@ -62,7 +75,9 @@ var apply = async (input, space, persisters) => {
|
|
|
62
75
|
errors: result.errors,
|
|
63
76
|
warnings: noWarnings(result.warnings)
|
|
64
77
|
};
|
|
65
|
-
const { draft, outcome,
|
|
78
|
+
const { draft, outcome, suggestions } = result;
|
|
79
|
+
const effects = effectsOf(space, draft);
|
|
80
|
+
const warnings = effects.length > 0 ? result.warnings : [NOTHING_CHANGED, ...result.warnings];
|
|
66
81
|
if (input.dryRun) return {
|
|
67
82
|
applied: false,
|
|
68
83
|
dryRun: true,
|
|
@@ -71,12 +86,13 @@ var apply = async (input, space, persisters) => {
|
|
|
71
86
|
updated: outcome.updated,
|
|
72
87
|
deleted: outcome.deleted
|
|
73
88
|
},
|
|
89
|
+
effects,
|
|
74
90
|
changed: changedResources(draft, env, outcome.staleResources),
|
|
75
91
|
elements: resolvedElements(draft, env, outcome.elementRefs),
|
|
76
92
|
warnings: noWarnings(warnings),
|
|
77
93
|
suggestions: noWarnings(suggestions)
|
|
78
94
|
};
|
|
79
|
-
|
|
95
|
+
const unsaved = [];
|
|
80
96
|
if (outcome.changedFunctions && draft.functions) {
|
|
81
97
|
if (persisters?.saveFunctions) {
|
|
82
98
|
const saved = await persisters.saveFunctions(draft.functions.files, space.functions?.version ?? "");
|
|
@@ -92,7 +108,7 @@ var apply = async (input, space, persisters) => {
|
|
|
92
108
|
errors: functionsErrors(saved, env),
|
|
93
109
|
warnings: noWarnings(warnings)
|
|
94
110
|
};
|
|
95
|
-
} else
|
|
111
|
+
} else unsaved.push("the functions");
|
|
96
112
|
}
|
|
97
113
|
if (outcome.changedData && draft.data) {
|
|
98
114
|
if (persisters?.saveData) {
|
|
@@ -109,56 +125,90 @@ var apply = async (input, space, persisters) => {
|
|
|
109
125
|
errors: dataErrors(saved, env),
|
|
110
126
|
warnings: noWarnings([...outcome.changedFunctions && persisters.saveFunctions ? ["The functions of this batch were saved; the data was not — apply the data again once fixed"] : [], ...warnings])
|
|
111
127
|
};
|
|
112
|
-
} else
|
|
128
|
+
} else unsaved.push("the data");
|
|
113
129
|
}
|
|
114
130
|
if (outcome.changedSchema) {
|
|
115
131
|
if (persisters?.schema) await persisters.schema(draft.schema);
|
|
116
|
-
else
|
|
132
|
+
else unsaved.push("the schema");
|
|
117
133
|
}
|
|
118
134
|
if (outcome.changedStyle) {
|
|
119
135
|
if (persisters?.style) {
|
|
120
136
|
draft.style.cache = generateCache(draft.style);
|
|
121
137
|
await persisters.style(draft.style);
|
|
122
|
-
} else
|
|
138
|
+
} else unsaved.push("the style");
|
|
123
139
|
}
|
|
124
140
|
for (const id of outcome.changedConnectors) {
|
|
125
141
|
const entry = draft.connectors.find((item) => item.id === id);
|
|
126
142
|
if (entry && persisters?.saveConnector) await persisters.saveConnector(entry);
|
|
127
|
-
else
|
|
143
|
+
else unsaved.push(`connector ${id}`);
|
|
128
144
|
}
|
|
129
145
|
for (const id of outcome.deletedConnectors) if (persisters?.deleteConnector) await persisters.deleteConnector(id);
|
|
130
|
-
else
|
|
146
|
+
else unsaved.push(`the removal of connector ${id}`);
|
|
131
147
|
for (const id of outcome.changedActions) {
|
|
132
148
|
const entry = draft.actions.find((item) => item.id === id);
|
|
133
149
|
if (entry && persisters?.saveAction) await persisters.saveAction(entry);
|
|
134
|
-
else
|
|
150
|
+
else unsaved.push(`action ${id}`);
|
|
135
151
|
}
|
|
136
152
|
for (const id of outcome.deletedActions) if (persisters?.deleteAction) await persisters.deleteAction(id);
|
|
137
|
-
else
|
|
153
|
+
else unsaved.push(`the removal of action ${id}`);
|
|
138
154
|
return {
|
|
139
155
|
applied: true,
|
|
140
|
-
persisted,
|
|
156
|
+
persisted: unsaved.length === 0,
|
|
141
157
|
summary: {
|
|
142
158
|
created: outcome.created,
|
|
143
159
|
updated: outcome.updated,
|
|
144
160
|
deleted: outcome.deleted
|
|
145
161
|
},
|
|
162
|
+
effects,
|
|
146
163
|
changed: changedResources(draft, env, outcome.staleResources),
|
|
147
164
|
elements: resolvedElements(draft, env, outcome.elementRefs),
|
|
148
|
-
warnings: noWarnings(warnings),
|
|
165
|
+
warnings: noWarnings([...unsaved.length > 0 ? [`NOT saved: ${unsaved.join(", ")} — this server has no store to save it in`] : [], ...warnings]),
|
|
149
166
|
suggestions: noWarnings(suggestions)
|
|
150
167
|
};
|
|
151
168
|
};
|
|
152
169
|
var applyTool = defineTool({
|
|
153
170
|
name: "plitzi_apply",
|
|
154
171
|
title: "Apply",
|
|
155
|
-
description: "Validate, apply and persist a batch of operations atomically. Returns
|
|
172
|
+
description: "Validate, apply and persist a batch of operations atomically. Returns `effects` (what it changed, read off the space), the changed resources’ new versions and each created or updated element’s detail. Pass dryRun to apply in memory only (inspect the outcome without committing), and `look` to see the page as the batch leaves it. Rejects the whole batch on any error or version conflict — INCLUDING a pre-existing malformation in any resource the batch touches (fix it in the same batch to unblock the save).",
|
|
156
173
|
inputShape: applyShape,
|
|
174
|
+
carriesOperations: true,
|
|
157
175
|
access: "write",
|
|
158
|
-
run: (input, ctx) =>
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
176
|
+
run: async (input, ctx) => {
|
|
177
|
+
const key = batchKey(`${String(ctx.spaceId ?? "")}:${ctx.env}`, input.operations);
|
|
178
|
+
const before = timesRefused(key);
|
|
179
|
+
if (before >= 2) return REPEATED;
|
|
180
|
+
const result = await apply({
|
|
181
|
+
...input,
|
|
182
|
+
environment: ctx.env
|
|
183
|
+
}, ctx.space, ctx.persisters);
|
|
184
|
+
if (result.errors?.length) {
|
|
185
|
+
noteRefused(key);
|
|
186
|
+
return before === 1 ? {
|
|
187
|
+
...result,
|
|
188
|
+
again: AGAIN
|
|
189
|
+
} : result;
|
|
190
|
+
}
|
|
191
|
+
forgetRefused(key);
|
|
192
|
+
if (!input.look || !result.applied && !result.dryRun) return result;
|
|
193
|
+
const look = await lookAt(ctx, {
|
|
194
|
+
pageRef: input.pageRef,
|
|
195
|
+
operations: result.dryRun ? input.operations : void 0,
|
|
196
|
+
view: input.look,
|
|
197
|
+
viewport: input.viewport,
|
|
198
|
+
fullPage: input.fullPage
|
|
199
|
+
});
|
|
200
|
+
if ("refused" in look) return {
|
|
201
|
+
...result,
|
|
202
|
+
look: look.refused
|
|
203
|
+
};
|
|
204
|
+
return look.images.length > 0 ? imageResult(look.images, {
|
|
205
|
+
...result,
|
|
206
|
+
look: look.meta
|
|
207
|
+
}) : {
|
|
208
|
+
...result,
|
|
209
|
+
look: look.meta
|
|
210
|
+
};
|
|
211
|
+
}
|
|
162
212
|
});
|
|
163
213
|
//#endregion
|
|
164
|
-
export { apply, applyShape, applyTool };
|
|
214
|
+
export { NOTHING_CHANGED, apply, applyShape, applyTool };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const describeOperationShape: {
|
|
3
|
+
type: z.ZodOptional<z.ZodString>;
|
|
4
|
+
};
|
|
5
|
+
/**
|
|
6
|
+
* One operation's schema, by its type — the low level asked for one piece at a time, so a connection does not have to
|
|
7
|
+
* load all of it to write one operation. A type that does not exist is answered with the closed list and the nearest
|
|
8
|
+
* name: an agent that guessed is told what there is, never left to guess again.
|
|
9
|
+
*/
|
|
10
|
+
export declare const describeOperation: (type: string | undefined) => Record<string, unknown>;
|
|
11
|
+
export declare const describeOperationTool: import('.').ToolDef;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { OPERATIONS_BY_TYPE } from "./operations/index.js";
|
|
2
|
+
import { defineTool } from "./shared/tool.js";
|
|
3
|
+
import { closest } from "@plitzi/sdk-authoring";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
//#region src/modules/mcp/tools/describeOperation.ts
|
|
6
|
+
var describeOperationShape = { type: z.string().optional().describe("The operation type, as in a batch: \"upsertElement\", \"patchDefinition\"… Left out, every type is listed.") };
|
|
7
|
+
/**
|
|
8
|
+
* One operation's schema, by its type — the low level asked for one piece at a time, so a connection does not have to
|
|
9
|
+
* load all of it to write one operation. A type that does not exist is answered with the closed list and the nearest
|
|
10
|
+
* name: an agent that guessed is told what there is, never left to guess again.
|
|
11
|
+
*/
|
|
12
|
+
var describeOperation = (type) => {
|
|
13
|
+
const types = [...OPERATIONS_BY_TYPE.keys()];
|
|
14
|
+
if (type === void 0) return { types };
|
|
15
|
+
const schema = OPERATIONS_BY_TYPE.get(type);
|
|
16
|
+
if (!schema) {
|
|
17
|
+
const nearest = closest(type, types);
|
|
18
|
+
return {
|
|
19
|
+
error: "UNKNOWN_OPERATION",
|
|
20
|
+
message: `There is no "${type}" operation${nearest ? ` — did you mean "${nearest}"?` : ""}`,
|
|
21
|
+
...nearest ? { describe: { type: nearest } } : {},
|
|
22
|
+
types
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
type,
|
|
27
|
+
schema: z.toJSONSchema(schema)
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
var describeOperationTool = defineTool({
|
|
31
|
+
name: "plitzi_describe_operation",
|
|
32
|
+
title: "Describe an operation",
|
|
33
|
+
description: "The schema of one operation plitzi_apply or plitzi_render takes, by its type: every field, which are required, what each means. Without a type, every operation type there is.",
|
|
34
|
+
inputShape: describeOperationShape,
|
|
35
|
+
access: "read",
|
|
36
|
+
spaceless: true,
|
|
37
|
+
run: (input) => describeOperation(input.type)
|
|
38
|
+
});
|
|
39
|
+
//#endregion
|
|
40
|
+
export { describeOperation, describeOperationShape, describeOperationTool };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { ToolDef } from './shared/tool';
|
|
2
2
|
export { apply, applyShape } from './apply';
|
|
3
|
-
export { validate, validateShape } from './validate';
|
|
4
3
|
export { search, searchShape } from './search';
|
|
5
4
|
export { read, readShape } from './read';
|
|
6
|
-
export { previewShape } from './preview';
|
|
7
|
-
export { render, renderShape } from './render';
|
|
8
|
-
export { screenshotShape } from './screenshot';
|
|
9
|
-
export { applyOperations } from './apply/dispatch';
|
|
10
5
|
export { validateOperations } from './shared/validator';
|
|
11
6
|
export { operation } from './operations';
|
|
12
7
|
/** The MCP tool registry — the single source the server registers from. Adding a tool is: create its file with a
|
|
@@ -15,4 +10,4 @@ export declare const tools: ToolDef[];
|
|
|
15
10
|
export type { ToolContext, ToolDef } from './shared/tool';
|
|
16
11
|
export type { OpResult } from '../helpers';
|
|
17
12
|
export type { DefinitionSlotInput, DefinitionSlotPatch, ElementInput, Operation, OperationType } from './operations';
|
|
18
|
-
export type { ApplyInput, ChangedResource, Conflict, Persisters, WriteElement, WriteResponse,
|
|
13
|
+
export type { ApplyInput, ChangedResource, Conflict, Persisters, WriteElement, WriteResponse, ValidationResult, MutationOutcome, SearchHit, SearchInput, SearchResponse, SearchPageHit, ReadInput, ReadResponse, ReadHit } from '../types';
|
|
@@ -1,26 +1,29 @@
|
|
|
1
1
|
import { operation } from "./operations/index.js";
|
|
2
|
-
import
|
|
3
|
-
import { applyOperations } from "./apply/dispatch.js";
|
|
2
|
+
import "./shared/validator/index.js";
|
|
4
3
|
import { apply, applyShape, applyTool } from "./apply/index.js";
|
|
5
|
-
import {
|
|
4
|
+
import { describeOperationTool } from "./describeOperation.js";
|
|
5
|
+
import { addPageTool, bindAttributeTool, placeComponentTool, setAttributesTool, setClassesTool } from "./intents.js";
|
|
6
|
+
import { lookTool } from "./look.js";
|
|
6
7
|
import { read, readShape, readTool } from "./read.js";
|
|
7
8
|
import { renderTool } from "./render.js";
|
|
8
|
-
import { screenshotTool } from "./screenshot.js";
|
|
9
9
|
import { search, searchShape, searchTool } from "./search.js";
|
|
10
10
|
import { tryFunctionTool } from "./tryFunction.js";
|
|
11
|
-
import { validate, validateShape, validateTool } from "./validate.js";
|
|
12
11
|
//#region src/modules/mcp/tools/index.ts
|
|
13
12
|
/** The MCP tool registry — the single source the server registers from. Adding a tool is: create its file with a
|
|
14
13
|
* ToolDef descriptor and append it here. */
|
|
15
14
|
var tools = [
|
|
16
15
|
applyTool,
|
|
17
|
-
|
|
16
|
+
setAttributesTool,
|
|
17
|
+
setClassesTool,
|
|
18
|
+
bindAttributeTool,
|
|
19
|
+
placeComponentTool,
|
|
20
|
+
addPageTool,
|
|
18
21
|
searchTool,
|
|
22
|
+
describeOperationTool,
|
|
19
23
|
readTool,
|
|
20
24
|
renderTool,
|
|
21
|
-
|
|
22
|
-
screenshotTool,
|
|
25
|
+
lookTool,
|
|
23
26
|
tryFunctionTool
|
|
24
27
|
];
|
|
25
28
|
//#endregion
|
|
26
|
-
export { apply,
|
|
29
|
+
export { apply, applyShape, operation, read, readShape, search, searchShape, tools };
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const setAttributesTool: import('.').ToolDef;
|
|
2
|
+
export declare const setClassesTool: import('.').ToolDef;
|
|
3
|
+
export declare const bindAttributeTool: import('.').ToolDef;
|
|
4
|
+
export declare const placeComponentTool: import('.').ToolDef;
|
|
5
|
+
export declare const addPageTool: import('.').ToolDef;
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { findElementByRef } from "../helpers/space.js";
|
|
2
|
+
import { scalar } from "./operations/schema/shared.js";
|
|
3
|
+
import { defineTool } from "./shared/tool.js";
|
|
4
|
+
import { apply } from "./apply/index.js";
|
|
5
|
+
import { closest } from "@plitzi/sdk-authoring";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
//#region src/modules/mcp/tools/intents.ts
|
|
8
|
+
/** The element a ref names, with the page or layout it is on — or the nearest ref there is. */
|
|
9
|
+
var locate = (ctx, ref) => {
|
|
10
|
+
const element = findElementByRef(ctx.space.schema, ref);
|
|
11
|
+
if (element) return {
|
|
12
|
+
element,
|
|
13
|
+
root: element.definition.rootId,
|
|
14
|
+
classes: element.definition.styleSelectors.base.split(/\s+/).filter(Boolean)
|
|
15
|
+
};
|
|
16
|
+
const nearest = closest(ref, Object.keys(ctx.space.schema.flat));
|
|
17
|
+
return {
|
|
18
|
+
done: false,
|
|
19
|
+
errors: [{
|
|
20
|
+
path: "ref",
|
|
21
|
+
message: `There is no element "${ref}"${nearest ? ` — did you mean "${nearest}"?` : ""}`,
|
|
22
|
+
hint: "plitzi_search finds an element by its words, type or class"
|
|
23
|
+
}]
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
/** The attributes of an element a binding computes, with the source each reads. */
|
|
27
|
+
var boundOf = (element) => new Map((element.definition.bindings?.attributes ?? []).map((binding) => [binding.to, binding.source]));
|
|
28
|
+
/**
|
|
29
|
+
* The batch applied, answered with what it did. `notes` are what the agent believed that was already so — a class it
|
|
30
|
+
* wears, an attribute it did not have — said beside the effects, never dropped because nothing came of them.
|
|
31
|
+
*/
|
|
32
|
+
var run = async (ctx, operations, page, notes = []) => {
|
|
33
|
+
const result = await apply({
|
|
34
|
+
operations,
|
|
35
|
+
environment: ctx.env
|
|
36
|
+
}, ctx.space, ctx.persisters);
|
|
37
|
+
const warnings = [...notes, ...result.warnings ?? []];
|
|
38
|
+
if (!result.applied) return {
|
|
39
|
+
done: false,
|
|
40
|
+
errors: result.errors ?? (result.conflict ? [result.conflict] : []),
|
|
41
|
+
...warnings.length > 0 ? { warnings } : {}
|
|
42
|
+
};
|
|
43
|
+
return {
|
|
44
|
+
done: true,
|
|
45
|
+
saved: result.persisted !== false,
|
|
46
|
+
effects: result.effects ?? [],
|
|
47
|
+
...warnings.length > 0 ? { warnings } : {},
|
|
48
|
+
next: `plitzi_look { pageRef: "${page}" } to see it`
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
var setAttributesTool = defineTool({
|
|
52
|
+
name: "plitzi_set_attributes",
|
|
53
|
+
title: "Set an element’s attributes",
|
|
54
|
+
description: "Set or remove attributes of one element — its words (`content`), a link’s `href`, an image’s `src` and `alt`, a field’s `label`… — by its ref. plitzi://explain/{type} lists what a type takes.",
|
|
55
|
+
inputShape: {
|
|
56
|
+
ref: z.string().describe("The element, by its ref"),
|
|
57
|
+
set: z.record(z.string(), scalar).optional().describe("Attributes to write: { \"content\": \"Start free\" }"),
|
|
58
|
+
unset: z.array(z.string()).optional().describe("Attributes to remove")
|
|
59
|
+
},
|
|
60
|
+
access: "write",
|
|
61
|
+
run: async (input, ctx) => {
|
|
62
|
+
const at = locate(ctx, input.ref);
|
|
63
|
+
if ("done" in at) return at;
|
|
64
|
+
const set = input.set ?? {};
|
|
65
|
+
const unset = input.unset ?? [];
|
|
66
|
+
const bound = boundOf(at.element);
|
|
67
|
+
const covered = [...Object.keys(set), ...unset].filter((key) => bound.has(key));
|
|
68
|
+
if (covered.length > 0) return {
|
|
69
|
+
done: false,
|
|
70
|
+
errors: covered.map((key) => ({
|
|
71
|
+
path: key,
|
|
72
|
+
message: `${key} of ${input.ref} is bound to ${String(bound.get(key))}: the page shows what the binding computes, not a value written here`,
|
|
73
|
+
hint: "plitzi_bind_attribute changes what it reads; to write a value instead, plitzi_apply deleteBinding first"
|
|
74
|
+
}))
|
|
75
|
+
};
|
|
76
|
+
const { attributes } = at.element;
|
|
77
|
+
const notes = [...Object.entries(set).filter(([key, value]) => attributes[key] === value).map(([key, value]) => `${key} was already ${JSON.stringify(value)}`), ...unset.filter((key) => !(key in attributes)).map((key) => `${key} was not set`)];
|
|
78
|
+
const props = {
|
|
79
|
+
...set,
|
|
80
|
+
...Object.fromEntries(unset.map((key) => [key, null]))
|
|
81
|
+
};
|
|
82
|
+
return run(ctx, [{
|
|
83
|
+
type: "patchElement",
|
|
84
|
+
pageRef: at.root,
|
|
85
|
+
ref: input.ref,
|
|
86
|
+
props
|
|
87
|
+
}], at.root, notes);
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
var setClassesTool = defineTool({
|
|
91
|
+
name: "plitzi_set_classes",
|
|
92
|
+
title: "Add or remove an element’s classes",
|
|
93
|
+
description: "Add classes to one element, or take them off; the ones it wears and you do not name stay. A class is a definition of the space (plitzi://definitions/{env}).",
|
|
94
|
+
inputShape: {
|
|
95
|
+
ref: z.string().describe("The element, by its ref"),
|
|
96
|
+
add: z.array(z.string()).optional().describe("Classes it should also wear, by name"),
|
|
97
|
+
remove: z.array(z.string()).optional().describe("Classes it should no longer wear, by name")
|
|
98
|
+
},
|
|
99
|
+
access: "write",
|
|
100
|
+
run: async (input, ctx) => {
|
|
101
|
+
const at = locate(ctx, input.ref);
|
|
102
|
+
if ("done" in at) return at;
|
|
103
|
+
const absent = (input.remove ?? []).filter((name) => !at.classes.includes(name));
|
|
104
|
+
if (absent.length > 0) return {
|
|
105
|
+
done: false,
|
|
106
|
+
errors: absent.map((name) => {
|
|
107
|
+
const nearest = closest(name, at.classes);
|
|
108
|
+
return {
|
|
109
|
+
path: "remove",
|
|
110
|
+
message: `${input.ref} does not wear "${name}"${nearest ? ` — did you mean "${nearest}"?` : ""}; it wears ${at.classes.join(", ") || "no class"}`
|
|
111
|
+
};
|
|
112
|
+
})
|
|
113
|
+
};
|
|
114
|
+
const notes = (input.add ?? []).filter((name) => at.classes.includes(name)).map((name) => `${input.ref} already wore ${name}`);
|
|
115
|
+
const removed = new Set(input.remove ?? []);
|
|
116
|
+
const base = [...at.classes.filter((name) => !removed.has(name)), ...(input.add ?? []).filter((name) => !at.classes.includes(name))];
|
|
117
|
+
return run(ctx, [{
|
|
118
|
+
type: "patchElement",
|
|
119
|
+
pageRef: at.root,
|
|
120
|
+
ref: input.ref,
|
|
121
|
+
style: { base }
|
|
122
|
+
}], at.root, notes);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
var bindAttributeTool = defineTool({
|
|
126
|
+
name: "plitzi_bind_attribute",
|
|
127
|
+
title: "Bind an element to data",
|
|
128
|
+
description: "Feed one attribute of an element from a data source — a list’s `items` from `apiContainer_products.data`, a heading’s `content` from a field. Sources: plitzi://data-sources/{env}.",
|
|
129
|
+
inputShape: {
|
|
130
|
+
ref: z.string().describe("The element, by its ref"),
|
|
131
|
+
to: z.string().describe("The attribute it feeds: \"items\", \"content\"…"),
|
|
132
|
+
source: z.string().describe("The source path, spelled in full: \"apiContainer_products.data\"")
|
|
133
|
+
},
|
|
134
|
+
access: "write",
|
|
135
|
+
run: async (input, ctx) => {
|
|
136
|
+
const at = locate(ctx, input.ref);
|
|
137
|
+
if ("done" in at) return at;
|
|
138
|
+
const was = boundOf(at.element).get(input.to);
|
|
139
|
+
return run(ctx, [{
|
|
140
|
+
type: "upsertBinding",
|
|
141
|
+
pageRef: at.root,
|
|
142
|
+
ref: input.ref,
|
|
143
|
+
category: "attributes",
|
|
144
|
+
binding: {
|
|
145
|
+
to: input.to,
|
|
146
|
+
source: input.source
|
|
147
|
+
}
|
|
148
|
+
}], at.root, was === void 0 ? [] : [`${input.ref}.${input.to} was bound to ${was}: replaced`]);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
var placeComponentTool = defineTool({
|
|
152
|
+
name: "plitzi_place_component",
|
|
153
|
+
title: "Place a component",
|
|
154
|
+
description: "Place an instance of one of the space’s components inside an element, handing it its props. plitzi://schema/{env}/components lists them and what each takes.",
|
|
155
|
+
inputShape: {
|
|
156
|
+
component: z.string().describe("The component, by its ref"),
|
|
157
|
+
ref: z.string().describe("A ref for the new instance"),
|
|
158
|
+
into: z.string().describe("The element it goes inside, by its ref"),
|
|
159
|
+
props: z.record(z.string(), scalar).optional().describe("What it hands the component: { \"title\": \"Lamp\" }")
|
|
160
|
+
},
|
|
161
|
+
access: "write",
|
|
162
|
+
run: async (input, ctx) => {
|
|
163
|
+
const at = locate(ctx, input.into);
|
|
164
|
+
if ("done" in at) return at;
|
|
165
|
+
return run(ctx, [{
|
|
166
|
+
type: "upsertElement",
|
|
167
|
+
pageRef: at.root,
|
|
168
|
+
parentRef: input.into,
|
|
169
|
+
element: {
|
|
170
|
+
ref: input.ref,
|
|
171
|
+
type: "reference",
|
|
172
|
+
props: {
|
|
173
|
+
referenceType: "component",
|
|
174
|
+
referenceId: input.component,
|
|
175
|
+
...input.props
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}], at.root);
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
var addPageTool = defineTool({
|
|
182
|
+
name: "plitzi_add_page",
|
|
183
|
+
title: "Add a page",
|
|
184
|
+
description: "A new page at a slug, inside a layout when the space has one for it (plitzi://schema/{env}/pages).",
|
|
185
|
+
inputShape: {
|
|
186
|
+
ref: z.string().describe("Its ref: \"pricing\""),
|
|
187
|
+
slug: z.string().describe("Its path, relative and without a leading slash: \"pricing\", \"posts/:postId\""),
|
|
188
|
+
label: z.string().optional().describe("Its name in the builder"),
|
|
189
|
+
layout: z.string().optional().describe("The layout it renders inside, by its ref")
|
|
190
|
+
},
|
|
191
|
+
access: "write",
|
|
192
|
+
run: async (input, ctx) => run(ctx, [{
|
|
193
|
+
type: "upsertPage",
|
|
194
|
+
ref: input.ref,
|
|
195
|
+
slug: input.slug,
|
|
196
|
+
...input.label ? { label: input.label } : {},
|
|
197
|
+
...input.layout ? { layout: input.layout } : {}
|
|
198
|
+
}], input.ref)
|
|
199
|
+
});
|
|
200
|
+
//#endregion
|
|
201
|
+
export { addPageTool, bindAttributeTool, placeComponentTool, setAttributesTool, setClassesTool };
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export declare const lookShape: {
|
|
3
|
+
pageRef: z.ZodOptional<z.ZodString>;
|
|
4
|
+
view: z.ZodOptional<z.ZodEnum<{
|
|
5
|
+
image: "image";
|
|
6
|
+
accessibility: "accessibility";
|
|
7
|
+
html: "html";
|
|
8
|
+
both: "both";
|
|
9
|
+
}>>;
|
|
10
|
+
viewport: z.ZodOptional<z.ZodEnum<{
|
|
11
|
+
desktop: "desktop";
|
|
12
|
+
mobile: "mobile";
|
|
13
|
+
both: "both";
|
|
14
|
+
}>>;
|
|
15
|
+
fullPage: z.ZodOptional<z.ZodBoolean>;
|
|
16
|
+
};
|
|
17
|
+
export declare const lookTool: import('.').ToolDef;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { fullPage, lookAt, pageRef, viewport } from "./shared/look.js";
|
|
2
|
+
import { defineTool, imageResult } from "./shared/tool.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
//#region src/modules/mcp/tools/look.ts
|
|
5
|
+
var lookShape = {
|
|
6
|
+
pageRef,
|
|
7
|
+
view: z.enum([
|
|
8
|
+
"accessibility",
|
|
9
|
+
"html",
|
|
10
|
+
"image",
|
|
11
|
+
"both"
|
|
12
|
+
]).optional().describe("How to see it. \"accessibility\" (default): the page as a screen reader and a browser agent read it — an outline of roles and names, every control with no name listed; text, and cheap. \"html\": the markup. \"image\": a PNG, for what only a picture shows — overflow, misalignment. \"both\": image and outline."),
|
|
13
|
+
viewport,
|
|
14
|
+
fullPage
|
|
15
|
+
};
|
|
16
|
+
var lookTool = defineTool({
|
|
17
|
+
name: "plitzi_look",
|
|
18
|
+
title: "Look at a page",
|
|
19
|
+
description: "See a saved page: its accessibility outline (default, text), its HTML, or a PNG. To see a change before saving it, plitzi_apply with `dryRun` and `look` instead.",
|
|
20
|
+
inputShape: lookShape,
|
|
21
|
+
access: "read",
|
|
22
|
+
run: async (input, ctx) => {
|
|
23
|
+
const look = await lookAt(ctx, {
|
|
24
|
+
...input,
|
|
25
|
+
view: input.view ?? "accessibility"
|
|
26
|
+
});
|
|
27
|
+
if ("refused" in look) return look.refused;
|
|
28
|
+
return look.images.length > 0 ? imageResult(look.images, look.meta) : look.meta;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
//#endregion
|
|
32
|
+
export { lookShape, lookTool };
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
/** A part of an operation (`an element`), closed: its fields as `shape` declares them, and no other. */
|
|
3
|
+
export declare const closedObject: <Shape extends z.ZodRawShape>(owner: string, shape: Shape, listed: string) => z.ZodObject<{ -readonly [P in keyof Shape]: Shape[P]; }, z.core.$strict>;
|
|
4
|
+
/** An operation's type, as its schema states it. */
|
|
5
|
+
export declare const typeOf: (operation: z.ZodObject) => string;
|
|
6
|
+
/** An operation, closed: its fields as declared, its description kept. */
|
|
7
|
+
export declare const closed: <Shape extends z.ZodRawShape>(operation: z.ZodObject<Shape>) => z.ZodObject<{ -readonly [P in keyof Shape]: Shape[P]; }, z.core.$strict>;
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { closest } from "@plitzi/sdk-authoring";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/modules/mcp/tools/operations/closed.ts
|
|
4
|
+
/**
|
|
5
|
+
* Objects of the write vocabulary that refuse a field they do not have. One left out of a schema used to be dropped as
|
|
6
|
+
* the batch was read — `prop` for `props` applied nothing and answered success, the one mistake an agent cannot see.
|
|
7
|
+
* Refused instead, with the field meant and where every field is listed.
|
|
8
|
+
*/
|
|
9
|
+
/** What a field `owner` does not have is answered with: the nearest one it does, and where every one is listed. */
|
|
10
|
+
var unknownFields = (owner, written, fields, listed) => {
|
|
11
|
+
return `${owner} has no field ${written.map((key) => {
|
|
12
|
+
const nearest = closest(key, fields);
|
|
13
|
+
return nearest ? `"${key}" (did you mean "${nearest}"?)` : `"${key}"`;
|
|
14
|
+
}).join(", ")}: ${listed} lists its fields`;
|
|
15
|
+
};
|
|
16
|
+
/** A part of an operation (`an element`), closed: its fields as `shape` declares them, and no other. */
|
|
17
|
+
var closedObject = (owner, shape, listed) => {
|
|
18
|
+
const fields = Object.keys(shape);
|
|
19
|
+
return z.strictObject(shape, { error: (issue) => issue.code === "unrecognized_keys" ? unknownFields(owner, issue.keys, fields, listed) : void 0 });
|
|
20
|
+
};
|
|
21
|
+
/** An operation's type, as its schema states it. */
|
|
22
|
+
var typeOf = (operation) => operation.shape.type instanceof z.ZodLiteral ? String(operation.shape.type.value) : "";
|
|
23
|
+
/** An operation, closed: its fields as declared, its description kept. */
|
|
24
|
+
var closed = (operation) => {
|
|
25
|
+
const type = typeOf(operation);
|
|
26
|
+
return closedObject(type, operation.shape, `plitzi_describe_operation { type: "${type}" }`).describe(operation.description ?? "");
|
|
27
|
+
};
|
|
28
|
+
//#endregion
|
|
29
|
+
export { closed, closedObject, typeOf };
|