@payloadcms/plugin-mcp 3.70.0 → 3.71.0-canary.3
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.
- package/dist/collections/createApiKeysCollection.d.ts +1 -1
- package/dist/collections/createApiKeysCollection.d.ts.map +1 -1
- package/dist/collections/createApiKeysCollection.js +10 -75
- package/dist/collections/createApiKeysCollection.js.map +1 -1
- package/dist/endpoints/mcp.d.ts.map +1 -1
- package/dist/endpoints/mcp.js +1 -0
- package/dist/endpoints/mcp.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp/getMcpHandler.d.ts.map +1 -1
- package/dist/mcp/getMcpHandler.js +24 -10
- package/dist/mcp/getMcpHandler.js.map +1 -1
- package/dist/mcp/tools/global/find.d.ts +5 -0
- package/dist/mcp/tools/global/find.d.ts.map +1 -0
- package/dist/mcp/tools/global/find.js +59 -0
- package/dist/mcp/tools/global/find.js.map +1 -0
- package/dist/mcp/tools/global/update.d.ts +6 -0
- package/dist/mcp/tools/global/update.d.ts.map +1 -0
- package/dist/mcp/tools/global/update.js +97 -0
- package/dist/mcp/tools/global/update.js.map +1 -0
- package/dist/mcp/tools/resource/find.d.ts.map +1 -1
- package/dist/mcp/tools/resource/find.js +9 -3
- package/dist/mcp/tools/resource/find.js.map +1 -1
- package/dist/mcp/tools/schemas.d.ts +58 -17
- package/dist/mcp/tools/schemas.d.ts.map +1 -1
- package/dist/mcp/tools/schemas.js +19 -0
- package/dist/mcp/tools/schemas.js.map +1 -1
- package/dist/types.d.ts +40 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/utils/adminEntitySettings.d.ts +17 -0
- package/dist/utils/adminEntitySettings.d.ts.map +1 -0
- package/dist/utils/adminEntitySettings.js +41 -0
- package/dist/utils/adminEntitySettings.js.map +1 -0
- package/dist/utils/createApiKeyFields.d.ts +15 -0
- package/dist/utils/createApiKeyFields.d.ts.map +1 -0
- package/dist/utils/createApiKeyFields.js +57 -0
- package/dist/utils/createApiKeyFields.js.map +1 -0
- package/dist/utils/getEnabledSlugs.d.ts +13 -0
- package/dist/utils/getEnabledSlugs.d.ts.map +1 -0
- package/dist/utils/getEnabledSlugs.js +32 -0
- package/dist/utils/getEnabledSlugs.js.map +1 -0
- package/package.json +3 -3
- package/src/collections/createApiKeysCollection.ts +11 -111
- package/src/endpoints/mcp.ts +1 -0
- package/src/index.ts +4 -0
- package/src/mcp/getMcpHandler.ts +62 -26
- package/src/mcp/tools/global/find.ts +104 -0
- package/src/mcp/tools/global/update.ts +168 -0
- package/src/mcp/tools/resource/find.ts +5 -2
- package/src/mcp/tools/schemas.ts +56 -0
- package/src/types.ts +59 -1
- package/src/utils/adminEntitySettings.ts +40 -0
- package/src/utils/createApiKeyFields.ts +72 -0
- package/src/utils/getEnabledSlugs.ts +42 -0
|
@@ -3,119 +3,11 @@ import type { CollectionConfig } from 'payload'
|
|
|
3
3
|
import type { PluginMCPServerConfig } from '../types.js'
|
|
4
4
|
|
|
5
5
|
import { toCamelCase } from '../utils/camelCase.js'
|
|
6
|
-
|
|
7
|
-
const addEnabledCollectionTools = (collections: PluginMCPServerConfig['collections']) => {
|
|
8
|
-
const enabledCollectionSlugs = Object.keys(collections || {}).filter((collection) => {
|
|
9
|
-
const fullyEnabled =
|
|
10
|
-
typeof collections?.[collection]?.enabled === 'boolean' && collections?.[collection]?.enabled
|
|
11
|
-
|
|
12
|
-
if (fullyEnabled) {
|
|
13
|
-
return true
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
const partiallyEnabled =
|
|
17
|
-
typeof collections?.[collection]?.enabled !== 'boolean' &&
|
|
18
|
-
((typeof collections?.[collection]?.enabled?.find === 'boolean' &&
|
|
19
|
-
collections?.[collection]?.enabled?.find === true) ||
|
|
20
|
-
(typeof collections?.[collection]?.enabled?.create === 'boolean' &&
|
|
21
|
-
collections?.[collection]?.enabled?.create === true) ||
|
|
22
|
-
(typeof collections?.[collection]?.enabled?.update === 'boolean' &&
|
|
23
|
-
collections?.[collection]?.enabled?.update === true) ||
|
|
24
|
-
(typeof collections?.[collection]?.enabled?.delete === 'boolean' &&
|
|
25
|
-
collections?.[collection]?.enabled?.delete === true))
|
|
26
|
-
|
|
27
|
-
if (partiallyEnabled) {
|
|
28
|
-
return true
|
|
29
|
-
}
|
|
30
|
-
})
|
|
31
|
-
return enabledCollectionSlugs.map((enabledCollectionSlug) => ({
|
|
32
|
-
type: 'collapsible' as const,
|
|
33
|
-
admin: {
|
|
34
|
-
description: `Manage client access to ${enabledCollectionSlug}`,
|
|
35
|
-
position: 'sidebar' as const,
|
|
36
|
-
},
|
|
37
|
-
fields: [
|
|
38
|
-
{
|
|
39
|
-
name: `${toCamelCase(enabledCollectionSlug)}`,
|
|
40
|
-
type: 'group' as const,
|
|
41
|
-
fields: [
|
|
42
|
-
...(collections?.[enabledCollectionSlug]?.enabled === true ||
|
|
43
|
-
(typeof collections?.[enabledCollectionSlug]?.enabled !== 'boolean' &&
|
|
44
|
-
typeof collections?.[enabledCollectionSlug]?.enabled?.find === 'boolean' &&
|
|
45
|
-
collections?.[enabledCollectionSlug]?.enabled?.find === true)
|
|
46
|
-
? [
|
|
47
|
-
{
|
|
48
|
-
name: `find`,
|
|
49
|
-
type: 'checkbox' as const,
|
|
50
|
-
admin: {
|
|
51
|
-
description: `Allow clients to find ${enabledCollectionSlug}.`,
|
|
52
|
-
},
|
|
53
|
-
defaultValue: false,
|
|
54
|
-
label: 'Find',
|
|
55
|
-
},
|
|
56
|
-
]
|
|
57
|
-
: []),
|
|
58
|
-
|
|
59
|
-
...(collections?.[enabledCollectionSlug]?.enabled === true ||
|
|
60
|
-
(typeof collections?.[enabledCollectionSlug]?.enabled !== 'boolean' &&
|
|
61
|
-
typeof collections?.[enabledCollectionSlug]?.enabled?.create === 'boolean' &&
|
|
62
|
-
collections?.[enabledCollectionSlug]?.enabled?.create === true)
|
|
63
|
-
? [
|
|
64
|
-
{
|
|
65
|
-
name: `create`,
|
|
66
|
-
type: 'checkbox' as const,
|
|
67
|
-
admin: {
|
|
68
|
-
description: `Allow clients to create ${enabledCollectionSlug}.`,
|
|
69
|
-
},
|
|
70
|
-
defaultValue: false,
|
|
71
|
-
label: 'Create',
|
|
72
|
-
},
|
|
73
|
-
]
|
|
74
|
-
: []),
|
|
75
|
-
|
|
76
|
-
...(collections?.[enabledCollectionSlug]?.enabled === true ||
|
|
77
|
-
(typeof collections?.[enabledCollectionSlug]?.enabled !== 'boolean' &&
|
|
78
|
-
typeof collections?.[enabledCollectionSlug]?.enabled?.update === 'boolean' &&
|
|
79
|
-
collections?.[enabledCollectionSlug]?.enabled?.update === true)
|
|
80
|
-
? [
|
|
81
|
-
{
|
|
82
|
-
name: `update`,
|
|
83
|
-
type: 'checkbox' as const,
|
|
84
|
-
admin: {
|
|
85
|
-
description: `Allow clients to update ${enabledCollectionSlug}.`,
|
|
86
|
-
},
|
|
87
|
-
defaultValue: false,
|
|
88
|
-
label: 'Update',
|
|
89
|
-
},
|
|
90
|
-
]
|
|
91
|
-
: []),
|
|
92
|
-
|
|
93
|
-
...(collections?.[enabledCollectionSlug]?.enabled === true ||
|
|
94
|
-
(typeof collections?.[enabledCollectionSlug]?.enabled !== 'boolean' &&
|
|
95
|
-
typeof collections?.[enabledCollectionSlug]?.enabled?.delete === 'boolean' &&
|
|
96
|
-
collections?.[enabledCollectionSlug]?.enabled?.delete === true)
|
|
97
|
-
? [
|
|
98
|
-
{
|
|
99
|
-
name: `delete`,
|
|
100
|
-
type: 'checkbox' as const,
|
|
101
|
-
admin: {
|
|
102
|
-
description: `Allow clients to delete ${enabledCollectionSlug}.`,
|
|
103
|
-
},
|
|
104
|
-
defaultValue: false,
|
|
105
|
-
label: 'Delete',
|
|
106
|
-
},
|
|
107
|
-
]
|
|
108
|
-
: []),
|
|
109
|
-
],
|
|
110
|
-
label: false as const,
|
|
111
|
-
},
|
|
112
|
-
],
|
|
113
|
-
label: `${enabledCollectionSlug.charAt(0).toUpperCase() + toCamelCase(enabledCollectionSlug).slice(1)}`,
|
|
114
|
-
}))
|
|
115
|
-
}
|
|
6
|
+
import { createApiKeyFields } from '../utils/createApiKeyFields.js'
|
|
116
7
|
|
|
117
8
|
export const createAPIKeysCollection = (
|
|
118
9
|
collections: PluginMCPServerConfig['collections'],
|
|
10
|
+
globals: PluginMCPServerConfig['globals'],
|
|
119
11
|
customTools: Array<{ description: string; name: string }> = [],
|
|
120
12
|
experimentalTools: NonNullable<PluginMCPServerConfig['experimental']>['tools'] = {},
|
|
121
13
|
pluginOptions: PluginMCPServerConfig,
|
|
@@ -204,7 +96,15 @@ export const createAPIKeysCollection = (
|
|
|
204
96
|
},
|
|
205
97
|
},
|
|
206
98
|
|
|
207
|
-
...
|
|
99
|
+
...createApiKeyFields({
|
|
100
|
+
config: collections,
|
|
101
|
+
configType: 'collection',
|
|
102
|
+
}),
|
|
103
|
+
|
|
104
|
+
...createApiKeyFields({
|
|
105
|
+
config: globals,
|
|
106
|
+
configType: 'global',
|
|
107
|
+
}),
|
|
208
108
|
|
|
209
109
|
...(customTools.length > 0
|
|
210
110
|
? [
|
package/src/endpoints/mcp.ts
CHANGED
package/src/index.ts
CHANGED
|
@@ -27,6 +27,8 @@ export const mcpPlugin =
|
|
|
27
27
|
|
|
28
28
|
// Collections
|
|
29
29
|
const collections = pluginOptions.collections || {}
|
|
30
|
+
// Globals
|
|
31
|
+
const globals = pluginOptions.globals || {}
|
|
30
32
|
// Extract custom tools for the global config
|
|
31
33
|
const customTools =
|
|
32
34
|
pluginOptions.mcp?.tools?.map((tool) => ({
|
|
@@ -46,11 +48,13 @@ export const mcpPlugin =
|
|
|
46
48
|
* For example:
|
|
47
49
|
* - If a collection has all of its capabilities enabled, admins can allow or disallow the create, update, delete, and find capabilities on that collection.
|
|
48
50
|
* - If a collection only has the find capability enabled, admins can only allow or disallow the find capability on that collection.
|
|
51
|
+
* - If a global has all of its capabilities enabled, admins can allow or disallow the find and update capabilities on that global.
|
|
49
52
|
* - If a custom tool has gone haywire, admins can disallow that tool.
|
|
50
53
|
*
|
|
51
54
|
*/
|
|
52
55
|
const apiKeyCollection = createAPIKeysCollection(
|
|
53
56
|
collections,
|
|
57
|
+
globals,
|
|
54
58
|
customTools,
|
|
55
59
|
experimentalTools,
|
|
56
60
|
pluginOptions,
|
package/src/mcp/getMcpHandler.ts
CHANGED
|
@@ -7,9 +7,12 @@ import { APIError, configToJSONSchema, type PayloadRequest, type TypedUser } fro
|
|
|
7
7
|
import type { MCPAccessSettings, PluginMCPServerConfig } from '../types.js'
|
|
8
8
|
|
|
9
9
|
import { toCamelCase } from '../utils/camelCase.js'
|
|
10
|
+
import { getEnabledSlugs } from '../utils/getEnabledSlugs.js'
|
|
10
11
|
import { registerTool } from './registerTool.js'
|
|
11
12
|
|
|
12
13
|
// Tools
|
|
14
|
+
import { findGlobalTool } from './tools/global/find.js'
|
|
15
|
+
import { updateGlobalTool } from './tools/global/update.js'
|
|
13
16
|
import { createResourceTool } from './tools/resource/create.js'
|
|
14
17
|
import { deleteResourceTool } from './tools/resource/delete.js'
|
|
15
18
|
import { findResourceTool } from './tools/resource/find.js'
|
|
@@ -81,6 +84,7 @@ export const getMCPHandler = (
|
|
|
81
84
|
const experimentalTools: NonNullable<PluginMCPServerConfig['experimental']>['tools'] =
|
|
82
85
|
pluginOptions?.experimental?.tools || {}
|
|
83
86
|
const collectionsPluginConfig = pluginOptions.collections || {}
|
|
87
|
+
const globalsPluginConfig = pluginOptions.globals || {}
|
|
84
88
|
const collectionsDirPath =
|
|
85
89
|
experimentalTools && experimentalTools.collections?.collectionsDirPath
|
|
86
90
|
? experimentalTools.collections.collectionsDirPath
|
|
@@ -97,32 +101,8 @@ export const getMCPHandler = (
|
|
|
97
101
|
try {
|
|
98
102
|
return createMcpHandler(
|
|
99
103
|
(server) => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
const fullyEnabled =
|
|
103
|
-
typeof collectionsPluginConfig?.[collection]?.enabled === 'boolean' &&
|
|
104
|
-
collectionsPluginConfig?.[collection]?.enabled
|
|
105
|
-
|
|
106
|
-
if (fullyEnabled) {
|
|
107
|
-
return true
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
const partiallyEnabled =
|
|
111
|
-
typeof collectionsPluginConfig?.[collection]?.enabled !== 'boolean' &&
|
|
112
|
-
((typeof collectionsPluginConfig?.[collection]?.enabled?.find === 'boolean' &&
|
|
113
|
-
collectionsPluginConfig?.[collection]?.enabled?.find === true) ||
|
|
114
|
-
(typeof collectionsPluginConfig?.[collection]?.enabled?.create === 'boolean' &&
|
|
115
|
-
collectionsPluginConfig?.[collection]?.enabled?.create === true) ||
|
|
116
|
-
(typeof collectionsPluginConfig?.[collection]?.enabled?.update === 'boolean' &&
|
|
117
|
-
collectionsPluginConfig?.[collection]?.enabled?.update === true) ||
|
|
118
|
-
(typeof collectionsPluginConfig?.[collection]?.enabled?.delete === 'boolean' &&
|
|
119
|
-
collectionsPluginConfig?.[collection]?.enabled?.delete === true))
|
|
120
|
-
|
|
121
|
-
if (partiallyEnabled) {
|
|
122
|
-
return true
|
|
123
|
-
}
|
|
124
|
-
},
|
|
125
|
-
)
|
|
104
|
+
// Get enabled collections
|
|
105
|
+
const enabledCollectionSlugs = getEnabledSlugs(collectionsPluginConfig, 'collection')
|
|
126
106
|
|
|
127
107
|
// Collection Operation Tools
|
|
128
108
|
enabledCollectionSlugs.forEach((enabledCollectionSlug) => {
|
|
@@ -215,6 +195,62 @@ export const getMCPHandler = (
|
|
|
215
195
|
}
|
|
216
196
|
})
|
|
217
197
|
|
|
198
|
+
// Global Operation Tools
|
|
199
|
+
const enabledGlobalSlugs = getEnabledSlugs(globalsPluginConfig, 'global')
|
|
200
|
+
|
|
201
|
+
enabledGlobalSlugs.forEach((enabledGlobalSlug) => {
|
|
202
|
+
try {
|
|
203
|
+
const schema = configSchema.definitions?.[enabledGlobalSlug] as JSONSchema4
|
|
204
|
+
|
|
205
|
+
const toolCapabilities = mcpAccessSettings?.[
|
|
206
|
+
`${toCamelCase(enabledGlobalSlug)}`
|
|
207
|
+
] as Record<string, unknown>
|
|
208
|
+
const allowFind: boolean | undefined = toolCapabilities?.['find'] as boolean
|
|
209
|
+
const allowUpdate: boolean | undefined = toolCapabilities?.['update'] as boolean
|
|
210
|
+
|
|
211
|
+
if (allowFind) {
|
|
212
|
+
registerTool(
|
|
213
|
+
allowFind,
|
|
214
|
+
`Find ${enabledGlobalSlug}`,
|
|
215
|
+
() =>
|
|
216
|
+
findGlobalTool(
|
|
217
|
+
server,
|
|
218
|
+
req,
|
|
219
|
+
user,
|
|
220
|
+
useVerboseLogs,
|
|
221
|
+
enabledGlobalSlug,
|
|
222
|
+
globalsPluginConfig,
|
|
223
|
+
),
|
|
224
|
+
payload,
|
|
225
|
+
useVerboseLogs,
|
|
226
|
+
)
|
|
227
|
+
}
|
|
228
|
+
if (allowUpdate) {
|
|
229
|
+
registerTool(
|
|
230
|
+
allowUpdate,
|
|
231
|
+
`Update ${enabledGlobalSlug}`,
|
|
232
|
+
() =>
|
|
233
|
+
updateGlobalTool(
|
|
234
|
+
server,
|
|
235
|
+
req,
|
|
236
|
+
user,
|
|
237
|
+
useVerboseLogs,
|
|
238
|
+
enabledGlobalSlug,
|
|
239
|
+
globalsPluginConfig,
|
|
240
|
+
schema,
|
|
241
|
+
),
|
|
242
|
+
payload,
|
|
243
|
+
useVerboseLogs,
|
|
244
|
+
)
|
|
245
|
+
}
|
|
246
|
+
} catch (error) {
|
|
247
|
+
throw new APIError(
|
|
248
|
+
`Error registering tools for global ${enabledGlobalSlug}: ${String(error)}`,
|
|
249
|
+
500,
|
|
250
|
+
)
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
|
|
218
254
|
// Custom tools
|
|
219
255
|
customMCPTools.forEach((tool) => {
|
|
220
256
|
const camelCasedToolName = toCamelCase(tool.name)
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
2
|
+
import type { PayloadRequest, TypedUser } from 'payload'
|
|
3
|
+
|
|
4
|
+
import type { PluginMCPServerConfig } from '../../../types.js'
|
|
5
|
+
|
|
6
|
+
import { toCamelCase } from '../../../utils/camelCase.js'
|
|
7
|
+
import { toolSchemas } from '../schemas.js'
|
|
8
|
+
|
|
9
|
+
export const findGlobalTool = (
|
|
10
|
+
server: McpServer,
|
|
11
|
+
req: PayloadRequest,
|
|
12
|
+
user: TypedUser,
|
|
13
|
+
verboseLogs: boolean,
|
|
14
|
+
globalSlug: string,
|
|
15
|
+
globals: PluginMCPServerConfig['globals'],
|
|
16
|
+
) => {
|
|
17
|
+
const tool = async (
|
|
18
|
+
depth: number = 0,
|
|
19
|
+
locale?: string,
|
|
20
|
+
fallbackLocale?: string,
|
|
21
|
+
): Promise<{
|
|
22
|
+
content: Array<{
|
|
23
|
+
text: string
|
|
24
|
+
type: 'text'
|
|
25
|
+
}>
|
|
26
|
+
}> => {
|
|
27
|
+
const payload = req.payload
|
|
28
|
+
|
|
29
|
+
if (verboseLogs) {
|
|
30
|
+
payload.logger.info(
|
|
31
|
+
`[payload-mcp] Reading global: ${globalSlug}, depth: ${depth}${locale ? `, locale: ${locale}` : ''}`,
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
const findOptions: Parameters<typeof payload.findGlobal>[0] = {
|
|
37
|
+
slug: globalSlug,
|
|
38
|
+
depth,
|
|
39
|
+
user,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Add locale parameters if provided
|
|
43
|
+
if (locale) {
|
|
44
|
+
findOptions.locale = locale
|
|
45
|
+
}
|
|
46
|
+
if (fallbackLocale) {
|
|
47
|
+
findOptions.fallbackLocale = fallbackLocale
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const result = await payload.findGlobal(findOptions)
|
|
51
|
+
|
|
52
|
+
if (verboseLogs) {
|
|
53
|
+
payload.logger.info(`[payload-mcp] Found global: ${globalSlug}`)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const response = {
|
|
57
|
+
content: [
|
|
58
|
+
{
|
|
59
|
+
type: 'text' as const,
|
|
60
|
+
text: `Global "${globalSlug}":
|
|
61
|
+
\`\`\`json
|
|
62
|
+
${JSON.stringify(result, null, 2)}
|
|
63
|
+
\`\`\``,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return (globals?.[globalSlug]?.overrideResponse?.(response, result, req) || response) as {
|
|
69
|
+
content: Array<{
|
|
70
|
+
text: string
|
|
71
|
+
type: 'text'
|
|
72
|
+
}>
|
|
73
|
+
}
|
|
74
|
+
} catch (error) {
|
|
75
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
76
|
+
payload.logger.error(`[payload-mcp] Error reading global ${globalSlug}: ${errorMessage}`)
|
|
77
|
+
const response = {
|
|
78
|
+
content: [
|
|
79
|
+
{
|
|
80
|
+
type: 'text' as const,
|
|
81
|
+
text: `❌ **Error reading global "${globalSlug}":** ${errorMessage}`,
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
}
|
|
85
|
+
return (globals?.[globalSlug]?.overrideResponse?.(response, {}, req) || response) as {
|
|
86
|
+
content: Array<{
|
|
87
|
+
text: string
|
|
88
|
+
type: 'text'
|
|
89
|
+
}>
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (globals?.[globalSlug]?.enabled) {
|
|
95
|
+
server.tool(
|
|
96
|
+
`find${globalSlug.charAt(0).toUpperCase() + toCamelCase(globalSlug).slice(1)}`,
|
|
97
|
+
`${toolSchemas.findGlobal.description.trim()}\n\n${globals?.[globalSlug]?.description || ''}`,
|
|
98
|
+
toolSchemas.findGlobal.parameters.shape,
|
|
99
|
+
async ({ depth, fallbackLocale, locale }) => {
|
|
100
|
+
return await tool(depth, locale, fallbackLocale)
|
|
101
|
+
},
|
|
102
|
+
)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
2
|
+
import type { JSONSchema4 } from 'json-schema'
|
|
3
|
+
import type { PayloadRequest, TypedUser } from 'payload'
|
|
4
|
+
|
|
5
|
+
import { z } from 'zod'
|
|
6
|
+
|
|
7
|
+
import type { PluginMCPServerConfig } from '../../../types.js'
|
|
8
|
+
|
|
9
|
+
import { toCamelCase } from '../../../utils/camelCase.js'
|
|
10
|
+
import { convertCollectionSchemaToZod } from '../../../utils/convertCollectionSchemaToZod.js'
|
|
11
|
+
import { toolSchemas } from '../schemas.js'
|
|
12
|
+
|
|
13
|
+
export const updateGlobalTool = (
|
|
14
|
+
server: McpServer,
|
|
15
|
+
req: PayloadRequest,
|
|
16
|
+
user: TypedUser,
|
|
17
|
+
verboseLogs: boolean,
|
|
18
|
+
globalSlug: string,
|
|
19
|
+
globals: PluginMCPServerConfig['globals'],
|
|
20
|
+
schema: JSONSchema4,
|
|
21
|
+
) => {
|
|
22
|
+
const tool = async (
|
|
23
|
+
data: string,
|
|
24
|
+
draft: boolean = false,
|
|
25
|
+
depth: number = 0,
|
|
26
|
+
locale?: string,
|
|
27
|
+
fallbackLocale?: string,
|
|
28
|
+
): Promise<{
|
|
29
|
+
content: Array<{
|
|
30
|
+
text: string
|
|
31
|
+
type: 'text'
|
|
32
|
+
}>
|
|
33
|
+
}> => {
|
|
34
|
+
const payload = req.payload
|
|
35
|
+
|
|
36
|
+
if (verboseLogs) {
|
|
37
|
+
payload.logger.info(
|
|
38
|
+
`[payload-mcp] Updating global: ${globalSlug}, draft: ${draft}${locale ? `, locale: ${locale}` : ''}`,
|
|
39
|
+
)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
try {
|
|
43
|
+
// Parse the data JSON
|
|
44
|
+
let parsedData: Record<string, unknown>
|
|
45
|
+
try {
|
|
46
|
+
parsedData = JSON.parse(data)
|
|
47
|
+
if (verboseLogs) {
|
|
48
|
+
payload.logger.info(
|
|
49
|
+
`[payload-mcp] Parsed data for ${globalSlug}: ${JSON.stringify(parsedData)}`,
|
|
50
|
+
)
|
|
51
|
+
}
|
|
52
|
+
} catch (_parseError) {
|
|
53
|
+
payload.logger.error(`[payload-mcp] Invalid JSON data provided: ${data}`)
|
|
54
|
+
const response = {
|
|
55
|
+
content: [{ type: 'text' as const, text: 'Error: Invalid JSON data provided' }],
|
|
56
|
+
}
|
|
57
|
+
return (globals?.[globalSlug]?.overrideResponse?.(response, {}, req) || response) as {
|
|
58
|
+
content: Array<{
|
|
59
|
+
text: string
|
|
60
|
+
type: 'text'
|
|
61
|
+
}>
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const updateOptions: Parameters<typeof payload.updateGlobal>[0] = {
|
|
66
|
+
slug: globalSlug,
|
|
67
|
+
data: parsedData,
|
|
68
|
+
depth,
|
|
69
|
+
draft,
|
|
70
|
+
user,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Add locale parameters if provided
|
|
74
|
+
if (locale) {
|
|
75
|
+
updateOptions.locale = locale
|
|
76
|
+
}
|
|
77
|
+
if (fallbackLocale) {
|
|
78
|
+
updateOptions.fallbackLocale = fallbackLocale
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const result = await payload.updateGlobal(updateOptions)
|
|
82
|
+
|
|
83
|
+
if (verboseLogs) {
|
|
84
|
+
payload.logger.info(`[payload-mcp] Successfully updated global: ${globalSlug}`)
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const response = {
|
|
88
|
+
content: [
|
|
89
|
+
{
|
|
90
|
+
type: 'text' as const,
|
|
91
|
+
text: `Global "${globalSlug}" updated successfully!
|
|
92
|
+
\`\`\`json
|
|
93
|
+
${JSON.stringify(result, null, 2)}
|
|
94
|
+
\`\`\``,
|
|
95
|
+
},
|
|
96
|
+
],
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return (globals?.[globalSlug]?.overrideResponse?.(response, result, req) || response) as {
|
|
100
|
+
content: Array<{
|
|
101
|
+
text: string
|
|
102
|
+
type: 'text'
|
|
103
|
+
}>
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
107
|
+
payload.logger.error(`[payload-mcp] Error updating global ${globalSlug}: ${errorMessage}`)
|
|
108
|
+
|
|
109
|
+
const response = {
|
|
110
|
+
content: [
|
|
111
|
+
{
|
|
112
|
+
type: 'text' as const,
|
|
113
|
+
text: `Error updating global "${globalSlug}": ${errorMessage}`,
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return (globals?.[globalSlug]?.overrideResponse?.(response, {}, req) || response) as {
|
|
119
|
+
content: Array<{
|
|
120
|
+
text: string
|
|
121
|
+
type: 'text'
|
|
122
|
+
}>
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (globals?.[globalSlug]?.enabled) {
|
|
128
|
+
const convertedFields = convertCollectionSchemaToZod(schema)
|
|
129
|
+
|
|
130
|
+
// Make all fields optional for partial updates (PATCH-style)
|
|
131
|
+
const optionalFields = Object.fromEntries(
|
|
132
|
+
Object.entries(convertedFields.shape).map(([key, value]) => [key, (value as any).optional()]),
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
const updateGlobalSchema = z.object({
|
|
136
|
+
...optionalFields,
|
|
137
|
+
depth: z.number().optional().describe('Optional: Depth of relationships to populate'),
|
|
138
|
+
draft: z.boolean().optional().describe('Optional: Whether to save as draft (default: false)'),
|
|
139
|
+
fallbackLocale: z
|
|
140
|
+
.string()
|
|
141
|
+
.optional()
|
|
142
|
+
.describe('Optional: fallback locale code to use when requested locale is not available'),
|
|
143
|
+
locale: z
|
|
144
|
+
.string()
|
|
145
|
+
.optional()
|
|
146
|
+
.describe(
|
|
147
|
+
'Optional: locale code to update data in (e.g., "en", "es"). Use "all" to update all locales for localized fields',
|
|
148
|
+
),
|
|
149
|
+
})
|
|
150
|
+
|
|
151
|
+
server.tool(
|
|
152
|
+
`update${globalSlug.charAt(0).toUpperCase() + toCamelCase(globalSlug).slice(1)}`,
|
|
153
|
+
`${toolSchemas.updateGlobal.description.trim()}\n\n${globals?.[globalSlug]?.description || ''}`,
|
|
154
|
+
updateGlobalSchema.shape,
|
|
155
|
+
async (params: Record<string, unknown>) => {
|
|
156
|
+
const { depth, draft, fallbackLocale, locale, ...rest } = params
|
|
157
|
+
const data = JSON.stringify(rest)
|
|
158
|
+
return await tool(
|
|
159
|
+
data,
|
|
160
|
+
draft as boolean,
|
|
161
|
+
depth as number,
|
|
162
|
+
locale as string,
|
|
163
|
+
fallbackLocale as string,
|
|
164
|
+
)
|
|
165
|
+
},
|
|
166
|
+
)
|
|
167
|
+
}
|
|
168
|
+
}
|
|
@@ -22,6 +22,7 @@ export const findResourceTool = (
|
|
|
22
22
|
where?: string,
|
|
23
23
|
locale?: string,
|
|
24
24
|
fallbackLocale?: string,
|
|
25
|
+
draft?: boolean,
|
|
25
26
|
): Promise<{
|
|
26
27
|
content: Array<{
|
|
27
28
|
text: string
|
|
@@ -71,6 +72,7 @@ export const findResourceTool = (
|
|
|
71
72
|
user,
|
|
72
73
|
...(locale && { locale }),
|
|
73
74
|
...(fallbackLocale && { fallbackLocale }),
|
|
75
|
+
...(draft !== undefined && { draft }),
|
|
74
76
|
})
|
|
75
77
|
|
|
76
78
|
if (verboseLogs) {
|
|
@@ -126,6 +128,7 @@ ${JSON.stringify(doc, null, 2)}`,
|
|
|
126
128
|
user,
|
|
127
129
|
...(locale && { locale }),
|
|
128
130
|
...(fallbackLocale && { fallbackLocale }),
|
|
131
|
+
...(draft !== undefined && { draft }),
|
|
129
132
|
}
|
|
130
133
|
|
|
131
134
|
if (sort) {
|
|
@@ -196,8 +199,8 @@ Page: ${result.page} of ${result.totalPages}
|
|
|
196
199
|
`find${collectionSlug.charAt(0).toUpperCase() + toCamelCase(collectionSlug).slice(1)}`,
|
|
197
200
|
`${collections?.[collectionSlug]?.description || toolSchemas.findResources.description.trim()}`,
|
|
198
201
|
toolSchemas.findResources.parameters.shape,
|
|
199
|
-
async ({ id, fallbackLocale, limit, locale, page, sort, where }) => {
|
|
200
|
-
return await tool(id, limit, page, sort, where, locale, fallbackLocale)
|
|
202
|
+
async ({ id, draft, fallbackLocale, limit, locale, page, sort, where }) => {
|
|
203
|
+
return await tool(id, limit, page, sort, where, locale, fallbackLocale, draft)
|
|
201
204
|
},
|
|
202
205
|
)
|
|
203
206
|
}
|
package/src/mcp/tools/schemas.ts
CHANGED
|
@@ -1,6 +1,30 @@
|
|
|
1
1
|
import { z } from 'zod'
|
|
2
2
|
|
|
3
3
|
export const toolSchemas = {
|
|
4
|
+
findGlobal: {
|
|
5
|
+
description: 'Find a Payload global singleton configuration.',
|
|
6
|
+
parameters: z.object({
|
|
7
|
+
depth: z
|
|
8
|
+
.number()
|
|
9
|
+
.int()
|
|
10
|
+
.min(0)
|
|
11
|
+
.max(10)
|
|
12
|
+
.optional()
|
|
13
|
+
.default(0)
|
|
14
|
+
.describe('Depth of population for relationships'),
|
|
15
|
+
fallbackLocale: z
|
|
16
|
+
.string()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe('Optional: fallback locale code to use when requested locale is not available'),
|
|
19
|
+
locale: z
|
|
20
|
+
.string()
|
|
21
|
+
.optional()
|
|
22
|
+
.describe(
|
|
23
|
+
'Optional: locale code to retrieve data in (e.g., "en", "es"). Use "all" to retrieve all locales for localized fields',
|
|
24
|
+
),
|
|
25
|
+
}),
|
|
26
|
+
},
|
|
27
|
+
|
|
4
28
|
findResources: {
|
|
5
29
|
description: 'Find documents in a collection by ID or where clause using Find or FindByID.',
|
|
6
30
|
parameters: z.object({
|
|
@@ -10,6 +34,12 @@ export const toolSchemas = {
|
|
|
10
34
|
.describe(
|
|
11
35
|
'Optional: specific document ID to retrieve. If not provided, returns all documents',
|
|
12
36
|
),
|
|
37
|
+
draft: z
|
|
38
|
+
.boolean()
|
|
39
|
+
.optional()
|
|
40
|
+
.describe(
|
|
41
|
+
'Optional: Whether the document should be queried from the versions table/collection or not.',
|
|
42
|
+
),
|
|
13
43
|
fallbackLocale: z
|
|
14
44
|
.string()
|
|
15
45
|
.optional()
|
|
@@ -147,6 +177,32 @@ export const toolSchemas = {
|
|
|
147
177
|
}),
|
|
148
178
|
},
|
|
149
179
|
|
|
180
|
+
updateGlobal: {
|
|
181
|
+
description: 'Update a Payload global singleton configuration.',
|
|
182
|
+
parameters: z.object({
|
|
183
|
+
data: z.string().describe('JSON string containing the data to update'),
|
|
184
|
+
depth: z
|
|
185
|
+
.number()
|
|
186
|
+
.int()
|
|
187
|
+
.min(0)
|
|
188
|
+
.max(10)
|
|
189
|
+
.optional()
|
|
190
|
+
.default(0)
|
|
191
|
+
.describe('Depth of population for relationships'),
|
|
192
|
+
draft: z.boolean().optional().default(false).describe('Whether to update as a draft'),
|
|
193
|
+
fallbackLocale: z
|
|
194
|
+
.string()
|
|
195
|
+
.optional()
|
|
196
|
+
.describe('Optional: fallback locale code to use when requested locale is not available'),
|
|
197
|
+
locale: z
|
|
198
|
+
.string()
|
|
199
|
+
.optional()
|
|
200
|
+
.describe(
|
|
201
|
+
'Optional: locale code to update data in (e.g., "en", "es"). Use "all" to update all locales for localized fields',
|
|
202
|
+
),
|
|
203
|
+
}),
|
|
204
|
+
},
|
|
205
|
+
|
|
150
206
|
// Experimental Below This Line
|
|
151
207
|
createCollection: {
|
|
152
208
|
description: 'Creates a new collection with specified fields and configuration.',
|