@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.
Files changed (173) hide show
  1. package/LICENSE.md +22 -0
  2. package/README.md +7 -0
  3. package/dist/collections/createApiKeysCollection.d.ts +7 -0
  4. package/dist/collections/createApiKeysCollection.d.ts.map +1 -0
  5. package/dist/collections/createApiKeysCollection.js +315 -0
  6. package/dist/collections/createApiKeysCollection.js.map +1 -0
  7. package/dist/endpoints/mcp.d.ts +4 -0
  8. package/dist/endpoints/mcp.d.ts.map +1 -0
  9. package/dist/endpoints/mcp.js +44 -0
  10. package/dist/endpoints/mcp.js.map +1 -0
  11. package/dist/index.d.ts +10 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +67 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/mcp/createRequest.d.ts +3 -0
  16. package/dist/mcp/createRequest.d.ts.map +1 -0
  17. package/dist/mcp/createRequest.js +14 -0
  18. package/dist/mcp/createRequest.js.map +1 -0
  19. package/dist/mcp/getMcpHandler.d.ts +4 -0
  20. package/dist/mcp/getMcpHandler.d.ts.map +1 -0
  21. package/dist/mcp/getMcpHandler.js +179 -0
  22. package/dist/mcp/getMcpHandler.js.map +1 -0
  23. package/dist/mcp/helpers/config.d.ts +30 -0
  24. package/dist/mcp/helpers/config.d.ts.map +1 -0
  25. package/dist/mcp/helpers/config.js +217 -0
  26. package/dist/mcp/helpers/config.js.map +1 -0
  27. package/dist/mcp/helpers/conversion.d.ts +2 -0
  28. package/dist/mcp/helpers/conversion.d.ts.map +1 -0
  29. package/dist/mcp/helpers/conversion.js +5 -0
  30. package/dist/mcp/helpers/conversion.js.map +1 -0
  31. package/dist/mcp/helpers/fields.d.ts +38 -0
  32. package/dist/mcp/helpers/fields.d.ts.map +1 -0
  33. package/dist/mcp/helpers/fields.js +96 -0
  34. package/dist/mcp/helpers/fields.js.map +1 -0
  35. package/dist/mcp/helpers/fileValidation.d.ts +69 -0
  36. package/dist/mcp/helpers/fileValidation.d.ts.map +1 -0
  37. package/dist/mcp/helpers/fileValidation.js +305 -0
  38. package/dist/mcp/helpers/fileValidation.js.map +1 -0
  39. package/dist/mcp/helpers/validation.d.ts +9 -0
  40. package/dist/mcp/helpers/validation.d.ts.map +1 -0
  41. package/dist/mcp/helpers/validation.js +22 -0
  42. package/dist/mcp/helpers/validation.js.map +1 -0
  43. package/dist/mcp/registerTool.d.ts +6 -0
  44. package/dist/mcp/registerTool.d.ts.map +1 -0
  45. package/dist/mcp/registerTool.js +18 -0
  46. package/dist/mcp/registerTool.js.map +1 -0
  47. package/dist/mcp/tools/auth/auth.d.ts +4 -0
  48. package/dist/mcp/tools/auth/auth.d.ts.map +1 -0
  49. package/dist/mcp/tools/auth/auth.js +54 -0
  50. package/dist/mcp/tools/auth/auth.js.map +1 -0
  51. package/dist/mcp/tools/auth/forgotPassword.d.ts +4 -0
  52. package/dist/mcp/tools/auth/forgotPassword.d.ts.map +1 -0
  53. package/dist/mcp/tools/auth/forgotPassword.js +45 -0
  54. package/dist/mcp/tools/auth/forgotPassword.js.map +1 -0
  55. package/dist/mcp/tools/auth/login.d.ts +4 -0
  56. package/dist/mcp/tools/auth/login.d.ts.map +1 -0
  57. package/dist/mcp/tools/auth/login.js +48 -0
  58. package/dist/mcp/tools/auth/login.js.map +1 -0
  59. package/dist/mcp/tools/auth/resetPassword.d.ts +4 -0
  60. package/dist/mcp/tools/auth/resetPassword.d.ts.map +1 -0
  61. package/dist/mcp/tools/auth/resetPassword.js +46 -0
  62. package/dist/mcp/tools/auth/resetPassword.js.map +1 -0
  63. package/dist/mcp/tools/auth/unlock.d.ts +4 -0
  64. package/dist/mcp/tools/auth/unlock.d.ts.map +1 -0
  65. package/dist/mcp/tools/auth/unlock.js +45 -0
  66. package/dist/mcp/tools/auth/unlock.js.map +1 -0
  67. package/dist/mcp/tools/auth/verify.d.ts +4 -0
  68. package/dist/mcp/tools/auth/verify.d.ts.map +1 -0
  69. package/dist/mcp/tools/auth/verify.js +42 -0
  70. package/dist/mcp/tools/auth/verify.js.map +1 -0
  71. package/dist/mcp/tools/collection/create.d.ts +10 -0
  72. package/dist/mcp/tools/collection/create.d.ts.map +1 -0
  73. package/dist/mcp/tools/collection/create.js +159 -0
  74. package/dist/mcp/tools/collection/create.js.map +1 -0
  75. package/dist/mcp/tools/collection/delete.d.ts +10 -0
  76. package/dist/mcp/tools/collection/delete.d.ts.map +1 -0
  77. package/dist/mcp/tools/collection/delete.js +162 -0
  78. package/dist/mcp/tools/collection/delete.js.map +1 -0
  79. package/dist/mcp/tools/collection/find.d.ts +10 -0
  80. package/dist/mcp/tools/collection/find.d.ts.map +1 -0
  81. package/dist/mcp/tools/collection/find.js +162 -0
  82. package/dist/mcp/tools/collection/find.js.map +1 -0
  83. package/dist/mcp/tools/collection/update.d.ts +10 -0
  84. package/dist/mcp/tools/collection/update.d.ts.map +1 -0
  85. package/dist/mcp/tools/collection/update.js +206 -0
  86. package/dist/mcp/tools/collection/update.js.map +1 -0
  87. package/dist/mcp/tools/config/find.d.ts +10 -0
  88. package/dist/mcp/tools/config/find.d.ts.map +1 -0
  89. package/dist/mcp/tools/config/find.js +94 -0
  90. package/dist/mcp/tools/config/find.js.map +1 -0
  91. package/dist/mcp/tools/config/update.d.ts +10 -0
  92. package/dist/mcp/tools/config/update.d.ts.map +1 -0
  93. package/dist/mcp/tools/config/update.js +212 -0
  94. package/dist/mcp/tools/config/update.js.map +1 -0
  95. package/dist/mcp/tools/job/create.d.ts +10 -0
  96. package/dist/mcp/tools/job/create.d.ts.map +1 -0
  97. package/dist/mcp/tools/job/create.js +293 -0
  98. package/dist/mcp/tools/job/create.js.map +1 -0
  99. package/dist/mcp/tools/job/run.d.ts +10 -0
  100. package/dist/mcp/tools/job/run.d.ts.map +1 -0
  101. package/dist/mcp/tools/job/run.js +147 -0
  102. package/dist/mcp/tools/job/run.js.map +1 -0
  103. package/dist/mcp/tools/job/update.d.ts +11 -0
  104. package/dist/mcp/tools/job/update.d.ts.map +1 -0
  105. package/dist/mcp/tools/job/update.js +211 -0
  106. package/dist/mcp/tools/job/update.js.map +1 -0
  107. package/dist/mcp/tools/resource/create.d.ts +6 -0
  108. package/dist/mcp/tools/resource/create.d.ts.map +1 -0
  109. package/dist/mcp/tools/resource/create.js +75 -0
  110. package/dist/mcp/tools/resource/create.js.map +1 -0
  111. package/dist/mcp/tools/resource/delete.d.ts +5 -0
  112. package/dist/mcp/tools/resource/delete.d.ts.map +1 -0
  113. package/dist/mcp/tools/resource/delete.js +140 -0
  114. package/dist/mcp/tools/resource/delete.js.map +1 -0
  115. package/dist/mcp/tools/resource/find.d.ts +5 -0
  116. package/dist/mcp/tools/resource/find.d.ts.map +1 -0
  117. package/dist/mcp/tools/resource/find.js +119 -0
  118. package/dist/mcp/tools/resource/find.js.map +1 -0
  119. package/dist/mcp/tools/resource/update.d.ts +6 -0
  120. package/dist/mcp/tools/resource/update.d.ts.map +1 -0
  121. package/dist/mcp/tools/resource/update.js +201 -0
  122. package/dist/mcp/tools/resource/update.js.map +1 -0
  123. package/dist/mcp/tools/schemas.d.ts +374 -0
  124. package/dist/mcp/tools/schemas.d.ts.map +1 -0
  125. package/dist/mcp/tools/schemas.js +201 -0
  126. package/dist/mcp/tools/schemas.js.map +1 -0
  127. package/dist/types.d.ts +379 -0
  128. package/dist/types.d.ts.map +1 -0
  129. package/dist/types.js +3 -0
  130. package/dist/types.js.map +1 -0
  131. package/dist/utils/camelCase.d.ts +9 -0
  132. package/dist/utils/camelCase.d.ts.map +1 -0
  133. package/dist/utils/camelCase.js +11 -0
  134. package/dist/utils/camelCase.js.map +1 -0
  135. package/dist/utils/convertCollectionSchemaToZod.d.ts +3 -0
  136. package/dist/utils/convertCollectionSchemaToZod.d.ts.map +1 -0
  137. package/dist/utils/convertCollectionSchemaToZod.js +30 -0
  138. package/dist/utils/convertCollectionSchemaToZod.js.map +1 -0
  139. package/package.json +64 -0
  140. package/src/collections/createApiKeysCollection.ts +393 -0
  141. package/src/endpoints/mcp.ts +60 -0
  142. package/src/index.ts +86 -0
  143. package/src/mcp/createRequest.ts +13 -0
  144. package/src/mcp/getMcpHandler.ts +433 -0
  145. package/src/mcp/helpers/config.ts +326 -0
  146. package/src/mcp/helpers/conversion.ts +3 -0
  147. package/src/mcp/helpers/fields.ts +158 -0
  148. package/src/mcp/helpers/fileValidation.ts +417 -0
  149. package/src/mcp/helpers/validation.ts +32 -0
  150. package/src/mcp/registerTool.ts +22 -0
  151. package/src/mcp/tools/auth/auth.ts +69 -0
  152. package/src/mcp/tools/auth/forgotPassword.ts +68 -0
  153. package/src/mcp/tools/auth/login.ts +70 -0
  154. package/src/mcp/tools/auth/resetPassword.ts +59 -0
  155. package/src/mcp/tools/auth/unlock.ts +62 -0
  156. package/src/mcp/tools/auth/verify.ts +55 -0
  157. package/src/mcp/tools/collection/create.ts +236 -0
  158. package/src/mcp/tools/collection/delete.ts +227 -0
  159. package/src/mcp/tools/collection/find.ts +222 -0
  160. package/src/mcp/tools/collection/update.ts +288 -0
  161. package/src/mcp/tools/config/find.ts +126 -0
  162. package/src/mcp/tools/config/update.ts +282 -0
  163. package/src/mcp/tools/job/create.ts +420 -0
  164. package/src/mcp/tools/job/run.ts +189 -0
  165. package/src/mcp/tools/job/update.ts +319 -0
  166. package/src/mcp/tools/resource/create.ts +121 -0
  167. package/src/mcp/tools/resource/delete.ts +210 -0
  168. package/src/mcp/tools/resource/find.ts +194 -0
  169. package/src/mcp/tools/resource/update.ts +314 -0
  170. package/src/mcp/tools/schemas.ts +373 -0
  171. package/src/types.ts +405 -0
  172. package/src/utils/camelCase.ts +12 -0
  173. 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
+ }