@payloadcms/plugin-mcp 3.70.0 → 3.71.0-internal.27e1e08
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/schemas.d.ts +55 -17
- package/dist/mcp/tools/schemas.d.ts.map +1 -1
- package/dist/mcp/tools/schemas.js +18 -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/schemas.ts +50 -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
|
@@ -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
|
+
}
|
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({
|
|
@@ -147,6 +171,32 @@ export const toolSchemas = {
|
|
|
147
171
|
}),
|
|
148
172
|
},
|
|
149
173
|
|
|
174
|
+
updateGlobal: {
|
|
175
|
+
description: 'Update a Payload global singleton configuration.',
|
|
176
|
+
parameters: z.object({
|
|
177
|
+
data: z.string().describe('JSON string containing the data to update'),
|
|
178
|
+
depth: z
|
|
179
|
+
.number()
|
|
180
|
+
.int()
|
|
181
|
+
.min(0)
|
|
182
|
+
.max(10)
|
|
183
|
+
.optional()
|
|
184
|
+
.default(0)
|
|
185
|
+
.describe('Depth of population for relationships'),
|
|
186
|
+
draft: z.boolean().optional().default(false).describe('Whether to update as a draft'),
|
|
187
|
+
fallbackLocale: z
|
|
188
|
+
.string()
|
|
189
|
+
.optional()
|
|
190
|
+
.describe('Optional: fallback locale code to use when requested locale is not available'),
|
|
191
|
+
locale: z
|
|
192
|
+
.string()
|
|
193
|
+
.optional()
|
|
194
|
+
.describe(
|
|
195
|
+
'Optional: locale code to update data in (e.g., "en", "es"). Use "all" to update all locales for localized fields',
|
|
196
|
+
),
|
|
197
|
+
}),
|
|
198
|
+
},
|
|
199
|
+
|
|
150
200
|
// Experimental Below This Line
|
|
151
201
|
createCollection: {
|
|
152
202
|
description: 'Creates a new collection with specified fields and configuration.',
|
package/src/types.ts
CHANGED
|
@@ -1,4 +1,10 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
CollectionConfig,
|
|
3
|
+
CollectionSlug,
|
|
4
|
+
GlobalSlug,
|
|
5
|
+
PayloadRequest,
|
|
6
|
+
TypedUser,
|
|
7
|
+
} from 'payload'
|
|
2
8
|
import type { z } from 'zod'
|
|
3
9
|
|
|
4
10
|
import { type ResourceTemplate } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
@@ -115,6 +121,51 @@ export type PluginMCPServerConfig = {
|
|
|
115
121
|
}
|
|
116
122
|
}
|
|
117
123
|
}
|
|
124
|
+
/**
|
|
125
|
+
* Set the globals that should be available as resources via MCP.
|
|
126
|
+
* Globals are singleton configuration objects (e.g., site settings, navigation).
|
|
127
|
+
* Note: Globals only support find and update operations.
|
|
128
|
+
*/
|
|
129
|
+
globals?: Partial<
|
|
130
|
+
Record<
|
|
131
|
+
GlobalSlug,
|
|
132
|
+
{
|
|
133
|
+
/**
|
|
134
|
+
* Set the description of the global. This is used by MCP clients to determine when to use the global as a resource.
|
|
135
|
+
*/
|
|
136
|
+
description?: string
|
|
137
|
+
/**
|
|
138
|
+
* Set the enabled capabilities of the global. Admins can then allow or disallow the use of the capability by MCP clients.
|
|
139
|
+
* Note: Globals only support find and update operations as they are singletons.
|
|
140
|
+
*/
|
|
141
|
+
enabled:
|
|
142
|
+
| {
|
|
143
|
+
find?: boolean
|
|
144
|
+
update?: boolean
|
|
145
|
+
}
|
|
146
|
+
| boolean
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Override the response generated by the MCP client. This allows you to modify the response that is sent to the MCP client. This is useful for adding additional data to the response, data normalization, or verifying data.
|
|
150
|
+
*/
|
|
151
|
+
overrideResponse?: (
|
|
152
|
+
response: {
|
|
153
|
+
content: Array<{
|
|
154
|
+
text: string
|
|
155
|
+
type: string
|
|
156
|
+
}>
|
|
157
|
+
},
|
|
158
|
+
doc: Record<string, unknown>,
|
|
159
|
+
req: PayloadRequest,
|
|
160
|
+
) => {
|
|
161
|
+
content: Array<{
|
|
162
|
+
text: string
|
|
163
|
+
type: string
|
|
164
|
+
}>
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
>
|
|
168
|
+
>
|
|
118
169
|
/**
|
|
119
170
|
* MCP Server options.
|
|
120
171
|
*/
|
|
@@ -349,6 +400,11 @@ export type MCPAccessSettings = {
|
|
|
349
400
|
find?: boolean
|
|
350
401
|
update?: boolean
|
|
351
402
|
}
|
|
403
|
+
custom?: Record<string, boolean>
|
|
404
|
+
globals?: {
|
|
405
|
+
find?: boolean
|
|
406
|
+
update?: boolean
|
|
407
|
+
}
|
|
352
408
|
jobs?: {
|
|
353
409
|
create?: boolean
|
|
354
410
|
run?: boolean
|
|
@@ -360,6 +416,8 @@ export type MCPAccessSettings = {
|
|
|
360
416
|
user: TypedUser
|
|
361
417
|
} & Record<string, unknown>
|
|
362
418
|
|
|
419
|
+
export type EntityConfig = PluginMCPServerConfig['collections'] | PluginMCPServerConfig['globals']
|
|
420
|
+
|
|
363
421
|
export type FieldDefinition = {
|
|
364
422
|
description?: string
|
|
365
423
|
name: string
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Defines the default admin entity settings for Collections and Globals.
|
|
3
|
+
* This is used to create the MCP API key permission fields for the API Keys collection.
|
|
4
|
+
*/
|
|
5
|
+
export const adminEntitySettings = {
|
|
6
|
+
collection: [
|
|
7
|
+
{
|
|
8
|
+
name: 'find',
|
|
9
|
+
description: (slug: string) => `Allow clients to find ${slug}.`,
|
|
10
|
+
label: 'Find',
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
name: 'create',
|
|
14
|
+
description: (slug: string) => `Allow clients to create ${slug}.`,
|
|
15
|
+
label: 'Create',
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
name: 'update',
|
|
19
|
+
description: (slug: string) => `Allow clients to update ${slug}.`,
|
|
20
|
+
label: 'Update',
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
name: 'delete',
|
|
24
|
+
description: (slug: string) => `Allow clients to delete ${slug}.`,
|
|
25
|
+
label: 'Delete',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
global: [
|
|
29
|
+
{
|
|
30
|
+
name: 'find',
|
|
31
|
+
description: (slug: string) => `Allow clients to find ${slug} global.`,
|
|
32
|
+
label: 'Find',
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
name: 'update',
|
|
36
|
+
description: (slug: string) => `Allow clients to update ${slug} global.`,
|
|
37
|
+
label: 'Update',
|
|
38
|
+
},
|
|
39
|
+
],
|
|
40
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Field } from 'payload'
|
|
2
|
+
|
|
3
|
+
import type { EntityConfig } from '../types.js'
|
|
4
|
+
|
|
5
|
+
import { adminEntitySettings } from './adminEntitySettings.js'
|
|
6
|
+
import { toCamelCase } from './camelCase.js'
|
|
7
|
+
import { getEnabledSlugs } from './getEnabledSlugs.js'
|
|
8
|
+
/**
|
|
9
|
+
* Creates MCP API key permission fields using collections or globals.
|
|
10
|
+
* Generates collapsible field groups with checkboxes for each enabled operation.
|
|
11
|
+
*
|
|
12
|
+
* @param config - The collections or globals configuration object
|
|
13
|
+
* @param configType - The type of configuration ('collection' or 'global')
|
|
14
|
+
* @returns Array of fields for the MCP API Keys collection
|
|
15
|
+
*/
|
|
16
|
+
export const createApiKeyFields = ({
|
|
17
|
+
config,
|
|
18
|
+
configType,
|
|
19
|
+
}: {
|
|
20
|
+
config: EntityConfig | undefined
|
|
21
|
+
configType: 'collection' | 'global'
|
|
22
|
+
}): Field[] => {
|
|
23
|
+
const operations = adminEntitySettings[configType]
|
|
24
|
+
const enabledSlugs = getEnabledSlugs(config, configType)
|
|
25
|
+
|
|
26
|
+
return enabledSlugs.map((slug) => {
|
|
27
|
+
const entityConfig = config?.[slug]
|
|
28
|
+
|
|
29
|
+
const enabledOperations = operations.filter((operation) => {
|
|
30
|
+
// If fully enabled, all operations are available
|
|
31
|
+
if (entityConfig?.enabled === true) {
|
|
32
|
+
return true
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// If partially enabled, check if this specific operation is enabled
|
|
36
|
+
const enabled = entityConfig?.enabled
|
|
37
|
+
if (typeof enabled !== 'boolean' && enabled) {
|
|
38
|
+
const operationEnabled = enabled[operation.name as keyof typeof enabled]
|
|
39
|
+
return typeof operationEnabled === 'boolean' && operationEnabled === true
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return false
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
// Generate checkbox fields for each enabled operation
|
|
46
|
+
const operationFields = enabledOperations.map((operation) => ({
|
|
47
|
+
name: operation.name,
|
|
48
|
+
type: 'checkbox' as const,
|
|
49
|
+
admin: {
|
|
50
|
+
description: operation.description(slug),
|
|
51
|
+
},
|
|
52
|
+
defaultValue: false,
|
|
53
|
+
label: operation.label,
|
|
54
|
+
}))
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
type: 'collapsible' as const,
|
|
58
|
+
admin: {
|
|
59
|
+
position: 'sidebar' as const,
|
|
60
|
+
},
|
|
61
|
+
fields: [
|
|
62
|
+
{
|
|
63
|
+
name: toCamelCase(slug),
|
|
64
|
+
type: 'group' as const,
|
|
65
|
+
fields: operationFields,
|
|
66
|
+
label: configType,
|
|
67
|
+
},
|
|
68
|
+
],
|
|
69
|
+
label: `${slug.charAt(0).toUpperCase() + toCamelCase(slug).slice(1)}`,
|
|
70
|
+
}
|
|
71
|
+
})
|
|
72
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { EntityConfig } from '../types.js'
|
|
2
|
+
|
|
3
|
+
import { adminEntitySettings } from './adminEntitySettings.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Extracts enabled slugs from collections or globals configuration.
|
|
7
|
+
* A slug is considered enabled if:
|
|
8
|
+
* 1. enabled is set to true (fully enabled)
|
|
9
|
+
* 2. enabled is an object with at least one operation set to true
|
|
10
|
+
*
|
|
11
|
+
* @param config - The collections or globals configuration object
|
|
12
|
+
* @param configType - The type of configuration ('collection' or 'global')
|
|
13
|
+
* @returns Array of enabled slugs
|
|
14
|
+
*/
|
|
15
|
+
export const getEnabledSlugs = (
|
|
16
|
+
config: EntityConfig | undefined,
|
|
17
|
+
configType: 'collection' | 'global',
|
|
18
|
+
): string[] => {
|
|
19
|
+
return Object.keys(config || {}).filter((slug) => {
|
|
20
|
+
const entityConfig = config?.[slug]
|
|
21
|
+
const operations = adminEntitySettings[configType]
|
|
22
|
+
|
|
23
|
+
// Check if fully enabled (boolean true)
|
|
24
|
+
const fullyEnabled =
|
|
25
|
+
typeof entityConfig?.enabled === 'boolean' && entityConfig?.enabled === true
|
|
26
|
+
|
|
27
|
+
if (fullyEnabled) {
|
|
28
|
+
return true
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Check if partially enabled (at least one operation is enabled)
|
|
32
|
+
const enabled = entityConfig?.enabled
|
|
33
|
+
if (typeof enabled !== 'boolean' && enabled) {
|
|
34
|
+
return operations.some((operation) => {
|
|
35
|
+
const operationEnabled = enabled[operation.name as keyof typeof enabled]
|
|
36
|
+
return typeof operationEnabled === 'boolean' && operationEnabled === true
|
|
37
|
+
})
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return false
|
|
41
|
+
})
|
|
42
|
+
}
|