@payloadcms/plugin-mcp 4.0.0-internal.bc5cf8c → 4.0.0-internal.bf4bcce

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 (204) hide show
  1. package/LICENSE.md +1 -1
  2. package/dist/defaultAccess.d.ts.map +1 -1
  3. package/dist/defineTool.d.ts +8 -8
  4. package/dist/defineTool.d.ts.map +1 -1
  5. package/dist/endpoint/access.d.ts +2 -7
  6. package/dist/endpoint/access.d.ts.map +1 -1
  7. package/dist/endpoint/access.js +17 -14
  8. package/dist/endpoint/access.js.map +1 -1
  9. package/dist/endpoint/index.d.ts +1 -1
  10. package/dist/endpoint/index.d.ts.map +1 -1
  11. package/dist/endpoint/index.js +91 -16
  12. package/dist/endpoint/index.js.map +1 -1
  13. package/dist/endpoint/oauth.d.ts +27 -0
  14. package/dist/endpoint/oauth.d.ts.map +1 -0
  15. package/dist/endpoint/oauth.js +61 -0
  16. package/dist/endpoint/oauth.js.map +1 -0
  17. package/dist/index.d.ts +3 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +9 -2
  20. package/dist/index.js.map +1 -1
  21. package/dist/mcp/buildMcpServer.d.ts +6 -6
  22. package/dist/mcp/buildMcpServer.d.ts.map +1 -1
  23. package/dist/mcp/buildMcpServer.js +68 -34
  24. package/dist/mcp/buildMcpServer.js.map +1 -1
  25. package/dist/mcp/builtin/collections/authTools.d.ts.map +1 -1
  26. package/dist/mcp/builtin/collections/authTools.js +80 -56
  27. package/dist/mcp/builtin/collections/authTools.js.map +1 -1
  28. package/dist/mcp/builtin/collections/countTool.d.ts.map +1 -1
  29. package/dist/mcp/builtin/collections/countTool.js +10 -16
  30. package/dist/mcp/builtin/collections/countTool.js.map +1 -1
  31. package/dist/mcp/builtin/collections/countVersionsTool.d.ts.map +1 -1
  32. package/dist/mcp/builtin/collections/countVersionsTool.js +10 -15
  33. package/dist/mcp/builtin/collections/countVersionsTool.js.map +1 -1
  34. package/dist/mcp/builtin/collections/createTool.d.ts +1 -1
  35. package/dist/mcp/builtin/collections/createTool.d.ts.map +1 -1
  36. package/dist/mcp/builtin/collections/createTool.js +104 -54
  37. package/dist/mcp/builtin/collections/createTool.js.map +1 -1
  38. package/dist/mcp/builtin/collections/deleteTool.d.ts.map +1 -1
  39. package/dist/mcp/builtin/collections/deleteTool.js +45 -45
  40. package/dist/mcp/builtin/collections/deleteTool.js.map +1 -1
  41. package/dist/mcp/builtin/collections/duplicateTool.d.ts.map +1 -1
  42. package/dist/mcp/builtin/collections/duplicateTool.js +32 -39
  43. package/dist/mcp/builtin/collections/duplicateTool.js.map +1 -1
  44. package/dist/mcp/builtin/collections/fileInput.d.ts +28 -0
  45. package/dist/mcp/builtin/collections/fileInput.d.ts.map +1 -0
  46. package/dist/mcp/builtin/collections/fileInput.js +88 -0
  47. package/dist/mcp/builtin/collections/fileInput.js.map +1 -0
  48. package/dist/mcp/builtin/collections/findDistinctTool.d.ts.map +1 -1
  49. package/dist/mcp/builtin/collections/findDistinctTool.js +12 -28
  50. package/dist/mcp/builtin/collections/findDistinctTool.js.map +1 -1
  51. package/dist/mcp/builtin/collections/findTool.d.ts.map +1 -1
  52. package/dist/mcp/builtin/collections/findTool.js +29 -48
  53. package/dist/mcp/builtin/collections/findTool.js.map +1 -1
  54. package/dist/mcp/builtin/collections/findVersionByIDTool.d.ts.map +1 -1
  55. package/dist/mcp/builtin/collections/findVersionByIDTool.js +18 -28
  56. package/dist/mcp/builtin/collections/findVersionByIDTool.js.map +1 -1
  57. package/dist/mcp/builtin/collections/findVersionsTool.d.ts.map +1 -1
  58. package/dist/mcp/builtin/collections/findVersionsTool.js +17 -32
  59. package/dist/mcp/builtin/collections/findVersionsTool.js.map +1 -1
  60. package/dist/mcp/builtin/collections/getCollectionSchemaTool.d.ts.map +1 -1
  61. package/dist/mcp/builtin/collections/getCollectionSchemaTool.js +68 -12
  62. package/dist/mcp/builtin/collections/getCollectionSchemaTool.js.map +1 -1
  63. package/dist/mcp/builtin/collections/restoreVersionTool.d.ts.map +1 -1
  64. package/dist/mcp/builtin/collections/restoreVersionTool.js +15 -28
  65. package/dist/mcp/builtin/collections/restoreVersionTool.js.map +1 -1
  66. package/dist/mcp/builtin/collections/updateTool.d.ts.map +1 -1
  67. package/dist/mcp/builtin/collections/updateTool.js +67 -87
  68. package/dist/mcp/builtin/collections/updateTool.js.map +1 -1
  69. package/dist/mcp/builtin/collections/uploadInstructionsTool.d.ts +2 -0
  70. package/dist/mcp/builtin/collections/uploadInstructionsTool.d.ts.map +1 -0
  71. package/dist/mcp/builtin/collections/uploadInstructionsTool.js +55 -0
  72. package/dist/mcp/builtin/collections/uploadInstructionsTool.js.map +1 -0
  73. package/dist/mcp/builtin/formatEntityError.d.ts +16 -0
  74. package/dist/mcp/builtin/formatEntityError.d.ts.map +1 -0
  75. package/dist/mcp/builtin/formatEntityError.js +55 -0
  76. package/dist/mcp/builtin/formatEntityError.js.map +1 -0
  77. package/dist/mcp/builtin/getConfigInfoTool.d.ts.map +1 -1
  78. package/dist/mcp/builtin/getConfigInfoTool.js +5 -7
  79. package/dist/mcp/builtin/getConfigInfoTool.js.map +1 -1
  80. package/dist/mcp/builtin/globals/countVersionsTool.d.ts.map +1 -1
  81. package/dist/mcp/builtin/globals/countVersionsTool.js +10 -15
  82. package/dist/mcp/builtin/globals/countVersionsTool.js.map +1 -1
  83. package/dist/mcp/builtin/globals/findTool.d.ts.map +1 -1
  84. package/dist/mcp/builtin/globals/findTool.js +12 -18
  85. package/dist/mcp/builtin/globals/findTool.js.map +1 -1
  86. package/dist/mcp/builtin/globals/findVersionByIDTool.d.ts.map +1 -1
  87. package/dist/mcp/builtin/globals/findVersionByIDTool.js +14 -26
  88. package/dist/mcp/builtin/globals/findVersionByIDTool.js.map +1 -1
  89. package/dist/mcp/builtin/globals/findVersionsTool.d.ts.map +1 -1
  90. package/dist/mcp/builtin/globals/findVersionsTool.js +14 -31
  91. package/dist/mcp/builtin/globals/findVersionsTool.js.map +1 -1
  92. package/dist/mcp/builtin/globals/getGlobalSchemaTool.d.ts.map +1 -1
  93. package/dist/mcp/builtin/globals/getGlobalSchemaTool.js +46 -12
  94. package/dist/mcp/builtin/globals/getGlobalSchemaTool.js.map +1 -1
  95. package/dist/mcp/builtin/globals/restoreVersionTool.d.ts.map +1 -1
  96. package/dist/mcp/builtin/globals/restoreVersionTool.js +16 -24
  97. package/dist/mcp/builtin/globals/restoreVersionTool.js.map +1 -1
  98. package/dist/mcp/builtin/globals/updateTool.d.ts.map +1 -1
  99. package/dist/mcp/builtin/globals/updateTool.js +28 -37
  100. package/dist/mcp/builtin/globals/updateTool.js.map +1 -1
  101. package/dist/mcp/builtinTools.d.ts +6 -0
  102. package/dist/mcp/builtinTools.d.ts.map +1 -1
  103. package/dist/mcp/builtinTools.js +9 -3
  104. package/dist/mcp/builtinTools.js.map +1 -1
  105. package/dist/mcp/sanitizeMCPConfig.d.ts.map +1 -1
  106. package/dist/mcp/sanitizeMCPConfig.js +6 -1
  107. package/dist/mcp/sanitizeMCPConfig.js.map +1 -1
  108. package/dist/stdio.d.ts +3 -4
  109. package/dist/stdio.d.ts.map +1 -1
  110. package/dist/stdio.js +65 -45
  111. package/dist/stdio.js.map +1 -1
  112. package/dist/types.d.ts +94 -16
  113. package/dist/types.d.ts.map +1 -1
  114. package/dist/types.js +2 -4
  115. package/dist/types.js.map +1 -1
  116. package/dist/utils/camelCase.d.ts.map +1 -1
  117. package/dist/utils/resolveProjectRoot.d.ts.map +1 -1
  118. package/dist/utils/toStandardSchema.d.ts.map +1 -1
  119. package/package.json +9 -13
  120. package/src/endpoint/access.ts +14 -14
  121. package/src/endpoint/index.ts +79 -14
  122. package/src/endpoint/oauth.ts +72 -0
  123. package/src/index.ts +11 -4
  124. package/src/mcp/buildMcpServer.ts +80 -50
  125. package/src/mcp/builtin/collections/authTools.ts +80 -69
  126. package/src/mcp/builtin/collections/countTool.ts +10 -23
  127. package/src/mcp/builtin/collections/countVersionsTool.ts +10 -20
  128. package/src/mcp/builtin/collections/createTool.ts +110 -77
  129. package/src/mcp/builtin/collections/deleteTool.ts +36 -65
  130. package/src/mcp/builtin/collections/duplicateTool.ts +36 -87
  131. package/src/mcp/builtin/collections/fileInput.spec.ts +93 -0
  132. package/src/mcp/builtin/collections/fileInput.ts +127 -0
  133. package/src/mcp/builtin/collections/findDistinctTool.ts +12 -65
  134. package/src/mcp/builtin/collections/findTool.ts +25 -108
  135. package/src/mcp/builtin/collections/findVersionByIDTool.ts +16 -60
  136. package/src/mcp/builtin/collections/findVersionsTool.ts +15 -87
  137. package/src/mcp/builtin/collections/getCollectionSchemaTool.ts +60 -15
  138. package/src/mcp/builtin/collections/restoreVersionTool.ts +15 -58
  139. package/src/mcp/builtin/collections/updateTool.ts +63 -101
  140. package/src/mcp/builtin/collections/uploadInstructionsTool.ts +64 -0
  141. package/src/mcp/builtin/formatEntityError.ts +77 -0
  142. package/src/mcp/builtin/getConfigInfoTool.ts +4 -5
  143. package/src/mcp/builtin/globals/countVersionsTool.ts +10 -19
  144. package/src/mcp/builtin/globals/findTool.ts +14 -53
  145. package/src/mcp/builtin/globals/findVersionByIDTool.ts +14 -54
  146. package/src/mcp/builtin/globals/findVersionsTool.ts +15 -94
  147. package/src/mcp/builtin/globals/getGlobalSchemaTool.ts +41 -9
  148. package/src/mcp/builtin/globals/restoreVersionTool.ts +15 -47
  149. package/src/mcp/builtin/globals/updateTool.ts +39 -62
  150. package/src/mcp/builtinTools.ts +9 -2
  151. package/src/mcp/sanitizeMCPConfig.ts +6 -1
  152. package/src/stdio.ts +63 -45
  153. package/src/types.ts +95 -16
  154. package/dist/@types/assets.d.js +0 -2
  155. package/dist/@types/assets.d.js.map +0 -1
  156. package/dist/mcp/builtin/collections/formatCollectionError.d.ts +0 -9
  157. package/dist/mcp/builtin/collections/formatCollectionError.d.ts.map +0 -1
  158. package/dist/mcp/builtin/collections/formatCollectionError.js +0 -60
  159. package/dist/mcp/builtin/collections/formatCollectionError.js.map +0 -1
  160. package/dist/mcp/builtin/validateEntityData.d.ts +0 -14
  161. package/dist/mcp/builtin/validateEntityData.d.ts.map +0 -1
  162. package/dist/mcp/builtin/validateEntityData.js +0 -82
  163. package/dist/mcp/builtin/validateEntityData.js.map +0 -1
  164. package/dist/utils/getVirtualFieldNames.d.ts +0 -14
  165. package/dist/utils/getVirtualFieldNames.d.ts.map +0 -1
  166. package/dist/utils/getVirtualFieldNames.js +0 -35
  167. package/dist/utils/getVirtualFieldNames.js.map +0 -1
  168. package/dist/utils/localAPIDefaults.d.ts +0 -20
  169. package/dist/utils/localAPIDefaults.d.ts.map +0 -1
  170. package/dist/utils/localAPIDefaults.js +0 -19
  171. package/dist/utils/localAPIDefaults.js.map +0 -1
  172. package/dist/utils/schemaConversion/getEntityInputSchema.d.ts +0 -11
  173. package/dist/utils/schemaConversion/getEntityInputSchema.d.ts.map +0 -1
  174. package/dist/utils/schemaConversion/getEntityInputSchema.js +0 -34
  175. package/dist/utils/schemaConversion/getEntityInputSchema.js.map +0 -1
  176. package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.d.ts +0 -7
  177. package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.d.ts.map +0 -1
  178. package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.js +0 -20
  179. package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.js.map +0 -1
  180. package/dist/utils/schemaConversion/sanitizeEntitySchema.d.ts +0 -15
  181. package/dist/utils/schemaConversion/sanitizeEntitySchema.d.ts.map +0 -1
  182. package/dist/utils/schemaConversion/sanitizeEntitySchema.js +0 -464
  183. package/dist/utils/schemaConversion/sanitizeEntitySchema.js.map +0 -1
  184. package/dist/utils/schemaConversion/sanitizeEntitySchema.spec.js +0 -158
  185. package/dist/utils/schemaConversion/sanitizeEntitySchema.spec.js.map +0 -1
  186. package/dist/utils/transformPointDataToPayload.d.ts +0 -7
  187. package/dist/utils/transformPointDataToPayload.d.ts.map +0 -1
  188. package/dist/utils/transformPointDataToPayload.js +0 -28
  189. package/dist/utils/transformPointDataToPayload.js.map +0 -1
  190. package/dist/utils/whereSchema.d.ts +0 -9
  191. package/dist/utils/whereSchema.d.ts.map +0 -1
  192. package/dist/utils/whereSchema.js +0 -13
  193. package/dist/utils/whereSchema.js.map +0 -1
  194. package/src/@types/assets.d.ts +0 -3
  195. package/src/mcp/builtin/collections/formatCollectionError.ts +0 -84
  196. package/src/mcp/builtin/validateEntityData.ts +0 -132
  197. package/src/utils/getVirtualFieldNames.ts +0 -53
  198. package/src/utils/localAPIDefaults.ts +0 -22
  199. package/src/utils/schemaConversion/getEntityInputSchema.ts +0 -78
  200. package/src/utils/schemaConversion/removeVirtualFieldsFromSchema.ts +0 -27
  201. package/src/utils/schemaConversion/sanitizeEntitySchema.spec.ts +0 -103
  202. package/src/utils/schemaConversion/sanitizeEntitySchema.ts +0 -529
  203. package/src/utils/transformPointDataToPayload.ts +0 -40
  204. package/src/utils/whereSchema.ts +0 -24
