payload-mcp-toolkit 0.3.4 → 0.7.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.
Files changed (144) hide show
  1. package/README.md +232 -151
  2. package/dist/__tests__/api-keys.test.js +292 -0
  3. package/dist/__tests__/api-keys.test.js.map +1 -0
  4. package/dist/__tests__/auth-strategy.test.js +681 -0
  5. package/dist/__tests__/auth-strategy.test.js.map +1 -0
  6. package/dist/__tests__/conflict-detection.test.js +69 -0
  7. package/dist/__tests__/conflict-detection.test.js.map +1 -0
  8. package/dist/__tests__/delete-document.test.js +70 -0
  9. package/dist/__tests__/delete-document.test.js.map +1 -0
  10. package/dist/__tests__/endpoint.test.js +143 -0
  11. package/dist/__tests__/endpoint.test.js.map +1 -0
  12. package/dist/__tests__/find-document.test.js +178 -0
  13. package/dist/__tests__/find-document.test.js.map +1 -0
  14. package/dist/__tests__/find-global.test.js +173 -0
  15. package/dist/__tests__/find-global.test.js.map +1 -0
  16. package/dist/__tests__/global-versions.test.js +183 -0
  17. package/dist/__tests__/global-versions.test.js.map +1 -0
  18. package/dist/__tests__/hash.test.js +58 -0
  19. package/dist/__tests__/hash.test.js.map +1 -0
  20. package/dist/__tests__/index-integration.test.js +191 -0
  21. package/dist/__tests__/index-integration.test.js.map +1 -0
  22. package/dist/__tests__/introspection.test.js +201 -1
  23. package/dist/__tests__/introspection.test.js.map +1 -1
  24. package/dist/__tests__/patch-global-layout.test.js +474 -0
  25. package/dist/__tests__/patch-global-layout.test.js.map +1 -0
  26. package/dist/__tests__/patch-layout.test.js +171 -0
  27. package/dist/__tests__/patch-layout.test.js.map +1 -0
  28. package/dist/__tests__/registry.test.js +795 -0
  29. package/dist/__tests__/registry.test.js.map +1 -0
  30. package/dist/__tests__/resources.test.js +139 -0
  31. package/dist/__tests__/resources.test.js.map +1 -0
  32. package/dist/__tests__/update-global.test.js +157 -0
  33. package/dist/__tests__/update-global.test.js.map +1 -0
  34. package/dist/api-keys.d.ts +46 -0
  35. package/dist/api-keys.js +272 -0
  36. package/dist/api-keys.js.map +1 -0
  37. package/dist/auth-strategy.d.ts +85 -0
  38. package/dist/auth-strategy.js +219 -0
  39. package/dist/auth-strategy.js.map +1 -0
  40. package/dist/components/CollectionScopesMatrix.d.ts +8 -0
  41. package/dist/components/CollectionScopesMatrix.js +32 -0
  42. package/dist/components/CollectionScopesMatrix.js.map +1 -0
  43. package/dist/components/GlobalScopesMatrix.d.ts +8 -0
  44. package/dist/components/GlobalScopesMatrix.js +28 -0
  45. package/dist/components/GlobalScopesMatrix.js.map +1 -0
  46. package/dist/components/ScopesTable.d.ts +19 -0
  47. package/dist/components/ScopesTable.js +285 -0
  48. package/dist/components/ScopesTable.js.map +1 -0
  49. package/dist/components/index.d.ts +2 -0
  50. package/dist/components/index.js +4 -0
  51. package/dist/components/index.js.map +1 -0
  52. package/dist/conflict-detection.d.ts +13 -0
  53. package/dist/conflict-detection.js +41 -0
  54. package/dist/conflict-detection.js.map +1 -0
  55. package/dist/draft-workflow.d.ts +46 -48
  56. package/dist/draft-workflow.js +53 -135
  57. package/dist/draft-workflow.js.map +1 -1
  58. package/dist/endpoint.d.ts +35 -0
  59. package/dist/endpoint.js +105 -0
  60. package/dist/endpoint.js.map +1 -0
  61. package/dist/hash.d.ts +21 -0
  62. package/dist/hash.js +36 -0
  63. package/dist/hash.js.map +1 -0
  64. package/dist/index.d.ts +9 -9
  65. package/dist/index.js +167 -69
  66. package/dist/index.js.map +1 -1
  67. package/dist/introspection.d.ts +17 -3
  68. package/dist/introspection.js +95 -36
  69. package/dist/introspection.js.map +1 -1
  70. package/dist/prompts.js +5 -5
  71. package/dist/prompts.js.map +1 -1
  72. package/dist/registry.d.ts +50 -0
  73. package/dist/registry.js +169 -0
  74. package/dist/registry.js.map +1 -0
  75. package/dist/resources.d.ts +5 -3
  76. package/dist/resources.js +23 -11
  77. package/dist/resources.js.map +1 -1
  78. package/dist/scope/audit-log.d.ts +18 -0
  79. package/dist/scope/audit-log.js +50 -0
  80. package/dist/scope/audit-log.js.map +1 -0
  81. package/dist/scope/policy.d.ts +73 -0
  82. package/dist/scope/policy.js +218 -0
  83. package/dist/scope/policy.js.map +1 -0
  84. package/dist/tools/_helpers.d.ts +28 -1
  85. package/dist/tools/_helpers.js +83 -0
  86. package/dist/tools/_helpers.js.map +1 -1
  87. package/dist/tools/_layout-helpers.d.ts +43 -0
  88. package/dist/tools/_layout-helpers.js +159 -0
  89. package/dist/tools/_layout-helpers.js.map +1 -0
  90. package/dist/tools/create-document.d.ts +5 -5
  91. package/dist/tools/create-document.js +25 -21
  92. package/dist/tools/create-document.js.map +1 -1
  93. package/dist/tools/delete-document.d.ts +25 -0
  94. package/dist/tools/delete-document.js +49 -0
  95. package/dist/tools/delete-document.js.map +1 -0
  96. package/dist/tools/find-document.d.ts +33 -0
  97. package/dist/tools/find-document.js +97 -0
  98. package/dist/tools/find-document.js.map +1 -0
  99. package/dist/tools/find-global.d.ts +26 -0
  100. package/dist/tools/find-global.js +122 -0
  101. package/dist/tools/find-global.js.map +1 -0
  102. package/dist/tools/global-versions.d.ts +39 -0
  103. package/dist/tools/global-versions.js +132 -0
  104. package/dist/tools/global-versions.js.map +1 -0
  105. package/dist/tools/patch-global-layout.d.ts +31 -0
  106. package/dist/tools/patch-global-layout.js +127 -0
  107. package/dist/tools/patch-global-layout.js.map +1 -0
  108. package/dist/tools/patch-layout.d.ts +5 -8
  109. package/dist/tools/patch-layout.js +18 -100
  110. package/dist/tools/patch-layout.js.map +1 -1
  111. package/dist/tools/publish-draft.d.ts +5 -4
  112. package/dist/tools/publish-draft.js +6 -1
  113. package/dist/tools/publish-draft.js.map +1 -1
  114. package/dist/tools/publish-global-draft.d.ts +20 -0
  115. package/dist/tools/publish-global-draft.js +50 -0
  116. package/dist/tools/publish-global-draft.js.map +1 -0
  117. package/dist/tools/resolve-reference.d.ts +5 -4
  118. package/dist/tools/resolve-reference.js +4 -0
  119. package/dist/tools/resolve-reference.js.map +1 -1
  120. package/dist/tools/safe-delete.d.ts +5 -5
  121. package/dist/tools/safe-delete.js +20 -15
  122. package/dist/tools/safe-delete.js.map +1 -1
  123. package/dist/tools/schedule-publish.d.ts +5 -5
  124. package/dist/tools/schedule-publish.js +23 -19
  125. package/dist/tools/schedule-publish.js.map +1 -1
  126. package/dist/tools/search-content.d.ts +5 -9
  127. package/dist/tools/search-content.js +16 -12
  128. package/dist/tools/search-content.js.map +1 -1
  129. package/dist/tools/update-document.d.ts +5 -5
  130. package/dist/tools/update-document.js +10 -5
  131. package/dist/tools/update-document.js.map +1 -1
  132. package/dist/tools/update-global.d.ts +27 -0
  133. package/dist/tools/update-global.js +72 -0
  134. package/dist/tools/update-global.js.map +1 -0
  135. package/dist/tools/upload-media.d.ts +5 -4
  136. package/dist/tools/upload-media.js +6 -1
  137. package/dist/tools/upload-media.js.map +1 -1
  138. package/dist/tools/versions.d.ts +10 -9
  139. package/dist/tools/versions.js +15 -7
  140. package/dist/tools/versions.js.map +1 -1
  141. package/dist/types.d.ts +56 -3
  142. package/dist/types.js +13 -6
  143. package/dist/types.js.map +1 -1
  144. package/package.json +11 -4
