payload-mcp-toolkit 0.2.0 → 0.3.1

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.
Files changed (61) hide show
  1. package/README.md +150 -133
  2. package/dist/__tests__/introspection.test.js +141 -46
  3. package/dist/__tests__/introspection.test.js.map +1 -1
  4. package/dist/draft-workflow.d.ts +24 -19
  5. package/dist/draft-workflow.js +89 -42
  6. package/dist/draft-workflow.js.map +1 -1
  7. package/dist/index.d.ts +10 -15
  8. package/dist/index.js +36 -76
  9. package/dist/index.js.map +1 -1
  10. package/dist/introspection.d.ts +18 -7
  11. package/dist/introspection.js +113 -84
  12. package/dist/introspection.js.map +1 -1
  13. package/dist/prompts.d.ts +4 -4
  14. package/dist/prompts.js +47 -38
  15. package/dist/prompts.js.map +1 -1
  16. package/dist/resources.d.ts +9 -4
  17. package/dist/resources.js +43 -58
  18. package/dist/resources.js.map +1 -1
  19. package/dist/tools/_helpers.d.ts +14 -0
  20. package/dist/tools/_helpers.js +35 -0
  21. package/dist/tools/_helpers.js.map +1 -0
  22. package/dist/tools/patch-layout.d.ts +15 -85
  23. package/dist/tools/patch-layout.js +142 -69
  24. package/dist/tools/patch-layout.js.map +1 -1
  25. package/dist/tools/publish-draft.d.ts +1 -11
  26. package/dist/tools/publish-draft.js +8 -39
  27. package/dist/tools/publish-draft.js.map +1 -1
  28. package/dist/tools/resolve-reference.d.ts +1 -12
  29. package/dist/tools/resolve-reference.js +45 -85
  30. package/dist/tools/resolve-reference.js.map +1 -1
  31. package/dist/tools/safe-delete.d.ts +8 -13
  32. package/dist/tools/safe-delete.js +68 -100
  33. package/dist/tools/safe-delete.js.map +1 -1
  34. package/dist/tools/schedule-publish.d.ts +11 -21
  35. package/dist/tools/schedule-publish.js +18 -61
  36. package/dist/tools/schedule-publish.js.map +1 -1
  37. package/dist/tools/search-content.d.ts +1 -6
  38. package/dist/tools/search-content.js +52 -64
  39. package/dist/tools/search-content.js.map +1 -1
  40. package/dist/tools/update-document.d.ts +4 -14
  41. package/dist/tools/update-document.js +23 -72
  42. package/dist/tools/update-document.js.map +1 -1
  43. package/dist/tools/upload-media.d.ts +1 -10
  44. package/dist/tools/upload-media.js +11 -54
  45. package/dist/tools/upload-media.js.map +1 -1
  46. package/dist/tools/versions.d.ts +7 -20
  47. package/dist/tools/versions.js +25 -82
  48. package/dist/tools/versions.js.map +1 -1
  49. package/dist/types.d.ts +82 -53
  50. package/dist/types.js +6 -1
  51. package/dist/types.js.map +1 -1
  52. package/package.json +1 -1
  53. package/dist/rate-limiter.d.ts +0 -25
  54. package/dist/rate-limiter.js +0 -51
  55. package/dist/rate-limiter.js.map +0 -1
  56. package/dist/tools/compose-helpers.d.ts +0 -117
  57. package/dist/tools/compose-helpers.js +0 -236
  58. package/dist/tools/compose-helpers.js.map +0 -1
  59. package/dist/tools/compose-layout.d.ts +0 -139
  60. package/dist/tools/compose-layout.js +0 -61
  61. package/dist/tools/compose-layout.js.map +0 -1
@@ -1,5 +1,11 @@
1
- /**
2
- * Introspect a Payload collection config into structured metadata.
1
+ /**
2
+ * True if the collection has Payload drafts enabled in its versions config.
3
+ */ export function hasCollectionDrafts(collection) {
4
+ const versions = collection.versions;
5
+ return typeof versions === 'object' && versions !== null && 'drafts' in versions && Boolean(versions.drafts);
6
+ }
7
+ /**
8
+ * Introspect a Payload collection config into structured metadata.
3
9
  */ export function introspectCollection(collection) {
4
10
  const fields = extractFields(collection.fields);
5
11
  const relationships = extractRelationships(collection.fields);
@@ -11,19 +17,18 @@
11
17
  'title',
12
18
  'slug'
13
19
  ].includes(f.name)).map((f)=>f.name);
14
- const hasDrafts = !!(collection.versions && typeof collection.versions === 'object' && 'drafts' in collection.versions && collection.versions.drafts);
15
20
  const hasLivePreview = !!(collection.admin && typeof collection.admin === 'object' && 'livePreview' in collection.admin && collection.admin.livePreview);
16
21
  return {
17
22
  slug: collection.slug,
18
23
  fields,
19
- hasDrafts,
24
+ hasDrafts: hasCollectionDrafts(collection),
20
25
  hasLivePreview,
21
26
  relationships,
22
27
  searchableFields
23
28
  };
24
29
  }
25
- /**
26
- * Introspect all collections into a map keyed by slug.
30
+ /**
31
+ * Introspect all collections into a map keyed by slug.
27
32
  */ export function introspectCollections(collections) {
28
33
  const map = new Map();
29
34
  for (const collection of collections){
@@ -31,58 +36,50 @@
31
36
  }
32
37
  return map;
33
38
  }
