@payloadcms/plugin-mcp 0.0.1-alpha.0
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/LICENSE.md +22 -0
- package/README.md +7 -0
- package/dist/collections/createApiKeysCollection.d.ts +7 -0
- package/dist/collections/createApiKeysCollection.d.ts.map +1 -0
- package/dist/collections/createApiKeysCollection.js +315 -0
- package/dist/collections/createApiKeysCollection.js.map +1 -0
- package/dist/endpoints/mcp.d.ts +4 -0
- package/dist/endpoints/mcp.d.ts.map +1 -0
- package/dist/endpoints/mcp.js +44 -0
- package/dist/endpoints/mcp.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +67 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/createRequest.d.ts +3 -0
- package/dist/mcp/createRequest.d.ts.map +1 -0
- package/dist/mcp/createRequest.js +14 -0
- package/dist/mcp/createRequest.js.map +1 -0
- package/dist/mcp/getMcpHandler.d.ts +4 -0
- package/dist/mcp/getMcpHandler.d.ts.map +1 -0
- package/dist/mcp/getMcpHandler.js +179 -0
- package/dist/mcp/getMcpHandler.js.map +1 -0
- package/dist/mcp/helpers/config.d.ts +30 -0
- package/dist/mcp/helpers/config.d.ts.map +1 -0
- package/dist/mcp/helpers/config.js +217 -0
- package/dist/mcp/helpers/config.js.map +1 -0
- package/dist/mcp/helpers/conversion.d.ts +2 -0
- package/dist/mcp/helpers/conversion.d.ts.map +1 -0
- package/dist/mcp/helpers/conversion.js +5 -0
- package/dist/mcp/helpers/conversion.js.map +1 -0
- package/dist/mcp/helpers/fields.d.ts +38 -0
- package/dist/mcp/helpers/fields.d.ts.map +1 -0
- package/dist/mcp/helpers/fields.js +96 -0
- package/dist/mcp/helpers/fields.js.map +1 -0
- package/dist/mcp/helpers/fileValidation.d.ts +69 -0
- package/dist/mcp/helpers/fileValidation.d.ts.map +1 -0
- package/dist/mcp/helpers/fileValidation.js +305 -0
- package/dist/mcp/helpers/fileValidation.js.map +1 -0
- package/dist/mcp/helpers/validation.d.ts +9 -0
- package/dist/mcp/helpers/validation.d.ts.map +1 -0
- package/dist/mcp/helpers/validation.js +22 -0
- package/dist/mcp/helpers/validation.js.map +1 -0
- package/dist/mcp/registerTool.d.ts +6 -0
- package/dist/mcp/registerTool.d.ts.map +1 -0
- package/dist/mcp/registerTool.js +18 -0
- package/dist/mcp/registerTool.js.map +1 -0
- package/dist/mcp/tools/auth/auth.d.ts +4 -0
- package/dist/mcp/tools/auth/auth.d.ts.map +1 -0
- package/dist/mcp/tools/auth/auth.js +54 -0
- package/dist/mcp/tools/auth/auth.js.map +1 -0
- package/dist/mcp/tools/auth/forgotPassword.d.ts +4 -0
- package/dist/mcp/tools/auth/forgotPassword.d.ts.map +1 -0
- package/dist/mcp/tools/auth/forgotPassword.js +45 -0
- package/dist/mcp/tools/auth/forgotPassword.js.map +1 -0
- package/dist/mcp/tools/auth/login.d.ts +4 -0
- package/dist/mcp/tools/auth/login.d.ts.map +1 -0
- package/dist/mcp/tools/auth/login.js +48 -0
- package/dist/mcp/tools/auth/login.js.map +1 -0
- package/dist/mcp/tools/auth/resetPassword.d.ts +4 -0
- package/dist/mcp/tools/auth/resetPassword.d.ts.map +1 -0
- package/dist/mcp/tools/auth/resetPassword.js +46 -0
- package/dist/mcp/tools/auth/resetPassword.js.map +1 -0
- package/dist/mcp/tools/auth/unlock.d.ts +4 -0
- package/dist/mcp/tools/auth/unlock.d.ts.map +1 -0
- package/dist/mcp/tools/auth/unlock.js +45 -0
- package/dist/mcp/tools/auth/unlock.js.map +1 -0
- package/dist/mcp/tools/auth/verify.d.ts +4 -0
- package/dist/mcp/tools/auth/verify.d.ts.map +1 -0
- package/dist/mcp/tools/auth/verify.js +42 -0
- package/dist/mcp/tools/auth/verify.js.map +1 -0
- package/dist/mcp/tools/collection/create.d.ts +10 -0
- package/dist/mcp/tools/collection/create.d.ts.map +1 -0
- package/dist/mcp/tools/collection/create.js +159 -0
- package/dist/mcp/tools/collection/create.js.map +1 -0
- package/dist/mcp/tools/collection/delete.d.ts +10 -0
- package/dist/mcp/tools/collection/delete.d.ts.map +1 -0
- package/dist/mcp/tools/collection/delete.js +162 -0
- package/dist/mcp/tools/collection/delete.js.map +1 -0
- package/dist/mcp/tools/collection/find.d.ts +10 -0
- package/dist/mcp/tools/collection/find.d.ts.map +1 -0
- package/dist/mcp/tools/collection/find.js +162 -0
- package/dist/mcp/tools/collection/find.js.map +1 -0
- package/dist/mcp/tools/collection/update.d.ts +10 -0
- package/dist/mcp/tools/collection/update.d.ts.map +1 -0
- package/dist/mcp/tools/collection/update.js +206 -0
- package/dist/mcp/tools/collection/update.js.map +1 -0
- package/dist/mcp/tools/config/find.d.ts +10 -0
- package/dist/mcp/tools/config/find.d.ts.map +1 -0
- package/dist/mcp/tools/config/find.js +94 -0
- package/dist/mcp/tools/config/find.js.map +1 -0
- package/dist/mcp/tools/config/update.d.ts +10 -0
- package/dist/mcp/tools/config/update.d.ts.map +1 -0
- package/dist/mcp/tools/config/update.js +212 -0
- package/dist/mcp/tools/config/update.js.map +1 -0
- package/dist/mcp/tools/job/create.d.ts +10 -0
- package/dist/mcp/tools/job/create.d.ts.map +1 -0
- package/dist/mcp/tools/job/create.js +293 -0
- package/dist/mcp/tools/job/create.js.map +1 -0
- package/dist/mcp/tools/job/run.d.ts +10 -0
- package/dist/mcp/tools/job/run.d.ts.map +1 -0
- package/dist/mcp/tools/job/run.js +147 -0
- package/dist/mcp/tools/job/run.js.map +1 -0
- package/dist/mcp/tools/job/update.d.ts +11 -0
- package/dist/mcp/tools/job/update.d.ts.map +1 -0
- package/dist/mcp/tools/job/update.js +211 -0
- package/dist/mcp/tools/job/update.js.map +1 -0
- package/dist/mcp/tools/resource/create.d.ts +6 -0
- package/dist/mcp/tools/resource/create.d.ts.map +1 -0
- package/dist/mcp/tools/resource/create.js +75 -0
- package/dist/mcp/tools/resource/create.js.map +1 -0
- package/dist/mcp/tools/resource/delete.d.ts +5 -0
- package/dist/mcp/tools/resource/delete.d.ts.map +1 -0
- package/dist/mcp/tools/resource/delete.js +140 -0
- package/dist/mcp/tools/resource/delete.js.map +1 -0
- package/dist/mcp/tools/resource/find.d.ts +5 -0
- package/dist/mcp/tools/resource/find.d.ts.map +1 -0
- package/dist/mcp/tools/resource/find.js +119 -0
- package/dist/mcp/tools/resource/find.js.map +1 -0
- package/dist/mcp/tools/resource/update.d.ts +6 -0
- package/dist/mcp/tools/resource/update.d.ts.map +1 -0
- package/dist/mcp/tools/resource/update.js +201 -0
- package/dist/mcp/tools/resource/update.js.map +1 -0
- package/dist/mcp/tools/schemas.d.ts +374 -0
- package/dist/mcp/tools/schemas.d.ts.map +1 -0
- package/dist/mcp/tools/schemas.js +201 -0
- package/dist/mcp/tools/schemas.js.map +1 -0
- package/dist/types.d.ts +379 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/camelCase.d.ts +9 -0
- package/dist/utils/camelCase.d.ts.map +1 -0
- package/dist/utils/camelCase.js +11 -0
- package/dist/utils/camelCase.js.map +1 -0
- package/dist/utils/convertCollectionSchemaToZod.d.ts +3 -0
- package/dist/utils/convertCollectionSchemaToZod.d.ts.map +1 -0
- package/dist/utils/convertCollectionSchemaToZod.js +30 -0
- package/dist/utils/convertCollectionSchemaToZod.js.map +1 -0
- package/package.json +64 -0
- package/src/collections/createApiKeysCollection.ts +393 -0
- package/src/endpoints/mcp.ts +60 -0
- package/src/index.ts +86 -0
- package/src/mcp/createRequest.ts +13 -0
- package/src/mcp/getMcpHandler.ts +433 -0
- package/src/mcp/helpers/config.ts +326 -0
- package/src/mcp/helpers/conversion.ts +3 -0
- package/src/mcp/helpers/fields.ts +158 -0
- package/src/mcp/helpers/fileValidation.ts +417 -0
- package/src/mcp/helpers/validation.ts +32 -0
- package/src/mcp/registerTool.ts +22 -0
- package/src/mcp/tools/auth/auth.ts +69 -0
- package/src/mcp/tools/auth/forgotPassword.ts +68 -0
- package/src/mcp/tools/auth/login.ts +70 -0
- package/src/mcp/tools/auth/resetPassword.ts +59 -0
- package/src/mcp/tools/auth/unlock.ts +62 -0
- package/src/mcp/tools/auth/verify.ts +55 -0
- package/src/mcp/tools/collection/create.ts +236 -0
- package/src/mcp/tools/collection/delete.ts +227 -0
- package/src/mcp/tools/collection/find.ts +222 -0
- package/src/mcp/tools/collection/update.ts +288 -0
- package/src/mcp/tools/config/find.ts +126 -0
- package/src/mcp/tools/config/update.ts +282 -0
- package/src/mcp/tools/job/create.ts +420 -0
- package/src/mcp/tools/job/run.ts +189 -0
- package/src/mcp/tools/job/update.ts +319 -0
- package/src/mcp/tools/resource/create.ts +121 -0
- package/src/mcp/tools/resource/delete.ts +210 -0
- package/src/mcp/tools/resource/find.ts +194 -0
- package/src/mcp/tools/resource/update.ts +314 -0
- package/src/mcp/tools/schemas.ts +373 -0
- package/src/types.ts +405 -0
- package/src/utils/camelCase.ts +12 -0
- package/src/utils/convertCollectionSchemaToZod.ts +35 -0
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
2
|
+
import type { PayloadRequest } from 'payload'
|
|
3
|
+
|
|
4
|
+
import { readdirSync, readFileSync, statSync } from 'fs'
|
|
5
|
+
import { extname, join } from 'path'
|
|
6
|
+
|
|
7
|
+
import { toolSchemas } from '../schemas.js'
|
|
8
|
+
|
|
9
|
+
export const readCollections = (
|
|
10
|
+
req: PayloadRequest,
|
|
11
|
+
verboseLogs: boolean,
|
|
12
|
+
collectionsDirPath: string,
|
|
13
|
+
collectionName?: string,
|
|
14
|
+
includeContent: boolean = false,
|
|
15
|
+
includeCount: boolean = false,
|
|
16
|
+
) => {
|
|
17
|
+
const payload = req.payload
|
|
18
|
+
|
|
19
|
+
if (verboseLogs) {
|
|
20
|
+
payload.logger.info(
|
|
21
|
+
`[payload-mcp] Reading collections${collectionName ? ` for: ${collectionName}` : ''}, includeContent: ${includeContent}, includeCount: ${includeCount}`,
|
|
22
|
+
)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// Read specific Collection (optional)
|
|
27
|
+
if (collectionName) {
|
|
28
|
+
const fileName = `${collectionName.charAt(0).toUpperCase() + collectionName.slice(1)}.ts`
|
|
29
|
+
const filePath = join(collectionsDirPath, fileName)
|
|
30
|
+
|
|
31
|
+
if (!filePath.startsWith(collectionsDirPath)) {
|
|
32
|
+
payload.logger.error(`[payload-mcp] Invalid collection name attempted: ${collectionName}`)
|
|
33
|
+
return {
|
|
34
|
+
content: [{ type: 'text' as const, text: 'Error: Invalid collection name' }],
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
try {
|
|
39
|
+
const content = readFileSync(filePath, 'utf8')
|
|
40
|
+
if (verboseLogs) {
|
|
41
|
+
payload.logger.info(`[payload-mcp] Successfully read collection: ${collectionName}`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
content: [
|
|
46
|
+
{
|
|
47
|
+
type: 'text' as const,
|
|
48
|
+
text: `Collection: ${collectionName}
|
|
49
|
+
File: ${fileName}
|
|
50
|
+
---
|
|
51
|
+
${content}`,
|
|
52
|
+
},
|
|
53
|
+
],
|
|
54
|
+
}
|
|
55
|
+
} catch (_error) {
|
|
56
|
+
payload.logger.warn(`[payload-mcp] Collection not found: ${collectionName}`)
|
|
57
|
+
return {
|
|
58
|
+
content: [
|
|
59
|
+
{
|
|
60
|
+
type: 'text' as const,
|
|
61
|
+
text: `Error: Collection '${collectionName}' not found`,
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Read all Collections
|
|
69
|
+
const files = readdirSync(collectionsDirPath)
|
|
70
|
+
.filter((file) => extname(file) === '.ts')
|
|
71
|
+
.sort()
|
|
72
|
+
|
|
73
|
+
if (verboseLogs) {
|
|
74
|
+
payload.logger.info(`[payload-mcp] Found ${files.length} collection files in directory`)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (files.length === 0) {
|
|
78
|
+
payload.logger.warn('[payload-mcp] No collection files found in src/collections directory')
|
|
79
|
+
return {
|
|
80
|
+
content: [
|
|
81
|
+
{
|
|
82
|
+
type: 'text' as const,
|
|
83
|
+
text: 'No collection files found in src/collections directory',
|
|
84
|
+
},
|
|
85
|
+
],
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const results = []
|
|
90
|
+
|
|
91
|
+
// Build complete table as a single markdown string
|
|
92
|
+
let tableContent = `Found ${files.length} collection file(s):\n\n`
|
|
93
|
+
|
|
94
|
+
// Build table header
|
|
95
|
+
let tableHeader = '| Collection | File | Size | Modified'
|
|
96
|
+
let tableSeparator = '|------------|------|------|----------'
|
|
97
|
+
|
|
98
|
+
if (includeCount) {
|
|
99
|
+
tableHeader += ' | Documents'
|
|
100
|
+
tableSeparator += ' |----------'
|
|
101
|
+
}
|
|
102
|
+
tableHeader += ' |'
|
|
103
|
+
tableSeparator += ' |'
|
|
104
|
+
|
|
105
|
+
tableContent += tableHeader + '\n'
|
|
106
|
+
tableContent += tableSeparator + '\n'
|
|
107
|
+
|
|
108
|
+
for (const file of files) {
|
|
109
|
+
const filePath = join(collectionsDirPath, file)
|
|
110
|
+
const stats = statSync(filePath)
|
|
111
|
+
const fileSize = stats.size
|
|
112
|
+
const lastModified = stats.mtime
|
|
113
|
+
|
|
114
|
+
const collectionName = file.replace('.ts', '')
|
|
115
|
+
|
|
116
|
+
// Build table row
|
|
117
|
+
let tableRow = `| **${collectionName}** | ${file} | ${fileSize.toLocaleString()} bytes | ${lastModified.toISOString()}`
|
|
118
|
+
|
|
119
|
+
// Add document count if requested
|
|
120
|
+
if (includeCount) {
|
|
121
|
+
try {
|
|
122
|
+
// For now, we'll skip document counting since we don't have access to payload instance
|
|
123
|
+
tableRow += ' | -'
|
|
124
|
+
} catch (error) {
|
|
125
|
+
tableRow += ` | Error: ${(error as Error).message}`
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
tableRow += ' |'
|
|
129
|
+
|
|
130
|
+
tableContent += tableRow + '\n'
|
|
131
|
+
|
|
132
|
+
if (includeContent) {
|
|
133
|
+
try {
|
|
134
|
+
const content = readFileSync(filePath, 'utf8')
|
|
135
|
+
tableContent += `\n**${collectionName} Content:**\n\`\`\`typescript\n${content}\n\`\`\`\n\n`
|
|
136
|
+
} catch (error) {
|
|
137
|
+
tableContent += `\nError reading content: ${(error as Error).message}\n\n`
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
results.push({
|
|
143
|
+
type: 'text' as const,
|
|
144
|
+
text: tableContent,
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
content: results,
|
|
149
|
+
}
|
|
150
|
+
} catch (error) {
|
|
151
|
+
const errorMessage = (error as Error).message
|
|
152
|
+
payload.logger.error(`[payload-mcp] Error reading collections: ${errorMessage}`)
|
|
153
|
+
return {
|
|
154
|
+
content: [
|
|
155
|
+
{
|
|
156
|
+
type: 'text' as const,
|
|
157
|
+
text: `❌ **Error reading collections**: ${errorMessage}`,
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// MCP Server tool registration
|
|
165
|
+
export const findCollectionTool = (
|
|
166
|
+
server: McpServer,
|
|
167
|
+
req: PayloadRequest,
|
|
168
|
+
verboseLogs: boolean,
|
|
169
|
+
collectionsDirPath: string,
|
|
170
|
+
) => {
|
|
171
|
+
const tool = (
|
|
172
|
+
collectionName?: string,
|
|
173
|
+
includeContent: boolean = false,
|
|
174
|
+
includeCount: boolean = false,
|
|
175
|
+
) => {
|
|
176
|
+
const payload = req.payload
|
|
177
|
+
|
|
178
|
+
if (verboseLogs) {
|
|
179
|
+
payload.logger.info(
|
|
180
|
+
`[payload-mcp] Finding collections${collectionName ? ` for: ${collectionName}` : ''}, includeContent: ${includeContent}, includeCount: ${includeCount}`,
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
const result = readCollections(
|
|
186
|
+
req,
|
|
187
|
+
verboseLogs,
|
|
188
|
+
collectionsDirPath,
|
|
189
|
+
collectionName,
|
|
190
|
+
includeContent,
|
|
191
|
+
includeCount,
|
|
192
|
+
)
|
|
193
|
+
|
|
194
|
+
if (verboseLogs) {
|
|
195
|
+
payload.logger.info(`[payload-mcp] Collection search completed`)
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return result
|
|
199
|
+
} catch (error) {
|
|
200
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
201
|
+
payload.logger.error(`[payload-mcp] Error finding collections: ${errorMessage}`)
|
|
202
|
+
|
|
203
|
+
return {
|
|
204
|
+
content: [
|
|
205
|
+
{
|
|
206
|
+
type: 'text' as const,
|
|
207
|
+
text: `Error finding collections: ${errorMessage}`,
|
|
208
|
+
},
|
|
209
|
+
],
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
server.tool(
|
|
215
|
+
'findCollections',
|
|
216
|
+
toolSchemas.findCollections.description,
|
|
217
|
+
toolSchemas.findCollections.parameters.shape,
|
|
218
|
+
({ collectionName, includeContent, includeCount }) => {
|
|
219
|
+
return tool(collectionName, includeContent, includeCount)
|
|
220
|
+
},
|
|
221
|
+
)
|
|
222
|
+
}
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
2
|
+
import type { PayloadRequest } from 'payload'
|
|
3
|
+
|
|
4
|
+
import { readFileSync, writeFileSync } from 'fs'
|
|
5
|
+
import { join } from 'path'
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
addFieldsToCollection,
|
|
9
|
+
modifyFieldsInCollection,
|
|
10
|
+
removeFieldsFromCollection,
|
|
11
|
+
} from '../../helpers/fields.js'
|
|
12
|
+
import { validateCollectionFile } from '../../helpers/fileValidation.js'
|
|
13
|
+
import { toolSchemas } from '../schemas.js'
|
|
14
|
+
|
|
15
|
+
export const updateCollection = async (
|
|
16
|
+
req: PayloadRequest,
|
|
17
|
+
verboseLogs: boolean,
|
|
18
|
+
collectionsDirPath: string,
|
|
19
|
+
configFilePath: string,
|
|
20
|
+
collectionName: string,
|
|
21
|
+
updateType: string,
|
|
22
|
+
newFields?: any[],
|
|
23
|
+
fieldNamesToRemove?: string[],
|
|
24
|
+
fieldModifications?: any[],
|
|
25
|
+
configUpdates?: any,
|
|
26
|
+
newContent?: string,
|
|
27
|
+
) => {
|
|
28
|
+
const payload = req.payload
|
|
29
|
+
if (verboseLogs) {
|
|
30
|
+
payload.logger.info(
|
|
31
|
+
`[payload-mcp] Updating collection: ${collectionName}, updateType: ${updateType}`,
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const capitalizedName = collectionName.charAt(0).toUpperCase() + collectionName.slice(1)
|
|
36
|
+
const fileName = `${capitalizedName}.ts`
|
|
37
|
+
const filePath = join(collectionsDirPath, fileName)
|
|
38
|
+
|
|
39
|
+
// Security check: ensure we're working with the collections directory
|
|
40
|
+
if (!filePath.startsWith(collectionsDirPath)) {
|
|
41
|
+
payload.logger.error(`[payload-mcp] Invalid collection path attempted: ${filePath}`)
|
|
42
|
+
return {
|
|
43
|
+
content: [
|
|
44
|
+
{
|
|
45
|
+
type: 'text' as const,
|
|
46
|
+
text: '❌ **Error**: Invalid collection path',
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
try {
|
|
53
|
+
// Check if collection file exists
|
|
54
|
+
let currentContent: string
|
|
55
|
+
try {
|
|
56
|
+
currentContent = readFileSync(filePath, 'utf8')
|
|
57
|
+
} catch (_ignore) {
|
|
58
|
+
return {
|
|
59
|
+
content: [
|
|
60
|
+
{
|
|
61
|
+
type: 'text' as const,
|
|
62
|
+
text: `❌ **Error**: Collection file not found: ${fileName}`,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
let updatedContent: string
|
|
69
|
+
let updateSummary: string[] = []
|
|
70
|
+
|
|
71
|
+
switch (updateType) {
|
|
72
|
+
case 'add_field':
|
|
73
|
+
if (!newFields || newFields.length === 0) {
|
|
74
|
+
return {
|
|
75
|
+
content: [
|
|
76
|
+
{
|
|
77
|
+
type: 'text' as const,
|
|
78
|
+
text: '❌ **Error**: No fields provided for add_field update type',
|
|
79
|
+
},
|
|
80
|
+
],
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
updatedContent = addFieldsToCollection(currentContent, newFields)
|
|
84
|
+
updateSummary = newFields.map((field: any) => `Added field: ${field.name} (${field.type})`)
|
|
85
|
+
break
|
|
86
|
+
|
|
87
|
+
case 'modify_field':
|
|
88
|
+
if (!fieldModifications || fieldModifications.length === 0) {
|
|
89
|
+
return {
|
|
90
|
+
content: [
|
|
91
|
+
{
|
|
92
|
+
type: 'text' as const,
|
|
93
|
+
text: '❌ **Error**: No field modifications provided for modify_field update type',
|
|
94
|
+
},
|
|
95
|
+
],
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
updatedContent = modifyFieldsInCollection(currentContent, fieldModifications)
|
|
99
|
+
updateSummary = fieldModifications.map((mod: any) => `Modified field: ${mod.fieldName}`)
|
|
100
|
+
break
|
|
101
|
+
|
|
102
|
+
case 'remove_field':
|
|
103
|
+
if (!fieldNamesToRemove || fieldNamesToRemove.length === 0) {
|
|
104
|
+
return {
|
|
105
|
+
content: [
|
|
106
|
+
{
|
|
107
|
+
type: 'text' as const,
|
|
108
|
+
text: '❌ **Error**: No field names provided for remove_field update type',
|
|
109
|
+
},
|
|
110
|
+
],
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
updatedContent = removeFieldsFromCollection(currentContent, fieldNamesToRemove)
|
|
114
|
+
updateSummary = fieldNamesToRemove.map((fieldName: string) => `Removed field: ${fieldName}`)
|
|
115
|
+
break
|
|
116
|
+
|
|
117
|
+
case 'replace_content':
|
|
118
|
+
if (!newContent) {
|
|
119
|
+
return {
|
|
120
|
+
content: [
|
|
121
|
+
{
|
|
122
|
+
type: 'text' as const,
|
|
123
|
+
text: '❌ **Error**: No new content provided for replace_content update type',
|
|
124
|
+
},
|
|
125
|
+
],
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
updatedContent = newContent
|
|
129
|
+
updateSummary = ['Replaced entire collection content']
|
|
130
|
+
break
|
|
131
|
+
|
|
132
|
+
case 'update_config':
|
|
133
|
+
if (!configUpdates) {
|
|
134
|
+
return {
|
|
135
|
+
content: [
|
|
136
|
+
{
|
|
137
|
+
type: 'text' as const,
|
|
138
|
+
text: '❌ **Error**: No config updates provided for update_config update type',
|
|
139
|
+
},
|
|
140
|
+
],
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// For now, we'll use a simple approach since the config helper might not have this functionality
|
|
144
|
+
updatedContent = currentContent
|
|
145
|
+
updateSummary = Object.keys(configUpdates).map((key) => `Updated config: ${key}`)
|
|
146
|
+
break
|
|
147
|
+
|
|
148
|
+
default:
|
|
149
|
+
return {
|
|
150
|
+
content: [
|
|
151
|
+
{
|
|
152
|
+
type: 'text' as const,
|
|
153
|
+
text: `❌ **Error**: Unknown update type: ${updateType}`,
|
|
154
|
+
},
|
|
155
|
+
],
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Write the updated content back to the file
|
|
160
|
+
writeFileSync(filePath, updatedContent, 'utf8')
|
|
161
|
+
if (verboseLogs) {
|
|
162
|
+
payload.logger.info(`[payload-mcp] Successfully updated collection file: ${filePath}`)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Validate the updated file
|
|
166
|
+
const validationResult = await validateCollectionFile(fileName)
|
|
167
|
+
if (validationResult.error) {
|
|
168
|
+
return {
|
|
169
|
+
content: [
|
|
170
|
+
{
|
|
171
|
+
type: 'text' as const,
|
|
172
|
+
text: `❌ **Error**: Updated collection has validation issues:\n\n${validationResult.error}`,
|
|
173
|
+
},
|
|
174
|
+
],
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
return {
|
|
179
|
+
content: [
|
|
180
|
+
{
|
|
181
|
+
type: 'text' as const,
|
|
182
|
+
text: `✅ **Collection updated successfully!**
|
|
183
|
+
|
|
184
|
+
**File**: \`${fileName}\`
|
|
185
|
+
**Update Type**: ${updateType}
|
|
186
|
+
|
|
187
|
+
**Changes Made**:
|
|
188
|
+
${updateSummary.map((summary) => `- ${summary}`).join('\n')}
|
|
189
|
+
|
|
190
|
+
**Updated Collection Code:**
|
|
191
|
+
\`\`\`typescript
|
|
192
|
+
${updatedContent}
|
|
193
|
+
\`\`\``,
|
|
194
|
+
},
|
|
195
|
+
],
|
|
196
|
+
}
|
|
197
|
+
} catch (error) {
|
|
198
|
+
const errorMessage = (error as Error).message
|
|
199
|
+
payload.logger.error(`[payload-mcp] Error updating collection: ${errorMessage}`)
|
|
200
|
+
return {
|
|
201
|
+
content: [
|
|
202
|
+
{
|
|
203
|
+
type: 'text' as const,
|
|
204
|
+
text: `❌ **Error updating collection**: ${errorMessage}`,
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export const updateCollectionTool = (
|
|
212
|
+
server: McpServer,
|
|
213
|
+
req: PayloadRequest,
|
|
214
|
+
verboseLogs: boolean,
|
|
215
|
+
collectionsDirPath: string,
|
|
216
|
+
configFilePath: string,
|
|
217
|
+
) => {
|
|
218
|
+
const tool = async ({
|
|
219
|
+
collectionName,
|
|
220
|
+
configUpdates,
|
|
221
|
+
fieldModifications,
|
|
222
|
+
fieldNamesToRemove,
|
|
223
|
+
newContent,
|
|
224
|
+
newFields,
|
|
225
|
+
updateType,
|
|
226
|
+
}: {
|
|
227
|
+
collectionName: string
|
|
228
|
+
configUpdates?: any
|
|
229
|
+
fieldModifications?: any[]
|
|
230
|
+
fieldNamesToRemove?: string[]
|
|
231
|
+
newContent?: string
|
|
232
|
+
newFields?: any[]
|
|
233
|
+
updateType: string
|
|
234
|
+
}) => {
|
|
235
|
+
const payload = req.payload
|
|
236
|
+
|
|
237
|
+
if (verboseLogs) {
|
|
238
|
+
payload.logger.info(
|
|
239
|
+
`[payload-mcp] Updating collection: ${collectionName}, updateType: ${updateType}`,
|
|
240
|
+
)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
try {
|
|
244
|
+
const result = await updateCollection(
|
|
245
|
+
req,
|
|
246
|
+
verboseLogs,
|
|
247
|
+
collectionsDirPath,
|
|
248
|
+
configFilePath,
|
|
249
|
+
collectionName,
|
|
250
|
+
updateType,
|
|
251
|
+
newFields,
|
|
252
|
+
fieldNamesToRemove,
|
|
253
|
+
fieldModifications,
|
|
254
|
+
configUpdates,
|
|
255
|
+
newContent,
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
if (verboseLogs) {
|
|
259
|
+
payload.logger.info(`[payload-mcp] Collection update completed for: ${collectionName}`)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return result
|
|
263
|
+
} catch (error) {
|
|
264
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
265
|
+
payload.logger.error(
|
|
266
|
+
`[payload-mcp] Error updating collection ${collectionName}: ${errorMessage}`,
|
|
267
|
+
)
|
|
268
|
+
|
|
269
|
+
return {
|
|
270
|
+
content: [
|
|
271
|
+
{
|
|
272
|
+
type: 'text' as const,
|
|
273
|
+
text: `Error updating collection "${collectionName}": ${errorMessage}`,
|
|
274
|
+
},
|
|
275
|
+
],
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
server.tool(
|
|
281
|
+
'updateCollection',
|
|
282
|
+
toolSchemas.updateCollection.description,
|
|
283
|
+
toolSchemas.updateCollection.parameters.shape,
|
|
284
|
+
async (args) => {
|
|
285
|
+
return await tool(args)
|
|
286
|
+
},
|
|
287
|
+
)
|
|
288
|
+
}
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
|
2
|
+
import type { PayloadRequest } from 'payload'
|
|
3
|
+
|
|
4
|
+
import { readFileSync, statSync } from 'fs'
|
|
5
|
+
|
|
6
|
+
import { toolSchemas } from '../schemas.js'
|
|
7
|
+
|
|
8
|
+
export const readConfigFile = (
|
|
9
|
+
req: PayloadRequest,
|
|
10
|
+
verboseLogs: boolean,
|
|
11
|
+
configFilePath: string,
|
|
12
|
+
includeMetadata: boolean = false,
|
|
13
|
+
) => {
|
|
14
|
+
const payload = req.payload
|
|
15
|
+
if (verboseLogs) {
|
|
16
|
+
payload.logger.info(`[payload-mcp] Reading config file, includeMetadata: ${includeMetadata}`)
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
try {
|
|
20
|
+
// Security check: ensure we're working with the specified config file
|
|
21
|
+
if (!configFilePath.startsWith(process.cwd()) && !configFilePath.startsWith('/')) {
|
|
22
|
+
payload.logger.error(`[payload-mcp] Invalid config path attempted: ${configFilePath}`)
|
|
23
|
+
return {
|
|
24
|
+
content: [
|
|
25
|
+
{
|
|
26
|
+
type: 'text' as const,
|
|
27
|
+
text: '❌ **Error**: Invalid config path',
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const content = readFileSync(configFilePath, 'utf8')
|
|
34
|
+
const stats = statSync(configFilePath)
|
|
35
|
+
|
|
36
|
+
if (verboseLogs) {
|
|
37
|
+
payload.logger.info(`[payload-mcp] Successfully read config file. Size: ${stats.size} bytes`)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
let responseText = `# Payload Configuration
|
|
41
|
+
|
|
42
|
+
**File**: \`${configFilePath}\``
|
|
43
|
+
|
|
44
|
+
if (includeMetadata) {
|
|
45
|
+
responseText += `
|
|
46
|
+
**Size**: ${stats.size.toLocaleString()} bytes
|
|
47
|
+
**Modified**: ${stats.mtime.toISOString()}
|
|
48
|
+
**Created**: ${stats.birthtime.toISOString()}`
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
responseText += `
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
**Configuration Content:**
|
|
55
|
+
\`\`\`typescript
|
|
56
|
+
${content}
|
|
57
|
+
\`\`\``
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
content: [
|
|
61
|
+
{
|
|
62
|
+
type: 'text' as const,
|
|
63
|
+
text: responseText,
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
const errorMessage = (error as Error).message
|
|
69
|
+
payload.logger.error(`[payload-mcp] Error reading config file: ${errorMessage}`)
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: 'text' as const,
|
|
74
|
+
text: `❌ **Error reading config file**: ${errorMessage}`,
|
|
75
|
+
},
|
|
76
|
+
],
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// MCP Server tool registration
|
|
82
|
+
export const findConfigTool = (
|
|
83
|
+
server: McpServer,
|
|
84
|
+
req: PayloadRequest,
|
|
85
|
+
verboseLogs: boolean,
|
|
86
|
+
configFilePath: string,
|
|
87
|
+
) => {
|
|
88
|
+
const tool = (includeMetadata: boolean = false) => {
|
|
89
|
+
const payload = req.payload
|
|
90
|
+
|
|
91
|
+
if (verboseLogs) {
|
|
92
|
+
payload.logger.info(`[payload-mcp] Finding config, includeMetadata: ${includeMetadata}`)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const result = readConfigFile(req, verboseLogs, configFilePath, includeMetadata)
|
|
97
|
+
|
|
98
|
+
if (verboseLogs) {
|
|
99
|
+
payload.logger.info(`[payload-mcp] Config search completed`)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return result
|
|
103
|
+
} catch (error) {
|
|
104
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error'
|
|
105
|
+
payload.logger.error(`[payload-mcp] Error finding config: ${errorMessage}`)
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
content: [
|
|
109
|
+
{
|
|
110
|
+
type: 'text' as const,
|
|
111
|
+
text: `Error finding config: ${errorMessage}`,
|
|
112
|
+
},
|
|
113
|
+
],
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
server.tool(
|
|
119
|
+
'findConfig',
|
|
120
|
+
toolSchemas.findConfig.description,
|
|
121
|
+
toolSchemas.findConfig.parameters.shape,
|
|
122
|
+
({ includeMetadata }) => {
|
|
123
|
+
return tool(includeMetadata)
|
|
124
|
+
},
|
|
125
|
+
)
|
|
126
|
+
}
|