@abinnovision/payloadcms-mcpx 1.0.0-beta.3 → 1.0.0-beta.4
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/README.md +12 -8
- package/dist/endpoint/result.mjs +9 -3
- package/dist/options.mjs +1 -1
- package/dist/schema/describe.mjs +8 -15
- package/dist/schema/pointer.mjs +7 -10
- package/dist/schema/shape.mjs +9 -9
- package/dist/schema/walk.mjs +18 -9
- package/dist/tools/create-document.mjs +1 -1
- package/dist/tools/describe-schema.mjs +3 -3
- package/dist/tools/get-document.mjs +2 -1
- package/dist/tools/list-capabilities.mjs +1 -1
- package/dist/tools/patch-document.mjs +1 -1
- package/dist/version.mjs +1 -1
- package/dist/write/patch.mjs +4 -5
- package/dist/write/publish-blockers.d.mts +1 -0
- package/dist/write/publish-blockers.mjs +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -134,19 +134,22 @@ reflects the key: write tools disappear for read-only keys, and every
|
|
|
134
134
|
|
|
135
135
|
Rules the tools enforce and explain in their own descriptions:
|
|
136
136
|
|
|
137
|
-
- `describeSchema` paths
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
that position.
|
|
137
|
+
- `describeSchema` paths stop at blocks fields, which list the block slugs they
|
|
138
|
+
accept; every node carries `next`, the ready-to-use paths for those blocks
|
|
139
|
+
(`/layout/sections/sectionWrapper`), so pass an entry of `next` as a `paths`
|
|
140
|
+
element to descend. A block is described as it exists at that position.
|
|
142
141
|
- Field and collection `admin.description` values are included in
|
|
143
142
|
`describeSchema` and `listCapabilities`, so intent written for the admin
|
|
144
143
|
panel reaches the client. Strings and locale-keyed records pass through;
|
|
145
144
|
functions and components are dropped.
|
|
146
145
|
- Builtin tools reject unknown arguments by name instead of silently ignoring
|
|
147
146
|
them.
|
|
148
|
-
-
|
|
149
|
-
|
|
147
|
+
- Every path this plugin accepts or reports is a JSON Pointer. A schema path
|
|
148
|
+
and a pointer into a document differ only in what stands in an element
|
|
149
|
+
position: a schema path writes `*` for an array element and names a block by
|
|
150
|
+
its slug, where a pointer carries a 0-based index. So `/items/*/title` is
|
|
151
|
+
written at `/items/0/title`, and `/layout/sections/hero` at
|
|
152
|
+
`/layout/sections/0`.
|
|
150
153
|
- Adding a block requires `blockType` on the value; append with `/-`.
|
|
151
154
|
- Clearing is `replace` with `null`; a list is emptied with `[]` and refuses
|
|
152
155
|
`null`. `remove` is only valid on list elements, because Payload keeps
|
|
@@ -175,7 +178,8 @@ validated; field `beforeChange` hooks run again during the check, so they must
|
|
|
175
178
|
be pure; and the check runs privileged, so blocker paths and messages may name
|
|
176
179
|
fields the key's user cannot read (values are never included).
|
|
177
180
|
Collections with `versions.drafts.validate: true` refuse invalid drafts
|
|
178
|
-
outright; those failures come back as `validationErrors`.
|
|
181
|
+
outright; those failures come back as `validationErrors`. Both carry pointers,
|
|
182
|
+
restated from the dotted paths Payload reports internally.
|
|
179
183
|
|
|
180
184
|
Writes also report `notApplied`: pointers whose value Payload kept unchanged,
|
|
181
185
|
which happens when field-level access denies the update.
|
package/dist/endpoint/result.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { pointerFromPayloadPath } from "../schema/walk.mjs";
|
|
1
2
|
import { APIError, ValidationError } from "payload";
|
|
2
3
|
//#region src/endpoint/result.ts
|
|
3
4
|
/**
|
|
@@ -41,12 +42,17 @@ import { APIError, ValidationError } from "payload";
|
|
|
41
42
|
* Maps an exception thrown by a tool to a result the client can read.
|
|
42
43
|
*
|
|
43
44
|
* Payload's public errors keep their message and status; a `ValidationError`
|
|
44
|
-
* also surfaces its per-field detail
|
|
45
|
-
*
|
|
45
|
+
* also surfaces its per-field detail, with each field's path restated as a
|
|
46
|
+
* JSON Pointer so it reads like every other path this plugin reports. Anything
|
|
47
|
+
* else is logged and reported as an internal error so no stack or driver
|
|
48
|
+
* message leaks to the client.
|
|
46
49
|
*/ const toToolError = (error, logger) => {
|
|
47
50
|
if (error instanceof ValidationError) return errorResult(error.message, {
|
|
48
51
|
status: error.status,
|
|
49
|
-
validationErrors: error.data.errors
|
|
52
|
+
validationErrors: error.data.errors.map((entry) => ({
|
|
53
|
+
...entry,
|
|
54
|
+
path: pointerFromPayloadPath(entry.path)
|
|
55
|
+
}))
|
|
50
56
|
});
|
|
51
57
|
if (error instanceof APIError && error.isPublic) return errorResult(error.message, { status: error.status });
|
|
52
58
|
logger.error({
|
package/dist/options.mjs
CHANGED
|
@@ -99,7 +99,7 @@ const normalizeLimits = (limits) => {
|
|
|
99
99
|
auth: options.auth,
|
|
100
100
|
serverInfo: {
|
|
101
101
|
name: options.serverInfo?.name ?? "payloadcms-mcpx",
|
|
102
|
-
version: options.serverInfo?.version ?? "
|
|
102
|
+
version: options.serverInfo?.version ?? "0.0.0"
|
|
103
103
|
}
|
|
104
104
|
};
|
|
105
105
|
};
|
package/dist/schema/describe.mjs
CHANGED
|
@@ -5,21 +5,22 @@ const blocksDescriptors = (fields) => describeFields(fields).filter((descriptor)
|
|
|
5
5
|
* Walks a schema path to the field list it addresses.
|
|
6
6
|
*
|
|
7
7
|
* A schema path alternates a blocks field's own path with the slug of one of
|
|
8
|
-
* the blocks it accepts, so
|
|
9
|
-
* reaches `hero` as it exists under `pages` specifically.
|
|
8
|
+
* the blocks it accepts, so `/layout/sections/sectionWrapper/modules/hero`
|
|
9
|
+
* reaches `hero` as it exists under `pages` specifically. The slug sits where
|
|
10
|
+
* a pointer into a document would carry the element's index.
|
|
10
11
|
*/ const fieldsAtSchemaPath = (config, collection, schemaPath) => {
|
|
11
12
|
let fields = collection.flattenedFields;
|
|
12
13
|
let blockType;
|
|
13
|
-
let remaining = splitPath(schemaPath)
|
|
14
|
+
let remaining = splitPath(schemaPath);
|
|
14
15
|
while (remaining.length > 0) {
|
|
15
16
|
/**
|
|
16
17
|
* A blocks field's own path may span several segments
|
|
17
|
-
* (
|
|
18
|
+
* (`/layout/sections`), so the longest matching one is taken.
|
|
18
19
|
*/ const match = blocksDescriptors(fields).map((descriptor) => splitPath(descriptor.path)).filter((parts) => parts.every((part, offset) => part === remaining[offset])).sort((left, right) => right.length - left.length)[0];
|
|
19
20
|
if (!match) throw new Error(`"${joinPath(remaining)}" does not address a blocks field. Blocks fields here: ${blocksDescriptors(fields).map((descriptor) => descriptor.path).join(", ") || "none"}`);
|
|
20
21
|
const slug = remaining.at(match.length);
|
|
21
22
|
const field = findBlocksField(fields, match);
|
|
22
|
-
if (!field) throw new Error(`"${match
|
|
23
|
+
if (!field) throw new Error(`"${joinPath(match)}" could not be resolved.`);
|
|
23
24
|
if (slug === void 0) throw new Error(`"${joinPath(match)}" is a blocks field; append one of: ${blockSlugsOf(field).join(", ")}`);
|
|
24
25
|
const block = blockOf(config, field, slug);
|
|
25
26
|
if (!block) throw new Error(`"${slug}" is not allowed at "${joinPath(match)}". Allowed: ${blockSlugsOf(field).join(", ")}`);
|
|
@@ -37,11 +38,7 @@ const blocksDescriptors = (fields) => describeFields(fields).filter((descriptor)
|
|
|
37
38
|
*/ const describeNode = (config, collection, schemaPath = "") => {
|
|
38
39
|
const { blockType, fields } = fieldsAtSchemaPath(config, collectionOf(config, collection), schemaPath);
|
|
39
40
|
const descriptors = describeFields(fields);
|
|
40
|
-
const next = descriptors.flatMap((descriptor) => (descriptor.blocks ?? []).map((slug) =>
|
|
41
|
-
schemaPath,
|
|
42
|
-
descriptor.path,
|
|
43
|
-
slug
|
|
44
|
-
].filter(Boolean).join(".")));
|
|
41
|
+
const next = descriptors.flatMap((descriptor) => (descriptor.blocks ?? []).map((slug) => `${schemaPath}${descriptor.path}/${slug}`));
|
|
45
42
|
return {
|
|
46
43
|
...blockType === void 0 ? {} : { blockType },
|
|
47
44
|
collection,
|
|
@@ -65,11 +62,7 @@ const blocksDescriptors = (fields) => describeFields(fields).filter((descriptor)
|
|
|
65
62
|
seen.push(schemaPath);
|
|
66
63
|
for (const descriptor of describeNode(config, collection, schemaPath).fields) for (const slug of descriptor.blocks ?? []) {
|
|
67
64
|
if (visited.includes(slug)) continue;
|
|
68
|
-
walk([
|
|
69
|
-
schemaPath,
|
|
70
|
-
descriptor.path,
|
|
71
|
-
slug
|
|
72
|
-
].filter(Boolean).join("."), [...visited, slug]);
|
|
65
|
+
walk(`${schemaPath}${descriptor.path}/${slug}`, [...visited, slug]);
|
|
73
66
|
}
|
|
74
67
|
};
|
|
75
68
|
walk("", []);
|
package/dist/schema/pointer.mjs
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { blockOf, blockSlugsOf, collectionOf, describeFields, findBlocksField, joinPath, splitPath } from "./walk.mjs";
|
|
2
2
|
//#region src/schema/pointer.ts
|
|
3
3
|
const isIndexSegment = (segment) => segment === "-" || /^\d+$/.test(segment);
|
|
4
|
-
|
|
5
|
-
* Decodes a JSON Pointer into its segments, unescaping `~1` and `~0`.
|
|
6
|
-
*/ const pointerSegments = (pointer) => pointer.split("/").slice(1).map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
7
|
-
const partMatches = (part, segment) => segment !== void 0 && (part === "[]" ? isIndexSegment(segment) : part === segment);
|
|
4
|
+
const partMatches = (part, segment) => segment !== void 0 && (part === "*" ? isIndexSegment(segment) : part === segment);
|
|
8
5
|
/**
|
|
9
6
|
* Longest descriptor whose path is fully consumed by the leading segments.
|
|
10
7
|
*/ const longestMatch = (descriptors, segments) => descriptors.map((descriptor) => ({
|
|
@@ -38,7 +35,7 @@ const partMatches = (part, segment) => segment !== void 0 && (part === "[]" ? is
|
|
|
38
35
|
let fields = collectionOf(config, target.collection).flattenedFields;
|
|
39
36
|
let data = target.doc;
|
|
40
37
|
let blockType;
|
|
41
|
-
let segments =
|
|
38
|
+
let segments = splitPath(target.pointer);
|
|
42
39
|
while (segments.length > 0) {
|
|
43
40
|
const descriptors = describeFields(fields);
|
|
44
41
|
const match = longestMatch(descriptors, segments);
|
|
@@ -46,7 +43,7 @@ const partMatches = (part, segment) => segment !== void 0 && (part === "[]" ? is
|
|
|
46
43
|
if (isSubtreePrefix(descriptors, segments)) return {
|
|
47
44
|
...blockType === void 0 ? {} : { blockType },
|
|
48
45
|
fields,
|
|
49
|
-
prefix:
|
|
46
|
+
prefix: segments
|
|
50
47
|
};
|
|
51
48
|
throw new Error(`"${joinPath(segments)}" is not a field here. Available: ${descriptors.map((descriptor) => descriptor.path).join(", ")}`);
|
|
52
49
|
}
|
|
@@ -55,9 +52,9 @@ const partMatches = (part, segment) => segment !== void 0 && (part === "[]" ? is
|
|
|
55
52
|
...blockType === void 0 ? {} : { blockType },
|
|
56
53
|
descriptor: match.descriptor,
|
|
57
54
|
fields,
|
|
58
|
-
prefix:
|
|
55
|
+
prefix: []
|
|
59
56
|
};
|
|
60
|
-
if (match.descriptor.type !== "blocks") throw new Error(`"${match.descriptor.path}" is a ${match.descriptor.type} field and has no "${rest
|
|
57
|
+
if (match.descriptor.type !== "blocks") throw new Error(`"${match.descriptor.path}" is a ${match.descriptor.type} field and has no "${joinPath(rest)}" beneath it.`);
|
|
61
58
|
const [index, ...remaining] = rest;
|
|
62
59
|
if (!isIndexSegment(index)) throw new Error(`"${match.descriptor.path}" is an array; "${index}" is not an index.`);
|
|
63
60
|
const parts = splitPath(match.descriptor.path);
|
|
@@ -76,8 +73,8 @@ const partMatches = (part, segment) => segment !== void 0 && (part === "[]" ? is
|
|
|
76
73
|
return {
|
|
77
74
|
...blockType === void 0 ? {} : { blockType },
|
|
78
75
|
fields,
|
|
79
|
-
prefix:
|
|
76
|
+
prefix: []
|
|
80
77
|
};
|
|
81
78
|
};
|
|
82
79
|
//#endregion
|
|
83
|
-
export {
|
|
80
|
+
export { resolveDataPointer };
|
package/dist/schema/shape.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { blockOf, blockSlugsOf, describeFields, findBlocksField,
|
|
1
|
+
import { blockOf, blockSlugsOf, describeFields, findBlocksField, splitPath } from "./walk.mjs";
|
|
2
2
|
//#region src/schema/shape.ts
|
|
3
3
|
/**
|
|
4
4
|
* Keys Payload manages on a row that a client may echo back harmlessly.
|
|
@@ -36,7 +36,7 @@ const isPlainObject = (value) => typeof value === "object" && value !== null &&
|
|
|
36
36
|
};
|
|
37
37
|
const checkLeafValue = (scope, descriptor, value) => {
|
|
38
38
|
if (descriptor.readOnly) {
|
|
39
|
-
scope.problems.push(`${scope.pointer}:
|
|
39
|
+
scope.problems.push(`${scope.pointer}: this field is read-only and cannot be written.`);
|
|
40
40
|
return;
|
|
41
41
|
}
|
|
42
42
|
if (descriptor.type === "richText") {
|
|
@@ -61,7 +61,7 @@ const checkLeafValue = (scope, descriptor, value) => {
|
|
|
61
61
|
...scope,
|
|
62
62
|
fields: block.flattenedFields,
|
|
63
63
|
pointer: `${scope.pointer}/${String(index)}`,
|
|
64
|
-
prefix:
|
|
64
|
+
prefix: []
|
|
65
65
|
}, row);
|
|
66
66
|
});
|
|
67
67
|
};
|
|
@@ -76,7 +76,7 @@ const checkLeafValue = (scope, descriptor, value) => {
|
|
|
76
76
|
* block would be stripped in silence.
|
|
77
77
|
*/ const checkValue = (scope, value) => {
|
|
78
78
|
if (!isPlainObject(value)) return;
|
|
79
|
-
const prefixParts = scope.prefix
|
|
79
|
+
const prefixParts = scope.prefix;
|
|
80
80
|
const relative = describeFields(scope.fields).flatMap((descriptor) => {
|
|
81
81
|
const parts = splitPath(descriptor.path);
|
|
82
82
|
return prefixParts.every((part, offset) => part === parts[offset]) ? [{
|
|
@@ -100,7 +100,7 @@ const checkLeafValue = (scope, descriptor, value) => {
|
|
|
100
100
|
}, exact.descriptor, entry);
|
|
101
101
|
continue;
|
|
102
102
|
}
|
|
103
|
-
if (candidates.some(({ parts }) => parts[1] === "
|
|
103
|
+
if (candidates.some(({ parts }) => parts[1] === "*")) {
|
|
104
104
|
if (!Array.isArray(entry)) {
|
|
105
105
|
scope.problems.push(`${pointer}: expected an array.`);
|
|
106
106
|
continue;
|
|
@@ -109,11 +109,11 @@ const checkLeafValue = (scope, descriptor, value) => {
|
|
|
109
109
|
checkValue({
|
|
110
110
|
...scope,
|
|
111
111
|
pointer: `${pointer}/${String(index)}`,
|
|
112
|
-
prefix:
|
|
112
|
+
prefix: [
|
|
113
113
|
...prefixParts,
|
|
114
114
|
key,
|
|
115
|
-
"
|
|
116
|
-
]
|
|
115
|
+
"*"
|
|
116
|
+
]
|
|
117
117
|
}, row);
|
|
118
118
|
});
|
|
119
119
|
continue;
|
|
@@ -121,7 +121,7 @@ const checkLeafValue = (scope, descriptor, value) => {
|
|
|
121
121
|
checkValue({
|
|
122
122
|
...scope,
|
|
123
123
|
pointer,
|
|
124
|
-
prefix:
|
|
124
|
+
prefix: [...prefixParts, key]
|
|
125
125
|
}, entry);
|
|
126
126
|
}
|
|
127
127
|
};
|
package/dist/schema/walk.mjs
CHANGED
|
@@ -11,13 +11,22 @@ import { fieldIsHiddenOrDisabled, fieldIsVirtual } from "payload/shared";
|
|
|
11
11
|
"updatedAt"
|
|
12
12
|
]);
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
15
|
-
|
|
16
|
-
*/ const joinPath = (parts) => parts.reduce((path, part) => part === "[]" ? `${path}[]` : path ? `${path}.${part}` : part, "");
|
|
14
|
+
* Shape a JSON Pointer must have to be parseable at all.
|
|
15
|
+
*/ const JSON_POINTER_PATTERN = /^(\/([^~/]|~[01])*)*$/;
|
|
17
16
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
|
|
17
|
+
* Joins segments into a JSON Pointer, so the segments `items`, `*`, `title`
|
|
18
|
+
* read as one path to a subfield of every element of `items`. No segments is
|
|
19
|
+
* the root pointer, `""`.
|
|
20
|
+
*/ const joinPath = (parts) => parts.map((part) => `/${part.replace(/~/g, "~0").replace(/\//g, "~1")}`).join("");
|
|
21
|
+
/**
|
|
22
|
+
* Splits a JSON Pointer into its segments, unescaping `~1` and `~0`. The root
|
|
23
|
+
* pointer yields no segments.
|
|
24
|
+
*/ const splitPath = (path) => path.split("/").slice(1).map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
25
|
+
/**
|
|
26
|
+
* Restates a path Payload reports on a validation error (`layout.0.title`) as
|
|
27
|
+
* a JSON Pointer, so everything this plugin hands back addresses documents the
|
|
28
|
+
* same way. Payload's path already carries real indices, so it maps directly.
|
|
29
|
+
*/ const pointerFromPayloadPath = (path) => path ? joinPath(path.split(".")) : "";
|
|
21
30
|
/**
|
|
22
31
|
* Blocks a blocks field accepts, by slug. On a flattened field, whichever of
|
|
23
32
|
* `blockReferences` and `blocks` was declared carries the definitions.
|
|
@@ -83,7 +92,7 @@ const withRows = (descriptor, field) => ({
|
|
|
83
92
|
const readOnly = parentReadOnly || isReadOnly(field);
|
|
84
93
|
const path = [...prefix, field.name];
|
|
85
94
|
if (field.type === "tab" || field.type === "group") return describeFields(field.flattenedFields, path, readOnly);
|
|
86
|
-
if (field.type === "array") return describeFields(field.flattenedFields, [...path, "
|
|
95
|
+
if (field.type === "array") return describeFields(field.flattenedFields, [...path, "*"], readOnly);
|
|
87
96
|
if (field.type === "blocks") return [withRows({
|
|
88
97
|
...describeBase(field, joinPath(path), readOnly),
|
|
89
98
|
blocks: blockSlugsOf(field)
|
|
@@ -97,7 +106,7 @@ const withRows = (descriptor, field) => ({
|
|
|
97
106
|
if (!("name" in field) || field.name !== path[0]) continue;
|
|
98
107
|
if (field.type === "blocks" && path.length === 1) return field;
|
|
99
108
|
if (field.type === "tab" || field.type === "group") return findBlocksField(field.flattenedFields, path.slice(1));
|
|
100
|
-
if (field.type === "array" && path[1] === "
|
|
109
|
+
if (field.type === "array" && path[1] === "*") return findBlocksField(field.flattenedFields, path.slice(2));
|
|
101
110
|
}
|
|
102
111
|
};
|
|
103
112
|
const collectionOf = (config, collection) => {
|
|
@@ -106,4 +115,4 @@ const collectionOf = (config, collection) => {
|
|
|
106
115
|
return found;
|
|
107
116
|
};
|
|
108
117
|
//#endregion
|
|
109
|
-
export { RESERVED_FIELD_NAMES, blockOf, blockSlugsOf, collectionOf, describeFields, findBlocksField, joinPath, splitPath, staticDescription };
|
|
118
|
+
export { JSON_POINTER_PATTERN, RESERVED_FIELD_NAMES, blockOf, blockSlugsOf, collectionOf, describeFields, findBlocksField, joinPath, pointerFromPayloadPath, splitPath, staticDescription };
|
|
@@ -7,9 +7,9 @@ const describeSchema = {
|
|
|
7
7
|
name: "describeSchema",
|
|
8
8
|
description: `Describes the writable shape of a document, one node at a time.
|
|
9
9
|
|
|
10
|
-
Call it with no "paths" to get a collection's own fields. Every "blocks" field stops there and lists the block slugs it accepts instead of nesting them; each node's "next" lists the ready-to-use paths for those blocks, so pass any entry of "next" as a "paths" element to descend, e.g. "layout
|
|
10
|
+
Call it with no "paths" to get a collection's own fields. Every "blocks" field stops there and lists the block slugs it accepts instead of nesting them; each node's "next" lists the ready-to-use paths for those blocks, so pass any entry of "next" as a "paths" element to descend, e.g. "/layout/sections/sectionWrapper" and then "/layout/sections/sectionWrapper/modules/hero". A block is described as it exists at that position, because the same block can accept different children elsewhere.
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Paths here use the same JSON Pointer syntax as getDocument and patchDocument, and are already resolved through anything that does not nest in the stored document. The difference is only what stands in an element position: a path names an array element "*" and a block by its slug, where a pointer into a document carries a 0-based index. So "/items/*/title" is written at "/items/0/title", and "/layout/sections/hero" at "/layout/sections/0".
|
|
13
13
|
|
|
14
14
|
Fields Payload maintains (id, _status, createdAt, updatedAt) are never listed and cannot be written. Fields marked readOnly are listed but refused on write.`,
|
|
15
15
|
annotations: {
|
|
@@ -19,7 +19,7 @@ Fields Payload maintains (id, _status, createdAt, updatedAt) are never listed an
|
|
|
19
19
|
isEnabled: (scope) => scope.readable.length > 0,
|
|
20
20
|
inputSchema: (scope) => ({
|
|
21
21
|
collection: collectionEnum(scope.readable).describe("Collection to describe."),
|
|
22
|
-
paths: z.array(z.string()).optional().describe("Schema paths to describe, e.g. \"layout
|
|
22
|
+
paths: z.array(z.string()).optional().describe("Schema paths to describe, e.g. \"/layout/sections/sectionWrapper\". Omit for the collection root."),
|
|
23
23
|
expand: z.boolean().optional().describe("Return every node reachable from the root in one response. Ignores paths.")
|
|
24
24
|
}),
|
|
25
25
|
handler: (args, scope) => {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { JSON_POINTER_PATTERN } from "../schema/walk.mjs";
|
|
1
2
|
import { errorResult, jsonResult } from "../endpoint/result.mjs";
|
|
2
3
|
import { collectionEnum, depthShape, ensureAllowed, idSchema, localeOf, localeShape } from "./shared.mjs";
|
|
3
4
|
import { z } from "zod";
|
|
@@ -14,7 +15,7 @@ const getDocument = {
|
|
|
14
15
|
inputSchema: (scope) => ({
|
|
15
16
|
collection: collectionEnum(scope.readable).describe("Collection holding the document."),
|
|
16
17
|
id: idSchema,
|
|
17
|
-
path: z.string().optional().describe("JSON pointer to return only a subtree, e.g. \"/layout/sections/0\"."),
|
|
18
|
+
path: z.string().regex(JSON_POINTER_PATTERN).optional().describe("JSON pointer to return only a subtree, e.g. \"/layout/sections/0\"."),
|
|
18
19
|
...depthShape(scope),
|
|
19
20
|
...localeShape(scope, {
|
|
20
21
|
required: false,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
+
import { staticDescription } from "../schema/walk.mjs";
|
|
1
2
|
import { jsonResult } from "../endpoint/result.mjs";
|
|
2
3
|
import { translateLabel } from "./shared.mjs";
|
|
3
|
-
import { staticDescription } from "../schema/walk.mjs";
|
|
4
4
|
import { hasDraftValidationEnabled } from "payload/shared";
|
|
5
5
|
//#region src/tools/list-capabilities.ts
|
|
6
6
|
const listCapabilities = {
|
|
@@ -10,7 +10,7 @@ const DESCRIPTION = `Applies RFC 6902 JSON Patch operations to one document.
|
|
|
10
10
|
|
|
11
11
|
The write always lands as a draft and is never published, whatever it contains; publishing stays a human action in the admin panel.
|
|
12
12
|
|
|
13
|
-
Only the fields describeSchema lists can be addressed. A pointer that does not resolve is refused with the fields that are valid at that point, and nothing is applied unless every operation in the batch validates first.
|
|
13
|
+
Only the fields describeSchema lists can be addressed. A pointer that does not resolve is refused with the fields that are valid at that point, and nothing is applied unless every operation in the batch validates first. describeSchema reports field paths in this same pointer syntax; a path becomes a pointer into a document by replacing each "*" and each block slug with its 0-based index.
|
|
14
14
|
|
|
15
15
|
Adding a block requires "blockType" on the value. Append with "/-" as the last segment. To clear a field use "replace" with null; an array or blocks field refuses null and is emptied with [] instead. "remove" is only for list elements, because a field left out of a write is kept rather than cleared. Read the document first to learn the indices, and pass its "updatedAt" as expectedUpdatedAt so a concurrent edit is refused rather than overwritten.
|
|
16
16
|
|
package/dist/version.mjs
CHANGED
package/dist/write/patch.mjs
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { RESERVED_FIELD_NAMES, blockOf, describeFields, findBlocksField, splitPath } from "../schema/walk.mjs";
|
|
1
|
+
import { JSON_POINTER_PATTERN, RESERVED_FIELD_NAMES, blockOf, describeFields, findBlocksField, splitPath } from "../schema/walk.mjs";
|
|
2
2
|
import { validateWriteValue } from "../schema/shape.mjs";
|
|
3
3
|
import { resolveDataPointer } from "../schema/pointer.mjs";
|
|
4
4
|
import { z } from "zod";
|
|
@@ -6,8 +6,7 @@ import { Pointer, applyPatch } from "rfc6902";
|
|
|
6
6
|
//#region src/write/patch.ts
|
|
7
7
|
/**
|
|
8
8
|
* One RFC 6902 operation as accepted by `patchDocument`.
|
|
9
|
-
*/ const
|
|
10
|
-
const PATCH_OPERATION_SCHEMA = z.object({
|
|
9
|
+
*/ const PATCH_OPERATION_SCHEMA = z.object({
|
|
11
10
|
from: z.string().regex(JSON_POINTER_PATTERN).optional(),
|
|
12
11
|
op: z.enum([
|
|
13
12
|
"add",
|
|
@@ -186,13 +185,13 @@ const isPlainObject = (value) => typeof value === "object" && value !== null &&
|
|
|
186
185
|
result[key] = entry;
|
|
187
186
|
continue;
|
|
188
187
|
}
|
|
189
|
-
if (candidates.some(({ parts }) => parts[1] === "
|
|
188
|
+
if (candidates.some(({ parts }) => parts[1] === "*")) {
|
|
190
189
|
result[key] = Array.isArray(entry) ? entry.map((row) => isPlainObject(row) ? pickDescribed(config, row, {
|
|
191
190
|
fields,
|
|
192
191
|
prefix: [
|
|
193
192
|
...prefix,
|
|
194
193
|
key,
|
|
195
|
-
"
|
|
194
|
+
"*"
|
|
196
195
|
],
|
|
197
196
|
isRow: true
|
|
198
197
|
}) : row) : entry;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { pointerFromPayloadPath } from "../schema/walk.mjs";
|
|
1
2
|
import { beforeChangeTraverseFields, beforeValidateTraverseFields } from "payload";
|
|
2
3
|
//#region src/write/publish-blockers.ts
|
|
3
4
|
/**
|
|
@@ -64,7 +65,7 @@ import { beforeChangeTraverseFields, beforeValidateTraverseFields } from "payloa
|
|
|
64
65
|
}
|
|
65
66
|
return errors.map((error) => ({
|
|
66
67
|
message: error.message,
|
|
67
|
-
path: error.path,
|
|
68
|
+
path: pointerFromPayloadPath(error.path),
|
|
68
69
|
...typeof error.label === "string" ? { field: error.label } : {}
|
|
69
70
|
}));
|
|
70
71
|
};
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@abinnovision/payloadcms-mcpx",
|
|
4
|
-
"version": "1.0.0-beta.
|
|
4
|
+
"version": "1.0.0-beta.4",
|
|
5
5
|
"description": "Payload CMS plugin exposing a fixed, schema-aware MCP tool surface with draft-only writes and per-API-key capabilities.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"payload",
|