@@ -1,12 +1,14 @@
1
+ import { getAccessResults, getGlobalInputSchema, getGlobalSchemaInputSchema } from 'payload'
2
+ import { getLLMInstructions } from 'payload/internal'
3
+
1
4
  import { defaultAccess } from '../../../defaultAccess.js'
2
5
  import { defineGlobalTool } from '../../../defineTool.js'
3
- import { getGlobalInputSchema } from '../../../utils/schemaConversion/getEntityInputSchema.js'
4
6
 
5
7
  export const getGlobalSchemaTool = defineGlobalTool({
6
8
  access: (args) => {
7
- const permissions = args.permissions?.globals?.[args.globalSlug]
9
+ const permissions = args.permissions?.globals?.[args.slug]
8
10
 
9
- return defaultAccess(args) && Boolean(permissions?.read || permissions?.update)
11
+ return defaultAccess(args) && Boolean(permissions?.update)
10
12
  },
11
13
  annotations: {
12
14
  destructiveHint: false,
@@ -15,27 +17,57 @@ export const getGlobalSchemaTool = defineGlobalTool({
15
17
  readOnlyHint: true,
16
18
  title: 'Get Global Schema',
17
19
  },
18
- description: 'Get the input schema for updating a global.',
19
- }).handler(({ globalSlug, req }) => {
20
- const inputSchema = getGlobalInputSchema({ globalSlug, req })
20
+ description: 'Get the input schema and LLM instructions before updating a global.',
21
+ input: getGlobalSchemaInputSchema,
22
+ }).handler(async ({ slug, authorizedMCP, req }) => {
23
+ const permissions = authorizedMCP.overrideAccess
24
+ ? null
25
+ : (await getAccessResults({ req })).globals?.[slug]
26
+
27
+ if (!authorizedMCP.overrideAccess && !permissions?.update) {
28
+ return {
29
+ content: [
30
+ {
31
+ type: 'text',
32
+ text: `Error: MCP access to "getGlobalSchema" is not enabled for global "${slug}"`,
33
+ },
34
+ ],
35
+ isError: true,
36
+ }
37
+ }
38
+
39
+ const inputSchema = getGlobalInputSchema({
40
+ globalSlug: slug,
41
+ req,
42
+ ...(permissions ? { permissions } : {}),
43
+ })
21
44
 
22
45
  if (!inputSchema) {
23
46
  return {
24
- content: [{ type: 'text', text: `Error: Global "${globalSlug}" not found` }],
47
+ content: [{ type: 'text', text: `Error: Global "${slug}" not found` }],
25
48
  isError: true,
26
49
  }
27
50
  }
28
51
 
52
+ const instructions = await getLLMInstructions({
53
+ slug,
54
+ type: 'global',
55
+ overrideAccess: authorizedMCP.overrideAccess,
56
+ req,
57
+ })
58
+
29
59
  return {
30
60
  content: [
31
61
  {
32
62
  type: 'text',
33
- text: `Schema for global "${globalSlug}":\n\`\`\`json\n${JSON.stringify(inputSchema)}\n\`\`\``,
63
+ text: `Schema for global "${slug}":\n\`\`\`json\n${JSON.stringify(inputSchema)}\n\`\`\``,
34
64
  },
65
+ ...(instructions ? [{ type: 'text' as const, text: instructions }] : []),
35
66
  ],
36
67
  structuredContent: {
37
- globalSlug,
68
+ slug,
38
69
  schema: inputSchema,
70
+ ...(instructions ? { instructions } : {}),
39
71
  },
40
72
  }
41
73
  })
@@ -1,18 +1,14 @@
1
- import type { PopulateType } from 'payload'
2
-
3
- import { z } from 'zod'
1
+ import { restoreGlobalVersionInputSchema } from 'payload'
4
2
 
5
3
  import { defaultAccess } from '../../../defaultAccess.js'
6
4
  import { defineGlobalTool } from '../../../defineTool.js'
7
5
  import { getLogger } from '../../../utils/getLogger.js'
8
- import { localAPIDefaults } from '../../../utils/localAPIDefaults.js'
9
6
 
10
7
  const DEFAULT_DESCRIPTION =
11
8
  'Restore a global from a previous version in any version-enabled global.'
12
9
 
13
10
  export const restoreGlobalVersionTool = defineGlobalTool({
14
- access: (args) =>
15
- defaultAccess(args) && Boolean(args.permissions?.globals?.[args.globalSlug]?.update),
11
+ access: (args) => defaultAccess(args) && Boolean(args.permissions?.globals?.[args.slug]?.update),
16
12
  annotations: {
17
13
  destructiveHint: true,
18
14
  idempotentHint: false,
@@ -21,53 +17,25 @@ export const restoreGlobalVersionTool = defineGlobalTool({
21
17
  title: 'Restore Global Version',
22
18
  },
23
19
  description: DEFAULT_DESCRIPTION,
24
- input: z.object({
25
- id: z.string().describe('The ID of the global version to restore'),
26
- depth: z
27
- .number()
28
- .int()
29
- .min(0)
30
- .max(10)
31
- .describe('How many levels deep to populate relationships in response')
32
- .optional()
33
- .default(0),
34
- fallbackLocale: z
35
- .string()
36
- .describe('Optional: fallback locale code to use when requested locale is not available')
37
- .optional(),
38
- locale: z
39
- .string()
40
- .describe('Optional: locale code to restore in (e.g., "en", "es")')
41
- .optional(),
42
- populate: z
43
- .record(z.string(), z.unknown())
44
- .describe(
45
- 'Optional: control which fields to include from populated relationship or upload documents.',
46
- )
47
- .optional(),
48
- showHiddenFields: z
49
- .boolean()
50
- .describe('Optional: include hidden fields in the restored global response')
51
- .optional(),
52
- }),
53
- }).handler(async ({ authorizedMCP, globalSlug, input, req }) => {
20
+ input: restoreGlobalVersionInputSchema,
21
+ }).handler(async ({ slug, authorizedMCP, input, req }) => {
54
22
  const payload = req.payload
55
23
  const logger = getLogger({ payload })
56
- const { id, depth, fallbackLocale, locale, populate, showHiddenFields } = input
24
+ const { id, depth, fallbackLocale, locale, populate, select } = input
57
25
 
58
- logger.info(`Restoring version for global: ${globalSlug} with ID: ${id}`)
26
+ logger.info(`Restoring version for global: ${slug} with ID: ${id}`)
59
27
 
60
28
  try {
61
29
  const result = await payload.restoreGlobalVersion({
62
- id,
63
- slug: globalSlug,
30
+ id: String(id),
31
+ slug,
64
32
  depth,
33
+ overrideAccess: authorizedMCP.overrideAccess,
65
34
  req,
66
- ...localAPIDefaults(authorizedMCP),
67
- ...(fallbackLocale ? { fallbackLocale } : {}),
35
+ ...(fallbackLocale !== undefined ? { fallbackLocale } : {}),
68
36
  ...(locale ? { locale } : {}),
69
- ...(populate ? { populate: populate as PopulateType } : {}),
70
- ...(showHiddenFields !== undefined ? { showHiddenFields } : {}),
37
+ ...(populate ? { populate } : {}),
38
+ ...(select ? { select } : {}),
71
39
  })
72
40
  const resultObject = result as Record<string, unknown>
73
41
  const restoredGlobal =
@@ -79,19 +47,19 @@ export const restoreGlobalVersionTool = defineGlobalTool({
79
47
  content: [
80
48
  {
81
49
  type: 'text',
82
- text: `Version "${id}" restored successfully for global "${globalSlug}"!\nRestored global:\n\`\`\`json\n${JSON.stringify(restoredGlobal)}\n\`\`\``,
50
+ text: `Version "${id}" restored successfully for global "${slug}"!\nRestored global:\n\`\`\`json\n${JSON.stringify(restoredGlobal)}\n\`\`\``,
83
51
  },
84
52
  ],
85
53
  doc: restoredGlobal,
86
54
  }
87
55
  } catch (error) {
88
56
  const errorMessage = error instanceof Error ? error.message : 'Unknown error'
89
- logger.error(`Error restoring version ${id} for global ${globalSlug}: ${errorMessage}`)
57
+ logger.error(`Error restoring version ${id} for global ${slug}: ${errorMessage}`)
90
58
  return {
91
59
  content: [
92
60
  {
93
61
  type: 'text',
94
- text: `❌ **Error restoring version "${id}" for global "${globalSlug}":** ${errorMessage}`,
62
+ text: `❌ **Error restoring version "${id}" for global "${slug}":** ${errorMessage}`,
95
63
  },
96
64
  ],
97
65
  isError: true,
@@ -1,23 +1,20 @@
1
- import type { SelectType } from 'payload'
2
-
3
- import { z } from 'zod'
1
+ import {
2
+ getGlobalVirtualFieldNames,
3
+ stripVirtualFields,
4
+ transformPointDataToPayload,
5
+ updateGlobalInputSchema,
6
+ validateGlobalData,
7
+ } from 'payload'
4
8
 
5
9
  import { defaultAccess } from '../../../defaultAccess.js'
6
10
  import { defineGlobalTool } from '../../../defineTool.js'
7
11
  import { getLogger } from '../../../utils/getLogger.js'
8
- import {
9
- getGlobalVirtualFieldNames,
10
- stripVirtualFields,
11
- } from '../../../utils/getVirtualFieldNames.js'
12
- import { localAPIDefaults } from '../../../utils/localAPIDefaults.js'
13
- import { transformPointDataToPayload } from '../../../utils/transformPointDataToPayload.js'
14
- import { validateGlobalData } from '../validateEntityData.js'
12
+ import { formatEntityError } from '../formatEntityError.js'
15
13
 
16
14
  const DEFAULT_DESCRIPTION = 'Update any Payload global by passing the global slug and data.'
17
15
 
18
16
  export const updateGlobalTool = defineGlobalTool({
19
- access: (args) =>
20
- defaultAccess(args) && Boolean(args.permissions?.globals?.[args.globalSlug]?.update),
17
+ access: (args) => defaultAccess(args) && Boolean(args.permissions?.globals?.[args.slug]?.update),
21
18
  annotations: {
22
19
  destructiveHint: true,
23
20
  idempotentHint: false,
@@ -26,72 +23,54 @@ export const updateGlobalTool = defineGlobalTool({
26
23
  title: 'Update Global',
27
24
  },
28
25
  description: DEFAULT_DESCRIPTION,
29
- input: z.object({
30
- data: z.record(z.string(), z.unknown()).describe('The global fields to update'),
31
- depth: z
32
- .number()
33
- .describe('Optional: Depth of relationships to populate')
34
- .optional()
35
- .default(0),
36
- draft: z
37
- .boolean()
38
- .describe('Optional: Whether to save as draft (default: false)')
39
- .optional()
40
- .default(false),
41
- fallbackLocale: z
42
- .string()
43
- .describe('Optional: fallback locale code to use when requested locale is not available')
44
- .optional(),
45
- locale: z
46
- .string()
47
- .describe(
48
- 'Optional: locale code to update data in (e.g., "en", "es"). Use "all" to update all locales for localized fields',
49
- )
50
- .optional(),
51
- select: z
52
- .record(z.string(), z.unknown())
53
- .describe(
54
- 'Optional: define exactly which fields you\'d like to return in the response, e.g., {"siteName": true}',
55
- )
56
- .optional(),
57
- }),
58
- }).handler(async ({ authorizedMCP, globalSlug, input, req }) => {
26
+ input: updateGlobalInputSchema,
27
+ }).handler(async ({ slug, authorizedMCP, input, req }) => {
59
28
  const payload = req.payload
60
29
  const logger = getLogger({ payload })
61
30
 
62
- const { data, depth, draft, fallbackLocale, locale, select } = input
31
+ const {
32
+ data,
33
+ depth,
34
+ draft,
35
+ fallbackLocale,
36
+ locale,
37
+ overrideLock,
38
+ populate,
39
+ publishAllLocales,
40
+ select,
41
+ unpublishAllLocales,
42
+ } = input
63
43
 
64
- logger.info(
65
- `Updating global: ${globalSlug}, draft: ${draft}${locale ? `, locale: ${locale}` : ''}`,
66
- )
44
+ logger.info(`Updating global: ${slug}, draft: ${draft}${locale ? `, locale: ${locale}` : ''}`)
67
45
 
68
46
  try {
69
- const virtualFieldNames = getGlobalVirtualFieldNames(payload.config, globalSlug)
47
+ const virtualFieldNames = getGlobalVirtualFieldNames(payload.config, slug)
70
48
  const inputData = stripVirtualFields(data, virtualFieldNames)
71
- const validationError = validateGlobalData({ data: inputData, globalSlug, req })
72
-
73
- if (validationError) {
74
- return validationError
75
- }
49
+ validateGlobalData({ slug, data: inputData, req })
76
50
 
77
51
  const parsedData = transformPointDataToPayload(inputData)
78
52
 
79
53
  const updateOptions: Parameters<typeof payload.updateGlobal>[0] = {
80
- slug: globalSlug,
54
+ slug,
81
55
  data: parsedData,
82
56
  depth,
83
57
  draft,
84
- ...localAPIDefaults(authorizedMCP),
58
+ overrideAccess: authorizedMCP.overrideAccess,
59
+ overrideLock,
60
+ populate,
61
+ publishAllLocales,
62
+ req,
63
+ unpublishAllLocales,
85
64
  }
86
65
 
87
66
  if (locale) {
88
67
  updateOptions.locale = locale
89
68
  }
90
- if (fallbackLocale) {
69
+ if (fallbackLocale !== undefined) {
91
70
  updateOptions.fallbackLocale = fallbackLocale
92
71
  }
93
72
  if (select) {
94
- updateOptions.select = select as SelectType
73
+ updateOptions.select = select
95
74
  }
96
75
 
97
76
  const result = await payload.updateGlobal(updateOptions)
@@ -100,16 +79,14 @@ export const updateGlobalTool = defineGlobalTool({
100
79
  content: [
101
80
  {
102
81
  type: 'text',
103
- text: `Global "${globalSlug}" updated successfully!\n\`\`\`json\n${JSON.stringify(result)}\n\`\`\``,
82
+ text: `Global "${slug}" updated successfully!\n\`\`\`json\n${JSON.stringify(result)}\n\`\`\``,
104
83
  },
105
84
  ],
106
85
  doc: result as Record<string, unknown>,
107
86
  }
108
87
  } catch (error) {
109
- const errorMessage = error instanceof Error ? error.message : 'Unknown error'
110
- logger.error(`Error updating global ${globalSlug}: ${errorMessage}`)
111
- return {
112
- content: [{ type: 'text', text: `Error updating global "${globalSlug}": ${errorMessage}` }],
113
- }
88
+ const message = error instanceof Error ? error.message : 'Unknown error'
89
+ logger.error(`Error updating global ${slug}: ${message}`)
90
+ return formatEntityError({ slug, action: 'updating', entity: 'global', error, req })
114
91
  }
115
92
  })
@@ -10,7 +10,7 @@ import {
10
10
  } from './builtin/collections/authTools.js'
11
11
  import { countDocumentsTool } from './builtin/collections/countTool.js'
12
12
  import { countVersionsTool } from './builtin/collections/countVersionsTool.js'
13
- import { createDocumentTool } from './builtin/collections/createTool.js'
13
+ import { createDocumentsTool } from './builtin/collections/createTool.js'
14
14
  import { deleteDocumentsTool } from './builtin/collections/deleteTool.js'
15
15
  import { duplicateDocumentTool } from './builtin/collections/duplicateTool.js'
16
16
  import { findDistinctTool } from './builtin/collections/findDistinctTool.js'
@@ -20,6 +20,7 @@ import { findVersionsTool } from './builtin/collections/findVersionsTool.js'
20
20
  import { getCollectionSchemaTool } from './builtin/collections/getCollectionSchemaTool.js'
21
21
  import { restoreVersionTool } from './builtin/collections/restoreVersionTool.js'
22
22
  import { updateDocumentTool } from './builtin/collections/updateTool.js'
23
+ import { getUploadInstructionsTool } from './builtin/collections/uploadInstructionsTool.js'
23
24
  import { getConfigInfoTool } from './builtin/getConfigInfoTool.js'
24
25
  import { countGlobalVersionsTool } from './builtin/globals/countVersionsTool.js'
25
26
  import { findGlobalTool } from './builtin/globals/findTool.js'
@@ -32,6 +33,7 @@ import { updateGlobalTool } from './builtin/globals/updateTool.js'
32
33
  type CollectionBuiltin = {
33
34
  mcpName: string
34
35
  requiresDuplicateEnabled?: boolean
36
+ requiresUpload?: boolean
35
37
  requiresVersions?: boolean
36
38
  tool: CollectionTool
37
39
  }
@@ -54,7 +56,7 @@ export const TOOL_BUILTINS = {
54
56
  export const COLLECTION_BUILTINS = {
55
57
  count: { mcpName: 'countDocuments', tool: countDocumentsTool },
56
58
  countVersions: { mcpName: 'countVersions', requiresVersions: true, tool: countVersionsTool },
57
- create: { mcpName: 'createDocument', tool: createDocumentTool },
59
+ create: { mcpName: 'createDocuments', tool: createDocumentsTool },
58
60
  delete: { mcpName: 'deleteDocuments', tool: deleteDocumentsTool },
59
61
  duplicate: {
60
62
  mcpName: 'duplicateDocument',
@@ -70,6 +72,11 @@ export const COLLECTION_BUILTINS = {
70
72
  },
71
73
  findVersions: { mcpName: 'findVersions', requiresVersions: true, tool: findVersionsTool },
72
74
  getCollectionSchema: { mcpName: 'getCollectionSchema', tool: getCollectionSchemaTool },
75
+ getUploadInstructions: {
76
+ mcpName: 'getUploadInstructions',
77
+ requiresUpload: true,
78
+ tool: getUploadInstructionsTool,
79
+ },
73
80
  restoreVersion: { mcpName: 'restoreVersion', requiresVersions: true, tool: restoreVersionTool },
74
81
  update: { mcpName: 'updateDocument', tool: updateDocumentTool },
75
82
  } satisfies Record<string, CollectionBuiltin>
@@ -100,8 +100,10 @@ export const sanitizeMCPConfig = ({
100
100
 
101
101
  return {
102
102
  disabled: pluginConfig.disabled,
103
+ hooks: pluginConfig.hooks,
103
104
  items,
104
105
  mcp: pluginConfig.mcp,
106
+ oauth: pluginConfig.oauth,
105
107
  overrideGetAuthorizedMCP: pluginConfig.overrideGetAuthorizedMCP,
106
108
  }
107
109
  }
@@ -126,7 +128,7 @@ const sanitizeCollectionConfig = ({
126
128
 
127
129
  for (const [
128
130
  toolKey,
129
- { mcpName, requiresDuplicateEnabled, requiresVersions, tool },
131
+ { mcpName, requiresDuplicateEnabled, requiresUpload, requiresVersions, tool },
130
132
  ] of COLLECTION_BUILTIN_ENTRIES) {
131
133
  if (requiresVersions && !collection.versions) {
132
134
  continue
@@ -134,6 +136,9 @@ const sanitizeCollectionConfig = ({
134
136
  if (requiresDuplicateEnabled && isDuplicateDisabled) {
135
137
  continue
136
138
  }
139
+ if (requiresUpload && !collection.upload) {
140
+ continue
141
+ }
137
142
  const matchedConfigEntry = collectionPluginConfig?.tools?.[toolKey]
138
143
  if (matchedConfigEntry === false) {
139
144
  continue
package/src/stdio.ts CHANGED
@@ -1,10 +1,10 @@
1
- import type { MaybePromise, Plugin, SanitizedConfig } from 'payload'
1
+ import type { Config, Plugin, SanitizedConfig } from 'payload'
2
2
 
3
3
  /* eslint-disable no-console */
4
- import { StdioServerTransport } from '@modelcontextprotocol/server'
4
+ import { serveStdio } from '@modelcontextprotocol/server/stdio'
5
5
  import { fileURLToPath, pathToFileURL } from 'node:url'
6
- import { createLocalReq, getPayload } from 'payload'
7
- import { findConfig } from 'payload/node'
6
+ import { createPayloadRequest, getPayload } from 'payload'
7
+ import { findConfig, loadEnv } from 'payload/node'
8
8
 
9
9
  import type { SanitizedMCPPluginConfig } from './types.js'
10
10
 
@@ -15,53 +15,62 @@ import { getPluginConfig } from './utils/getPluginConfig.js'
15
15
  import { resolveProjectRoot } from './utils/resolveProjectRoot.js'
16
16
 
17
17
  /**
18
- * Stdio adapter for the Payload MCP server.
18
+ * Starts Payload's MCP server over stdin and stdout.
19
19
  *
20
- * Pass PAYLOAD_MCP_AUTHORIZATION when stdio should authenticate with the same
21
- * Payload auth header as HTTP. In development, PAYLOAD_MCP_OVERRIDE_ACCESS=true
22
- * skips access control for local setup.
20
+ * Set `PAYLOAD_MCP_AUTHORIZATION` to authenticate. In development,
21
+ * `PAYLOAD_MCP_OVERRIDE_ACCESS=true` skips access checks.
23
22
  */
24
23
  export const runMcpStdio = async (): Promise<void> => {
25
- /**
26
- * If MCP clients spawn stdio servers from an arbitrary working directory,
27
- * Payload's cwd-anchored `findConfig()` can't locate the project on
28
- * its own. This module always lives inside the project's `node_modules`, so
29
- * we derive the project root from its own path and `chdir` into it.
30
- *
31
- * An absolute `PAYLOAD_CONFIG_PATH` still overrides everything; unusual
32
- * layouts (e.g. a monorepo where the package is hoisted above the app) can
33
- * fall back to it. See the implementation of findConfig for details.
34
- */
24
+ // MCP clients may start this command from another folder. Move to the Payload
25
+ // project so findConfig() can find the config.
35
26
  const projectRoot = resolveProjectRoot(fileURLToPath(import.meta.url))
36
27
  if (projectRoot) {
37
28
  process.chdir(projectRoot)
38
29
  }
39
30
 
31
+ loadEnv()
40
32
  const configPath = findConfig()
41
33
  const configModule = await import(pathToFileURL(configPath).toString())
42
- const config = (await (configModule.default ?? configModule)) as MaybePromise<SanitizedConfig>
43
-
44
- const payload = await getPayload({ config })
34
+ const config = (await (configModule.default ?? configModule)) as SanitizedConfig
45
35
 
46
36
  /**
47
- * If the user added `mcpPlugin({...})` to their `plugins` array, read the
48
- * sanitized config they registered. Otherwise fall back to the defaults. That way,
49
- * the mcp works on any project that has `@payloadcms/plugin-mcp` installed,
50
- * even if the plugin is not added to the plugins array.
37
+ * stdout is only for MCP messages. The spec says the server must not write
38
+ * logs there and may write them to stderr instead.
39
+ *
40
+ * The 2.0 client library recovers from stray stdout logs, but that behavior is not
41
+ * guaranteed. Move Payload logs to stderr so Payload does not add invalid data
42
+ * to stdout. Keep options from configurable loggers. Replace pre-built loggers
43
+ * because their output cannot be redirected.
44
+ *
45
+ * @see https://modelcontextprotocol.io/specification/2025-11-25/basic/transports#stdio
51
46
  */
47
+ const loggerConfig: { logger?: Config['logger'] } = config
48
+ if (
49
+ loggerConfig.logger &&
50
+ typeof loggerConfig.logger === 'object' &&
51
+ 'options' in loggerConfig.logger
52
+ ) {
53
+ loggerConfig.logger.destination = process.stderr
54
+ } else {
55
+ loggerConfig.logger = { destination: process.stderr, options: {} }
56
+ }
57
+
58
+ const payload = await getPayload({ config, cron: false, disableOnInit: true })
59
+
52
60
  let pluginConfig: SanitizedMCPPluginConfig
53
61
  try {
54
62
  pluginConfig = getPluginConfig({ config: payload.config })
55
63
  } catch {
64
+ // The command also works when mcpPlugin() is not in the Payload config.
56
65
  pluginConfig = sanitizeMCPConfig({ config: payload.config, pluginConfig: {} })
57
66
 
58
- const fakePluginFn: Plugin = (config) => config
59
- fakePluginFn.slug = '@payloadcms/plugin-mcp'
60
- // @ts-expect-error
61
- fakePluginFn.sanitizedOptions = pluginConfig
62
-
63
- // Push to payload config, to ensure consecutive calls to `getPluginConfig()` work
64
- ;(payload.config.plugins ??= []).push(fakePluginFn)
67
+ const fallbackPlugin: Plugin = (config) => config
68
+ Object.assign(fallbackPlugin, {
69
+ slug: '@payloadcms/plugin-mcp',
70
+ sanitizedOptions: pluginConfig,
71
+ })
72
+ payload.config.plugins ??= []
73
+ payload.config.plugins.push(fallbackPlugin)
65
74
  }
66
75
 
67
76
  const overrideAccessEnv = process.env.PAYLOAD_MCP_OVERRIDE_ACCESS
@@ -82,32 +91,41 @@ export const runMcpStdio = async (): Promise<void> => {
82
91
  headers.set('Authorization', process.env.PAYLOAD_MCP_AUTHORIZATION)
83
92
  }
84
93
 
85
- const req = await createLocalReq({ req: { headers } }, payload)
94
+ const req = await createPayloadRequest({ payload, req: { headers } })
86
95
  req.payloadAPI = 'MCP' as const
87
96
  const authorizedMCP = await getAuthorizedMCP({ overrideAccess, req })
88
97
 
89
- const server = buildMcpServer({ authorizedMCP, pluginConfig, req })
90
-
91
- const transport = new StdioServerTransport()
92
- await server.connect(transport)
98
+ const stdioServer = serveStdio(() => buildMcpServer({ authorizedMCP, pluginConfig, req }), {
99
+ onerror: (err) => {
100
+ // MCP messages use stdout, so write SDK errors to stderr.
101
+ console.error('[payload-mcp] error serving MCP over stdio:', err)
102
+ },
103
+ })
104
+
105
+ // Close the MCP server and Payload when the client disconnects or the process stops.
106
+ let isShuttingDown = false
107
+ const shutdown = async () => {
108
+ if (isShuttingDown) {
109
+ return
110
+ }
111
+ isShuttingDown = true
93
112
 
94
- // Process now lives until stdin closes. Wire graceful shutdown so DB
95
- // connections (and any other Payload-managed resources) get released.
96
- const shutdown = async (code = 0) => {
97
113
  try {
98
- await server.close()
114
+ await stdioServer.close()
99
115
  } catch (err) {
100
116
  console.error('[payload-mcp] error closing server:', err)
101
117
  }
118
+
102
119
  try {
103
120
  await payload.destroy()
104
121
  } catch (err) {
105
122
  console.error('[payload-mcp] error destroying payload:', err)
106
123
  }
107
- process.exit(code)
124
+
125
+ process.exit(0)
108
126
  }
109
127
 
110
- process.on('SIGINT', () => void shutdown(0))
111
- process.on('SIGTERM', () => void shutdown(0))
112
- process.stdin.on('close', () => void shutdown(0))
128
+ process.once('SIGINT', () => void shutdown())
129
+ process.once('SIGTERM', () => void shutdown())
130
+ process.stdin.once('close', () => void shutdown())
113
131
  }