payload-mcp-toolkit 0.8.0 → 0.9.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 (101) hide show
  1. package/README.md +100 -237
  2. package/dist/api-keys.js +16 -0
  3. package/dist/api-keys.js.map +1 -1
  4. package/dist/auth-strategy.js +49 -49
  5. package/dist/auth-strategy.js.map +1 -1
  6. package/dist/components/AgentConnectPill.d.ts +3 -0
  7. package/dist/components/AgentConnectPill.js +121 -0
  8. package/dist/components/AgentConnectPill.js.map +1 -0
  9. package/dist/components/CollectionScopesMatrix.js.map +1 -1
  10. package/dist/components/ConsentPermissions.d.ts +10 -0
  11. package/dist/components/ConsentPermissions.js +165 -0
  12. package/dist/components/ConsentPermissions.js.map +1 -0
  13. package/dist/components/GlobalScopesMatrix.js.map +1 -1
  14. package/dist/components/OAuthView.d.ts +4 -0
  15. package/dist/components/OAuthView.js +269 -0
  16. package/dist/components/OAuthView.js.map +1 -0
  17. package/dist/components/ScopesTable.d.ts +28 -0
  18. package/dist/components/ScopesTable.js +165 -130
  19. package/dist/components/ScopesTable.js.map +1 -1
  20. package/dist/components/agentInstructions.d.ts +2 -0
  21. package/dist/components/agentInstructions.js +34 -0
  22. package/dist/components/agentInstructions.js.map +1 -0
  23. package/dist/components/index.d.ts +2 -0
  24. package/dist/components/index.js +2 -0
  25. package/dist/components/index.js.map +1 -1
  26. package/dist/components/oauth.css +111 -0
  27. package/dist/conflict-detection.js +13 -13
  28. package/dist/conflict-detection.js.map +1 -1
  29. package/dist/draft-workflow.js +26 -26
  30. package/dist/draft-workflow.js.map +1 -1
  31. package/dist/endpoint.d.ts +5 -0
  32. package/dist/endpoint.js +21 -13
  33. package/dist/endpoint.js.map +1 -1
  34. package/dist/hash.js +15 -15
  35. package/dist/hash.js.map +1 -1
  36. package/dist/index.d.ts +2 -0
  37. package/dist/index.js +67 -3
  38. package/dist/index.js.map +1 -1
  39. package/dist/introspection.js +47 -47
  40. package/dist/introspection.js.map +1 -1
  41. package/dist/oauth-permissions.d.ts +52 -0
  42. package/dist/oauth-permissions.js +100 -0
  43. package/dist/oauth-permissions.js.map +1 -0
  44. package/dist/oauth-store.d.ts +19 -0
  45. package/dist/oauth-store.js +122 -0
  46. package/dist/oauth-store.js.map +1 -0
  47. package/dist/oauth.d.ts +19 -0
  48. package/dist/oauth.js +523 -0
  49. package/dist/oauth.js.map +1 -0
  50. package/dist/prompts.js +5 -5
  51. package/dist/prompts.js.map +1 -1
  52. package/dist/registry.js +7 -1
  53. package/dist/registry.js.map +1 -1
  54. package/dist/resources.js +10 -10
  55. package/dist/resources.js.map +1 -1
  56. package/dist/scope/audit-log.js +9 -9
  57. package/dist/scope/audit-log.js.map +1 -1
  58. package/dist/scope/policy.js +7 -7
  59. package/dist/scope/policy.js.map +1 -1
  60. package/dist/tools/_helpers.d.ts +10 -0
  61. package/dist/tools/_helpers.js +53 -41
  62. package/dist/tools/_helpers.js.map +1 -1
  63. package/dist/tools/_layout-helpers.js +33 -33
  64. package/dist/tools/_layout-helpers.js.map +1 -1
  65. package/dist/tools/create-document.js +20 -19
  66. package/dist/tools/create-document.js.map +1 -1
  67. package/dist/tools/delete-document.js +10 -9
  68. package/dist/tools/delete-document.js.map +1 -1
  69. package/dist/tools/find-document.js +18 -15
  70. package/dist/tools/find-document.js.map +1 -1
  71. package/dist/tools/find-global.js +13 -13
  72. package/dist/tools/find-global.js.map +1 -1
  73. package/dist/tools/global-versions.js +8 -7
  74. package/dist/tools/global-versions.js.map +1 -1
  75. package/dist/tools/patch-global-layout.js +9 -9
  76. package/dist/tools/patch-global-layout.js.map +1 -1
  77. package/dist/tools/patch-layout.js +15 -14
  78. package/dist/tools/patch-layout.js.map +1 -1
  79. package/dist/tools/publish-draft.js +2 -1
  80. package/dist/tools/publish-draft.js.map +1 -1
  81. package/dist/tools/publish-global-draft.js +4 -4
  82. package/dist/tools/publish-global-draft.js.map +1 -1
  83. package/dist/tools/resolve-reference.js.map +1 -1
  84. package/dist/tools/safe-delete.js +14 -14
  85. package/dist/tools/safe-delete.js.map +1 -1
  86. package/dist/tools/schedule-publish.js +21 -20
  87. package/dist/tools/schedule-publish.js.map +1 -1
  88. package/dist/tools/search-content.js +12 -12
  89. package/dist/tools/search-content.js.map +1 -1
  90. package/dist/tools/update-document.js +6 -5
  91. package/dist/tools/update-document.js.map +1 -1
  92. package/dist/tools/update-global.js +9 -9
  93. package/dist/tools/update-global.js.map +1 -1
  94. package/dist/tools/upload-media.js +2 -1
  95. package/dist/tools/upload-media.js.map +1 -1
  96. package/dist/tools/versions.js +10 -8
  97. package/dist/tools/versions.js.map +1 -1
  98. package/dist/types.d.ts +3 -0
  99. package/dist/types.js.map +1 -1
  100. package/docs/oauth.md +96 -0
  101. package/package.json +7 -2
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/tools/find-global.ts"],"sourcesContent":["import { z } from 'zod'\r\nimport type { GlobalConfig, PayloadRequest } from 'payload'\r\nimport type { GlobalSchema } from '../types'\r\nimport { errorMessage, jsonResponse, slugEnum, stampMcpContext, textResponse } from './_helpers'\r\n\r\ninterface FindGlobalArgs {\r\n slug: string\r\n draft?: boolean\r\n depth?: number\r\n locale?: string\r\n}\r\n\r\n/**\r\n * Read any global by slug. Mirrors `findDocument`'s slug-enum + draft +\r\n * preview behaviour but acts on Payload's `findGlobal` Local API method —\r\n * globals are singletons, so there's no `id`, `where`, or `limit`.\r\n *\r\n * Preview stamping uses each global's `admin.livePreview.url` (preferred)\r\n * or `admin.preview` function, same fallback chain as collections.\r\n */\r\nexport function createFindGlobalTool(\r\n globalSchemas: Map<string, GlobalSchema>,\r\n draftGlobals: Set<string>,\r\n globalsBySlug: Map<string, GlobalConfig>,\r\n previewSiteUrl: string | undefined,\r\n previewDisabled = false,\r\n) {\r\n const findableSlugs = [...globalSchemas.keys()]\r\n const descriptionLines = findableSlugs.map(\r\n (slug) => ` - \"${slug}\"${draftGlobals.has(slug) ? ' (draft-enabled)' : ''}`,\r\n )\r\n\r\n return {\r\n name: 'findGlobal',\r\n routing: { kind: 'global', action: 'read' } as const,\r\n description:\r\n 'Read a global (a singleton site-wide settings document) by slug. ' +\r\n 'Globals carry things like site name, footer config, navigation menus — there is no `id`, no list, no `where` filter. ' +\r\n 'Use `draft: true` to read the draft version of a draft-enabled global.\\n\\n' +\r\n 'Available globals:\\n' +\r\n descriptionLines.join('\\n'),\r\n parameters: {\r\n slug: slugEnum(findableSlugs, 'global').describe(\r\n `The global slug. One of: ${findableSlugs.join(', ')}`,\r\n ),\r\n draft: z\r\n .boolean()\r\n .optional()\r\n .describe(\r\n 'When true, returns the draft version of a draft-enabled global. Default false (published).',\r\n ),\r\n depth: z\r\n .number()\r\n .int()\r\n .min(0)\r\n .max(3)\r\n .optional()\r\n .describe('Relationship population depth. Default 1.'),\r\n locale: z\r\n .string()\r\n .optional()\r\n .describe(\r\n 'Optional locale code (e.g. \"en\", \"fr\") for localized fields. Defaults to the request locale.',\r\n ),\r\n },\r\n handler: async (args: Record<string, unknown>, req: PayloadRequest, _extra: unknown) => {\r\n const { slug, draft, depth, locale } = args as unknown as FindGlobalArgs\r\n\r\n if (!globalSchemas.has(slug)) {\r\n return textResponse(\r\n `Error: Unknown global \"${slug}\". Available: ${findableSlugs.join(', ')}`,\r\n )\r\n }\r\n\r\n stampMcpContext(req)\r\n const globalConfig = globalsBySlug.get(slug)\r\n\r\n try {\r\n const doc = await req.payload.findGlobal({\r\n slug: slug as never,\r\n depth: depth ?? 1,\r\n draft: draft ?? false,\r\n ...(locale ? { locale: locale as never } : {}),\r\n req,\r\n overrideAccess: false,\r\n user: req.user,\r\n })\r\n\r\n const base = jsonResponse(doc)\r\n if (previewDisabled || !globalConfig) return base\r\n\r\n const isDraft =\r\n (doc as Record<string, unknown> | undefined)?._status === 'draft'\r\n if (!isDraft) return base\r\n\r\n const previewUrl = await resolveGlobalPreviewUrl(\r\n globalConfig,\r\n doc as Record<string, unknown>,\r\n req,\r\n previewSiteUrl,\r\n )\r\n const hint = previewUrl\r\n ? `\\n📋 This global is in draft status. Preview it here: ${previewUrl}`\r\n : '\\n📋 This global is in draft status. Use the admin panel to preview it.'\r\n return { content: [...base.content, { type: 'text' as const, text: hint }] }\r\n } catch (err) {\r\n return textResponse(`Error reading global \"${slug}\": ${errorMessage(err)}`)\r\n }\r\n },\r\n }\r\n}\r\n\r\n/**\r\n * Resolve a preview URL from a global's `admin.livePreview.url` / `admin.preview`\r\n * config. Same fallback chain as `resolvePreviewUrl` for collections, but\r\n * passes `globalConfig` (not `collectionConfig`) to the user callback.\r\n */\r\nasync function resolveGlobalPreviewUrl(\r\n global: GlobalConfig,\r\n doc: Record<string, unknown>,\r\n req: PayloadRequest,\r\n siteUrl: string | undefined,\r\n): Promise<string | null> {\r\n const admin = (global.admin ?? {}) as Record<string, any>\r\n const locale = (req as unknown as { locale?: string }).locale ?? 'en'\r\n\r\n let raw: string | null | undefined\r\n\r\n const livePreviewUrl = admin.livePreview?.url\r\n if (typeof livePreviewUrl === 'function') {\r\n try {\r\n raw = await livePreviewUrl({\r\n data: doc,\r\n locale: { code: locale, label: locale },\r\n req,\r\n payload: req.payload,\r\n globalConfig: global,\r\n })\r\n } catch (err) {\r\n req.payload.logger?.warn?.(\r\n { err, slug: global.slug },\r\n `[payload-mcp-toolkit] admin.livePreview.url threw for global \"${global.slug}\"`,\r\n )\r\n raw = null\r\n }\r\n } else if (typeof livePreviewUrl === 'string') {\r\n raw = livePreviewUrl\r\n }\r\n\r\n if (!raw && typeof admin.preview === 'function') {\r\n try {\r\n raw = await admin.preview(doc, { locale, req, token: null })\r\n } catch (err) {\r\n req.payload.logger?.warn?.(\r\n { err, slug: global.slug },\r\n `[payload-mcp-toolkit] admin.preview threw for global \"${global.slug}\"`,\r\n )\r\n raw = null\r\n }\r\n }\r\n\r\n if (!raw || typeof raw !== 'string') return null\r\n if (raw.startsWith('http://') || raw.startsWith('https://')) return raw\r\n if (!siteUrl) return null\r\n\r\n const base = siteUrl.endsWith('/') ? siteUrl.slice(0, -1) : siteUrl\r\n const path = raw.startsWith('/') ? raw : `/${raw}`\r\n return `${base}${path}`\r\n}\r\n"],"names":["z","errorMessage","jsonResponse","slugEnum","stampMcpContext","textResponse","createFindGlobalTool","globalSchemas","draftGlobals","globalsBySlug","previewSiteUrl","previewDisabled","findableSlugs","keys","descriptionLines","map","slug","has","name","routing","kind","action","description","join","parameters","describe","draft","boolean","optional","depth","number","int","min","max","locale","string","handler","args","req","_extra","globalConfig","get","doc","payload","findGlobal","overrideAccess","user","base","isDraft","_status","previewUrl","resolveGlobalPreviewUrl","hint","content","type","text","err","global","siteUrl","admin","raw","livePreviewUrl","livePreview","url","data","code","label","logger","warn","preview","token","startsWith","endsWith","slice","path"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/tools/find-global.ts"],"sourcesContent":["import { z } from 'zod'\nimport type { GlobalConfig, PayloadRequest } from 'payload'\nimport type { GlobalSchema } from '../types'\nimport { errorMessage, jsonResponse, slugEnum, stampMcpContext, textResponse, populateDepth } from './_helpers'\n\ninterface FindGlobalArgs {\n slug: string\n draft?: boolean\n depth?: number\n locale?: string\n}\n\n/**\n * Read any global by slug. Mirrors `findDocument`'s slug-enum + draft +\n * preview behaviour but acts on Payload's `findGlobal` Local API method —\n * globals are singletons, so there's no `id`, `where`, or `limit`.\n *\n * Preview stamping uses each global's `admin.livePreview.url` (preferred)\n * or `admin.preview` function, same fallback chain as collections.\n */\nexport function createFindGlobalTool(\n globalSchemas: Map<string, GlobalSchema>,\n draftGlobals: Set<string>,\n globalsBySlug: Map<string, GlobalConfig>,\n previewSiteUrl: string | undefined,\n previewDisabled = false,\n) {\n const findableSlugs = [...globalSchemas.keys()]\n const descriptionLines = findableSlugs.map(\n (slug) => ` - \"${slug}\"${draftGlobals.has(slug) ? ' (draft-enabled)' : ''}`,\n )\n\n return {\n name: 'findGlobal',\n routing: { kind: 'global', action: 'read' } as const,\n description:\n 'Read a global (a singleton site-wide settings document) by slug. ' +\n 'Globals carry things like site name, footer config, navigation menus — there is no `id`, no list, no `where` filter. ' +\n 'Use `draft: true` to read the draft version of a draft-enabled global.\\n\\n' +\n 'Available globals:\\n' +\n descriptionLines.join('\\n'),\n parameters: {\n slug: slugEnum(findableSlugs, 'global').describe(\n `The global slug. One of: ${findableSlugs.join(', ')}`,\n ),\n draft: z\n .boolean()\n .optional()\n .describe(\n 'When true, returns the draft version of a draft-enabled global. Default false (published).',\n ),\n depth: z\n .number()\n .int()\n .min(0)\n .max(3)\n .optional()\n .describe('Relationship population depth. Default 1.'),\n locale: z\n .string()\n .optional()\n .describe(\n 'Optional locale code (e.g. \"en\", \"fr\") for localized fields. Defaults to the request locale.',\n ),\n },\n handler: async (args: Record<string, unknown>, req: PayloadRequest, _extra: unknown) => {\n const { slug, draft, depth, locale } = args as unknown as FindGlobalArgs\n\n if (!globalSchemas.has(slug)) {\n return textResponse(\n `Error: Unknown global \"${slug}\". Available: ${findableSlugs.join(', ')}`,\n )\n }\n\n stampMcpContext(req)\n const globalConfig = globalsBySlug.get(slug)\n\n try {\n const doc = await req.payload.findGlobal({\n slug: slug as never,\n depth: populateDepth(req, depth ?? 1),\n draft: draft ?? false,\n ...(locale ? { locale: locale as never } : {}),\n req,\n overrideAccess: false,\n user: req.user,\n })\n\n const base = jsonResponse(doc)\n if (previewDisabled || !globalConfig) return base\n\n const isDraft =\n (doc as Record<string, unknown> | undefined)?._status === 'draft'\n if (!isDraft) return base\n\n const previewUrl = await resolveGlobalPreviewUrl(\n globalConfig,\n doc as Record<string, unknown>,\n req,\n previewSiteUrl,\n )\n const hint = previewUrl\n ? `\\n📋 This global is in draft status. Preview it here: ${previewUrl}`\n : '\\n📋 This global is in draft status. Use the admin panel to preview it.'\n return { content: [...base.content, { type: 'text' as const, text: hint }] }\n } catch (err) {\n return textResponse(`Error reading global \"${slug}\": ${errorMessage(err)}`)\n }\n },\n }\n}\n\n/**\n * Resolve a preview URL from a global's `admin.livePreview.url` / `admin.preview`\n * config. Same fallback chain as `resolvePreviewUrl` for collections, but\n * passes `globalConfig` (not `collectionConfig`) to the user callback.\n */\nasync function resolveGlobalPreviewUrl(\n global: GlobalConfig,\n doc: Record<string, unknown>,\n req: PayloadRequest,\n siteUrl: string | undefined,\n): Promise<string | null> {\n const admin = (global.admin ?? {}) as Record<string, any>\n const locale = (req as unknown as { locale?: string }).locale ?? 'en'\n\n let raw: string | null | undefined\n\n const livePreviewUrl = admin.livePreview?.url\n if (typeof livePreviewUrl === 'function') {\n try {\n raw = await livePreviewUrl({\n data: doc,\n locale: { code: locale, label: locale },\n req,\n payload: req.payload,\n globalConfig: global,\n })\n } catch (err) {\n req.payload.logger?.warn?.(\n { err, slug: global.slug },\n `[payload-mcp-toolkit] admin.livePreview.url threw for global \"${global.slug}\"`,\n )\n raw = null\n }\n } else if (typeof livePreviewUrl === 'string') {\n raw = livePreviewUrl\n }\n\n if (!raw && typeof admin.preview === 'function') {\n try {\n raw = await admin.preview(doc, { locale, req, token: null })\n } catch (err) {\n req.payload.logger?.warn?.(\n { err, slug: global.slug },\n `[payload-mcp-toolkit] admin.preview threw for global \"${global.slug}\"`,\n )\n raw = null\n }\n }\n\n if (!raw || typeof raw !== 'string') return null\n if (raw.startsWith('http://') || raw.startsWith('https://')) return raw\n if (!siteUrl) return null\n\n const base = siteUrl.endsWith('/') ? siteUrl.slice(0, -1) : siteUrl\n const path = raw.startsWith('/') ? raw : `/${raw}`\n return `${base}${path}`\n}\n"],"names":["z","errorMessage","jsonResponse","slugEnum","stampMcpContext","textResponse","populateDepth","createFindGlobalTool","globalSchemas","draftGlobals","globalsBySlug","previewSiteUrl","previewDisabled","findableSlugs","keys","descriptionLines","map","slug","has","name","routing","kind","action","description","join","parameters","describe","draft","boolean","optional","depth","number","int","min","max","locale","string","handler","args","req","_extra","globalConfig","get","doc","payload","findGlobal","overrideAccess","user","base","isDraft","_status","previewUrl","resolveGlobalPreviewUrl","hint","content","type","text","err","global","siteUrl","admin","raw","livePreviewUrl","livePreview","url","data","code","label","logger","warn","preview","token","startsWith","endsWith","slice","path"],"mappings":"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"}
@@ -1,9 +1,9 @@
1
1
  import { z } from 'zod';
