@payloadcms/plugin-mcp 4.0.0-internal.c2b57ce → 4.0.0-internal.c6494b5

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 (241) hide show
  1. package/LICENSE.md +1 -1
  2. package/dist/defaultAccess.d.ts +3 -0
  3. package/dist/defaultAccess.d.ts.map +1 -0
  4. package/dist/defaultAccess.js +3 -0
  5. package/dist/defaultAccess.js.map +1 -0
  6. package/dist/defineTool.d.ts +8 -8
  7. package/dist/defineTool.d.ts.map +1 -1
  8. package/dist/endpoint/access.d.ts +12 -5
  9. package/dist/endpoint/access.d.ts.map +1 -1
  10. package/dist/endpoint/access.js +75 -88
  11. package/dist/endpoint/access.js.map +1 -1
  12. package/dist/endpoint/index.d.ts +1 -1
  13. package/dist/endpoint/index.d.ts.map +1 -1
  14. package/dist/endpoint/index.js +98 -18
  15. package/dist/endpoint/index.js.map +1 -1
  16. package/dist/exports/client.d.ts +1 -1
  17. package/dist/exports/client.d.ts.map +1 -1
  18. package/dist/exports/client.js +1 -1
  19. package/dist/exports/client.js.map +1 -1
  20. package/dist/exports/internal.d.ts +2 -0
  21. package/dist/exports/internal.d.ts.map +1 -0
  22. package/dist/exports/internal.js +3 -0
  23. package/dist/exports/internal.js.map +1 -0
  24. package/dist/index.d.ts +2 -2
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +15 -20
  27. package/dist/index.js.map +1 -1
  28. package/dist/mcp/buildMcpServer.d.ts +6 -6
  29. package/dist/mcp/buildMcpServer.d.ts.map +1 -1
  30. package/dist/mcp/buildMcpServer.js +146 -74
  31. package/dist/mcp/buildMcpServer.js.map +1 -1
  32. package/dist/mcp/builtin/collections/authTools.d.ts.map +1 -1
  33. package/dist/mcp/builtin/collections/authTools.js +123 -61
  34. package/dist/mcp/builtin/collections/authTools.js.map +1 -1
  35. package/dist/mcp/builtin/collections/countTool.d.ts +2 -0
  36. package/dist/mcp/builtin/collections/countTool.d.ts.map +1 -0
  37. package/dist/mcp/builtin/collections/countTool.js +63 -0
  38. package/dist/mcp/builtin/collections/countTool.js.map +1 -0
  39. package/dist/mcp/builtin/collections/countVersionsTool.d.ts +2 -0
  40. package/dist/mcp/builtin/collections/countVersionsTool.d.ts.map +1 -0
  41. package/dist/mcp/builtin/collections/countVersionsTool.js +60 -0
  42. package/dist/mcp/builtin/collections/countVersionsTool.js.map +1 -0
  43. package/dist/mcp/builtin/collections/createTool.d.ts +1 -1
  44. package/dist/mcp/builtin/collections/createTool.d.ts.map +1 -1
  45. package/dist/mcp/builtin/collections/createTool.js +114 -64
  46. package/dist/mcp/builtin/collections/createTool.js.map +1 -1
  47. package/dist/mcp/builtin/collections/deleteTool.d.ts +1 -1
  48. package/dist/mcp/builtin/collections/deleteTool.d.ts.map +1 -1
  49. package/dist/mcp/builtin/collections/deleteTool.js +55 -61
  50. package/dist/mcp/builtin/collections/deleteTool.js.map +1 -1
  51. package/dist/mcp/builtin/collections/duplicateTool.d.ts +2 -0
  52. package/dist/mcp/builtin/collections/duplicateTool.d.ts.map +1 -0
  53. package/dist/mcp/builtin/collections/duplicateTool.js +90 -0
  54. package/dist/mcp/builtin/collections/duplicateTool.js.map +1 -0
  55. package/dist/mcp/builtin/collections/fileInput.d.ts +28 -0
  56. package/dist/mcp/builtin/collections/fileInput.d.ts.map +1 -0
  57. package/dist/mcp/builtin/collections/fileInput.js +88 -0
  58. package/dist/mcp/builtin/collections/fileInput.js.map +1 -0
  59. package/dist/mcp/builtin/collections/findDistinctTool.d.ts +2 -0
  60. package/dist/mcp/builtin/collections/findDistinctTool.d.ts.map +1 -0
  61. package/dist/mcp/builtin/collections/findDistinctTool.js +77 -0
  62. package/dist/mcp/builtin/collections/findDistinctTool.js.map +1 -0
  63. package/dist/mcp/builtin/collections/findTool.d.ts +1 -1
  64. package/dist/mcp/builtin/collections/findTool.d.ts.map +1 -1
  65. package/dist/mcp/builtin/collections/findTool.js +61 -74
  66. package/dist/mcp/builtin/collections/findTool.js.map +1 -1
  67. package/dist/mcp/builtin/collections/findVersionByIDTool.d.ts +2 -0
  68. package/dist/mcp/builtin/collections/findVersionByIDTool.d.ts.map +1 -0
  69. package/dist/mcp/builtin/collections/findVersionByIDTool.js +74 -0
  70. package/dist/mcp/builtin/collections/findVersionByIDTool.js.map +1 -0
  71. package/dist/mcp/builtin/collections/findVersionsTool.d.ts +2 -0
  72. package/dist/mcp/builtin/collections/findVersionsTool.d.ts.map +1 -0
  73. package/dist/mcp/builtin/collections/findVersionsTool.js +84 -0
  74. package/dist/mcp/builtin/collections/findVersionsTool.js.map +1 -0
  75. package/dist/mcp/builtin/collections/getCollectionSchemaTool.d.ts +2 -0
  76. package/dist/mcp/builtin/collections/getCollectionSchemaTool.d.ts.map +1 -0
  77. package/dist/mcp/builtin/collections/getCollectionSchemaTool.js +103 -0
  78. package/dist/mcp/builtin/collections/getCollectionSchemaTool.js.map +1 -0
  79. package/dist/mcp/builtin/collections/restoreVersionTool.d.ts +2 -0
  80. package/dist/mcp/builtin/collections/restoreVersionTool.d.ts.map +1 -0
  81. package/dist/mcp/builtin/collections/restoreVersionTool.js +69 -0
  82. package/dist/mcp/builtin/collections/restoreVersionTool.js.map +1 -0
  83. package/dist/mcp/builtin/collections/updateTool.d.ts +1 -1
  84. package/dist/mcp/builtin/collections/updateTool.d.ts.map +1 -1
  85. package/dist/mcp/builtin/collections/updateTool.js +118 -133
  86. package/dist/mcp/builtin/collections/updateTool.js.map +1 -1
  87. package/dist/mcp/builtin/collections/uploadInstructionsTool.d.ts +2 -0
  88. package/dist/mcp/builtin/collections/uploadInstructionsTool.d.ts.map +1 -0
  89. package/dist/mcp/builtin/collections/uploadInstructionsTool.js +55 -0
  90. package/dist/mcp/builtin/collections/uploadInstructionsTool.js.map +1 -0
  91. package/dist/mcp/builtin/formatEntityError.d.ts +16 -0
  92. package/dist/mcp/builtin/formatEntityError.d.ts.map +1 -0
  93. package/dist/mcp/builtin/formatEntityError.js +55 -0
  94. package/dist/mcp/builtin/formatEntityError.js.map +1 -0
  95. package/dist/mcp/builtin/getConfigInfoTool.d.ts +2 -0
  96. package/dist/mcp/builtin/getConfigInfoTool.d.ts.map +1 -0
  97. package/dist/mcp/builtin/getConfigInfoTool.js +69 -0
  98. package/dist/mcp/builtin/getConfigInfoTool.js.map +1 -0
  99. package/dist/mcp/builtin/globals/countVersionsTool.d.ts +2 -0
  100. package/dist/mcp/builtin/globals/countVersionsTool.d.ts.map +1 -0
  101. package/dist/mcp/builtin/globals/countVersionsTool.js +60 -0
  102. package/dist/mcp/builtin/globals/countVersionsTool.js.map +1 -0
  103. package/dist/mcp/builtin/globals/findTool.d.ts.map +1 -1
  104. package/dist/mcp/builtin/globals/findTool.js +27 -36
  105. package/dist/mcp/builtin/globals/findTool.js.map +1 -1
  106. package/dist/mcp/builtin/globals/findVersionByIDTool.d.ts +2 -0
  107. package/dist/mcp/builtin/globals/findVersionByIDTool.d.ts.map +1 -0
  108. package/dist/mcp/builtin/globals/findVersionByIDTool.js +68 -0
  109. package/dist/mcp/builtin/globals/findVersionByIDTool.js.map +1 -0
  110. package/dist/mcp/builtin/globals/findVersionsTool.d.ts +2 -0
  111. package/dist/mcp/builtin/globals/findVersionsTool.d.ts.map +1 -0
  112. package/dist/mcp/builtin/globals/findVersionsTool.js +78 -0
  113. package/dist/mcp/builtin/globals/findVersionsTool.js.map +1 -0
  114. package/dist/mcp/builtin/globals/getGlobalSchemaTool.d.ts +2 -0
  115. package/dist/mcp/builtin/globals/getGlobalSchemaTool.d.ts.map +1 -0
  116. package/dist/mcp/builtin/globals/getGlobalSchemaTool.js +81 -0
  117. package/dist/mcp/builtin/globals/getGlobalSchemaTool.js.map +1 -0
  118. package/dist/mcp/builtin/globals/restoreVersionTool.d.ts +2 -0
  119. package/dist/mcp/builtin/globals/restoreVersionTool.d.ts.map +1 -0
  120. package/dist/mcp/builtin/globals/restoreVersionTool.js +70 -0
  121. package/dist/mcp/builtin/globals/restoreVersionTool.js.map +1 -0
  122. package/dist/mcp/builtin/globals/updateTool.d.ts.map +1 -1
  123. package/dist/mcp/builtin/globals/updateTool.js +44 -58
  124. package/dist/mcp/builtin/globals/updateTool.js.map +1 -1
  125. package/dist/mcp/builtinTools.d.ts +150 -19
  126. package/dist/mcp/builtinTools.d.ts.map +1 -1
  127. package/dist/mcp/builtinTools.js +125 -20
  128. package/dist/mcp/builtinTools.js.map +1 -1
  129. package/dist/mcp/sanitizeMCPConfig.d.ts +0 -1
  130. package/dist/mcp/sanitizeMCPConfig.d.ts.map +1 -1
  131. package/dist/mcp/sanitizeMCPConfig.js +124 -58
  132. package/dist/mcp/sanitizeMCPConfig.js.map +1 -1
  133. package/dist/stdio.d.ts +3 -3
  134. package/dist/stdio.d.ts.map +1 -1
  135. package/dist/stdio.js +86 -47
  136. package/dist/stdio.js.map +1 -1
  137. package/dist/types.d.ts +123 -114
  138. package/dist/types.d.ts.map +1 -1
  139. package/dist/types.js +2 -4
  140. package/dist/types.js.map +1 -1
  141. package/dist/utils/camelCase.d.ts.map +1 -1
  142. package/dist/utils/getPluginConfig.d.ts +1 -1
  143. package/dist/utils/getPluginConfig.js +1 -1
  144. package/dist/utils/getPluginConfig.js.map +1 -1
  145. package/dist/utils/resolveProjectRoot.d.ts.map +1 -1
  146. package/dist/utils/toStandardSchema.d.ts.map +1 -1
  147. package/package.json +14 -21
  148. package/src/defaultAccess.ts +3 -0
  149. package/src/endpoint/access.ts +80 -101
  150. package/src/endpoint/index.ts +88 -16
  151. package/src/exports/client.ts +2 -1
  152. package/src/exports/internal.ts +1 -0
  153. package/src/index.ts +12 -20
  154. package/src/mcp/buildMcpServer.ts +180 -110
  155. package/src/mcp/builtin/collections/authTools.ts +122 -75
  156. package/src/mcp/builtin/collections/countTool.ts +61 -0
  157. package/src/mcp/builtin/collections/countVersionsTool.ts +60 -0
  158. package/src/mcp/builtin/collections/createTool.ts +117 -88
  159. package/src/mcp/builtin/collections/deleteTool.ts +48 -74
  160. package/src/mcp/builtin/collections/duplicateTool.ts +84 -0
  161. package/src/mcp/builtin/collections/fileInput.spec.ts +93 -0
  162. package/src/mcp/builtin/collections/fileInput.ts +127 -0
  163. package/src/mcp/builtin/collections/findDistinctTool.ts +67 -0
  164. package/src/mcp/builtin/collections/findTool.ts +57 -110
  165. package/src/mcp/builtin/collections/findVersionByIDTool.ts +66 -0
  166. package/src/mcp/builtin/collections/findVersionsTool.ts +83 -0
  167. package/src/mcp/builtin/collections/getCollectionSchemaTool.ts +91 -0
  168. package/src/mcp/builtin/collections/restoreVersionTool.ts +65 -0
  169. package/src/mcp/builtin/collections/updateTool.ts +106 -138
  170. package/src/mcp/builtin/collections/uploadInstructionsTool.ts +64 -0
  171. package/src/mcp/builtin/formatEntityError.ts +77 -0
  172. package/src/mcp/builtin/getConfigInfoTool.ts +68 -0
  173. package/src/mcp/builtin/globals/countVersionsTool.ts +60 -0
  174. package/src/mcp/builtin/globals/findTool.ts +27 -57
  175. package/src/mcp/builtin/globals/findVersionByIDTool.ts +64 -0
  176. package/src/mcp/builtin/globals/findVersionsTool.ts +69 -0
  177. package/src/mcp/builtin/globals/getGlobalSchemaTool.ts +73 -0
  178. package/src/mcp/builtin/globals/restoreVersionTool.ts +68 -0
  179. package/src/mcp/builtin/globals/updateTool.ts +51 -77
  180. package/src/mcp/builtinTools.ts +118 -41
  181. package/src/mcp/sanitizeMCPConfig.ts +120 -78
  182. package/src/stdio.ts +80 -47
  183. package/src/types.ts +141 -120
  184. package/src/utils/getPluginConfig.ts +1 -1
  185. package/dist/collection/getAccessField.d.ts +0 -12
  186. package/dist/collection/getAccessField.d.ts.map +0 -1
  187. package/dist/collection/getAccessField.js +0 -57
  188. package/dist/collection/getAccessField.js.map +0 -1
  189. package/dist/collection/index.d.ts +0 -6
  190. package/dist/collection/index.d.ts.map +0 -1
  191. package/dist/collection/index.js +0 -59
  192. package/dist/collection/index.js.map +0 -1
  193. package/dist/components/AccessField/index.client.d.ts +0 -10
  194. package/dist/components/AccessField/index.client.d.ts.map +0 -1
  195. package/dist/components/AccessField/index.client.js +0 -305
  196. package/dist/components/AccessField/index.client.js.map +0 -1
  197. package/dist/components/AccessField/index.css +0 -93
  198. package/dist/utils/getVirtualFieldNames.d.ts +0 -14
  199. package/dist/utils/getVirtualFieldNames.d.ts.map +0 -1
  200. package/dist/utils/getVirtualFieldNames.js +0 -35
  201. package/dist/utils/getVirtualFieldNames.js.map +0 -1
  202. package/dist/utils/localAPIDefaults.d.ts +0 -20
  203. package/dist/utils/localAPIDefaults.d.ts.map +0 -1
  204. package/dist/utils/localAPIDefaults.js +0 -19
  205. package/dist/utils/localAPIDefaults.js.map +0 -1
  206. package/dist/utils/schemaConversion/prepareCollectionSchema.d.ts +0 -7
  207. package/dist/utils/schemaConversion/prepareCollectionSchema.d.ts.map +0 -1
  208. package/dist/utils/schemaConversion/prepareCollectionSchema.js +0 -37
  209. package/dist/utils/schemaConversion/prepareCollectionSchema.js.map +0 -1
  210. package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.d.ts +0 -7
  211. package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.d.ts.map +0 -1
  212. package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.js +0 -20
  213. package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.js.map +0 -1
  214. package/dist/utils/schemaConversion/sanitizeJsonSchema.d.ts +0 -13
  215. package/dist/utils/schemaConversion/sanitizeJsonSchema.d.ts.map +0 -1
  216. package/dist/utils/schemaConversion/sanitizeJsonSchema.js +0 -56
  217. package/dist/utils/schemaConversion/sanitizeJsonSchema.js.map +0 -1
  218. package/dist/utils/schemaConversion/simplifyRelationshipFields.d.ts +0 -20
  219. package/dist/utils/schemaConversion/simplifyRelationshipFields.d.ts.map +0 -1
  220. package/dist/utils/schemaConversion/simplifyRelationshipFields.js +0 -56
  221. package/dist/utils/schemaConversion/simplifyRelationshipFields.js.map +0 -1
  222. package/dist/utils/schemaConversion/transformPointFields.d.ts +0 -3
  223. package/dist/utils/schemaConversion/transformPointFields.d.ts.map +0 -1
  224. package/dist/utils/schemaConversion/transformPointFields.js +0 -57
  225. package/dist/utils/schemaConversion/transformPointFields.js.map +0 -1
  226. package/dist/utils/transformPointDataToPayload.d.ts +0 -7
  227. package/dist/utils/transformPointDataToPayload.d.ts.map +0 -1
  228. package/dist/utils/transformPointDataToPayload.js +0 -28
  229. package/dist/utils/transformPointDataToPayload.js.map +0 -1
  230. package/src/collection/getAccessField.ts +0 -64
  231. package/src/collection/index.ts +0 -63
  232. package/src/components/AccessField/index.client.tsx +0 -344
  233. package/src/components/AccessField/index.css +0 -93
  234. package/src/utils/getVirtualFieldNames.ts +0 -53
  235. package/src/utils/localAPIDefaults.ts +0 -22
  236. package/src/utils/schemaConversion/prepareCollectionSchema.ts +0 -39
  237. package/src/utils/schemaConversion/removeVirtualFieldsFromSchema.ts +0 -27
  238. package/src/utils/schemaConversion/sanitizeJsonSchema.ts +0 -62
  239. package/src/utils/schemaConversion/simplifyRelationshipFields.ts +0 -70
  240. package/src/utils/schemaConversion/transformPointFields.ts +0 -56
  241. package/src/utils/transformPointDataToPayload.ts +0 -40