34
- /**
35
- * Introspect block configs into a block catalog with section/leaf hierarchy and nesting rules.
36
- *
37
- * NOTE: In the Payload plugin context, blockReferences may or may not be resolved
38
- * depending on when introspection runs. This function handles both cases:
39
- * - If field.blocks contains resolved block objects, reads slugs from them
40
- * - If field.blocks is empty and field.blockReferences exists, uses those slugs directly
41
- */ export function introspectBlocks(sectionBlocks, leafBlocks) {
42
- const leafSlugs = new Set(leafBlocks.map((b)=>b.slug));
43
- const sections = sectionBlocks.map((section)=>{
44
- const blockFields = findBlockFields(section.fields);
45
- if (blockFields.length === 0) {
46
- // Fixed section — no nested blocks
47
- return {
48
- slug: section.slug,
49
- nestingType: 'fixed',
50
- acceptedLeafSlugs: [],
51
- fields: extractFields(section.fields)
52
- };
53
- }
54
- // Determine accepted leaf slugs from all blocks-type fields
55
- const acceptedSlugs = new Set();
56
- let maxRows;
57
- for (const bf of blockFields){
58
- const slugs = getBlockSlugsFromField(bf);
59
- for (const s of slugs){
60
- if (leafSlugs.has(s)) acceptedSlugs.add(s);
61
- }
62
- if (bf.maxRows) maxRows = bf.maxRows;
63
- }
64
- const nestingType = acceptedSlugs.size < leafSlugs.size || maxRows ? 'constrained' : 'composable';
65
- return {
66
- slug: section.slug,
67
- nestingType,
68
- acceptedLeafSlugs: [
69
- ...acceptedSlugs
70
- ],
71
- maxRows,
72
- fields: extractFields(section.fields)
73
- };
74
- });
75
- const leaves = leafBlocks.map((leaf)=>({
76
- slug: leaf.slug,
77
- fields: extractFields(leaf.fields)
39
+ /**
40
+ * Build a flat catalog of every block in the schema. Whether a block can
41
+ * nest other blocks is represented separately in the BlockNestingMap, not
42
+ * as a section/leaf classification the AI reads both and composes
43
+ * arbitrarily-nested layouts from there.
44
+ */ export function introspectBlocks(blocks) {
45
+ const catalog = blocks.map((block)=>({
46
+ slug: block.slug,
47
+ fields: extractFields(block.fields)
78
48
  }));
79
49
  return {
80
- sections,
81
- leaves
50
+ blocks: catalog
82
51
  };
83
52
  }
84
- /**
85
- * Build a relationship graph from introspected collection schemas.
53
+ /**
54
+ * Walk every collection and every block, recording each `blocks`-typed
55
+ * field's owner, dotted path, and accepted slugs.
56
+ *
57
+ * The AI uses this to compose layouts at any depth: it looks up which
58
+ * slugs the relevant field accepts, picks one, then if that block has
59
+ * its own `blocks` fields it recurses against the same map.
60
+ */ export function buildBlockNestingMap(collections, blocks) {
61
+ const knownSlugs = new Set(blocks.map((b)=>b.slug));
62
+ const edges = [];
63
+ for (const collection of collections){
64
+ edges.push(...collectBlocksFieldEdges(collection.fields, {
65
+ owner: collection.slug,
66
+ ownerType: 'collection',
67
+ prefix: '',
68
+ knownSlugs
69
+ }));
70
+ }
71
+ for (const block of blocks){
72
+ edges.push(...collectBlocksFieldEdges(block.fields, {
73
+ owner: block.slug,
74
+ ownerType: 'block',
75
+ prefix: '',
76
+ knownSlugs
77
+ }));
78
+ }
79
+ return edges;
80
+ }
81
+ /**
82
+ * Build a relationship graph from introspected collection schemas.
86
83
  */ export function buildRelationshipGraph(schemas) {
87
84
  const edges = [];
88
85
  for (const [slug, schema] of schemas){
@@ -98,9 +95,9 @@
98
95
  return edges;
99
96
  }
100
97
  // ─── Internal helpers ──────────────────────────────────────────────
101
- /**
102
- * Recursively extract field metadata from a Payload fields array.
103
- * Handles tabs, groups, arrays, rows, and collapsible containers.
98
+ /**
99
+ * Recursively extract field metadata from a Payload fields array.
100
+ * Handles tabs, groups, arrays, rows, and collapsible containers.
104
101
  */ function extractFields(fields) {
105
102
  const result = [];
106
103
  for (const field of fields){
@@ -111,9 +108,10 @@
111
108
  };
112
109
  if ('required' in field && field.required) schema.required = true;
113
110
  if ('hasMany' in field && field.hasMany) schema.hasMany = true;
114
- if ('relationTo' in field && field.relationTo) schema.relationTo = field.relationTo;
111
+ if ('relationTo' in field && field.relationTo) {
112
+ schema.relationTo = field.relationTo;
113
+ }
115
114
  if ('maxRows' in field && field.maxRows) schema.maxRows = field.maxRows;
116
- // Extract select options
117
115
  if (field.type === 'select' && 'options' in field && Array.isArray(field.options)) {
118
116
  schema.options = field.options.map((opt)=>typeof opt === 'string' ? {
119
117
  label: opt,
@@ -123,7 +121,6 @@
123
121
  value: String(opt.value)
124
122
  });
125
123
  }
126
- // Recurse into nested fields (arrays, groups)
127
124
  if (field.type === 'array' && 'fields' in field) {
128
125
  schema.fields = extractFields(field.fields);
129
126
  }
@@ -132,7 +129,6 @@
132
129
  }
133
130
  result.push(schema);
134
131
  }
135
- // Transparent containers — recurse without creating a named field
136
132
  if (field.type === 'tabs' && 'tabs' in field) {
137
133
  for (const tab of field.tabs){
138
134
  if ('fields' in tab) {
@@ -149,8 +145,8 @@
149
145
  }
150
146
  return result;
151
147
  }
152
- /**
153
- * Extract relationship metadata from fields (for the relationship graph).
148
+ /**
149
+ * Extract relationship metadata from fields (for the relationship graph).
154
150
  */ function extractRelationships(fields, prefix = '') {
155
151
  const rels = [];
156
152
  for (const field of fields){
@@ -169,7 +165,6 @@
169
165
  hasMany: !!('hasMany' in field && field.hasMany)
170
166
  });
171
167
  }
172
- // Recurse into containers
173
168
  if (field.type === 'tabs' && 'tabs' in field) {
174
169
  for (const tab of field.tabs){
175
170
  if ('fields' in tab) {
@@ -192,43 +187,77 @@
192
187
  }
193
188
  return rels;
194
189
  }
195
- /**
196
- * Find all blocks-type fields within a field array (recursing into tabs/rows/etc).
197
- */ function findBlockFields(fields) {
198
- const result = [];
190
+ /**
191
+ * Walk fields recording every `blocks`-typed field encountered, including
192
+ * those nested in tabs/rows/groups/arrays/collapsibles. Each entry carries
193
+ * the dotted path from the owner root to the field.
194
+ */ function collectBlocksFieldEdges(fields, ctx) {
195
+ const edges = [];
199
196
  for (const field of fields){
200
197
  if (field.type === 'blocks') {
201
- result.push(field);
198
+ const fieldName = 'name' in field && field.name ? field.name : '';
199
+ const fullPath = ctx.prefix ? `${ctx.prefix}.${fieldName}` : fieldName;
200
+ const allSlugs = readBlockSlugs(field);
201
+ const acceptedSlugs = allSlugs.filter((s)=>ctx.knownSlugs.has(s));
202
+ const edge = {
203
+ owner: ctx.owner,
204
+ ownerType: ctx.ownerType,
205
+ fieldPath: fullPath,
206
+ acceptedBlockSlugs: acceptedSlugs
207
+ };
208
+ const maxRows = field.maxRows;
209
+ if (typeof maxRows === 'number') edge.maxRows = maxRows;
210
+ edges.push(edge);
211
+ continue;
202
212
  }
203
213
  if (field.type === 'tabs' && 'tabs' in field) {
204
214
  for (const tab of field.tabs){
205
- if ('fields' in tab) {
206
- result.push(...findBlockFields(tab.fields));
207
- }
215
+ if (!('fields' in tab)) continue;
216
+ const tabName = 'name' in tab && tab.name ? tab.name : '';
217
+ const tabPrefix = tabName ? ctx.prefix ? `${ctx.prefix}.${tabName}` : tabName : ctx.prefix;
218
+ edges.push(...collectBlocksFieldEdges(tab.fields, {
219
+ ...ctx,
220
+ prefix: tabPrefix
221
+ }));
208
222
  }
223
+ continue;
209
224
  }
210
225
  if (field.type === 'row' && 'fields' in field) {
211
- result.push(...findBlockFields(field.fields));
226
+ edges.push(...collectBlocksFieldEdges(field.fields, ctx));
227
+ continue;
212
228
  }
213
229
  if (field.type === 'collapsible' && 'fields' in field) {
214
- result.push(...findBlockFields(field.fields));
230
+ edges.push(...collectBlocksFieldEdges(field.fields, ctx));
231
+ continue;
215
232
  }
216
- if (field.type === 'group' && 'fields' in field) {
217
- result.push(...findBlockFields(field.fields));
233
+ if (field.type === 'group' && 'fields' in field && 'name' in field && field.name) {
234
+ const newPrefix = ctx.prefix ? `${ctx.prefix}.${field.name}` : field.name;
235
+ edges.push(...collectBlocksFieldEdges(field.fields, {
236
+ ...ctx,
237
+ prefix: newPrefix
238
+ }));
239
+ continue;
240
+ }
241
+ if (field.type === 'array' && 'fields' in field && 'name' in field && field.name) {
242
+ const newPrefix = ctx.prefix ? `${ctx.prefix}.${field.name}[]` : `${field.name}[]`;
243
+ edges.push(...collectBlocksFieldEdges(field.fields, {
244
+ ...ctx,
245
+ prefix: newPrefix
246
+ }));
247
+ continue;
218
248
  }
219
249
  }
220
- return result;
250
+ return edges;
221
251
  }
222
- /**
223
- * Get block slugs from a blocks-type field.
224
- * Handles both resolved blocks (field.blocks has objects) and unresolved blockReferences.
225
- */ function getBlockSlugsFromField(field) {
252
+ /**
253
+ * Read block slugs from a blocks-typed field, handling both resolved
254
+ * (field.blocks contains objects) and unresolved (field.blockReferences
255
+ * holds slug strings) forms.
256
+ */ function readBlockSlugs(field) {
226
257
  const f = field;
227
- // Resolved blocks field.blocks contains full block objects
228
- if (Array.isArray(f.blocks) && f.blocks.length > 0 && typeof f.blocks[0] === 'object' && f.blocks[0].slug) {
258
+ if (Array.isArray(f.blocks) && f.blocks.length > 0 && typeof f.blocks[0] === 'object' && f.blocks[0]?.slug) {
229
259
  return f.blocks.map((b)=>b.slug);
230
260
  }
231
- // Unresolved — blockReferences contains slug strings
232
261
  if (Array.isArray(f.blockReferences) && f.blockReferences.length > 0) {
233
262
  return f.blockReferences.filter((ref)=>typeof ref === 'string');
234
263
  }
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/introspection.ts"],"sourcesContent":["import type { Block, CollectionConfig, Field, GlobalConfig } from 'payload'\nimport type {\n BlockCatalog,\n BlockNestingType,\n CollectionSchema,\n FieldSchema,\n LeafBlockSchema,\n RelationshipEdge,\n SectionBlockSchema,\n} from './types'\n\n/**\n * Introspect a Payload collection config into structured metadata.\n */\nexport function introspectCollection(collection: CollectionConfig): CollectionSchema {\n const fields = extractFields(collection.fields)\n const relationships = extractRelationships(collection.fields)\n const searchableFields = fields\n .filter((f) => ['text', 'email'].includes(f.type) && ['name', 'title', 'slug'].includes(f.name))\n .map((f) => f.name)\n\n const hasDrafts = !!(collection.versions && typeof collection.versions === 'object' && 'drafts' in collection.versions && collection.versions.drafts)\n const hasLivePreview = !!(collection.admin && typeof collection.admin === 'object' && 'livePreview' in collection.admin && collection.admin.livePreview)\n\n return {\n slug: collection.slug,\n fields,\n hasDrafts,\n hasLivePreview,\n relationships,\n searchableFields,\n }\n}\n\n/**\n * Introspect all collections into a map keyed by slug.\n */\nexport function introspectCollections(\n collections: CollectionConfig[],\n): Map<string, CollectionSchema> {\n const map = new Map<string, CollectionSchema>()\n for (const collection of collections) {\n map.set(collection.slug, introspectCollection(collection))\n }\n return map\n}\n\n/**\n * Introspect block configs into a block catalog with section/leaf hierarchy and nesting rules.\n *\n * NOTE: In the Payload plugin context, blockReferences may or may not be resolved\n * depending on when introspection runs. This function handles both cases:\n * - If field.blocks contains resolved block objects, reads slugs from them\n * - If field.blocks is empty and field.blockReferences exists, uses those slugs directly\n */\nexport function introspectBlocks(\n sectionBlocks: Block[],\n leafBlocks: Block[],\n): BlockCatalog {\n const leafSlugs = new Set(leafBlocks.map((b) => b.slug))\n\n const sections: SectionBlockSchema[] = sectionBlocks.map((section) => {\n const blockFields = findBlockFields(section.fields)\n\n if (blockFields.length === 0) {\n // Fixed section — no nested blocks\n return {\n slug: section.slug,\n nestingType: 'fixed' as BlockNestingType,\n acceptedLeafSlugs: [],\n fields: extractFields(section.fields),\n }\n }\n\n // Determine accepted leaf slugs from all blocks-type fields\n const acceptedSlugs = new Set<string>()\n let maxRows: number | undefined\n\n for (const bf of blockFields) {\n const slugs = getBlockSlugsFromField(bf)\n for (const s of slugs) {\n if (leafSlugs.has(s)) acceptedSlugs.add(s)\n }\n if (bf.maxRows) maxRows = bf.maxRows\n }\n\n const nestingType: BlockNestingType =\n acceptedSlugs.size < leafSlugs.size || maxRows ? 'constrained' : 'composable'\n\n return {\n slug: section.slug,\n nestingType,\n acceptedLeafSlugs: [...acceptedSlugs],\n maxRows,\n fields: extractFields(section.fields),\n }\n })\n\n const leaves: LeafBlockSchema[] = leafBlocks.map((leaf) => ({\n slug: leaf.slug,\n fields: extractFields(leaf.fields),\n }))\n\n return { sections, leaves }\n}\n\n/**\n * Build a relationship graph from introspected collection schemas.\n */\nexport function buildRelationshipGraph(\n schemas: Map<string, CollectionSchema>,\n): RelationshipEdge[] {\n const edges: RelationshipEdge[] = []\n for (const [slug, schema] of schemas) {\n for (const rel of schema.relationships) {\n edges.push({\n fromCollection: slug,\n fieldName: rel.fieldName,\n toCollection: rel.relationTo,\n hasMany: rel.hasMany,\n })\n }\n }\n return edges\n}\n\n// ─── Internal helpers ──────────────────────────────────────────────\n\n/**\n * Recursively extract field metadata from a Payload fields array.\n * Handles tabs, groups, arrays, rows, and collapsible containers.\n */\nfunction extractFields(fields: Field[]): FieldSchema[] {\n const result: FieldSchema[] = []\n\n for (const field of fields) {\n if ('name' in field && field.name) {\n const schema: FieldSchema = {\n name: field.name,\n type: field.type,\n }\n\n if ('required' in field && field.required) schema.required = true\n if ('hasMany' in field && field.hasMany) schema.hasMany = true\n if ('relationTo' in field && field.relationTo) schema.relationTo = field.relationTo as string | string[]\n if ('maxRows' in field && field.maxRows) schema.maxRows = field.maxRows\n\n // Extract select options\n if (field.type === 'select' && 'options' in field && Array.isArray(field.options)) {\n schema.options = field.options.map((opt) =>\n typeof opt === 'string' ? { label: opt, value: opt } : { label: String(opt.label), value: String(opt.value) },\n )\n }\n\n // Recurse into nested fields (arrays, groups)\n if (field.type === 'array' && 'fields' in field) {\n schema.fields = extractFields(field.fields)\n }\n if (field.type === 'group' && 'fields' in field) {\n schema.fields = extractFields(field.fields)\n }\n\n result.push(schema)\n }\n\n // Transparent containers — recurse without creating a named field\n if (field.type === 'tabs' && 'tabs' in field) {\n for (const tab of field.tabs) {\n if ('fields' in tab) {\n result.push(...extractFields(tab.fields))\n }\n }\n }\n if (field.type === 'row' && 'fields' in field) {\n result.push(...extractFields(field.fields))\n }\n if (field.type === 'collapsible' && 'fields' in field) {\n result.push(...extractFields(field.fields))\n }\n }\n\n return result\n}\n\n/**\n * Extract relationship metadata from fields (for the relationship graph).\n */\nfunction extractRelationships(\n fields: Field[],\n prefix = '',\n): Array<{ fieldName: string; relationTo: string | string[]; hasMany: boolean }> {\n const rels: Array<{ fieldName: string; relationTo: string | string[]; hasMany: boolean }> = []\n\n for (const field of fields) {\n const fieldName = 'name' in field && field.name ? `${prefix}${field.name}` : prefix\n\n if (field.type === 'relationship' && 'relationTo' in field) {\n rels.push({\n fieldName,\n relationTo: field.relationTo as string | string[],\n hasMany: !!('hasMany' in field && field.hasMany),\n })\n }\n\n if (field.type === 'upload' && 'relationTo' in field) {\n rels.push({\n fieldName,\n relationTo: field.relationTo as string,\n hasMany: !!('hasMany' in field && field.hasMany),\n })\n }\n\n // Recurse into containers\n if (field.type === 'tabs' && 'tabs' in field) {\n for (const tab of field.tabs) {\n if ('fields' in tab) {\n rels.push(...extractRelationships(tab.fields, prefix))\n }\n }\n }\n if (field.type === 'group' && 'fields' in field) {\n rels.push(...extractRelationships(field.fields, `${fieldName}.`))\n }\n if (field.type === 'array' && 'fields' in field) {\n rels.push(...extractRelationships(field.fields, `${fieldName}[].`))\n }\n if (field.type === 'row' && 'fields' in field) {\n rels.push(...extractRelationships(field.fields, prefix))\n }\n if (field.type === 'collapsible' && 'fields' in field) {\n rels.push(...extractRelationships(field.fields, prefix))\n }\n }\n\n return rels\n}\n\n/**\n * Find all blocks-type fields within a field array (recursing into tabs/rows/etc).\n */\nfunction findBlockFields(fields: Field[]): Array<Field & { type: 'blocks' }> {\n const result: Array<Field & { type: 'blocks' }> = []\n\n for (const field of fields) {\n if (field.type === 'blocks') {\n result.push(field as Field & { type: 'blocks' })\n }\n if (field.type === 'tabs' && 'tabs' in field) {\n for (const tab of field.tabs) {\n if ('fields' in tab) {\n result.push(...findBlockFields(tab.fields))\n }\n }\n }\n if (field.type === 'row' && 'fields' in field) {\n result.push(...findBlockFields(field.fields))\n }\n if (field.type === 'collapsible' && 'fields' in field) {\n result.push(...findBlockFields(field.fields))\n }\n if (field.type === 'group' && 'fields' in field) {\n result.push(...findBlockFields(field.fields))\n }\n }\n\n return result\n}\n\n/**\n * Get block slugs from a blocks-type field.\n * Handles both resolved blocks (field.blocks has objects) and unresolved blockReferences.\n */\nfunction getBlockSlugsFromField(field: Field & { type: 'blocks' }): string[] {\n const f = field as any\n\n // Resolved blocks — field.blocks contains full block objects\n if (Array.isArray(f.blocks) && f.blocks.length > 0 && typeof f.blocks[0] === 'object' && f.blocks[0].slug) {\n return f.blocks.map((b: { slug: string }) => b.slug)\n }\n\n // Unresolved — blockReferences contains slug strings\n if (Array.isArray(f.blockReferences) && f.blockReferences.length > 0) {\n return f.blockReferences.filter((ref: unknown) => typeof ref === 'string') as string[]\n }\n\n return []\n}\n"],"names":["introspectCollection","collection","fields","extractFields","relationships","extractRelationships","searchableFields","filter","f","includes","type","name","map","hasDrafts","versions","drafts","hasLivePreview","admin","livePreview","slug","introspectCollections","collections","Map","set","introspectBlocks","sectionBlocks","leafBlocks","leafSlugs","Set","b","sections","section","blockFields","findBlockFields","length","nestingType","acceptedLeafSlugs","acceptedSlugs","maxRows","bf","slugs","getBlockSlugsFromField","s","has","add","size","leaves","leaf","buildRelationshipGraph","schemas","edges","schema","rel","push","fromCollection","fieldName","toCollection","relationTo","hasMany","result","field","required","Array","isArray","options","opt","label","value","String","tab","tabs","prefix","rels","blocks","blockReferences","ref"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/introspection.ts"],"sourcesContent":["import type { Block, CollectionConfig, Field } from 'payload'\r\nimport type {\r\n BlockCatalog,\r\n BlockNestingMap,\r\n BlockSchema,\r\n CollectionSchema,\r\n FieldSchema,\r\n RelationshipEdge,\r\n} from './types'\r\n\r\n/**\r\n * True if the collection has Payload drafts enabled in its versions config.\r\n */\r\nexport function hasCollectionDrafts(collection: CollectionConfig): boolean {\r\n const versions = collection.versions\r\n return (\r\n typeof versions === 'object' &&\r\n versions !== null &&\r\n 'drafts' in versions &&\r\n Boolean(versions.drafts)\r\n )\r\n}\r\n\r\n/**\r\n * Introspect a Payload collection config into structured metadata.\r\n */\r\nexport function introspectCollection(collection: CollectionConfig): CollectionSchema {\r\n const fields = extractFields(collection.fields)\r\n const relationships = extractRelationships(collection.fields)\r\n const searchableFields = fields\r\n .filter((f) => ['text', 'email'].includes(f.type) && ['name', 'title', 'slug'].includes(f.name))\r\n .map((f) => f.name)\r\n\r\n const hasLivePreview = !!(\r\n collection.admin &&\r\n typeof collection.admin === 'object' &&\r\n 'livePreview' in collection.admin &&\r\n collection.admin.livePreview\r\n )\r\n\r\n return {\r\n slug: collection.slug,\r\n fields,\r\n hasDrafts: hasCollectionDrafts(collection),\r\n hasLivePreview,\r\n relationships,\r\n searchableFields,\r\n }\r\n}\r\n\r\n/**\r\n * Introspect all collections into a map keyed by slug.\r\n */\r\nexport function introspectCollections(\r\n collections: CollectionConfig[],\r\n): Map<string, CollectionSchema> {\r\n const map = new Map<string, CollectionSchema>()\r\n for (const collection of collections) {\r\n map.set(collection.slug, introspectCollection(collection))\r\n }\r\n return map\r\n}\r\n\r\n/**\r\n * Build a flat catalog of every block in the schema. Whether a block can\r\n * nest other blocks is represented separately in the BlockNestingMap, not\r\n * as a section/leaf classification — the AI reads both and composes\r\n * arbitrarily-nested layouts from there.\r\n */\r\nexport function introspectBlocks(blocks: Block[]): BlockCatalog {\r\n const catalog: BlockSchema[] = blocks.map((block) => ({\r\n slug: block.slug,\r\n fields: extractFields(block.fields),\r\n }))\r\n return { blocks: catalog }\r\n}\r\n\r\n/**\r\n * Walk every collection and every block, recording each `blocks`-typed\r\n * field's owner, dotted path, and accepted slugs.\r\n *\r\n * The AI uses this to compose layouts at any depth: it looks up which\r\n * slugs the relevant field accepts, picks one, then if that block has\r\n * its own `blocks` fields it recurses against the same map.\r\n */\r\nexport function buildBlockNestingMap(\r\n collections: CollectionConfig[],\r\n blocks: Block[],\r\n): BlockNestingMap {\r\n const knownSlugs = new Set(blocks.map((b) => b.slug))\r\n const edges: BlockNestingMap = []\r\n\r\n for (const collection of collections) {\r\n edges.push(\r\n ...collectBlocksFieldEdges(collection.fields, {\r\n owner: collection.slug,\r\n ownerType: 'collection',\r\n prefix: '',\r\n knownSlugs,\r\n }),\r\n )\r\n }\r\n\r\n for (const block of blocks) {\r\n edges.push(\r\n ...collectBlocksFieldEdges(block.fields, {\r\n owner: block.slug,\r\n ownerType: 'block',\r\n prefix: '',\r\n knownSlugs,\r\n }),\r\n )\r\n }\r\n\r\n return edges\r\n}\r\n\r\n/**\r\n * Build a relationship graph from introspected collection schemas.\r\n */\r\nexport function buildRelationshipGraph(\r\n schemas: Map<string, CollectionSchema>,\r\n): RelationshipEdge[] {\r\n const edges: RelationshipEdge[] = []\r\n for (const [slug, schema] of schemas) {\r\n for (const rel of schema.relationships) {\r\n edges.push({\r\n fromCollection: slug,\r\n fieldName: rel.fieldName,\r\n toCollection: rel.relationTo,\r\n hasMany: rel.hasMany,\r\n })\r\n }\r\n }\r\n return edges\r\n}\r\n\r\n// ─── Internal helpers ──────────────────────────────────────────────\r\n\r\n/**\r\n * Recursively extract field metadata from a Payload fields array.\r\n * Handles tabs, groups, arrays, rows, and collapsible containers.\r\n */\r\nfunction extractFields(fields: Field[]): FieldSchema[] {\r\n const result: FieldSchema[] = []\r\n\r\n for (const field of fields) {\r\n if ('name' in field && field.name) {\r\n const schema: FieldSchema = {\r\n name: field.name,\r\n type: field.type,\r\n }\r\n\r\n if ('required' in field && field.required) schema.required = true\r\n if ('hasMany' in field && field.hasMany) schema.hasMany = true\r\n if ('relationTo' in field && field.relationTo) {\r\n schema.relationTo = field.relationTo as string | string[]\r\n }\r\n if ('maxRows' in field && field.maxRows) schema.maxRows = field.maxRows\r\n\r\n if (field.type === 'select' && 'options' in field && Array.isArray(field.options)) {\r\n schema.options = field.options.map((opt) =>\r\n typeof opt === 'string'\r\n ? { label: opt, value: opt }\r\n : { label: String(opt.label), value: String(opt.value) },\r\n )\r\n }\r\n\r\n if (field.type === 'array' && 'fields' in field) {\r\n schema.fields = extractFields(field.fields)\r\n }\r\n if (field.type === 'group' && 'fields' in field) {\r\n schema.fields = extractFields(field.fields)\r\n }\r\n\r\n result.push(schema)\r\n }\r\n\r\n if (field.type === 'tabs' && 'tabs' in field) {\r\n for (const tab of field.tabs) {\r\n if ('fields' in tab) {\r\n result.push(...extractFields(tab.fields))\r\n }\r\n }\r\n }\r\n if (field.type === 'row' && 'fields' in field) {\r\n result.push(...extractFields(field.fields))\r\n }\r\n if (field.type === 'collapsible' && 'fields' in field) {\r\n result.push(...extractFields(field.fields))\r\n }\r\n }\r\n\r\n return result\r\n}\r\n\r\n/**\r\n * Extract relationship metadata from fields (for the relationship graph).\r\n */\r\nfunction extractRelationships(\r\n fields: Field[],\r\n prefix = '',\r\n): Array<{ fieldName: string; relationTo: string | string[]; hasMany: boolean }> {\r\n const rels: Array<{ fieldName: string; relationTo: string | string[]; hasMany: boolean }> = []\r\n\r\n for (const field of fields) {\r\n const fieldName = 'name' in field && field.name ? `${prefix}${field.name}` : prefix\r\n\r\n if (field.type === 'relationship' && 'relationTo' in field) {\r\n rels.push({\r\n fieldName,\r\n relationTo: field.relationTo as string | string[],\r\n hasMany: !!('hasMany' in field && field.hasMany),\r\n })\r\n }\r\n\r\n if (field.type === 'upload' && 'relationTo' in field) {\r\n rels.push({\r\n fieldName,\r\n relationTo: field.relationTo as string,\r\n hasMany: !!('hasMany' in field && field.hasMany),\r\n })\r\n }\r\n\r\n if (field.type === 'tabs' && 'tabs' in field) {\r\n for (const tab of field.tabs) {\r\n if ('fields' in tab) {\r\n rels.push(...extractRelationships(tab.fields, prefix))\r\n }\r\n }\r\n }\r\n if (field.type === 'group' && 'fields' in field) {\r\n rels.push(...extractRelationships(field.fields, `${fieldName}.`))\r\n }\r\n if (field.type === 'array' && 'fields' in field) {\r\n rels.push(...extractRelationships(field.fields, `${fieldName}[].`))\r\n }\r\n if (field.type === 'row' && 'fields' in field) {\r\n rels.push(...extractRelationships(field.fields, prefix))\r\n }\r\n if (field.type === 'collapsible' && 'fields' in field) {\r\n rels.push(...extractRelationships(field.fields, prefix))\r\n }\r\n }\r\n\r\n return rels\r\n}\r\n\r\ninterface NestingScanContext {\r\n owner: string\r\n ownerType: 'collection' | 'block'\r\n prefix: string\r\n knownSlugs: Set<string>\r\n}\r\n\r\n/**\r\n * Walk fields recording every `blocks`-typed field encountered, including\r\n * those nested in tabs/rows/groups/arrays/collapsibles. Each entry carries\r\n * the dotted path from the owner root to the field.\r\n */\r\nfunction collectBlocksFieldEdges(fields: Field[], ctx: NestingScanContext): BlockNestingMap {\r\n const edges: BlockNestingMap = []\r\n\r\n for (const field of fields) {\r\n if (field.type === 'blocks') {\r\n const fieldName = 'name' in field && field.name ? field.name : ''\r\n const fullPath = ctx.prefix ? `${ctx.prefix}.${fieldName}` : fieldName\r\n const allSlugs = readBlockSlugs(field as Field & { type: 'blocks' })\r\n const acceptedSlugs = allSlugs.filter((s) => ctx.knownSlugs.has(s))\r\n\r\n const edge: BlockNestingMap[number] = {\r\n owner: ctx.owner,\r\n ownerType: ctx.ownerType,\r\n fieldPath: fullPath,\r\n acceptedBlockSlugs: acceptedSlugs,\r\n }\r\n const maxRows = (field as Field & { type: 'blocks' }).maxRows\r\n if (typeof maxRows === 'number') edge.maxRows = maxRows\r\n edges.push(edge)\r\n continue\r\n }\r\n\r\n if (field.type === 'tabs' && 'tabs' in field) {\r\n for (const tab of field.tabs) {\r\n if (!('fields' in tab)) continue\r\n const tabName = 'name' in tab && tab.name ? tab.name : ''\r\n const tabPrefix = tabName\r\n ? ctx.prefix\r\n ? `${ctx.prefix}.${tabName}`\r\n : tabName\r\n : ctx.prefix\r\n edges.push(...collectBlocksFieldEdges(tab.fields, { ...ctx, prefix: tabPrefix }))\r\n }\r\n continue\r\n }\r\n if (field.type === 'row' && 'fields' in field) {\r\n edges.push(...collectBlocksFieldEdges(field.fields, ctx))\r\n continue\r\n }\r\n if (field.type === 'collapsible' && 'fields' in field) {\r\n edges.push(...collectBlocksFieldEdges(field.fields, ctx))\r\n continue\r\n }\r\n if (field.type === 'group' && 'fields' in field && 'name' in field && field.name) {\r\n const newPrefix = ctx.prefix ? `${ctx.prefix}.${field.name}` : field.name\r\n edges.push(...collectBlocksFieldEdges(field.fields, { ...ctx, prefix: newPrefix }))\r\n continue\r\n }\r\n if (field.type === 'array' && 'fields' in field && 'name' in field && field.name) {\r\n const newPrefix = ctx.prefix ? `${ctx.prefix}.${field.name}[]` : `${field.name}[]`\r\n edges.push(...collectBlocksFieldEdges(field.fields, { ...ctx, prefix: newPrefix }))\r\n continue\r\n }\r\n }\r\n\r\n return edges\r\n}\r\n\r\n/**\r\n * Read block slugs from a blocks-typed field, handling both resolved\r\n * (field.blocks contains objects) and unresolved (field.blockReferences\r\n * holds slug strings) forms.\r\n */\r\nfunction readBlockSlugs(field: Field & { type: 'blocks' }): string[] {\r\n const f = field as any\r\n\r\n if (\r\n Array.isArray(f.blocks) &&\r\n f.blocks.length > 0 &&\r\n typeof f.blocks[0] === 'object' &&\r\n f.blocks[0]?.slug\r\n ) {\r\n return f.blocks.map((b: { slug: string }) => b.slug)\r\n }\r\n\r\n if (Array.isArray(f.blockReferences) && f.blockReferences.length > 0) {\r\n return f.blockReferences.filter((ref: unknown) => typeof ref === 'string') as string[]\r\n }\r\n\r\n return []\r\n}\r\n"],"names":["hasCollectionDrafts","collection","versions","Boolean","drafts","introspectCollection","fields","extractFields","relationships","extractRelationships","searchableFields","filter","f","includes","type","name","map","hasLivePreview","admin","livePreview","slug","hasDrafts","introspectCollections","collections","Map","set","introspectBlocks","blocks","catalog","block","buildBlockNestingMap","knownSlugs","Set","b","edges","push","collectBlocksFieldEdges","owner","ownerType","prefix","buildRelationshipGraph","schemas","schema","rel","fromCollection","fieldName","toCollection","relationTo","hasMany","result","field","required","maxRows","Array","isArray","options","opt","label","value","String","tab","tabs","rels","ctx","fullPath","allSlugs","readBlockSlugs","acceptedSlugs","s","has","edge","fieldPath","acceptedBlockSlugs","tabName","tabPrefix","newPrefix","length","blockReferences","ref"],"mappings":"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"}
package/dist/prompts.d.ts CHANGED
@@ -1,11 +1,11 @@
1
- import type { BlockCatalog, CollectionSchema, DomainPrompt, RelationshipEdge } from './types';
1
+ import type { BlockCatalog, BlockNestingMap, CollectionSchema, DomainPrompt, RelationshipEdge } from './types';
2
2
  /**
3
3
  * Generate MCP prompts that teach the AI about the content model.
4
4
  *
5
- * Auto-generates three prompts (content model overview, block composition guide,
6
- * draft workflow guide) and merges with any user-provided domain prompts.
5
+ * Auto-generates content model overview, block composition guide, and
6
+ * draft workflow guide. User-supplied domain prompts are appended.
7
7
  */
8
- export declare function generatePrompts(schemas: Map<string, CollectionSchema>, catalog: BlockCatalog, relationships: RelationshipEdge[], domainPrompts?: DomainPrompt[]): {
8
+ export declare function generatePrompts(schemas: Map<string, CollectionSchema>, catalog: BlockCatalog, nesting: BlockNestingMap, relationships: RelationshipEdge[], domainPrompts?: DomainPrompt[]): {
9
9
  name: string;
10
10
  title: string;
11
11
  description: string;
package/dist/prompts.js CHANGED
@@ -1,12 +1,12 @@
1
- /**
2
- * Generate MCP prompts that teach the AI about the content model.
3
- *
4
- * Auto-generates three prompts (content model overview, block composition guide,
5
- * draft workflow guide) and merges with any user-provided domain prompts.
6
- */ export function generatePrompts(schemas, catalog, relationships, domainPrompts) {
1
+ /**
2
+ * Generate MCP prompts that teach the AI about the content model.
3
+ *
4
+ * Auto-generates content model overview, block composition guide, and
5
+ * draft workflow guide. User-supplied domain prompts are appended.
6
+ */ export function generatePrompts(schemas, catalog, nesting, relationships, domainPrompts) {
7
7
  const prompts = [
8
8
  buildContentModelOverview(schemas, relationships),
9
- buildBlockCompositionGuide(catalog),
9
+ buildBlockCompositionGuide(catalog, nesting),
10
10
  buildDraftWorkflowGuide(schemas)
11
11
  ];
12
12
  if (domainPrompts?.length) {
@@ -49,7 +49,6 @@ function buildContentModelOverview(schemas, relationships) {
49
49
  lines.push(`Draft support: ${schema.hasDrafts ? 'yes' : 'no'}`);
50
50
  lines.push(`Live preview: ${schema.hasLivePreview ? 'yes' : 'no'}`);
51
51
  lines.push('');
52
- // Fields summary
53
52
  lines.push('### Fields');
54
53
  for (const field of schema.fields){
55
54
  const parts = [
@@ -69,7 +68,6 @@ function buildContentModelOverview(schemas, relationships) {
69
68
  lines.push(parts.join(''));
70
69
  }
71
70
  lines.push('');
72
- // Relationships
73
71
  const collRels = relationships.filter((r)=>r.fromCollection === slug);
74
72
  if (collRels.length > 0) {
75
73
  lines.push('### Relationships');
@@ -94,47 +92,58 @@ function buildContentModelOverview(schemas, relationships) {
94
92
  }
95
93
  };
96
94
  }
97
- function buildBlockCompositionGuide(catalog) {
95
+ function buildBlockCompositionGuide(catalog, nesting) {
98
96
  return {
99
97
  name: 'blockCompositionGuide',
100
98
  title: 'Block Composition Guide',
101
- description: 'Explains the section/leaf block hierarchy, valid nesting rules, and how to compose page layouts.',
99
+ description: 'Lists every block type, its fields, and which slugs each blocks-typed field accepts. Use this with the relationshipGraph and collectionSchema resources to compose layouts at any depth.',
102
100
  handler () {
103
101
  const lines = [
104
102
  '# Block Composition Guide',
105
103
  '',
106
- 'Pages are built from **section** blocks. Each section can contain **leaf** blocks according to its nesting rules.',
104
+ 'Every block has a `blockType` discriminator plus its own fields. A block may include one or more `blocks`-typed fields that nest other blocks. The accepted slugs per field are listed below — recurse into the same map for deeper nesting.',
107
105
  ''
108
106
  ];
109
- // Section blocks
110
- lines.push('## Section Blocks');
111
- for (const section of catalog.sections){
112
- lines.push(`### ${section.slug} (${section.nestingType})`);
113
- if (section.nestingType === 'fixed') {
114
- lines.push('This section has no nested blocks — configure it with its own fields only.');
115
- } else if (section.nestingType === 'constrained') {
116
- lines.push(`Accepts only: ${section.acceptedLeafSlugs.join(', ')}`);
117
- if (section.maxRows) {
118
- lines.push(`Maximum ${section.maxRows} leaf block(s).`);
119
- }
120
- } else {
121
- lines.push(`Accepts all leaf blocks: ${section.acceptedLeafSlugs.join(', ')}`);
107
+ lines.push('## Where blocks can nest');
108
+ lines.push('');
109
+ const collectionEdges = nesting.filter((e)=>e.ownerType === 'collection');
110
+ const blockEdges = nesting.filter((e)=>e.ownerType === 'block');
111
+ if (collectionEdges.length > 0) {
112
+ lines.push('### In collections');
113
+ for (const edge of collectionEdges){
114
+ const cap = edge.maxRows ? ` (max ${edge.maxRows})` : '';
115
+ lines.push(`- \`${edge.owner}.${edge.fieldPath}\`${cap} accepts: ${edge.acceptedBlockSlugs.join(', ') || '(none)'}`);
122
116
  }
123
- if (section.fields.length > 0) {
124
- lines.push('Section-level fields:');
125
- for (const f of section.fields){
126
- lines.push(` - ${f.name} (${f.type})${f.required ? ' *required*' : ''}`);
127
- }
117
+ lines.push('');
118
+ }
119
+ if (blockEdges.length > 0) {
120
+ lines.push('### In blocks (nested composition)');
121
+ for (const edge of blockEdges){
122
+ const cap = edge.maxRows ? ` (max ${edge.maxRows})` : '';
123
+ lines.push(`- block \`${edge.owner}\` field \`${edge.fieldPath}\`${cap} accepts: ${edge.acceptedBlockSlugs.join(', ') || '(none)'}`);
128
124
  }
129
125
  lines.push('');
130
126
  }
131
- // Leaf blocks
132
- lines.push('## Leaf Blocks');
133
- for (const leaf of catalog.leaves){
134
- lines.push(`### ${leaf.slug}`);
135
- if (leaf.fields.length > 0) {
136
- for (const f of leaf.fields){
137
- lines.push(` - ${f.name} (${f.type})${f.required ? ' *required*' : ''}`);
127
+ lines.push('## Block fields');
128
+ lines.push('');
129
+ for (const block of catalog.blocks){
130
+ lines.push(`### ${block.slug}`);
131
+ if (block.fields.length === 0) {
132
+ lines.push('(no fields)');
133
+ } else {
134
+ for (const f of block.fields){
135
+ const parts = [
136
+ `- ${f.name} (${f.type})`
137
+ ];
138
+ if (f.required) parts.push(' *required*');
139
+ if (f.options?.length) {
140
+ parts.push(` [${f.options.map((o)=>o.value).join(', ')}]`);
141
+ }
142
+ if (f.relationTo) {
143
+ const targets = Array.isArray(f.relationTo) ? f.relationTo.join(', ') : f.relationTo;
144
+ parts.push(` → ${targets}`);
145
+ }
146
+ lines.push(parts.join(''));
138
147
  }
139
148
  }
140
149
  lines.push('');
@@ -184,7 +193,7 @@ function buildDraftWorkflowGuide(schemas) {
184
193
  lines.push('### How drafts work');
185
194
  lines.push('1. When you create or update a document in a draft-enabled collection, set `_status: "draft"` to keep it unpublished.');
186
195
  lines.push('2. Draft documents are only visible via preview URLs or the admin panel — they are not public.');
187
- lines.push('3. To publish a draft, update the document with `_status: "published"` or use the `publishDraft` tool.');
196
+ lines.push('3. To publish a draft, use the `publishDraft` tool (raw `update` is locked on always-draft collections).');
188
197
  lines.push('4. You can review a draft via its preview URL before publishing.');
189
198
  }
190
199
  lines.push('');
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/prompts.ts"],"sourcesContent":["import type {\n BlockCatalog,\n CollectionSchema,\n DomainPrompt,\n RelationshipEdge,\n} from './types'\n\n/**\n * Generate MCP prompts that teach the AI about the content model.\n *\n * Auto-generates three prompts (content model overview, block composition guide,\n * draft workflow guide) and merges with any user-provided domain prompts.\n */\nexport function generatePrompts(\n schemas: Map<string, CollectionSchema>,\n catalog: BlockCatalog,\n relationships: RelationshipEdge[],\n domainPrompts?: DomainPrompt[],\n) {\n const prompts = [\n buildContentModelOverview(schemas, relationships),\n buildBlockCompositionGuide(catalog),\n buildDraftWorkflowGuide(schemas),\n ]\n\n if (domainPrompts?.length) {\n for (const dp of domainPrompts) {\n prompts.push({\n name: dp.name,\n title: dp.title,\n description: dp.description,\n handler() {\n return {\n messages: [\n {\n content: { type: 'text' as const, text: dp.content },\n role: 'user' as const,\n },\n ],\n }\n },\n })\n }\n }\n\n return prompts\n}\n\n// ─── Prompt builders ──────────────────────────────────────────────\n\nfunction buildContentModelOverview(\n schemas: Map<string, CollectionSchema>,\n relationships: RelationshipEdge[],\n) {\n return {\n name: 'contentModelOverview',\n title: 'Content Model Overview',\n description:\n 'Describes every collection in the CMS — its purpose, fields, and relationships to other collections.',\n handler() {\n const lines: string[] = ['# Content Model Overview', '']\n\n for (const [slug, schema] of schemas) {\n lines.push(`## Collection: ${slug}`)\n lines.push(`Draft support: ${schema.hasDrafts ? 'yes' : 'no'}`)\n lines.push(`Live preview: ${schema.hasLivePreview ? 'yes' : 'no'}`)\n lines.push('')\n\n // Fields summary\n lines.push('### Fields')\n for (const field of schema.fields) {\n const parts = [`- **${field.name}** (${field.type})`]\n if (field.required) parts.push(' *required*')\n if (field.hasMany) parts.push(' hasMany')\n if (field.relationTo) {\n const targets = Array.isArray(field.relationTo)\n ? field.relationTo.join(', ')\n : field.relationTo\n parts.push(` → ${targets}`)\n }\n if (field.options?.length) {\n const vals = field.options.map((o) => o.value).join(', ')\n parts.push(` [${vals}]`)\n }\n if (field.maxRows) parts.push(` maxRows: ${field.maxRows}`)\n lines.push(parts.join(''))\n }\n lines.push('')\n\n // Relationships\n const collRels = relationships.filter((r) => r.fromCollection === slug)\n if (collRels.length > 0) {\n lines.push('### Relationships')\n for (const rel of collRels) {\n const targets = Array.isArray(rel.toCollection)\n ? rel.toCollection.join(', ')\n : rel.toCollection\n lines.push(\n `- ${rel.fieldName} → ${targets}${rel.hasMany ? ' (hasMany)' : ''}`,\n )\n }\n lines.push('')\n }\n }\n\n return {\n messages: [\n {\n content: { type: 'text' as const, text: lines.join('\\n') },\n role: 'user' as const,\n },\n ],\n }\n },\n }\n}\n\nfunction buildBlockCompositionGuide(catalog: BlockCatalog) {\n return {\n name: 'blockCompositionGuide',\n title: 'Block Composition Guide',\n description:\n 'Explains the section/leaf block hierarchy, valid nesting rules, and how to compose page layouts.',\n handler() {\n const lines: string[] = [\n '# Block Composition Guide',\n '',\n 'Pages are built from **section** blocks. Each section can contain **leaf** blocks according to its nesting rules.',\n '',\n ]\n\n // Section blocks\n lines.push('## Section Blocks')\n for (const section of catalog.sections) {\n lines.push(`### ${section.slug} (${section.nestingType})`)\n\n if (section.nestingType === 'fixed') {\n lines.push('This section has no nested blocks — configure it with its own fields only.')\n } else if (section.nestingType === 'constrained') {\n lines.push(\n `Accepts only: ${section.acceptedLeafSlugs.join(', ')}`,\n )\n if (section.maxRows) {\n lines.push(`Maximum ${section.maxRows} leaf block(s).`)\n }\n } else {\n lines.push(\n `Accepts all leaf blocks: ${section.acceptedLeafSlugs.join(', ')}`,\n )\n }\n\n if (section.fields.length > 0) {\n lines.push('Section-level fields:')\n for (const f of section.fields) {\n lines.push(` - ${f.name} (${f.type})${f.required ? ' *required*' : ''}`)\n }\n }\n lines.push('')\n }\n\n // Leaf blocks\n lines.push('## Leaf Blocks')\n for (const leaf of catalog.leaves) {\n lines.push(`### ${leaf.slug}`)\n if (leaf.fields.length > 0) {\n for (const f of leaf.fields) {\n lines.push(` - ${f.name} (${f.type})${f.required ? ' *required*' : ''}`)\n }\n }\n lines.push('')\n }\n\n return {\n messages: [\n {\n content: { type: 'text' as const, text: lines.join('\\n') },\n role: 'user' as const,\n },\n ],\n }\n },\n }\n}\n\nfunction buildDraftWorkflowGuide(schemas: Map<string, CollectionSchema>) {\n return {\n name: 'draftWorkflowGuide',\n title: 'Draft Workflow Guide',\n description:\n 'Explains which collections support drafts, how to create drafts, review them, and publish.',\n handler() {\n const draftCollections: string[] = []\n const publishCollections: string[] = []\n\n for (const [slug, schema] of schemas) {\n if (schema.hasDrafts) {\n draftCollections.push(slug)\n } else {\n publishCollections.push(slug)\n }\n }\n\n const lines: string[] = [\n '# Draft Workflow Guide',\n '',\n '## Collections with draft support',\n '',\n ]\n\n if (draftCollections.length === 0) {\n lines.push('No collections have draft support enabled.')\n } else {\n for (const slug of draftCollections) {\n lines.push(`- **${slug}**`)\n }\n lines.push('')\n lines.push('### How drafts work')\n lines.push(\n '1. When you create or update a document in a draft-enabled collection, set `_status: \"draft\"` to keep it unpublished.',\n )\n lines.push(\n '2. Draft documents are only visible via preview URLs or the admin panel — they are not public.',\n )\n lines.push(\n '3. To publish a draft, update the document with `_status: \"published\"` or use the `publishDraft` tool.',\n )\n lines.push(\n '4. You can review a draft via its preview URL before publishing.',\n )\n }\n\n lines.push('')\n lines.push('## Collections without draft support')\n lines.push('')\n\n if (publishCollections.length === 0) {\n lines.push('All collections support drafts.')\n } else {\n for (const slug of publishCollections) {\n lines.push(`- **${slug}** — changes are published immediately`)\n }\n }\n\n return {\n messages: [\n {\n content: { type: 'text' as const, text: lines.join('\\n') },\n role: 'user' as const,\n },\n ],\n }\n },\n }\n}\n"],"names":["generatePrompts","schemas","catalog","relationships","domainPrompts","prompts","buildContentModelOverview","buildBlockCompositionGuide","buildDraftWorkflowGuide","length","dp","push","name","title","description","handler","messages","content","type","text","role","lines","slug","schema","hasDrafts","hasLivePreview","field","fields","parts","required","hasMany","relationTo","targets","Array","isArray","join","options","vals","map","o","value","maxRows","collRels","filter","r","fromCollection","rel","toCollection","fieldName","section","sections","nestingType","acceptedLeafSlugs","f","leaf","leaves","draftCollections","publishCollections"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/prompts.ts"],"sourcesContent":["import type {\r\n BlockCatalog,\r\n BlockNestingMap,\r\n CollectionSchema,\r\n DomainPrompt,\r\n RelationshipEdge,\r\n} from './types'\r\n\r\n/**\r\n * Generate MCP prompts that teach the AI about the content model.\r\n *\r\n * Auto-generates content model overview, block composition guide, and\r\n * draft workflow guide. User-supplied domain prompts are appended.\r\n */\r\nexport function generatePrompts(\r\n schemas: Map<string, CollectionSchema>,\r\n catalog: BlockCatalog,\r\n nesting: BlockNestingMap,\r\n relationships: RelationshipEdge[],\r\n domainPrompts?: DomainPrompt[],\r\n) {\r\n const prompts = [\r\n buildContentModelOverview(schemas, relationships),\r\n buildBlockCompositionGuide(catalog, nesting),\r\n buildDraftWorkflowGuide(schemas),\r\n ]\r\n\r\n if (domainPrompts?.length) {\r\n for (const dp of domainPrompts) {\r\n prompts.push({\r\n name: dp.name,\r\n title: dp.title,\r\n description: dp.description,\r\n handler() {\r\n return {\r\n messages: [\r\n {\r\n content: { type: 'text' as const, text: dp.content },\r\n role: 'user' as const,\r\n },\r\n ],\r\n }\r\n },\r\n })\r\n }\r\n }\r\n\r\n return prompts\r\n}\r\n\r\n// ─── Prompt builders ──────────────────────────────────────────────\r\n\r\nfunction buildContentModelOverview(\r\n schemas: Map<string, CollectionSchema>,\r\n relationships: RelationshipEdge[],\r\n) {\r\n return {\r\n name: 'contentModelOverview',\r\n title: 'Content Model Overview',\r\n description:\r\n 'Describes every collection in the CMS — its purpose, fields, and relationships to other collections.',\r\n handler() {\r\n const lines: string[] = ['# Content Model Overview', '']\r\n\r\n for (const [slug, schema] of schemas) {\r\n lines.push(`## Collection: ${slug}`)\r\n lines.push(`Draft support: ${schema.hasDrafts ? 'yes' : 'no'}`)\r\n lines.push(`Live preview: ${schema.hasLivePreview ? 'yes' : 'no'}`)\r\n lines.push('')\r\n\r\n lines.push('### Fields')\r\n for (const field of schema.fields) {\r\n const parts = [`- **${field.name}** (${field.type})`]\r\n if (field.required) parts.push(' *required*')\r\n if (field.hasMany) parts.push(' hasMany')\r\n if (field.relationTo) {\r\n const targets = Array.isArray(field.relationTo)\r\n ? field.relationTo.join(', ')\r\n : field.relationTo\r\n parts.push(` → ${targets}`)\r\n }\r\n if (field.options?.length) {\r\n const vals = field.options.map((o) => o.value).join(', ')\r\n parts.push(` [${vals}]`)\r\n }\r\n if (field.maxRows) parts.push(` maxRows: ${field.maxRows}`)\r\n lines.push(parts.join(''))\r\n }\r\n lines.push('')\r\n\r\n const collRels = relationships.filter((r) => r.fromCollection === slug)\r\n if (collRels.length > 0) {\r\n lines.push('### Relationships')\r\n for (const rel of collRels) {\r\n const targets = Array.isArray(rel.toCollection)\r\n ? rel.toCollection.join(', ')\r\n : rel.toCollection\r\n lines.push(`- ${rel.fieldName} → ${targets}${rel.hasMany ? ' (hasMany)' : ''}`)\r\n }\r\n lines.push('')\r\n }\r\n }\r\n\r\n return {\r\n messages: [\r\n {\r\n content: { type: 'text' as const, text: lines.join('\\n') },\r\n role: 'user' as const,\r\n },\r\n ],\r\n }\r\n },\r\n }\r\n}\r\n\r\nfunction buildBlockCompositionGuide(catalog: BlockCatalog, nesting: BlockNestingMap) {\r\n return {\r\n name: 'blockCompositionGuide',\r\n title: 'Block Composition Guide',\r\n description:\r\n 'Lists every block type, its fields, and which slugs each blocks-typed field accepts. Use this with the relationshipGraph and collectionSchema resources to compose layouts at any depth.',\r\n handler() {\r\n const lines: string[] = [\r\n '# Block Composition Guide',\r\n '',\r\n 'Every block has a `blockType` discriminator plus its own fields. A block may include one or more `blocks`-typed fields that nest other blocks. The accepted slugs per field are listed below — recurse into the same map for deeper nesting.',\r\n '',\r\n ]\r\n\r\n lines.push('## Where blocks can nest')\r\n lines.push('')\r\n const collectionEdges = nesting.filter((e) => e.ownerType === 'collection')\r\n const blockEdges = nesting.filter((e) => e.ownerType === 'block')\r\n\r\n if (collectionEdges.length > 0) {\r\n lines.push('### In collections')\r\n for (const edge of collectionEdges) {\r\n const cap = edge.maxRows ? ` (max ${edge.maxRows})` : ''\r\n lines.push(\r\n `- \\`${edge.owner}.${edge.fieldPath}\\`${cap} accepts: ${edge.acceptedBlockSlugs.join(', ') || '(none)'}`,\r\n )\r\n }\r\n lines.push('')\r\n }\r\n\r\n if (blockEdges.length > 0) {\r\n lines.push('### In blocks (nested composition)')\r\n for (const edge of blockEdges) {\r\n const cap = edge.maxRows ? ` (max ${edge.maxRows})` : ''\r\n lines.push(\r\n `- block \\`${edge.owner}\\` field \\`${edge.fieldPath}\\`${cap} accepts: ${edge.acceptedBlockSlugs.join(', ') || '(none)'}`,\r\n )\r\n }\r\n lines.push('')\r\n }\r\n\r\n lines.push('## Block fields')\r\n lines.push('')\r\n for (const block of catalog.blocks) {\r\n lines.push(`### ${block.slug}`)\r\n if (block.fields.length === 0) {\r\n lines.push('(no fields)')\r\n } else {\r\n for (const f of block.fields) {\r\n const parts = [`- ${f.name} (${f.type})`]\r\n if (f.required) parts.push(' *required*')\r\n if (f.options?.length) {\r\n parts.push(` [${f.options.map((o) => o.value).join(', ')}]`)\r\n }\r\n if (f.relationTo) {\r\n const targets = Array.isArray(f.relationTo)\r\n ? f.relationTo.join(', ')\r\n : f.relationTo\r\n parts.push(` → ${targets}`)\r\n }\r\n lines.push(parts.join(''))\r\n }\r\n }\r\n lines.push('')\r\n }\r\n\r\n return {\r\n messages: [\r\n {\r\n content: { type: 'text' as const, text: lines.join('\\n') },\r\n role: 'user' as const,\r\n },\r\n ],\r\n }\r\n },\r\n }\r\n}\r\n\r\nfunction buildDraftWorkflowGuide(schemas: Map<string, CollectionSchema>) {\r\n return {\r\n name: 'draftWorkflowGuide',\r\n title: 'Draft Workflow Guide',\r\n description:\r\n 'Explains which collections support drafts, how to create drafts, review them, and publish.',\r\n handler() {\r\n const draftCollections: string[] = []\r\n const publishCollections: string[] = []\r\n\r\n for (const [slug, schema] of schemas) {\r\n if (schema.hasDrafts) {\r\n draftCollections.push(slug)\r\n } else {\r\n publishCollections.push(slug)\r\n }\r\n }\r\n\r\n const lines: string[] = ['# Draft Workflow Guide', '', '## Collections with draft support', '']\r\n\r\n if (draftCollections.length === 0) {\r\n lines.push('No collections have draft support enabled.')\r\n } else {\r\n for (const slug of draftCollections) {\r\n lines.push(`- **${slug}**`)\r\n }\r\n lines.push('')\r\n lines.push('### How drafts work')\r\n lines.push(\r\n '1. When you create or update a document in a draft-enabled collection, set `_status: \"draft\"` to keep it unpublished.',\r\n )\r\n lines.push(\r\n '2. Draft documents are only visible via preview URLs or the admin panel — they are not public.',\r\n )\r\n lines.push(\r\n '3. To publish a draft, use the `publishDraft` tool (raw `update` is locked on always-draft collections).',\r\n )\r\n lines.push('4. You can review a draft via its preview URL before publishing.')\r\n }\r\n\r\n lines.push('')\r\n lines.push('## Collections without draft support')\r\n lines.push('')\r\n\r\n if (publishCollections.length === 0) {\r\n lines.push('All collections support drafts.')\r\n } else {\r\n for (const slug of publishCollections) {\r\n lines.push(`- **${slug}** — changes are published immediately`)\r\n }\r\n }\r\n\r\n return {\r\n messages: [\r\n {\r\n content: { type: 'text' as const, text: lines.join('\\n') },\r\n role: 'user' as const,\r\n },\r\n ],\r\n }\r\n },\r\n }\r\n}\r\n"],"names":["generatePrompts","schemas","catalog","nesting","relationships","domainPrompts","prompts","buildContentModelOverview","buildBlockCompositionGuide","buildDraftWorkflowGuide","length","dp","push","name","title","description","handler","messages","content","type","text","role","lines","slug","schema","hasDrafts","hasLivePreview","field","fields","parts","required","hasMany","relationTo","targets","Array","isArray","join","options","vals","map","o","value","maxRows","collRels","filter","r","fromCollection","rel","toCollection","fieldName","collectionEdges","e","ownerType","blockEdges","edge","cap","owner","fieldPath","acceptedBlockSlugs","block","blocks","f","draftCollections","publishCollections"],"mappings":"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"}
@@ -1,9 +1,14 @@
1
- import type { BlockCatalog, CollectionSchema, RelationshipEdge } from './types';
1
+ import type { BlockCatalog, BlockNestingMap, CollectionSchema, RelationshipEdge } from './types';
2
2
  /**
3
- * Generate MCP resources that expose the block catalog,
4
- * collection schemas, and relationship graph as static JSON.
3
+ * Generate MCP resources that expose the introspected schema as static JSON.
4
+ *
5
+ * Four resources:
6
+ * - blocks://catalog — flat list of every block and its fields
7
+ * - blocks://nesting — per-blocks-field map of which slugs each field accepts
8
+ * - collections://schema — collection field metadata
9
+ * - collections://relationships — collection relationship graph
5
10
  */
6
- export declare function generateResources(schemas: Map<string, CollectionSchema>, catalog: BlockCatalog, relationships: RelationshipEdge[]): {
11
+ export declare function generateResources(schemas: Map<string, CollectionSchema>, catalog: BlockCatalog, nesting: BlockNestingMap, relationships: RelationshipEdge[]): {
7
12
  name: string;
8
13
  title: string;
9
14
  description: string;