2
- import { errorMessage, jsonResponse, slugEnum, stampMcpContext, textResponse } from './_helpers';
2
+ import { errorMessage, jsonResponse, slugEnum, stampMcpContext, textResponse, populateDepth } from './_helpers';
3
3
  const DEFAULT_LIST_LIMIT = 10;
4
- /**
5
- * List recent saved versions of a draft-enabled global. Returns null when no
6
- * global has drafts enabled so the plugin entry can skip registration.
4
+ /**
5
+ * List recent saved versions of a draft-enabled global. Returns null when no
6
+ * global has drafts enabled so the plugin entry can skip registration.
7
7
  */ export function createListGlobalVersionsTool(draftGlobals) {
8
8
  if (draftGlobals.size === 0) return null;
9
9
  const slugs = [
@@ -29,6 +29,7 @@ const DEFAULT_LIST_LIMIT = 10;
29
29
  stampMcpContext(req);
30
30
  try {
31
31
  const result = await req.payload.findGlobalVersions({
32
+ depth: populateDepth(req),
32
33
  slug: slug,
33
34
  sort: '-updatedAt',
34
35
  limit,
@@ -61,9 +62,9 @@ const DEFAULT_LIST_LIMIT = 10;
61
62
  }
62
63
  };
63
64
  }
64
- /**
65
- * Restore a draft-enabled global to a previously saved version. Returns null
66
- * when no global has drafts enabled.
65
+ /**
66
+ * Restore a draft-enabled global to a previously saved version. Returns null
67
+ * when no global has drafts enabled.
67
68
  */ export function createRestoreGlobalVersionTool(draftGlobals) {
68
69
  if (draftGlobals.size === 0) return null;
69
70
  const slugs = [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/tools/global-versions.ts"],"sourcesContent":["import { z } from 'zod'\r\nimport type { PayloadRequest } from 'payload'\r\nimport { errorMessage, jsonResponse, slugEnum, stampMcpContext, textResponse } from './_helpers'\r\n\r\nconst DEFAULT_LIST_LIMIT = 10\r\n\r\n/**\r\n * List recent saved versions of a draft-enabled global. Returns null when no\r\n * global has drafts enabled so the plugin entry can skip registration.\r\n */\r\nexport function createListGlobalVersionsTool(draftGlobals: Set<string>) {\r\n if (draftGlobals.size === 0) return null\r\n\r\n const slugs = [...draftGlobals]\r\n return {\r\n name: 'listGlobalVersions',\r\n routing: { kind: 'global', action: 'read' } as const,\r\n description:\r\n 'List recent saved versions of a draft-enabled global. ' +\r\n 'Use before restoreGlobalVersion to pick the right point in time. ' +\r\n `Draft-enabled globals: ${slugs.join(', ')}`,\r\n parameters: {\r\n slug: slugEnum(slugs, 'global').describe(`The global slug. One of: ${slugs.join(', ')}`),\r\n limit: z\r\n .number()\r\n .optional()\r\n .default(DEFAULT_LIST_LIMIT)\r\n .describe(`Maximum number of versions to return (default ${DEFAULT_LIST_LIMIT})`),\r\n locale: z\r\n .string()\r\n .optional()\r\n .describe('Optional locale code (e.g. \"en\", \"fr\") to filter version snapshots to.'),\r\n },\r\n handler: async (\r\n args: Record<string, unknown>,\r\n req: PayloadRequest,\r\n _extra: unknown,\r\n ) => {\r\n const { slug, limit = DEFAULT_LIST_LIMIT, locale } = args as {\r\n slug: string\r\n limit?: number\r\n locale?: string\r\n }\r\n\r\n if (!draftGlobals.has(slug)) {\r\n return textResponse(\r\n `Error: Global \"${slug}\" does not support versions. Draft-enabled globals: ${slugs.join(', ') || 'none'}`,\r\n )\r\n }\r\n\r\n stampMcpContext(req)\r\n\r\n try {\r\n const result = await req.payload.findGlobalVersions({\r\n slug: slug as never,\r\n sort: '-updatedAt',\r\n limit,\r\n ...(locale ? { locale: locale as never } : {}),\r\n req,\r\n overrideAccess: false,\r\n user: req.user,\r\n })\r\n\r\n const versions = result.docs.map((v: any) => {\r\n const snapshot = v.version || {}\r\n return {\r\n id: v.id,\r\n updatedAt: v.updatedAt,\r\n createdAt: v.createdAt,\r\n status: snapshot._status ?? 'unknown',\r\n autosave: v.autosave === true,\r\n }\r\n })\r\n\r\n return jsonResponse({\r\n slug,\r\n totalDocs: result.totalDocs,\r\n returned: versions.length,\r\n versions,\r\n })\r\n } catch (err) {\r\n return textResponse(`Error listing versions for global \"${slug}\": ${errorMessage(err)}`)\r\n }\r\n },\r\n }\r\n}\r\n\r\n/**\r\n * Restore a draft-enabled global to a previously saved version. Returns null\r\n * when no global has drafts enabled.\r\n */\r\nexport function createRestoreGlobalVersionTool(draftGlobals: Set<string>) {\r\n if (draftGlobals.size === 0) return null\r\n\r\n const slugs = [...draftGlobals]\r\n return {\r\n name: 'restoreGlobalVersion',\r\n routing: { kind: 'global', action: 'update' } as const,\r\n description:\r\n 'Restore a global to a previously saved version. ' +\r\n 'Use listGlobalVersions first to find the version ID. ' +\r\n 'Restoring creates a new version on top, so the previous state is recoverable. ' +\r\n `Draft-enabled globals: ${slugs.join(', ')}`,\r\n parameters: {\r\n slug: slugEnum(slugs, 'global').describe(`The global slug. One of: ${slugs.join(', ')}`),\r\n versionId: z\r\n .string()\r\n .describe('The version ID returned by listGlobalVersions (NOT the global slug)'),\r\n expectedUpdatedAt: z\r\n .string()\r\n .optional()\r\n .describe(\r\n 'Optional CAS guard. If set, the restore is rejected when the global\\'s current updatedAt differs from this value — protects against clobbering concurrent edits. Pass the updatedAt returned by a prior findGlobal call.',\r\n ),\r\n locale: z\r\n .string()\r\n .optional()\r\n .describe('Optional locale code (e.g. \"en\", \"fr\") to scope the restore to a single locale.'),\r\n },\r\n handler: async (\r\n args: Record<string, unknown>,\r\n req: PayloadRequest,\r\n _extra: unknown,\r\n ) => {\r\n const { slug, versionId, expectedUpdatedAt, locale } = args as {\r\n slug: string\r\n versionId: string\r\n expectedUpdatedAt?: string\r\n locale?: string\r\n }\r\n\r\n if (!draftGlobals.has(slug)) {\r\n return textResponse(\r\n `Error: Global \"${slug}\" does not support versions. Draft-enabled globals: ${slugs.join(', ') || 'none'}`,\r\n )\r\n }\r\n\r\n stampMcpContext(req)\r\n\r\n if (expectedUpdatedAt !== undefined) {\r\n try {\r\n const current = (await req.payload.findGlobal({\r\n slug: slug as never,\r\n depth: 0,\r\n draft: true,\r\n ...(locale ? { locale: locale as never } : {}),\r\n req,\r\n overrideAccess: false,\r\n user: req.user,\r\n })) as { updatedAt?: unknown } | undefined\r\n const currentUpdatedAt = current?.updatedAt\r\n const currentStr =\r\n typeof currentUpdatedAt === 'string'\r\n ? currentUpdatedAt\r\n : currentUpdatedAt instanceof Date\r\n ? currentUpdatedAt.toISOString()\r\n : undefined\r\n if (currentStr !== expectedUpdatedAt) {\r\n return textResponse(\r\n `Conflict: global \"${slug}\" was modified since expectedUpdatedAt (${expectedUpdatedAt}); current updatedAt is ${currentStr ?? 'unknown'}. Re-read the global and retry.`,\r\n )\r\n }\r\n } catch (err) {\r\n return textResponse(\r\n `Error checking expectedUpdatedAt on global \"${slug}\": ${errorMessage(err)}`,\r\n )\r\n }\r\n }\r\n\r\n try {\r\n await req.payload.restoreGlobalVersion({\r\n slug: slug as never,\r\n id: versionId,\r\n ...(locale ? { locale: locale as never } : {}),\r\n req,\r\n overrideAccess: false,\r\n user: req.user,\r\n })\r\n return textResponse(\r\n `Restored global \"${slug}\" from version ${versionId}. ` +\r\n `The global is now in draft status — use publishGlobalDraft to make the restored content live.`,\r\n )\r\n } catch (err) {\r\n return textResponse(\r\n `Error restoring global \"${slug}\" from version ${versionId}: ${errorMessage(err)}`,\r\n )\r\n }\r\n },\r\n }\r\n}\r\n"],"names":["z","errorMessage","jsonResponse","slugEnum","stampMcpContext","textResponse","DEFAULT_LIST_LIMIT","createListGlobalVersionsTool","draftGlobals","size","slugs","name","routing","kind","action","description","join","parameters","slug","describe","limit","number","optional","default","locale","string","handler","args","req","_extra","has","result","payload","findGlobalVersions","sort","overrideAccess","user","versions","docs","map","v","snapshot","version","id","updatedAt","createdAt","status","_status","autosave","totalDocs","returned","length","err","createRestoreGlobalVersionTool","versionId","expectedUpdatedAt","undefined","current","findGlobal","depth","draft","currentUpdatedAt","currentStr","Date","toISOString","restoreGlobalVersion"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/tools/global-versions.ts"],"sourcesContent":["import { z } from 'zod'\nimport type { PayloadRequest } from 'payload'\nimport { errorMessage, jsonResponse, slugEnum, stampMcpContext, textResponse, populateDepth } from './_helpers'\n\nconst DEFAULT_LIST_LIMIT = 10\n\n/**\n * List recent saved versions of a draft-enabled global. Returns null when no\n * global has drafts enabled so the plugin entry can skip registration.\n */\nexport function createListGlobalVersionsTool(draftGlobals: Set<string>) {\n if (draftGlobals.size === 0) return null\n\n const slugs = [...draftGlobals]\n return {\n name: 'listGlobalVersions',\n routing: { kind: 'global', action: 'read' } as const,\n description:\n 'List recent saved versions of a draft-enabled global. ' +\n 'Use before restoreGlobalVersion to pick the right point in time. ' +\n `Draft-enabled globals: ${slugs.join(', ')}`,\n parameters: {\n slug: slugEnum(slugs, 'global').describe(`The global slug. One of: ${slugs.join(', ')}`),\n limit: z\n .number()\n .optional()\n .default(DEFAULT_LIST_LIMIT)\n .describe(`Maximum number of versions to return (default ${DEFAULT_LIST_LIMIT})`),\n locale: z\n .string()\n .optional()\n .describe('Optional locale code (e.g. \"en\", \"fr\") to filter version snapshots to.'),\n },\n handler: async (\n args: Record<string, unknown>,\n req: PayloadRequest,\n _extra: unknown,\n ) => {\n const { slug, limit = DEFAULT_LIST_LIMIT, locale } = args as {\n slug: string\n limit?: number\n locale?: string\n }\n\n if (!draftGlobals.has(slug)) {\n return textResponse(\n `Error: Global \"${slug}\" does not support versions. Draft-enabled globals: ${slugs.join(', ') || 'none'}`,\n )\n }\n\n stampMcpContext(req)\n\n try {\n const result = await req.payload.findGlobalVersions({\n depth: populateDepth(req),\n slug: slug as never,\n sort: '-updatedAt',\n limit,\n ...(locale ? { locale: locale as never } : {}),\n req,\n overrideAccess: false,\n user: req.user,\n })\n\n const versions = result.docs.map((v: any) => {\n const snapshot = v.version || {}\n return {\n id: v.id,\n updatedAt: v.updatedAt,\n createdAt: v.createdAt,\n status: snapshot._status ?? 'unknown',\n autosave: v.autosave === true,\n }\n })\n\n return jsonResponse({\n slug,\n totalDocs: result.totalDocs,\n returned: versions.length,\n versions,\n })\n } catch (err) {\n return textResponse(`Error listing versions for global \"${slug}\": ${errorMessage(err)}`)\n }\n },\n }\n}\n\n/**\n * Restore a draft-enabled global to a previously saved version. Returns null\n * when no global has drafts enabled.\n */\nexport function createRestoreGlobalVersionTool(draftGlobals: Set<string>) {\n if (draftGlobals.size === 0) return null\n\n const slugs = [...draftGlobals]\n return {\n name: 'restoreGlobalVersion',\n routing: { kind: 'global', action: 'update' } as const,\n description:\n 'Restore a global to a previously saved version. ' +\n 'Use listGlobalVersions first to find the version ID. ' +\n 'Restoring creates a new version on top, so the previous state is recoverable. ' +\n `Draft-enabled globals: ${slugs.join(', ')}`,\n parameters: {\n slug: slugEnum(slugs, 'global').describe(`The global slug. One of: ${slugs.join(', ')}`),\n versionId: z\n .string()\n .describe('The version ID returned by listGlobalVersions (NOT the global slug)'),\n expectedUpdatedAt: z\n .string()\n .optional()\n .describe(\n 'Optional CAS guard. If set, the restore is rejected when the global\\'s current updatedAt differs from this value — protects against clobbering concurrent edits. Pass the updatedAt returned by a prior findGlobal call.',\n ),\n locale: z\n .string()\n .optional()\n .describe('Optional locale code (e.g. \"en\", \"fr\") to scope the restore to a single locale.'),\n },\n handler: async (\n args: Record<string, unknown>,\n req: PayloadRequest,\n _extra: unknown,\n ) => {\n const { slug, versionId, expectedUpdatedAt, locale } = args as {\n slug: string\n versionId: string\n expectedUpdatedAt?: string\n locale?: string\n }\n\n if (!draftGlobals.has(slug)) {\n return textResponse(\n `Error: Global \"${slug}\" does not support versions. Draft-enabled globals: ${slugs.join(', ') || 'none'}`,\n )\n }\n\n stampMcpContext(req)\n\n if (expectedUpdatedAt !== undefined) {\n try {\n const current = (await req.payload.findGlobal({\n slug: slug as never,\n depth: 0,\n draft: true,\n ...(locale ? { locale: locale as never } : {}),\n req,\n overrideAccess: false,\n user: req.user,\n })) as { updatedAt?: unknown } | undefined\n const currentUpdatedAt = current?.updatedAt\n const currentStr =\n typeof currentUpdatedAt === 'string'\n ? currentUpdatedAt\n : currentUpdatedAt instanceof Date\n ? currentUpdatedAt.toISOString()\n : undefined\n if (currentStr !== expectedUpdatedAt) {\n return textResponse(\n `Conflict: global \"${slug}\" was modified since expectedUpdatedAt (${expectedUpdatedAt}); current updatedAt is ${currentStr ?? 'unknown'}. Re-read the global and retry.`,\n )\n }\n } catch (err) {\n return textResponse(\n `Error checking expectedUpdatedAt on global \"${slug}\": ${errorMessage(err)}`,\n )\n }\n }\n\n try {\n await req.payload.restoreGlobalVersion({\n slug: slug as never,\n id: versionId,\n ...(locale ? { locale: locale as never } : {}),\n req,\n overrideAccess: false,\n user: req.user,\n })\n return textResponse(\n `Restored global \"${slug}\" from version ${versionId}. ` +\n `The global is now in draft status — use publishGlobalDraft to make the restored content live.`,\n )\n } catch (err) {\n return textResponse(\n `Error restoring global \"${slug}\" from version ${versionId}: ${errorMessage(err)}`,\n )\n }\n },\n }\n}\n"],"names":["z","errorMessage","jsonResponse","slugEnum","stampMcpContext","textResponse","populateDepth","DEFAULT_LIST_LIMIT","createListGlobalVersionsTool","draftGlobals","size","slugs","name","routing","kind","action","description","join","parameters","slug","describe","limit","number","optional","default","locale","string","handler","args","req","_extra","has","result","payload","findGlobalVersions","depth","sort","overrideAccess","user","versions","docs","map","v","snapshot","version","id","updatedAt","createdAt","status","_status","autosave","totalDocs","returned","length","err","createRestoreGlobalVersionTool","versionId","expectedUpdatedAt","undefined","current","findGlobal","draft","currentUpdatedAt","currentStr","Date","toISOString","restoreGlobalVersion"],"mappings":"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"}
@@ -1,15 +1,15 @@
1
1
  import { z } from 'zod';
2
2
  import { DRAFT_NOTE, errorMessage, jsonResponse, slugEnum, stampMcpContext } from './_helpers';
3
3
  import { applyOperation, errorResponse, readPath, validateBlockList, writePath } from './_layout-helpers';
4
- /**
5
- * patchGlobalLayout — surgical wrapper that mutates a single blocks-typed
6
- * field on a global without round-tripping the entire array through
7
- * updateGlobal. Same operation grammar as patchLayout (append, prepend,
8
- * insertAt, replaceAt, full); same recursive nesting validator against the
9
- * unified BlockNestingMap.
10
- *
11
- * Returns null when no global has any blocks field — the plugin entry
12
- * skips registration in that case, mirroring the schedulePublish pattern.
4
+ /**
5
+ * patchGlobalLayout — surgical wrapper that mutates a single blocks-typed
6
+ * field on a global without round-tripping the entire array through
7
+ * updateGlobal. Same operation grammar as patchLayout (append, prepend,
8
+ * insertAt, replaceAt, full); same recursive nesting validator against the
9
+ * unified BlockNestingMap.
10
+ *
11
+ * Returns null when no global has any blocks field — the plugin entry
12
+ * skips registration in that case, mirroring the schedulePublish pattern.
13
13
  */ export function createPatchGlobalLayoutTool(catalog, nesting, draftGlobals) {
14
14
  const allBlockSlugs = new Set(catalog.blocks.map((b)=>b.slug));
15
15
  const nestingByGlobalField = new Map();
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/tools/patch-global-layout.ts"],"sourcesContent":["import { z } from 'zod'\r\nimport type { PayloadRequest } from 'payload'\r\nimport type { BlockCatalog, BlockNestingMap } from '../types'\r\nimport { DRAFT_NOTE, errorMessage, jsonResponse, slugEnum, stampMcpContext } from './_helpers'\r\nimport {\r\n applyOperation,\r\n errorResponse,\r\n readPath,\r\n validateBlockList,\r\n writePath,\r\n} from './_layout-helpers'\r\n\r\n/**\r\n * patchGlobalLayout — surgical wrapper that mutates a single blocks-typed\r\n * field on a global without round-tripping the entire array through\r\n * updateGlobal. Same operation grammar as patchLayout (append, prepend,\r\n * insertAt, replaceAt, full); same recursive nesting validator against the\r\n * unified BlockNestingMap.\r\n *\r\n * Returns null when no global has any blocks field — the plugin entry\r\n * skips registration in that case, mirroring the schedulePublish pattern.\r\n */\r\nexport function createPatchGlobalLayoutTool(\r\n catalog: BlockCatalog,\r\n nesting: BlockNestingMap,\r\n draftGlobals: Set<string>,\r\n) {\r\n const allBlockSlugs = new Set(catalog.blocks.map((b) => b.slug))\r\n\r\n const nestingByGlobalField = new Map<string, string[]>()\r\n const nestingByBlockField = new Map<string, string[]>()\r\n for (const edge of nesting) {\r\n const key = `${edge.owner}:${edge.fieldPath}`\r\n if (edge.ownerType === 'global') {\r\n nestingByGlobalField.set(key, edge.acceptedBlockSlugs)\r\n } else if (edge.ownerType === 'block') {\r\n nestingByBlockField.set(key, edge.acceptedBlockSlugs)\r\n }\r\n }\r\n\r\n // Conditional registration: no global has a blocks field → no tool.\r\n if (nestingByGlobalField.size === 0) return null\r\n\r\n const patchableSlugs = [...new Set([...nestingByGlobalField.keys()].map((k) => k.split(':')[0]))]\r\n\r\n return {\r\n name: 'patchGlobalLayout',\r\n routing: { kind: 'global', action: 'update' } as const,\r\n description:\r\n 'Surgically modify a blocks-typed field on a global (e.g. footer sections, header nav) without sending the whole array. Same operation grammar as patchLayout. Use the blockNesting resource to see which slugs each field accepts; global-owned edges have ownerType \"global\".',\r\n parameters: {\r\n slug: slugEnum(patchableSlugs, 'global').describe(\r\n `The global slug whose blocks-typed field will be patched. One of: ${patchableSlugs.join(', ')}`,\r\n ),\r\n layoutField: z\r\n .string()\r\n .describe(\r\n 'Name (or dotted path) of the blocks-typed field on the global to patch — e.g. \"layout\", \"footer.sections\".',\r\n ),\r\n blocks: z\r\n .array(z.record(z.string(), z.unknown()))\r\n .describe(\r\n 'Blocks to compose. Each must have a `blockType` discriminator plus any block-specific fields. Nested blocks fields hold their own `blocks` arrays at any depth.',\r\n ),\r\n operation: z\r\n .enum(['append', 'prepend', 'insertAt', 'replaceAt', 'full'])\r\n .describe(\r\n 'How to apply the blocks: append (end), prepend (start), insertAt (at index), replaceAt (overwrite N starting at index), full (replace entire array — use with care).',\r\n ),\r\n insertIndex: z.number().optional().describe('Index for insertAt/replaceAt operations'),\r\n expectedUpdatedAt: z\r\n .string()\r\n .optional()\r\n .describe(\r\n 'Optional CAS guard. If set, the patch is rejected when the global\\'s current updatedAt differs from this value — protects against lost writes when concurrent edits race. Pass the updatedAt returned by a prior findGlobal call.',\r\n ),\r\n locale: z\r\n .string()\r\n .optional()\r\n .describe(\r\n 'Optional locale code (e.g. \"en\", \"fr\") to scope the patch to a single locale on localized blocks fields.',\r\n ),\r\n },\r\n handler: async (\r\n args: Record<string, unknown>,\r\n req: PayloadRequest,\r\n _extra: unknown,\r\n ) => {\r\n const { slug, layoutField, blocks, operation, insertIndex, expectedUpdatedAt, locale } =\r\n args as {\r\n slug: string\r\n layoutField: string\r\n blocks: Array<Record<string, unknown>>\r\n operation: 'append' | 'prepend' | 'insertAt' | 'replaceAt' | 'full'\r\n insertIndex?: number\r\n expectedUpdatedAt?: string\r\n locale?: string\r\n }\r\n\r\n const rootKey = `${slug}:${layoutField}`\r\n const rootAllowed = nestingByGlobalField.get(rootKey)\r\n if (!rootAllowed) {\r\n return errorResponse(\r\n `Field \"${layoutField}\" on global \"${slug}\" is not a blocks-typed field, or no nesting map entry exists for it.`,\r\n { availableFields: [...nestingByGlobalField.keys()] },\r\n )\r\n }\r\n\r\n const errors: string[] = []\r\n validateBlockList(blocks, rootAllowed, layoutField, allBlockSlugs, nestingByBlockField, errors)\r\n\r\n if (errors.length > 0) {\r\n return errorResponse('Block validation failed.', { errors })\r\n }\r\n\r\n stampMcpContext(req)\r\n\r\n let existing: Record<string, unknown> | undefined\r\n try {\r\n existing = (await req.payload.findGlobal({\r\n slug: slug as never,\r\n depth: 0,\r\n draft: true,\r\n ...(locale ? { locale: locale as never } : {}),\r\n req,\r\n overrideAccess: false,\r\n user: req.user,\r\n })) as Record<string, unknown> | undefined\r\n } catch (error) {\r\n return errorResponse(`Error fetching global \"${slug}\": ${errorMessage(error)}`)\r\n }\r\n\r\n if (expectedUpdatedAt !== undefined) {\r\n const currentUpdatedAt = (existing as { updatedAt?: unknown })?.updatedAt\r\n const currentStr =\r\n typeof currentUpdatedAt === 'string'\r\n ? currentUpdatedAt\r\n : currentUpdatedAt instanceof Date\r\n ? currentUpdatedAt.toISOString()\r\n : undefined\r\n if (currentStr !== expectedUpdatedAt) {\r\n return errorResponse(\r\n `Conflict: global \"${slug}\" was modified since expectedUpdatedAt (${expectedUpdatedAt}); current updatedAt is ${currentStr ?? 'unknown'}. Re-read the global and retry.`,\r\n { currentUpdatedAt: currentStr },\r\n )\r\n }\r\n }\r\n\r\n const currentLayout = readPath(existing, layoutField)\r\n const finalLayout = applyOperation(blocks, operation, insertIndex, currentLayout)\r\n\r\n const isDraftGlobal = draftGlobals.has(slug)\r\n\r\n try {\r\n await req.payload.updateGlobal({\r\n slug: slug as never,\r\n data: writePath(existing ?? {}, layoutField, finalLayout) as never,\r\n draft: isDraftGlobal,\r\n ...(locale ? { locale: locale as never } : {}),\r\n req,\r\n overrideAccess: false,\r\n user: req.user,\r\n })\r\n\r\n const draftNote = isDraftGlobal ? DRAFT_NOTE : ''\r\n return jsonResponse({\r\n success: true,\r\n message:\r\n `Patched ${layoutField} on global \"${slug}\". ` +\r\n `Operation: ${operation}. Block count: ${finalLayout.length}.` +\r\n draftNote,\r\n blockCount: finalLayout.length,\r\n operation,\r\n })\r\n } catch (error) {\r\n return errorResponse(`Error patching global \"${slug}\": ${errorMessage(error)}`)\r\n }\r\n },\r\n }\r\n}\r\n\r\n"],"names":["z","DRAFT_NOTE","errorMessage","jsonResponse","slugEnum","stampMcpContext","applyOperation","errorResponse","readPath","validateBlockList","writePath","createPatchGlobalLayoutTool","catalog","nesting","draftGlobals","allBlockSlugs","Set","blocks","map","b","slug","nestingByGlobalField","Map","nestingByBlockField","edge","key","owner","fieldPath","ownerType","set","acceptedBlockSlugs","size","patchableSlugs","keys","k","split","name","routing","kind","action","description","parameters","describe","join","layoutField","string","array","record","unknown","operation","enum","insertIndex","number","optional","expectedUpdatedAt","locale","handler","args","req","_extra","rootKey","rootAllowed","get","availableFields","errors","length","existing","payload","findGlobal","depth","draft","overrideAccess","user","error","undefined","currentUpdatedAt","updatedAt","currentStr","Date","toISOString","currentLayout","finalLayout","isDraftGlobal","has","updateGlobal","data","draftNote","success","message","blockCount"],"mappings":"AAAA,SAASA,CAAC,QAAQ,MAAK;AAGvB,SAASC,UAAU,EAAEC,YAAY,EAAEC,YAAY,EAAEC,QAAQ,EAAEC,eAAe,QAAQ,aAAY;AAC9F,SACEC,cAAc,EACdC,aAAa,EACbC,QAAQ,EACRC,iBAAiB,EACjBC,SAAS,QACJ,oBAAmB;AAE1B;;;;;;;;;CASC,GACD,OAAO,SAASC,4BACdC,OAAqB,EACrBC,OAAwB,EACxBC,YAAyB;IAEzB,MAAMC,gBAAgB,IAAIC,IAAIJ,QAAQK,MAAM,CAACC,GAAG,CAAC,CAACC,IAAMA,EAAEC,IAAI;IAE9D,MAAMC,uBAAuB,IAAIC;IACjC,MAAMC,sBAAsB,IAAID;IAChC,KAAK,MAAME,QAAQX,QAAS;QAC1B,MAAMY,MAAM,GAAGD,KAAKE,KAAK,CAAC,CAAC,EAAEF,KAAKG,SAAS,EAAE;QAC7C,IAAIH,KAAKI,SAAS,KAAK,UAAU;YAC/BP,qBAAqBQ,GAAG,CAACJ,KAAKD,KAAKM,kBAAkB;QACvD,OAAO,IAAIN,KAAKI,SAAS,KAAK,SAAS;YACrCL,oBAAoBM,GAAG,CAACJ,KAAKD,KAAKM,kBAAkB;QACtD;IACF;IAEA,oEAAoE;IACpE,IAAIT,qBAAqBU,IAAI,KAAK,GAAG,OAAO;IAE5C,MAAMC,iBAAiB;WAAI,IAAIhB,IAAI;eAAIK,qBAAqBY,IAAI;SAAG,CAACf,GAAG,CAAC,CAACgB,IAAMA,EAAEC,KAAK,CAAC,IAAI,CAAC,EAAE;KAAG;IAEjG,OAAO;QACLC,MAAM;QACNC,SAAS;YAAEC,MAAM;YAAUC,QAAQ;QAAS;QAC5CC,aACE;QACFC,YAAY;YACVrB,MAAMhB,SAAS4B,gBAAgB,UAAUU,QAAQ,CAC/C,CAAC,kEAAkE,EAAEV,eAAeW,IAAI,CAAC,OAAO;YAElGC,aAAa5C,EACV6C,MAAM,GACNH,QAAQ,CACP;YAEJzB,QAAQjB,EACL8C,KAAK,CAAC9C,EAAE+C,MAAM,CAAC/C,EAAE6C,MAAM,IAAI7C,EAAEgD,OAAO,KACpCN,QAAQ,CACP;YAEJO,WAAWjD,EACRkD,IAAI,CAAC;gBAAC;gBAAU;gBAAW;gBAAY;gBAAa;aAAO,EAC3DR,QAAQ,CACP;YAEJS,aAAanD,EAAEoD,MAAM,GAAGC,QAAQ,GAAGX,QAAQ,CAAC;YAC5CY,mBAAmBtD,EAChB6C,MAAM,GACNQ,QAAQ,GACRX,QAAQ,CACP;YAEJa,QAAQvD,EACL6C,MAAM,GACNQ,QAAQ,GACRX,QAAQ,CACP;QAEN;QACAc,SAAS,OACPC,MACAC,KACAC;YAEA,MAAM,EAAEvC,IAAI,EAAEwB,WAAW,EAAE3B,MAAM,EAAEgC,SAAS,EAAEE,WAAW,EAAEG,iBAAiB,EAAEC,MAAM,EAAE,GACpFE;YAUF,MAAMG,UAAU,GAAGxC,KAAK,CAAC,EAAEwB,aAAa;YACxC,MAAMiB,cAAcxC,qBAAqByC,GAAG,CAACF;YAC7C,IAAI,CAACC,aAAa;gBAChB,OAAOtD,cACL,CAAC,OAAO,EAAEqC,YAAY,aAAa,EAAExB,KAAK,qEAAqE,CAAC,EAChH;oBAAE2C,iBAAiB;2BAAI1C,qBAAqBY,IAAI;qBAAG;gBAAC;YAExD;YAEA,MAAM+B,SAAmB,EAAE;YAC3BvD,kBAAkBQ,QAAQ4C,aAAajB,aAAa7B,eAAeQ,qBAAqByC;YAExF,IAAIA,OAAOC,MAAM,GAAG,GAAG;gBACrB,OAAO1D,cAAc,4BAA4B;oBAAEyD;gBAAO;YAC5D;YAEA3D,gBAAgBqD;YAEhB,IAAIQ;YACJ,IAAI;gBACFA,WAAY,MAAMR,IAAIS,OAAO,CAACC,UAAU,CAAC;oBACvChD,MAAMA;oBACNiD,OAAO;oBACPC,OAAO;oBACP,GAAIf,SAAS;wBAAEA,QAAQA;oBAAgB,IAAI,CAAC,CAAC;oBAC7CG;oBACAa,gBAAgB;oBAChBC,MAAMd,IAAIc,IAAI;gBAChB;YACF,EAAE,OAAOC,OAAO;gBACd,OAAOlE,cAAc,CAAC,uBAAuB,EAAEa,KAAK,GAAG,EAAElB,aAAauE,QAAQ;YAChF;YAEA,IAAInB,sBAAsBoB,WAAW;gBACnC,MAAMC,mBAAoBT,UAAsCU;gBAChE,MAAMC,aACJ,OAAOF,qBAAqB,WACxBA,mBACAA,4BAA4BG,OAC1BH,iBAAiBI,WAAW,KAC5BL;gBACR,IAAIG,eAAevB,mBAAmB;oBACpC,OAAO/C,cACL,CAAC,kBAAkB,EAAEa,KAAK,wCAAwC,EAAEkC,kBAAkB,wBAAwB,EAAEuB,cAAc,UAAU,+BAA+B,CAAC,EACxK;wBAAEF,kBAAkBE;oBAAW;gBAEnC;YACF;YAEA,MAAMG,gBAAgBxE,SAAS0D,UAAUtB;YACzC,MAAMqC,cAAc3E,eAAeW,QAAQgC,WAAWE,aAAa6B;YAEnE,MAAME,gBAAgBpE,aAAaqE,GAAG,CAAC/D;YAEvC,IAAI;gBACF,MAAMsC,IAAIS,OAAO,CAACiB,YAAY,CAAC;oBAC7BhE,MAAMA;oBACNiE,MAAM3E,UAAUwD,YAAY,CAAC,GAAGtB,aAAaqC;oBAC7CX,OAAOY;oBACP,GAAI3B,SAAS;wBAAEA,QAAQA;oBAAgB,IAAI,CAAC,CAAC;oBAC7CG;oBACAa,gBAAgB;oBAChBC,MAAMd,IAAIc,IAAI;gBAChB;gBAEA,MAAMc,YAAYJ,gBAAgBjF,aAAa;gBAC/C,OAAOE,aAAa;oBAClBoF,SAAS;oBACTC,SACE,CAAC,QAAQ,EAAE5C,YAAY,YAAY,EAAExB,KAAK,GAAG,CAAC,GAC9C,CAAC,WAAW,EAAE6B,UAAU,eAAe,EAAEgC,YAAYhB,MAAM,CAAC,CAAC,CAAC,GAC9DqB;oBACFG,YAAYR,YAAYhB,MAAM;oBAC9BhB;gBACF;YACF,EAAE,OAAOwB,OAAO;gBACd,OAAOlE,cAAc,CAAC,uBAAuB,EAAEa,KAAK,GAAG,EAAElB,aAAauE,QAAQ;YAChF;QACF;IACF;AACF"}
1
+ {"version":3,"sources":["../../src/tools/patch-global-layout.ts"],"sourcesContent":["import { z } from 'zod'\nimport type { PayloadRequest } from 'payload'\nimport type { BlockCatalog, BlockNestingMap } from '../types'\nimport { DRAFT_NOTE, errorMessage, jsonResponse, slugEnum, stampMcpContext } from './_helpers'\nimport {\n applyOperation,\n errorResponse,\n readPath,\n validateBlockList,\n writePath,\n} from './_layout-helpers'\n\n/**\n * patchGlobalLayout — surgical wrapper that mutates a single blocks-typed\n * field on a global without round-tripping the entire array through\n * updateGlobal. Same operation grammar as patchLayout (append, prepend,\n * insertAt, replaceAt, full); same recursive nesting validator against the\n * unified BlockNestingMap.\n *\n * Returns null when no global has any blocks field — the plugin entry\n * skips registration in that case, mirroring the schedulePublish pattern.\n */\nexport function createPatchGlobalLayoutTool(\n catalog: BlockCatalog,\n nesting: BlockNestingMap,\n draftGlobals: Set<string>,\n) {\n const allBlockSlugs = new Set(catalog.blocks.map((b) => b.slug))\n\n const nestingByGlobalField = new Map<string, string[]>()\n const nestingByBlockField = new Map<string, string[]>()\n for (const edge of nesting) {\n const key = `${edge.owner}:${edge.fieldPath}`\n if (edge.ownerType === 'global') {\n nestingByGlobalField.set(key, edge.acceptedBlockSlugs)\n } else if (edge.ownerType === 'block') {\n nestingByBlockField.set(key, edge.acceptedBlockSlugs)\n }\n }\n\n // Conditional registration: no global has a blocks field → no tool.\n if (nestingByGlobalField.size === 0) return null\n\n const patchableSlugs = [...new Set([...nestingByGlobalField.keys()].map((k) => k.split(':')[0]))]\n\n return {\n name: 'patchGlobalLayout',\n routing: { kind: 'global', action: 'update' } as const,\n description:\n 'Surgically modify a blocks-typed field on a global (e.g. footer sections, header nav) without sending the whole array. Same operation grammar as patchLayout. Use the blockNesting resource to see which slugs each field accepts; global-owned edges have ownerType \"global\".',\n parameters: {\n slug: slugEnum(patchableSlugs, 'global').describe(\n `The global slug whose blocks-typed field will be patched. One of: ${patchableSlugs.join(', ')}`,\n ),\n layoutField: z\n .string()\n .describe(\n 'Name (or dotted path) of the blocks-typed field on the global to patch — e.g. \"layout\", \"footer.sections\".',\n ),\n blocks: z\n .array(z.record(z.string(), z.unknown()))\n .describe(\n 'Blocks to compose. Each must have a `blockType` discriminator plus any block-specific fields. Nested blocks fields hold their own `blocks` arrays at any depth.',\n ),\n operation: z\n .enum(['append', 'prepend', 'insertAt', 'replaceAt', 'full'])\n .describe(\n 'How to apply the blocks: append (end), prepend (start), insertAt (at index), replaceAt (overwrite N starting at index), full (replace entire array — use with care).',\n ),\n insertIndex: z.number().optional().describe('Index for insertAt/replaceAt operations'),\n expectedUpdatedAt: z\n .string()\n .optional()\n .describe(\n 'Optional CAS guard. If set, the patch is rejected when the global\\'s current updatedAt differs from this value — protects against lost writes when concurrent edits race. Pass the updatedAt returned by a prior findGlobal call.',\n ),\n locale: z\n .string()\n .optional()\n .describe(\n 'Optional locale code (e.g. \"en\", \"fr\") to scope the patch to a single locale on localized blocks fields.',\n ),\n },\n handler: async (\n args: Record<string, unknown>,\n req: PayloadRequest,\n _extra: unknown,\n ) => {\n const { slug, layoutField, blocks, operation, insertIndex, expectedUpdatedAt, locale } =\n args as {\n slug: string\n layoutField: string\n blocks: Array<Record<string, unknown>>\n operation: 'append' | 'prepend' | 'insertAt' | 'replaceAt' | 'full'\n insertIndex?: number\n expectedUpdatedAt?: string\n locale?: string\n }\n\n const rootKey = `${slug}:${layoutField}`\n const rootAllowed = nestingByGlobalField.get(rootKey)\n if (!rootAllowed) {\n return errorResponse(\n `Field \"${layoutField}\" on global \"${slug}\" is not a blocks-typed field, or no nesting map entry exists for it.`,\n { availableFields: [...nestingByGlobalField.keys()] },\n )\n }\n\n const errors: string[] = []\n validateBlockList(blocks, rootAllowed, layoutField, allBlockSlugs, nestingByBlockField, errors)\n\n if (errors.length > 0) {\n return errorResponse('Block validation failed.', { errors })\n }\n\n stampMcpContext(req)\n\n let existing: Record<string, unknown> | undefined\n try {\n existing = (await req.payload.findGlobal({\n slug: slug as never,\n depth: 0,\n draft: true,\n ...(locale ? { locale: locale as never } : {}),\n req,\n overrideAccess: false,\n user: req.user,\n })) as Record<string, unknown> | undefined\n } catch (error) {\n return errorResponse(`Error fetching global \"${slug}\": ${errorMessage(error)}`)\n }\n\n if (expectedUpdatedAt !== undefined) {\n const currentUpdatedAt = (existing as { updatedAt?: unknown })?.updatedAt\n const currentStr =\n typeof currentUpdatedAt === 'string'\n ? currentUpdatedAt\n : currentUpdatedAt instanceof Date\n ? currentUpdatedAt.toISOString()\n : undefined\n if (currentStr !== expectedUpdatedAt) {\n return errorResponse(\n `Conflict: global \"${slug}\" was modified since expectedUpdatedAt (${expectedUpdatedAt}); current updatedAt is ${currentStr ?? 'unknown'}. Re-read the global and retry.`,\n { currentUpdatedAt: currentStr },\n )\n }\n }\n\n const currentLayout = readPath(existing, layoutField)\n const finalLayout = applyOperation(blocks, operation, insertIndex, currentLayout)\n\n const isDraftGlobal = draftGlobals.has(slug)\n\n try {\n await req.payload.updateGlobal({\n slug: slug as never,\n data: writePath(existing ?? {}, layoutField, finalLayout) as never,\n draft: isDraftGlobal,\n ...(locale ? { locale: locale as never } : {}),\n req,\n overrideAccess: false,\n user: req.user,\n })\n\n const draftNote = isDraftGlobal ? DRAFT_NOTE : ''\n return jsonResponse({\n success: true,\n message:\n `Patched ${layoutField} on global \"${slug}\". ` +\n `Operation: ${operation}. Block count: ${finalLayout.length}.` +\n draftNote,\n blockCount: finalLayout.length,\n operation,\n })\n } catch (error) {\n return errorResponse(`Error patching global \"${slug}\": ${errorMessage(error)}`)\n }\n },\n }\n}\n\n"],"names":["z","DRAFT_NOTE","errorMessage","jsonResponse","slugEnum","stampMcpContext","applyOperation","errorResponse","readPath","validateBlockList","writePath","createPatchGlobalLayoutTool","catalog","nesting","draftGlobals","allBlockSlugs","Set","blocks","map","b","slug","nestingByGlobalField","Map","nestingByBlockField","edge","key","owner","fieldPath","ownerType","set","acceptedBlockSlugs","size","patchableSlugs","keys","k","split","name","routing","kind","action","description","parameters","describe","join","layoutField","string","array","record","unknown","operation","enum","insertIndex","number","optional","expectedUpdatedAt","locale","handler","args","req","_extra","rootKey","rootAllowed","get","availableFields","errors","length","existing","payload","findGlobal","depth","draft","overrideAccess","user","error","undefined","currentUpdatedAt","updatedAt","currentStr","Date","toISOString","currentLayout","finalLayout","isDraftGlobal","has","updateGlobal","data","draftNote","success","message","blockCount"],"mappings":"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"}
@@ -1,19 +1,19 @@
1
1
  import { z } from 'zod';
2
- import { DRAFT_NOTE, errorMessage, getDocDisplayName, jsonResponse, stampMcpContext } from './_helpers';
2
+ import { DRAFT_NOTE, errorMessage, getDocDisplayName, jsonResponse, stampMcpContext, populateDepth } from './_helpers';
3
3
  import { applyOperation, errorResponse, validateBlockList } from './_layout-helpers';
4
- /**
5
- * patchLayout — surgical wrapper that mutates a single document's blocks-typed
6
- * field directly without round-tripping the entire array through updateDocument.
7
- *
8
- * Why this exists: prompting an LLM to "add a CTA at the bottom of the home
9
- * page" via updateDocument forces it to send the whole layout array, which one
10
- * bad token can wipe out. patchLayout fetches the current array itself,
11
- * applies a scoped operation, and writes back — the LLM only describes the
12
- * delta.
13
- *
14
- * Validation walks every block recursively against the introspected
15
- * BlockNestingMap, so arbitrarily-nested layouts work as long as each
16
- * `blocks`-typed field's content matches that field's allow list.
4
+ /**
5
+ * patchLayout — surgical wrapper that mutates a single document's blocks-typed
6
+ * field directly without round-tripping the entire array through updateDocument.
7
+ *
8
+ * Why this exists: prompting an LLM to "add a CTA at the bottom of the home
9
+ * page" via updateDocument forces it to send the whole layout array, which one
10
+ * bad token can wipe out. patchLayout fetches the current array itself,
11
+ * applies a scoped operation, and writes back — the LLM only describes the
12
+ * delta.
13
+ *
14
+ * Validation walks every block recursively against the introspected
15
+ * BlockNestingMap, so arbitrarily-nested layouts work as long as each
16
+ * `blocks`-typed field's content matches that field's allow list.
17
17
  */ export function createPatchLayoutTool(catalog, nesting, draftCollections) {
18
18
  const allBlockSlugs = new Set(catalog.blocks.map((b)=>b.slug));
19
19
  // Lookups keyed by `<owner>:<fieldPath>` for O(1) access during recursive validation.
@@ -86,6 +86,7 @@ import { applyOperation, errorResponse, validateBlockList } from './_layout-help
86
86
  const isDraftCollection = draftCollections.has(collection);
87
87
  try {
88
88
  const updated = await req.payload.update({
89
+ depth: populateDepth(req),
89
90
  collection: collection,
90
91
  id: documentId,
91
92
  data: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/tools/patch-layout.ts"],"sourcesContent":["import { z } from 'zod'\r\nimport type { PayloadRequest } from 'payload'\r\nimport type { BlockCatalog, BlockNestingMap } from '../types'\r\nimport {\r\n DRAFT_NOTE,\r\n errorMessage,\r\n getDocDisplayName,\r\n jsonResponse,\r\n stampMcpContext,\r\n} from './_helpers'\r\nimport { applyOperation, errorResponse, validateBlockList } from './_layout-helpers'\r\n\r\n/**\r\n * patchLayout — surgical wrapper that mutates a single document's blocks-typed\r\n * field directly without round-tripping the entire array through updateDocument.\r\n *\r\n * Why this exists: prompting an LLM to \"add a CTA at the bottom of the home\r\n * page\" via updateDocument forces it to send the whole layout array, which one\r\n * bad token can wipe out. patchLayout fetches the current array itself,\r\n * applies a scoped operation, and writes back — the LLM only describes the\r\n * delta.\r\n *\r\n * Validation walks every block recursively against the introspected\r\n * BlockNestingMap, so arbitrarily-nested layouts work as long as each\r\n * `blocks`-typed field's content matches that field's allow list.\r\n */\r\nexport function createPatchLayoutTool(\r\n catalog: BlockCatalog,\r\n nesting: BlockNestingMap,\r\n draftCollections: Set<string>,\r\n) {\r\n const allBlockSlugs = new Set(catalog.blocks.map((b) => b.slug))\r\n\r\n // Lookups keyed by `<owner>:<fieldPath>` for O(1) access during recursive validation.\r\n const nestingByCollectionField = new Map<string, string[]>()\r\n const nestingByBlockField = new Map<string, string[]>()\r\n for (const edge of nesting) {\r\n const key = `${edge.owner}:${edge.fieldPath}`\r\n if (edge.ownerType === 'collection') {\r\n nestingByCollectionField.set(key, edge.acceptedBlockSlugs)\r\n } else {\r\n nestingByBlockField.set(key, edge.acceptedBlockSlugs)\r\n }\r\n }\r\n\r\n return {\r\n name: 'patchLayout',\r\n routing: { kind: 'collection', action: 'update' } as const,\r\n description:\r\n 'Surgically modify a document\\'s blocks-typed field (e.g. \"layout\") without sending the whole array. Pass the blocks to add/replace plus an operation (append, prepend, insertAt, replaceAt, full). The current array is fetched server-side and the operation is applied atomically. Each block must include a `blockType` plus its fields; nested `blocks`-typed fields can contain arbitrarily-deep block arrays as long as each level matches the schema. Use the `blockNesting` resource to see which slugs each field accepts.',\r\n parameters: {\r\n collection: z.string().describe('The collection slug containing the document'),\r\n documentId: z.string().describe('The ID of the document to patch'),\r\n layoutField: z\r\n .string()\r\n .optional()\r\n .default('layout')\r\n .describe('Name of the blocks-typed field to patch (default \"layout\")'),\r\n blocks: z\r\n .array(z.record(z.string(), z.unknown()))\r\n .describe(\r\n 'Blocks to compose. Each must have a `blockType` discriminator plus any block-specific fields. Nested blocks fields hold their own `blocks` arrays at any depth.',\r\n ),\r\n operation: z\r\n .enum(['append', 'prepend', 'insertAt', 'replaceAt', 'full'])\r\n .describe(\r\n 'How to apply the blocks: append (end), prepend (start), insertAt (at index), replaceAt (overwrite N starting at index), full (replace entire array — use with care).',\r\n ),\r\n insertIndex: z\r\n .number()\r\n .optional()\r\n .describe('Index for insertAt/replaceAt operations'),\r\n },\r\n handler: async (\r\n args: Record<string, unknown>,\r\n req: PayloadRequest,\r\n _extra: unknown,\r\n ) => {\r\n const {\r\n collection,\r\n documentId,\r\n layoutField = 'layout',\r\n blocks,\r\n operation,\r\n insertIndex,\r\n } = args as {\r\n collection: string\r\n documentId: string\r\n layoutField?: string\r\n blocks: Array<Record<string, unknown>>\r\n operation: 'append' | 'prepend' | 'insertAt' | 'replaceAt' | 'full'\r\n insertIndex?: number\r\n }\r\n\r\n const rootKey = `${collection}:${layoutField}`\r\n const rootAllowed = nestingByCollectionField.get(rootKey)\r\n if (!rootAllowed) {\r\n return errorResponse(\r\n `Field \"${layoutField}\" on collection \"${collection}\" is not a blocks-typed field, or no nesting map entry exists for it.`,\r\n { availableFields: [...nestingByCollectionField.keys()] },\r\n )\r\n }\r\n\r\n const errors: string[] = []\r\n validateBlockList(blocks, rootAllowed, layoutField, allBlockSlugs, nestingByBlockField, errors)\r\n\r\n if (errors.length > 0) {\r\n return errorResponse('Block validation failed.', { errors })\r\n }\r\n\r\n stampMcpContext(req)\r\n\r\n let existing: any\r\n try {\r\n existing = await req.payload.findByID({\r\n collection: collection as any,\r\n id: documentId,\r\n depth: 0,\r\n draft: true,\r\n req,\r\n overrideAccess: false,\r\n user: req.user,\r\n })\r\n } catch (error) {\r\n return errorResponse(`Error fetching ${collection}#${documentId}: ${errorMessage(error)}`)\r\n }\r\n\r\n const currentLayout = Array.isArray(existing?.[layoutField]) ? existing[layoutField] : []\r\n const finalLayout = applyOperation(blocks, operation, insertIndex, currentLayout)\r\n\r\n const isDraftCollection = draftCollections.has(collection)\r\n\r\n try {\r\n const updated = await req.payload.update({\r\n collection: collection as any,\r\n id: documentId,\r\n data: { [layoutField]: finalLayout } as any,\r\n draft: isDraftCollection,\r\n req,\r\n overrideAccess: false,\r\n user: req.user,\r\n })\r\n\r\n const displayName = getDocDisplayName(updated, documentId)\r\n const draftNote = isDraftCollection ? DRAFT_NOTE : ''\r\n\r\n return jsonResponse({\r\n success: true,\r\n message:\r\n `Patched ${layoutField} on \"${displayName}\" (${collection}#${documentId}). ` +\r\n `Operation: ${operation}. Block count: ${finalLayout.length}.` +\r\n draftNote,\r\n blockCount: finalLayout.length,\r\n operation,\r\n })\r\n } catch (error) {\r\n return errorResponse(`Error patching ${collection}#${documentId}: ${errorMessage(error)}`)\r\n }\r\n },\r\n }\r\n}\r\n\r\n"],"names":["z","DRAFT_NOTE","errorMessage","getDocDisplayName","jsonResponse","stampMcpContext","applyOperation","errorResponse","validateBlockList","createPatchLayoutTool","catalog","nesting","draftCollections","allBlockSlugs","Set","blocks","map","b","slug","nestingByCollectionField","Map","nestingByBlockField","edge","key","owner","fieldPath","ownerType","set","acceptedBlockSlugs","name","routing","kind","action","description","parameters","collection","string","describe","documentId","layoutField","optional","default","array","record","unknown","operation","enum","insertIndex","number","handler","args","req","_extra","rootKey","rootAllowed","get","availableFields","keys","errors","length","existing","payload","findByID","id","depth","draft","overrideAccess","user","error","currentLayout","Array","isArray","finalLayout","isDraftCollection","has","updated","update","data","displayName","draftNote","success","message","blockCount"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/tools/patch-layout.ts"],"sourcesContent":["import { z } from 'zod'\nimport type { PayloadRequest } from 'payload'\nimport type { BlockCatalog, BlockNestingMap } from '../types'\nimport {\n DRAFT_NOTE,\n errorMessage,\n getDocDisplayName,\n jsonResponse,\n stampMcpContext, populateDepth } from './_helpers'\nimport { applyOperation, errorResponse, validateBlockList } from './_layout-helpers'\n\n/**\n * patchLayout — surgical wrapper that mutates a single document's blocks-typed\n * field directly without round-tripping the entire array through updateDocument.\n *\n * Why this exists: prompting an LLM to \"add a CTA at the bottom of the home\n * page\" via updateDocument forces it to send the whole layout array, which one\n * bad token can wipe out. patchLayout fetches the current array itself,\n * applies a scoped operation, and writes back — the LLM only describes the\n * delta.\n *\n * Validation walks every block recursively against the introspected\n * BlockNestingMap, so arbitrarily-nested layouts work as long as each\n * `blocks`-typed field's content matches that field's allow list.\n */\nexport function createPatchLayoutTool(\n catalog: BlockCatalog,\n nesting: BlockNestingMap,\n draftCollections: Set<string>,\n) {\n const allBlockSlugs = new Set(catalog.blocks.map((b) => b.slug))\n\n // Lookups keyed by `<owner>:<fieldPath>` for O(1) access during recursive validation.\n const nestingByCollectionField = new Map<string, string[]>()\n const nestingByBlockField = new Map<string, string[]>()\n for (const edge of nesting) {\n const key = `${edge.owner}:${edge.fieldPath}`\n if (edge.ownerType === 'collection') {\n nestingByCollectionField.set(key, edge.acceptedBlockSlugs)\n } else {\n nestingByBlockField.set(key, edge.acceptedBlockSlugs)\n }\n }\n\n return {\n name: 'patchLayout',\n routing: { kind: 'collection', action: 'update' } as const,\n description:\n 'Surgically modify a document\\'s blocks-typed field (e.g. \"layout\") without sending the whole array. Pass the blocks to add/replace plus an operation (append, prepend, insertAt, replaceAt, full). The current array is fetched server-side and the operation is applied atomically. Each block must include a `blockType` plus its fields; nested `blocks`-typed fields can contain arbitrarily-deep block arrays as long as each level matches the schema. Use the `blockNesting` resource to see which slugs each field accepts.',\n parameters: {\n collection: z.string().describe('The collection slug containing the document'),\n documentId: z.string().describe('The ID of the document to patch'),\n layoutField: z\n .string()\n .optional()\n .default('layout')\n .describe('Name of the blocks-typed field to patch (default \"layout\")'),\n blocks: z\n .array(z.record(z.string(), z.unknown()))\n .describe(\n 'Blocks to compose. Each must have a `blockType` discriminator plus any block-specific fields. Nested blocks fields hold their own `blocks` arrays at any depth.',\n ),\n operation: z\n .enum(['append', 'prepend', 'insertAt', 'replaceAt', 'full'])\n .describe(\n 'How to apply the blocks: append (end), prepend (start), insertAt (at index), replaceAt (overwrite N starting at index), full (replace entire array — use with care).',\n ),\n insertIndex: z\n .number()\n .optional()\n .describe('Index for insertAt/replaceAt operations'),\n },\n handler: async (\n args: Record<string, unknown>,\n req: PayloadRequest,\n _extra: unknown,\n ) => {\n const {\n collection,\n documentId,\n layoutField = 'layout',\n blocks,\n operation,\n insertIndex,\n } = args as {\n collection: string\n documentId: string\n layoutField?: string\n blocks: Array<Record<string, unknown>>\n operation: 'append' | 'prepend' | 'insertAt' | 'replaceAt' | 'full'\n insertIndex?: number\n }\n\n const rootKey = `${collection}:${layoutField}`\n const rootAllowed = nestingByCollectionField.get(rootKey)\n if (!rootAllowed) {\n return errorResponse(\n `Field \"${layoutField}\" on collection \"${collection}\" is not a blocks-typed field, or no nesting map entry exists for it.`,\n { availableFields: [...nestingByCollectionField.keys()] },\n )\n }\n\n const errors: string[] = []\n validateBlockList(blocks, rootAllowed, layoutField, allBlockSlugs, nestingByBlockField, errors)\n\n if (errors.length > 0) {\n return errorResponse('Block validation failed.', { errors })\n }\n\n stampMcpContext(req)\n\n let existing: any\n try {\n existing = await req.payload.findByID({\n collection: collection as any,\n id: documentId,\n depth: 0,\n draft: true,\n req,\n overrideAccess: false,\n user: req.user,\n })\n } catch (error) {\n return errorResponse(`Error fetching ${collection}#${documentId}: ${errorMessage(error)}`)\n }\n\n const currentLayout = Array.isArray(existing?.[layoutField]) ? existing[layoutField] : []\n const finalLayout = applyOperation(blocks, operation, insertIndex, currentLayout)\n\n const isDraftCollection = draftCollections.has(collection)\n\n try {\n const updated = await req.payload.update({\n depth: populateDepth(req),\n collection: collection as any,\n id: documentId,\n data: { [layoutField]: finalLayout } as any,\n draft: isDraftCollection,\n req,\n overrideAccess: false,\n user: req.user,\n })\n\n const displayName = getDocDisplayName(updated, documentId)\n const draftNote = isDraftCollection ? DRAFT_NOTE : ''\n\n return jsonResponse({\n success: true,\n message:\n `Patched ${layoutField} on \"${displayName}\" (${collection}#${documentId}). ` +\n `Operation: ${operation}. Block count: ${finalLayout.length}.` +\n draftNote,\n blockCount: finalLayout.length,\n operation,\n })\n } catch (error) {\n return errorResponse(`Error patching ${collection}#${documentId}: ${errorMessage(error)}`)\n }\n },\n }\n}\n\n"],"names":["z","DRAFT_NOTE","errorMessage","getDocDisplayName","jsonResponse","stampMcpContext","populateDepth","applyOperation","errorResponse","validateBlockList","createPatchLayoutTool","catalog","nesting","draftCollections","allBlockSlugs","Set","blocks","map","b","slug","nestingByCollectionField","Map","nestingByBlockField","edge","key","owner","fieldPath","ownerType","set","acceptedBlockSlugs","name","routing","kind","action","description","parameters","collection","string","describe","documentId","layoutField","optional","default","array","record","unknown","operation","enum","insertIndex","number","handler","args","req","_extra","rootKey","rootAllowed","get","availableFields","keys","errors","length","existing","payload","findByID","id","depth","draft","overrideAccess","user","error","currentLayout","Array","isArray","finalLayout","isDraftCollection","has","updated","update","data","displayName","draftNote","success","message","blockCount"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { z } from 'zod';
2
- import { errorMessage, getDocDisplayName, requireDraftCollection, snapshotPublishMarker, stampMcpContext, textResponse, verifyPublishSucceededDespiteError } from './_helpers';
2
+ import { errorMessage, getDocDisplayName, requireDraftCollection, snapshotPublishMarker, stampMcpContext, textResponse, verifyPublishSucceededDespiteError, populateDepth } from './_helpers';
3
3
  export function createPublishDraftTool(draftCollections) {
4
4
  return {
5
5
  name: 'publishDraft',
@@ -31,6 +31,7 @@ export function createPublishDraftTool(draftCollections) {
31
31
  });
32
32
  try {
33
33
  const doc = await req.payload.update({
34
+ depth: populateDepth(req),
34
35
  collection: collection,
35
36
  id: documentId,
36
37
  data: {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/tools/publish-draft.ts"],"sourcesContent":["import { z } from 'zod'\r\nimport type { PayloadRequest } from 'payload'\r\nimport {\r\n errorMessage,\r\n getDocDisplayName,\r\n requireDraftCollection,\r\n snapshotPublishMarker,\r\n stampMcpContext,\r\n textResponse,\r\n verifyPublishSucceededDespiteError,\r\n} from './_helpers'\r\n\r\nexport function createPublishDraftTool(draftCollections: Set<string>) {\r\n return {\r\n name: 'publishDraft',\r\n routing: { kind: 'collection', action: 'update' } as const,\r\n description:\r\n 'Publish a draft document by transitioning its _status from \"draft\" to \"published\". ' +\r\n 'Only works on collections that support drafts. Use after creating or editing content ' +\r\n 'to make it live on the site.',\r\n parameters: {\r\n collection: z\r\n .string()\r\n .describe(\r\n `The collection slug. Draft-enabled collections: ${[...draftCollections].join(', ') || 'none'}`,\r\n ),\r\n documentId: z.string().describe('The ID of the document to publish'),\r\n },\r\n handler: async (\r\n rawArgs: Record<string, unknown>,\r\n req: PayloadRequest,\r\n _extra: unknown,\r\n ) => {\r\n const args = rawArgs as { collection: string; documentId: string }\r\n const { collection, documentId } = args\r\n\r\n const guard = requireDraftCollection(collection, draftCollections)\r\n if (guard) return guard\r\n\r\n stampMcpContext(req)\r\n\r\n // Snapshot the doc's pre-update `updatedAt` so the recovery branch\r\n // below can distinguish \"this attempt landed despite a post-write\r\n // validator throw\" from \"an older publish was successful and this\r\n // attempt did nothing\" (see verifyPublishSucceededDespiteError).\r\n const preMarker = await snapshotPublishMarker(req, {\r\n kind: 'collection',\r\n slug: collection,\r\n id: documentId,\r\n })\r\n\r\n try {\r\n const doc = await req.payload.update({\r\n collection: collection as any,\r\n id: documentId,\r\n data: { _status: 'published' } as any,\r\n req,\r\n overrideAccess: false,\r\n user: req.user,\r\n })\r\n\r\n const displayName = getDocDisplayName(doc, documentId)\r\n return textResponse(\r\n `Successfully published \"${displayName}\" in ${collection} (ID: ${documentId}).`,\r\n )\r\n } catch (error) {\r\n // Payload's update can throw a field-validation error AFTER a new\r\n // published version has already been written to the `_<slug>_v`\r\n // versions table (the validator runs in beforeChange-Fields, which\r\n // fires after Collection-level beforeChange hooks have already\r\n // mutated `data` and after the version row has been committed in\r\n // some draft+versions setups — see the breadcrumb self-reference\r\n // bug surfaced by `@payloadcms/plugin-nested-docs` on Payload v3).\r\n // The visible-to-the-user effect is \"publish appears to fail but\r\n // the document is in fact live\". Verify against the pre-update\r\n // marker before downgrading to a warning, so a stale published\r\n // version from a prior successful publish cannot mask a real\r\n // failure of the current attempt.\r\n const liveDoc = await verifyPublishSucceededDespiteError(\r\n req,\r\n { kind: 'collection', slug: collection, id: documentId },\r\n preMarker,\r\n )\r\n if (liveDoc) {\r\n const displayName = getDocDisplayName(liveDoc, documentId)\r\n // Stable token prefix lets MCP clients branch on the published-\r\n // with-warning state without regex-matching the prose body.\r\n return textResponse(\r\n `[publishDraft:published_with_warning] ` +\r\n `Published \"${displayName}\" in ${collection} (ID: ${documentId}) — ` +\r\n `but Payload reported a post-write validation error: ${errorMessage(error)}. ` +\r\n `The document is live; the error did not roll back the published version.`,\r\n )\r\n }\r\n return textResponse(\r\n `Error publishing document ${documentId} in ${collection}: ${errorMessage(error)}`,\r\n )\r\n }\r\n },\r\n }\r\n}\r\n"],"names":["z","errorMessage","getDocDisplayName","requireDraftCollection","snapshotPublishMarker","stampMcpContext","textResponse","verifyPublishSucceededDespiteError","createPublishDraftTool","draftCollections","name","routing","kind","action","description","parameters","collection","string","describe","join","documentId","handler","rawArgs","req","_extra","args","guard","preMarker","slug","id","doc","payload","update","data","_status","overrideAccess","user","displayName","error","liveDoc"],"mappings":"AAAA,SAASA,CAAC,QAAQ,MAAK;AAEvB,SACEC,YAAY,EACZC,iBAAiB,EACjBC,sBAAsB,EACtBC,qBAAqB,EACrBC,eAAe,EACfC,YAAY,EACZC,kCAAkC,QAC7B,aAAY;AAEnB,OAAO,SAASC,uBAAuBC,gBAA6B;IAClE,OAAO;QACLC,MAAM;QACNC,SAAS;YAAEC,MAAM;YAAcC,QAAQ;QAAS;QAChDC,aACE,wFACA,0FACA;QACFC,YAAY;YACVC,YAAYhB,EACTiB,MAAM,GACNC,QAAQ,CACP,CAAC,gDAAgD,EAAE;mBAAIT;aAAiB,CAACU,IAAI,CAAC,SAAS,QAAQ;YAEnGC,YAAYpB,EAAEiB,MAAM,GAAGC,QAAQ,CAAC;QAClC;QACAG,SAAS,OACPC,SACAC,KACAC;YAEA,MAAMC,OAAOH;YACb,MAAM,EAAEN,UAAU,EAAEI,UAAU,EAAE,GAAGK;YAEnC,MAAMC,QAAQvB,uBAAuBa,YAAYP;YACjD,IAAIiB,OAAO,OAAOA;YAElBrB,gBAAgBkB;YAEhB,mEAAmE;YACnE,kEAAkE;YAClE,kEAAkE;YAClE,iEAAiE;YACjE,MAAMI,YAAY,MAAMvB,sBAAsBmB,KAAK;gBACjDX,MAAM;gBACNgB,MAAMZ;gBACNa,IAAIT;YACN;YAEA,IAAI;gBACF,MAAMU,MAAM,MAAMP,IAAIQ,OAAO,CAACC,MAAM,CAAC;oBACnChB,YAAYA;oBACZa,IAAIT;oBACJa,MAAM;wBAAEC,SAAS;oBAAY;oBAC7BX;oBACAY,gBAAgB;oBAChBC,MAAMb,IAAIa,IAAI;gBAChB;gBAEA,MAAMC,cAAcnC,kBAAkB4B,KAAKV;gBAC3C,OAAOd,aACL,CAAC,wBAAwB,EAAE+B,YAAY,KAAK,EAAErB,WAAW,MAAM,EAAEI,WAAW,EAAE,CAAC;YAEnF,EAAE,OAAOkB,OAAO;gBACd,kEAAkE;gBAClE,gEAAgE;gBAChE,mEAAmE;gBACnE,+DAA+D;gBAC/D,iEAAiE;gBACjE,iEAAiE;gBACjE,mEAAmE;gBACnE,iEAAiE;gBACjE,+DAA+D;gBAC/D,+DAA+D;gBAC/D,6DAA6D;gBAC7D,kCAAkC;gBAClC,MAAMC,UAAU,MAAMhC,mCACpBgB,KACA;oBAAEX,MAAM;oBAAcgB,MAAMZ;oBAAYa,IAAIT;gBAAW,GACvDO;gBAEF,IAAIY,SAAS;oBACX,MAAMF,cAAcnC,kBAAkBqC,SAASnB;oBAC/C,gEAAgE;oBAChE,4DAA4D;oBAC5D,OAAOd,aACL,CAAC,sCAAsC,CAAC,GACtC,CAAC,WAAW,EAAE+B,YAAY,KAAK,EAAErB,WAAW,MAAM,EAAEI,WAAW,IAAI,CAAC,GACpE,CAAC,oDAAoD,EAAEnB,aAAaqC,OAAO,EAAE,CAAC,GAC9E,CAAC,wEAAwE,CAAC;gBAEhF;gBACA,OAAOhC,aACL,CAAC,0BAA0B,EAAEc,WAAW,IAAI,EAAEJ,WAAW,EAAE,EAAEf,aAAaqC,QAAQ;YAEtF;QACF;IACF;AACF"}
1
+ {"version":3,"sources":["../../src/tools/publish-draft.ts"],"sourcesContent":["import { z } from 'zod'\nimport type { PayloadRequest } from 'payload'\nimport {\n errorMessage,\n getDocDisplayName,\n requireDraftCollection,\n snapshotPublishMarker,\n stampMcpContext,\n textResponse,\n verifyPublishSucceededDespiteError, populateDepth } from './_helpers'\n\nexport function createPublishDraftTool(draftCollections: Set<string>) {\n return {\n name: 'publishDraft',\n routing: { kind: 'collection', action: 'update' } as const,\n description:\n 'Publish a draft document by transitioning its _status from \"draft\" to \"published\". ' +\n 'Only works on collections that support drafts. Use after creating or editing content ' +\n 'to make it live on the site.',\n parameters: {\n collection: z\n .string()\n .describe(\n `The collection slug. Draft-enabled collections: ${[...draftCollections].join(', ') || 'none'}`,\n ),\n documentId: z.string().describe('The ID of the document to publish'),\n },\n handler: async (\n rawArgs: Record<string, unknown>,\n req: PayloadRequest,\n _extra: unknown,\n ) => {\n const args = rawArgs as { collection: string; documentId: string }\n const { collection, documentId } = args\n\n const guard = requireDraftCollection(collection, draftCollections)\n if (guard) return guard\n\n stampMcpContext(req)\n\n // Snapshot the doc's pre-update `updatedAt` so the recovery branch\n // below can distinguish \"this attempt landed despite a post-write\n // validator throw\" from \"an older publish was successful and this\n // attempt did nothing\" (see verifyPublishSucceededDespiteError).\n const preMarker = await snapshotPublishMarker(req, {\n kind: 'collection',\n slug: collection,\n id: documentId,\n })\n\n try {\n const doc = await req.payload.update({\n depth: populateDepth(req),\n collection: collection as any,\n id: documentId,\n data: { _status: 'published' } as any,\n req,\n overrideAccess: false,\n user: req.user,\n })\n\n const displayName = getDocDisplayName(doc, documentId)\n return textResponse(\n `Successfully published \"${displayName}\" in ${collection} (ID: ${documentId}).`,\n )\n } catch (error) {\n // Payload's update can throw a field-validation error AFTER a new\n // published version has already been written to the `_<slug>_v`\n // versions table (the validator runs in beforeChange-Fields, which\n // fires after Collection-level beforeChange hooks have already\n // mutated `data` and after the version row has been committed in\n // some draft+versions setups — see the breadcrumb self-reference\n // bug surfaced by `@payloadcms/plugin-nested-docs` on Payload v3).\n // The visible-to-the-user effect is \"publish appears to fail but\n // the document is in fact live\". Verify against the pre-update\n // marker before downgrading to a warning, so a stale published\n // version from a prior successful publish cannot mask a real\n // failure of the current attempt.\n const liveDoc = await verifyPublishSucceededDespiteError(\n req,\n { kind: 'collection', slug: collection, id: documentId },\n preMarker,\n )\n if (liveDoc) {\n const displayName = getDocDisplayName(liveDoc, documentId)\n // Stable token prefix lets MCP clients branch on the published-\n // with-warning state without regex-matching the prose body.\n return textResponse(\n `[publishDraft:published_with_warning] ` +\n `Published \"${displayName}\" in ${collection} (ID: ${documentId}) — ` +\n `but Payload reported a post-write validation error: ${errorMessage(error)}. ` +\n `The document is live; the error did not roll back the published version.`,\n )\n }\n return textResponse(\n `Error publishing document ${documentId} in ${collection}: ${errorMessage(error)}`,\n )\n }\n },\n }\n}\n"],"names":["z","errorMessage","getDocDisplayName","requireDraftCollection","snapshotPublishMarker","stampMcpContext","textResponse","verifyPublishSucceededDespiteError","populateDepth","createPublishDraftTool","draftCollections","name","routing","kind","action","description","parameters","collection","string","describe","join","documentId","handler","rawArgs","req","_extra","args","guard","preMarker","slug","id","doc","payload","update","depth","data","_status","overrideAccess","user","displayName","error","liveDoc"],"mappings":"AAAA,SAASA,CAAC,QAAQ,MAAK;AAEvB,SACEC,YAAY,EACZC,iBAAiB,EACjBC,sBAAsB,EACtBC,qBAAqB,EACrBC,eAAe,EACfC,YAAY,EACZC,kCAAkC,EAAEC,aAAa,QAAQ,aAAY;AAEvE,OAAO,SAASC,uBAAuBC,gBAA6B;IAClE,OAAO;QACLC,MAAM;QACNC,SAAS;YAAEC,MAAM;YAAcC,QAAQ;QAAS;QAChDC,aACE,wFACA,0FACA;QACFC,YAAY;YACVC,YAAYjB,EACTkB,MAAM,GACNC,QAAQ,CACP,CAAC,gDAAgD,EAAE;mBAAIT;aAAiB,CAACU,IAAI,CAAC,SAAS,QAAQ;YAEnGC,YAAYrB,EAAEkB,MAAM,GAAGC,QAAQ,CAAC;QAClC;QACAG,SAAS,OACPC,SACAC,KACAC;YAEA,MAAMC,OAAOH;YACb,MAAM,EAAEN,UAAU,EAAEI,UAAU,EAAE,GAAGK;YAEnC,MAAMC,QAAQxB,uBAAuBc,YAAYP;YACjD,IAAIiB,OAAO,OAAOA;YAElBtB,gBAAgBmB;YAEhB,mEAAmE;YACnE,kEAAkE;YAClE,kEAAkE;YAClE,iEAAiE;YACjE,MAAMI,YAAY,MAAMxB,sBAAsBoB,KAAK;gBACjDX,MAAM;gBACNgB,MAAMZ;gBACNa,IAAIT;YACN;YAEA,IAAI;gBACF,MAAMU,MAAM,MAAMP,IAAIQ,OAAO,CAACC,MAAM,CAAC;oBACnCC,OAAO1B,cAAcgB;oBACrBP,YAAYA;oBACZa,IAAIT;oBACJc,MAAM;wBAAEC,SAAS;oBAAY;oBAC7BZ;oBACAa,gBAAgB;oBAChBC,MAAMd,IAAIc,IAAI;gBAChB;gBAEA,MAAMC,cAAcrC,kBAAkB6B,KAAKV;gBAC3C,OAAOf,aACL,CAAC,wBAAwB,EAAEiC,YAAY,KAAK,EAAEtB,WAAW,MAAM,EAAEI,WAAW,EAAE,CAAC;YAEnF,EAAE,OAAOmB,OAAO;gBACd,kEAAkE;gBAClE,gEAAgE;gBAChE,mEAAmE;gBACnE,+DAA+D;gBAC/D,iEAAiE;gBACjE,iEAAiE;gBACjE,mEAAmE;gBACnE,iEAAiE;gBACjE,+DAA+D;gBAC/D,+DAA+D;gBAC/D,6DAA6D;gBAC7D,kCAAkC;gBAClC,MAAMC,UAAU,MAAMlC,mCACpBiB,KACA;oBAAEX,MAAM;oBAAcgB,MAAMZ;oBAAYa,IAAIT;gBAAW,GACvDO;gBAEF,IAAIa,SAAS;oBACX,MAAMF,cAAcrC,kBAAkBuC,SAASpB;oBAC/C,gEAAgE;oBAChE,4DAA4D;oBAC5D,OAAOf,aACL,CAAC,sCAAsC,CAAC,GACtC,CAAC,WAAW,EAAEiC,YAAY,KAAK,EAAEtB,WAAW,MAAM,EAAEI,WAAW,IAAI,CAAC,GACpE,CAAC,oDAAoD,EAAEpB,aAAauC,OAAO,EAAE,CAAC,GAC9E,CAAC,wEAAwE,CAAC;gBAEhF;gBACA,OAAOlC,aACL,CAAC,0BAA0B,EAAEe,WAAW,IAAI,EAAEJ,WAAW,EAAE,EAAEhB,aAAauC,QAAQ;YAEtF;QACF;IACF;AACF"}
@@ -1,9 +1,9 @@
1
1
  import { z } from 'zod';
2
2
  import { errorMessage, slugEnum, snapshotPublishMarker, stampMcpContext, textResponse, verifyPublishSucceededDespiteError } from './_helpers';
3
- /**
4
- * Promote a draft-enabled global's pending draft to published. Mirrors
5
- * publishDraft for collections. Returns null when no global has drafts
6
- * enabled so the plugin entry can skip registration.
3
+ /**
4
+ * Promote a draft-enabled global's pending draft to published. Mirrors
5
+ * publishDraft for collections. Returns null when no global has drafts
6
+ * enabled so the plugin entry can skip registration.
7
7
  */ export function createPublishGlobalDraftTool(draftGlobals) {
8
8
  if (draftGlobals.size === 0) return null;
9
9
  const slugs = [
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/tools/publish-global-draft.ts"],"sourcesContent":["import { z } from 'zod'\r\nimport type { PayloadRequest } from 'payload'\r\nimport {\r\n errorMessage,\r\n slugEnum,\r\n snapshotPublishMarker,\r\n stampMcpContext,\r\n textResponse,\r\n verifyPublishSucceededDespiteError,\r\n} from './_helpers'\r\n\r\n/**\r\n * Promote a draft-enabled global's pending draft to published. Mirrors\r\n * publishDraft for collections. Returns null when no global has drafts\r\n * enabled so the plugin entry can skip registration.\r\n */\r\nexport function createPublishGlobalDraftTool(draftGlobals: Set<string>) {\r\n if (draftGlobals.size === 0) return null\r\n\r\n const slugs = [...draftGlobals]\r\n return {\r\n name: 'publishGlobalDraft',\r\n routing: { kind: 'global', action: 'update' } as const,\r\n description:\r\n 'Publish a draft-enabled global by transitioning its _status from \"draft\" to \"published\". ' +\r\n `Draft-enabled globals: ${slugs.join(', ')}`,\r\n parameters: {\r\n slug: slugEnum(slugs, 'global').describe(`The global slug. One of: ${slugs.join(', ')}`),\r\n locale: z\r\n .string()\r\n .optional()\r\n .describe('Optional locale code (e.g. \"en\", \"fr\") for the publish operation.'),\r\n },\r\n handler: async (args: Record<string, unknown>, req: PayloadRequest, _extra: unknown) => {\r\n const { slug, locale } = args as { slug: string; locale?: string }\r\n\r\n if (!draftGlobals.has(slug)) {\r\n return textResponse(\r\n `Error: Global \"${slug}\" does not support drafts. Draft-enabled globals: ${slugs.join(', ') || 'none'}`,\r\n )\r\n }\r\n\r\n stampMcpContext(req)\r\n\r\n // Capture pre-update marker so the recovery branch below can\r\n // distinguish a publish that landed despite a post-write throw from\r\n // a pre-existing published version inherited from an earlier\r\n // successful publish.\r\n const preMarker = await snapshotPublishMarker(req, {\r\n kind: 'global',\r\n slug,\r\n ...(locale ? { locale } : {}),\r\n })\r\n\r\n try {\r\n // `slug as never` / `locale as never`: Payload's updateGlobal /\r\n // findGlobal generic narrows the slug to a TConfig-derived literal\r\n // union we cannot satisfy with a runtime-supplied string.\r\n await req.payload.updateGlobal({\r\n slug: slug as never,\r\n data: { _status: 'published' } as never,\r\n ...(locale ? { locale: locale as never } : {}),\r\n req,\r\n overrideAccess: false,\r\n user: req.user,\r\n })\r\n return textResponse(`Successfully published global \"${slug}\".`)\r\n } catch (err) {\r\n // Mirror publishDraft's recovery (see publish-draft.ts for the\r\n // longer note on the post-write validator throw). The shared\r\n // helper enforces the strictly-newer `updatedAt` check, so a\r\n // pre-existing published row from an earlier publish cannot mask\r\n // a real failure of the current attempt.\r\n const liveGlobal = await verifyPublishSucceededDespiteError(\r\n req,\r\n { kind: 'global', slug, ...(locale ? { locale } : {}) },\r\n preMarker,\r\n )\r\n if (liveGlobal) {\r\n return textResponse(\r\n `[publishGlobalDraft:published_with_warning] ` +\r\n `Published global \"${slug}\" — but Payload reported a post-write validation error: ` +\r\n `${errorMessage(err)}. The global is live; the error did not roll back the ` +\r\n `published version.`,\r\n )\r\n }\r\n return textResponse(`Error publishing global \"${slug}\": ${errorMessage(err)}`)\r\n }\r\n },\r\n }\r\n}\r\n"],"names":["z","errorMessage","slugEnum","snapshotPublishMarker","stampMcpContext","textResponse","verifyPublishSucceededDespiteError","createPublishGlobalDraftTool","draftGlobals","size","slugs","name","routing","kind","action","description","join","parameters","slug","describe","locale","string","optional","handler","args","req","_extra","has","preMarker","payload","updateGlobal","data","_status","overrideAccess","user","err","liveGlobal"],"mappings":"AAAA,SAASA,CAAC,QAAQ,MAAK;AAEvB,SACEC,YAAY,EACZC,QAAQ,EACRC,qBAAqB,EACrBC,eAAe,EACfC,YAAY,EACZC,kCAAkC,QAC7B,aAAY;AAEnB;;;;CAIC,GACD,OAAO,SAASC,6BAA6BC,YAAyB;IACpE,IAAIA,aAAaC,IAAI,KAAK,GAAG,OAAO;IAEpC,MAAMC,QAAQ;WAAIF;KAAa;IAC/B,OAAO;QACLG,MAAM;QACNC,SAAS;YAAEC,MAAM;YAAUC,QAAQ;QAAS;QAC5CC,aACE,8FACA,CAAC,uBAAuB,EAAEL,MAAMM,IAAI,CAAC,OAAO;QAC9CC,YAAY;YACVC,MAAMhB,SAASQ,OAAO,UAAUS,QAAQ,CAAC,CAAC,yBAAyB,EAAET,MAAMM,IAAI,CAAC,OAAO;YACvFI,QAAQpB,EACLqB,MAAM,GACNC,QAAQ,GACRH,QAAQ,CAAC;QACd;QACAI,SAAS,OAAOC,MAA+BC,KAAqBC;YAClE,MAAM,EAAER,IAAI,EAAEE,MAAM,EAAE,GAAGI;YAEzB,IAAI,CAAChB,aAAamB,GAAG,CAACT,OAAO;gBAC3B,OAAOb,aACL,CAAC,eAAe,EAAEa,KAAK,kDAAkD,EAAER,MAAMM,IAAI,CAAC,SAAS,QAAQ;YAE3G;YAEAZ,gBAAgBqB;YAEhB,6DAA6D;YAC7D,oEAAoE;YACpE,6DAA6D;YAC7D,sBAAsB;YACtB,MAAMG,YAAY,MAAMzB,sBAAsBsB,KAAK;gBACjDZ,MAAM;gBACNK;gBACA,GAAIE,SAAS;oBAAEA;gBAAO,IAAI,CAAC,CAAC;YAC9B;YAEA,IAAI;gBACF,gEAAgE;gBAChE,mEAAmE;gBACnE,0DAA0D;gBAC1D,MAAMK,IAAII,OAAO,CAACC,YAAY,CAAC;oBAC7BZ,MAAMA;oBACNa,MAAM;wBAAEC,SAAS;oBAAY;oBAC7B,GAAIZ,SAAS;wBAAEA,QAAQA;oBAAgB,IAAI,CAAC,CAAC;oBAC7CK;oBACAQ,gBAAgB;oBAChBC,MAAMT,IAAIS,IAAI;gBAChB;gBACA,OAAO7B,aAAa,CAAC,+BAA+B,EAAEa,KAAK,EAAE,CAAC;YAChE,EAAE,OAAOiB,KAAK;gBACZ,+DAA+D;gBAC/D,6DAA6D;gBAC7D,6DAA6D;gBAC7D,iEAAiE;gBACjE,yCAAyC;gBACzC,MAAMC,aAAa,MAAM9B,mCACvBmB,KACA;oBAAEZ,MAAM;oBAAUK;oBAAM,GAAIE,SAAS;wBAAEA;oBAAO,IAAI,CAAC,CAAC;gBAAE,GACtDQ;gBAEF,IAAIQ,YAAY;oBACd,OAAO/B,aACL,CAAC,4CAA4C,CAAC,GAC5C,CAAC,kBAAkB,EAAEa,KAAK,wDAAwD,CAAC,GACnF,GAAGjB,aAAakC,KAAK,sDAAsD,CAAC,GAC5E,CAAC,kBAAkB,CAAC;gBAE1B;gBACA,OAAO9B,aAAa,CAAC,yBAAyB,EAAEa,KAAK,GAAG,EAAEjB,aAAakC,MAAM;YAC/E;QACF;IACF;AACF"}
1
+ {"version":3,"sources":["../../src/tools/publish-global-draft.ts"],"sourcesContent":["import { z } from 'zod'\nimport type { PayloadRequest } from 'payload'\nimport {\n errorMessage,\n slugEnum,\n snapshotPublishMarker,\n stampMcpContext,\n textResponse,\n verifyPublishSucceededDespiteError,\n} from './_helpers'\n\n/**\n * Promote a draft-enabled global's pending draft to published. Mirrors\n * publishDraft for collections. Returns null when no global has drafts\n * enabled so the plugin entry can skip registration.\n */\nexport function createPublishGlobalDraftTool(draftGlobals: Set<string>) {\n if (draftGlobals.size === 0) return null\n\n const slugs = [...draftGlobals]\n return {\n name: 'publishGlobalDraft',\n routing: { kind: 'global', action: 'update' } as const,\n description:\n 'Publish a draft-enabled global by transitioning its _status from \"draft\" to \"published\". ' +\n `Draft-enabled globals: ${slugs.join(', ')}`,\n parameters: {\n slug: slugEnum(slugs, 'global').describe(`The global slug. One of: ${slugs.join(', ')}`),\n locale: z\n .string()\n .optional()\n .describe('Optional locale code (e.g. \"en\", \"fr\") for the publish operation.'),\n },\n handler: async (args: Record<string, unknown>, req: PayloadRequest, _extra: unknown) => {\n const { slug, locale } = args as { slug: string; locale?: string }\n\n if (!draftGlobals.has(slug)) {\n return textResponse(\n `Error: Global \"${slug}\" does not support drafts. Draft-enabled globals: ${slugs.join(', ') || 'none'}`,\n )\n }\n\n stampMcpContext(req)\n\n // Capture pre-update marker so the recovery branch below can\n // distinguish a publish that landed despite a post-write throw from\n // a pre-existing published version inherited from an earlier\n // successful publish.\n const preMarker = await snapshotPublishMarker(req, {\n kind: 'global',\n slug,\n ...(locale ? { locale } : {}),\n })\n\n try {\n // `slug as never` / `locale as never`: Payload's updateGlobal /\n // findGlobal generic narrows the slug to a TConfig-derived literal\n // union we cannot satisfy with a runtime-supplied string.\n await req.payload.updateGlobal({\n slug: slug as never,\n data: { _status: 'published' } as never,\n ...(locale ? { locale: locale as never } : {}),\n req,\n overrideAccess: false,\n user: req.user,\n })\n return textResponse(`Successfully published global \"${slug}\".`)\n } catch (err) {\n // Mirror publishDraft's recovery (see publish-draft.ts for the\n // longer note on the post-write validator throw). The shared\n // helper enforces the strictly-newer `updatedAt` check, so a\n // pre-existing published row from an earlier publish cannot mask\n // a real failure of the current attempt.\n const liveGlobal = await verifyPublishSucceededDespiteError(\n req,\n { kind: 'global', slug, ...(locale ? { locale } : {}) },\n preMarker,\n )\n if (liveGlobal) {\n return textResponse(\n `[publishGlobalDraft:published_with_warning] ` +\n `Published global \"${slug}\" — but Payload reported a post-write validation error: ` +\n `${errorMessage(err)}. The global is live; the error did not roll back the ` +\n `published version.`,\n )\n }\n return textResponse(`Error publishing global \"${slug}\": ${errorMessage(err)}`)\n }\n },\n }\n}\n"],"names":["z","errorMessage","slugEnum","snapshotPublishMarker","stampMcpContext","textResponse","verifyPublishSucceededDespiteError","createPublishGlobalDraftTool","draftGlobals","size","slugs","name","routing","kind","action","description","join","parameters","slug","describe","locale","string","optional","handler","args","req","_extra","has","preMarker","payload","updateGlobal","data","_status","overrideAccess","user","err","liveGlobal"],"mappings":"AAAA,SAASA,CAAC,QAAQ,MAAK;AAEvB,SACEC,YAAY,EACZC,QAAQ,EACRC,qBAAqB,EACrBC,eAAe,EACfC,YAAY,EACZC,kCAAkC,QAC7B,aAAY;AAEnB;;;;CAIC,GACD,OAAO,SAASC,6BAA6BC,YAAyB;IACpE,IAAIA,aAAaC,IAAI,KAAK,GAAG,OAAO;IAEpC,MAAMC,QAAQ;WAAIF;KAAa;IAC/B,OAAO;QACLG,MAAM;QACNC,SAAS;YAAEC,MAAM;YAAUC,QAAQ;QAAS;QAC5CC,aACE,8FACA,CAAC,uBAAuB,EAAEL,MAAMM,IAAI,CAAC,OAAO;QAC9CC,YAAY;YACVC,MAAMhB,SAASQ,OAAO,UAAUS,QAAQ,CAAC,CAAC,yBAAyB,EAAET,MAAMM,IAAI,CAAC,OAAO;YACvFI,QAAQpB,EACLqB,MAAM,GACNC,QAAQ,GACRH,QAAQ,CAAC;QACd;QACAI,SAAS,OAAOC,MAA+BC,KAAqBC;YAClE,MAAM,EAAER,IAAI,EAAEE,MAAM,EAAE,GAAGI;YAEzB,IAAI,CAAChB,aAAamB,GAAG,CAACT,OAAO;gBAC3B,OAAOb,aACL,CAAC,eAAe,EAAEa,KAAK,kDAAkD,EAAER,MAAMM,IAAI,CAAC,SAAS,QAAQ;YAE3G;YAEAZ,gBAAgBqB;YAEhB,6DAA6D;YAC7D,oEAAoE;YACpE,6DAA6D;YAC7D,sBAAsB;YACtB,MAAMG,YAAY,MAAMzB,sBAAsBsB,KAAK;gBACjDZ,MAAM;gBACNK;gBACA,GAAIE,SAAS;oBAAEA;gBAAO,IAAI,CAAC,CAAC;YAC9B;YAEA,IAAI;gBACF,gEAAgE;gBAChE,mEAAmE;gBACnE,0DAA0D;gBAC1D,MAAMK,IAAII,OAAO,CAACC,YAAY,CAAC;oBAC7BZ,MAAMA;oBACNa,MAAM;wBAAEC,SAAS;oBAAY;oBAC7B,GAAIZ,SAAS;wBAAEA,QAAQA;oBAAgB,IAAI,CAAC,CAAC;oBAC7CK;oBACAQ,gBAAgB;oBAChBC,MAAMT,IAAIS,IAAI;gBAChB;gBACA,OAAO7B,aAAa,CAAC,+BAA+B,EAAEa,KAAK,EAAE,CAAC;YAChE,EAAE,OAAOiB,KAAK;gBACZ,+DAA+D;gBAC/D,6DAA6D;gBAC7D,6DAA6D;gBAC7D,iEAAiE;gBACjE,yCAAyC;gBACzC,MAAMC,aAAa,MAAM9B,mCACvBmB,KACA;oBAAEZ,MAAM;oBAAUK;oBAAM,GAAIE,SAAS;wBAAEA;oBAAO,IAAI,CAAC,CAAC;gBAAE,GACtDQ;gBAEF,IAAIQ,YAAY;oBACd,OAAO/B,aACL,CAAC,4CAA4C,CAAC,GAC5C,CAAC,kBAAkB,EAAEa,KAAK,wDAAwD,CAAC,GACnF,GAAGjB,aAAakC,KAAK,sDAAsD,CAAC,GAC5E,CAAC,kBAAkB,CAAC;gBAE1B;gBACA,OAAO9B,aAAa,CAAC,yBAAyB,EAAEa,KAAK,GAAG,EAAEjB,aAAakC,MAAM;YAC/E;QACF;IACF;AACF"}
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/tools/resolve-reference.ts"],"sourcesContent":["import { z } from 'zod'\r\nimport type { PayloadRequest } from 'payload'\r\nimport { jsonResponse, stampMcpContext } from './_helpers'\r\n\r\ninterface MatchCandidate {\r\n collection: string\r\n id: string | number\r\n displayName: string\r\n matchedField: string\r\n matchType: 'exact-slug' | 'exact' | 'partial'\r\n}\r\n\r\nexport function createResolveReferenceTool(\r\n searchableCollections: Map<string, string[]>,\r\n) {\r\n return {\r\n name: 'resolveReference',\r\n routing: { kind: 'account', action: 'read' } as const,\r\n description:\r\n 'Search for documents across collections by name, title, or slug. ' +\r\n 'Returns ranked candidates with IDs for use in relationship fields. ' +\r\n 'Optionally filter to a specific collection.',\r\n parameters: z.object({\r\n query: z.string().describe('Search term to match against name, title, or slug fields'),\r\n collection: z\r\n .string()\r\n .optional()\r\n .describe('Optional collection slug to restrict search to a single collection'),\r\n }),\r\n handler: async (args: Record<string, unknown>, req: PayloadRequest) => {\r\n stampMcpContext(req)\r\n\r\n const { query, collection } = args as { query: string; collection?: string }\r\n\r\n const collectionsToSearch = collection\r\n ? new Map(\r\n searchableCollections.has(collection)\r\n ? [[collection, searchableCollections.get(collection)!]]\r\n : [],\r\n )\r\n : searchableCollections\r\n\r\n if (collectionsToSearch.size === 0) {\r\n const available = Array.from(searchableCollections.keys()).join(', ')\r\n return jsonResponse({\r\n candidates: [],\r\n message: collection\r\n ? `Collection \"${collection}\" has no searchable fields or does not exist. Available searchable collections: ${available}`\r\n : 'No searchable collections found.',\r\n })\r\n }\r\n\r\n const targets = Array.from(collectionsToSearch.entries()).filter(\r\n ([, fields]) => fields.length > 0,\r\n )\r\n\r\n const settled = await Promise.allSettled(\r\n targets.map(([slug, fields]) => {\r\n const selectFields: Record<string, true> = {}\r\n for (const field of fields) selectFields[field] = true\r\n return req.payload.find({\r\n collection: slug as any,\r\n where: { or: fields.map((field) => ({ [field]: { like: query } })) },\r\n limit: 5,\r\n select: selectFields,\r\n req,\r\n overrideAccess: false,\r\n user: req.user,\r\n })\r\n }),\r\n )\r\n\r\n const allCandidates: MatchCandidate[] = []\r\n settled.forEach((outcome, i) => {\r\n if (outcome.status !== 'fulfilled') return\r\n const [slug, fields] = targets[i]\r\n for (const doc of outcome.value.docs) {\r\n allCandidates.push(...rankDocument(doc, slug, fields, query))\r\n }\r\n })\r\n\r\n allCandidates.sort(\r\n (a, b) => matchTypePriority(a.matchType) - matchTypePriority(b.matchType),\r\n )\r\n\r\n if (allCandidates.length === 0) {\r\n const searched = Array.from(collectionsToSearch.keys()).join(', ')\r\n const available = Array.from(searchableCollections.keys()).join(', ')\r\n return jsonResponse({\r\n candidates: {},\r\n message: `No results found for \"${query}\" in: ${searched}. Try a different spelling or search term. All searchable collections: ${available}`,\r\n })\r\n }\r\n\r\n const grouped: Record<string, Omit<MatchCandidate, 'collection'>[]> = {}\r\n for (const candidate of allCandidates) {\r\n const { collection: col, ...rest } = candidate\r\n if (!grouped[col]) grouped[col] = []\r\n grouped[col].push(rest)\r\n }\r\n\r\n return jsonResponse({\r\n candidates: grouped,\r\n total: allCandidates.length,\r\n })\r\n },\r\n }\r\n}\r\n\r\nfunction rankDocument(\r\n doc: Record<string, any>,\r\n collection: string,\r\n fields: string[],\r\n query: string,\r\n): MatchCandidate[] {\r\n const queryLower = query.toLowerCase()\r\n let bestMatch: MatchCandidate | null = null\r\n\r\n for (const field of fields) {\r\n const value = doc[field]\r\n if (typeof value !== 'string') continue\r\n\r\n const valueLower = value.toLowerCase()\r\n let matchType: MatchCandidate['matchType']\r\n\r\n if (field === 'slug' && valueLower === queryLower) {\r\n matchType = 'exact-slug'\r\n } else if (valueLower === queryLower) {\r\n matchType = 'exact'\r\n } else {\r\n matchType = 'partial'\r\n }\r\n\r\n if (\r\n !bestMatch ||\r\n matchTypePriority(matchType) < matchTypePriority(bestMatch.matchType)\r\n ) {\r\n bestMatch = {\r\n collection,\r\n id: doc.id,\r\n displayName: getDisplayName(doc, fields),\r\n matchedField: field,\r\n matchType,\r\n }\r\n }\r\n }\r\n\r\n return bestMatch ? [bestMatch] : []\r\n}\r\n\r\nfunction matchTypePriority(type: MatchCandidate['matchType']): number {\r\n switch (type) {\r\n case 'exact-slug':\r\n return 0\r\n case 'exact':\r\n return 1\r\n case 'partial':\r\n return 2\r\n }\r\n}\r\n\r\nfunction getDisplayName(doc: Record<string, any>, fields: string[]): string {\r\n for (const preferred of ['name', 'title', 'slug']) {\r\n if (fields.includes(preferred) && typeof doc[preferred] === 'string') {\r\n return doc[preferred]\r\n }\r\n }\r\n return String(doc.id)\r\n}\r\n"],"names":["z","jsonResponse","stampMcpContext","createResolveReferenceTool","searchableCollections","name","routing","kind","action","description","parameters","object","query","string","describe","collection","optional","handler","args","req","collectionsToSearch","Map","has","get","size","available","Array","from","keys","join","candidates","message","targets","entries","filter","fields","length","settled","Promise","allSettled","map","slug","selectFields","field","payload","find","where","or","like","limit","select","overrideAccess","user","allCandidates","forEach","outcome","i","status","doc","value","docs","push","rankDocument","sort","a","b","matchTypePriority","matchType","searched","grouped","candidate","col","rest","total","queryLower","toLowerCase","bestMatch","valueLower","id","displayName","getDisplayName","matchedField","type","preferred","includes","String"],"mappings":"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"}
1
+ {"version":3,"sources":["../../src/tools/resolve-reference.ts"],"sourcesContent":["import { z } from 'zod'\nimport type { PayloadRequest } from 'payload'\nimport { jsonResponse, stampMcpContext } from './_helpers'\n\ninterface MatchCandidate {\n collection: string\n id: string | number\n displayName: string\n matchedField: string\n matchType: 'exact-slug' | 'exact' | 'partial'\n}\n\nexport function createResolveReferenceTool(\n searchableCollections: Map<string, string[]>,\n) {\n return {\n name: 'resolveReference',\n routing: { kind: 'account', action: 'read' } as const,\n description:\n 'Search for documents across collections by name, title, or slug. ' +\n 'Returns ranked candidates with IDs for use in relationship fields. ' +\n 'Optionally filter to a specific collection.',\n parameters: z.object({\n query: z.string().describe('Search term to match against name, title, or slug fields'),\n collection: z\n .string()\n .optional()\n .describe('Optional collection slug to restrict search to a single collection'),\n }),\n handler: async (args: Record<string, unknown>, req: PayloadRequest) => {\n stampMcpContext(req)\n\n const { query, collection } = args as { query: string; collection?: string }\n\n const collectionsToSearch = collection\n ? new Map(\n searchableCollections.has(collection)\n ? [[collection, searchableCollections.get(collection)!]]\n : [],\n )\n : searchableCollections\n\n if (collectionsToSearch.size === 0) {\n const available = Array.from(searchableCollections.keys()).join(', ')\n return jsonResponse({\n candidates: [],\n message: collection\n ? `Collection \"${collection}\" has no searchable fields or does not exist. Available searchable collections: ${available}`\n : 'No searchable collections found.',\n })\n }\n\n const targets = Array.from(collectionsToSearch.entries()).filter(\n ([, fields]) => fields.length > 0,\n )\n\n const settled = await Promise.allSettled(\n targets.map(([slug, fields]) => {\n const selectFields: Record<string, true> = {}\n for (const field of fields) selectFields[field] = true\n return req.payload.find({\n collection: slug as any,\n where: { or: fields.map((field) => ({ [field]: { like: query } })) },\n limit: 5,\n select: selectFields,\n req,\n overrideAccess: false,\n user: req.user,\n })\n }),\n )\n\n const allCandidates: MatchCandidate[] = []\n settled.forEach((outcome, i) => {\n if (outcome.status !== 'fulfilled') return\n const [slug, fields] = targets[i]\n for (const doc of outcome.value.docs) {\n allCandidates.push(...rankDocument(doc, slug, fields, query))\n }\n })\n\n allCandidates.sort(\n (a, b) => matchTypePriority(a.matchType) - matchTypePriority(b.matchType),\n )\n\n if (allCandidates.length === 0) {\n const searched = Array.from(collectionsToSearch.keys()).join(', ')\n const available = Array.from(searchableCollections.keys()).join(', ')\n return jsonResponse({\n candidates: {},\n message: `No results found for \"${query}\" in: ${searched}. Try a different spelling or search term. All searchable collections: ${available}`,\n })\n }\n\n const grouped: Record<string, Omit<MatchCandidate, 'collection'>[]> = {}\n for (const candidate of allCandidates) {\n const { collection: col, ...rest } = candidate\n if (!grouped[col]) grouped[col] = []\n grouped[col].push(rest)\n }\n\n return jsonResponse({\n candidates: grouped,\n total: allCandidates.length,\n })\n },\n }\n}\n\nfunction rankDocument(\n doc: Record<string, any>,\n collection: string,\n fields: string[],\n query: string,\n): MatchCandidate[] {\n const queryLower = query.toLowerCase()\n let bestMatch: MatchCandidate | null = null\n\n for (const field of fields) {\n const value = doc[field]\n if (typeof value !== 'string') continue\n\n const valueLower = value.toLowerCase()\n let matchType: MatchCandidate['matchType']\n\n if (field === 'slug' && valueLower === queryLower) {\n matchType = 'exact-slug'\n } else if (valueLower === queryLower) {\n matchType = 'exact'\n } else {\n matchType = 'partial'\n }\n\n if (\n !bestMatch ||\n matchTypePriority(matchType) < matchTypePriority(bestMatch.matchType)\n ) {\n bestMatch = {\n collection,\n id: doc.id,\n displayName: getDisplayName(doc, fields),\n matchedField: field,\n matchType,\n }\n }\n }\n\n return bestMatch ? [bestMatch] : []\n}\n\nfunction matchTypePriority(type: MatchCandidate['matchType']): number {\n switch (type) {\n case 'exact-slug':\n return 0\n case 'exact':\n return 1\n case 'partial':\n return 2\n }\n}\n\nfunction getDisplayName(doc: Record<string, any>, fields: string[]): string {\n for (const preferred of ['name', 'title', 'slug']) {\n if (fields.includes(preferred) && typeof doc[preferred] === 'string') {\n return doc[preferred]\n }\n }\n return String(doc.id)\n}\n"],"names":["z","jsonResponse","stampMcpContext","createResolveReferenceTool","searchableCollections","name","routing","kind","action","description","parameters","object","query","string","describe","collection","optional","handler","args","req","collectionsToSearch","Map","has","get","size","available","Array","from","keys","join","candidates","message","targets","entries","filter","fields","length","settled","Promise","allSettled","map","slug","selectFields","field","payload","find","where","or","like","limit","select","overrideAccess","user","allCandidates","forEach","outcome","i","status","doc","value","docs","push","rankDocument","sort","a","b","matchTypePriority","matchType","searched","grouped","candidate","col","rest","total","queryLower","toLowerCase","bestMatch","valueLower","id","displayName","getDisplayName","matchedField","type","preferred","includes","String"],"mappings":"AAAA,SAASA,CAAC,QAAQ,MAAK;AAEvB,SAASC,YAAY,EAAEC,eAAe,QAAQ,aAAY;AAU1D,OAAO,SAASC,2BACdC,qBAA4C;IAE5C,OAAO;QACLC,MAAM;QACNC,SAAS;YAAEC,MAAM;YAAWC,QAAQ;QAAO;QAC3CC,aACE,sEACA,wEACA;QACFC,YAAYV,EAAEW,MAAM,CAAC;YACnBC,OAAOZ,EAAEa,MAAM,GAAGC,QAAQ,CAAC;YAC3BC,YAAYf,EACTa,MAAM,GACNG,QAAQ,GACRF,QAAQ,CAAC;QACd;QACAG,SAAS,OAAOC,MAA+BC;YAC7CjB,gBAAgBiB;YAEhB,MAAM,EAAEP,KAAK,EAAEG,UAAU,EAAE,GAAGG;YAE9B,MAAME,sBAAsBL,aACxB,IAAIM,IACFjB,sBAAsBkB,GAAG,CAACP,cACtB;gBAAC;oBAACA;oBAAYX,sBAAsBmB,GAAG,CAACR;iBAAa;aAAC,GACtD,EAAE,IAERX;YAEJ,IAAIgB,oBAAoBI,IAAI,KAAK,GAAG;gBAClC,MAAMC,YAAYC,MAAMC,IAAI,CAACvB,sBAAsBwB,IAAI,IAAIC,IAAI,CAAC;gBAChE,OAAO5B,aAAa;oBAClB6B,YAAY,EAAE;oBACdC,SAAShB,aACL,CAAC,YAAY,EAAEA,WAAW,gFAAgF,EAAEU,WAAW,GACvH;gBACN;YACF;YAEA,MAAMO,UAAUN,MAAMC,IAAI,CAACP,oBAAoBa,OAAO,IAAIC,MAAM,CAC9D,CAAC,GAAGC,OAAO,GAAKA,OAAOC,MAAM,GAAG;YAGlC,MAAMC,UAAU,MAAMC,QAAQC,UAAU,CACtCP,QAAQQ,GAAG,CAAC,CAAC,CAACC,MAAMN,OAAO;gBACzB,MAAMO,eAAqC,CAAC;gBAC5C,KAAK,MAAMC,SAASR,OAAQO,YAAY,CAACC,MAAM,GAAG;gBAClD,OAAOxB,IAAIyB,OAAO,CAACC,IAAI,CAAC;oBACtB9B,YAAY0B;oBACZK,OAAO;wBAAEC,IAAIZ,OAAOK,GAAG,CAAC,CAACG,QAAW,CAAA;gCAAE,CAACA,MAAM,EAAE;oCAAEK,MAAMpC;gCAAM;4BAAE,CAAA;oBAAI;oBACnEqC,OAAO;oBACPC,QAAQR;oBACRvB;oBACAgC,gBAAgB;oBAChBC,MAAMjC,IAAIiC,IAAI;gBAChB;YACF;YAGF,MAAMC,gBAAkC,EAAE;YAC1ChB,QAAQiB,OAAO,CAAC,CAACC,SAASC;gBACxB,IAAID,QAAQE,MAAM,KAAK,aAAa;gBACpC,MAAM,CAAChB,MAAMN,OAAO,GAAGH,OAAO,CAACwB,EAAE;gBACjC,KAAK,MAAME,OAAOH,QAAQI,KAAK,CAACC,IAAI,CAAE;oBACpCP,cAAcQ,IAAI,IAAIC,aAAaJ,KAAKjB,MAAMN,QAAQvB;gBACxD;YACF;YAEAyC,cAAcU,IAAI,CAChB,CAACC,GAAGC,IAAMC,kBAAkBF,EAAEG,SAAS,IAAID,kBAAkBD,EAAEE,SAAS;YAG1E,IAAId,cAAcjB,MAAM,KAAK,GAAG;gBAC9B,MAAMgC,WAAW1C,MAAMC,IAAI,CAACP,oBAAoBQ,IAAI,IAAIC,IAAI,CAAC;gBAC7D,MAAMJ,YAAYC,MAAMC,IAAI,CAACvB,sBAAsBwB,IAAI,IAAIC,IAAI,CAAC;gBAChE,OAAO5B,aAAa;oBAClB6B,YAAY,CAAC;oBACbC,SAAS,CAAC,sBAAsB,EAAEnB,MAAM,MAAM,EAAEwD,SAAS,uEAAuE,EAAE3C,WAAW;gBAC/I;YACF;YAEA,MAAM4C,UAAgE,CAAC;YACvE,KAAK,MAAMC,aAAajB,cAAe;gBACrC,MAAM,EAAEtC,YAAYwD,GAAG,EAAE,GAAGC,MAAM,GAAGF;gBACrC,IAAI,CAACD,OAAO,CAACE,IAAI,EAAEF,OAAO,CAACE,IAAI,GAAG,EAAE;gBACpCF,OAAO,CAACE,IAAI,CAACV,IAAI,CAACW;YACpB;YAEA,OAAOvE,aAAa;gBAClB6B,YAAYuC;gBACZI,OAAOpB,cAAcjB,MAAM;YAC7B;QACF;IACF;AACF;AAEA,SAAS0B,aACPJ,GAAwB,EACxB3C,UAAkB,EAClBoB,MAAgB,EAChBvB,KAAa;IAEb,MAAM8D,aAAa9D,MAAM+D,WAAW;IACpC,IAAIC,YAAmC;IAEvC,KAAK,MAAMjC,SAASR,OAAQ;QAC1B,MAAMwB,QAAQD,GAAG,CAACf,MAAM;QACxB,IAAI,OAAOgB,UAAU,UAAU;QAE/B,MAAMkB,aAAalB,MAAMgB,WAAW;QACpC,IAAIR;QAEJ,IAAIxB,UAAU,UAAUkC,eAAeH,YAAY;YACjDP,YAAY;QACd,OAAO,IAAIU,eAAeH,YAAY;YACpCP,YAAY;QACd,OAAO;YACLA,YAAY;QACd;QAEA,IACE,CAACS,aACDV,kBAAkBC,aAAaD,kBAAkBU,UAAUT,SAAS,GACpE;YACAS,YAAY;gBACV7D;gBACA+D,IAAIpB,IAAIoB,EAAE;gBACVC,aAAaC,eAAetB,KAAKvB;gBACjC8C,cAActC;gBACdwB;YACF;QACF;IACF;IAEA,OAAOS,YAAY;QAACA;KAAU,GAAG,EAAE;AACrC;AAEA,SAASV,kBAAkBgB,IAAiC;IAC1D,OAAQA;QACN,KAAK;YACH,OAAO;QACT,KAAK;YACH,OAAO;QACT,KAAK;YACH,OAAO;IACX;AACF;AAEA,SAASF,eAAetB,GAAwB,EAAEvB,MAAgB;IAChE,KAAK,MAAMgD,aAAa;QAAC;QAAQ;QAAS;KAAO,CAAE;QACjD,IAAIhD,OAAOiD,QAAQ,CAACD,cAAc,OAAOzB,GAAG,CAACyB,UAAU,KAAK,UAAU;YACpE,OAAOzB,GAAG,CAACyB,UAAU;QACvB;IACF;IACA,OAAOE,OAAO3B,IAAIoB,EAAE;AACtB"}
@@ -1,20 +1,20 @@
1
1
  import { z } from 'zod';
2
2
  import { errorMessage, jsonResponse, stampMcpContext, textResponse } from './_helpers';
3
3
  const SAMPLE_LIMIT = 5;
4
- /**
5
- * safeDelete wraps the official delete operation with a relationship-graph
6
- * pre-check.
7
- *
8
- * Workflow:
9
- * 1. Use the introspected relationshipGraph to find every (collection, field)
10
- * pair that points TO the target collection.
11
- * 2. For each, query for documents that reference the target ID. Aggregate
12
- * counts and a small sample of inbound document IDs.
13
- * 3. If any inbound references exist and `confirm` is false, refuse and
14
- * return the impact summary so the caller can decide.
15
- * 4. Otherwise, perform the delete.
16
- *
17
- * Excludes built-in Payload bookkeeping collections from the inbound search.
4
+ /**
5
+ * safeDelete wraps the official delete operation with a relationship-graph
6
+ * pre-check.
7
+ *
8
+ * Workflow:
9
+ * 1. Use the introspected relationshipGraph to find every (collection, field)
10
+ * pair that points TO the target collection.
11
+ * 2. For each, query for documents that reference the target ID. Aggregate
12
+ * counts and a small sample of inbound document IDs.
13
+ * 3. If any inbound references exist and `confirm` is false, refuse and
14
+ * return the impact summary so the caller can decide.
15
+ * 4. Otherwise, perform the delete.
16
+ *
17
+ * Excludes built-in Payload bookkeeping collections from the inbound search.
18
18
  */ export function createSafeDeleteTool(relationships) {
19
19
  // reverseIndex: target collection slug → edges that reference it
20
20
  const reverseIndex = new Map();