package/src/stdio.ts CHANGED
@@ -1,98 +1,131 @@
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
- import type { AuthorizedMCP, SanitizedMCPPluginConfig } from './types.js'
9
+ import type { SanitizedMCPPluginConfig } from './types.js'
10
10
 
11
+ import { getAuthorizedMCP } from './endpoint/access.js'
11
12
  import { buildMcpServer } from './mcp/buildMcpServer.js'
12
13
  import { sanitizeMCPConfig } from './mcp/sanitizeMCPConfig.js'
13
14
  import { getPluginConfig } from './utils/getPluginConfig.js'
14
15
  import { resolveProjectRoot } from './utils/resolveProjectRoot.js'
15
16
 
16
17
  /**
17
- * Stdio adapter for the Payload MCP server.
18
+ * Starts Payload's MCP server over stdin and stdout.
18
19
  *
19
- * Do not use in production. There's no auth; whoever can spawn the process gets
20
- * full access to your local data.
20
+ * Set `PAYLOAD_MCP_AUTHORIZATION` to authenticate. In development,
21
+ * `PAYLOAD_MCP_OVERRIDE_ACCESS=true` skips access checks.
21
22
  */
22
23
  export const runMcpStdio = async (): Promise<void> => {
23
- /**
24
- * If MCP clients spawn stdio servers from an arbitrary working directory,
25
- * Payload's cwd-anchored `findConfig()` can't locate the project on
26
- * its own. This module always lives inside the project's `node_modules`, so
27
- * we derive the project root from its own path and `chdir` into it.
28
- *
29
- * An absolute `PAYLOAD_CONFIG_PATH` still overrides everything; unusual
30
- * layouts (e.g. a monorepo where the package is hoisted above the app) can
31
- * fall back to it. See the implementation of findConfig for details.
32
- */
24
+ // MCP clients may start this command from another folder. Move to the Payload
25
+ // project so findConfig() can find the config.
33
26
  const projectRoot = resolveProjectRoot(fileURLToPath(import.meta.url))
34
27
  if (projectRoot) {
35
28
  process.chdir(projectRoot)
36
29
  }
37
30
 
31
+ loadEnv()
38
32
  const configPath = findConfig()
39
33
  const configModule = await import(pathToFileURL(configPath).toString())
40
- const config = (await (configModule.default ?? configModule)) as MaybePromise<SanitizedConfig>
41
-
42
- const payload = await getPayload({ config })
34
+ const config = (await (configModule.default ?? configModule)) as SanitizedConfig
43
35
 
44
36
  /**
45
- * If the user added `mcpPlugin({...})` to their `plugins` array, read the
46
- * sanitized config they registered. Otherwise fall back to the defaults. That way,
47
- * the mcp works on any project that has `@payloadcms/plugin-mcp` installed,
48
- * 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
49
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
+
50
60
  let pluginConfig: SanitizedMCPPluginConfig
51
61
  try {
52
62
  pluginConfig = getPluginConfig({ config: payload.config })
53
63
  } catch {
64
+ // The command also works when mcpPlugin() is not in the Payload config.
54
65
  pluginConfig = sanitizeMCPConfig({ config: payload.config, pluginConfig: {} })
55
66
 
56
- const fakePluginFn: Plugin = (config) => config
57
- fakePluginFn.slug = '@payloadcms/plugin-mcp'
58
- // @ts-expect-error
59
- fakePluginFn.sanitizedOptions = pluginConfig
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)
74
+ }
75
+
76
+ const overrideAccessEnv = process.env.PAYLOAD_MCP_OVERRIDE_ACCESS
77
+ let overrideAccess = false
78
+
79
+ if (overrideAccessEnv === 'true') {
80
+ overrideAccess = true
81
+ } else if (overrideAccessEnv && overrideAccessEnv !== 'false') {
82
+ throw new Error('PAYLOAD_MCP_OVERRIDE_ACCESS must be "true" or "false".')
83
+ }
60
84
 
61
- // Push to payload config, to ensure consecutive calls to `getPluginConfig()` work
62
- ;(payload.config.plugins ??= []).push(fakePluginFn)
85
+ if (overrideAccess && process.env.NODE_ENV !== 'development') {
86
+ throw new Error('PAYLOAD_MCP_OVERRIDE_ACCESS is only available in development.')
63
87
  }
64
88
 
65
- const authorizedMCP: AuthorizedMCP = {
66
- items: pluginConfig.items,
67
- overrideAccess: true,
68
- user: null,
89
+ const headers = new Headers()
90
+ if (process.env.PAYLOAD_MCP_AUTHORIZATION) {
91
+ headers.set('Authorization', process.env.PAYLOAD_MCP_AUTHORIZATION)
69
92
  }
70
93
 
71
- const req = await createLocalReq({}, payload)
94
+ const req = await createPayloadRequest({ payload, req: { headers } })
72
95
  req.payloadAPI = 'MCP' as const
96
+ const authorizedMCP = await getAuthorizedMCP({ overrideAccess, req })
73
97
 
74
- const server = buildMcpServer({ authorizedMCP, pluginConfig, req })
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
+ })
75
104
 
76
- const transport = new StdioServerTransport()
77
- await server.connect(transport)
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
78
112
 
79
- // Process now lives until stdin closes. Wire graceful shutdown so DB
80
- // connections (and any other Payload-managed resources) get released.
81
- const shutdown = async (code = 0) => {
82
113
  try {
83
- await server.close()
114
+ await stdioServer.close()
84
115
  } catch (err) {
85
116
  console.error('[payload-mcp] error closing server:', err)
86
117
  }
118
+
87
119
  try {
88
120
  await payload.destroy()
89
121
  } catch (err) {
90
122
  console.error('[payload-mcp] error destroying payload:', err)
91
123
  }
92
- process.exit(code)
124
+
125
+ process.exit(0)
93
126
  }
94
127
 
95
- process.on('SIGINT', () => void shutdown(0))
96
- process.on('SIGTERM', () => void shutdown(0))
97
- 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())
98
131
  }
package/src/types.ts CHANGED
@@ -1,23 +1,24 @@
1
1
  import type {
2
2
  CallToolResult,
3
3
  ContentBlock,
4
+ Implementation,
4
5
  JsonSchemaType,
5
- McpServer,
6
+ McpServerOptions,
6
7
  ResourceTemplate,
7
8
  ServerContext,
8
9
  StandardSchemaWithJSON,
10
+ ToolAnnotations,
9
11
  } from '@modelcontextprotocol/server'
10
12
  import type {
11
13
  AuthCollectionSlug,
12
- CollectionConfig,
13
14
  CollectionSlug,
14
- DefaultDocumentIDType,
15
15
  GlobalSlug,
16
16
  MaybePromise,
17
17
  PayloadRequest,
18
- TypedUser,
18
+ SanitizedPermissions,
19
19
  } from 'payload'
20
20
 
21
+ import type { GetAuthorizedMCPArgs } from './endpoint/access.js'
21
22
  import type {
22
23
  MCPCollectionAuthToolName,
23
24
  MCPCollectionBuiltinName,
@@ -26,34 +27,16 @@ import type {
26
27
 
27
28
  export type { MCPCollectionAuthToolName, MCPCollectionBuiltinName, MCPGlobalBuiltinName }
28
29
 
29
- /** Re-exported from `@modelcontextprotocol/server` — the JSON Schema shape the MCP runtime validates against. */
30
- export type { JsonSchemaType, StandardSchemaWithJSON }
30
+ /** Re-exported from `@modelcontextprotocol/server` — common MCP types used in plugin config. */
31
+ export type { JsonSchemaType, StandardSchemaWithJSON, ToolAnnotations }
31
32
 
32
33
  /**
33
34
  * What a tool's `input` (or a prompt's `argsSchema`) can be — either a raw
34
- * JSON Schema literal, or a Standard Schema instance (Zod, Valibot, …).
35
+ * JSON Schema 2020-12 literal, or a Standard Schema instance (Zod, Valibot, …).
36
+ * Raw schemas may omit `$schema`; when present it must declare the 2020-12 dialect.
35
37
  */
36
38
  export type ToolInputSchema = JsonSchemaType | StandardSchemaWithJSON
37
39
 
38
- /**
39
- * Serializable mirror of `SanitizedMCPPluginConfig` for client components —
40
- * the full sanitized config carries functions (tool handlers, etc.) that can't
41
- * cross the server→client boundary. Built by `sanitizeClientPluginConfig` and
42
- * passed to the `AccessField` component via `clientProps`.
43
- *
44
- * @internal
45
- */
46
- export type ClientMCPPluginConfig = {
47
- items: Array<{
48
- collectionSlug?: string
49
- description: string
50
- globalSlug?: string
51
- key: string
52
- label: string
53
- type: 'collectionTool' | 'globalTool' | 'prompt' | 'resource' | 'tool'
54
- }>
55
- }
56
-
57
40
  export type MCPToolResponse = {
58
41
  content: ContentBlock[]
59
42
  /**
@@ -70,6 +53,13 @@ export type MCPResponseOverride = (
70
53
  req: PayloadRequest,
71
54
  ) => MCPToolResponse
72
55
 
56
+ export type MCPAfterToolCallHook = (args: {
57
+ input: unknown
58
+ req: PayloadRequest
59
+ response: MCPToolResponse
60
+ toolName: string
61
+ }) => MaybePromise<MCPToolResponse>
62
+
73
63
  /**
74
64
  * The handler's `input` type. A specific Standard Schema (Zod, Valibot, …) gets
75
65
  * its inferred output; anything else falls back to `Record<string, unknown>`.
@@ -80,6 +70,19 @@ export type TypedInput<TSchema> = TSchema extends StandardSchemaWithJSON
80
70
  : StandardSchemaWithJSON.InferOutput<TSchema>
81
71
  : Record<string, unknown>
82
72
 
73
+ export type MCPAccessArgs = {
74
+ permissions?: SanitizedPermissions
75
+ req: PayloadRequest
76
+ }
77
+
78
+ export type CollectionMCPAccessArgs = {
79
+ slug: CollectionSlug
80
+ } & MCPAccessArgs
81
+
82
+ export type GlobalMCPAccessArgs = {
83
+ slug: GlobalSlug
84
+ } & MCPAccessArgs
85
+
83
86
  export type ToolHandlerArgs<TSchema = undefined> = {
84
87
  authorizedMCP: AuthorizedMCP
85
88
  input: TypedInput<TSchema>
@@ -88,14 +91,21 @@ export type ToolHandlerArgs<TSchema = undefined> = {
88
91
  }
89
92
 
90
93
  export type CollectionToolHandlerArgs<TSchema = undefined> = {
91
- collectionSlug: CollectionSlug
94
+ slug: CollectionSlug
92
95
  } & ToolHandlerArgs<TSchema>
93
96
 
94
97
  export type GlobalToolHandlerArgs<TSchema = undefined> = {
95
- globalSlug: GlobalSlug
98
+ slug: GlobalSlug
96
99
  } & ToolHandlerArgs<TSchema>
97
100
 
98
101
  export type Tool<TSchema extends ToolInputSchema | undefined = ToolInputSchema | undefined> = {
102
+ /**
103
+ * Runs while authorizing each MCP request, before the tool is advertised or called. Return
104
+ * `false` to make the tool unavailable for that request. This is skipped when `overrideAccess`
105
+ * is enabled.
106
+ */
107
+ access?: (args: MCPAccessArgs) => MaybePromise<boolean>
108
+ annotations?: ToolAnnotations
99
109
  description: string
100
110
  handler: (args: ToolHandlerArgs<TSchema>) => MaybePromise<MCPToolResponse>
101
111
  input?: TSchema
@@ -105,30 +115,57 @@ export type Tool<TSchema extends ToolInputSchema | undefined = ToolInputSchema |
105
115
  overrideResponse?: MCPResponseOverride
106
116
  }
107
117
 
108
- /**
109
- * `TSchema` is the schema itself (Standard Schema, raw JSON Schema, or undefined).
110
- * The function-form variant of `input` carries a concrete `{ collectionSchema: JsonSchemaType }`
111
- * parameter type so callers can write `({ collectionSchema }) => …` without annotating it.
112
- */
113
118
  export type CollectionTool<
114
119
  TSchema extends ToolInputSchema | undefined = ToolInputSchema | undefined,
115
120
  > = {
121
+ /**
122
+ * Runs while authorizing each MCP request for this collection. Return `false` to reject calls
123
+ * to this tool for the collection. The shared tool is not advertised when no collections allow
124
+ * it, but can remain advertised when it is available for another collection. This is skipped
125
+ * when `overrideAccess` is enabled.
126
+ */
127
+ access?: (args: CollectionMCPAccessArgs) => MaybePromise<boolean>
116
128
  handler: (args: CollectionToolHandlerArgs<TSchema>) => MaybePromise<MCPToolResponse>
117
- input?: ((args: { collectionSchema: JsonSchemaType }) => TSchema) | TSchema
118
- } & Pick<Tool, 'description' | 'overrideResponse'>
129
+ input?: TSchema
130
+ } & Pick<Tool, 'annotations' | 'description' | 'overrideResponse'>
119
131
 
