@abinnovision/payloadcms-mcpx 2.0.0-beta.18 → 2.0.0-beta.20
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 +2 -2
- package/dist/admin/capability-matrix.d.mts +2 -1
- package/dist/admin/capability-matrix.mjs +291 -340
- package/dist/admin/capability-toggles.mjs +24 -31
- package/dist/admin/confirmations-panel.mjs +61 -71
- package/dist/admin/confirmations.mjs +2 -1
- package/dist/admin/endpoint-url.mjs +7 -0
- package/dist/admin/setup-guide.mjs +2 -1
- package/dist/api-keys/capability-matrix.d.mts +4 -7
- package/dist/api-keys/capability-matrix.mjs +12 -51
- package/dist/api-keys/collection.mjs +10 -17
- package/dist/api-keys/confirmation-view.mjs +2 -1
- package/dist/api-keys/fields.mjs +53 -69
- package/dist/auth/resolve.mjs +29 -10
- package/dist/capabilities.d.mts +2 -0
- package/dist/capabilities.mjs +62 -19
- package/dist/endpoint/confirmations.mjs +14 -14
- package/dist/endpoint/handler.mjs +6 -22
- package/dist/endpoint/http.mjs +27 -0
- package/dist/endpoint/upload.mjs +11 -43
- package/dist/grants/confirmation.mjs +11 -42
- package/dist/grants/grant.mjs +58 -40
- package/dist/guards.mjs +8 -1
- package/dist/index.d.mts +2 -2
- package/dist/index.mjs +1 -1
- package/dist/options.mjs +34 -54
- package/dist/plugin.mjs +2 -1
- package/dist/request.mjs +5 -1
- package/dist/result.mjs +11 -1
- package/dist/schema/admin-hidden.mjs +4 -4
- package/dist/schema/describe.mjs +12 -18
- package/dist/schema/index.mjs +5 -6
- package/dist/schema/lexical-pointer.mjs +8 -8
- package/dist/schema/lexical.mjs +15 -13
- package/dist/schema/outline.mjs +4 -5
- package/dist/schema/path.mjs +4 -1
- package/dist/schema/pointer.mjs +11 -17
- package/dist/schema/shape.mjs +38 -50
- package/dist/schema/walk.mjs +57 -18
- package/dist/tools/create-document.mjs +16 -44
- package/dist/tools/delete-document.mjs +14 -17
- package/dist/tools/describe-schema.mjs +7 -5
- package/dist/tools/document.mjs +130 -44
- package/dist/tools/duplicate-document.mjs +11 -10
- package/dist/tools/find-documents.mjs +15 -20
- package/dist/tools/find-versions.mjs +10 -14
- package/dist/tools/get-document.mjs +8 -12
- package/dist/tools/links.mjs +2 -1
- package/dist/tools/list-capabilities.mjs +2 -1
- package/dist/tools/patch-document.mjs +12 -44
- package/dist/tools/publish-document.mjs +9 -27
- package/dist/tools/query-paths.mjs +1 -1
- package/dist/tools/run-confirmed.mjs +38 -50
- package/dist/tools/shared.mjs +43 -41
- package/dist/tools/validate-document.mjs +4 -6
- package/dist/tools/versions.mjs +11 -6
- package/dist/types.d.mts +12 -26
- package/dist/upload/file.mjs +10 -18
- package/dist/write/draft-guard.mjs +20 -20
- package/dist/write/patch.mjs +4 -4
- package/dist/write/publish-intent.mjs +22 -28
- package/dist/write/row-ids.mjs +3 -1
- package/dist/write/write-data.mjs +18 -31
- package/package.json +5 -5
- package/dist/builtin-tool-names.mjs +0 -19
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { defineMcpxTool } from "../define-tool.mjs";
|
|
2
2
|
import { jsonResult } from "../result.mjs";
|
|
3
|
+
import { SchemaError } from "../schema/errors.mjs";
|
|
3
4
|
import { nodePropertiesFor } from "../schema/lexical.mjs";
|
|
4
5
|
import { translatorFor } from "../i18n.mjs";
|
|
5
|
-
import { SchemaError } from "../schema/errors.mjs";
|
|
6
6
|
import { nodeDescriber, reachableSchemaPaths } from "../schema/describe.mjs";
|
|
7
7
|
import "../schema/index.mjs";
|
|
8
|
-
import { entityShape } from "./shared.mjs";
|
|
8
|
+
import { entityShape, reaches } from "./shared.mjs";
|
|
9
9
|
import { resolveEntity } from "./document.mjs";
|
|
10
10
|
import { z } from "zod";
|
|
11
11
|
//#region src/tools/describe-schema.ts
|
|
@@ -30,7 +30,7 @@ const PATHS_LIMIT = 400;
|
|
|
30
30
|
readOnlyHint: true,
|
|
31
31
|
openWorldHint: false
|
|
32
32
|
},
|
|
33
|
-
isEnabled: (scope) => scope
|
|
33
|
+
isEnabled: (scope) => reaches(scope, "read"),
|
|
34
34
|
inputSchema: (scope) => ({
|
|
35
35
|
...entityShape(scope, "read", "Instead of \"collection\"."),
|
|
36
36
|
paths: z.array(z.string()).max(PATHS_LIMIT).optional().describe("Schema paths from \"next\". Omit for the top-level node."),
|
|
@@ -43,9 +43,12 @@ const PATHS_LIMIT = 400;
|
|
|
43
43
|
const expanded = args.expand === true ? reachableSchemaPaths(config, ref) : void 0;
|
|
44
44
|
const requested = expanded?.paths ?? (args.paths && args.paths.length > 0 ? args.paths : [""]);
|
|
45
45
|
const failures = [];
|
|
46
|
+
const nodeTypes = [];
|
|
46
47
|
const nodes = requested.map((schemaPath) => {
|
|
47
48
|
try {
|
|
48
|
-
|
|
49
|
+
const node = describeNode(config, ref, schemaPath);
|
|
50
|
+
nodeTypes.push(...node.fields.flatMap((field) => field.nodes ?? []));
|
|
51
|
+
return node;
|
|
49
52
|
} catch (error) {
|
|
50
53
|
if (error instanceof SchemaError) return {
|
|
51
54
|
error: error.message,
|
|
@@ -62,7 +65,6 @@ const PATHS_LIMIT = 400;
|
|
|
62
65
|
err: failures[0],
|
|
63
66
|
msg: `[payloadcms-mcpx] Describing ${String(failures.length)} schema paths failed.`
|
|
64
67
|
});
|
|
65
|
-
const nodeTypes = nodes.flatMap((node) => (node.fields ?? []).flatMap((field) => field.nodes ?? []));
|
|
66
68
|
if (nodeTypes.length > 0) nodes.push({ nodeProperties: nodePropertiesFor(nodeTypes) });
|
|
67
69
|
if (expanded?.truncated) nodes.push({ error: `Result truncated after ${String(400)} nodes. Request explicit paths instead.` });
|
|
68
70
|
return jsonResult(nodes);
|
package/dist/tools/document.mjs
CHANGED
|
@@ -1,34 +1,44 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { definedProps } from "../guards.mjs";
|
|
2
|
+
import { errorResult, jsonResult } from "../result.mjs";
|
|
3
|
+
import { documentLinks } from "./links.mjs";
|
|
2
4
|
import { collectPublishBlockers } from "../write/publish-blockers.mjs";
|
|
3
5
|
import { slugsFor } from "./shared.mjs";
|
|
4
6
|
import { APIError, Forbidden, NotFound } from "payload";
|
|
5
7
|
//#region src/tools/document.ts
|
|
6
8
|
/**
|
|
9
|
+
* `payload.globals` is `{ config: SanitizedGlobalConfig[] }`, an array, not the
|
|
10
|
+
* slug-keyed map `payload.collections` is.
|
|
11
|
+
*/ const globalConfig = (payload, slug) => payload.globals.config.find((candidate) => candidate.slug === slug);
|
|
12
|
+
/**
|
|
13
|
+
* For a tool that takes a collection and no global, so the result needs no
|
|
14
|
+
* check for one.
|
|
15
|
+
*/ const resolveCollection = (scope, slug, operation) => {
|
|
16
|
+
const found = scope.req.payload.collections[slug]?.config;
|
|
17
|
+
if (!slugsFor(scope, operation).collections.includes(slug) || !found) throw new Forbidden(scope.req.t);
|
|
18
|
+
return {
|
|
19
|
+
kind: "collection",
|
|
20
|
+
slug,
|
|
21
|
+
config: found
|
|
22
|
+
};
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
7
25
|
* A raw input shape leaves no top-level `.refine` to express "exactly one of
|
|
8
26
|
* collection and global", so the rule is enforced here, with a message naming
|
|
9
27
|
* the offending arguments.
|
|
10
28
|
*/ const resolveEntity = (scope, args, operation) => {
|
|
11
29
|
const { collection, global } = args;
|
|
12
|
-
const allowedSlugs = slugsFor(scope, operation);
|
|
13
30
|
if (collection !== void 0 && global !== void 0) throw new APIError("Pass either \"collection\" or \"global\", not both.", 400);
|
|
14
|
-
if (collection
|
|
15
|
-
if (
|
|
16
|
-
const found = scope.req.payload
|
|
17
|
-
if (!
|
|
31
|
+
if (collection !== void 0) return resolveCollection(scope, collection, operation);
|
|
32
|
+
if (global !== void 0) {
|
|
33
|
+
const found = globalConfig(scope.req.payload, global);
|
|
34
|
+
if (!slugsFor(scope, operation).globals.includes(global) || !found) throw new Forbidden(scope.req.t);
|
|
18
35
|
return {
|
|
19
|
-
kind: "
|
|
20
|
-
slug:
|
|
21
|
-
config: found
|
|
36
|
+
kind: "global",
|
|
37
|
+
slug: global,
|
|
38
|
+
config: found
|
|
22
39
|
};
|
|
23
40
|
}
|
|
24
|
-
|
|
25
|
-
const found = scope.req.payload.globals.config.find((candidate) => candidate.slug === slug);
|
|
26
|
-
if (!allowedSlugs.globals.includes(slug) || !found) throw new Forbidden(scope.req.t);
|
|
27
|
-
return {
|
|
28
|
-
kind: "global",
|
|
29
|
-
slug,
|
|
30
|
-
config: found
|
|
31
|
-
};
|
|
41
|
+
throw new APIError("One of \"collection\" or \"global\" is required. Call listCapabilities to see which slugs are available.", 400);
|
|
32
42
|
};
|
|
33
43
|
/**
|
|
34
44
|
* Resolves the entity and checks `id` against it. A collection document needs
|
|
@@ -48,7 +58,45 @@ import { APIError, Forbidden, NotFound } from "payload";
|
|
|
48
58
|
};
|
|
49
59
|
/**
|
|
50
60
|
* What a response names its subject by: a document's id, or a global's slug.
|
|
51
|
-
*/ const identityOf = (target
|
|
61
|
+
*/ const identityOf = (target) => target.kind === "collection" ? { id: target.id } : { global: target.slug };
|
|
62
|
+
/**
|
|
63
|
+
* The fields every write result starts with, read from the saved `doc`.
|
|
64
|
+
*/ const documentSummary = (target, doc) => ({
|
|
65
|
+
...target.kind === "collection" ? { id: doc["id"] } : { global: target.slug },
|
|
66
|
+
status: doc["_status"],
|
|
67
|
+
updatedAt: doc["updatedAt"]
|
|
68
|
+
});
|
|
69
|
+
/**
|
|
70
|
+
* Names the blockers of a check, and whether the check itself failed.
|
|
71
|
+
*/ const blockerFields = (result, key) => ({
|
|
72
|
+
...result.blockers.length > 0 ? { [key]: result.blockers } : {},
|
|
73
|
+
...result.unavailable ? { [`${key}Unavailable`]: true } : {}
|
|
74
|
+
});
|
|
75
|
+
/**
|
|
76
|
+
* What a tool that wrote a document returns: the document, re-read privileged
|
|
77
|
+
* in `locale`, with its links and publish blockers. `extra` follows them.
|
|
78
|
+
*/ const writtenResult = async (scope, args) => {
|
|
79
|
+
const { target, locale } = args;
|
|
80
|
+
const saved = await readDraft(scope, {
|
|
81
|
+
target,
|
|
82
|
+
locale,
|
|
83
|
+
privileged: true
|
|
84
|
+
});
|
|
85
|
+
const validation = await collectPublishBlockers(scope.req, {
|
|
86
|
+
doc: saved,
|
|
87
|
+
entity: target
|
|
88
|
+
});
|
|
89
|
+
return jsonResult({
|
|
90
|
+
...documentSummary(target, saved),
|
|
91
|
+
...await documentLinks(scope.req, {
|
|
92
|
+
target,
|
|
93
|
+
doc: saved,
|
|
94
|
+
locale
|
|
95
|
+
}),
|
|
96
|
+
...blockerFields(validation, "publishBlockers"),
|
|
97
|
+
...args.extra
|
|
98
|
+
});
|
|
99
|
+
};
|
|
52
100
|
const sameInstant = (left, right) => (typeof left === "string" || left instanceof Date) && new Date(left).getTime() === new Date(right).getTime();
|
|
53
101
|
/**
|
|
54
102
|
* Refuses a write when the document changed since the client read it.
|
|
@@ -75,7 +123,8 @@ const sameInstant = (left, right) => (typeof left === "string" || left instanceo
|
|
|
75
123
|
...shared,
|
|
76
124
|
collection: args.target.slug,
|
|
77
125
|
id: args.target.id,
|
|
78
|
-
disableErrors: true
|
|
126
|
+
disableErrors: true,
|
|
127
|
+
...args.trash ? { trash: true } : {}
|
|
79
128
|
});
|
|
80
129
|
if (!doc) throw new NotFound(scope.req.t);
|
|
81
130
|
return doc;
|
|
@@ -86,39 +135,76 @@ const sameInstant = (left, right) => (typeof left === "string" || left instanceo
|
|
|
86
135
|
});
|
|
87
136
|
};
|
|
88
137
|
/**
|
|
138
|
+
* Updates a collection document or a global with no fallback locale, so a value
|
|
139
|
+
* missing in the written locale is not backfilled from another and persisted.
|
|
140
|
+
*/ const updateTarget = async (scope, target, write) => {
|
|
141
|
+
const { payload } = scope.req;
|
|
142
|
+
const { data, draft, file, ...optional } = write;
|
|
143
|
+
const shared = {
|
|
144
|
+
data,
|
|
145
|
+
depth: 0,
|
|
146
|
+
draft,
|
|
147
|
+
fallbackLocale: false,
|
|
148
|
+
overrideAccess: false,
|
|
149
|
+
req: scope.req,
|
|
150
|
+
...definedProps(optional)
|
|
151
|
+
};
|
|
152
|
+
if (target.kind === "collection") await payload.update({
|
|
153
|
+
...shared,
|
|
154
|
+
...definedProps({ file }),
|
|
155
|
+
collection: target.slug,
|
|
156
|
+
id: target.id
|
|
157
|
+
});
|
|
158
|
+
else await payload.updateGlobal({
|
|
159
|
+
...shared,
|
|
160
|
+
slug: target.slug
|
|
161
|
+
});
|
|
162
|
+
};
|
|
163
|
+
/**
|
|
164
|
+
* Runs `run`, then puts back the request's locale and fallback locale, which
|
|
165
|
+
* Payload's local API assigns in place.
|
|
166
|
+
*/ const withRequestLocale = async (req, run) => {
|
|
167
|
+
const { fallbackLocale, locale } = req;
|
|
168
|
+
try {
|
|
169
|
+
return await run();
|
|
170
|
+
} finally {
|
|
171
|
+
if (locale !== void 0) req.locale = locale;
|
|
172
|
+
if (fallbackLocale !== void 0) req.fallbackLocale = fallbackLocale;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
89
176
|
* {@link collectPublishBlockers} over the draft of each locale, each blocker
|
|
90
177
|
* tagged with its locale. Locales run in turn because hooks share `req`, and
|
|
91
178
|
* the request's locale and fallback locale are restored afterwards.
|
|
92
179
|
*/ const collectLocaleBlockers = async (scope, target, locales) => {
|
|
93
180
|
const { req } = scope;
|
|
94
|
-
const { fallbackLocale, locale: requestLocale } = req;
|
|
95
181
|
const blockers = [];
|
|
96
|
-
let unavailable = false;
|
|
97
|
-
for (const locale of locales) try {
|
|
98
|
-
const doc = await readDraft(scope, {
|
|
99
|
-
target,
|
|
100
|
-
locale,
|
|
101
|
-
privileged: true
|
|
102
|
-
});
|
|
103
|
-
const result = await collectPublishBlockers(req, {
|
|
104
|
-
doc,
|
|
105
|
-
entity: target
|
|
106
|
-
});
|
|
107
|
-
unavailable ||= result.unavailable === true;
|
|
108
|
-
blockers.push(...result.blockers.map((entry) => ({
|
|
109
|
-
...entry,
|
|
110
|
-
locale
|
|
111
|
-
})));
|
|
112
|
-
} catch (error) {
|
|
113
|
-
req.payload.logger.warn(`[payloadcms-mcpx] Could not read the ${target.slug} draft in ${locale}: ${error instanceof Error ? error.message : "unknown error"}`);
|
|
114
|
-
unavailable = true;
|
|
115
|
-
}
|
|
116
|
-
if (requestLocale !== void 0) req.locale = requestLocale;
|
|
117
|
-
if (fallbackLocale !== void 0) req.fallbackLocale = fallbackLocale;
|
|
118
182
|
return {
|
|
119
183
|
blockers,
|
|
120
|
-
...
|
|
184
|
+
...await withRequestLocale(req, async () => {
|
|
185
|
+
let failed = false;
|
|
186
|
+
for (const locale of locales) try {
|
|
187
|
+
const doc = await readDraft(scope, {
|
|
188
|
+
target,
|
|
189
|
+
locale,
|
|
190
|
+
privileged: true
|
|
191
|
+
});
|
|
192
|
+
const result = await collectPublishBlockers(req, {
|
|
193
|
+
doc,
|
|
194
|
+
entity: target
|
|
195
|
+
});
|
|
196
|
+
failed ||= result.unavailable === true;
|
|
197
|
+
blockers.push(...result.blockers.map((entry) => ({
|
|
198
|
+
...entry,
|
|
199
|
+
locale
|
|
200
|
+
})));
|
|
201
|
+
} catch (error) {
|
|
202
|
+
req.payload.logger.warn(`[payloadcms-mcpx] Could not read the ${target.slug} draft in ${locale}: ${error instanceof Error ? error.message : "unknown error"}`);
|
|
203
|
+
failed = true;
|
|
204
|
+
}
|
|
205
|
+
return failed;
|
|
206
|
+
}) ? { unavailable: true } : {}
|
|
121
207
|
};
|
|
122
208
|
};
|
|
123
209
|
//#endregion
|
|
124
|
-
export { collectLocaleBlockers, identityOf, readDraft, resolveDocument, resolveEntity, staleReadResult };
|
|
210
|
+
export { blockerFields, collectLocaleBlockers, documentSummary, globalConfig, identityOf, readDraft, resolveCollection, resolveDocument, resolveEntity, staleReadResult, updateTarget, withRequestLocale, writtenResult };
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { defineMcpxTool } from "../define-tool.mjs";
|
|
2
|
-
import { idSchema, liveWriteSentence, localeOf,
|
|
3
|
-
import {
|
|
4
|
-
import { createdResult } from "./create-document.mjs";
|
|
2
|
+
import { idSchema, liveWriteSentence, localeOf, slugsFor, stringEnum } from "./shared.mjs";
|
|
3
|
+
import { resolveCollection, writtenResult } from "./document.mjs";
|
|
5
4
|
import { withDuplicateIntent } from "../write/publish-intent.mjs";
|
|
6
5
|
import { Forbidden } from "payload";
|
|
7
6
|
//#region src/tools/duplicate-document.ts
|
|
8
|
-
const duplicableSlugs = (scope) => slugsFor(scope, "create").collections.filter((slug) => scope.req.payload.
|
|
7
|
+
const duplicableSlugs = (scope) => slugsFor(scope, "create").collections.filter((slug) => scope.req.payload.collections[slug]?.config.disableDuplicate !== true && !scope.exposure.collections.some((entity) => entity.slug === slug && entity.isUpload));
|
|
9
8
|
const DESCRIPTION = (scope) => `Copies a document into a new one in the same collection. Use it instead of getDocument and createDocument to copy a document. It copies the latest version, the draft if there is one, in every locale. Unique text fields get " - Copy" appended. Returns the same fields as createDocument. To rename the copy, call patchDocument with the returned "updatedAt".
|
|
10
9
|
|
|
11
10
|
${liveWriteSentence(scope, "create")}`;
|
|
@@ -23,12 +22,12 @@ ${liveWriteSentence(scope, "create")}`;
|
|
|
23
22
|
},
|
|
24
23
|
isEnabled: (scope) => duplicableSlugs(scope).length > 0,
|
|
25
24
|
inputSchema: (scope) => ({
|
|
26
|
-
collection:
|
|
25
|
+
collection: stringEnum(duplicableSlugs(scope)),
|
|
27
26
|
id: idSchema
|
|
28
27
|
}),
|
|
29
28
|
handler: async ({ args, scope }) => {
|
|
30
|
-
const target =
|
|
31
|
-
if (
|
|
29
|
+
const target = resolveCollection(scope, args.collection, "create");
|
|
30
|
+
if (!duplicableSlugs(scope).includes(target.slug)) throw new Forbidden(scope.req.t);
|
|
32
31
|
const created = await scope.req.payload.duplicate({
|
|
33
32
|
collection: target.slug,
|
|
34
33
|
id: args.id,
|
|
@@ -38,9 +37,11 @@ ${liveWriteSentence(scope, "create")}`;
|
|
|
38
37
|
overrideAccess: false,
|
|
39
38
|
req: scope.req
|
|
40
39
|
});
|
|
41
|
-
return await
|
|
42
|
-
target
|
|
43
|
-
|
|
40
|
+
return await writtenResult(scope, {
|
|
41
|
+
target: {
|
|
42
|
+
...target,
|
|
43
|
+
id: created["id"]
|
|
44
|
+
},
|
|
44
45
|
locale: localeOf(scope, void 0)
|
|
45
46
|
});
|
|
46
47
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { defineMcpxTool } from "../define-tool.mjs";
|
|
2
|
+
import { definedProps } from "../guards.mjs";
|
|
2
3
|
import { jsonResult } from "../result.mjs";
|
|
3
4
|
import { stripAdminHidden } from "../schema/admin-hidden.mjs";
|
|
4
5
|
import "../schema/index.mjs";
|
|
5
|
-
import { READ_LOCALE_DESCRIPTION, depthShape, localeOf, localeShape,
|
|
6
|
-
import {
|
|
6
|
+
import { READ_LOCALE_DESCRIPTION, depthShape, localeOf, localeShape, pageFields, pageShape, stringEnum } from "./shared.mjs";
|
|
7
|
+
import { resolveCollection } from "./document.mjs";
|
|
7
8
|
import { assertQueryable } from "./query-paths.mjs";
|
|
8
9
|
import { mcpxReadRequest } from "./read-request.mjs";
|
|
9
10
|
import { z } from "zod";
|
|
@@ -23,11 +24,10 @@ import { z } from "zod";
|
|
|
23
24
|
},
|
|
24
25
|
isEnabled: (scope) => scope.collections.readable.length > 0,
|
|
25
26
|
inputSchema: (scope) => ({
|
|
26
|
-
collection:
|
|
27
|
+
collection: stringEnum(scope.collections.readable),
|
|
27
28
|
where: z.record(z.string(), z.unknown()).optional(),
|
|
28
29
|
sort: z.string().optional().describe("e.g. \"-updatedAt\" for descending."),
|
|
29
|
-
|
|
30
|
-
page: z.number().int().min(1).optional(),
|
|
30
|
+
...pageShape(scope),
|
|
31
31
|
...depthShape(scope),
|
|
32
32
|
select: z.record(z.string(), z.unknown()).optional().describe("Fields to return, e.g. {\"title\":true}."),
|
|
33
33
|
...localeShape(scope, {
|
|
@@ -37,7 +37,7 @@ import { z } from "zod";
|
|
|
37
37
|
draft: z.boolean().optional().describe("Default true: latest drafts.")
|
|
38
38
|
}),
|
|
39
39
|
handler: async ({ args, scope }) => {
|
|
40
|
-
|
|
40
|
+
const target = resolveCollection(scope, args.collection, "read");
|
|
41
41
|
const locale = localeOf(scope, args.locale);
|
|
42
42
|
assertQueryable(scope, {
|
|
43
43
|
collection: args.collection,
|
|
@@ -52,22 +52,17 @@ import { z } from "zod";
|
|
|
52
52
|
limit: args.limit ?? 10,
|
|
53
53
|
overrideAccess: false,
|
|
54
54
|
req: mcpxReadRequest(scope),
|
|
55
|
-
...
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
55
|
+
...definedProps({
|
|
56
|
+
page: args.page,
|
|
57
|
+
sort: args.sort,
|
|
58
|
+
where: args.where,
|
|
59
|
+
select: args.select,
|
|
60
|
+
locale
|
|
61
|
+
})
|
|
60
62
|
});
|
|
61
63
|
return jsonResult({
|
|
62
|
-
docs: result.docs.map((doc) => stripAdminHidden(scope.req.payload.config,
|
|
63
|
-
|
|
64
|
-
slug: args.collection
|
|
65
|
-
}, doc)),
|
|
66
|
-
totalDocs: result.totalDocs,
|
|
67
|
-
page: result.page,
|
|
68
|
-
totalPages: result.totalPages,
|
|
69
|
-
limit: result.limit,
|
|
70
|
-
hasNextPage: result.hasNextPage
|
|
64
|
+
docs: result.docs.map((doc) => stripAdminHidden(scope.req.payload.config, target, doc)),
|
|
65
|
+
...pageFields(result)
|
|
71
66
|
});
|
|
72
67
|
}
|
|
73
68
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { defineMcpxTool } from "../define-tool.mjs";
|
|
2
|
+
import { definedProps } from "../guards.mjs";
|
|
2
3
|
import { jsonResult } from "../result.mjs";
|
|
3
|
-
import {
|
|
4
|
+
import { entityShape, idShape, localeOf, localeShape, pageFields, pageShape, reaches, slugsFor, slugsWhere, widen } from "./shared.mjs";
|
|
4
5
|
import { resolveDocument } from "./document.mjs";
|
|
5
6
|
import { queryVersions } from "./versions.mjs";
|
|
6
7
|
import { z } from "zod";
|
|
@@ -9,7 +10,7 @@ import { hasDraftsEnabled } from "payload/shared";
|
|
|
9
10
|
//#region src/tools/find-versions.ts
|
|
10
11
|
const DESCRIPTION = `Lists the versions of one document or global, newest first, autosaves included. Returns per version "versionId", "createdAt", "updatedAt", "status", "latest", "autosave" and, where set, "publishedLocale", without the content. Read a version with getDocument "versionId", or compare it with "diffFrom".`;
|
|
11
12
|
const statusShape = (scope) => {
|
|
12
|
-
const { collections, globals } =
|
|
13
|
+
const { collections, globals } = slugsWhere(scope, (entity) => entity.hasDrafts, slugsFor(scope, "versions"));
|
|
13
14
|
if (collections.length + globals.length === 0) return widen({});
|
|
14
15
|
return widen({ status: z.enum(["published", "draft"]).optional() });
|
|
15
16
|
};
|
|
@@ -25,15 +26,11 @@ const statusShape = (scope) => {
|
|
|
25
26
|
readOnlyHint: true,
|
|
26
27
|
openWorldHint: false
|
|
27
28
|
},
|
|
28
|
-
isEnabled: (scope) =>
|
|
29
|
-
const { collections, globals } = slugsFor(scope, "versions");
|
|
30
|
-
return collections.length + globals.length > 0;
|
|
31
|
-
},
|
|
29
|
+
isEnabled: (scope) => reaches(scope, "versions"),
|
|
32
30
|
inputSchema: (scope) => ({
|
|
33
31
|
...entityShape(scope, "versions"),
|
|
34
32
|
...idShape(scope, "versions"),
|
|
35
|
-
|
|
36
|
-
page: z.number().int().min(1).optional(),
|
|
33
|
+
...pageShape(scope),
|
|
37
34
|
...statusShape(scope),
|
|
38
35
|
...localeShape(scope, {
|
|
39
36
|
required: false,
|
|
@@ -49,8 +46,10 @@ const statusShape = (scope) => {
|
|
|
49
46
|
locale: localeOf(scope, args.locale)
|
|
50
47
|
}, {
|
|
51
48
|
limit: args.limit ?? 10,
|
|
52
|
-
...
|
|
53
|
-
|
|
49
|
+
...definedProps({
|
|
50
|
+
page: args.page,
|
|
51
|
+
status: args.status
|
|
52
|
+
})
|
|
54
53
|
});
|
|
55
54
|
const versions = result.docs.map((doc) => ({
|
|
56
55
|
versionId: doc.id,
|
|
@@ -63,10 +62,7 @@ const statusShape = (scope) => {
|
|
|
63
62
|
}));
|
|
64
63
|
return jsonResult({
|
|
65
64
|
versions,
|
|
66
|
-
|
|
67
|
-
page: result.page,
|
|
68
|
-
totalPages: result.totalPages,
|
|
69
|
-
hasNextPage: result.hasNextPage
|
|
65
|
+
...pageFields(result)
|
|
70
66
|
});
|
|
71
67
|
}
|
|
72
68
|
});
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { defineMcpxTool } from "../define-tool.mjs";
|
|
2
2
|
import { errorResult, jsonResult } from "../result.mjs";
|
|
3
|
-
import { JSON_POINTER_PATTERN, prototypeSegmentProblem, splitPath } from "../schema/path.mjs";
|
|
4
|
-
import { findRichTextField } from "../schema/walk.mjs";
|
|
5
3
|
import { SchemaError } from "../schema/errors.mjs";
|
|
4
|
+
import { JSON_POINTER_PATTERN, prototypeSegmentProblem, splitPath } from "../schema/path.mjs";
|
|
5
|
+
import { findFieldAt } from "../schema/walk.mjs";
|
|
6
6
|
import { lexicalOutline } from "../schema/outline.mjs";
|
|
7
7
|
import { resolveDataPointer } from "../schema/pointer.mjs";
|
|
8
8
|
import "../schema/index.mjs";
|
|
9
9
|
import { downloadHandoff, downloadSlugs } from "../upload/file.mjs";
|
|
10
|
-
import { READ_LOCALE_DESCRIPTION, depthShape, entityShape, idSchema, idShape, localeOf, localeShape, slugsFor, widen } from "./shared.mjs";
|
|
10
|
+
import { READ_LOCALE_DESCRIPTION, depthShape, entityShape, idSchema, idShape, localeOf, localeShape, reaches, slugsFor, widen } from "./shared.mjs";
|
|
11
11
|
import { identityOf, resolveDocument } from "./document.mjs";
|
|
12
12
|
import { diffDocuments, loadPublished, loadVersion, readLive } from "./versions.mjs";
|
|
13
13
|
import { z } from "zod";
|
|
@@ -19,10 +19,6 @@ const VERSION_PARAGRAPH = `
|
|
|
19
19
|
|
|
20
20
|
"versionId" and "diffFrom" work where findVersions does. To revert, call getDocument with the old version as "versionId", the version findVersions marks "latest" as "diffFrom" and the locale you will write, then apply the returned "patch" with patchDocument.`;
|
|
21
21
|
const DESCRIPTION = `Reads one document or global, or with "path" only the value at that pointer. Returns the latest draft by default, with the "updatedAt" a write takes as "expectedUpdatedAt".`;
|
|
22
|
-
const exposesVersions = (scope) => {
|
|
23
|
-
const { collections, globals } = slugsFor(scope, "versions");
|
|
24
|
-
return collections.length + globals.length > 0;
|
|
25
|
-
};
|
|
26
22
|
const assertVersionArgs = (scope, target, args) => {
|
|
27
23
|
if (args.versionId === void 0 && args.diffFrom === void 0) return;
|
|
28
24
|
const { collections, globals } = slugsFor(scope, "versions");
|
|
@@ -41,7 +37,7 @@ const diffResult = async (scope, read, diff, doc) => {
|
|
|
41
37
|
});
|
|
42
38
|
};
|
|
43
39
|
const versionShape = (scope) => {
|
|
44
|
-
if (!
|
|
40
|
+
if (!reaches(scope, "versions")) return widen({});
|
|
45
41
|
return widen({
|
|
46
42
|
versionId: idSchema.optional().describe("From findVersions. Not with \"draft\"."),
|
|
47
43
|
diffFrom: idSchema.optional().describe("Version id, or \"published\" for the newest version published in any locale. Returns {from, to, patch}, the JSON Patch from that version to what is read.")
|
|
@@ -65,12 +61,12 @@ const withDownload = async (scope, doc, target) => typeof doc["filename"] === "s
|
|
|
65
61
|
* the `expectedUpdatedAt` timestamp without a second call.
|
|
66
62
|
*/ const getDocument = defineMcpxTool({
|
|
67
63
|
name: "getDocument",
|
|
68
|
-
description: (scope) =>
|
|
64
|
+
description: (scope) => reaches(scope, "versions") ? DESCRIPTION + VERSION_PARAGRAPH : DESCRIPTION,
|
|
69
65
|
annotations: {
|
|
70
66
|
readOnlyHint: true,
|
|
71
67
|
openWorldHint: false
|
|
72
68
|
},
|
|
73
|
-
isEnabled: (scope) => scope
|
|
69
|
+
isEnabled: (scope) => reaches(scope, "read"),
|
|
74
70
|
inputSchema: (scope) => ({
|
|
75
71
|
...entityShape(scope, "read"),
|
|
76
72
|
...idShape(scope, "read"),
|
|
@@ -121,7 +117,7 @@ const withDownload = async (scope, doc, target) => typeof doc["filename"] === "s
|
|
|
121
117
|
const path = pointer.toString();
|
|
122
118
|
const value = pointer.get(doc);
|
|
123
119
|
const envelope = {
|
|
124
|
-
...identityOf(target
|
|
120
|
+
...identityOf(target),
|
|
125
121
|
status: doc["_status"],
|
|
126
122
|
updatedAt: doc["updatedAt"],
|
|
127
123
|
path
|
|
@@ -141,7 +137,7 @@ const withDownload = async (scope, doc, target) => typeof doc["filename"] === "s
|
|
|
141
137
|
if (!(error instanceof SchemaError)) throw error;
|
|
142
138
|
return errorResult(error.message);
|
|
143
139
|
}
|
|
144
|
-
const field = resolution.descriptor?.type === "richText" && !resolution.lexical ?
|
|
140
|
+
const field = resolution.descriptor?.type === "richText" && !resolution.lexical ? findFieldAt(resolution.fields, splitPath(resolution.descriptor.path), "richText") : void 0;
|
|
145
141
|
if (!field) return errorResult(OUTLINE_ERROR);
|
|
146
142
|
return jsonResult({
|
|
147
143
|
...envelope,
|
package/dist/tools/links.mjs
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { publicOrigin } from "../request.mjs";
|
|
1
2
|
import { formatAdminURL } from "payload/shared";
|
|
2
3
|
//#region src/tools/links.ts
|
|
3
4
|
const livePreviewOf = (req, target) => {
|
|
@@ -35,7 +36,7 @@ const previewOf = async (req, args) => {
|
|
|
35
36
|
*/ const documentLinks = async (req, args) => {
|
|
36
37
|
const { target, doc, locale } = args;
|
|
37
38
|
const { config } = req.payload;
|
|
38
|
-
const origin =
|
|
39
|
+
const origin = publicOrigin(req);
|
|
39
40
|
const path = target.kind === "collection" ? `/collections/${target.slug}/${String(doc["id"])}` : `/globals/${target.slug}`;
|
|
40
41
|
const adminUrl = formatAdminURL({
|
|
41
42
|
adminRoute: config.routes.admin,
|
|
@@ -2,6 +2,7 @@ import { defineMcpxTool } from "../define-tool.mjs";
|
|
|
2
2
|
import { jsonResult } from "../result.mjs";
|
|
3
3
|
import { translateStatic, translatorFor } from "../i18n.mjs";
|
|
4
4
|
import { slugsFor } from "./shared.mjs";
|
|
5
|
+
import { globalConfig } from "./document.mjs";
|
|
5
6
|
import { MCPX_ISSUES_URL, MCPX_REPOSITORY_URL } from "../version.mjs";
|
|
6
7
|
import { hasDraftValidationEnabled } from "payload/shared";
|
|
7
8
|
//#region src/tools/list-capabilities.ts
|
|
@@ -59,7 +60,7 @@ const translateLabel = (scope, label, fallback) => {
|
|
|
59
60
|
});
|
|
60
61
|
const globals = scope.exposure.globals.flatMap((entry) => {
|
|
61
62
|
const capability = scope.capabilities.globals[entry.slug];
|
|
62
|
-
const config = payload
|
|
63
|
+
const config = globalConfig(payload, entry.slug);
|
|
63
64
|
if (!capability || !config || !capability.read) return [];
|
|
64
65
|
const description = translate(config.admin.description);
|
|
65
66
|
return [{
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import { defineMcpxTool } from "../define-tool.mjs";
|
|
2
2
|
import { isPlainObject } from "../guards.mjs";
|
|
3
|
-
import { errorResult
|
|
3
|
+
import { errorResult } from "../result.mjs";
|
|
4
4
|
import { JSON_POINTER_PATTERN } from "../schema/path.mjs";
|
|
5
5
|
import "../schema/index.mjs";
|
|
6
|
-
import { collectPublishBlockers } from "../write/publish-blockers.mjs";
|
|
7
6
|
import { assertAcceptsFile, fileShape, fileSlugs, fileWriteRefusal, uploadHandoff, uploadedFile } from "../upload/file.mjs";
|
|
8
|
-
import { entityShape, idShape, liveWriteSentence, localeOf, localeShape } from "./shared.mjs";
|
|
9
|
-
import {
|
|
10
|
-
import { documentLinks } from "./links.mjs";
|
|
7
|
+
import { entityShape, expectedUpdatedAtShape, idShape, liveWriteSentence, localeOf, localeShape, reaches } from "./shared.mjs";
|
|
8
|
+
import { readDraft, resolveDocument, staleReadResult, updateTarget, writtenResult } from "./document.mjs";
|
|
11
9
|
import { withTransaction } from "../write/transaction.mjs";
|
|
12
10
|
import { applyPatchOperations, isElementPointer } from "../write/patch.mjs";
|
|
13
11
|
import { buildWriteData } from "../write/write-data.mjs";
|
|
@@ -88,7 +86,7 @@ const notAppliedPointers = (patches, intended, saved) => patches.flatMap((operat
|
|
|
88
86
|
idempotentHint: false,
|
|
89
87
|
openWorldHint: false
|
|
90
88
|
},
|
|
91
|
-
isEnabled: (scope) => scope
|
|
89
|
+
isEnabled: (scope) => reaches(scope, "write"),
|
|
92
90
|
inputSchema: (scope) => ({
|
|
93
91
|
...entityShape(scope, "write"),
|
|
94
92
|
...idShape(scope, "write"),
|
|
@@ -97,7 +95,7 @@ const notAppliedPointers = (patches, intended, saved) => patches.flatMap((operat
|
|
|
97
95
|
description: "Localized fields are written in this locale only."
|
|
98
96
|
}),
|
|
99
97
|
patches: fileSlugs(scope).length === 0 ? z.array(PATCH_OPERATION_SCHEMA).min(1).max(PATCHES_LIMIT) : z.array(PATCH_OPERATION_SCHEMA).max(PATCHES_LIMIT).describe("May be empty when \"file\" is given."),
|
|
100
|
-
|
|
98
|
+
...expectedUpdatedAtShape,
|
|
101
99
|
...fileShape(scope, (slugs) => `Replaces the file of a document in ${slugs}. The call returns an "upload" for the bytes instead of writing.`)
|
|
102
100
|
}),
|
|
103
101
|
handler: async ({ args, scope }) => {
|
|
@@ -110,11 +108,6 @@ const notAppliedPointers = (patches, intended, saved) => patches.flatMap((operat
|
|
|
110
108
|
if (file !== void 0) assertAcceptsFile(scope, target, file);
|
|
111
109
|
if (patches.length === 0 && file === void 0) throw new APIError("\"patches\" may be empty only when \"file\" is given.", 400);
|
|
112
110
|
const report = async (next) => {
|
|
113
|
-
const saved = await readDraft(scope, {
|
|
114
|
-
target,
|
|
115
|
-
locale,
|
|
116
|
-
privileged: true
|
|
117
|
-
});
|
|
118
111
|
const readable = await readDraft(scope, {
|
|
119
112
|
target,
|
|
120
113
|
locale
|
|
@@ -123,22 +116,10 @@ const notAppliedPointers = (patches, intended, saved) => patches.flatMap((operat
|
|
|
123
116
|
throw error;
|
|
124
117
|
});
|
|
125
118
|
const notApplied = readable ? notAppliedPointers(patches, next, readable) : [];
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
return jsonResult({
|
|
131
|
-
...identityOf(target, saved["id"]),
|
|
132
|
-
status: saved["_status"],
|
|
133
|
-
updatedAt: saved["updatedAt"],
|
|
134
|
-
...await documentLinks(scope.req, {
|
|
135
|
-
target,
|
|
136
|
-
doc: saved,
|
|
137
|
-
locale
|
|
138
|
-
}),
|
|
139
|
-
...validation.blockers.length > 0 ? { publishBlockers: validation.blockers } : {},
|
|
140
|
-
...validation.unavailable ? { publishBlockersUnavailable: true } : {},
|
|
141
|
-
...notApplied.length > 0 ? { notApplied } : {}
|
|
119
|
+
return await writtenResult(scope, {
|
|
120
|
+
target,
|
|
121
|
+
locale,
|
|
122
|
+
extra: notApplied.length > 0 ? { notApplied } : {}
|
|
142
123
|
});
|
|
143
124
|
};
|
|
144
125
|
const outcome = await withTransaction(scope.req, async () => {
|
|
@@ -167,24 +148,11 @@ const notAppliedPointers = (patches, intended, saved) => patches.flatMap((operat
|
|
|
167
148
|
expectedUpdatedAt: doc["updatedAt"]
|
|
168
149
|
} };
|
|
169
150
|
}
|
|
170
|
-
|
|
151
|
+
await updateTarget(scope, target, {
|
|
171
152
|
data: buildWriteData(payload.config, target.config, applied.next),
|
|
172
|
-
depth: 0,
|
|
173
153
|
draft: true,
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
...locale === void 0 ? {} : { locale }
|
|
177
|
-
};
|
|
178
|
-
if (target.kind === "collection") await payload.update({
|
|
179
|
-
...write,
|
|
180
|
-
collection: target.slug,
|
|
181
|
-
id: target.id,
|
|
182
|
-
...bytes === void 0 ? {} : { file: bytes }
|
|
183
|
-
});
|
|
184
|
-
else await payload.updateGlobal({
|
|
185
|
-
...write,
|
|
186
|
-
fallbackLocale: false,
|
|
187
|
-
slug: target.slug
|
|
154
|
+
locale,
|
|
155
|
+
file: bytes
|
|
188
156
|
});
|
|
189
157
|
return bytes === void 0 ? { done: await report(applied.next) } : { written: applied.next };
|
|
190
158
|
});
|