@payloadcms/plugin-mcp 3.65.0-canary.6 → 3.65.0-canary.8

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 (39) hide show
  1. package/dist/endpoints/mcp.js.map +1 -1
  2. package/dist/mcp/getMcpHandler.d.ts.map +1 -1
  3. package/dist/mcp/getMcpHandler.js +14 -3
  4. package/dist/mcp/getMcpHandler.js.map +1 -1
  5. package/dist/mcp/helpers/fileValidation.js +22 -22
  6. package/dist/mcp/helpers/fileValidation.js.map +1 -1
  7. package/dist/mcp/tools/resource/create.d.ts.map +1 -1
  8. package/dist/mcp/tools/resource/create.js +22 -6
  9. package/dist/mcp/tools/resource/create.js.map +1 -1
  10. package/dist/mcp/tools/resource/delete.d.ts.map +1 -1
  11. package/dist/mcp/tools/resource/delete.js +11 -5
  12. package/dist/mcp/tools/resource/delete.js.map +1 -1
  13. package/dist/mcp/tools/resource/find.d.ts.map +1 -1
  14. package/dist/mcp/tools/resource/find.js +18 -6
  15. package/dist/mcp/tools/resource/find.js.map +1 -1
  16. package/dist/mcp/tools/resource/update.d.ts.map +1 -1
  17. package/dist/mcp/tools/resource/update.js +24 -6
  18. package/dist/mcp/tools/resource/update.js.map +1 -1
  19. package/dist/mcp/tools/schemas.d.ts +33 -9
  20. package/dist/mcp/tools/schemas.d.ts.map +1 -1
  21. package/dist/mcp/tools/schemas.js +21 -4
  22. package/dist/mcp/tools/schemas.js.map +1 -1
  23. package/dist/types.d.ts +39 -4
  24. package/dist/types.d.ts.map +1 -1
  25. package/dist/types.js.map +1 -1
  26. package/dist/utils/convertCollectionSchemaToZod.d.ts.map +1 -1
  27. package/dist/utils/convertCollectionSchemaToZod.js +1 -2
  28. package/dist/utils/convertCollectionSchemaToZod.js.map +1 -1
  29. package/package.json +3 -3
  30. package/src/endpoints/mcp.ts +1 -1
  31. package/src/mcp/getMcpHandler.ts +31 -4
  32. package/src/mcp/helpers/fileValidation.ts +22 -22
  33. package/src/mcp/tools/resource/create.ts +40 -4
  34. package/src/mcp/tools/resource/delete.ts +8 -4
  35. package/src/mcp/tools/resource/find.ts +10 -4
  36. package/src/mcp/tools/resource/update.ts +26 -5
  37. package/src/mcp/tools/schemas.ts +49 -3
  38. package/src/types.ts +58 -9
  39. package/src/utils/convertCollectionSchemaToZod.ts +1 -2
@@ -2,6 +2,8 @@ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
2
  import type { JSONSchema4 } from 'json-schema'
3
3
  import type { PayloadRequest, TypedUser } from 'payload'
4
4
 
5
+ import { z } from 'zod'
6
+
5
7
  import type { PluginMCPServerConfig } from '../../../types.js'
6
8
 
7
9
  import { toCamelCase } from '../../../utils/camelCase.js'
