@payloadcms/plugin-mcp 4.0.0-internal.40de3ec → 4.0.0-internal.4804abd

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 (214) hide show
  1. package/dist/defaultAccess.d.ts +3 -0
  2. package/dist/defaultAccess.d.ts.map +1 -0
  3. package/dist/defaultAccess.js +3 -0
  4. package/dist/defaultAccess.js.map +1 -0
  5. package/dist/endpoint/access.d.ts +17 -5
  6. package/dist/endpoint/access.d.ts.map +1 -1
  7. package/dist/endpoint/access.js +82 -88
  8. package/dist/endpoint/access.js.map +1 -1
  9. package/dist/endpoint/index.d.ts.map +1 -1
  10. package/dist/endpoint/index.js +11 -0
  11. package/dist/endpoint/index.js.map +1 -1
  12. package/dist/exports/client.d.ts +1 -1
  13. package/dist/exports/client.d.ts.map +1 -1
  14. package/dist/exports/client.js +1 -1
  15. package/dist/exports/client.js.map +1 -1
  16. package/dist/exports/internal.d.ts +2 -0
  17. package/dist/exports/internal.d.ts.map +1 -0
  18. package/dist/exports/internal.js +3 -0
  19. package/dist/exports/internal.js.map +1 -0
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.js +13 -18
  22. package/dist/index.js.map +1 -1
  23. package/dist/mcp/buildMcpServer.d.ts.map +1 -1
  24. package/dist/mcp/buildMcpServer.js +102 -71
  25. package/dist/mcp/buildMcpServer.js.map +1 -1
  26. package/dist/mcp/builtin/collections/authTools.d.ts.map +1 -1
  27. package/dist/mcp/builtin/collections/authTools.js +44 -6
  28. package/dist/mcp/builtin/collections/authTools.js.map +1 -1
  29. package/dist/mcp/builtin/collections/countTool.d.ts +2 -0
  30. package/dist/mcp/builtin/collections/countTool.d.ts.map +1 -0
  31. package/dist/mcp/builtin/collections/countTool.js +69 -0
  32. package/dist/mcp/builtin/collections/countTool.js.map +1 -0
  33. package/dist/mcp/builtin/collections/countVersionsTool.d.ts +2 -0
  34. package/dist/mcp/builtin/collections/countVersionsTool.d.ts.map +1 -0
  35. package/dist/mcp/builtin/collections/countVersionsTool.js +65 -0
  36. package/dist/mcp/builtin/collections/countVersionsTool.js.map +1 -0
  37. package/dist/mcp/builtin/collections/createTool.d.ts +1 -1
  38. package/dist/mcp/builtin/collections/createTool.d.ts.map +1 -1
  39. package/dist/mcp/builtin/collections/createTool.js +38 -42
  40. package/dist/mcp/builtin/collections/createTool.js.map +1 -1
  41. package/dist/mcp/builtin/collections/deleteTool.d.ts +1 -1
  42. package/dist/mcp/builtin/collections/deleteTool.d.ts.map +1 -1
  43. package/dist/mcp/builtin/collections/deleteTool.js +14 -20
  44. package/dist/mcp/builtin/collections/deleteTool.js.map +1 -1
  45. package/dist/mcp/builtin/collections/duplicateTool.d.ts +2 -0
  46. package/dist/mcp/builtin/collections/duplicateTool.d.ts.map +1 -0
  47. package/dist/mcp/builtin/collections/duplicateTool.js +97 -0
  48. package/dist/mcp/builtin/collections/duplicateTool.js.map +1 -0
  49. package/dist/mcp/builtin/collections/findDistinctTool.d.ts +2 -0
  50. package/dist/mcp/builtin/collections/findDistinctTool.d.ts.map +1 -0
  51. package/dist/mcp/builtin/collections/findDistinctTool.js +93 -0
  52. package/dist/mcp/builtin/collections/findDistinctTool.js.map +1 -0
  53. package/dist/mcp/builtin/collections/findTool.d.ts +1 -1
  54. package/dist/mcp/builtin/collections/findTool.d.ts.map +1 -1
  55. package/dist/mcp/builtin/collections/findTool.js +49 -43
  56. package/dist/mcp/builtin/collections/findTool.js.map +1 -1
  57. package/dist/mcp/builtin/collections/findVersionByIDTool.d.ts +2 -0
  58. package/dist/mcp/builtin/collections/findVersionByIDTool.d.ts.map +1 -0
  59. package/dist/mcp/builtin/collections/findVersionByIDTool.js +84 -0
  60. package/dist/mcp/builtin/collections/findVersionByIDTool.js.map +1 -0
  61. package/dist/mcp/builtin/collections/findVersionsTool.d.ts +2 -0
  62. package/dist/mcp/builtin/collections/findVersionsTool.d.ts.map +1 -0
  63. package/dist/mcp/builtin/collections/findVersionsTool.js +99 -0
  64. package/dist/mcp/builtin/collections/findVersionsTool.js.map +1 -0
  65. package/dist/mcp/builtin/collections/formatCollectionError.d.ts +9 -0
  66. package/dist/mcp/builtin/collections/formatCollectionError.d.ts.map +1 -0
  67. package/dist/mcp/builtin/collections/formatCollectionError.js +60 -0
  68. package/dist/mcp/builtin/collections/formatCollectionError.js.map +1 -0
  69. package/dist/mcp/builtin/collections/getCollectionSchemaTool.d.ts +2 -0
  70. package/dist/mcp/builtin/collections/getCollectionSchemaTool.d.ts.map +1 -0
  71. package/dist/mcp/builtin/collections/getCollectionSchemaTool.js +47 -0
  72. package/dist/mcp/builtin/collections/getCollectionSchemaTool.js.map +1 -0
  73. package/dist/mcp/builtin/collections/restoreVersionTool.d.ts +2 -0
  74. package/dist/mcp/builtin/collections/restoreVersionTool.d.ts.map +1 -0
  75. package/dist/mcp/builtin/collections/restoreVersionTool.js +82 -0
  76. package/dist/mcp/builtin/collections/restoreVersionTool.js.map +1 -0
  77. package/dist/mcp/builtin/collections/updateTool.d.ts +1 -1
  78. package/dist/mcp/builtin/collections/updateTool.d.ts.map +1 -1
  79. package/dist/mcp/builtin/collections/updateTool.js +87 -86
  80. package/dist/mcp/builtin/collections/updateTool.js.map +1 -1
  81. package/dist/mcp/builtin/getConfigInfoTool.d.ts +2 -0
  82. package/dist/mcp/builtin/getConfigInfoTool.d.ts.map +1 -0
  83. package/dist/mcp/builtin/getConfigInfoTool.js +71 -0
  84. package/dist/mcp/builtin/getConfigInfoTool.js.map +1 -0
  85. package/dist/mcp/builtin/globals/countVersionsTool.d.ts +2 -0
  86. package/dist/mcp/builtin/globals/countVersionsTool.d.ts.map +1 -0
  87. package/dist/mcp/builtin/globals/countVersionsTool.js +65 -0
  88. package/dist/mcp/builtin/globals/countVersionsTool.js.map +1 -0
  89. package/dist/mcp/builtin/globals/findTool.d.ts.map +1 -1
  90. package/dist/mcp/builtin/globals/findTool.js +18 -21
  91. package/dist/mcp/builtin/globals/findTool.js.map +1 -1
  92. package/dist/mcp/builtin/globals/findVersionByIDTool.d.ts +2 -0
  93. package/dist/mcp/builtin/globals/findVersionByIDTool.d.ts.map +1 -0
  94. package/dist/mcp/builtin/globals/findVersionByIDTool.js +80 -0
  95. package/dist/mcp/builtin/globals/findVersionByIDTool.js.map +1 -0
  96. package/dist/mcp/builtin/globals/findVersionsTool.d.ts +2 -0
  97. package/dist/mcp/builtin/globals/findVersionsTool.d.ts.map +1 -0
  98. package/dist/mcp/builtin/globals/findVersionsTool.js +95 -0
  99. package/dist/mcp/builtin/globals/findVersionsTool.js.map +1 -0
  100. package/dist/mcp/builtin/globals/getGlobalSchemaTool.d.ts +2 -0
  101. package/dist/mcp/builtin/globals/getGlobalSchemaTool.d.ts.map +1 -0
  102. package/dist/mcp/builtin/globals/getGlobalSchemaTool.js +47 -0
  103. package/dist/mcp/builtin/globals/getGlobalSchemaTool.js.map +1 -0
  104. package/dist/mcp/builtin/globals/restoreVersionTool.d.ts +2 -0
  105. package/dist/mcp/builtin/globals/restoreVersionTool.d.ts.map +1 -0
  106. package/dist/mcp/builtin/globals/restoreVersionTool.js +78 -0
  107. package/dist/mcp/builtin/globals/restoreVersionTool.js.map +1 -0
  108. package/dist/mcp/builtin/globals/updateTool.d.ts.map +1 -1
  109. package/dist/mcp/builtin/globals/updateTool.js +31 -40
  110. package/dist/mcp/builtin/globals/updateTool.js.map +1 -1
  111. package/dist/mcp/builtin/validateEntityData.d.ts +14 -0
  112. package/dist/mcp/builtin/validateEntityData.d.ts.map +1 -0
  113. package/dist/mcp/builtin/validateEntityData.js +82 -0
  114. package/dist/mcp/builtin/validateEntityData.js.map +1 -0
  115. package/dist/mcp/builtinTools.d.ts +144 -19
  116. package/dist/mcp/builtinTools.d.ts.map +1 -1
  117. package/dist/mcp/builtinTools.js +119 -20
  118. package/dist/mcp/builtinTools.js.map +1 -1
  119. package/dist/mcp/sanitizeMCPConfig.d.ts +0 -1
  120. package/dist/mcp/sanitizeMCPConfig.d.ts.map +1 -1
  121. package/dist/mcp/sanitizeMCPConfig.js +120 -58
  122. package/dist/mcp/sanitizeMCPConfig.js.map +1 -1
  123. package/dist/stdio.d.ts +3 -2
  124. package/dist/stdio.d.ts.map +1 -1
  125. package/dist/stdio.js +27 -8
  126. package/dist/stdio.js.map +1 -1
  127. package/dist/types.d.ts +62 -108
  128. package/dist/types.d.ts.map +1 -1
  129. package/dist/types.js +4 -4
  130. package/dist/types.js.map +1 -1
  131. package/dist/utils/getPluginConfig.d.ts +1 -1
  132. package/dist/utils/getPluginConfig.js +1 -1
  133. package/dist/utils/getPluginConfig.js.map +1 -1
  134. package/dist/utils/schemaConversion/getEntityInputSchema.d.ts +11 -0
  135. package/dist/utils/schemaConversion/getEntityInputSchema.d.ts.map +1 -0
  136. package/dist/utils/schemaConversion/getEntityInputSchema.js +32 -0
  137. package/dist/utils/schemaConversion/getEntityInputSchema.js.map +1 -0
  138. package/dist/utils/schemaConversion/sanitizeEntitySchema.d.ts +5 -10
  139. package/dist/utils/schemaConversion/sanitizeEntitySchema.d.ts.map +1 -1
  140. package/dist/utils/schemaConversion/sanitizeEntitySchema.js +15 -95
  141. package/dist/utils/schemaConversion/sanitizeEntitySchema.js.map +1 -1
  142. package/dist/utils/schemaConversion/sanitizeEntitySchema.spec.js +15 -40
  143. package/dist/utils/schemaConversion/sanitizeEntitySchema.spec.js.map +1 -1
  144. package/dist/utils/whereSchema.d.ts +9 -0
  145. package/dist/utils/whereSchema.d.ts.map +1 -0
  146. package/dist/utils/whereSchema.js +13 -0
  147. package/dist/utils/whereSchema.js.map +1 -0
  148. package/package.json +8 -11
  149. package/src/defaultAccess.ts +3 -0
  150. package/src/endpoint/access.ts +88 -101
  151. package/src/endpoint/index.ts +14 -1
  152. package/src/exports/client.ts +2 -1
  153. package/src/exports/internal.ts +1 -0
  154. package/src/index.ts +8 -16
  155. package/src/mcp/buildMcpServer.ts +131 -96
  156. package/src/mcp/builtin/collections/authTools.ts +45 -9
  157. package/src/mcp/builtin/collections/countTool.ts +74 -0
  158. package/src/mcp/builtin/collections/countVersionsTool.ts +70 -0
  159. package/src/mcp/builtin/collections/createTool.ts +55 -59
  160. package/src/mcp/builtin/collections/deleteTool.ts +19 -16
  161. package/src/mcp/builtin/collections/duplicateTool.ts +135 -0
  162. package/src/mcp/builtin/collections/findDistinctTool.ts +120 -0
  163. package/src/mcp/builtin/collections/findTool.ts +63 -33
  164. package/src/mcp/builtin/collections/findVersionByIDTool.ts +110 -0
  165. package/src/mcp/builtin/collections/findVersionsTool.ts +155 -0
  166. package/src/mcp/builtin/collections/formatCollectionError.ts +84 -0
  167. package/src/mcp/builtin/collections/getCollectionSchemaTool.ts +46 -0
  168. package/src/mcp/builtin/collections/restoreVersionTool.ts +108 -0
  169. package/src/mcp/builtin/collections/updateTool.ts +111 -110
  170. package/src/mcp/builtin/getConfigInfoTool.ts +69 -0
  171. package/src/mcp/builtin/globals/countVersionsTool.ts +69 -0
  172. package/src/mcp/builtin/globals/findTool.ts +26 -17
  173. package/src/mcp/builtin/globals/findVersionByIDTool.ts +104 -0
  174. package/src/mcp/builtin/globals/findVersionsTool.ts +148 -0
  175. package/src/mcp/builtin/globals/getGlobalSchemaTool.ts +41 -0
  176. package/src/mcp/builtin/globals/restoreVersionTool.ts +100 -0
  177. package/src/mcp/builtin/globals/updateTool.ts +50 -55
  178. package/src/mcp/builtin/validateEntityData.ts +132 -0
  179. package/src/mcp/builtinTools.ts +111 -41
  180. package/src/mcp/sanitizeMCPConfig.ts +116 -78
  181. package/src/stdio.ts +23 -8
  182. package/src/types.ts +77 -112
  183. package/src/utils/getPluginConfig.ts +1 -1
  184. package/src/utils/schemaConversion/getEntityInputSchema.ts +62 -0
  185. package/src/utils/schemaConversion/sanitizeEntitySchema.spec.ts +12 -19
  186. package/src/utils/schemaConversion/sanitizeEntitySchema.ts +17 -114
  187. package/src/utils/whereSchema.ts +24 -0
  188. package/dist/collection/getAccessField.d.ts +0 -12
  189. package/dist/collection/getAccessField.d.ts.map +0 -1
  190. package/dist/collection/getAccessField.js +0 -57
  191. package/dist/collection/getAccessField.js.map +0 -1
  192. package/dist/collection/index.d.ts +0 -6
  193. package/dist/collection/index.d.ts.map +0 -1
  194. package/dist/collection/index.js +0 -60
  195. package/dist/collection/index.js.map +0 -1
  196. package/dist/components/AccessField/index.client.d.ts +0 -10
  197. package/dist/components/AccessField/index.client.d.ts.map +0 -1
  198. package/dist/components/AccessField/index.client.js +0 -305
  199. package/dist/components/AccessField/index.client.js.map +0 -1
  200. package/dist/components/AccessField/index.css +0 -93
  201. package/dist/utils/schemaConversion/buildToolInput.d.ts +0 -29
  202. package/dist/utils/schemaConversion/buildToolInput.d.ts.map +0 -1
  203. package/dist/utils/schemaConversion/buildToolInput.js +0 -51
  204. package/dist/utils/schemaConversion/buildToolInput.js.map +0 -1
  205. package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.d.ts +0 -7
  206. package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.d.ts.map +0 -1
  207. package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.js +0 -20
  208. package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.js.map +0 -1
  209. package/src/collection/getAccessField.ts +0 -64
  210. package/src/collection/index.ts +0 -64
  211. package/src/components/AccessField/index.client.tsx +0 -344
  212. package/src/components/AccessField/index.css +0 -93
  213. package/src/utils/schemaConversion/buildToolInput.ts +0 -68
  214. package/src/utils/schemaConversion/removeVirtualFieldsFromSchema.ts +0 -27