package/dist/index.js CHANGED
@@ -1,9 +1,20 @@
1
- import { mcpPlugin } from '@payloadcms/plugin-mcp';
2
- import { introspectCollections, introspectBlocks, buildBlockNestingMap, buildRelationshipGraph } from './introspection';
1
+ import { introspectCollections, introspectGlobals, introspectBlocks, buildBlockNestingMap, buildRelationshipGraph } from './introspection';
3
2
  import { generatePrompts } from './prompts';
4
3
  import { generateResources } from './resources';
5
- import { generateMcpCollectionConfigs } from './draft-workflow';
4
+ import { computeDraftCollections, computeDraftGlobals } from './draft-workflow';
5
+ import { createApiKeysCollection, API_KEYS_DEFAULT_SLUG } from './api-keys';
6
+ import { createBearerStrategy } from './auth-strategy';
7
+ import { createMcpEndpoints } from './endpoint';
8
+ import { createInitializeServer } from './registry';
9
+ import { assertNoSlugConflict, assertNoUpstreamPlugin } from './conflict-detection';
6
10
  import { createCreateDocumentTool } from './tools/create-document';
11
+ import { createDeleteDocumentTool } from './tools/delete-document';
12
+ import { createFindDocumentTool } from './tools/find-document';
13
+ import { createFindGlobalTool } from './tools/find-global';
14
+ import { createUpdateGlobalTool } from './tools/update-global';
15
+ import { createPatchGlobalLayoutTool } from './tools/patch-global-layout';
16
+ import { createPublishGlobalDraftTool } from './tools/publish-global-draft';
17
+ import { createListGlobalVersionsTool, createRestoreGlobalVersionTool } from './tools/global-versions';
7
18
  import { createPatchLayoutTool } from './tools/patch-layout';
8
19
  import { createPublishDraftTool } from './tools/publish-draft';
9
20
  import { createResolveReferenceTool } from './tools/resolve-reference';
@@ -13,53 +24,102 @@ import { createSearchContentTool } from './tools/search-content';
13
24
  import { createUpdateDocumentTool } from './tools/update-document';
14
25
  import { createUploadMediaTool } from './tools/upload-media';
15
26
  import { createListVersionsTool, createRestoreVersionTool } from './tools/versions';