120
132
  export type GlobalTool<TSchema extends ToolInputSchema | undefined = ToolInputSchema | undefined> =
121
133
  {
134
+ /**
135
+ * Runs while authorizing each MCP request for this global. Return `false` to reject calls to
136
+ * this tool for the global. The shared tool is not advertised when no globals allow it, but
137
+ * can remain advertised when it is available for another global. This is skipped when
138
+ * `overrideAccess` is enabled.
139
+ */
140
+ access?: (args: GlobalMCPAccessArgs) => MaybePromise<boolean>
122
141
  handler: (args: GlobalToolHandlerArgs<TSchema>) => MaybePromise<MCPToolResponse>
123
- input?: ((args: { globalSchema: JsonSchemaType }) => TSchema) | TSchema
124
- } & Pick<Tool, 'description' | 'overrideResponse'>
142
+ input?: TSchema
143
+ } & Pick<Tool, 'annotations' | 'description' | 'overrideResponse'>
125
144
 
126
145
  /**
127
146
  * Configures (or disables) a built-in tool without replacing it.
128
147
  * `handler?: never` prevents a full `CollectionTool`/`GlobalTool` (which has a
129
148
  * required handler) from being silently accepted at a built-in key slot.
130
149
  */
131
- export type MCPBuiltInToolOverride = {
150
+ export type MCPBuiltInCollectionToolOverride = {
151
+ /**
152
+ * Replaces the built-in tool's access check. Return `false` to make the tool unavailable for
153
+ * this collection. This is skipped when `overrideAccess` is enabled.
154
+ */
155
+ access?: (args: CollectionMCPAccessArgs) => MaybePromise<boolean>
156
+ annotations?: ToolAnnotations
157
+ description?: string
158
+ handler?: never
159
+ overrideResponse?: MCPResponseOverride
160
+ }
161
+
162
+ export type MCPBuiltInGlobalToolOverride = {
163
+ /**
164
+ * Replaces the built-in tool's access check. Return `false` to make the tool unavailable for
165
+ * this global. This is skipped when `overrideAccess` is enabled.
166
+ */
167
+ access?: (args: GlobalMCPAccessArgs) => MaybePromise<boolean>
168
+ annotations?: ToolAnnotations
132
169
  description?: string
133
170
  handler?: never
134
171
  overrideResponse?: MCPResponseOverride
@@ -142,13 +179,13 @@ export type MCPBuiltInToolOverride = {
142
179
  export type MCPTopLevelToolEntry = Tool
143
180
 
144
181
  export type MCPCollectionToolsMap = {
145
- [customToolName: string]: boolean | CollectionTool | MCPBuiltInToolOverride | undefined
182
+ [customToolName: string]: boolean | CollectionTool | MCPBuiltInCollectionToolOverride | undefined
146
183
  } & {
147
- [K in MCPCollectionBuiltinName]?: false | MCPBuiltInToolOverride
184
+ [K in MCPCollectionBuiltinName]?: false | MCPBuiltInCollectionToolOverride
148
185
  }
149
186
 
150
187
  export type MCPAuthCollectionToolsMap = {
151
- [K in MCPCollectionAuthToolName]?: MCPBuiltInToolOverride | true
188
+ [K in MCPCollectionAuthToolName]?: MCPBuiltInCollectionToolOverride | true
152
189
  } & MCPCollectionToolsMap
153
190
 
154
191
  /** Auth-enabled collections get auth-tool name autocomplete; others get CRUD-only. */
@@ -157,9 +194,9 @@ export type MCPToolsMapForCollection<Slug extends CollectionSlug> = Slug extends
157
194
  : MCPCollectionToolsMap
158
195
 
159
196
  export type MCPGlobalToolsMap = {
160
- [customToolName: string]: boolean | GlobalTool | MCPBuiltInToolOverride | undefined
197
+ [customToolName: string]: boolean | GlobalTool | MCPBuiltInGlobalToolOverride | undefined
161
198
  } & {
162
- [K in MCPGlobalBuiltinName]?: false | MCPBuiltInToolOverride
199
+ [K in MCPGlobalBuiltinName]?: false | MCPBuiltInGlobalToolOverride
163
200
  }
164
201
 
165
202
  export type MCPTopLevelToolsMap = Record<string, Tool>
@@ -171,6 +208,12 @@ export type PromptHandlerArgs<TSchema = undefined> = {
171
208
  }
172
209
 
173
210
  export type Prompt<TSchema extends ToolInputSchema = ToolInputSchema> = {
211
+ /**
212
+ * Runs while authorizing each MCP request, before the prompt is advertised or used. Return
213
+ * `false` to make the prompt unavailable for that request. This is skipped when
214
+ * `overrideAccess` is enabled.
215
+ */
216
+ access?: (args: MCPAccessArgs) => MaybePromise<boolean>
174
217
  argsSchema: TSchema
175
218
  description: string
176
219
  handler: (args: PromptHandlerArgs<TSchema>) => MaybePromise<{
@@ -188,6 +231,12 @@ export type ResourceHandlerArgs = {
188
231
  }
189
232
 
190
233
  export type Resource = {
234
+ /**
235
+ * Runs while authorizing each MCP request, before the resource is advertised or read. Return
236
+ * `false` to make the resource unavailable for that request. This is skipped when
237
+ * `overrideAccess` is enabled.
238
+ */
239
+ access?: (args: MCPAccessArgs) => MaybePromise<boolean>
191
240
  description: string
192
241
  handler: (args: ResourceHandlerArgs) => MaybePromise<{
193
242
  contents: Array<{ text: string; uri: string }>
@@ -221,106 +270,79 @@ export type MCPPluginConfig = {
221
270
  collections?: {
222
271
  [Slug in CollectionSlug]?: MCPPluginCollectionConfig<Slug>
223
272
  }
224
- /** Skip MCP registration. The API key collection is still added (so DB / types stay stable). */
273
+ /** Skip MCP endpoint registration. */
225
274
  disabled?: boolean
226
275
  globals?: {
227
276
  [Slug in GlobalSlug]?: MCPPluginGlobalConfig
228
277
  }
278
+ hooks?: {
279
+ /** Transform a tool response after its handler returns */
280
+ afterToolCall?: MCPAfterToolCallHook[]
281
+ }
229
282
  mcp?: {
283
+ /**
284
+ * Largest HTTP request body, in bytes, that the MCP endpoint accepts. Larger requests
285
+ * are answered with `413`. Base64 file uploads count toward this limit.
286
+ *
287
+ * @default 4194304 (4 MiB, the MCP SDK default)
288
+ */
289
+ maxRequestBodySize?: number
230
290
  serverOptions?: MCPServerOptions
231
291
  verboseLogs?: boolean
232
292
  }
233
- overrideApiKeyCollection?: (collection: CollectionConfig) => CollectionConfig
234
- /** Replace the default API-key auth with a custom resolver. */
235
- overrideAuth?: (args: {
236
- getAPIKeyDoc: (overrideApiKey?: string) => Promise<MCPAPIKeysDoc>
237
- getAuthorizedMCP: (args: { apiKeyDoc: MCPAPIKeysDoc }) => AuthorizedMCP
238
- pluginConfig: SanitizedMCPPluginConfig
239
- req: PayloadRequest
240
- }) => MaybePromise<AuthorizedMCP>
293
+ /**
294
+ * Replace the default MCP authorization resolver.
295
+ *
296
+ * This hook replaces the default authentication flow. It must set `req.user` to the
297
+ * authenticated Payload user, or to `null` for an anonymous caller, before returning.
298
+ */
299
+ overrideGetAuthorizedMCP?: (
300
+ args: {
301
+ pluginConfig: SanitizedMCPPluginConfig
302
+ } & GetAuthorizedMCPArgs,
303
+ ) => MaybePromise<AuthorizedMCP>
241
304
  prompts?: Record<string, Prompt>
242
305
  resources?: Record<string, Resource>
243
306
  /** Cross-cutting tools (not scoped to any collection or global). */
244
307
  tools?: MCPTopLevelToolsMap
245
- userCollection?: CollectionSlug
246
308
  }
247
309
 
248
310
  export type SanitizedMCPPluginConfig = {
249
311
  items: MCPItem[]
250
- userCollection: CollectionSlug
251
- } & Pick<MCPPluginConfig, 'disabled' | 'mcp' | 'overrideApiKeyCollection' | 'overrideAuth'>
312
+ } & Pick<MCPPluginConfig, 'disabled' | 'hooks' | 'mcp' | 'overrideGetAuthorizedMCP'>
252
313
 
253
314
  export type MCPServerOptions = {
254
- options?: ConstructorParameters<typeof McpServer>[1]
255
- serverInfo?: Partial<ConstructorParameters<typeof McpServer>[0]>
256
- }
257
-
258
- /**
259
- * Nested access tree as stored in the collection.
260
- * A `false` leaf disables that tool; missing keys defer to
261
- * defaults (built-in CRUD is on, opt-in tools are off).
262
- */
263
- export type MCPAPIKeysDocAccessTree = {
264
- collections?: {
265
- [CollectionSlug: CollectionSlug]: {
266
- [ToolKey: string]: boolean
267
- }
268
- }
269
- globals?: {
270
- [GlobalSlug: GlobalSlug]: {
271
- [ToolKey: string]: boolean
272
- }
273
- }
274
- prompts?: {
275
- [PromptKey: string]: boolean
276
- }
277
- resources?: {
278
- [ResourceKey: string]: boolean
279
- }
280
- tools?: {
281
- [ToolKey: string]: boolean
282
- }
283
- }
284
-
285
- /**
286
- * Stored on `payload-mcp-api-keys` docs
287
- */
288
- export type MCPAPIKeysDoc = {
289
- access: MCPAPIKeysDocAccessTree
290
- id: DefaultDocumentIDType
291
- overrideAccess?: boolean
292
- user: null | TypedUser
315
+ options?: McpServerOptions
316
+ serverInfo?: Partial<Implementation>
293
317
  }
294
318
 
295
319
  /**
296
- * One MCP primitive — tool, prompt, or resource — paired with the metadata both
297
- * the endpoint and the API key collection need. Built by `sanitizeMCPConfig`,
298
- * filtered by `getAuthorizedMCP`, registered by the MCP endpoint.
320
+ * One MCP primitive plus the metadata needed for config-driven filtering and
321
+ * registration.
299
322
  *
300
- * - `key`: the config identifier (`find`, `echo`). Used for the API-key deny
301
- * lookup and as the admin checkbox field name. For collection/global tools,
302
- * the MCP wire name (`findPosts`) is derived from `key + slug` at
303
- * registration time.
304
- * - `label`: human-readable admin-UI display text for the checkbox.
305
- * - `tool` / `prompt` / `resource`: the live primitive (its own
306
- * `description` is what both MCP clients and the admin UI surface).
323
+ * - `configKey`: the config identifier, e.g. `find` or `echo`.
324
+ * - `mcpName`: the MCP wire name, e.g. `findDocuments` or `echo`.
325
+ * - `label`: human-readable display text.
307
326
  */
308
327
  export type MCPItemBase = {
309
- key: string
328
+ configKey: string
310
329
  label: string
330
+ mcpName: string
311
331
  }
312
332
 
333
+ export type CollectionMCPItem = {
334
+ collectionSlug: CollectionSlug
335
+ tool: CollectionTool
336
+ type: 'collectionTool'
337
+ } & MCPItemBase
338
+
339
+ export type GlobalMCPItem = {
340
+ globalSlug: GlobalSlug
341
+ tool: GlobalTool
342
+ type: 'globalTool'
343
+ } & MCPItemBase
344
+
313
345
  export type MCPItem =
314
- | ({
315
- collectionSlug: CollectionSlug
316
- tool: CollectionTool
317
- type: 'collectionTool'
318
- } & MCPItemBase)
319
- | ({
320
- globalSlug: GlobalSlug
321
- tool: GlobalTool
322
- type: 'globalTool'
323
- } & MCPItemBase)
324
346
  | ({
325
347
  prompt: Prompt
326
348
  type: 'prompt'
@@ -333,15 +355,14 @@ export type MCPItem =
333
355
  tool: Tool
334
356
  type: 'tool'
335
357
  } & MCPItemBase)
358
+ | CollectionMCPItem
359
+ | GlobalMCPItem
336
360
 
337
361
  /**
338
- * The caller's identity + the MCP items they can use for this request. Returned
339
- * by `getAuthorizedMCP`; denied items are simply absent from `items`. Handlers
340
- * receive this via `args.authorizedMCP` so they can spread
341
- * `localAPIDefaults(authorizedMCP)` into every local API call.
362
+ * The MCP items and access mode authorized for this request. The authenticated user is available
363
+ * as `req.user`.
342
364
  */
343
365
  export type AuthorizedMCP = {
344
366
  items: MCPItem[]
345
367
  overrideAccess: boolean
346
- user: null | TypedUser
347
368
  }
@@ -5,7 +5,7 @@ import type { SanitizedMCPPluginConfig } from '../types.js'
5
5
  /**
6
6
  * Returns the sanitized plugin config — the form produced by
7
7
  * `sanitizeMCPConfig` and stashed back on the plugin's `options` during init.
8
- * Callers downstream of plugin init can rely on `items`, `userCollection`, etc.
8
+ * Callers downstream of plugin init can rely on `items` and other defaults
9
9
  * being fully resolved.
10
10
  */
11
11
  export const getPluginConfig: (args: { config: SanitizedConfig }) => SanitizedMCPPluginConfig = ({
@@ -1,12 +0,0 @@
1
- import type { JSONField } from 'payload';
2
- import type { SanitizedMCPPluginConfig } from '../types.js';
3
- /**
4
- * Returns the API key collection's `access` field — a JSON field whose value
5
- * is the `MCPAPIKeysDocAccessTree` the endpoint consults. The actual UI is
6
- * a custom client component (`AccessField`) which renders a permissions
7
- * matrix; the field's stored shape is just the runtime access tree.
8
- */
9
- export declare const getAccessField: ({ pluginConfig, }: {
10
- pluginConfig: SanitizedMCPPluginConfig;
11
- }) => JSONField;
12
- //# sourceMappingURL=getAccessField.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"getAccessField.d.ts","sourceRoot":"","sources":["../../src/collection/getAccessField.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAExC,OAAO,KAAK,EAAkC,wBAAwB,EAAE,MAAM,aAAa,CAAA;AAE3F;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,mBAE5B;IACD,YAAY,EAAE,wBAAwB,CAAA;CACvC,KAAG,SAqBH,CAAA"}