payload-mcp-toolkit 0.3.4 → 0.7.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 +253 -151
- package/dist/api-keys.d.ts +46 -0
- package/dist/api-keys.js +308 -0
- package/dist/api-keys.js.map +1 -0
- package/dist/auth-strategy.d.ts +96 -0
- package/dist/auth-strategy.js +261 -0
- package/dist/auth-strategy.js.map +1 -0
- package/dist/components/CollectionScopesMatrix.d.ts +8 -0
- package/dist/components/CollectionScopesMatrix.js +32 -0
- package/dist/components/CollectionScopesMatrix.js.map +1 -0
- package/dist/components/GlobalScopesMatrix.d.ts +8 -0
- package/dist/components/GlobalScopesMatrix.js +28 -0
- package/dist/components/GlobalScopesMatrix.js.map +1 -0
- package/dist/components/ScopesTable.d.ts +19 -0
- package/dist/components/ScopesTable.js +285 -0
- package/dist/components/ScopesTable.js.map +1 -0
- package/dist/components/index.d.ts +2 -0
- package/dist/components/index.js +4 -0
- package/dist/components/index.js.map +1 -0
- package/dist/conflict-detection.d.ts +13 -0
- package/dist/conflict-detection.js +41 -0
- package/dist/conflict-detection.js.map +1 -0
- package/dist/draft-workflow.d.ts +46 -48
- package/dist/draft-workflow.js +53 -135
- package/dist/draft-workflow.js.map +1 -1
- package/dist/endpoint.d.ts +35 -0
- package/dist/endpoint.js +105 -0
- package/dist/endpoint.js.map +1 -0
- package/dist/hash.d.ts +21 -0
- package/dist/hash.js +36 -0
- package/dist/hash.js.map +1 -0
- package/dist/index.d.ts +9 -9
- package/dist/index.js +167 -69
- package/dist/index.js.map +1 -1
- package/dist/introspection.d.ts +17 -3
- package/dist/introspection.js +95 -36
- package/dist/introspection.js.map +1 -1
- package/dist/prompts.js +5 -5
- package/dist/prompts.js.map +1 -1
- package/dist/registry.d.ts +50 -0
- package/dist/registry.js +169 -0
- package/dist/registry.js.map +1 -0
- package/dist/resources.d.ts +5 -3
- package/dist/resources.js +23 -11
- package/dist/resources.js.map +1 -1
- package/dist/scope/audit-log.d.ts +18 -0
- package/dist/scope/audit-log.js +50 -0
- package/dist/scope/audit-log.js.map +1 -0
- package/dist/scope/policy.d.ts +73 -0
- package/dist/scope/policy.js +218 -0
- package/dist/scope/policy.js.map +1 -0
- package/dist/tools/_helpers.d.ts +62 -1
- package/dist/tools/_helpers.js +181 -0
- package/dist/tools/_helpers.js.map +1 -1
- package/dist/tools/_layout-helpers.d.ts +43 -0
- package/dist/tools/_layout-helpers.js +159 -0
- package/dist/tools/_layout-helpers.js.map +1 -0
- package/dist/tools/create-document.d.ts +5 -5
- package/dist/tools/create-document.js +25 -21
- package/dist/tools/create-document.js.map +1 -1
- package/dist/tools/delete-document.d.ts +25 -0
- package/dist/tools/delete-document.js +49 -0
- package/dist/tools/delete-document.js.map +1 -0
- package/dist/tools/find-document.d.ts +33 -0
- package/dist/tools/find-document.js +97 -0
- package/dist/tools/find-document.js.map +1 -0
- package/dist/tools/find-global.d.ts +26 -0
- package/dist/tools/find-global.js +122 -0
- package/dist/tools/find-global.js.map +1 -0
- package/dist/tools/global-versions.d.ts +39 -0
- package/dist/tools/global-versions.js +132 -0
- package/dist/tools/global-versions.js.map +1 -0
- package/dist/tools/patch-global-layout.d.ts +31 -0
- package/dist/tools/patch-global-layout.js +127 -0
- package/dist/tools/patch-global-layout.js.map +1 -0
- package/dist/tools/patch-layout.d.ts +5 -8
- package/dist/tools/patch-layout.js +18 -100
- package/dist/tools/patch-layout.js.map +1 -1
- package/dist/tools/publish-draft.d.ts +5 -4
- package/dist/tools/publish-draft.js +39 -2
- package/dist/tools/publish-draft.js.map +1 -1
- package/dist/tools/publish-global-draft.d.ts +20 -0
- package/dist/tools/publish-global-draft.js +79 -0
- package/dist/tools/publish-global-draft.js.map +1 -0
- package/dist/tools/resolve-reference.d.ts +5 -4
- package/dist/tools/resolve-reference.js +4 -0
- package/dist/tools/resolve-reference.js.map +1 -1
- package/dist/tools/safe-delete.d.ts +5 -5
- package/dist/tools/safe-delete.js +20 -15
- package/dist/tools/safe-delete.js.map +1 -1
- package/dist/tools/schedule-publish.d.ts +5 -5
- package/dist/tools/schedule-publish.js +23 -19
- package/dist/tools/schedule-publish.js.map +1 -1
- package/dist/tools/search-content.d.ts +5 -9
- package/dist/tools/search-content.js +16 -12
- package/dist/tools/search-content.js.map +1 -1
- package/dist/tools/update-document.d.ts +5 -5
- package/dist/tools/update-document.js +10 -5
- package/dist/tools/update-document.js.map +1 -1
- package/dist/tools/update-global.d.ts +27 -0
- package/dist/tools/update-global.js +72 -0
- package/dist/tools/update-global.js.map +1 -0
- package/dist/tools/upload-media.d.ts +5 -4
- package/dist/tools/upload-media.js +6 -1
- package/dist/tools/upload-media.js.map +1 -1
- package/dist/tools/versions.d.ts +10 -9
- package/dist/tools/versions.js +15 -7
- package/dist/tools/versions.js.map +1 -1
- package/dist/types.d.ts +56 -3
- package/dist/types.js +13 -6
- package/dist/types.js.map +1 -1
- package/package.json +39 -18
- package/dist/__tests__/introspection.test.js +0 -459
- package/dist/__tests__/introspection.test.js.map +0 -1
- package/dist/__tests__/url-validator.test.js +0 -326
- package/dist/__tests__/url-validator.test.js.map +0 -1
|
@@ -1,18 +1,19 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { DRAFT_NOTE, errorMessage, getDocDisplayName, jsonResponse, stampMcpContext } from './_helpers';
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
3
|
+
import { applyOperation, errorResponse, validateBlockList } from './_layout-helpers';
|
|
4
|
+
/**
|
|
5
|
+
* patchLayout — surgical wrapper that mutates a single document's blocks-typed
|
|
6
|
+
* field directly without round-tripping the entire array through updateDocument.
|
|
7
|
+
*
|
|
8
|
+
* Why this exists: prompting an LLM to "add a CTA at the bottom of the home
|
|
9
|
+
* page" via updateDocument forces it to send the whole layout array, which one
|
|
10
|
+
* bad token can wipe out. patchLayout fetches the current array itself,
|
|
11
|
+
* applies a scoped operation, and writes back — the LLM only describes the
|
|
12
|
+
* delta.
|
|
13
|
+
*
|
|
14
|
+
* Validation walks every block recursively against the introspected
|
|
15
|
+
* BlockNestingMap, so arbitrarily-nested layouts work as long as each
|
|
16
|
+
* `blocks`-typed field's content matches that field's allow list.
|
|
16
17
|
*/ export function createPatchLayoutTool(catalog, nesting, draftCollections) {
|
|
17
18
|
const allBlockSlugs = new Set(catalog.blocks.map((b)=>b.slug));
|
|
18
19
|
// Lookups keyed by `<owner>:<fieldPath>` for O(1) access during recursive validation.
|
|
@@ -28,6 +29,10 @@ import { DRAFT_NOTE, errorMessage, getDocDisplayName, jsonResponse, stampMcpCont
|
|
|
28
29
|
}
|
|
29
30
|
return {
|
|
30
31
|
name: 'patchLayout',
|
|
32
|
+
routing: {
|
|
33
|
+
kind: 'collection',
|
|
34
|
+
action: 'update'
|
|
35
|
+
},
|
|
31
36
|
description: 'Surgically modify a document\'s blocks-typed field (e.g. "layout") without sending the whole array. Pass the blocks to add/replace plus an operation (append, prepend, insertAt, replaceAt, full). The current array is fetched server-side and the operation is applied atomically. Each block must include a `blockType` plus its fields; nested `blocks`-typed fields can contain arbitrarily-deep block arrays as long as each level matches the schema. Use the `blockNesting` resource to see which slugs each field accepts.',
|
|
32
37
|
parameters: {
|
|
33
38
|
collection: z.string().describe('The collection slug containing the document'),
|
|
@@ -105,92 +110,5 @@ import { DRAFT_NOTE, errorMessage, getDocDisplayName, jsonResponse, stampMcpCont
|
|
|
105
110
|
}
|
|
106
111
|
};
|
|
107
112
|
}
|
|
108
|
-
function errorResponse(message, extra) {
|
|
109
|
-
return jsonResponse({
|
|
110
|
-
success: false,
|
|
111
|
-
error: message,
|
|
112
|
-
...extra ?? {}
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Recursively validate a block array against an allow list, descending into
|
|
117
|
-
* each block's own `blocks`-typed fields when present.
|
|
118
|
-
*/ function validateBlockList(blocks, allowedSlugs, pathLabel, allBlockSlugs, nestingByBlockField, errors) {
|
|
119
|
-
for(let i = 0; i < blocks.length; i++){
|
|
120
|
-
const block = blocks[i];
|
|
121
|
-
const here = `${pathLabel}[${i}]`;
|
|
122
|
-
if (!block || typeof block !== 'object') {
|
|
123
|
-
errors.push(`${here}: not an object`);
|
|
124
|
-
continue;
|
|
125
|
-
}
|
|
126
|
-
const slug = block.blockType;
|
|
127
|
-
if (typeof slug !== 'string' || !slug) {
|
|
128
|
-
errors.push(`${here}: missing string \`blockType\``);
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
if (!allBlockSlugs.has(slug)) {
|
|
132
|
-
errors.push(`${here}: unknown blockType "${slug}". Known: ${[
|
|
133
|
-
...allBlockSlugs
|
|
134
|
-
].join(', ')}`);
|
|
135
|
-
continue;
|
|
136
|
-
}
|
|
137
|
-
if (!allowedSlugs.includes(slug)) {
|
|
138
|
-
errors.push(`${here}: blockType "${slug}" not allowed here. Allowed at this position: ${allowedSlugs.join(', ') || '(none)'}`);
|
|
139
|
-
continue;
|
|
140
|
-
}
|
|
141
|
-
// Any value that is itself an array of objects with `blockType` is treated
|
|
142
|
-
// as a nested blocks field. The field name is cross-checked against the
|
|
143
|
-
// nesting map to find the next-level allow list.
|
|
144
|
-
for (const [fieldName, value] of Object.entries(block)){
|
|
145
|
-
if (!Array.isArray(value)) continue;
|
|
146
|
-
if (value.length === 0) continue;
|
|
147
|
-
if (!value.every((v)=>v && typeof v === 'object' && 'blockType' in v)) continue;
|
|
148
|
-
const nextKey = `${slug}:${fieldName}`;
|
|
149
|
-
const nextAllowed = nestingByBlockField.get(nextKey);
|
|
150
|
-
if (!nextAllowed) {
|
|
151
|
-
errors.push(`${here}.${fieldName}: block "${slug}" has no blocks field named "${fieldName}" in the schema`);
|
|
152
|
-
continue;
|
|
153
|
-
}
|
|
154
|
-
validateBlockList(value, nextAllowed, `${here}.${fieldName}`, allBlockSlugs, nestingByBlockField, errors);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Apply a list operation against an existing array of blocks.
|
|
160
|
-
* `full` always replaces; the rest preserve the existing array.
|
|
161
|
-
*/ function applyOperation(newBlocks, operation, insertIndex, existingLayout) {
|
|
162
|
-
if (operation === 'full' || !existingLayout) {
|
|
163
|
-
return newBlocks;
|
|
164
|
-
}
|
|
165
|
-
const existing = [
|
|
166
|
-
...existingLayout
|
|
167
|
-
];
|
|
168
|
-
if (operation === 'append') return [
|
|
169
|
-
...existing,
|
|
170
|
-
...newBlocks
|
|
171
|
-
];
|
|
172
|
-
if (operation === 'prepend') return [
|
|
173
|
-
...newBlocks,
|
|
174
|
-
...existing
|
|
175
|
-
];
|
|
176
|
-
if (operation === 'insertAt') {
|
|
177
|
-
if (insertIndex === undefined || insertIndex < 0 || insertIndex > existing.length) {
|
|
178
|
-
return [
|
|
179
|
-
...existing,
|
|
180
|
-
...newBlocks
|
|
181
|
-
];
|
|
182
|
-
}
|
|
183
|
-
existing.splice(insertIndex, 0, ...newBlocks);
|
|
184
|
-
return existing;
|
|
185
|
-
}
|
|
186
|
-
if (operation === 'replaceAt') {
|
|
187
|
-
if (insertIndex === undefined || insertIndex < 0 || insertIndex >= existing.length) {
|
|
188
|
-
return existing;
|
|
189
|
-
}
|
|
190
|
-
existing.splice(insertIndex, newBlocks.length, ...newBlocks);
|
|
191
|
-
return existing;
|
|
192
|
-
}
|
|
193
|
-
return newBlocks;
|
|
194
|
-
}
|
|
195
113
|
|
|
196
114
|
//# sourceMappingURL=patch-layout.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/tools/patch-layout.ts"],"sourcesContent":["import { z } from 'zod'\nimport type { PayloadRequest } from 'payload'\nimport type { BlockCatalog, BlockNestingMap } from '../types'\nimport {\n DRAFT_NOTE,\n errorMessage,\n getDocDisplayName,\n jsonResponse,\n stampMcpContext,\n} from './_helpers'\n\n/**\n * patchLayout — surgical wrapper that mutates a single document's blocks-typed\n * field directly without round-tripping the entire array through updateDocument.\n *\n * Why this exists: prompting an LLM to \"add a CTA at the bottom of the home\n * page\" via updateDocument forces it to send the whole layout array, which one\n * bad token can wipe out. patchLayout fetches the current array itself,\n * applies a scoped operation, and writes back — the LLM only describes the\n * delta.\n *\n * Validation walks every block recursively against the introspected\n * BlockNestingMap, so arbitrarily-nested layouts work as long as each\n * `blocks`-typed field's content matches that field's allow list.\n */\nexport function createPatchLayoutTool(\n catalog: BlockCatalog,\n nesting: BlockNestingMap,\n draftCollections: Set<string>,\n) {\n const allBlockSlugs = new Set(catalog.blocks.map((b) => b.slug))\n\n // Lookups keyed by `<owner>:<fieldPath>` for O(1) access during recursive validation.\n const nestingByCollectionField = new Map<string, string[]>()\n const nestingByBlockField = new Map<string, string[]>()\n for (const edge of nesting) {\n const key = `${edge.owner}:${edge.fieldPath}`\n if (edge.ownerType === 'collection') {\n nestingByCollectionField.set(key, edge.acceptedBlockSlugs)\n } else {\n nestingByBlockField.set(key, edge.acceptedBlockSlugs)\n }\n }\n\n return {\n name: 'patchLayout',\n description:\n 'Surgically modify a document\\'s blocks-typed field (e.g. \"layout\") without sending the whole array. Pass the blocks to add/replace plus an operation (append, prepend, insertAt, replaceAt, full). The current array is fetched server-side and the operation is applied atomically. Each block must include a `blockType` plus its fields; nested `blocks`-typed fields can contain arbitrarily-deep block arrays as long as each level matches the schema. Use the `blockNesting` resource to see which slugs each field accepts.',\n parameters: {\n collection: z.string().describe('The collection slug containing the document'),\n documentId: z.string().describe('The ID of the document to patch'),\n layoutField: z\n .string()\n .optional()\n .default('layout')\n .describe('Name of the blocks-typed field to patch (default \"layout\")'),\n blocks: z\n .array(z.record(z.string(), z.unknown()))\n .describe(\n 'Blocks to compose. Each must have a `blockType` discriminator plus any block-specific fields. Nested blocks fields hold their own `blocks` arrays at any depth.',\n ),\n operation: z\n .enum(['append', 'prepend', 'insertAt', 'replaceAt', 'full'])\n .describe(\n 'How to apply the blocks: append (end), prepend (start), insertAt (at index), replaceAt (overwrite N starting at index), full (replace entire array — use with care).',\n ),\n insertIndex: z\n .number()\n .optional()\n .describe('Index for insertAt/replaceAt operations'),\n },\n handler: async (\n args: {\n collection: string\n documentId: string\n layoutField?: string\n blocks: Array<Record<string, unknown>>\n operation: 'append' | 'prepend' | 'insertAt' | 'replaceAt' | 'full'\n insertIndex?: number\n },\n req: PayloadRequest,\n _extra: unknown,\n ) => {\n const {\n collection,\n documentId,\n layoutField = 'layout',\n blocks,\n operation,\n insertIndex,\n } = args\n\n const rootKey = `${collection}:${layoutField}`\n const rootAllowed = nestingByCollectionField.get(rootKey)\n if (!rootAllowed) {\n return errorResponse(\n `Field \"${layoutField}\" on collection \"${collection}\" is not a blocks-typed field, or no nesting map entry exists for it.`,\n { availableFields: [...nestingByCollectionField.keys()] },\n )\n }\n\n const errors: string[] = []\n validateBlockList(blocks, rootAllowed, layoutField, allBlockSlugs, nestingByBlockField, errors)\n\n if (errors.length > 0) {\n return errorResponse('Block validation failed.', { errors })\n }\n\n stampMcpContext(req)\n\n let existing: any\n try {\n existing = await req.payload.findByID({\n collection: collection as any,\n id: documentId,\n depth: 0,\n draft: true,\n req,\n overrideAccess: false,\n user: req.user,\n })\n } catch (error) {\n return errorResponse(`Error fetching ${collection}#${documentId}: ${errorMessage(error)}`)\n }\n\n const currentLayout = Array.isArray(existing?.[layoutField]) ? existing[layoutField] : []\n const finalLayout = applyOperation(blocks, operation, insertIndex, currentLayout)\n\n const isDraftCollection = draftCollections.has(collection)\n\n try {\n const updated = await req.payload.update({\n collection: collection as any,\n id: documentId,\n data: { [layoutField]: finalLayout } as any,\n draft: isDraftCollection,\n req,\n overrideAccess: false,\n user: req.user,\n })\n\n const displayName = getDocDisplayName(updated, documentId)\n const draftNote = isDraftCollection ? DRAFT_NOTE : ''\n\n return jsonResponse({\n success: true,\n message:\n `Patched ${layoutField} on \"${displayName}\" (${collection}#${documentId}). ` +\n `Operation: ${operation}. Block count: ${finalLayout.length}.` +\n draftNote,\n blockCount: finalLayout.length,\n operation,\n })\n } catch (error) {\n return errorResponse(`Error patching ${collection}#${documentId}: ${errorMessage(error)}`)\n }\n },\n }\n}\n\nfunction errorResponse(message: string, extra?: Record<string, unknown>) {\n return jsonResponse({ success: false, error: message, ...(extra ?? {}) })\n}\n\n/**\n * Recursively validate a block array against an allow list, descending into\n * each block's own `blocks`-typed fields when present.\n */\nfunction validateBlockList(\n blocks: Array<Record<string, unknown>>,\n allowedSlugs: string[],\n pathLabel: string,\n allBlockSlugs: Set<string>,\n nestingByBlockField: Map<string, string[]>,\n errors: string[],\n) {\n for (let i = 0; i < blocks.length; i++) {\n const block = blocks[i]\n const here = `${pathLabel}[${i}]`\n\n if (!block || typeof block !== 'object') {\n errors.push(`${here}: not an object`)\n continue\n }\n\n const slug = block.blockType\n if (typeof slug !== 'string' || !slug) {\n errors.push(`${here}: missing string \\`blockType\\``)\n continue\n }\n\n if (!allBlockSlugs.has(slug)) {\n errors.push(`${here}: unknown blockType \"${slug}\". Known: ${[...allBlockSlugs].join(', ')}`)\n continue\n }\n\n if (!allowedSlugs.includes(slug)) {\n errors.push(\n `${here}: blockType \"${slug}\" not allowed here. Allowed at this position: ${allowedSlugs.join(', ') || '(none)'}`,\n )\n continue\n }\n\n // Any value that is itself an array of objects with `blockType` is treated\n // as a nested blocks field. The field name is cross-checked against the\n // nesting map to find the next-level allow list.\n for (const [fieldName, value] of Object.entries(block)) {\n if (!Array.isArray(value)) continue\n if (value.length === 0) continue\n if (!value.every((v) => v && typeof v === 'object' && 'blockType' in v)) continue\n\n const nextKey = `${slug}:${fieldName}`\n const nextAllowed = nestingByBlockField.get(nextKey)\n if (!nextAllowed) {\n errors.push(\n `${here}.${fieldName}: block \"${slug}\" has no blocks field named \"${fieldName}\" in the schema`,\n )\n continue\n }\n\n validateBlockList(\n value as Array<Record<string, unknown>>,\n nextAllowed,\n `${here}.${fieldName}`,\n allBlockSlugs,\n nestingByBlockField,\n errors,\n )\n }\n }\n}\n\n/**\n * Apply a list operation against an existing array of blocks.\n * `full` always replaces; the rest preserve the existing array.\n */\nfunction applyOperation(\n newBlocks: Record<string, unknown>[],\n operation: 'full' | 'append' | 'prepend' | 'insertAt' | 'replaceAt',\n insertIndex: number | undefined,\n existingLayout: Record<string, unknown>[] | undefined,\n): Record<string, unknown>[] {\n if (operation === 'full' || !existingLayout) {\n return newBlocks\n }\n\n const existing = [...existingLayout]\n\n if (operation === 'append') return [...existing, ...newBlocks]\n if (operation === 'prepend') return [...newBlocks, ...existing]\n\n if (operation === 'insertAt') {\n if (insertIndex === undefined || insertIndex < 0 || insertIndex > existing.length) {\n return [...existing, ...newBlocks]\n }\n existing.splice(insertIndex, 0, ...newBlocks)\n return existing\n }\n\n if (operation === 'replaceAt') {\n if (insertIndex === undefined || insertIndex < 0 || insertIndex >= existing.length) {\n return existing\n }\n existing.splice(insertIndex, newBlocks.length, ...newBlocks)\n return existing\n }\n\n return newBlocks\n}\n"],"names":["z","DRAFT_NOTE","errorMessage","getDocDisplayName","jsonResponse","stampMcpContext","createPatchLayoutTool","catalog","nesting","draftCollections","allBlockSlugs","Set","blocks","map","b","slug","nestingByCollectionField","Map","nestingByBlockField","edge","key","owner","fieldPath","ownerType","set","acceptedBlockSlugs","name","description","parameters","collection","string","describe","documentId","layoutField","optional","default","array","record","unknown","operation","enum","insertIndex","number","handler","args","req","_extra","rootKey","rootAllowed","get","errorResponse","availableFields","keys","errors","validateBlockList","length","existing","payload","findByID","id","depth","draft","overrideAccess","user","error","currentLayout","Array","isArray","finalLayout","applyOperation","isDraftCollection","has","updated","update","data","displayName","draftNote","success","message","blockCount","extra","allowedSlugs","pathLabel","i","block","here","push","blockType","join","includes","fieldName","value","Object","entries","every","v","nextKey","nextAllowed","newBlocks","existingLayout","undefined","splice"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../src/tools/patch-layout.ts"],"sourcesContent":["import { z } from 'zod'\r\nimport type { PayloadRequest } from 'payload'\r\nimport type { BlockCatalog, BlockNestingMap } from '../types'\r\nimport {\r\n DRAFT_NOTE,\r\n errorMessage,\r\n getDocDisplayName,\r\n jsonResponse,\r\n stampMcpContext,\r\n} from './_helpers'\r\nimport { applyOperation, errorResponse, validateBlockList } from './_layout-helpers'\r\n\r\n/**\r\n * patchLayout — surgical wrapper that mutates a single document's blocks-typed\r\n * field directly without round-tripping the entire array through updateDocument.\r\n *\r\n * Why this exists: prompting an LLM to \"add a CTA at the bottom of the home\r\n * page\" via updateDocument forces it to send the whole layout array, which one\r\n * bad token can wipe out. patchLayout fetches the current array itself,\r\n * applies a scoped operation, and writes back — the LLM only describes the\r\n * delta.\r\n *\r\n * Validation walks every block recursively against the introspected\r\n * BlockNestingMap, so arbitrarily-nested layouts work as long as each\r\n * `blocks`-typed field's content matches that field's allow list.\r\n */\r\nexport function createPatchLayoutTool(\r\n catalog: BlockCatalog,\r\n nesting: BlockNestingMap,\r\n draftCollections: Set<string>,\r\n) {\r\n const allBlockSlugs = new Set(catalog.blocks.map((b) => b.slug))\r\n\r\n // Lookups keyed by `<owner>:<fieldPath>` for O(1) access during recursive validation.\r\n const nestingByCollectionField = new Map<string, string[]>()\r\n const nestingByBlockField = new Map<string, string[]>()\r\n for (const edge of nesting) {\r\n const key = `${edge.owner}:${edge.fieldPath}`\r\n if (edge.ownerType === 'collection') {\r\n nestingByCollectionField.set(key, edge.acceptedBlockSlugs)\r\n } else {\r\n nestingByBlockField.set(key, edge.acceptedBlockSlugs)\r\n }\r\n }\r\n\r\n return {\r\n name: 'patchLayout',\r\n routing: { kind: 'collection', action: 'update' } as const,\r\n description:\r\n 'Surgically modify a document\\'s blocks-typed field (e.g. \"layout\") without sending the whole array. Pass the blocks to add/replace plus an operation (append, prepend, insertAt, replaceAt, full). The current array is fetched server-side and the operation is applied atomically. Each block must include a `blockType` plus its fields; nested `blocks`-typed fields can contain arbitrarily-deep block arrays as long as each level matches the schema. Use the `blockNesting` resource to see which slugs each field accepts.',\r\n parameters: {\r\n collection: z.string().describe('The collection slug containing the document'),\r\n documentId: z.string().describe('The ID of the document to patch'),\r\n layoutField: z\r\n .string()\r\n .optional()\r\n .default('layout')\r\n .describe('Name of the blocks-typed field to patch (default \"layout\")'),\r\n blocks: z\r\n .array(z.record(z.string(), z.unknown()))\r\n .describe(\r\n 'Blocks to compose. Each must have a `blockType` discriminator plus any block-specific fields. Nested blocks fields hold their own `blocks` arrays at any depth.',\r\n ),\r\n operation: z\r\n .enum(['append', 'prepend', 'insertAt', 'replaceAt', 'full'])\r\n .describe(\r\n 'How to apply the blocks: append (end), prepend (start), insertAt (at index), replaceAt (overwrite N starting at index), full (replace entire array — use with care).',\r\n ),\r\n insertIndex: z\r\n .number()\r\n .optional()\r\n .describe('Index for insertAt/replaceAt operations'),\r\n },\r\n handler: async (\r\n args: Record<string, unknown>,\r\n req: PayloadRequest,\r\n _extra: unknown,\r\n ) => {\r\n const {\r\n collection,\r\n documentId,\r\n layoutField = 'layout',\r\n blocks,\r\n operation,\r\n insertIndex,\r\n } = args as {\r\n collection: string\r\n documentId: string\r\n layoutField?: string\r\n blocks: Array<Record<string, unknown>>\r\n operation: 'append' | 'prepend' | 'insertAt' | 'replaceAt' | 'full'\r\n insertIndex?: number\r\n }\r\n\r\n const rootKey = `${collection}:${layoutField}`\r\n const rootAllowed = nestingByCollectionField.get(rootKey)\r\n if (!rootAllowed) {\r\n return errorResponse(\r\n `Field \"${layoutField}\" on collection \"${collection}\" is not a blocks-typed field, or no nesting map entry exists for it.`,\r\n { availableFields: [...nestingByCollectionField.keys()] },\r\n )\r\n }\r\n\r\n const errors: string[] = []\r\n validateBlockList(blocks, rootAllowed, layoutField, allBlockSlugs, nestingByBlockField, errors)\r\n\r\n if (errors.length > 0) {\r\n return errorResponse('Block validation failed.', { errors })\r\n }\r\n\r\n stampMcpContext(req)\r\n\r\n let existing: any\r\n try {\r\n existing = await req.payload.findByID({\r\n collection: collection as any,\r\n id: documentId,\r\n depth: 0,\r\n draft: true,\r\n req,\r\n overrideAccess: false,\r\n user: req.user,\r\n })\r\n } catch (error) {\r\n return errorResponse(`Error fetching ${collection}#${documentId}: ${errorMessage(error)}`)\r\n }\r\n\r\n const currentLayout = Array.isArray(existing?.[layoutField]) ? existing[layoutField] : []\r\n const finalLayout = applyOperation(blocks, operation, insertIndex, currentLayout)\r\n\r\n const isDraftCollection = draftCollections.has(collection)\r\n\r\n try {\r\n const updated = await req.payload.update({\r\n collection: collection as any,\r\n id: documentId,\r\n data: { [layoutField]: finalLayout } as any,\r\n draft: isDraftCollection,\r\n req,\r\n overrideAccess: false,\r\n user: req.user,\r\n })\r\n\r\n const displayName = getDocDisplayName(updated, documentId)\r\n const draftNote = isDraftCollection ? DRAFT_NOTE : ''\r\n\r\n return jsonResponse({\r\n success: true,\r\n message:\r\n `Patched ${layoutField} on \"${displayName}\" (${collection}#${documentId}). ` +\r\n `Operation: ${operation}. Block count: ${finalLayout.length}.` +\r\n draftNote,\r\n blockCount: finalLayout.length,\r\n operation,\r\n })\r\n } catch (error) {\r\n return errorResponse(`Error patching ${collection}#${documentId}: ${errorMessage(error)}`)\r\n }\r\n },\r\n }\r\n}\r\n\r\n"],"names":["z","DRAFT_NOTE","errorMessage","getDocDisplayName","jsonResponse","stampMcpContext","applyOperation","errorResponse","validateBlockList","createPatchLayoutTool","catalog","nesting","draftCollections","allBlockSlugs","Set","blocks","map","b","slug","nestingByCollectionField","Map","nestingByBlockField","edge","key","owner","fieldPath","ownerType","set","acceptedBlockSlugs","name","routing","kind","action","description","parameters","collection","string","describe","documentId","layoutField","optional","default","array","record","unknown","operation","enum","insertIndex","number","handler","args","req","_extra","rootKey","rootAllowed","get","availableFields","keys","errors","length","existing","payload","findByID","id","depth","draft","overrideAccess","user","error","currentLayout","Array","isArray","finalLayout","isDraftCollection","has","updated","update","data","displayName","draftNote","success","message","blockCount"],"mappings":"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"}
|
|
@@ -2,13 +2,14 @@ import { z } from 'zod';
|
|
|
2
2
|
import type { PayloadRequest } from 'payload';
|
|
3
3
|
export declare function createPublishDraftTool(draftCollections: Set<string>): {
|
|
4
4
|
name: string;
|
|
5
|
+
routing: {
|
|
6
|
+
readonly kind: "collection";
|
|
7
|
+
readonly action: "update";
|
|
8
|
+
};
|
|
5
9
|
description: string;
|
|
6
10
|
parameters: {
|
|
7
11
|
collection: z.ZodString;
|
|
8
12
|
documentId: z.ZodString;
|
|
9
13
|
};
|
|
10
|
-
handler: (
|
|
11
|
-
collection: string;
|
|
12
|
-
documentId: string;
|
|
13
|
-
}, req: PayloadRequest, _extra: unknown) => Promise<import("./_helpers").McpTextResponse>;
|
|
14
|
+
handler: (rawArgs: Record<string, unknown>, req: PayloadRequest, _extra: unknown) => Promise<import("./_helpers").McpTextResponse>;
|
|
14
15
|
};
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
|
-
import { errorMessage, getDocDisplayName, requireDraftCollection, stampMcpContext, textResponse } from './_helpers';
|
|
2
|
+
import { errorMessage, getDocDisplayName, requireDraftCollection, snapshotPublishMarker, stampMcpContext, textResponse, verifyPublishSucceededDespiteError } from './_helpers';
|
|
3
3
|
export function createPublishDraftTool(draftCollections) {
|
|
4
4
|
return {
|
|
5
5
|
name: 'publishDraft',
|
|
6
|
+
routing: {
|
|
7
|
+
kind: 'collection',
|
|
8
|
+
action: 'update'
|
|
9
|
+
},
|
|
6
10
|
description: 'Publish a draft document by transitioning its _status from "draft" to "published". ' + 'Only works on collections that support drafts. Use after creating or editing content ' + 'to make it live on the site.',
|
|
7
11
|
parameters: {
|
|
8
12
|
collection: z.string().describe(`The collection slug. Draft-enabled collections: ${[
|
|
@@ -10,11 +14,21 @@ export function createPublishDraftTool(draftCollections) {
|
|
|
10
14
|
].join(', ') || 'none'}`),
|
|
11
15
|
documentId: z.string().describe('The ID of the document to publish')
|
|
12
16
|
},
|
|
13
|
-
handler: async (
|
|
17
|
+
handler: async (rawArgs, req, _extra)=>{
|
|
18
|
+
const args = rawArgs;
|
|
14
19
|
const { collection, documentId } = args;
|
|
15
20
|
const guard = requireDraftCollection(collection, draftCollections);
|
|
16
21
|
if (guard) return guard;
|
|
17
22
|
stampMcpContext(req);
|
|
23
|
+
// Snapshot the doc's pre-update `updatedAt` so the recovery branch
|
|
24
|
+
// below can distinguish "this attempt landed despite a post-write
|
|
25
|
+
// validator throw" from "an older publish was successful and this
|
|
26
|
+
// attempt did nothing" (see verifyPublishSucceededDespiteError).
|
|
27
|
+
const preMarker = await snapshotPublishMarker(req, {
|
|
28
|
+
kind: 'collection',
|
|
29
|
+
slug: collection,
|
|
30
|
+
id: documentId
|
|
31
|
+
});
|
|
18
32
|
try {
|
|
19
33
|
const doc = await req.payload.update({
|
|
20
34
|
collection: collection,
|
|
@@ -29,6 +43,29 @@ export function createPublishDraftTool(draftCollections) {
|
|
|
29
43
|
const displayName = getDocDisplayName(doc, documentId);
|
|
30
44
|
return textResponse(`Successfully published "${displayName}" in ${collection} (ID: ${documentId}).`);
|
|
31
45
|
} catch (error) {
|
|
46
|
+
// Payload's update can throw a field-validation error AFTER a new
|
|
47
|
+
// published version has already been written to the `_<slug>_v`
|
|
48
|
+
// versions table (the validator runs in beforeChange-Fields, which
|
|
49
|
+
// fires after Collection-level beforeChange hooks have already
|
|
50
|
+
// mutated `data` and after the version row has been committed in
|
|
51
|
+
// some draft+versions setups — see the breadcrumb self-reference
|
|
52
|
+
// bug surfaced by `@payloadcms/plugin-nested-docs` on Payload v3).
|
|
53
|
+
// The visible-to-the-user effect is "publish appears to fail but
|
|
54
|
+
// the document is in fact live". Verify against the pre-update
|
|
55
|
+
// marker before downgrading to a warning, so a stale published
|
|
56
|
+
// version from a prior successful publish cannot mask a real
|
|
57
|
+
// failure of the current attempt.
|
|
58
|
+
const liveDoc = await verifyPublishSucceededDespiteError(req, {
|
|
59
|
+
kind: 'collection',
|
|
60
|
+
slug: collection,
|
|
61
|
+
id: documentId
|
|
62
|
+
}, preMarker);
|
|
63
|
+
if (liveDoc) {
|
|
64
|
+
const displayName = getDocDisplayName(liveDoc, documentId);
|
|
65
|
+
// Stable token prefix lets MCP clients branch on the published-
|
|
66
|
+
// with-warning state without regex-matching the prose body.
|
|
67
|
+
return textResponse(`[publishDraft:published_with_warning] ` + `Published "${displayName}" in ${collection} (ID: ${documentId}) — ` + `but Payload reported a post-write validation error: ${errorMessage(error)}. ` + `The document is live; the error did not roll back the published version.`);
|
|
68
|
+
}
|
|
32
69
|
return textResponse(`Error publishing document ${documentId} in ${collection}: ${errorMessage(error)}`);
|
|
33
70
|
}
|
|
34
71
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/tools/publish-draft.ts"],"sourcesContent":["import { z } from 'zod'\nimport type { PayloadRequest } from 'payload'\nimport {\n errorMessage,\n getDocDisplayName,\n requireDraftCollection,\n stampMcpContext,\n textResponse,\n} from './_helpers'\n\nexport function createPublishDraftTool(draftCollections: Set<string>) {\n return {\n name: 'publishDraft',\n description:\n 'Publish a draft document by transitioning its _status from \"draft\" to \"published\". ' +\n 'Only works on collections that support drafts. Use after creating or editing content ' +\n 'to make it live on the site.',\n parameters: {\n collection: z\n .string()\n .describe(\n `The collection slug. Draft-enabled collections: ${[...draftCollections].join(', ') || 'none'}`,\n ),\n documentId: z.string().describe('The ID of the document to publish'),\n },\n handler: async (\n
|
|
1
|
+
{"version":3,"sources":["../../src/tools/publish-draft.ts"],"sourcesContent":["import { z } from 'zod'\r\nimport type { PayloadRequest } from 'payload'\r\nimport {\r\n errorMessage,\r\n getDocDisplayName,\r\n requireDraftCollection,\r\n snapshotPublishMarker,\r\n stampMcpContext,\r\n textResponse,\r\n verifyPublishSucceededDespiteError,\r\n} from './_helpers'\r\n\r\nexport function createPublishDraftTool(draftCollections: Set<string>) {\r\n return {\r\n name: 'publishDraft',\r\n routing: { kind: 'collection', action: 'update' } as const,\r\n description:\r\n 'Publish a draft document by transitioning its _status from \"draft\" to \"published\". ' +\r\n 'Only works on collections that support drafts. Use after creating or editing content ' +\r\n 'to make it live on the site.',\r\n parameters: {\r\n collection: z\r\n .string()\r\n .describe(\r\n `The collection slug. Draft-enabled collections: ${[...draftCollections].join(', ') || 'none'}`,\r\n ),\r\n documentId: z.string().describe('The ID of the document to publish'),\r\n },\r\n handler: async (\r\n rawArgs: Record<string, unknown>,\r\n req: PayloadRequest,\r\n _extra: unknown,\r\n ) => {\r\n const args = rawArgs as { collection: string; documentId: string }\r\n const { collection, documentId } = args\r\n\r\n const guard = requireDraftCollection(collection, draftCollections)\r\n if (guard) return guard\r\n\r\n stampMcpContext(req)\r\n\r\n // Snapshot the doc's pre-update `updatedAt` so the recovery branch\r\n // below can distinguish \"this attempt landed despite a post-write\r\n // validator throw\" from \"an older publish was successful and this\r\n // attempt did nothing\" (see verifyPublishSucceededDespiteError).\r\n const preMarker = await snapshotPublishMarker(req, {\r\n kind: 'collection',\r\n slug: collection,\r\n id: documentId,\r\n })\r\n\r\n try {\r\n const doc = await req.payload.update({\r\n collection: collection as any,\r\n id: documentId,\r\n data: { _status: 'published' } as any,\r\n req,\r\n overrideAccess: false,\r\n user: req.user,\r\n })\r\n\r\n const displayName = getDocDisplayName(doc, documentId)\r\n return textResponse(\r\n `Successfully published \"${displayName}\" in ${collection} (ID: ${documentId}).`,\r\n )\r\n } catch (error) {\r\n // Payload's update can throw a field-validation error AFTER a new\r\n // published version has already been written to the `_<slug>_v`\r\n // versions table (the validator runs in beforeChange-Fields, which\r\n // fires after Collection-level beforeChange hooks have already\r\n // mutated `data` and after the version row has been committed in\r\n // some draft+versions setups — see the breadcrumb self-reference\r\n // bug surfaced by `@payloadcms/plugin-nested-docs` on Payload v3).\r\n // The visible-to-the-user effect is \"publish appears to fail but\r\n // the document is in fact live\". Verify against the pre-update\r\n // marker before downgrading to a warning, so a stale published\r\n // version from a prior successful publish cannot mask a real\r\n // failure of the current attempt.\r\n const liveDoc = await verifyPublishSucceededDespiteError(\r\n req,\r\n { kind: 'collection', slug: collection, id: documentId },\r\n preMarker,\r\n )\r\n if (liveDoc) {\r\n const displayName = getDocDisplayName(liveDoc, documentId)\r\n // Stable token prefix lets MCP clients branch on the published-\r\n // with-warning state without regex-matching the prose body.\r\n return textResponse(\r\n `[publishDraft:published_with_warning] ` +\r\n `Published \"${displayName}\" in ${collection} (ID: ${documentId}) — ` +\r\n `but Payload reported a post-write validation error: ${errorMessage(error)}. ` +\r\n `The document is live; the error did not roll back the published version.`,\r\n )\r\n }\r\n return textResponse(\r\n `Error publishing document ${documentId} in ${collection}: ${errorMessage(error)}`,\r\n )\r\n }\r\n },\r\n }\r\n}\r\n"],"names":["z","errorMessage","getDocDisplayName","requireDraftCollection","snapshotPublishMarker","stampMcpContext","textResponse","verifyPublishSucceededDespiteError","createPublishDraftTool","draftCollections","name","routing","kind","action","description","parameters","collection","string","describe","join","documentId","handler","rawArgs","req","_extra","args","guard","preMarker","slug","id","doc","payload","update","data","_status","overrideAccess","user","displayName","error","liveDoc"],"mappings":"AAAA,SAASA,CAAC,QAAQ,MAAK;AAEvB,SACEC,YAAY,EACZC,iBAAiB,EACjBC,sBAAsB,EACtBC,qBAAqB,EACrBC,eAAe,EACfC,YAAY,EACZC,kCAAkC,QAC7B,aAAY;AAEnB,OAAO,SAASC,uBAAuBC,gBAA6B;IAClE,OAAO;QACLC,MAAM;QACNC,SAAS;YAAEC,MAAM;YAAcC,QAAQ;QAAS;QAChDC,aACE,wFACA,0FACA;QACFC,YAAY;YACVC,YAAYhB,EACTiB,MAAM,GACNC,QAAQ,CACP,CAAC,gDAAgD,EAAE;mBAAIT;aAAiB,CAACU,IAAI,CAAC,SAAS,QAAQ;YAEnGC,YAAYpB,EAAEiB,MAAM,GAAGC,QAAQ,CAAC;QAClC;QACAG,SAAS,OACPC,SACAC,KACAC;YAEA,MAAMC,OAAOH;YACb,MAAM,EAAEN,UAAU,EAAEI,UAAU,EAAE,GAAGK;YAEnC,MAAMC,QAAQvB,uBAAuBa,YAAYP;YACjD,IAAIiB,OAAO,OAAOA;YAElBrB,gBAAgBkB;YAEhB,mEAAmE;YACnE,kEAAkE;YAClE,kEAAkE;YAClE,iEAAiE;YACjE,MAAMI,YAAY,MAAMvB,sBAAsBmB,KAAK;gBACjDX,MAAM;gBACNgB,MAAMZ;gBACNa,IAAIT;YACN;YAEA,IAAI;gBACF,MAAMU,MAAM,MAAMP,IAAIQ,OAAO,CAACC,MAAM,CAAC;oBACnChB,YAAYA;oBACZa,IAAIT;oBACJa,MAAM;wBAAEC,SAAS;oBAAY;oBAC7BX;oBACAY,gBAAgB;oBAChBC,MAAMb,IAAIa,IAAI;gBAChB;gBAEA,MAAMC,cAAcnC,kBAAkB4B,KAAKV;gBAC3C,OAAOd,aACL,CAAC,wBAAwB,EAAE+B,YAAY,KAAK,EAAErB,WAAW,MAAM,EAAEI,WAAW,EAAE,CAAC;YAEnF,EAAE,OAAOkB,OAAO;gBACd,kEAAkE;gBAClE,gEAAgE;gBAChE,mEAAmE;gBACnE,+DAA+D;gBAC/D,iEAAiE;gBACjE,iEAAiE;gBACjE,mEAAmE;gBACnE,iEAAiE;gBACjE,+DAA+D;gBAC/D,+DAA+D;gBAC/D,6DAA6D;gBAC7D,kCAAkC;gBAClC,MAAMC,UAAU,MAAMhC,mCACpBgB,KACA;oBAAEX,MAAM;oBAAcgB,MAAMZ;oBAAYa,IAAIT;gBAAW,GACvDO;gBAEF,IAAIY,SAAS;oBACX,MAAMF,cAAcnC,kBAAkBqC,SAASnB;oBAC/C,gEAAgE;oBAChE,4DAA4D;oBAC5D,OAAOd,aACL,CAAC,sCAAsC,CAAC,GACtC,CAAC,WAAW,EAAE+B,YAAY,KAAK,EAAErB,WAAW,MAAM,EAAEI,WAAW,IAAI,CAAC,GACpE,CAAC,oDAAoD,EAAEnB,aAAaqC,OAAO,EAAE,CAAC,GAC9E,CAAC,wEAAwE,CAAC;gBAEhF;gBACA,OAAOhC,aACL,CAAC,0BAA0B,EAAEc,WAAW,IAAI,EAAEJ,WAAW,EAAE,EAAEf,aAAaqC,QAAQ;YAEtF;QACF;IACF;AACF"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import type { PayloadRequest } from 'payload';
|
|
3
|
+
/**
|
|
4
|
+
* Promote a draft-enabled global's pending draft to published. Mirrors
|
|
5
|
+
* publishDraft for collections. Returns null when no global has drafts
|
|
6
|
+
* enabled so the plugin entry can skip registration.
|
|
7
|
+
*/
|
|
8
|
+
export declare function createPublishGlobalDraftTool(draftGlobals: Set<string>): {
|
|
9
|
+
name: string;
|
|
10
|
+
routing: {
|
|
11
|
+
readonly kind: "global";
|
|
12
|
+
readonly action: "update";
|
|
13
|
+
};
|
|
14
|
+
description: string;
|
|
15
|
+
parameters: {
|
|
16
|
+
slug: z.ZodEnum<[string, ...string[]]>;
|
|
17
|
+
locale: z.ZodOptional<z.ZodString>;
|
|
18
|
+
};
|
|
19
|
+
handler: (args: Record<string, unknown>, req: PayloadRequest, _extra: unknown) => Promise<import("./_helpers").McpTextResponse>;
|
|
20
|
+
} | null;
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { errorMessage, slugEnum, snapshotPublishMarker, stampMcpContext, textResponse, verifyPublishSucceededDespiteError } from './_helpers';
|
|
3
|
+
/**
|
|
4
|
+
* Promote a draft-enabled global's pending draft to published. Mirrors
|
|
5
|
+
* publishDraft for collections. Returns null when no global has drafts
|
|
6
|
+
* enabled so the plugin entry can skip registration.
|
|
7
|
+
*/ export function createPublishGlobalDraftTool(draftGlobals) {
|
|
8
|
+
if (draftGlobals.size === 0) return null;
|
|
9
|
+
const slugs = [
|
|
10
|
+
...draftGlobals
|
|
11
|
+
];
|
|
12
|
+
return {
|
|
13
|
+
name: 'publishGlobalDraft',
|
|
14
|
+
routing: {
|
|
15
|
+
kind: 'global',
|
|
16
|
+
action: 'update'
|
|
17
|
+
},
|
|
18
|
+
description: 'Publish a draft-enabled global by transitioning its _status from "draft" to "published". ' + `Draft-enabled globals: ${slugs.join(', ')}`,
|
|
19
|
+
parameters: {
|
|
20
|
+
slug: slugEnum(slugs, 'global').describe(`The global slug. One of: ${slugs.join(', ')}`),
|
|
21
|
+
locale: z.string().optional().describe('Optional locale code (e.g. "en", "fr") for the publish operation.')
|
|
22
|
+
},
|
|
23
|
+
handler: async (args, req, _extra)=>{
|
|
24
|
+
const { slug, locale } = args;
|
|
25
|
+
if (!draftGlobals.has(slug)) {
|
|
26
|
+
return textResponse(`Error: Global "${slug}" does not support drafts. Draft-enabled globals: ${slugs.join(', ') || 'none'}`);
|
|
27
|
+
}
|
|
28
|
+
stampMcpContext(req);
|
|
29
|
+
// Capture pre-update marker so the recovery branch below can
|
|
30
|
+
// distinguish a publish that landed despite a post-write throw from
|
|
31
|
+
// a pre-existing published version inherited from an earlier
|
|
32
|
+
// successful publish.
|
|
33
|
+
const preMarker = await snapshotPublishMarker(req, {
|
|
34
|
+
kind: 'global',
|
|
35
|
+
slug,
|
|
36
|
+
...locale ? {
|
|
37
|
+
locale
|
|
38
|
+
} : {}
|
|
39
|
+
});
|
|
40
|
+
try {
|
|
41
|
+
// `slug as never` / `locale as never`: Payload's updateGlobal /
|
|
42
|
+
// findGlobal generic narrows the slug to a TConfig-derived literal
|
|
43
|
+
// union we cannot satisfy with a runtime-supplied string.
|
|
44
|
+
await req.payload.updateGlobal({
|
|
45
|
+
slug: slug,
|
|
46
|
+
data: {
|
|
47
|
+
_status: 'published'
|
|
48
|
+
},
|
|
49
|
+
...locale ? {
|
|
50
|
+
locale: locale
|
|
51
|
+
} : {},
|
|
52
|
+
req,
|
|
53
|
+
overrideAccess: false,
|
|
54
|
+
user: req.user
|
|
55
|
+
});
|
|
56
|
+
return textResponse(`Successfully published global "${slug}".`);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
// Mirror publishDraft's recovery (see publish-draft.ts for the
|
|
59
|
+
// longer note on the post-write validator throw). The shared
|
|
60
|
+
// helper enforces the strictly-newer `updatedAt` check, so a
|
|
61
|
+
// pre-existing published row from an earlier publish cannot mask
|
|
62
|
+
// a real failure of the current attempt.
|
|
63
|
+
const liveGlobal = await verifyPublishSucceededDespiteError(req, {
|
|
64
|
+
kind: 'global',
|
|
65
|
+
slug,
|
|
66
|
+
...locale ? {
|
|
67
|
+
locale
|
|
68
|
+
} : {}
|
|
69
|
+
}, preMarker);
|
|
70
|
+
if (liveGlobal) {
|
|
71
|
+
return textResponse(`[publishGlobalDraft:published_with_warning] ` + `Published global "${slug}" — but Payload reported a post-write validation error: ` + `${errorMessage(err)}. The global is live; the error did not roll back the ` + `published version.`);
|
|
72
|
+
}
|
|
73
|
+
return textResponse(`Error publishing global "${slug}": ${errorMessage(err)}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
//# sourceMappingURL=publish-global-draft.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/tools/publish-global-draft.ts"],"sourcesContent":["import { z } from 'zod'\r\nimport type { PayloadRequest } from 'payload'\r\nimport {\r\n errorMessage,\r\n slugEnum,\r\n snapshotPublishMarker,\r\n stampMcpContext,\r\n textResponse,\r\n verifyPublishSucceededDespiteError,\r\n} from './_helpers'\r\n\r\n/**\r\n * Promote a draft-enabled global's pending draft to published. Mirrors\r\n * publishDraft for collections. Returns null when no global has drafts\r\n * enabled so the plugin entry can skip registration.\r\n */\r\nexport function createPublishGlobalDraftTool(draftGlobals: Set<string>) {\r\n if (draftGlobals.size === 0) return null\r\n\r\n const slugs = [...draftGlobals]\r\n return {\r\n name: 'publishGlobalDraft',\r\n routing: { kind: 'global', action: 'update' } as const,\r\n description:\r\n 'Publish a draft-enabled global by transitioning its _status from \"draft\" to \"published\". ' +\r\n `Draft-enabled globals: ${slugs.join(', ')}`,\r\n parameters: {\r\n slug: slugEnum(slugs, 'global').describe(`The global slug. One of: ${slugs.join(', ')}`),\r\n locale: z\r\n .string()\r\n .optional()\r\n .describe('Optional locale code (e.g. \"en\", \"fr\") for the publish operation.'),\r\n },\r\n handler: async (args: Record<string, unknown>, req: PayloadRequest, _extra: unknown) => {\r\n const { slug, locale } = args as { slug: string; locale?: string }\r\n\r\n if (!draftGlobals.has(slug)) {\r\n return textResponse(\r\n `Error: Global \"${slug}\" does not support drafts. Draft-enabled globals: ${slugs.join(', ') || 'none'}`,\r\n )\r\n }\r\n\r\n stampMcpContext(req)\r\n\r\n // Capture pre-update marker so the recovery branch below can\r\n // distinguish a publish that landed despite a post-write throw from\r\n // a pre-existing published version inherited from an earlier\r\n // successful publish.\r\n const preMarker = await snapshotPublishMarker(req, {\r\n kind: 'global',\r\n slug,\r\n ...(locale ? { locale } : {}),\r\n })\r\n\r\n try {\r\n // `slug as never` / `locale as never`: Payload's updateGlobal /\r\n // findGlobal generic narrows the slug to a TConfig-derived literal\r\n // union we cannot satisfy with a runtime-supplied string.\r\n await req.payload.updateGlobal({\r\n slug: slug as never,\r\n data: { _status: 'published' } as never,\r\n ...(locale ? { locale: locale as never } : {}),\r\n req,\r\n overrideAccess: false,\r\n user: req.user,\r\n })\r\n return textResponse(`Successfully published global \"${slug}\".`)\r\n } catch (err) {\r\n // Mirror publishDraft's recovery (see publish-draft.ts for the\r\n // longer note on the post-write validator throw). The shared\r\n // helper enforces the strictly-newer `updatedAt` check, so a\r\n // pre-existing published row from an earlier publish cannot mask\r\n // a real failure of the current attempt.\r\n const liveGlobal = await verifyPublishSucceededDespiteError(\r\n req,\r\n { kind: 'global', slug, ...(locale ? { locale } : {}) },\r\n preMarker,\r\n )\r\n if (liveGlobal) {\r\n return textResponse(\r\n `[publishGlobalDraft:published_with_warning] ` +\r\n `Published global \"${slug}\" — but Payload reported a post-write validation error: ` +\r\n `${errorMessage(err)}. The global is live; the error did not roll back the ` +\r\n `published version.`,\r\n )\r\n }\r\n return textResponse(`Error publishing global \"${slug}\": ${errorMessage(err)}`)\r\n }\r\n },\r\n }\r\n}\r\n"],"names":["z","errorMessage","slugEnum","snapshotPublishMarker","stampMcpContext","textResponse","verifyPublishSucceededDespiteError","createPublishGlobalDraftTool","draftGlobals","size","slugs","name","routing","kind","action","description","join","parameters","slug","describe","locale","string","optional","handler","args","req","_extra","has","preMarker","payload","updateGlobal","data","_status","overrideAccess","user","err","liveGlobal"],"mappings":"AAAA,SAASA,CAAC,QAAQ,MAAK;AAEvB,SACEC,YAAY,EACZC,QAAQ,EACRC,qBAAqB,EACrBC,eAAe,EACfC,YAAY,EACZC,kCAAkC,QAC7B,aAAY;AAEnB;;;;CAIC,GACD,OAAO,SAASC,6BAA6BC,YAAyB;IACpE,IAAIA,aAAaC,IAAI,KAAK,GAAG,OAAO;IAEpC,MAAMC,QAAQ;WAAIF;KAAa;IAC/B,OAAO;QACLG,MAAM;QACNC,SAAS;YAAEC,MAAM;YAAUC,QAAQ;QAAS;QAC5CC,aACE,8FACA,CAAC,uBAAuB,EAAEL,MAAMM,IAAI,CAAC,OAAO;QAC9CC,YAAY;YACVC,MAAMhB,SAASQ,OAAO,UAAUS,QAAQ,CAAC,CAAC,yBAAyB,EAAET,MAAMM,IAAI,CAAC,OAAO;YACvFI,QAAQpB,EACLqB,MAAM,GACNC,QAAQ,GACRH,QAAQ,CAAC;QACd;QACAI,SAAS,OAAOC,MAA+BC,KAAqBC;YAClE,MAAM,EAAER,IAAI,EAAEE,MAAM,EAAE,GAAGI;YAEzB,IAAI,CAAChB,aAAamB,GAAG,CAACT,OAAO;gBAC3B,OAAOb,aACL,CAAC,eAAe,EAAEa,KAAK,kDAAkD,EAAER,MAAMM,IAAI,CAAC,SAAS,QAAQ;YAE3G;YAEAZ,gBAAgBqB;YAEhB,6DAA6D;YAC7D,oEAAoE;YACpE,6DAA6D;YAC7D,sBAAsB;YACtB,MAAMG,YAAY,MAAMzB,sBAAsBsB,KAAK;gBACjDZ,MAAM;gBACNK;gBACA,GAAIE,SAAS;oBAAEA;gBAAO,IAAI,CAAC,CAAC;YAC9B;YAEA,IAAI;gBACF,gEAAgE;gBAChE,mEAAmE;gBACnE,0DAA0D;gBAC1D,MAAMK,IAAII,OAAO,CAACC,YAAY,CAAC;oBAC7BZ,MAAMA;oBACNa,MAAM;wBAAEC,SAAS;oBAAY;oBAC7B,GAAIZ,SAAS;wBAAEA,QAAQA;oBAAgB,IAAI,CAAC,CAAC;oBAC7CK;oBACAQ,gBAAgB;oBAChBC,MAAMT,IAAIS,IAAI;gBAChB;gBACA,OAAO7B,aAAa,CAAC,+BAA+B,EAAEa,KAAK,EAAE,CAAC;YAChE,EAAE,OAAOiB,KAAK;gBACZ,+DAA+D;gBAC/D,6DAA6D;gBAC7D,6DAA6D;gBAC7D,iEAAiE;gBACjE,yCAAyC;gBACzC,MAAMC,aAAa,MAAM9B,mCACvBmB,KACA;oBAAEZ,MAAM;oBAAUK;oBAAM,GAAIE,SAAS;wBAAEA;oBAAO,IAAI,CAAC,CAAC;gBAAE,GACtDQ;gBAEF,IAAIQ,YAAY;oBACd,OAAO/B,aACL,CAAC,4CAA4C,CAAC,GAC5C,CAAC,kBAAkB,EAAEa,KAAK,wDAAwD,CAAC,GACnF,GAAGjB,aAAakC,KAAK,sDAAsD,CAAC,GAC5E,CAAC,kBAAkB,CAAC;gBAE1B;gBACA,OAAO9B,aAAa,CAAC,yBAAyB,EAAEa,KAAK,GAAG,EAAEjB,aAAakC,MAAM;YAC/E;QACF;IACF;AACF"}
|
|
@@ -2,6 +2,10 @@ import { z } from 'zod';
|
|
|
2
2
|
import type { PayloadRequest } from 'payload';
|
|
3
3
|
export declare function createResolveReferenceTool(searchableCollections: Map<string, string[]>): {
|
|
4
4
|
name: string;
|
|
5
|
+
routing: {
|
|
6
|
+
readonly kind: "account";
|
|
7
|
+
readonly action: "read";
|
|
8
|
+
};
|
|
5
9
|
description: string;
|
|
6
10
|
parameters: z.ZodObject<{
|
|
7
11
|
query: z.ZodString;
|
|
@@ -13,8 +17,5 @@ export declare function createResolveReferenceTool(searchableCollections: Map<st
|
|
|
13
17
|
query: string;
|
|
14
18
|
collection?: string | undefined;
|
|
15
19
|
}>;
|
|
16
|
-
handler: (args:
|
|
17
|
-
query: string;
|
|
18
|
-
collection?: string;
|
|
19
|
-
}, req: PayloadRequest) => Promise<import("./_helpers").McpTextResponse>;
|
|
20
|
+
handler: (args: Record<string, unknown>, req: PayloadRequest) => Promise<import("./_helpers").McpTextResponse>;
|
|
20
21
|
};
|
|
@@ -3,6 +3,10 @@ import { jsonResponse, stampMcpContext } from './_helpers';
|
|
|
3
3
|
export function createResolveReferenceTool(searchableCollections) {
|
|
4
4
|
return {
|
|
5
5
|
name: 'resolveReference',
|
|
6
|
+
routing: {
|
|
7
|
+
kind: 'account',
|
|
8
|
+
action: 'read'
|
|
9
|
+
},
|
|
6
10
|
description: 'Search for documents across collections by name, title, or slug. ' + 'Returns ranked candidates with IDs for use in relationship fields. ' + 'Optionally filter to a specific collection.',
|
|
7
11
|
parameters: z.object({
|
|
8
12
|
query: z.string().describe('Search term to match against name, title, or slug fields'),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/tools/resolve-reference.ts"],"sourcesContent":["import { z } from 'zod'\nimport type { PayloadRequest } from 'payload'\nimport { jsonResponse, stampMcpContext } from './_helpers'\n\ninterface MatchCandidate {\n collection: string\n id: string | number\n displayName: string\n matchedField: string\n matchType: 'exact-slug' | 'exact' | 'partial'\n}\n\nexport function createResolveReferenceTool(\n searchableCollections: Map<string, string[]>,\n) {\n return {\n name: 'resolveReference',\n description:\n 'Search for documents across collections by name, title, or slug. ' +\n 'Returns ranked candidates with IDs for use in relationship fields. ' +\n 'Optionally filter to a specific collection.',\n parameters: z.object({\n query: z.string().describe('Search term to match against name, title, or slug fields'),\n collection: z\n .string()\n .optional()\n .describe('Optional collection slug to restrict search to a single collection'),\n }),\n handler: async (args:
|
|
1
|
+
{"version":3,"sources":["../../src/tools/resolve-reference.ts"],"sourcesContent":["import { z } from 'zod'\r\nimport type { PayloadRequest } from 'payload'\r\nimport { jsonResponse, stampMcpContext } from './_helpers'\r\n\r\ninterface MatchCandidate {\r\n collection: string\r\n id: string | number\r\n displayName: string\r\n matchedField: string\r\n matchType: 'exact-slug' | 'exact' | 'partial'\r\n}\r\n\r\nexport function createResolveReferenceTool(\r\n searchableCollections: Map<string, string[]>,\r\n) {\r\n return {\r\n name: 'resolveReference',\r\n routing: { kind: 'account', action: 'read' } as const,\r\n description:\r\n 'Search for documents across collections by name, title, or slug. ' +\r\n 'Returns ranked candidates with IDs for use in relationship fields. ' +\r\n 'Optionally filter to a specific collection.',\r\n parameters: z.object({\r\n query: z.string().describe('Search term to match against name, title, or slug fields'),\r\n collection: z\r\n .string()\r\n .optional()\r\n .describe('Optional collection slug to restrict search to a single collection'),\r\n }),\r\n handler: async (args: Record<string, unknown>, req: PayloadRequest) => {\r\n stampMcpContext(req)\r\n\r\n const { query, collection } = args as { query: string; collection?: string }\r\n\r\n const collectionsToSearch = collection\r\n ? new Map(\r\n searchableCollections.has(collection)\r\n ? [[collection, searchableCollections.get(collection)!]]\r\n : [],\r\n )\r\n : searchableCollections\r\n\r\n if (collectionsToSearch.size === 0) {\r\n const available = Array.from(searchableCollections.keys()).join(', ')\r\n return jsonResponse({\r\n candidates: [],\r\n message: collection\r\n ? `Collection \"${collection}\" has no searchable fields or does not exist. Available searchable collections: ${available}`\r\n : 'No searchable collections found.',\r\n })\r\n }\r\n\r\n const targets = Array.from(collectionsToSearch.entries()).filter(\r\n ([, fields]) => fields.length > 0,\r\n )\r\n\r\n const settled = await Promise.allSettled(\r\n targets.map(([slug, fields]) => {\r\n const selectFields: Record<string, true> = {}\r\n for (const field of fields) selectFields[field] = true\r\n return req.payload.find({\r\n collection: slug as any,\r\n where: { or: fields.map((field) => ({ [field]: { like: query } })) },\r\n limit: 5,\r\n select: selectFields,\r\n req,\r\n overrideAccess: false,\r\n user: req.user,\r\n })\r\n }),\r\n )\r\n\r\n const allCandidates: MatchCandidate[] = []\r\n settled.forEach((outcome, i) => {\r\n if (outcome.status !== 'fulfilled') return\r\n const [slug, fields] = targets[i]\r\n for (const doc of outcome.value.docs) {\r\n allCandidates.push(...rankDocument(doc, slug, fields, query))\r\n }\r\n })\r\n\r\n allCandidates.sort(\r\n (a, b) => matchTypePriority(a.matchType) - matchTypePriority(b.matchType),\r\n )\r\n\r\n if (allCandidates.length === 0) {\r\n const searched = Array.from(collectionsToSearch.keys()).join(', ')\r\n const available = Array.from(searchableCollections.keys()).join(', ')\r\n return jsonResponse({\r\n candidates: {},\r\n message: `No results found for \"${query}\" in: ${searched}. Try a different spelling or search term. All searchable collections: ${available}`,\r\n })\r\n }\r\n\r\n const grouped: Record<string, Omit<MatchCandidate, 'collection'>[]> = {}\r\n for (const candidate of allCandidates) {\r\n const { collection: col, ...rest } = candidate\r\n if (!grouped[col]) grouped[col] = []\r\n grouped[col].push(rest)\r\n }\r\n\r\n return jsonResponse({\r\n candidates: grouped,\r\n total: allCandidates.length,\r\n })\r\n },\r\n }\r\n}\r\n\r\nfunction rankDocument(\r\n doc: Record<string, any>,\r\n collection: string,\r\n fields: string[],\r\n query: string,\r\n): MatchCandidate[] {\r\n const queryLower = query.toLowerCase()\r\n let bestMatch: MatchCandidate | null = null\r\n\r\n for (const field of fields) {\r\n const value = doc[field]\r\n if (typeof value !== 'string') continue\r\n\r\n const valueLower = value.toLowerCase()\r\n let matchType: MatchCandidate['matchType']\r\n\r\n if (field === 'slug' && valueLower === queryLower) {\r\n matchType = 'exact-slug'\r\n } else if (valueLower === queryLower) {\r\n matchType = 'exact'\r\n } else {\r\n matchType = 'partial'\r\n }\r\n\r\n if (\r\n !bestMatch ||\r\n matchTypePriority(matchType) < matchTypePriority(bestMatch.matchType)\r\n ) {\r\n bestMatch = {\r\n collection,\r\n id: doc.id,\r\n displayName: getDisplayName(doc, fields),\r\n matchedField: field,\r\n matchType,\r\n }\r\n }\r\n }\r\n\r\n return bestMatch ? [bestMatch] : []\r\n}\r\n\r\nfunction matchTypePriority(type: MatchCandidate['matchType']): number {\r\n switch (type) {\r\n case 'exact-slug':\r\n return 0\r\n case 'exact':\r\n return 1\r\n case 'partial':\r\n return 2\r\n }\r\n}\r\n\r\nfunction getDisplayName(doc: Record<string, any>, fields: string[]): string {\r\n for (const preferred of ['name', 'title', 'slug']) {\r\n if (fields.includes(preferred) && typeof doc[preferred] === 'string') {\r\n return doc[preferred]\r\n }\r\n }\r\n return String(doc.id)\r\n}\r\n"],"names":["z","jsonResponse","stampMcpContext","createResolveReferenceTool","searchableCollections","name","routing","kind","action","description","parameters","object","query","string","describe","collection","optional","handler","args","req","collectionsToSearch","Map","has","get","size","available","Array","from","keys","join","candidates","message","targets","entries","filter","fields","length","settled","Promise","allSettled","map","slug","selectFields","field","payload","find","where","or","like","limit","select","overrideAccess","user","allCandidates","forEach","outcome","i","status","doc","value","docs","push","rankDocument","sort","a","b","matchTypePriority","matchType","searched","grouped","candidate","col","rest","total","queryLower","toLowerCase","bestMatch","valueLower","id","displayName","getDisplayName","matchedField","type","preferred","includes","String"],"mappings":"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"}
|
|
@@ -18,15 +18,15 @@ import type { RelationshipEdge } from '../types';
|
|
|
18
18
|
*/
|
|
19
19
|
export declare function createSafeDeleteTool(relationships: RelationshipEdge[]): {
|
|
20
20
|
name: string;
|
|
21
|
+
routing: {
|
|
22
|
+
readonly kind: "collection";
|
|
23
|
+
readonly action: "delete";
|
|
24
|
+
};
|
|
21
25
|
description: string;
|
|
22
26
|
parameters: {
|
|
23
27
|
collection: z.ZodString;
|
|
24
28
|
documentId: z.ZodString;
|
|
25
29
|
confirm: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
|
|
26
30
|
};
|
|
27
|
-
handler: (
|
|
28
|
-
collection: string;
|
|
29
|
-
documentId: string;
|
|
30
|
-
confirm?: boolean;
|
|
31
|
-
}, req: PayloadRequest, _extra: unknown) => Promise<import("./_helpers").McpTextResponse>;
|
|
31
|
+
handler: (rawArgs: Record<string, unknown>, req: PayloadRequest, _extra: unknown) => Promise<import("./_helpers").McpTextResponse>;
|
|
32
32
|
};
|
|
@@ -1,20 +1,20 @@
|
|
|
1
1
|
import { z } from 'zod';
|
|
2
2
|
import { errorMessage, jsonResponse, stampMcpContext, textResponse } from './_helpers';
|
|
3
3
|
const SAMPLE_LIMIT = 5;
|
|
4
|
-
/**
|
|
5
|
-
* safeDelete wraps the official delete operation with a relationship-graph
|
|
6
|
-
* pre-check.
|
|
7
|
-
*
|
|
8
|
-
* Workflow:
|
|
9
|
-
* 1. Use the introspected relationshipGraph to find every (collection, field)
|
|
10
|
-
* pair that points TO the target collection.
|
|
11
|
-
* 2. For each, query for documents that reference the target ID. Aggregate
|
|
12
|
-
* counts and a small sample of inbound document IDs.
|
|
13
|
-
* 3. If any inbound references exist and `confirm` is false, refuse and
|
|
14
|
-
* return the impact summary so the caller can decide.
|
|
15
|
-
* 4. Otherwise, perform the delete.
|
|
16
|
-
*
|
|
17
|
-
* Excludes built-in Payload bookkeeping collections from the inbound search.
|
|
4
|
+
/**
|
|
5
|
+
* safeDelete wraps the official delete operation with a relationship-graph
|
|
6
|
+
* pre-check.
|
|
7
|
+
*
|
|
8
|
+
* Workflow:
|
|
9
|
+
* 1. Use the introspected relationshipGraph to find every (collection, field)
|
|
10
|
+
* pair that points TO the target collection.
|
|
11
|
+
* 2. For each, query for documents that reference the target ID. Aggregate
|
|
12
|
+
* counts and a small sample of inbound document IDs.
|
|
13
|
+
* 3. If any inbound references exist and `confirm` is false, refuse and
|
|
14
|
+
* return the impact summary so the caller can decide.
|
|
15
|
+
* 4. Otherwise, perform the delete.
|
|
16
|
+
*
|
|
17
|
+
* Excludes built-in Payload bookkeeping collections from the inbound search.
|
|
18
18
|
*/ export function createSafeDeleteTool(relationships) {
|
|
19
19
|
// reverseIndex: target collection slug → edges that reference it
|
|
20
20
|
const reverseIndex = new Map();
|
|
@@ -33,13 +33,18 @@ const SAMPLE_LIMIT = 5;
|
|
|
33
33
|
}
|
|
34
34
|
return {
|
|
35
35
|
name: 'safeDelete',
|
|
36
|
+
routing: {
|
|
37
|
+
kind: 'collection',
|
|
38
|
+
action: 'delete'
|
|
39
|
+
},
|
|
36
40
|
description: 'Delete a document only after checking for inbound relationships. ' + 'If other documents reference the target, the delete is refused unless `confirm` is true. ' + 'Use this in preference to the raw delete tools when removing entities that other content might depend on ' + '(authors, categories, media, locations, etc.). Returns the impact summary either way.',
|
|
37
41
|
parameters: {
|
|
38
42
|
collection: z.string().describe('Slug of the collection containing the document to delete'),
|
|
39
43
|
documentId: z.string().describe('ID of the document to delete'),
|
|
40
44
|
confirm: z.boolean().optional().default(false).describe('If false (default), refuse to delete when inbound references exist and return the impact summary. ' + 'Pass true to delete anyway after reviewing the impact.')
|
|
41
45
|
},
|
|
42
|
-
handler: async (
|
|
46
|
+
handler: async (rawArgs, req, _extra)=>{
|
|
47
|
+
const args = rawArgs;
|
|
43
48
|
const { collection, documentId, confirm = false } = args;
|
|
44
49
|
stampMcpContext(req);
|
|
45
50
|
const inboundEdges = (reverseIndex.get(collection) ?? []).filter((edge)=>!edge.fromCollection.startsWith('payload-'));
|