payload-mcp-toolkit 0.2.0 → 0.3.0
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 +59 -42
- package/dist/__tests__/introspection.test.js +141 -46
- package/dist/__tests__/introspection.test.js.map +1 -1
- package/dist/draft-workflow.d.ts +24 -19
- package/dist/draft-workflow.js +78 -31
- package/dist/draft-workflow.js.map +1 -1
- package/dist/index.d.ts +10 -15
- package/dist/index.js +43 -70
- package/dist/index.js.map +1 -1
- package/dist/introspection.d.ts +14 -7
- package/dist/introspection.js +92 -68
- package/dist/introspection.js.map +1 -1
- package/dist/prompts.d.ts +4 -4
- package/dist/prompts.js +44 -35
- package/dist/prompts.js.map +1 -1
- package/dist/resources.d.ts +9 -4
- package/dist/resources.js +44 -52
- package/dist/resources.js.map +1 -1
- package/dist/tools/patch-layout.d.ts +15 -79
- package/dist/tools/patch-layout.js +140 -48
- package/dist/tools/patch-layout.js.map +1 -1
- package/dist/types.d.ts +82 -53
- package/dist/types.js +6 -1
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/dist/tools/compose-helpers.d.ts +0 -117
- package/dist/tools/compose-helpers.js +0 -236
- package/dist/tools/compose-helpers.js.map +0 -1
- package/dist/tools/compose-layout.d.ts +0 -139
- package/dist/tools/compose-layout.js +0 -61
- package/dist/tools/compose-layout.js.map +0 -1
package/dist/draft-workflow.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Determines the draft behavior for a collection
|
|
2
|
+
* Determines the draft behavior for a collection.
|
|
3
3
|
*
|
|
4
|
-
* -
|
|
5
|
-
* -
|
|
6
|
-
* -
|
|
4
|
+
* - No drafts configured → 'publish' (raw update allowed; no draft concept)
|
|
5
|
+
* - Drafts configured + override given → use override
|
|
6
|
+
* - Drafts configured + no override → 'always-draft' (raw update locked)
|
|
7
7
|
*/ export function getDraftBehavior(collection, options) {
|
|
8
8
|
const hasDrafts = typeof collection.versions === 'object' && collection.versions !== null && 'drafts' in collection.versions && Boolean(collection.versions.drafts);
|
|
9
9
|
if (!hasDrafts) return 'publish';
|
|
@@ -12,29 +12,70 @@
|
|
|
12
12
|
return 'always-draft';
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
15
|
+
* Build a preview URL for a draft document by delegating to the collection's
|
|
16
|
+
* own configured preview URL function. Tries `admin.livePreview.url` first
|
|
17
|
+
* (the modern API), then `admin.preview` (the older `GeneratePreviewURL`).
|
|
16
18
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*/ function
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
19
|
+
* If neither is configured, or the function returns null/undefined/empty,
|
|
20
|
+
* returns null and the override response will skip preview injection.
|
|
21
|
+
*/ async function resolvePreviewUrl(collection, doc, req, siteUrl) {
|
|
22
|
+
const admin = collection.admin ?? {};
|
|
23
|
+
const locale = req.locale ?? 'en';
|
|
24
|
+
let raw;
|
|
25
|
+
const livePreviewUrl = admin.livePreview?.url;
|
|
26
|
+
if (typeof livePreviewUrl === 'function') {
|
|
27
|
+
try {
|
|
28
|
+
raw = await livePreviewUrl({
|
|
29
|
+
data: doc,
|
|
30
|
+
locale: {
|
|
31
|
+
code: locale,
|
|
32
|
+
label: locale
|
|
33
|
+
},
|
|
34
|
+
req,
|
|
35
|
+
payload: req.payload,
|
|
36
|
+
collectionConfig: collection
|
|
37
|
+
});
|
|
38
|
+
} catch {
|
|
39
|
+
raw = null;
|
|
40
|
+
}
|
|
41
|
+
} else if (typeof livePreviewUrl === 'string') {
|
|
42
|
+
raw = livePreviewUrl;
|
|
43
|
+
}
|
|
44
|
+
if (!raw && typeof admin.preview === 'function') {
|
|
45
|
+
try {
|
|
46
|
+
raw = await admin.preview(doc, {
|
|
47
|
+
locale,
|
|
48
|
+
req,
|
|
49
|
+
token: null
|
|
50
|
+
});
|
|
51
|
+
} catch {
|
|
52
|
+
raw = null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (!raw || typeof raw !== 'string') return null;
|
|
56
|
+
if (raw.startsWith('http://') || raw.startsWith('https://')) {
|
|
57
|
+
return raw;
|
|
58
|
+
}
|
|
59
|
+
if (!siteUrl) return null;
|
|
60
|
+
const base = siteUrl.endsWith('/') ? siteUrl.slice(0, -1) : siteUrl;
|
|
61
|
+
const path = raw.startsWith('/') ? raw : `/${raw}`;
|
|
62
|
+
return `${base}${path}`;
|
|
30
63
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* When a document has `_status === 'draft'`, appends a preview URL to the response.
|
|
34
|
-
*/ function createOverrideResponse(collectionSlug, siteUrl, previewSecret, previewPaths) {
|
|
35
|
-
return (response, doc)=>{
|
|
64
|
+
function createOverrideResponse(collection, siteUrl) {
|
|
65
|
+
return async (response, doc, req)=>{
|
|
36
66
|
if (doc._status !== 'draft') return response;
|
|
37
|
-
const previewUrl =
|
|
67
|
+
const previewUrl = await resolvePreviewUrl(collection, doc, req, siteUrl);
|
|
68
|
+
if (!previewUrl) {
|
|
69
|
+
return {
|
|
70
|
+
content: [
|
|
71
|
+
...response.content,
|
|
72
|
+
{
|
|
73
|
+
type: 'text',
|
|
74
|
+
text: '\n📋 This document is a draft. Use the admin panel to preview it.'
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
};
|
|
78
|
+
}
|
|
38
79
|
return {
|
|
39
80
|
content: [
|
|
40
81
|
...response.content,
|
|
@@ -49,22 +90,28 @@
|
|
|
49
90
|
/**
|
|
50
91
|
* Generates the mcpCollections config object for the official mcpPlugin.
|
|
51
92
|
*
|
|
52
|
-
* For each collection:
|
|
93
|
+
* For each non-excluded collection:
|
|
53
94
|
* - Determines enabled CRUD operations based on draft behavior
|
|
54
|
-
* - For 'always-draft' collections: disables raw `update` to force clients
|
|
55
|
-
*
|
|
95
|
+
* - For 'always-draft' collections: disables raw `update` to force clients
|
|
96
|
+
* through publishDraft / patchLayout / updateDocument (which preserve
|
|
97
|
+
* draft semantics)
|
|
98
|
+
* - For draft collections: attaches an `overrideResponse` that appends a
|
|
99
|
+
* preview URL — sourced from the collection's own livePreview/preview
|
|
100
|
+
* function — to draft documents. Falls back to a generic admin-panel
|
|
101
|
+
* message when no preview function is configured.
|
|
56
102
|
*
|
|
57
|
-
* @returns
|
|
103
|
+
* @returns Map of slug → MCP collection config, plus the set of draft slugs
|
|
58
104
|
*/ export function generateMcpCollectionConfigs(collections, options) {
|
|
59
105
|
const mcpCollections = {};
|
|
60
106
|
const draftCollections = new Set();
|
|
61
107
|
const excludeSlugs = new Set([
|
|
62
|
-
'users',
|
|
63
108
|
'payload-mcp-api-keys',
|
|
64
109
|
...options.excludeCollections ?? []
|
|
65
110
|
]);
|
|
66
111
|
for (const collection of collections){
|
|
67
112
|
if (excludeSlugs.has(collection.slug)) continue;
|
|
113
|
+
// Auth-enabled collections are users — never expose them via MCP.
|
|
114
|
+
if (collection.auth) continue;
|
|
68
115
|
const behavior = getDraftBehavior(collection, options);
|
|
69
116
|
if (behavior !== 'publish') {
|
|
70
117
|
draftCollections.add(collection.slug);
|
|
@@ -79,8 +126,8 @@
|
|
|
79
126
|
description: `Manage ${collection.slug} content`,
|
|
80
127
|
enabled
|
|
81
128
|
};
|
|
82
|
-
if (draftCollections.has(collection.slug)) {
|
|
83
|
-
config.overrideResponse = createOverrideResponse(collection
|
|
129
|
+
if (draftCollections.has(collection.slug) && !options.previewDisabled) {
|
|
130
|
+
config.overrideResponse = createOverrideResponse(collection, options.siteUrl);
|
|
84
131
|
}
|
|
85
132
|
mcpCollections[collection.slug] = config;
|
|
86
133
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/draft-workflow.ts"],"sourcesContent":["import type { CollectionConfig, PayloadRequest } from 'payload'\
|
|
1
|
+
{"version":3,"sources":["../src/draft-workflow.ts"],"sourcesContent":["import type { CollectionConfig, PayloadRequest } from 'payload'\n\n/** MCP response shape used by overrideResponse */\ninterface McpResponse {\n content: Array<{ text: string; type: string }>\n}\n\n/** Per-collection MCP config with enabled operations and optional overrideResponse */\ninterface McpCollectionConfig {\n description: string\n enabled: {\n create: boolean\n delete: boolean\n find: boolean\n update: boolean\n }\n overrideResponse?: (\n response: McpResponse,\n doc: Record<string, unknown>,\n req: PayloadRequest,\n ) => McpResponse | Promise<McpResponse>\n}\n\ninterface GenerateOptions {\n /**\n * Optional absolute base URL prepended to relative preview paths returned\n * by the collection's own preview URL function. Resolved upstream from\n * (in order): `options.preview.siteUrl`, `incomingConfig.serverURL`,\n * `process.env.NEXT_PUBLIC_SERVER_URL`, `process.env.SITE_URL`. May be\n * undefined — relative-path returns will then be skipped.\n */\n siteUrl?: string\n /** Per-collection draft behavior overrides */\n draftBehavior?: Record<string, 'always-draft' | 'always-publish'>\n /** Collection slugs to exclude from MCP */\n excludeCollections?: string[]\n /** Disable preview URL injection entirely */\n previewDisabled?: boolean\n}\n\n/**\n * Determines the draft behavior for a collection.\n *\n * - No drafts configured → 'publish' (raw update allowed; no draft concept)\n * - Drafts configured + override given → use override\n * - Drafts configured + no override → 'always-draft' (raw update locked)\n */\nexport function getDraftBehavior(\n collection: CollectionConfig,\n options?: { draftBehavior?: Record<string, 'always-draft' | 'always-publish'> },\n): 'always-draft' | 'always-publish' | 'publish' {\n const hasDrafts =\n typeof collection.versions === 'object' &&\n collection.versions !== null &&\n 'drafts' in collection.versions &&\n Boolean(collection.versions.drafts)\n\n if (!hasDrafts) return 'publish'\n\n const override = options?.draftBehavior?.[collection.slug]\n if (override) return override\n\n return 'always-draft'\n}\n\n/**\n * Build a preview URL for a draft document by delegating to the collection's\n * own configured preview URL function. Tries `admin.livePreview.url` first\n * (the modern API), then `admin.preview` (the older `GeneratePreviewURL`).\n *\n * If neither is configured, or the function returns null/undefined/empty,\n * returns null and the override response will skip preview injection.\n */\nasync function resolvePreviewUrl(\n collection: CollectionConfig,\n doc: Record<string, unknown>,\n req: PayloadRequest,\n siteUrl: string | undefined,\n): Promise<string | null> {\n const admin = (collection.admin ?? {}) as Record<string, any>\n const locale = (req as any).locale ?? 'en'\n\n let raw: string | null | undefined\n\n const livePreviewUrl = admin.livePreview?.url\n if (typeof livePreviewUrl === 'function') {\n try {\n raw = await livePreviewUrl({\n data: doc,\n locale: { code: locale, label: locale },\n req,\n payload: req.payload,\n collectionConfig: collection as any,\n })\n } catch {\n raw = null\n }\n } else if (typeof livePreviewUrl === 'string') {\n raw = livePreviewUrl\n }\n\n if (!raw && typeof admin.preview === 'function') {\n try {\n raw = await admin.preview(doc, {\n locale,\n req,\n token: null,\n })\n } catch {\n raw = null\n }\n }\n\n if (!raw || typeof raw !== 'string') return null\n\n if (raw.startsWith('http://') || raw.startsWith('https://')) {\n return raw\n }\n\n if (!siteUrl) return null\n\n const base = siteUrl.endsWith('/') ? siteUrl.slice(0, -1) : siteUrl\n const path = raw.startsWith('/') ? raw : `/${raw}`\n return `${base}${path}`\n}\n\nfunction createOverrideResponse(\n collection: CollectionConfig,\n siteUrl: string | undefined,\n): McpCollectionConfig['overrideResponse'] {\n return async (response, doc, req): Promise<McpResponse> => {\n if (doc._status !== 'draft') return response\n\n const previewUrl = await resolvePreviewUrl(collection, doc, req, siteUrl)\n if (!previewUrl) {\n return {\n content: [\n ...response.content,\n {\n type: 'text',\n text: '\\n📋 This document is a draft. Use the admin panel to preview it.',\n },\n ],\n }\n }\n\n return {\n content: [\n ...response.content,\n {\n type: 'text',\n text: `\\n📋 This document is a draft. Preview it here: ${previewUrl}`,\n },\n ],\n }\n }\n}\n\n/**\n * Generates the mcpCollections config object for the official mcpPlugin.\n *\n * For each non-excluded collection:\n * - Determines enabled CRUD operations based on draft behavior\n * - For 'always-draft' collections: disables raw `update` to force clients\n * through publishDraft / patchLayout / updateDocument (which preserve\n * draft semantics)\n * - For draft collections: attaches an `overrideResponse` that appends a\n * preview URL — sourced from the collection's own livePreview/preview\n * function — to draft documents. Falls back to a generic admin-panel\n * message when no preview function is configured.\n *\n * @returns Map of slug → MCP collection config, plus the set of draft slugs\n */\nexport function generateMcpCollectionConfigs(\n collections: CollectionConfig[],\n options: GenerateOptions,\n): {\n mcpCollections: Record<string, McpCollectionConfig>\n draftCollections: Set<string>\n} {\n const mcpCollections: Record<string, McpCollectionConfig> = {}\n const draftCollections = new Set<string>()\n\n const excludeSlugs = new Set([\n 'payload-mcp-api-keys',\n ...(options.excludeCollections ?? []),\n ])\n\n for (const collection of collections) {\n if (excludeSlugs.has(collection.slug)) continue\n\n // Auth-enabled collections are users — never expose them via MCP.\n if ((collection as any).auth) continue\n\n const behavior = getDraftBehavior(collection, options)\n\n if (behavior !== 'publish') {\n draftCollections.add(collection.slug)\n }\n\n const enabled = {\n find: true,\n create: true,\n update: behavior !== 'always-draft',\n delete: true,\n }\n\n const config: McpCollectionConfig = {\n description: `Manage ${collection.slug} content`,\n enabled,\n }\n\n if (draftCollections.has(collection.slug) && !options.previewDisabled) {\n config.overrideResponse = createOverrideResponse(collection, options.siteUrl)\n }\n\n mcpCollections[collection.slug] = config\n }\n\n return { mcpCollections, draftCollections }\n}\n"],"names":["getDraftBehavior","collection","options","hasDrafts","versions","Boolean","drafts","override","draftBehavior","slug","resolvePreviewUrl","doc","req","siteUrl","admin","locale","raw","livePreviewUrl","livePreview","url","data","code","label","payload","collectionConfig","preview","token","startsWith","base","endsWith","slice","path","createOverrideResponse","response","_status","previewUrl","content","type","text","generateMcpCollectionConfigs","collections","mcpCollections","draftCollections","Set","excludeSlugs","excludeCollections","has","auth","behavior","add","enabled","find","create","update","delete","config","description","previewDisabled","overrideResponse"],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,22 +3,17 @@ import type { ContentToolkitOptions } from './types';
|
|
|
3
3
|
/**
|
|
4
4
|
* Payload MCP Toolkit
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Layered on top of the official @payloadcms/plugin-mcp. The toolkit
|
|
7
|
+
* introspects your Payload config and registers schema-aware prompts,
|
|
8
|
+
* resources, and tools so AI clients can drive the CMS without
|
|
9
|
+
* hand-built plumbing.
|
|
8
10
|
*
|
|
9
|
-
*
|
|
11
|
+
* Zero-config usage:
|
|
10
12
|
* ```ts
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
* plugins: [
|
|
14
|
-
* contentToolkitPlugin({
|
|
15
|
-
* siteUrl: process.env.SITE_URL!,
|
|
16
|
-
* previewSecret: process.env.PREVIEW_SECRET!,
|
|
17
|
-
* previewPaths: { posts: '/blog', pages: '' },
|
|
18
|
-
* draftBehavior: { pages: 'always-draft' },
|
|
19
|
-
* }),
|
|
20
|
-
* ]
|
|
13
|
+
* plugins: [contentToolkitPlugin()]
|
|
21
14
|
* ```
|
|
15
|
+
*
|
|
16
|
+
* Every option below is an optional escape hatch — see ContentToolkitOptions.
|
|
22
17
|
*/
|
|
23
|
-
export declare function contentToolkitPlugin(options
|
|
24
|
-
export type { ContentToolkitOptions, DomainPrompt,
|
|
18
|
+
export declare function contentToolkitPlugin(options?: ContentToolkitOptions): Plugin;
|
|
19
|
+
export type { ContentToolkitOptions, DomainPrompt, CollectionSchema, BlockCatalog, BlockSchema, BlockNestingMap, BlockNestingEdge, RelationshipEdge, FieldSchema, } from './types';
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { mcpPlugin } from '@payloadcms/plugin-mcp';
|
|
2
|
-
import { introspectCollections, introspectBlocks, buildRelationshipGraph } from './introspection';
|
|
2
|
+
import { introspectCollections, introspectBlocks, buildBlockNestingMap, buildRelationshipGraph } from './introspection';
|
|
3
3
|
import { generatePrompts } from './prompts';
|
|
4
4
|
import { generateResources } from './resources';
|
|
5
5
|
import { generateMcpCollectionConfigs } from './draft-workflow';
|
|
6
|
-
import { createComposeLayoutTool } from './tools/compose-layout';
|
|
7
6
|
import { createPatchLayoutTool } from './tools/patch-layout';
|
|
8
7
|
import { createPublishDraftTool } from './tools/publish-draft';
|
|
9
8
|
import { createResolveReferenceTool } from './tools/resolve-reference';
|
|
@@ -16,80 +15,50 @@ import { createListVersionsTool, createRestoreVersionTool } from './tools/versio
|
|
|
16
15
|
/**
|
|
17
16
|
* Payload MCP Toolkit
|
|
18
17
|
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
18
|
+
* Layered on top of the official @payloadcms/plugin-mcp. The toolkit
|
|
19
|
+
* introspects your Payload config and registers schema-aware prompts,
|
|
20
|
+
* resources, and tools so AI clients can drive the CMS without
|
|
21
|
+
* hand-built plumbing.
|
|
21
22
|
*
|
|
22
|
-
*
|
|
23
|
+
* Zero-config usage:
|
|
23
24
|
* ```ts
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* plugins: [
|
|
27
|
-
* contentToolkitPlugin({
|
|
28
|
-
* siteUrl: process.env.SITE_URL!,
|
|
29
|
-
* previewSecret: process.env.PREVIEW_SECRET!,
|
|
30
|
-
* previewPaths: { posts: '/blog', pages: '' },
|
|
31
|
-
* draftBehavior: { pages: 'always-draft' },
|
|
32
|
-
* }),
|
|
33
|
-
* ]
|
|
25
|
+
* plugins: [contentToolkitPlugin()]
|
|
34
26
|
* ```
|
|
35
|
-
|
|
27
|
+
*
|
|
28
|
+
* Every option below is an optional escape hatch — see ContentToolkitOptions.
|
|
29
|
+
*/ export function contentToolkitPlugin(options = {}) {
|
|
36
30
|
return (incomingConfig)=>{
|
|
37
31
|
const collections = incomingConfig.collections ?? [];
|
|
38
32
|
const allBlocks = incomingConfig.blocks ?? [];
|
|
39
|
-
//
|
|
40
|
-
//
|
|
41
|
-
// Preferred: `options.sectionBlockSlugs` — explicit, unambiguous.
|
|
42
|
-
// Fallback heuristic: blocks containing a nested `blocks`-type field are
|
|
43
|
-
// sections, all others are leaves. The heuristic mis-classifies "fixed"
|
|
44
|
-
// sections (no nested blocks but their own standalone fields), so prefer
|
|
45
|
-
// the explicit option whenever the schema has any fixed sections.
|
|
46
|
-
const sectionBlocks = [];
|
|
47
|
-
const leafBlocks = [];
|
|
48
|
-
if (options.sectionBlockSlugs && options.sectionBlockSlugs.length > 0) {
|
|
49
|
-
const sectionSlugs = new Set(options.sectionBlockSlugs);
|
|
50
|
-
for (const block of allBlocks){
|
|
51
|
-
if (sectionSlugs.has(block.slug)) {
|
|
52
|
-
sectionBlocks.push(block);
|
|
53
|
-
} else {
|
|
54
|
-
leafBlocks.push(block);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
} else {
|
|
58
|
-
for (const block of allBlocks){
|
|
59
|
-
const hasBlocksField = block.fields.some((f)=>f.type === 'blocks' || f.type === 'tabs' && 'tabs' in f && f.tabs.some((tab)=>'fields' in tab && tab.fields.some((tf)=>tf.type === 'blocks')));
|
|
60
|
-
if (hasBlocksField) {
|
|
61
|
-
sectionBlocks.push(block);
|
|
62
|
-
} else {
|
|
63
|
-
leafBlocks.push(block);
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
// 1. Schema Introspection
|
|
33
|
+
// 1. Schema introspection
|
|
68
34
|
const collectionSchemas = introspectCollections(collections);
|
|
69
|
-
const blockCatalog = introspectBlocks(
|
|
35
|
+
const blockCatalog = introspectBlocks(allBlocks);
|
|
36
|
+
const blockNesting = buildBlockNestingMap(collections, allBlocks);
|
|
70
37
|
const relationships = buildRelationshipGraph(collectionSchemas);
|
|
71
|
-
// 2.
|
|
72
|
-
|
|
73
|
-
//
|
|
74
|
-
const
|
|
75
|
-
//
|
|
38
|
+
// 2. Resolve preview siteUrl from explicit option, then Payload's own
|
|
39
|
+
// serverURL, then conventional env vars. May still be undefined —
|
|
40
|
+
// that's fine; relative-path preview URLs are skipped in that case.
|
|
41
|
+
const previewSiteUrl = options.preview?.siteUrl ?? incomingConfig.serverURL ?? process.env.NEXT_PUBLIC_SERVER_URL ?? process.env.SITE_URL;
|
|
42
|
+
// 3. Generate prompts and resources
|
|
43
|
+
const prompts = generatePrompts(collectionSchemas, blockCatalog, blockNesting, relationships, options.domainPrompts);
|
|
44
|
+
const resources = generateResources(collectionSchemas, blockCatalog, blockNesting, relationships);
|
|
45
|
+
// 4. Build MCP collection configs (preview URL + draft behavior)
|
|
46
|
+
const { mcpCollections, draftCollections } = generateMcpCollectionConfigs(collections, {
|
|
47
|
+
siteUrl: previewSiteUrl,
|
|
48
|
+
draftBehavior: options.draftBehavior,
|
|
49
|
+
excludeCollections: options.exclude?.collections,
|
|
50
|
+
previewDisabled: options.preview?.disabled
|
|
51
|
+
});
|
|
52
|
+
// 5. Build the searchable-fields map for resolveReference
|
|
76
53
|
const searchableCollections = new Map();
|
|
77
54
|
for (const [slug, schema] of collectionSchemas){
|
|
78
55
|
if (schema.searchableFields.length > 0) {
|
|
79
56
|
searchableCollections.set(slug, schema.searchableFields);
|
|
80
57
|
}
|
|
81
58
|
}
|
|
82
|
-
//
|
|
83
|
-
const { mcpCollections, draftCollections } = generateMcpCollectionConfigs(collections, {
|
|
84
|
-
siteUrl: options.siteUrl,
|
|
85
|
-
previewSecret: options.previewSecret,
|
|
86
|
-
previewPaths: options.previewPaths,
|
|
87
|
-
draftBehavior: options.draftBehavior,
|
|
88
|
-
excludeCollections: options.excludeCollections
|
|
89
|
-
});
|
|
59
|
+
// 6. Tools
|
|
90
60
|
const tools = [
|
|
91
|
-
|
|
92
|
-
createPatchLayoutTool(blockCatalog, draftCollections),
|
|
61
|
+
createPatchLayoutTool(blockCatalog, blockNesting, draftCollections),
|
|
93
62
|
createPublishDraftTool(draftCollections),
|
|
94
63
|
createResolveReferenceTool(searchableCollections),
|
|
95
64
|
createSafeDeleteTool(relationships),
|
|
@@ -102,12 +71,11 @@ import { createListVersionsTool, createRestoreVersionTool } from './tools/versio
|
|
|
102
71
|
createListVersionsTool(draftCollections),
|
|
103
72
|
createRestoreVersionTool(draftCollections)
|
|
104
73
|
];
|
|
105
|
-
// schedulePublish only registers when at least one draft collection has a `publishedAt` date field
|
|
106
74
|
const schedulePublish = createSchedulePublishTool(collectionSchemas, draftCollections);
|
|
107
75
|
if (schedulePublish) tools.push(schedulePublish);
|
|
108
|
-
//
|
|
76
|
+
// 7. Globals — expose every non-excluded global with default capabilities
|
|
109
77
|
const mcpGlobals = {};
|
|
110
|
-
const excludeGlobalSlugs = new Set(options.
|
|
78
|
+
const excludeGlobalSlugs = new Set(options.exclude?.globals ?? []);
|
|
111
79
|
for (const global of incomingConfig.globals ?? []){
|
|
112
80
|
if (excludeGlobalSlugs.has(global.slug)) continue;
|
|
113
81
|
mcpGlobals[global.slug] = {
|
|
@@ -115,20 +83,25 @@ import { createListVersionsTool, createRestoreVersionTool } from './tools/versio
|
|
|
115
83
|
description: `Manage ${global.slug} global settings`
|
|
116
84
|
};
|
|
117
85
|
}
|
|
118
|
-
// Apply the official MCP plugin with our generated config
|
|
86
|
+
// 8. Apply the official MCP plugin with our generated config.
|
|
87
|
+
//
|
|
88
|
+
// userCollection: passthrough — when omitted, the official plugin falls
|
|
89
|
+
// back to `incomingConfig.admin.user`, which is already the canonical
|
|
90
|
+
// Payload way to declare your auth collection.
|
|
91
|
+
//
|
|
92
|
+
// overrideAuth: rebinds req.user from the API key's linked user so our
|
|
93
|
+
// custom tools' overrideAccess: false runs against the right identity.
|
|
94
|
+
// Safe — getDefault() throws inside the official plugin if the API key
|
|
95
|
+
// has no linked user, so settings.user is guaranteed here.
|
|
119
96
|
const withMcp = mcpPlugin({
|
|
120
97
|
collections: mcpCollections,
|
|
121
98
|
globals: mcpGlobals,
|
|
99
|
+
userCollection: options.userCollection,
|
|
122
100
|
mcp: {
|
|
123
101
|
tools: tools,
|
|
124
102
|
prompts: prompts,
|
|
125
103
|
resources: resources
|
|
126
104
|
},
|
|
127
|
-
// Set req.user from the API key's linked user so custom tools
|
|
128
|
-
// can use overrideAccess: false and relationship field validation
|
|
129
|
-
// has a valid user context for access control checks.
|
|
130
|
-
// Safe: getDefault() throws inside the official plugin if the API key
|
|
131
|
-
// has no linked user, so settings.user is guaranteed to exist here.
|
|
132
105
|
overrideAuth: async (req, getDefault)=>{
|
|
133
106
|
const settings = await getDefault();
|
|
134
107
|
req.user = settings.user;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Block, CollectionConfig, Config, Plugin } from 'payload'\nimport { mcpPlugin } from '@payloadcms/plugin-mcp'\nimport type { ContentToolkitOptions } from './types'\nimport { introspectCollections, introspectBlocks, buildRelationshipGraph } from './introspection'\nimport { generatePrompts } from './prompts'\nimport { generateResources } from './resources'\nimport { generateMcpCollectionConfigs } from './draft-workflow'\nimport { createComposeLayoutTool } from './tools/compose-layout'\nimport { createPatchLayoutTool } from './tools/patch-layout'\nimport { createPublishDraftTool } from './tools/publish-draft'\nimport { createResolveReferenceTool } from './tools/resolve-reference'\nimport { createSafeDeleteTool } from './tools/safe-delete'\nimport { createSchedulePublishTool } from './tools/schedule-publish'\nimport { createSearchContentTool } from './tools/search-content'\nimport { createUpdateDocumentTool } from './tools/update-document'\nimport { createUploadMediaTool } from './tools/upload-media'\nimport { createListVersionsTool, createRestoreVersionTool } from './tools/versions'\n\n/**\n * Payload MCP Toolkit\n *\n * A wrapper plugin that introspects the Payload config and generates\n * domain-aware MCP tools, prompts, and resources for AI content management.\n *\n * Usage in payload.config.ts:\n * ```ts\n * import { contentToolkitPlugin } from 'payload-mcp-toolkit'\n *\n * plugins: [\n * contentToolkitPlugin({\n * siteUrl: process.env.SITE_URL!,\n * previewSecret: process.env.PREVIEW_SECRET!,\n * previewPaths: { posts: '/blog', pages: '' },\n * draftBehavior: { pages: 'always-draft' },\n * }),\n * ]\n * ```\n */\nexport function contentToolkitPlugin(options: ContentToolkitOptions): Plugin {\n return (incomingConfig: Config): Config => {\n const collections = (incomingConfig.collections ?? []) as CollectionConfig[]\n const allBlocks = (incomingConfig.blocks ?? []) as Block[]\n\n // Separate section blocks from leaf blocks.\n //\n // Preferred: `options.sectionBlockSlugs` — explicit, unambiguous.\n // Fallback heuristic: blocks containing a nested `blocks`-type field are\n // sections, all others are leaves. The heuristic mis-classifies \"fixed\"\n // sections (no nested blocks but their own standalone fields), so prefer\n // the explicit option whenever the schema has any fixed sections.\n const sectionBlocks: Block[] = []\n const leafBlocks: Block[] = []\n\n if (options.sectionBlockSlugs && options.sectionBlockSlugs.length > 0) {\n const sectionSlugs = new Set(options.sectionBlockSlugs)\n for (const block of allBlocks) {\n if (sectionSlugs.has(block.slug)) {\n sectionBlocks.push(block)\n } else {\n leafBlocks.push(block)\n }\n }\n } else {\n for (const block of allBlocks) {\n const hasBlocksField = block.fields.some(\n (f) => f.type === 'blocks' ||\n (f.type === 'tabs' && 'tabs' in f && f.tabs.some(\n (tab: any) => 'fields' in tab && tab.fields.some((tf: any) => tf.type === 'blocks')\n ))\n )\n if (hasBlocksField) {\n sectionBlocks.push(block)\n } else {\n leafBlocks.push(block)\n }\n }\n }\n\n // 1. Schema Introspection\n const collectionSchemas = introspectCollections(collections)\n const blockCatalog = introspectBlocks(sectionBlocks, leafBlocks)\n const relationships = buildRelationshipGraph(collectionSchemas)\n\n // 2. Generate Prompts\n const prompts = generatePrompts(\n collectionSchemas,\n blockCatalog,\n relationships,\n options.domainPrompts,\n )\n\n // 3. Generate Resources\n const resources = generateResources(collectionSchemas, blockCatalog, relationships)\n\n // 4. Generate Tools\n const searchableCollections = new Map<string, string[]>()\n for (const [slug, schema] of collectionSchemas) {\n if (schema.searchableFields.length > 0) {\n searchableCollections.set(slug, schema.searchableFields)\n }\n }\n\n // 5. Generate MCP Collection Configs with draft workflow\n const { mcpCollections, draftCollections } = generateMcpCollectionConfigs(collections, {\n siteUrl: options.siteUrl,\n previewSecret: options.previewSecret,\n previewPaths: options.previewPaths,\n draftBehavior: options.draftBehavior,\n excludeCollections: options.excludeCollections,\n })\n\n const tools: any[] = [\n createComposeLayoutTool(blockCatalog),\n createPatchLayoutTool(blockCatalog, draftCollections),\n createPublishDraftTool(draftCollections),\n createResolveReferenceTool(searchableCollections),\n createSafeDeleteTool(relationships),\n createSearchContentTool(collectionSchemas),\n createUpdateDocumentTool(collectionSchemas, draftCollections),\n createUploadMediaTool({\n maxFileSize: options.mediaUpload?.maxFileSize,\n collectionSlug: options.mediaUpload?.collectionSlug,\n }),\n createListVersionsTool(draftCollections),\n createRestoreVersionTool(draftCollections),\n ]\n\n // schedulePublish only registers when at least one draft collection has a `publishedAt` date field\n const schedulePublish = createSchedulePublishTool(collectionSchemas, draftCollections)\n if (schedulePublish) tools.push(schedulePublish)\n\n // Build MCP global configs\n const mcpGlobals: Record<string, { enabled: boolean; description?: string }> = {}\n const excludeGlobalSlugs = new Set(options.excludeGlobals ?? [])\n\n for (const global of (incomingConfig.globals ?? []) as Array<{ slug: string }>) {\n if (excludeGlobalSlugs.has(global.slug)) continue\n mcpGlobals[global.slug] = {\n enabled: true,\n description: `Manage ${global.slug} global settings`,\n }\n }\n\n // Apply the official MCP plugin with our generated config\n const withMcp = mcpPlugin({\n collections: mcpCollections as any,\n globals: mcpGlobals as any,\n mcp: {\n tools: tools as any[],\n prompts: prompts as any[],\n resources: resources as any[],\n },\n // Set req.user from the API key's linked user so custom tools\n // can use overrideAccess: false and relationship field validation\n // has a valid user context for access control checks.\n // Safe: getDefault() throws inside the official plugin if the API key\n // has no linked user, so settings.user is guaranteed to exist here.\n overrideAuth: async (req, getDefault) => {\n const settings = await getDefault()\n req.user = (settings as any).user\n return settings\n },\n })\n\n return withMcp(incomingConfig)\n }\n}\n\nexport type {\n ContentToolkitOptions,\n DomainPrompt,\n DraftBehavior,\n CollectionSchema,\n BlockCatalog,\n SectionBlockSchema,\n LeafBlockSchema,\n RelationshipEdge,\n FieldSchema,\n} from './types'\n"],"names":["mcpPlugin","introspectCollections","introspectBlocks","buildRelationshipGraph","generatePrompts","generateResources","generateMcpCollectionConfigs","createComposeLayoutTool","createPatchLayoutTool","createPublishDraftTool","createResolveReferenceTool","createSafeDeleteTool","createSchedulePublishTool","createSearchContentTool","createUpdateDocumentTool","createUploadMediaTool","createListVersionsTool","createRestoreVersionTool","contentToolkitPlugin","options","incomingConfig","collections","allBlocks","blocks","sectionBlocks","leafBlocks","sectionBlockSlugs","length","sectionSlugs","Set","block","has","slug","push","hasBlocksField","fields","some","f","type","tabs","tab","tf","collectionSchemas","blockCatalog","relationships","prompts","domainPrompts","resources","searchableCollections","Map","schema","searchableFields","set","mcpCollections","draftCollections","siteUrl","previewSecret","previewPaths","draftBehavior","excludeCollections","tools","maxFileSize","mediaUpload","collectionSlug","schedulePublish","mcpGlobals","excludeGlobalSlugs","excludeGlobals","global","globals","enabled","description","withMcp","mcp","overrideAuth","req","getDefault","settings","user"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Block, CollectionConfig, Config, Plugin } from 'payload'\nimport { mcpPlugin } from '@payloadcms/plugin-mcp'\nimport type { ContentToolkitOptions } from './types'\nimport {\n introspectCollections,\n introspectBlocks,\n buildBlockNestingMap,\n buildRelationshipGraph,\n} from './introspection'\nimport { generatePrompts } from './prompts'\nimport { generateResources } from './resources'\nimport { generateMcpCollectionConfigs } from './draft-workflow'\nimport { createPatchLayoutTool } from './tools/patch-layout'\nimport { createPublishDraftTool } from './tools/publish-draft'\nimport { createResolveReferenceTool } from './tools/resolve-reference'\nimport { createSafeDeleteTool } from './tools/safe-delete'\nimport { createSchedulePublishTool } from './tools/schedule-publish'\nimport { createSearchContentTool } from './tools/search-content'\nimport { createUpdateDocumentTool } from './tools/update-document'\nimport { createUploadMediaTool } from './tools/upload-media'\nimport { createListVersionsTool, createRestoreVersionTool } from './tools/versions'\n\n/**\n * Payload MCP Toolkit\n *\n * Layered on top of the official @payloadcms/plugin-mcp. The toolkit\n * introspects your Payload config and registers schema-aware prompts,\n * resources, and tools so AI clients can drive the CMS without\n * hand-built plumbing.\n *\n * Zero-config usage:\n * ```ts\n * plugins: [contentToolkitPlugin()]\n * ```\n *\n * Every option below is an optional escape hatch — see ContentToolkitOptions.\n */\nexport function contentToolkitPlugin(options: ContentToolkitOptions = {}): Plugin {\n return (incomingConfig: Config): Config => {\n const collections = (incomingConfig.collections ?? []) as CollectionConfig[]\n const allBlocks = (incomingConfig.blocks ?? []) as Block[]\n\n // 1. Schema introspection\n const collectionSchemas = introspectCollections(collections)\n const blockCatalog = introspectBlocks(allBlocks)\n const blockNesting = buildBlockNestingMap(collections, allBlocks)\n const relationships = buildRelationshipGraph(collectionSchemas)\n\n // 2. Resolve preview siteUrl from explicit option, then Payload's own\n // serverURL, then conventional env vars. May still be undefined —\n // that's fine; relative-path preview URLs are skipped in that case.\n const previewSiteUrl =\n options.preview?.siteUrl ??\n incomingConfig.serverURL ??\n process.env.NEXT_PUBLIC_SERVER_URL ??\n process.env.SITE_URL\n\n // 3. Generate prompts and resources\n const prompts = generatePrompts(\n collectionSchemas,\n blockCatalog,\n blockNesting,\n relationships,\n options.domainPrompts,\n )\n const resources = generateResources(\n collectionSchemas,\n blockCatalog,\n blockNesting,\n relationships,\n )\n\n // 4. Build MCP collection configs (preview URL + draft behavior)\n const { mcpCollections, draftCollections } = generateMcpCollectionConfigs(collections, {\n siteUrl: previewSiteUrl,\n draftBehavior: options.draftBehavior,\n excludeCollections: options.exclude?.collections,\n previewDisabled: options.preview?.disabled,\n })\n\n // 5. Build the searchable-fields map for resolveReference\n const searchableCollections = new Map<string, string[]>()\n for (const [slug, schema] of collectionSchemas) {\n if (schema.searchableFields.length > 0) {\n searchableCollections.set(slug, schema.searchableFields)\n }\n }\n\n // 6. Tools\n const tools: any[] = [\n createPatchLayoutTool(blockCatalog, blockNesting, draftCollections),\n createPublishDraftTool(draftCollections),\n createResolveReferenceTool(searchableCollections),\n createSafeDeleteTool(relationships),\n createSearchContentTool(collectionSchemas),\n createUpdateDocumentTool(collectionSchemas, draftCollections),\n createUploadMediaTool({\n maxFileSize: options.mediaUpload?.maxFileSize,\n collectionSlug: options.mediaUpload?.collectionSlug,\n }),\n createListVersionsTool(draftCollections),\n createRestoreVersionTool(draftCollections),\n ]\n\n const schedulePublish = createSchedulePublishTool(collectionSchemas, draftCollections)\n if (schedulePublish) tools.push(schedulePublish)\n\n // 7. Globals — expose every non-excluded global with default capabilities\n const mcpGlobals: Record<string, { enabled: boolean; description?: string }> = {}\n const excludeGlobalSlugs = new Set(options.exclude?.globals ?? [])\n for (const global of (incomingConfig.globals ?? []) as Array<{ slug: string }>) {\n if (excludeGlobalSlugs.has(global.slug)) continue\n mcpGlobals[global.slug] = {\n enabled: true,\n description: `Manage ${global.slug} global settings`,\n }\n }\n\n // 8. Apply the official MCP plugin with our generated config.\n //\n // userCollection: passthrough — when omitted, the official plugin falls\n // back to `incomingConfig.admin.user`, which is already the canonical\n // Payload way to declare your auth collection.\n //\n // overrideAuth: rebinds req.user from the API key's linked user so our\n // custom tools' overrideAccess: false runs against the right identity.\n // Safe — getDefault() throws inside the official plugin if the API key\n // has no linked user, so settings.user is guaranteed here.\n const withMcp = mcpPlugin({\n collections: mcpCollections as any,\n globals: mcpGlobals as any,\n userCollection: options.userCollection as any,\n mcp: {\n tools: tools as any[],\n prompts: prompts as any[],\n resources: resources as any[],\n },\n overrideAuth: async (req, getDefault) => {\n const settings = await getDefault()\n req.user = (settings as any).user\n return settings\n },\n })\n\n return withMcp(incomingConfig)\n }\n}\n\nexport type {\n ContentToolkitOptions,\n DomainPrompt,\n CollectionSchema,\n BlockCatalog,\n BlockSchema,\n BlockNestingMap,\n BlockNestingEdge,\n RelationshipEdge,\n FieldSchema,\n} from './types'\n"],"names":["mcpPlugin","introspectCollections","introspectBlocks","buildBlockNestingMap","buildRelationshipGraph","generatePrompts","generateResources","generateMcpCollectionConfigs","createPatchLayoutTool","createPublishDraftTool","createResolveReferenceTool","createSafeDeleteTool","createSchedulePublishTool","createSearchContentTool","createUpdateDocumentTool","createUploadMediaTool","createListVersionsTool","createRestoreVersionTool","contentToolkitPlugin","options","incomingConfig","collections","allBlocks","blocks","collectionSchemas","blockCatalog","blockNesting","relationships","previewSiteUrl","preview","siteUrl","serverURL","process","env","NEXT_PUBLIC_SERVER_URL","SITE_URL","prompts","domainPrompts","resources","mcpCollections","draftCollections","draftBehavior","excludeCollections","exclude","previewDisabled","disabled","searchableCollections","Map","slug","schema","searchableFields","length","set","tools","maxFileSize","mediaUpload","collectionSlug","schedulePublish","push","mcpGlobals","excludeGlobalSlugs","Set","globals","global","has","enabled","description","withMcp","userCollection","mcp","overrideAuth","req","getDefault","settings","user"],"mappings":"AACA,SAASA,SAAS,QAAQ,yBAAwB;AAElD,SACEC,qBAAqB,EACrBC,gBAAgB,EAChBC,oBAAoB,EACpBC,sBAAsB,QACjB,kBAAiB;AACxB,SAASC,eAAe,QAAQ,YAAW;AAC3C,SAASC,iBAAiB,QAAQ,cAAa;AAC/C,SAASC,4BAA4B,QAAQ,mBAAkB;AAC/D,SAASC,qBAAqB,QAAQ,uBAAsB;AAC5D,SAASC,sBAAsB,QAAQ,wBAAuB;AAC9D,SAASC,0BAA0B,QAAQ,4BAA2B;AACtE,SAASC,oBAAoB,QAAQ,sBAAqB;AAC1D,SAASC,yBAAyB,QAAQ,2BAA0B;AACpE,SAASC,uBAAuB,QAAQ,yBAAwB;AAChE,SAASC,wBAAwB,QAAQ,0BAAyB;AAClE,SAASC,qBAAqB,QAAQ,uBAAsB;AAC5D,SAASC,sBAAsB,EAAEC,wBAAwB,QAAQ,mBAAkB;AAEnF;;;;;;;;;;;;;;CAcC,GACD,OAAO,SAASC,qBAAqBC,UAAiC,CAAC,CAAC;IACtE,OAAO,CAACC;QACN,MAAMC,cAAeD,eAAeC,WAAW,IAAI,EAAE;QACrD,MAAMC,YAAaF,eAAeG,MAAM,IAAI,EAAE;QAE9C,0BAA0B;QAC1B,MAAMC,oBAAoBvB,sBAAsBoB;QAChD,MAAMI,eAAevB,iBAAiBoB;QACtC,MAAMI,eAAevB,qBAAqBkB,aAAaC;QACvD,MAAMK,gBAAgBvB,uBAAuBoB;QAE7C,sEAAsE;QACtE,qEAAqE;QACrE,uEAAuE;QACvE,MAAMI,iBACJT,QAAQU,OAAO,EAAEC,WACjBV,eAAeW,SAAS,IACxBC,QAAQC,GAAG,CAACC,sBAAsB,IAClCF,QAAQC,GAAG,CAACE,QAAQ;QAEtB,oCAAoC;QACpC,MAAMC,UAAU/B,gBACdmB,mBACAC,cACAC,cACAC,eACAR,QAAQkB,aAAa;QAEvB,MAAMC,YAAYhC,kBAChBkB,mBACAC,cACAC,cACAC;QAGF,iEAAiE;QACjE,MAAM,EAAEY,cAAc,EAAEC,gBAAgB,EAAE,GAAGjC,6BAA6Bc,aAAa;YACrFS,SAASF;YACTa,eAAetB,QAAQsB,aAAa;YACpCC,oBAAoBvB,QAAQwB,OAAO,EAAEtB;YACrCuB,iBAAiBzB,QAAQU,OAAO,EAAEgB;QACpC;QAEA,0DAA0D;QAC1D,MAAMC,wBAAwB,IAAIC;QAClC,KAAK,MAAM,CAACC,MAAMC,OAAO,IAAIzB,kBAAmB;YAC9C,IAAIyB,OAAOC,gBAAgB,CAACC,MAAM,GAAG,GAAG;gBACtCL,sBAAsBM,GAAG,CAACJ,MAAMC,OAAOC,gBAAgB;YACzD;QACF;QAEA,WAAW;QACX,MAAMG,QAAe;YACnB7C,sBAAsBiB,cAAcC,cAAcc;YAClD/B,uBAAuB+B;YACvB9B,2BAA2BoC;YAC3BnC,qBAAqBgB;YACrBd,wBAAwBW;YACxBV,yBAAyBU,mBAAmBgB;YAC5CzB,sBAAsB;gBACpBuC,aAAanC,QAAQoC,WAAW,EAAED;gBAClCE,gBAAgBrC,QAAQoC,WAAW,EAAEC;YACvC;YACAxC,uBAAuBwB;YACvBvB,yBAAyBuB;SAC1B;QAED,MAAMiB,kBAAkB7C,0BAA0BY,mBAAmBgB;QACrE,IAAIiB,iBAAiBJ,MAAMK,IAAI,CAACD;QAEhC,0EAA0E;QAC1E,MAAME,aAAyE,CAAC;QAChF,MAAMC,qBAAqB,IAAIC,IAAI1C,QAAQwB,OAAO,EAAEmB,WAAW,EAAE;QACjE,KAAK,MAAMC,UAAW3C,eAAe0C,OAAO,IAAI,EAAE,CAA8B;YAC9E,IAAIF,mBAAmBI,GAAG,CAACD,OAAOf,IAAI,GAAG;YACzCW,UAAU,CAACI,OAAOf,IAAI,CAAC,GAAG;gBACxBiB,SAAS;gBACTC,aAAa,CAAC,OAAO,EAAEH,OAAOf,IAAI,CAAC,gBAAgB,CAAC;YACtD;QACF;QAEA,8DAA8D;QAC9D,EAAE;QACF,wEAAwE;QACxE,sEAAsE;QACtE,+CAA+C;QAC/C,EAAE;QACF,uEAAuE;QACvE,uEAAuE;QACvE,uEAAuE;QACvE,2DAA2D;QAC3D,MAAMmB,UAAUnE,UAAU;YACxBqB,aAAakB;YACbuB,SAASH;YACTS,gBAAgBjD,QAAQiD,cAAc;YACtCC,KAAK;gBACHhB,OAAOA;gBACPjB,SAASA;gBACTE,WAAWA;YACb;YACAgC,cAAc,OAAOC,KAAKC;gBACxB,MAAMC,WAAW,MAAMD;gBACvBD,IAAIG,IAAI,GAAG,AAACD,SAAiBC,IAAI;gBACjC,OAAOD;YACT;QACF;QAEA,OAAON,QAAQ/C;IACjB;AACF"}
|
package/dist/introspection.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { Block, CollectionConfig } from 'payload';
|
|
2
|
-
import type { BlockCatalog, CollectionSchema, RelationshipEdge } from './types';
|
|
2
|
+
import type { BlockCatalog, BlockNestingMap, CollectionSchema, RelationshipEdge } from './types';
|
|
3
3
|
/**
|
|
4
4
|
* Introspect a Payload collection config into structured metadata.
|
|
5
5
|
*/
|
|
@@ -9,14 +9,21 @@ export declare function introspectCollection(collection: CollectionConfig): Coll
|
|
|
9
9
|
*/
|
|
10
10
|
export declare function introspectCollections(collections: CollectionConfig[]): Map<string, CollectionSchema>;
|
|
11
11
|
/**
|
|
12
|
-
*
|
|
12
|
+
* Build a flat catalog of every block in the schema. Whether a block can
|
|
13
|
+
* nest other blocks is represented separately in the BlockNestingMap, not
|
|
14
|
+
* as a section/leaf classification — the AI reads both and composes
|
|
15
|
+
* arbitrarily-nested layouts from there.
|
|
16
|
+
*/
|
|
17
|
+
export declare function introspectBlocks(blocks: Block[]): BlockCatalog;
|
|
18
|
+
/**
|
|
19
|
+
* Walk every collection and every block, recording each `blocks`-typed
|
|
20
|
+
* field's owner, dotted path, and accepted slugs.
|
|
13
21
|
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* - If field.blocks is empty and field.blockReferences exists, uses those slugs directly
|
|
22
|
+
* The AI uses this to compose layouts at any depth: it looks up which
|
|
23
|
+
* slugs the relevant field accepts, picks one, then if that block has
|
|
24
|
+
* its own `blocks` fields it recurses against the same map.
|
|
18
25
|
*/
|
|
19
|
-
export declare function
|
|
26
|
+
export declare function buildBlockNestingMap(collections: CollectionConfig[], blocks: Block[]): BlockNestingMap;
|
|
20
27
|
/**
|
|
21
28
|
* Build a relationship graph from introspected collection schemas.
|
|
22
29
|
*/
|