package/src/types.ts CHANGED
@@ -6,18 +6,19 @@ import type {
6
6
  ResourceTemplate,
7
7
  ServerContext,
8
8
  StandardSchemaWithJSON,
9
+ ToolAnnotations,
9
10
  } from '@modelcontextprotocol/server'
10
11
  import type {
11
12
  AuthCollectionSlug,
12
- CollectionConfig,
13
13
  CollectionSlug,
14
- DefaultDocumentIDType,
15
14
  GlobalSlug,
16
15
  MaybePromise,
17
16
  PayloadRequest,
17
+ SanitizedPermissions,
18
18
  TypedUser,
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,8 +27,8 @@ 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
@@ -35,25 +36,6 @@ export type { JsonSchemaType, StandardSchemaWithJSON }
35
36
  */
36
37
  export type ToolInputSchema = JsonSchemaType | StandardSchemaWithJSON
37
38
 
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
39
  export type MCPToolResponse = {
58
40
  content: ContentBlock[]
59
41
  /**
@@ -80,6 +62,19 @@ export type TypedInput<TSchema> = TSchema extends StandardSchemaWithJSON
80
62
  : StandardSchemaWithJSON.InferOutput<TSchema>
81
63
  : Record<string, unknown>
82
64
 
65
+ export type MCPAccessArgs = {
66
+ permissions?: SanitizedPermissions
67
+ req: PayloadRequest
68
+ }
69
+
70
+ export type CollectionMCPAccessArgs = {
71
+ collectionSlug: CollectionSlug
72
+ } & MCPAccessArgs
73
+
74
+ export type GlobalMCPAccessArgs = {
75
+ globalSlug: GlobalSlug
76
+ } & MCPAccessArgs
77
+
83
78
  export type ToolHandlerArgs<TSchema = undefined> = {
84
79
  authorizedMCP: AuthorizedMCP
85
80
  input: TypedInput<TSchema>
@@ -96,6 +91,8 @@ export type GlobalToolHandlerArgs<TSchema = undefined> = {
96
91
  } & ToolHandlerArgs<TSchema>
97
92
 
98
93
  export type Tool<TSchema extends ToolInputSchema | undefined = ToolInputSchema | undefined> = {
94
+ access?: (args: MCPAccessArgs) => MaybePromise<boolean>
95
+ annotations?: ToolAnnotations
99
96
  description: string
100
97
  handler: (args: ToolHandlerArgs<TSchema>) => MaybePromise<MCPToolResponse>
101
98
  input?: TSchema
@@ -105,30 +102,37 @@ export type Tool<TSchema extends ToolInputSchema | undefined = ToolInputSchema |
105
102
  overrideResponse?: MCPResponseOverride
106
103
  }
107
104
 
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
105
  export type CollectionTool<
114
106
  TSchema extends ToolInputSchema | undefined = ToolInputSchema | undefined,
115
107
  > = {
108
+ access?: (args: CollectionMCPAccessArgs) => MaybePromise<boolean>
116
109
  handler: (args: CollectionToolHandlerArgs<TSchema>) => MaybePromise<MCPToolResponse>
117
- input?: ((args: { collectionSchema: JsonSchemaType }) => TSchema) | TSchema
118
- } & Pick<Tool, 'description' | 'overrideResponse'>
110
+ input?: TSchema
111
+ } & Pick<Tool, 'annotations' | 'description' | 'overrideResponse'>
119
112
 
120
113
  export type GlobalTool<TSchema extends ToolInputSchema | undefined = ToolInputSchema | undefined> =
121
114
  {
115
+ access?: (args: GlobalMCPAccessArgs) => MaybePromise<boolean>
122
116
  handler: (args: GlobalToolHandlerArgs<TSchema>) => MaybePromise<MCPToolResponse>
123
- input?: ((args: { globalSchema: JsonSchemaType }) => TSchema) | TSchema
124
- } & Pick<Tool, 'description' | 'overrideResponse'>
117
+ input?: TSchema
118
+ } & Pick<Tool, 'annotations' | 'description' | 'overrideResponse'>
125
119
 
126
120
  /**
127
121
  * Configures (or disables) a built-in tool without replacing it.
128
122
  * `handler?: never` prevents a full `CollectionTool`/`GlobalTool` (which has a
129
123
  * required handler) from being silently accepted at a built-in key slot.
130
124
  */
131
- export type MCPBuiltInToolOverride = {
125
+ export type MCPBuiltInCollectionToolOverride = {
126
+ access?: (args: CollectionMCPAccessArgs) => MaybePromise<boolean>
127
+ annotations?: ToolAnnotations
128
+ description?: string
129
+ handler?: never
130
+ overrideResponse?: MCPResponseOverride
131
+ }
132
+
133
+ export type MCPBuiltInGlobalToolOverride = {
134
+ access?: (args: GlobalMCPAccessArgs) => MaybePromise<boolean>
135
+ annotations?: ToolAnnotations
132
136
  description?: string
133
137
  handler?: never
134
138
  overrideResponse?: MCPResponseOverride
@@ -142,13 +146,13 @@ export type MCPBuiltInToolOverride = {
142
146
  export type MCPTopLevelToolEntry = Tool
143
147
 
144
148
  export type MCPCollectionToolsMap = {
145
- [customToolName: string]: boolean | CollectionTool | MCPBuiltInToolOverride | undefined
149
+ [customToolName: string]: boolean | CollectionTool | MCPBuiltInCollectionToolOverride | undefined
146
150
  } & {
147
- [K in MCPCollectionBuiltinName]?: false | MCPBuiltInToolOverride
151
+ [K in MCPCollectionBuiltinName]?: false | MCPBuiltInCollectionToolOverride
148
152
  }
149
153
 
150
154
  export type MCPAuthCollectionToolsMap = {
151
- [K in MCPCollectionAuthToolName]?: MCPBuiltInToolOverride | true
155
+ [K in MCPCollectionAuthToolName]?: MCPBuiltInCollectionToolOverride | true
152
156
  } & MCPCollectionToolsMap
153
157
 
154
158
  /** Auth-enabled collections get auth-tool name autocomplete; others get CRUD-only. */
@@ -157,9 +161,9 @@ export type MCPToolsMapForCollection<Slug extends CollectionSlug> = Slug extends
157
161
  : MCPCollectionToolsMap
158
162
 
159
163
  export type MCPGlobalToolsMap = {
160
- [customToolName: string]: boolean | GlobalTool | MCPBuiltInToolOverride | undefined
164
+ [customToolName: string]: boolean | GlobalTool | MCPBuiltInGlobalToolOverride | undefined
161
165
  } & {
162
- [K in MCPGlobalBuiltinName]?: false | MCPBuiltInToolOverride
166
+ [K in MCPGlobalBuiltinName]?: false | MCPBuiltInGlobalToolOverride
163
167
  }
164
168
 
165
169
  export type MCPTopLevelToolsMap = Record<string, Tool>
@@ -171,6 +175,7 @@ export type PromptHandlerArgs<TSchema = undefined> = {
171
175
  }
172
176
 
173
177
  export type Prompt<TSchema extends ToolInputSchema = ToolInputSchema> = {
178
+ access?: (args: MCPAccessArgs) => MaybePromise<boolean>
174
179
  argsSchema: TSchema
175
180
  description: string
176
181
  handler: (args: PromptHandlerArgs<TSchema>) => MaybePromise<{
@@ -188,6 +193,7 @@ export type ResourceHandlerArgs = {
188
193
  }
189
194
 
190
195
  export type Resource = {
196
+ access?: (args: MCPAccessArgs) => MaybePromise<boolean>
191
197
  description: string
192
198
  handler: (args: ResourceHandlerArgs) => MaybePromise<{
193
199
  contents: Array<{ text: string; uri: string }>
@@ -221,7 +227,7 @@ export type MCPPluginConfig = {
221
227
  collections?: {
222
228
  [Slug in CollectionSlug]?: MCPPluginCollectionConfig<Slug>
223
229
  }
224
- /** Skip MCP registration. The API key collection is still added (so DB / types stay stable). */
230
+ /** Skip MCP endpoint registration. */
225
231
  disabled?: boolean
226
232
  globals?: {
227
233
  [Slug in GlobalSlug]?: MCPPluginGlobalConfig
@@ -230,25 +236,21 @@ export type MCPPluginConfig = {
230
236
  serverOptions?: MCPServerOptions
231
237
  verboseLogs?: boolean
232
238
  }
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>
239
+ /** Replace the default MCP authorization resolver. */
240
+ overrideGetAuthorizedMCP?: (
241
+ args: {
242
+ pluginConfig: SanitizedMCPPluginConfig
243
+ } & GetAuthorizedMCPArgs,
244
+ ) => MaybePromise<AuthorizedMCP>
241
245
  prompts?: Record<string, Prompt>
242
246
  resources?: Record<string, Resource>
243
247
  /** Cross-cutting tools (not scoped to any collection or global). */
244
248
  tools?: MCPTopLevelToolsMap
245
- userCollection?: CollectionSlug
246
249
  }
247
250
 
248
251
  export type SanitizedMCPPluginConfig = {
249
252
  items: MCPItem[]
250
- userCollection: CollectionSlug
251
- } & Pick<MCPPluginConfig, 'disabled' | 'mcp' | 'overrideApiKeyCollection' | 'overrideAuth'>
253
+ } & Pick<MCPPluginConfig, 'disabled' | 'mcp' | 'overrideGetAuthorizedMCP'>
252
254
 
253
255
  export type MCPServerOptions = {
254
256
  options?: ConstructorParameters<typeof McpServer>[1]
@@ -256,71 +258,32 @@ export type MCPServerOptions = {
256
258
  }
257
259
 
258
260
  /**
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
293
- }
294
-
295
- /**
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.
261
+ * One MCP primitive plus the metadata needed for config-driven filtering and
262
+ * registration.
299
263
  *
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).
264
+ * - `configKey`: the config identifier, e.g. `find` or `echo`.
265
+ * - `mcpName`: the MCP wire name, e.g. `findDocuments` or `echo`.
266
+ * - `label`: human-readable display text.
307
267
  */
308
268
  export type MCPItemBase = {
309
- key: string
269
+ configKey: string
310
270
  label: string
271
+ mcpName: string
311
272
  }
312
273
 
274
+ export type CollectionMCPItem = {
275
+ collectionSlug: CollectionSlug
276
+ tool: CollectionTool
277
+ type: 'collectionTool'
278
+ } & MCPItemBase
279
+
280
+ export type GlobalMCPItem = {
281
+ globalSlug: GlobalSlug
282
+ tool: GlobalTool
283
+ type: 'globalTool'
284
+ } & MCPItemBase
285
+
313
286
  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
287
  | ({
325
288
  prompt: Prompt
326
289
  type: 'prompt'
@@ -333,12 +296,14 @@ export type MCPItem =
333
296
  tool: Tool
334
297
  type: 'tool'
335
298
  } & MCPItemBase)
299
+ | CollectionMCPItem
300
+ | GlobalMCPItem
336
301
 
337
302
  /**
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.
303
+ * The caller's identity + the MCP items authorized for this request. Disabled
304
+ * items and items blocked by access callbacks or Payload operation access are
305
+ * absent from `items`. Tool handlers receive this via `args.authorizedMCP` so
306
+ * they can spread `localAPIDefaults(authorizedMCP)` into every local API call.
342
307
  */
343
308
  export type AuthorizedMCP = {
344
309
  items: MCPItem[]
@@ -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 = ({
@@ -0,0 +1,62 @@
1
+ import {
2
+ type CollectionSlug,
3
+ entityToStandaloneJSONSchema,
4
+ type GlobalSlug,
5
+ type PayloadRequest,
6
+ type SanitizedCollectionConfig,
7
+ type SanitizedGlobalConfig,
8
+ } from 'payload'
9
+
10
+ import type { JsonSchemaType } from '../../types.js'
11
+
12
+ import { sanitizeEntitySchema } from './sanitizeEntitySchema.js'
13
+
14
+ export const getCollectionInputSchema = ({
15
+ collectionSlug,
16
+ req,
17
+ }: {
18
+ collectionSlug: CollectionSlug
19
+ req: PayloadRequest
20
+ }): JsonSchemaType | null => {
21
+ const collection = req.payload.collections[collectionSlug]?.config
22
+
23
+ if (!collection) {
24
+ return null
25
+ }
26
+
27
+ return buildEntityInputSchema({ entity: collection, req })
28
+ }
29
+
30
+ export const getGlobalInputSchema = ({
31
+ globalSlug,
32
+ req,
33
+ }: {
34
+ globalSlug: GlobalSlug
35
+ req: PayloadRequest
36
+ }): JsonSchemaType | null => {
37
+ const global = req.payload.config.globals.find((globalConfig) => globalConfig.slug === globalSlug)
38
+
39
+ if (!global) {
40
+ return null
41
+ }
42
+
43
+ return buildEntityInputSchema({ entity: global, req })
44
+ }
45
+
46
+ const buildEntityInputSchema = ({
47
+ entity,
48
+ req,
49
+ }: {
50
+ entity: SanitizedCollectionConfig | SanitizedGlobalConfig
51
+ req: PayloadRequest
52
+ }): JsonSchemaType =>
53
+ // The `input` variant is already the write shape; sanitizeEntitySchema only adds MCP-specific passes.
54
+ sanitizeEntitySchema(
55
+ entityToStandaloneJSONSchema({
56
+ config: req.payload.config,
57
+ defaultIDType: req.payload.db.defaultIDType,
58
+ entity,
59
+ i18n: req.i18n,
60
+ variant: 'input',
61
+ }) as unknown as JsonSchemaType,
62
+ )
@@ -5,10 +5,11 @@ import type { JsonSchemaType } from '../../types.js'
5
5
  import { sanitizeEntitySchema } from './sanitizeEntitySchema.js'
6
6
 
7
7
  describe('sanitizeEntitySchema', () => {
8
- it('keeps a Lexical node union strict (oneOf) while simplifying relationship values to IDs', () => {
9
- // Shaped like the schema the MCP server prepares for a collection with a Lexical richText field:
10
- // a `$defs` node union (a `oneOf` of node shapes) where a relationship node's `value` is either
11
- // an ID or a `$ref` to the populated related collection.
8
+ it('renames the Lexical node-union definition to a short name and keeps it a strict oneOf', () => {
9
+ // Shaped like the schema the MCP server prepares for a collection with a Lexical richText field
10
+ // (Payload's `input` variant): a `$defs` node union (a `oneOf` of node shapes). The input variant
11
+ // already types the relationship node's `value` as a bare ID - there is no populated-doc `$ref` to
12
+ // reduce here (the variant did that upstream), so sanitize leaves the value untouched.
12
13
  const standalone: JsonSchemaType = {
13
14
  type: 'object',
14
15
  $defs: {
@@ -25,17 +26,12 @@ describe('sanitizeEntitySchema', () => {
25
26
  additionalProperties: false,
26
27
  properties: {
27
28
  type: { const: 'relationship' },
28
- value: { oneOf: [{ type: 'string' }, { $ref: '#/$defs/posts' }] },
29
+ value: { type: 'string', description: 'The related document ID.' },
29
30
  },
30
31
  required: ['type'],
31
32
  },
32
33
  ],
33
34
  },
34
- posts: {
35
- type: 'object',
36
- additionalProperties: false,
37
- properties: { id: { type: 'string' }, title: { type: 'string' } },
38
- },
39
35
  },
40
36
  properties: {
41
37
  id: { type: 'string' },
@@ -57,7 +53,7 @@ describe('sanitizeEntitySchema', () => {
57
53
  type: 'object',
58
54
  $defs: {
59
55
  // The node union is renamed to a short, readable `node`, and stays a strict discriminated
60
- // `oneOf` - not loosened to `anyOf`...
56
+ // `oneOf` - not loosened to `anyOf`.
61
57
  node: {
62
58
  oneOf: [
63
59
  {
@@ -71,21 +67,18 @@ describe('sanitizeEntitySchema', () => {
71
67
  additionalProperties: false,
72
68
  properties: {
73
69
  type: { const: 'relationship' },
74
- // ...while the relationship `value` is simplified to a bare ID (the populated-doc `$ref` is dropped).
75
- value: { type: 'string', description: 'The ID of the related "posts" document.' },
70
+ // The value is already ID-only from the input variant - left as-is.
71
+ value: { type: 'string', description: 'The related document ID.' },
76
72
  },
77
73
  required: ['type'],
78
74
  },
79
75
  ],
80
76
  },
81
- posts: {
82
- type: 'object',
83
- additionalProperties: false,
84
- properties: { id: { type: 'string' }, title: { type: 'string' } },
85
- },
86
77
  },
87
78
  properties: {
88
- // `id` is dropped - it's a Payload-managed field MCP clients never set.
79
+ // Managed fields (createdAt/updatedAt/_status) are excluded upstream by the `input` variant;
80
+ // `id` stays because it's a valid optional input (a client may supply a custom ID).
81
+ id: { type: 'string' },
89
82
  content: {
90
83
  type: 'object',
91
84
  properties: {
@@ -1,30 +1,22 @@
1
1
  import type { JsonSchemaType } from '../../types.js'
2
2
 
3
3
  /**
4
- * Turns the JSON Schema that Payload generates for a collection or global into the input schema for
5
- * an MCP create/update tool. In short, it:
6
- *
7
- * - drops fields a client can't set (`id`, `createdAt`, the draft `_status`, …),
8
- * - rewrites Payload-specific field shapes (points, relationships) into plain JSON the model can fill,
9
- * - and shrinks the result so it's cheaper for the model to read,
10
- *
11
- * while keeping every node valid JSON Schema draft 2020-12. Each step below is tagged with why it runs -
12
- * **Correctness** (valid input the API accepts), **Size** (equivalence-preserving shrink), or **LLM
13
- * ergonomics** (easier for the model to read/fill) - and carries a before/after example on its definition.
4
+ * Refines Payload's `input`-variant JSON Schema into an MCP create/update tool's input schema. The
5
+ * variant already covers correctness (ID-only relationships, no managed/virtual/join fields); the
6
+ * passes here are MCP-specific - reshaping fields for the model and shrinking the result - and keep
7
+ * every node valid JSON Schema draft 2020-12. Each is tagged **Correctness**, **Size**, or **LLM
8
+ * ergonomics**, with a before/after example on its definition.
14
9
  */
15
10
  export const sanitizeEntitySchema = (schema: JsonSchemaType): JsonSchemaType => {
16
11
  // Work on a copy — the caller reuses the original schema elsewhere (e.g. when listing tools).
17
12
  let result = structuredClone(schema)
18
13
 
19
- // Correctness — drop the fields a client can't set (id, createdAt, updatedAt, draft _status) + collapse nullable types.
20
- result = removeManagedFields(result)
14
+ // LLM ergonomics — drop the redundant `'null'` from optional arrays/objects (`['array','null']` `'array'`).
15
+ result = collapseOptionalContainerTypes(result)
21
16
 
22
17
  // LLM ergonomics — rewrite point fields from a `[number, number]` tuple into a `{ longitude, latitude }` object.
23
18
  result = pointFieldsToObjects(result)
24
19
 
25
- // Correctness — a relationship value can be an ID or a populated doc; on input only the ID is valid, so keep that.
26
- result = relationshipsToIds(result)
27
-
28
20
  // Size — strip inert type-gen leftovers that only bloat the schema (`tsType`, block-collision notes).
29
21
  result = removeTypeGenArtifacts(result)
30
22
 
@@ -58,26 +50,20 @@ const mapNodes = (node: unknown, visit: (node: JsonSchemaType) => JsonSchemaType
58
50
  for (const [key, value] of Object.entries(node)) {
59
51
  out[key] = mapNodes(value, visit)
60
52
  }
61
- return visit(out as JsonSchemaType)
53
+ return visit(out)
62
54
  }
63
55
 
64
- // Payload sets these on every document, so an MCP client never provides them when creating/updating.
65
- const PAYLOAD_MANAGED_FIELDS = new Set(['_status', 'createdAt', 'id', 'updatedAt'])
66
-
67
56
  /**
68
- * **Correctness.** Removes the fields a client can't set on create/update - `id`, `createdAt`, `updatedAt`, and the draft
69
- * `_status` - from every field object's `properties` and `required` (recursing into nested objects and
70
- * array items). Along the way it also collapses optional array/object types via {@link collapseNullableType}.
57
+ * **LLM ergonomics.** Collapses optional array/object types (`['array', 'null']` `'array'`) via
58
+ * {@link collapseNullableType}, recursing through `properties` and array items. The `required` list
59
+ * already conveys optionality, so the `'null'` is redundant noise for the model.
71
60
  *
72
61
  * @example
73
- * { properties: { id: { type: 'string' }, tags: { type: ['array', 'null'], items: {} } }, required: ['id', 'tags'] }
74
- * → { properties: { tags: { type: 'array', items: {} } }, required: ['tags'] }
62
+ * { properties: { tags: { type: ['array', 'null'], items: {} } }, required: [] }
63
+ * → { properties: { tags: { type: 'array', items: {} } }, required: [] }
75
64
  */
76
- const removeManagedFields = (schema: JsonSchemaType): JsonSchemaType => {
65
+ const collapseOptionalContainerTypes = (schema: JsonSchemaType): JsonSchemaType => {
77
66
  if (schema.properties && typeof schema.properties === 'object') {
78
- for (const field of PAYLOAD_MANAGED_FIELDS) {
79
- delete schema.properties[field]
80
- }
81
67
  for (const key of Object.keys(schema.properties)) {
82
68
  const prop = schema.properties[key] as JsonSchemaType
83
69
  if (!prop || typeof prop !== 'object') {
@@ -86,21 +72,14 @@ const removeManagedFields = (schema: JsonSchemaType): JsonSchemaType => {
86
72
  const isRequired = Array.isArray(schema.required) && schema.required.includes(key)
87
73
  collapseNullableType(prop, isRequired)
88
74
  if (prop.properties) {
89
- removeManagedFields(prop)
75
+ collapseOptionalContainerTypes(prop)
90
76
  }
91
77
  if (prop.items && typeof prop.items === 'object' && !Array.isArray(prop.items)) {
92
- removeManagedFields(prop.items)
78
+ collapseOptionalContainerTypes(prop.items)
93
79
  }
94
80
  }
95
81
  }
96
82
 
97
- if (Array.isArray(schema.required)) {
98
- schema.required = schema.required.filter((name) => !PAYLOAD_MANAGED_FIELDS.has(name))
99
- if (schema.required.length === 0) {
100
- delete schema.required
101
- }
102
- }
103
-
104
83
  return schema
105
84
  }
106
85
 
@@ -193,82 +172,6 @@ const pointFieldsToObjects = (schema: JsonSchemaType): JsonSchemaType => {
193
172
  return transformed
194
173
  }
195
174
 
196
- /**
197
- * **Correctness.** Reduces relationship/upload fields to the IDs a client actually sends. Payload types the value as
198
- * "an ID or the full related document" - but the populated-document form only appears in read responses;
199
- * on create/update you always reference a relationship by its ID. So we drop that `$ref` option, leaving
200
- * the bare ID. A single remaining target collapses inline (with a description naming the target
201
- * collection); several targets become an `anyOf` of IDs.
202
- *
203
- * @example
204
- * { oneOf: [{ type: 'string' }, { $ref: '#/$defs/posts' }] }
205
- * → { type: 'string', description: 'The ID of the related "posts" document.' }
206
- */
207
- const relationshipsToIds = (schema: JsonSchemaType): JsonSchemaType => {
208
- if (!schema || typeof schema !== 'object') {
209
- return schema
210
- }
211
-
212
- const processed = { ...schema }
213
-
214
- if (Array.isArray(processed.oneOf)) {
215
- const isRelatedDocRef = (option: unknown): option is { $ref: string } =>
216
- !!option && typeof option === 'object' && '$ref' in option
217
-
218
- if (processed.oneOf.some(isRelatedDocRef)) {
219
- // A relationship value is "an ID, or the populated related document". Keep the ID option(s) and
220
- // drop the `$ref`s to the related collections, since a client only ever sends the ID.
221
- const idOptions = processed.oneOf
222
- .filter((option) => !isRelatedDocRef(option))
223
- .map((option) => (typeof option === 'object' ? relationshipsToIds(option) : option))
224
- const targetCollections = processed.oneOf
225
- .filter(isRelatedDocRef)
226
- .map((option) => option.$ref.replace('#/$defs/', ''))
227
-
228
- if (idOptions.length === 1) {
229
- delete processed.oneOf
230
- Object.assign(processed, idOptions[0])
231
- if (targetCollections.length > 0 && !processed.description) {
232
- processed.description = `The ID of the related "${targetCollections.join('" or "')}" document.`
233
- }
234
- } else if (idOptions.length > 1) {
235
- delete processed.oneOf
236
- processed.anyOf = idOptions
237
- }
238
- } else {
239
- processed.oneOf = processed.oneOf.map((option) =>
240
- typeof option === 'object' ? relationshipsToIds(option) : option,
241
- )
242
- }
243
- }
244
-
245
- if (processed.properties && typeof processed.properties === 'object') {
246
- processed.properties = Object.fromEntries(
247
- Object.entries(processed.properties).map(([key, value]) => [
248
- key,
249
- typeof value === 'object' ? relationshipsToIds(value) : value,
250
- ]),
251
- )
252
- }
253
-
254
- if (processed.items && typeof processed.items === 'object' && !Array.isArray(processed.items)) {
255
- processed.items = relationshipsToIds(processed.items)
256
- }
257
-
258
- // Lexical node unions and blocks live under `$defs` and have their own relationship fields, so walk
259
- // those too — otherwise their `$ref`s would dangle once we don't bundle the related collections.
260
- if (processed.$defs && typeof processed.$defs === 'object') {
261
- processed.$defs = Object.fromEntries(
262
- Object.entries(processed.$defs).map(([key, value]) => [
263
- key,
264
- typeof value === 'object' ? relationshipsToIds(value) : value,
265
- ]),
266
- )
267
- }
268
-
269
- return processed
270
- }
271
-
272
175
  /**
273
176
  * **Size.** Strips type-generation leftovers that bloat the schema without helping the model: the `tsType`
274
177
  * hint (a `json-schema-to-typescript` extension; JSON Schema validators just ignore it, they don't reject it)
@@ -448,7 +351,7 @@ const deduplicateIntoDefinitions = (schema: JsonSchemaType): JsonSchemaType => {
448
351
  if (!name) {
449
352
  name = `shared_${nameByKey.size}`
450
353
  nameByKey.set(key, name)
451
- sharedEntries[name] = node as JsonSchemaType
354
+ sharedEntries[name] = node
452
355
  }
453
356
  return { $ref: `#/$defs/${name}` }
454
357
  }