@@ -18,6 +20,9 @@ export const createResourceTool = (
18
20
  ) => {
19
21
  const tool = async (
20
22
  data: string,
23
+ draft: boolean,
24
+ locale?: string,
25
+ fallbackLocale?: string,
21
26
  ): Promise<{
22
27
  content: Array<{
23
28
  text: string
@@ -27,7 +32,9 @@ export const createResourceTool = (
27
32
  const payload = req.payload
28
33
 
29
34
  if (verboseLogs) {
30
- payload.logger.info(`[payload-mcp] Creating resource in collection: ${collectionSlug}`)
35
+ payload.logger.info(
36
+ `[payload-mcp] Creating resource in collection: ${collectionSlug}${locale ? ` with locale: ${locale}` : ''}`,
37
+ )
31
38
  }
32
39
 
33
40
  try {
@@ -51,9 +58,12 @@ export const createResourceTool = (
51
58
  const result = await payload.create({
52
59
  collection: collectionSlug,
53
60
  data: parsedData,
61
+ draft,
54
62
  overrideAccess: false,
55
63
  req,
56
64
  user,
65
+ ...(locale && { locale }),
66
+ ...(fallbackLocale && { fallbackLocale }),
57
67
  })
58
68
 
59
69
  if (verboseLogs) {
@@ -109,13 +119,39 @@ ${JSON.stringify(result, null, 2)}
109
119
  if (collections?.[collectionSlug]?.enabled) {
110
120
  const convertedFields = convertCollectionSchemaToZod(schema)
111
121
 
122
+ // Create a new schema that combines the converted fields with create-specific parameters
123
+ const createResourceSchema = z.object({
124
+ ...convertedFields.shape,
125
+ draft: z
126
+ .boolean()
127
+ .optional()
128
+ .default(false)
129
+ .describe('Whether to create the document as a draft'),
130
+ fallbackLocale: z
131
+ .string()
132
+ .optional()
133
+ .describe('Optional: fallback locale code to use when requested locale is not available'),
134
+ locale: z
135
+ .string()
136
+ .optional()
137
+ .describe(
138
+ 'Optional: locale code to create the document in (e.g., "en", "es"). Defaults to the default locale',
139
+ ),
140
+ })
141
+
112
142
  server.tool(
113
143
  `create${collectionSlug.charAt(0).toUpperCase() + toCamelCase(collectionSlug).slice(1)}`,
114
144
  `${collections?.[collectionSlug]?.description || toolSchemas.createResource.description.trim()}`,
115
- convertedFields.shape,
145
+ createResourceSchema.shape,
116
146
  async (params: Record<string, unknown>) => {
117
- const data = JSON.stringify(params)
118
- return await tool(data)
147
+ const { draft, fallbackLocale, locale, ...fieldData } = params
148
+ const data = JSON.stringify(fieldData)
149
+ return await tool(
150
+ data,
151
+ draft as boolean,
152
+ locale as string | undefined,
153
+ fallbackLocale as string | undefined,
154
+ )
119
155
  },
120
156
  )
121
157
  }
@@ -15,9 +15,11 @@ export const deleteResourceTool = (
15
15
  collections: PluginMCPServerConfig['collections'],
16
16
  ) => {
17
17
  const tool = async (
18
- id?: string,
18
+ id?: number | string,
19
19
  where?: string,
20
20
  depth: number = 0,
21
+ locale?: string,
22
+ fallbackLocale?: string,
21
23
  ): Promise<{
22
24
  content: Array<{
23
25
  text: string
@@ -28,7 +30,7 @@ export const deleteResourceTool = (
28
30
 
29
31
  if (verboseLogs) {
30
32
  payload.logger.info(
31
- `[payload-mcp] Deleting resource from collection: ${collectionSlug}${id ? ` with ID: ${id}` : ' with where clause'}`,
33
+ `[payload-mcp] Deleting resource from collection: ${collectionSlug}${id ? ` with ID: ${id}` : ' with where clause'}${locale ? `, locale: ${locale}` : ''}`,
32
34
  )
33
35
  }
34
36
 
@@ -80,6 +82,8 @@ export const deleteResourceTool = (
80
82
  overrideAccess: false,
81
83
  req,
82
84
  user,
85
+ ...(locale && { locale }),
86
+ ...(fallbackLocale && { fallbackLocale }),
83
87
  }
84
88
 
85
89
  // Delete by ID or where clause
@@ -204,8 +208,8 @@ ${JSON.stringify(errors, null, 2)}
204
208
  `delete${collectionSlug.charAt(0).toUpperCase() + toCamelCase(collectionSlug).slice(1)}`,
205
209
  `${collections?.[collectionSlug]?.description || toolSchemas.deleteResource.description.trim()}`,
206
210
  toolSchemas.deleteResource.parameters.shape,
207
- async ({ id, depth, where }) => {
208
- return await tool(id, where, depth)
211
+ async ({ id, depth, fallbackLocale, locale, where }) => {
212
+ return await tool(id, where, depth, locale, fallbackLocale)
209
213
  },
210
214
  )
211
215
  }
@@ -15,11 +15,13 @@ export const findResourceTool = (
15
15
  collections: PluginMCPServerConfig['collections'],
16
16
  ) => {
17
17
  const tool = async (
18
- id?: string,
18
+ id?: number | string,
19
19
  limit: number = 10,
20
20
  page: number = 1,
21
21
  sort?: string,
22
22
  where?: string,
23
+ locale?: string,
24
+ fallbackLocale?: string,
23
25
  ): Promise<{
24
26
  content: Array<{
25
27
  text: string
@@ -30,7 +32,7 @@ export const findResourceTool = (
30
32
 
31
33
  if (verboseLogs) {
32
34
  payload.logger.info(
33
- `[payload-mcp] Reading resource from collection: ${collectionSlug}${id ? ` with ID: ${id}` : ''}, limit: ${limit}, page: ${page}`,
35
+ `[payload-mcp] Reading resource from collection: ${collectionSlug}${id ? ` with ID: ${id}` : ''}, limit: ${limit}, page: ${page}${locale ? `, locale: ${locale}` : ''}`,
34
36
  )
35
37
  }
36
38
 
@@ -67,6 +69,8 @@ export const findResourceTool = (
67
69
  overrideAccess: false,
68
70
  req,
69
71
  user,
72
+ ...(locale && { locale }),
73
+ ...(fallbackLocale && { fallbackLocale }),
70
74
  })
71
75
 
72
76
  if (verboseLogs) {
@@ -120,6 +124,8 @@ ${JSON.stringify(doc, null, 2)}`,
120
124
  page,
121
125
  req,
122
126
  user,
127
+ ...(locale && { locale }),
128
+ ...(fallbackLocale && { fallbackLocale }),
123
129
  }
124
130
 
125
131
  if (sort) {
@@ -190,8 +196,8 @@ Page: ${result.page} of ${result.totalPages}
190
196
  `find${collectionSlug.charAt(0).toUpperCase() + toCamelCase(collectionSlug).slice(1)}`,
191
197
  `${collections?.[collectionSlug]?.description || toolSchemas.findResources.description.trim()}`,
192
198
  toolSchemas.findResources.parameters.shape,
193
- async ({ id, limit, page, sort, where }) => {
194
- return await tool(id, limit, page, sort, where)
199
+ async ({ id, fallbackLocale, limit, locale, page, sort, where }) => {
200
+ return await tool(id, limit, page, sort, where, locale, fallbackLocale)
195
201
  },
196
202
  )
197
203
  }
@@ -20,13 +20,15 @@ export const updateResourceTool = (
20
20
  ) => {
21
21
  const tool = async (
22
22
  data: string,
23
- id?: string,
23
+ id?: number | string,
24
24
  where?: string,
25
25
  draft: boolean = false,
26
26
  depth: number = 0,
27
27
  overrideLock: boolean = true,
28
28
  filePath?: string,
29
29
  overwriteExistingFiles: boolean = false,
30
+ locale?: string,
31
+ fallbackLocale?: string,
30
32
  ): Promise<{
31
33
  content: Array<{
32
34
  text: string
@@ -37,7 +39,7 @@ export const updateResourceTool = (
37
39
 
38
40
  if (verboseLogs) {
39
41
  payload.logger.info(
40
- `[payload-mcp] Updating resource in collection: ${collectionSlug}${id ? ` with ID: ${id}` : ' with where clause'}, draft: ${draft}`,
42
+ `[payload-mcp] Updating resource in collection: ${collectionSlug}${id ? ` with ID: ${id}` : ' with where clause'}, draft: ${draft}${locale ? `, locale: ${locale}` : ''}`,
41
43
  )
42
44
  }
43
45
 
@@ -120,6 +122,8 @@ export const updateResourceTool = (
120
122
  user,
121
123
  ...(filePath && { filePath }),
122
124
  ...(overwriteExistingFiles && { overwriteExistingFiles }),
125
+ ...(locale && { locale }),
126
+ ...(fallbackLocale && { fallbackLocale }),
123
127
  }
124
128
 
125
129
  if (verboseLogs) {
@@ -168,6 +172,8 @@ ${JSON.stringify(result, null, 2)}
168
172
  where: whereClause,
169
173
  ...(filePath && { filePath }),
170
174
  ...(overwriteExistingFiles && { overwriteExistingFiles }),
175
+ ...(locale && { locale }),
176
+ ...(fallbackLocale && { fallbackLocale }),
171
177
  }
172
178
 
173
179
  if (verboseLogs) {
@@ -255,9 +261,10 @@ ${JSON.stringify(errors, null, 2)}
255
261
  const convertedFields = convertCollectionSchemaToZod(schema)
256
262
 
257
263
  // Create a new schema that combines the converted fields with update-specific parameters
264
+ // Use .partial() to make all fields optional for partial updates
258
265
  const updateResourceSchema = z.object({
259
- ...convertedFields.shape,
260
- id: z.string().optional().describe('The ID of the document to update'),
266
+ ...convertedFields.partial().shape,
267
+ id: z.union([z.string(), z.number()]).optional().describe('The ID of the document to update'),
261
268
  depth: z
262
269
  .number()
263
270
  .optional()
@@ -268,7 +275,17 @@ ${JSON.stringify(errors, null, 2)}
268
275
  .optional()
269
276
  .default(false)
270
277
  .describe('Whether to update the document as a draft'),
278
+ fallbackLocale: z
279
+ .string()
280
+ .optional()
281
+ .describe('Optional: fallback locale code to use when requested locale is not available'),
271
282
  filePath: z.string().optional().describe('File path for file uploads'),
283
+ locale: z
284
+ .string()
285
+ .optional()
286
+ .describe(
287
+ 'Optional: locale code to update the document in (e.g., "en", "es"). Defaults to the default locale',
288
+ ),
272
289
  overrideLock: z
273
290
  .boolean()
274
291
  .optional()
@@ -294,7 +311,9 @@ ${JSON.stringify(errors, null, 2)}
294
311
  id,
295
312
  depth,
296
313
  draft,
314
+ fallbackLocale,
297
315
  filePath,
316
+ locale,
298
317
  overrideLock,
299
318
  overwriteExistingFiles,
300
319
  where,
@@ -304,13 +323,15 @@ ${JSON.stringify(errors, null, 2)}
304
323
  const data = JSON.stringify(fieldData)
305
324
  return await tool(
306
325
  data,
307
- id as string | undefined,
326
+ id as number | string | undefined,
308
327
  where as string | undefined,
309
328
  draft as boolean,
310
329
  depth as number,
311
330
  overrideLock as boolean,
312
331
  filePath as string | undefined,
313
332
  overwriteExistingFiles as boolean,
333
+ locale as string | undefined,
334
+ fallbackLocale as string | undefined,
314
335
  )
315
336
  },
316
337
  )
@@ -5,11 +5,15 @@ export const toolSchemas = {
5
5
  description: 'Find documents in a collection by ID or where clause using Find or FindByID.',
6
6
  parameters: z.object({
7
7
  id: z
8
- .string()
8
+ .union([z.string(), z.number()])
9
9
  .optional()
10
10
  .describe(
11
11
  'Optional: specific document ID to retrieve. If not provided, returns all documents',
12
12
  ),
13
+ fallbackLocale: z
14
+ .string()
15
+ .optional()
16
+ .describe('Optional: fallback locale code to use when requested locale is not available'),
13
17
  limit: z
14
18
  .number()
15
19
  .int()
@@ -18,6 +22,12 @@ export const toolSchemas = {
18
22
  .optional()
19
23
  .default(10)
20
24
  .describe('Maximum number of documents to return (default: 10, max: 100)'),
25
+ locale: z
26
+ .string()
27
+ .optional()
28
+ .describe(
29
+ 'Optional: locale code to retrieve data in (e.g., "en", "es"). Use "all" to retrieve all locales for localized fields',
30
+ ),
21
31
  page: z
22
32
  .number()
23
33
  .int()
@@ -47,13 +57,26 @@ export const toolSchemas = {
47
57
  .optional()
48
58
  .default(false)
49
59
  .describe('Whether to create the document as a draft'),
60
+ fallbackLocale: z
61
+ .string()
62
+ .optional()
63
+ .describe('Optional: fallback locale code to use when requested locale is not available'),
64
+ locale: z
65
+ .string()
66
+ .optional()
67
+ .describe(
68
+ 'Optional: locale code to create the document in (e.g., "en", "es"). Defaults to the default locale',
69
+ ),
50
70
  }),
51
71
  },
52
72
 
53
73
  updateResource: {
54
74
  description: 'Update documents in a collection by ID or where clause.',
55
75
  parameters: z.object({
56
- id: z.string().optional().describe('Optional: specific document ID to update'),
76
+ id: z
77
+ .union([z.string(), z.number()])
78
+ .optional()
79
+ .describe('Optional: specific document ID to update'),
57
80
  data: z.string().describe('JSON string containing the data to update'),
58
81
  depth: z
59
82
  .number()
@@ -64,7 +87,17 @@ export const toolSchemas = {
64
87
  .default(0)
65
88
  .describe('Depth of population for relationships'),
66
89
  draft: z.boolean().optional().default(false).describe('Whether to update as a draft'),
90
+ fallbackLocale: z
91
+ .string()
92
+ .optional()
93
+ .describe('Optional: fallback locale code to use when requested locale is not available'),
67
94
  filePath: z.string().optional().describe('Optional: absolute file path for file uploads'),
95
+ locale: z
96
+ .string()
97
+ .optional()
98
+ .describe(
99
+ 'Optional: locale code to update the document in (e.g., "en", "es"). Defaults to the default locale',
100
+ ),
68
101
  overrideLock: z
69
102
  .boolean()
70
103
  .optional()
@@ -85,7 +118,10 @@ export const toolSchemas = {
85
118
  deleteResource: {
86
119
  description: 'Delete documents in a collection by ID or where clause.',
87
120
  parameters: z.object({
88
- id: z.string().optional().describe('Optional: specific document ID to delete'),
121
+ id: z
122
+ .union([z.string(), z.number()])
123
+ .optional()
124
+ .describe('Optional: specific document ID to delete'),
89
125
  depth: z
90
126
  .number()
91
127
  .int()
@@ -94,6 +130,16 @@ export const toolSchemas = {
94
130
  .optional()
95
131
  .default(0)
96
132
  .describe('Depth of population for relationships in response'),
133
+ fallbackLocale: z
134
+ .string()
135
+ .optional()
136
+ .describe('Optional: fallback locale code to use when requested locale is not available'),
137
+ locale: z
138
+ .string()
139
+ .optional()
140
+ .describe(
141
+ 'Optional: locale code for the operation (e.g., "en", "es"). Defaults to the default locale',
142
+ ),
97
143
  where: z
98
144
  .string()
99
145
  .optional()
package/src/types.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { CollectionConfig, CollectionSlug, PayloadRequest } from 'payload'
1
+ import type { CollectionConfig, CollectionSlug, PayloadRequest, TypedUser } from 'payload'
2
2
  import type { z } from 'zod'
3
3
 
4
4
  import { type ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'
@@ -135,7 +135,29 @@ export type PluginMCPServerConfig = {
135
135
  /**
136
136
  * Set the handler of the prompt. This is the function that will be called when the prompt is used.
137
137
  */
138
- handler: (...args: any) => any
138
+ handler: (
139
+ args: Record<string, unknown>,
140
+ req: PayloadRequest,
141
+ _extra: unknown,
142
+ ) =>
143
+ | {
144
+ messages: Array<{
145
+ content: {
146
+ text: string
147
+ type: 'text'
148
+ }
149
+ role: 'assistant' | 'user'
150
+ }>
151
+ }
152
+ | Promise<{
153
+ messages: Array<{
154
+ content: {
155
+ text: string
156
+ type: 'text'
157
+ }
158
+ role: 'assistant' | 'user'
159
+ }>
160
+ }>
139
161
  /**
140
162
  * Set the function name of the prompt.
141
163
  */
@@ -157,8 +179,21 @@ export type PluginMCPServerConfig = {
157
179
  description: string
158
180
  /**
159
181
  * Set the handler of the resource. This is the function that will be called when the resource is used.
182
+ * The handler can have either 3 arguments (when no args are passed) or 4 arguments (when args are passed).
160
183
  */
161
- handler: (...args: any) => any
184
+ handler: (...args: any[]) =>
185
+ | {
186
+ contents: Array<{
187
+ text: string
188
+ uri: string
189
+ }>
190
+ }
191
+ | Promise<{
192
+ contents: Array<{
193
+ text: string
194
+ uri: string
195
+ }>
196
+ }>
162
197
  /**
163
198
  * Set the mime type of the resource.
164
199
  * example: 'text/plain'
@@ -192,12 +227,25 @@ export type PluginMCPServerConfig = {
192
227
  /**
193
228
  * Set the handler of the tool. This is the function that will be called when the tool is used.
194
229
  */
195
- handler: (args: Record<string, unknown>) => Promise<{
196
- content: Array<{
197
- text: string
198
- type: 'text'
199
- }>
200
- }>
230
+ handler: (
231
+ args: Record<string, unknown>,
232
+ req: PayloadRequest,
233
+ _extra: unknown,
234
+ ) =>
235
+ | {
236
+ content: Array<{
237
+ text: string
238
+ type: 'text'
239
+ }>
240
+ role?: string
241
+ }
242
+ | Promise<{
243
+ content: Array<{
244
+ text: string
245
+ type: 'text'
246
+ }>
247
+ role?: string
248
+ }>
201
249
  /**
202
250
  * Set the name of the tool. This is the name that will be used to identify the tool. LLMs will interperate the name to determine when to use the tool.
203
251
  */
@@ -309,6 +357,7 @@ export type MCPAccessSettings = {
309
357
  'payload-mcp-prompt'?: Record<string, boolean>
310
358
  'payload-mcp-resource'?: Record<string, boolean>
311
359
  'payload-mcp-tool'?: Record<string, boolean>
360
+ user: TypedUser
312
361
  } & Record<string, unknown>
313
362
 
314
363
  export type FieldDefinition = {
@@ -30,6 +30,5 @@ export const convertCollectionSchemaToZod = (schema: JSONSchema4) => {
30
30
  * 5. No user input or external data is involved in the schema generation process
31
31
  */
32
32
  // eslint-disable-next-line @typescript-eslint/no-implied-eval
33
- const zodSchema = new Function('z', `return ${transpileResult.outputText}`)(z)
34
- return zodSchema
33
+ return new Function('z', `return ${transpileResult.outputText}`)(z)
35
34
  }