16
- /**
17
- * Payload MCP Toolkit
18
- *
19
- * Layered on top of the official @payloadcms/plugin-mcp. The toolkit
20
- * introspects your Payload config and registers schema-aware prompts,
21
- * resources, and tools so AI clients can drive the CMS without
22
- * hand-built plumbing.
23
- *
24
- * Zero-config usage:
25
- * ```ts
26
- * plugins: [contentToolkitPlugin()]
27
- * ```
28
- *
29
- * Every option below is an optional escape hatch — see ContentToolkitOptions.
30
- */ export function contentToolkitPlugin(options = {}) {
27
+ /**
28
+ * Resolves the user collection slug for API-key linkage.
29
+ *
30
+ * Resolution order: explicit `options.apiKeyCollection.userCollection`
31
+ * explicit `options.userCollection` `incomingConfig.admin.user`
32
+ * 'users' as a last resort.
33
+ */ function resolveUserCollection(options, incomingConfig) {
34
+ return options.apiKeyCollection?.userCollection ?? options.userCollection ?? incomingConfig.admin?.user ?? 'users';
35
+ }
36
+ /**
37
+ * payload-mcp-toolkit — standalone Payload v3 MCP plugin.
38
+ *
39
+ * Owns the `/api/mcp` endpoint, the `payload-mcp-api-keys` collection,
40
+ * bearer authentication via Payload's `auth.strategies` extension point,
41
+ * and the per-tool scope check. Upstream `@payloadcms/plugin-mcp` is no
42
+ * longer required (and is incompatible — see `assertNoUpstreamPlugin`).
43
+ *
44
+ * Zero-config usage:
45
+ * ```ts
46
+ * plugins: [mcpToolkitPlugin()]
47
+ * ```
48
+ *
49
+ * See `ContentToolkitOptions` for the (entirely optional) escape hatches.
50
+ */ export function mcpToolkitPlugin(options = {}) {
31
51
  return (incomingConfig)=>{
52
+ const apiKeysSlug = options.apiKeyCollection?.slug ?? API_KEYS_DEFAULT_SLUG;
53
+ // Conflict detection — fail fast with actionable messages.
54
+ assertNoUpstreamPlugin(incomingConfig.plugins);
55
+ assertNoSlugConflict(incomingConfig.collections, apiKeysSlug);
32
56
  const collections = incomingConfig.collections ?? [];
57
+ const globals = incomingConfig.globals ?? [];
33
58
  const allBlocks = incomingConfig.blocks ?? [];
34
59
  const collectionSchemas = introspectCollections(collections);
60
+ const globalSchemas = introspectGlobals(globals);
35
61
  const blockCatalog = introspectBlocks(allBlocks);
36
- const blockNesting = buildBlockNestingMap(collections, allBlocks);
37
62
  const relationships = buildRelationshipGraph(collectionSchemas);
38
- // Preview siteUrl resolves: explicit option Payload serverURL env vars.
39
- // May be undefined; relative-path preview URLs are skipped in that case.
40
- const previewSiteUrl = options.preview?.siteUrl ?? incomingConfig.serverURL ?? process.env.NEXT_PUBLIC_SERVER_URL ?? process.env.SITE_URL;
41
- const prompts = generatePrompts(collectionSchemas, blockCatalog, blockNesting, relationships, options.domainPrompts);
42
- const resources = generateResources(collectionSchemas, blockCatalog, blockNesting, relationships);
43
- const { mcpCollections, draftCollections } = generateMcpCollectionConfigs(collections, {
44
- siteUrl: previewSiteUrl,
63
+ const previewSiteUrl = options.preview?.disabled ? undefined : options.preview?.siteUrl ?? incomingConfig.serverURL ?? process.env.NEXT_PUBLIC_SERVER_URL ?? process.env.SITE_URL;
64
+ const previewDisabled = options.preview?.disabled === true;
65
+ const { draftCollections, excluded } = computeDraftCollections(collections, {
45
66
  draftBehavior: options.draftBehavior,
46
67
  excludeCollections: options.exclude?.collections,
47
- previewDisabled: options.preview?.disabled
68
+ apiKeysSlug
48
69
  });
49
- const searchableCollections = new Map();
70
+ const { draftGlobals, excluded: excludedGlobals } = computeDraftGlobals(globals, {
71
+ draftBehavior: options.draftBehavior,
72
+ excludeGlobals: options.exclude?.globals
73
+ });
74
+ // Build blockNesting from exclusion-filtered inputs so excluded
75
+ // collections/globals never appear in patchLayout/patchGlobalLayout slug
76
+ // enums or in the `blocks://nesting` resource body.
77
+ const exposedCollectionsForNesting = collections.filter((c)=>!excluded.has(c.slug));
78
+ const exposedGlobalsForNesting = globals.filter((g)=>!excludedGlobals.has(g.slug));
79
+ const blockNesting = buildBlockNestingMap(exposedCollectionsForNesting, exposedGlobalsForNesting, allBlocks);
80
+ // Build a slug → CollectionConfig map for tools that need access to
81
+ // collection-level admin config (preview functions, etc).
82
+ const collectionsBySlug = new Map();
83
+ for (const c of collections){
84
+ if (!excluded.has(c.slug)) collectionsBySlug.set(c.slug, c);
85
+ }
86
+ // Schemas-without-excluded view, used by the polymorphic tool factories
87
+ // so excluded collections don't appear in tool descriptions.
88
+ const exposedSchemas = new Map();
50
89
  for (const [slug, schema] of collectionSchemas){
90
+ if (!excluded.has(slug)) exposedSchemas.set(slug, schema);
91
+ }
92
+ // Same shape for globals — excluded slugs are stripped at registration
93
+ // time so they never reach Zod input enums, the globals://schema
94
+ // resource, or availableGlobals on the admin matrix. composeScopes
95
+ // stays exclusion-unaware (mirrors the collection mechanism).
96
+ const exposedGlobalSchemas = new Map();
97
+ const globalsBySlug = new Map();
98
+ for (const [slug, schema] of globalSchemas){
99
+ if (excludedGlobals.has(slug)) continue;
100
+ exposedGlobalSchemas.set(slug, schema);
101
+ }
102
+ for (const g of globals){
103
+ if (!excludedGlobals.has(g.slug)) globalsBySlug.set(g.slug, g);
104
+ }
105
+ const prompts = generatePrompts(exposedSchemas, blockCatalog, blockNesting, relationships, options.domainPrompts);
106
+ const resources = generateResources(exposedSchemas, blockCatalog, blockNesting, relationships, exposedGlobalSchemas);
107
+ const searchableCollections = new Map();
108
+ for (const [slug, schema] of exposedSchemas){
51
109
  if (schema.searchableFields.length > 0) {
52
110
  searchableCollections.set(slug, schema.searchableFields);
53
111
  }
54
112
  }
55
113
  const tools = [
56
- createCreateDocumentTool(collectionSchemas, draftCollections),
114
+ createCreateDocumentTool(exposedSchemas, draftCollections),
115
+ createDeleteDocumentTool(exposedSchemas),
116
+ createFindDocumentTool(exposedSchemas, draftCollections, collectionsBySlug, previewSiteUrl, previewDisabled),
57
117
  createPatchLayoutTool(blockCatalog, blockNesting, draftCollections),
58
118
  createPublishDraftTool(draftCollections),
59
119
  createResolveReferenceTool(searchableCollections),
60
120
  createSafeDeleteTool(relationships),
61
- createSearchContentTool(collectionSchemas),
62
- createUpdateDocumentTool(collectionSchemas, draftCollections),
121
+ createSearchContentTool(exposedSchemas),
122
+ createUpdateDocumentTool(exposedSchemas, draftCollections),
63
123
  createUploadMediaTool({
64
124
  maxFileSize: options.mediaUpload?.maxFileSize,
65
125
  collectionSlug: options.mediaUpload?.collectionSlug
@@ -67,47 +127,85 @@ import { createListVersionsTool, createRestoreVersionTool } from './tools/versio
67
127
  createListVersionsTool(draftCollections),
68
128
  createRestoreVersionTool(draftCollections)
69
129
  ];
70
- const schedulePublish = createSchedulePublishTool(collectionSchemas, draftCollections);
130
+ const schedulePublish = createSchedulePublishTool(exposedSchemas, draftCollections);
71
131
  if (schedulePublish) tools.push(schedulePublish);
72
- // Globals get `find` only. The official plugin's `update<Global>` tool
73
- // hits the same `convertCollectionSchemaToZod` path that crashes on
74
- // richText / upload / blocks fields (here it throws
75
- // `Cannot convert undefined or null to object` because globals/update.ts
76
- // calls `Object.entries(convertedFields.shape)` and the fallback
77
- // `z.record()` has no `.shape`). Until the toolkit's `updateDocument`
78
- // gains global support, edit globals via the admin panel.
79
- const mcpGlobals = {};
80
- const excludeGlobalSlugs = new Set(options.exclude?.globals ?? []);
81
- for (const global of incomingConfig.globals ?? []){
82
- if (excludeGlobalSlugs.has(global.slug)) continue;
83
- mcpGlobals[global.slug] = {
84
- enabled: {
85
- find: true,
86
- update: false
87
- },
88
- description: `Read ${global.slug} global settings`
89
- };
132
+ // Global tools registered only when at least one global is exposed.
133
+ if (exposedGlobalSchemas.size > 0) {
134
+ tools.push(createFindGlobalTool(exposedGlobalSchemas, draftGlobals, globalsBySlug, previewSiteUrl, previewDisabled), createUpdateGlobalTool(exposedGlobalSchemas, draftGlobals));
135
+ const patchGlobalLayout = createPatchGlobalLayoutTool(blockCatalog, blockNesting, draftGlobals);
136
+ if (patchGlobalLayout) tools.push(patchGlobalLayout);
137
+ const publishGlobalDraft = createPublishGlobalDraftTool(draftGlobals);
138
+ if (publishGlobalDraft) tools.push(publishGlobalDraft);
139
+ const listGlobalVersions = createListGlobalVersionsTool(draftGlobals);
140
+ if (listGlobalVersions) tools.push(listGlobalVersions);
141
+ const restoreGlobalVersion = createRestoreGlobalVersionTool(draftGlobals);
142
+ if (restoreGlobalVersion) tools.push(restoreGlobalVersion);
90
143
  }
91
- // overrideAuth rebinds req.user from the API key's linked user so our
92
- // custom tools' `overrideAccess: false` checks run against the right
93
- // identity. userCollection passthrough lets the official plugin fall
94
- // back to `incomingConfig.admin.user`.
95
- const withMcp = mcpPlugin({
96
- collections: mcpCollections,
97
- globals: mcpGlobals,
98
- userCollection: options.userCollection,
99
- mcp: {
100
- tools: tools,
101
- prompts: prompts,
102
- resources: resources
103
- },
104
- overrideAuth: async (req, getDefault)=>{
105
- const settings = await getDefault();
106
- req.user = settings.user;
107
- return settings;
108
- }
144
+ // Build the per-request initializer that mcp-handler invokes.
145
+ const buildInitializeServer = createInitializeServer({
146
+ tools,
147
+ prompts: prompts,
148
+ resources: resources
149
+ });
150
+ // Attach API-keys collection. Available collections / tools are
151
+ // snapshotted now so the admin UI's scope dropdowns reflect the host
152
+ // config at boot time. Adding a collection requires a dev restart for
153
+ // it to surface as a scope option.
154
+ const userCollection = resolveUserCollection(options, incomingConfig);
155
+ const availableCollections = collections.map((c)=>c.slug).filter((s)=>s !== apiKeysSlug);
156
+ const availableGlobals = [
157
+ ...exposedGlobalSchemas.keys()
158
+ ];
159
+ const availableTools = tools.map((t)=>t.name);
160
+ const apiKeysCollection = createApiKeysCollection({
161
+ slug: apiKeysSlug,
162
+ userCollection,
163
+ availableCollections,
164
+ availableGlobals,
165
+ availableTools
166
+ });
167
+ const updatedCollections = [
168
+ ...collections,
169
+ apiKeysCollection
170
+ ];
171
+ // Attach the bearer strategy to the user collection's auth config.
172
+ const bearerStrategy = createBearerStrategy({
173
+ collectionSlug: apiKeysSlug,
174
+ userCollection
175
+ });
176
+ const collectionsWithStrategy = updatedCollections.map((c)=>{
177
+ if (c.slug !== userCollection) return c;
178
+ const existingAuth = c.auth;
179
+ if (!existingAuth) return c;
180
+ const authConfig = typeof existingAuth === 'object' && existingAuth !== null ? {
181
+ ...existingAuth
182
+ } : {
183
+ useAPIKey: existingAuth === true ? false : false
184
+ };
185
+ const existingStrategies = Array.isArray(authConfig.strategies) ? authConfig.strategies : [];
186
+ authConfig.strategies = [
187
+ ...existingStrategies,
188
+ bearerStrategy
189
+ ];
190
+ return {
191
+ ...c,
192
+ auth: authConfig
193
+ };
194
+ });
195
+ // Attach the MCP endpoints additively to the host config.
196
+ const mcpEndpoints = createMcpEndpoints({
197
+ buildInitializeServer,
198
+ allowedOrigins: options.auth?.allowedOrigins,
199
+ serverURL: incomingConfig.serverURL
109
200
  });
110
- return withMcp(incomingConfig);
201
+ return {
202
+ ...incomingConfig,
203
+ collections: collectionsWithStrategy,
204
+ endpoints: [
205
+ ...incomingConfig.endpoints ?? [],
206
+ ...mcpEndpoints
207
+ ]
208
+ };
111
209
  };
112
210
  }
113
211
 
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 {\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 { createCreateDocumentTool } from './tools/create-document'\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 const collectionSchemas = introspectCollections(collections)\n const blockCatalog = introspectBlocks(allBlocks)\n const blockNesting = buildBlockNestingMap(collections, allBlocks)\n const relationships = buildRelationshipGraph(collectionSchemas)\n\n // Preview siteUrl resolves: explicit option → Payload serverURL → env vars.\n // May be undefined; 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 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 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 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 const tools: any[] = [\n createCreateDocumentTool(collectionSchemas, draftCollections),\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 // Globals get `find` only. The official plugin's `update<Global>` tool\n // hits the same `convertCollectionSchemaToZod` path that crashes on\n // richText / upload / blocks fields (here it throws\n // `Cannot convert undefined or null to object` because globals/update.ts\n // calls `Object.entries(convertedFields.shape)` and the fallback\n // `z.record()` has no `.shape`). Until the toolkit's `updateDocument`\n // gains global support, edit globals via the admin panel.\n const mcpGlobals: Record<\n string,\n { enabled: { find: boolean; update: boolean }; description?: string }\n > = {}\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: { find: true, update: false },\n description: `Read ${global.slug} global settings`,\n }\n }\n\n // overrideAuth rebinds req.user from the API key's linked user so our\n // custom tools' `overrideAccess: false` checks run against the right\n // identity. userCollection passthrough lets the official plugin fall\n // back to `incomingConfig.admin.user`.\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","createCreateDocumentTool","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","find","update","description","withMcp","userCollection","mcp","overrideAuth","req","getDefault","settings","user"],"mappings":"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"}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { Block, CollectionConfig, Config, GlobalConfig, Plugin } from 'payload'\r\nimport type { ContentToolkitOptions, GlobalSchema } from './types'\r\nimport {\r\n introspectCollections,\r\n introspectGlobals,\r\n introspectBlocks,\r\n buildBlockNestingMap,\r\n buildRelationshipGraph,\r\n} from './introspection'\r\nimport { generatePrompts } from './prompts'\r\nimport { generateResources } from './resources'\r\nimport { computeDraftCollections, computeDraftGlobals } from './draft-workflow'\r\nimport { createApiKeysCollection, API_KEYS_DEFAULT_SLUG } from './api-keys'\r\nimport { createBearerStrategy } from './auth-strategy'\r\nimport { createMcpEndpoints } from './endpoint'\r\nimport {\r\n createInitializeServer,\r\n type ToolFactoryOutput,\r\n} from './registry'\r\nimport { assertNoSlugConflict, assertNoUpstreamPlugin } from './conflict-detection'\r\nimport { createCreateDocumentTool } from './tools/create-document'\r\nimport { createDeleteDocumentTool } from './tools/delete-document'\r\nimport { createFindDocumentTool } from './tools/find-document'\r\nimport { createFindGlobalTool } from './tools/find-global'\r\nimport { createUpdateGlobalTool } from './tools/update-global'\r\nimport { createPatchGlobalLayoutTool } from './tools/patch-global-layout'\r\nimport { createPublishGlobalDraftTool } from './tools/publish-global-draft'\r\nimport {\r\n createListGlobalVersionsTool,\r\n createRestoreGlobalVersionTool,\r\n} from './tools/global-versions'\r\nimport { createPatchLayoutTool } from './tools/patch-layout'\r\nimport { createPublishDraftTool } from './tools/publish-draft'\r\nimport { createResolveReferenceTool } from './tools/resolve-reference'\r\nimport { createSafeDeleteTool } from './tools/safe-delete'\r\nimport { createSchedulePublishTool } from './tools/schedule-publish'\r\nimport { createSearchContentTool } from './tools/search-content'\r\nimport { createUpdateDocumentTool } from './tools/update-document'\r\nimport { createUploadMediaTool } from './tools/upload-media'\r\nimport { createListVersionsTool, createRestoreVersionTool } from './tools/versions'\r\n\r\n/**\r\n * Resolves the user collection slug for API-key linkage.\r\n *\r\n * Resolution order: explicit `options.apiKeyCollection.userCollection` →\r\n * explicit `options.userCollection` → `incomingConfig.admin.user` →\r\n * 'users' as a last resort.\r\n */\r\nfunction resolveUserCollection(\r\n options: ContentToolkitOptions,\r\n incomingConfig: Config,\r\n): string {\r\n return (\r\n options.apiKeyCollection?.userCollection ??\r\n options.userCollection ??\r\n (incomingConfig.admin?.user as string | undefined) ??\r\n 'users'\r\n )\r\n}\r\n\r\n/**\r\n * payload-mcp-toolkit — standalone Payload v3 MCP plugin.\r\n *\r\n * Owns the `/api/mcp` endpoint, the `payload-mcp-api-keys` collection,\r\n * bearer authentication via Payload's `auth.strategies` extension point,\r\n * and the per-tool scope check. Upstream `@payloadcms/plugin-mcp` is no\r\n * longer required (and is incompatible — see `assertNoUpstreamPlugin`).\r\n *\r\n * Zero-config usage:\r\n * ```ts\r\n * plugins: [mcpToolkitPlugin()]\r\n * ```\r\n *\r\n * See `ContentToolkitOptions` for the (entirely optional) escape hatches.\r\n */\r\nexport function mcpToolkitPlugin(options: ContentToolkitOptions = {}): Plugin {\r\n return (incomingConfig: Config): Config => {\r\n const apiKeysSlug = options.apiKeyCollection?.slug ?? API_KEYS_DEFAULT_SLUG\r\n\r\n // Conflict detection — fail fast with actionable messages.\r\n assertNoUpstreamPlugin(incomingConfig.plugins)\r\n assertNoSlugConflict(incomingConfig.collections as CollectionConfig[] | undefined, apiKeysSlug)\r\n\r\n const collections = (incomingConfig.collections ?? []) as CollectionConfig[]\r\n const globals = (incomingConfig.globals ?? []) as GlobalConfig[]\r\n const allBlocks = (incomingConfig.blocks ?? []) as Block[]\r\n\r\n const collectionSchemas = introspectCollections(collections)\r\n const globalSchemas = introspectGlobals(globals)\r\n const blockCatalog = introspectBlocks(allBlocks)\r\n const relationships = buildRelationshipGraph(collectionSchemas)\r\n\r\n const previewSiteUrl = options.preview?.disabled\r\n ? undefined\r\n : options.preview?.siteUrl ??\r\n incomingConfig.serverURL ??\r\n process.env.NEXT_PUBLIC_SERVER_URL ??\r\n process.env.SITE_URL\r\n const previewDisabled = options.preview?.disabled === true\r\n\r\n const { draftCollections, excluded } = computeDraftCollections(collections, {\r\n draftBehavior: options.draftBehavior,\r\n excludeCollections: options.exclude?.collections,\r\n apiKeysSlug,\r\n })\r\n\r\n const { draftGlobals, excluded: excludedGlobals } = computeDraftGlobals(globals, {\r\n draftBehavior: options.draftBehavior,\r\n excludeGlobals: options.exclude?.globals,\r\n })\r\n\r\n // Build blockNesting from exclusion-filtered inputs so excluded\r\n // collections/globals never appear in patchLayout/patchGlobalLayout slug\r\n // enums or in the `blocks://nesting` resource body.\r\n const exposedCollectionsForNesting = collections.filter((c) => !excluded.has(c.slug))\r\n const exposedGlobalsForNesting = globals.filter((g) => !excludedGlobals.has(g.slug))\r\n const blockNesting = buildBlockNestingMap(\r\n exposedCollectionsForNesting,\r\n exposedGlobalsForNesting,\r\n allBlocks,\r\n )\r\n\r\n // Build a slug → CollectionConfig map for tools that need access to\r\n // collection-level admin config (preview functions, etc).\r\n const collectionsBySlug = new Map<string, CollectionConfig>()\r\n for (const c of collections) {\r\n if (!excluded.has(c.slug)) collectionsBySlug.set(c.slug, c)\r\n }\r\n\r\n // Schemas-without-excluded view, used by the polymorphic tool factories\r\n // so excluded collections don't appear in tool descriptions.\r\n const exposedSchemas = new Map<string, ReturnType<typeof introspectCollections> extends Map<string, infer V> ? V : never>()\r\n for (const [slug, schema] of collectionSchemas) {\r\n if (!excluded.has(slug)) exposedSchemas.set(slug, schema)\r\n }\r\n\r\n // Same shape for globals — excluded slugs are stripped at registration\r\n // time so they never reach Zod input enums, the globals://schema\r\n // resource, or availableGlobals on the admin matrix. composeScopes\r\n // stays exclusion-unaware (mirrors the collection mechanism).\r\n const exposedGlobalSchemas = new Map<string, GlobalSchema>()\r\n const globalsBySlug = new Map<string, GlobalConfig>()\r\n for (const [slug, schema] of globalSchemas) {\r\n if (excludedGlobals.has(slug)) continue\r\n exposedGlobalSchemas.set(slug, schema)\r\n }\r\n for (const g of globals) {\r\n if (!excludedGlobals.has(g.slug)) globalsBySlug.set(g.slug, g)\r\n }\r\n\r\n const prompts = generatePrompts(\r\n exposedSchemas,\r\n blockCatalog,\r\n blockNesting,\r\n relationships,\r\n options.domainPrompts,\r\n )\r\n const resources = generateResources(\r\n exposedSchemas,\r\n blockCatalog,\r\n blockNesting,\r\n relationships,\r\n exposedGlobalSchemas,\r\n )\r\n\r\n const searchableCollections = new Map<string, string[]>()\r\n for (const [slug, schema] of exposedSchemas) {\r\n if (schema.searchableFields.length > 0) {\r\n searchableCollections.set(slug, schema.searchableFields)\r\n }\r\n }\r\n\r\n const tools: ToolFactoryOutput[] = [\r\n createCreateDocumentTool(exposedSchemas, draftCollections),\r\n createDeleteDocumentTool(exposedSchemas),\r\n createFindDocumentTool(\r\n exposedSchemas,\r\n draftCollections,\r\n collectionsBySlug,\r\n previewSiteUrl,\r\n previewDisabled,\r\n ),\r\n createPatchLayoutTool(blockCatalog, blockNesting, draftCollections),\r\n createPublishDraftTool(draftCollections),\r\n createResolveReferenceTool(searchableCollections),\r\n createSafeDeleteTool(relationships),\r\n createSearchContentTool(exposedSchemas),\r\n createUpdateDocumentTool(exposedSchemas, draftCollections),\r\n createUploadMediaTool({\r\n maxFileSize: options.mediaUpload?.maxFileSize,\r\n collectionSlug: options.mediaUpload?.collectionSlug,\r\n }),\r\n createListVersionsTool(draftCollections),\r\n createRestoreVersionTool(draftCollections),\r\n ]\r\n\r\n const schedulePublish = createSchedulePublishTool(exposedSchemas, draftCollections)\r\n if (schedulePublish) tools.push(schedulePublish)\r\n\r\n // Global tools — registered only when at least one global is exposed.\r\n if (exposedGlobalSchemas.size > 0) {\r\n tools.push(\r\n createFindGlobalTool(\r\n exposedGlobalSchemas,\r\n draftGlobals,\r\n globalsBySlug,\r\n previewSiteUrl,\r\n previewDisabled,\r\n ),\r\n createUpdateGlobalTool(exposedGlobalSchemas, draftGlobals),\r\n )\r\n\r\n const patchGlobalLayout = createPatchGlobalLayoutTool(blockCatalog, blockNesting, draftGlobals)\r\n if (patchGlobalLayout) tools.push(patchGlobalLayout)\r\n\r\n const publishGlobalDraft = createPublishGlobalDraftTool(draftGlobals)\r\n if (publishGlobalDraft) tools.push(publishGlobalDraft)\r\n\r\n const listGlobalVersions = createListGlobalVersionsTool(draftGlobals)\r\n if (listGlobalVersions) tools.push(listGlobalVersions)\r\n\r\n const restoreGlobalVersion = createRestoreGlobalVersionTool(draftGlobals)\r\n if (restoreGlobalVersion) tools.push(restoreGlobalVersion)\r\n }\r\n\r\n // Build the per-request initializer that mcp-handler invokes.\r\n const buildInitializeServer = createInitializeServer({\r\n tools,\r\n prompts: prompts as never,\r\n resources: resources as never,\r\n })\r\n\r\n // Attach API-keys collection. Available collections / tools are\r\n // snapshotted now so the admin UI's scope dropdowns reflect the host\r\n // config at boot time. Adding a collection requires a dev restart for\r\n // it to surface as a scope option.\r\n const userCollection = resolveUserCollection(options, incomingConfig)\r\n const availableCollections = collections\r\n .map((c) => c.slug)\r\n .filter((s) => s !== apiKeysSlug)\r\n const availableGlobals = [...exposedGlobalSchemas.keys()]\r\n const availableTools = tools.map((t) => t.name)\r\n const apiKeysCollection = createApiKeysCollection({\r\n slug: apiKeysSlug,\r\n userCollection,\r\n availableCollections,\r\n availableGlobals,\r\n availableTools,\r\n })\r\n const updatedCollections: CollectionConfig[] = [...collections, apiKeysCollection]\r\n\r\n // Attach the bearer strategy to the user collection's auth config.\r\n const bearerStrategy = createBearerStrategy({\r\n collectionSlug: apiKeysSlug,\r\n userCollection,\r\n })\r\n const collectionsWithStrategy = updatedCollections.map((c) => {\r\n if (c.slug !== userCollection) return c\r\n const existingAuth = c.auth\r\n if (!existingAuth) return c\r\n const authConfig =\r\n typeof existingAuth === 'object' && existingAuth !== null\r\n ? { ...existingAuth }\r\n : { useAPIKey: existingAuth === true ? false : false }\r\n const existingStrategies = Array.isArray((authConfig as { strategies?: unknown[] }).strategies)\r\n ? ((authConfig as { strategies: unknown[] }).strategies as unknown[])\r\n : []\r\n ;(authConfig as { strategies: unknown[] }).strategies = [\r\n ...existingStrategies,\r\n bearerStrategy,\r\n ]\r\n return { ...c, auth: authConfig } as CollectionConfig\r\n })\r\n\r\n // Attach the MCP endpoints additively to the host config.\r\n const mcpEndpoints = createMcpEndpoints({\r\n buildInitializeServer,\r\n allowedOrigins: options.auth?.allowedOrigins,\r\n serverURL: incomingConfig.serverURL,\r\n })\r\n\r\n return {\r\n ...incomingConfig,\r\n collections: collectionsWithStrategy,\r\n endpoints: [...(incomingConfig.endpoints ?? []), ...mcpEndpoints],\r\n }\r\n }\r\n}\r\n\r\nexport type {\r\n ContentToolkitOptions,\r\n DomainPrompt,\r\n CollectionSchema,\r\n GlobalSchema,\r\n BlockCatalog,\r\n BlockSchema,\r\n BlockNestingMap,\r\n BlockNestingEdge,\r\n RelationshipEdge,\r\n FieldSchema,\r\n CollectionAction,\r\n GlobalAction,\r\n KeyScopes,\r\n ScopePreset,\r\n} from './types'\r\n"],"names":["introspectCollections","introspectGlobals","introspectBlocks","buildBlockNestingMap","buildRelationshipGraph","generatePrompts","generateResources","computeDraftCollections","computeDraftGlobals","createApiKeysCollection","API_KEYS_DEFAULT_SLUG","createBearerStrategy","createMcpEndpoints","createInitializeServer","assertNoSlugConflict","assertNoUpstreamPlugin","createCreateDocumentTool","createDeleteDocumentTool","createFindDocumentTool","createFindGlobalTool","createUpdateGlobalTool","createPatchGlobalLayoutTool","createPublishGlobalDraftTool","createListGlobalVersionsTool","createRestoreGlobalVersionTool","createPatchLayoutTool","createPublishDraftTool","createResolveReferenceTool","createSafeDeleteTool","createSchedulePublishTool","createSearchContentTool","createUpdateDocumentTool","createUploadMediaTool","createListVersionsTool","createRestoreVersionTool","resolveUserCollection","options","incomingConfig","apiKeyCollection","userCollection","admin","user","mcpToolkitPlugin","apiKeysSlug","slug","plugins","collections","globals","allBlocks","blocks","collectionSchemas","globalSchemas","blockCatalog","relationships","previewSiteUrl","preview","disabled","undefined","siteUrl","serverURL","process","env","NEXT_PUBLIC_SERVER_URL","SITE_URL","previewDisabled","draftCollections","excluded","draftBehavior","excludeCollections","exclude","draftGlobals","excludedGlobals","excludeGlobals","exposedCollectionsForNesting","filter","c","has","exposedGlobalsForNesting","g","blockNesting","collectionsBySlug","Map","set","exposedSchemas","schema","exposedGlobalSchemas","globalsBySlug","prompts","domainPrompts","resources","searchableCollections","searchableFields","length","tools","maxFileSize","mediaUpload","collectionSlug","schedulePublish","push","size","patchGlobalLayout","publishGlobalDraft","listGlobalVersions","restoreGlobalVersion","buildInitializeServer","availableCollections","map","s","availableGlobals","keys","availableTools","t","name","apiKeysCollection","updatedCollections","bearerStrategy","collectionsWithStrategy","existingAuth","auth","authConfig","useAPIKey","existingStrategies","Array","isArray","strategies","mcpEndpoints","allowedOrigins","endpoints"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
- import type { Block, CollectionConfig } from 'payload';
2
- import type { BlockCatalog, BlockNestingMap, CollectionSchema, RelationshipEdge } from './types';
1
+ import type { Block, CollectionConfig, GlobalConfig } from 'payload';
2
+ import type { BlockCatalog, BlockNestingMap, CollectionSchema, GlobalSchema, RelationshipEdge } from './types';
3
3
  /**
4
4
  * True if the collection has Payload drafts enabled in its versions config.
5
5
  */
@@ -12,6 +12,20 @@ export declare function introspectCollection(collection: CollectionConfig): Coll
12
12
  * Introspect all collections into a map keyed by slug.
13
13
  */
14
14
  export declare function introspectCollections(collections: CollectionConfig[]): Map<string, CollectionSchema>;
15
+ /**
16
+ * True if the global has Payload drafts enabled in its versions config.
17
+ * Mirrors `hasCollectionDrafts` — globals use the same `versions.drafts`
18
+ * shape as collections.
19
+ */
20
+ export declare function hasGlobalDrafts(global: GlobalConfig): boolean;
21
+ /**
22
+ * Introspect a Payload global config into structured metadata.
23
+ */
24
+ export declare function introspectGlobal(global: GlobalConfig): GlobalSchema;
25
+ /**
26
+ * Introspect all globals into a map keyed by slug.
27
+ */
28
+ export declare function introspectGlobals(globals: GlobalConfig[]): Map<string, GlobalSchema>;
15
29
  /**
16
30
  * Build a flat catalog of every block in the schema. Whether a block can
17
31
  * nest other blocks is represented separately in the BlockNestingMap, not
@@ -27,7 +41,7 @@ export declare function introspectBlocks(blocks: Block[]): BlockCatalog;
27
41
  * slugs the relevant field accepts, picks one, then if that block has
28
42
  * its own `blocks` fields it recurses against the same map.
29
43
  */
30
- export declare function buildBlockNestingMap(collections: CollectionConfig[], blocks: Block[]): BlockNestingMap;
44
+ export declare function buildBlockNestingMap(collections: CollectionConfig[], globalsOrBlocks: GlobalConfig[] | Block[], blocks?: Block[]): BlockNestingMap;
31
45
  /**
32
46
  * Build a relationship graph from introspected collection schemas.
33
47
  */
@@ -1,11 +1,11 @@
1
- /**
2
- * True if the collection has Payload drafts enabled in its versions config.
1
+ /**
2
+ * True if the collection has Payload drafts enabled in its versions config.
3
3
  */ export function hasCollectionDrafts(collection) {
4
4
  const versions = collection.versions;
5
5
  return typeof versions === 'object' && versions !== null && 'drafts' in versions && Boolean(versions.drafts);
6
6
  }
7
- /**
8
- * Introspect a Payload collection config into structured metadata.
7
+ /**
8
+ * Introspect a Payload collection config into structured metadata.
9
9
  */ export function introspectCollection(collection) {
10
10
  const fields = extractFields(collection.fields);
11
11
  const relationships = extractRelationships(collection.fields);
@@ -27,8 +27,8 @@
27
27
  searchableFields
28
28
  };
29
29
  }
30
- /**
31
- * Introspect all collections into a map keyed by slug.
30
+ /**
31
+ * Introspect all collections into a map keyed by slug.
32
32
  */ export function introspectCollections(collections) {
33
33
  const map = new Map();
34
34
  for (const collection of collections){
@@ -36,11 +36,40 @@
36
36
  }
37
37
  return map;
38
38
  }
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.
39
+ /**
40
+ * True if the global has Payload drafts enabled in its versions config.
41
+ * Mirrors `hasCollectionDrafts` globals use the same `versions.drafts`
42
+ * shape as collections.
43
+ */ export function hasGlobalDrafts(global) {
44
+ const versions = global.versions;
45
+ return typeof versions === 'object' && versions !== null && 'drafts' in versions && Boolean(versions.drafts);
46
+ }
47
+ /**
48
+ * Introspect a Payload global config into structured metadata.
49
+ */ export function introspectGlobal(global) {
50
+ const fields = extractFields(global.fields);
51
+ const hasLivePreview = !!(global.admin && typeof global.admin === 'object' && 'livePreview' in global.admin && global.admin.livePreview);
52
+ return {
53
+ slug: global.slug,
54
+ fields,
55
+ hasDrafts: hasGlobalDrafts(global),
56
+ hasLivePreview
57
+ };
58
+ }
59
+ /**
60
+ * Introspect all globals into a map keyed by slug.
61
+ */ export function introspectGlobals(globals) {
62
+ const map = new Map();
63
+ for (const global of globals){
64
+ map.set(global.slug, introspectGlobal(global));
65
+ }
66
+ return map;
67
+ }
68
+ /**
69
+ * Build a flat catalog of every block in the schema. Whether a block can
70
+ * nest other blocks is represented separately in the BlockNestingMap, not
71
+ * as a section/leaf classification — the AI reads both and composes
72
+ * arbitrarily-nested layouts from there.
44
73
  */ export function introspectBlocks(blocks) {
45
74
  const catalog = blocks.map((block)=>({
46
75
  slug: block.slug,
@@ -50,15 +79,19 @@
50
79
  blocks: catalog
51
80
  };
52
81
  }
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));
82
+ /**
83
+ * Walk every collection and every block, recording each `blocks`-typed
84
+ * field's owner, dotted path, and accepted slugs.
85
+ *
86
+ * The AI uses this to compose layouts at any depth: it looks up which
87
+ * slugs the relevant field accepts, picks one, then if that block has
88
+ * its own `blocks` fields it recurses against the same map.
89
+ */ export function buildBlockNestingMap(collections, globalsOrBlocks, blocks) {
90
+ // Back-compat overload: when called with two args, the second is `blocks`
91
+ // and there are no globals. The new shape is `(collections, globals, blocks)`.
92
+ const globals = blocks === undefined ? [] : globalsOrBlocks;
93
+ const effectiveBlocks = blocks === undefined ? globalsOrBlocks : blocks;
94
+ const knownSlugs = new Set(effectiveBlocks.map((b)=>b.slug));
62
95
  const edges = [];
63
96
  for (const collection of collections){
64
97
  edges.push(...collectBlocksFieldEdges(collection.fields, {
@@ -68,7 +101,15 @@
68
101
  knownSlugs
69
102
  }));
70
103
  }
71
- for (const block of blocks){
104
+ for (const global of globals){
105
+ edges.push(...collectBlocksFieldEdges(global.fields, {
106
+ owner: global.slug,
107
+ ownerType: 'global',
108
+ prefix: '',
109
+ knownSlugs
110
+ }));
111
+ }
112
+ for (const block of effectiveBlocks){
72
113
  edges.push(...collectBlocksFieldEdges(block.fields, {
73
114
  owner: block.slug,
74
115
  ownerType: 'block',
@@ -76,10 +117,28 @@
76
117
  knownSlugs
77
118
  }));
78
119
  }
120
+ assertBlockNestingMapInvariant(edges);
79
121
  return edges;
80
122
  }
81
- /**
82
- * Build a relationship graph from introspected collection schemas.
123
+ /**
124
+ * Guard against `(owner, fieldPath)` collisions across different `ownerType`
125
+ * values. Payload's own slug registry forbids collection/global slug
126
+ * collisions, but a defensive check here keeps the validator lookup in
127
+ * `patchLayout` / `patchGlobalLayout` unambiguous and surfaces config
128
+ * mistakes loudly instead of silently picking the first match.
129
+ */ function assertBlockNestingMapInvariant(edges) {
130
+ const seen = new Map();
131
+ for (const edge of edges){
132
+ const key = `${edge.owner}.${edge.fieldPath}`;
133
+ const prior = seen.get(key);
134
+ if (prior && prior !== edge.ownerType) {
135
+ throw new Error(`[payload-mcp-toolkit] Block-nesting map invariant violated: ` + `"${key}" appears as both "${prior}" and "${edge.ownerType}". ` + `A collection and a global cannot share a slug; rename one.`);
136
+ }
137
+ seen.set(key, edge.ownerType);
138
+ }
139
+ }
140
+ /**
141
+ * Build a relationship graph from introspected collection schemas.
83
142
  */ export function buildRelationshipGraph(schemas) {
84
143
  const edges = [];
85
144
  for (const [slug, schema] of schemas){
@@ -95,9 +154,9 @@
95
154
  return edges;
96
155
  }
97
156
  // ─── Internal helpers ──────────────────────────────────────────────
98
- /**
99
- * Recursively extract field metadata from a Payload fields array.
100
- * Handles tabs, groups, arrays, rows, and collapsible containers.
157
+ /**
158
+ * Recursively extract field metadata from a Payload fields array.
159
+ * Handles tabs, groups, arrays, rows, and collapsible containers.
101
160
  */ function extractFields(fields) {
102
161
  const result = [];
103
162
  for (const field of fields){
@@ -145,8 +204,8 @@
145
204
  }
146
205
  return result;
147
206
  }
148
- /**
149
- * Extract relationship metadata from fields (for the relationship graph).
207
+ /**
208
+ * Extract relationship metadata from fields (for the relationship graph).
150
209
  */ function extractRelationships(fields, prefix = '') {
151
210
  const rels = [];
152
211
  for (const field of fields){
@@ -187,10 +246,10 @@
187
246
  }
188
247
  return rels;
189
248
  }
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.
249
+ /**
250
+ * Walk fields recording every `blocks`-typed field encountered, including
251
+ * those nested in tabs/rows/groups/arrays/collapsibles. Each entry carries
252
+ * the dotted path from the owner root to the field.
194
253
  */ function collectBlocksFieldEdges(fields, ctx) {
195
254
  const edges = [];
196
255
  for (const field of fields){
@@ -249,10 +308,10 @@
249
308
  }
250
309
  return edges;
251
310
  }
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.
311
+ /**
312
+ * Read block slugs from a blocks-typed field, handling both resolved
313
+ * (field.blocks contains objects) and unresolved (field.blockReferences
314
+ * holds slug strings) forms.
256
315
  */ function readBlockSlugs(field) {
257
316
  const f = field;
258
317
  if (Array.isArray(f.blocks) && f.blocks.length > 0 && typeof f.blocks[0] === 'object' && f.blocks[0]?.slug) {