@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,189 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
+ import type { PayloadRequest } from 'payload'
3
+
4
+ import { toolSchemas } from '../schemas.js'
5
+
6
+ // Reusable function for running jobs
7
+ export const runJob = async (
8
+ req: PayloadRequest,
9
+ verboseLogs: boolean,
10
+ jobSlug: string,
11
+ input: Record<string, any>,
12
+ queue?: string,
13
+ priority?: number,
14
+ delay?: number,
15
+ ) => {
16
+ const payload = req.payload
17
+
18
+ if (verboseLogs) {
19
+ payload.logger.info(`[payload-mcp] Running job: ${jobSlug}`)
20
+ }
21
+
22
+ try {
23
+ // Actually run the job using Payload's job queue
24
+ const jobQueueOptions: Record<string, unknown> = {
25
+ input,
26
+ task: jobSlug,
27
+ }
28
+
29
+ if (queue && queue !== 'default') {
30
+ jobQueueOptions.queue = queue
31
+ if (verboseLogs) {
32
+ payload.logger.info(`[payload-mcp] Using custom queue: ${queue}`)
33
+ }
34
+ }
35
+
36
+ if (priority && priority > 0) {
37
+ jobQueueOptions.priority = priority
38
+ if (verboseLogs) {
39
+ payload.logger.info(`[payload-mcp] Setting job priority: ${priority}`)
40
+ }
41
+ }
42
+
43
+ if (delay && delay > 0) {
44
+ jobQueueOptions.waitUntil = new Date(Date.now() + delay)
45
+ if (verboseLogs) {
46
+ payload.logger.info(`[payload-mcp] Setting job delay: ${delay}ms`)
47
+ }
48
+ }
49
+
50
+ if (verboseLogs) {
51
+ payload.logger.info(
52
+ `[payload-mcp] Queuing job with options: ${JSON.stringify(jobQueueOptions)}`,
53
+ )
54
+ }
55
+
56
+ const job = await payload.jobs.queue(
57
+ jobQueueOptions as Parameters<typeof payload.jobs.queue>[0],
58
+ )
59
+
60
+ const jobId = (job as { id?: string })?.id || 'unknown'
61
+
62
+ if (verboseLogs) {
63
+ payload.logger.info(`[payload-mcp] Job created successfully: ${jobId}`)
64
+ }
65
+
66
+ return {
67
+ content: [
68
+ {
69
+ type: 'text' as const,
70
+ text: `# Job Queued Successfully: ${jobSlug}
71
+
72
+ ## Job Details
73
+ - **Job ID**: ${jobId}
74
+ - **Job Slug**: ${jobSlug}
75
+ - **Queue**: ${queue || 'default'}
76
+ - **Priority**: ${priority || 'default'}
77
+ - **Delay**: ${delay ? `${delay}ms` : 'none'}
78
+ - **Status**: Queued and Running
79
+
80
+ ## Input Data
81
+ \`\`\`json
82
+ ${JSON.stringify(input, null, 2)}
83
+ \`\`\`
84
+
85
+ ## Job Status
86
+ The job has been successfully queued and will be processed according to the queue settings.
87
+
88
+ ## Monitoring the Job
89
+ You can monitor the job status using:
90
+
91
+ \`\`\`typescript
92
+ // Check job status
93
+ const jobStatus = await payload.jobs.status('${jobId}')
94
+ console.log('Job status:', jobStatus)
95
+
96
+ // Wait for completion
97
+ const result = await payload.jobs.wait('${jobId}')
98
+ console.log('Job result:', result)
99
+ \`\`\`
100
+
101
+ ✅ Job successfully queued with ID: ${jobId}`,
102
+ },
103
+ ],
104
+ }
105
+ } catch (error) {
106
+ const errorMsg = (error as Error).message
107
+ payload.logger.error(`[payload-mcp] Error running job "${jobSlug}": ${errorMsg}`)
108
+
109
+ return {
110
+ content: [
111
+ {
112
+ type: 'text' as const,
113
+ text: `❌ Error running job "${jobSlug}": ${errorMsg}
114
+
115
+ ## Common Issues:
116
+ 1. **Job not found**: The job "${jobSlug}" may not be registered in your Payload configuration
117
+ 2. **Invalid input format**: Ensure the input matches the job's input schema
118
+ 3. **Queue not configured**: The queue "${queue || 'default'}" may not be properly set up
119
+ 4. **Permission issues**: Ensure proper access rights for job execution
120
+ 5. **Job handler error**: The job implementation may have errors
121
+
122
+ ## Input Data Provided:
123
+ \`\`\`json
124
+ ${JSON.stringify(input, null, 2)}
125
+ \`\`\`
126
+
127
+ ## Next Steps:
128
+ 1. **Verify job exists**: Check that the job "${jobSlug}" is properly registered
129
+ 2. **Check input format**: Ensure the input data matches the expected schema
130
+ 3. **Review job configuration**: Verify the job is properly configured in your Payload setup
131
+ 4. **Check permissions**: Ensure you have the necessary permissions to run jobs
132
+ 5. **Review error logs**: Check the server logs for more detailed error information
133
+
134
+ ## Troubleshooting:
135
+ - **Job not found**: Verify the job slug and check your jobs configuration
136
+ - **Schema mismatch**: Ensure input data matches the job's input schema
137
+ - **Queue issues**: Check that the specified queue is properly configured
138
+ - **Permission errors**: Verify user permissions for job execution`,
139
+ },
140
+ ],
141
+ }
142
+ }
143
+ }
144
+
145
+ export const runJobTool = (server: McpServer, req: PayloadRequest, verboseLogs: boolean) => {
146
+ const tool = async (
147
+ jobSlug: string,
148
+ input: Record<string, any>,
149
+ queue?: string,
150
+ priority?: number,
151
+ delay?: number,
152
+ ) => {
153
+ if (verboseLogs) {
154
+ req.payload.logger.info(`[payload-mcp] Run Job Tool called with: ${jobSlug}`)
155
+ }
156
+
157
+ try {
158
+ const result = await runJob(req, verboseLogs, jobSlug, input, queue, priority, delay)
159
+
160
+ if (verboseLogs) {
161
+ req.payload.logger.info(`[payload-mcp] Run Job Tool completed successfully`)
162
+ }
163
+
164
+ return result
165
+ } catch (error) {
166
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error'
167
+ req.payload.logger.error(`[payload-mcp] Error in Run Job Tool: ${errorMessage}`)
168
+
169
+ return {
170
+ content: [
171
+ {
172
+ type: 'text' as const,
173
+ text: `❌ **Error in Run Job Tool**: ${errorMessage}`,
174
+ },
175
+ ],
176
+ }
177
+ }
178
+ }
179
+
180
+ server.tool(
181
+ 'runJob',
182
+ 'Runs a Payload job with specified input data and queue options',
183
+ toolSchemas.runJob.parameters.shape,
184
+ async (args) => {
185
+ const { delay, input, jobSlug, priority, queue } = args
186
+ return await tool(jobSlug, input, queue, priority, delay)
187
+ },
188
+ )
189
+ }
@@ -0,0 +1,319 @@
1
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
2
+ import type { PayloadRequest } from 'payload'
3
+
4
+ import { existsSync, readFileSync, writeFileSync } from 'fs'
5
+ import { join } from 'path'
6
+
7
+ import type { JobConfigUpdate, SchemaField, TaskSequenceItem } from '../../../types.js'
8
+
9
+ import { validatePayloadFile } from '../../helpers/fileValidation.js'
10
+ import { toolSchemas } from '../schemas.js'
11
+
12
+ // Reusable function for updating jobs
13
+ export const updateJob = async (
14
+ req: PayloadRequest,
15
+ verboseLogs: boolean,
16
+ jobsDir: string,
17
+ jobSlug: string,
18
+ updateType: string,
19
+ inputSchema?: SchemaField[],
20
+ outputSchema?: SchemaField[],
21
+ taskSequence?: TaskSequenceItem[],
22
+ configUpdate?: JobConfigUpdate,
23
+ handlerCode?: string,
24
+ ) => {
25
+ const payload = req.payload
26
+
27
+ if (verboseLogs) {
28
+ payload.logger.info(`[payload-mcp] Updating job: ${jobSlug} (${updateType})`)
29
+ }
30
+
31
+ try {
32
+ const camelCaseJobSlug = toCamelCase(jobSlug)
33
+
34
+ // Find the job file - check both tasks and workflows
35
+ let filePath: null | string = null
36
+ let jobType: 'task' | 'workflow' | null = null
37
+
38
+ const taskPath = join(jobsDir, 'tasks', `${camelCaseJobSlug}.ts`)
39
+ const workflowPath = join(jobsDir, 'workflows', `${camelCaseJobSlug}.ts`)
40
+
41
+ if (existsSync(taskPath)) {
42
+ filePath = taskPath
43
+ jobType = 'task'
44
+ if (verboseLogs) {
45
+ payload.logger.info(`[payload-mcp] Found task file: ${taskPath}`)
46
+ }
47
+ } else if (existsSync(workflowPath)) {
48
+ filePath = workflowPath
49
+ jobType = 'workflow'
50
+ if (verboseLogs) {
51
+ payload.logger.info(`[payload-mcp] Found workflow file: ${workflowPath}`)
52
+ }
53
+ } else {
54
+ throw new Error(`No task or workflow file found for job slug: ${jobSlug}`)
55
+ }
56
+
57
+ // Read the current file content
58
+ let content = readFileSync(filePath, 'utf8')
59
+ const originalContent = content
60
+
61
+ if (verboseLogs) {
62
+ payload.logger.info(`[payload-mcp] Applying update type: ${updateType}`)
63
+ }
64
+
65
+ // Apply updates based on type
66
+ switch (updateType) {
67
+ case 'change_config':
68
+ if (!configUpdate) {
69
+ throw new Error('config must be provided for change_config')
70
+ }
71
+
72
+ content = updateConfig(content, jobSlug, configUpdate)
73
+ if (verboseLogs) {
74
+ payload.logger.info(`[payload-mcp] Configuration updated successfully`)
75
+ }
76
+ break
77
+
78
+ case 'modify_schema':
79
+ if (!inputSchema && !outputSchema) {
80
+ throw new Error('Either inputSchema or outputSchema must be provided for modify_schema')
81
+ }
82
+
83
+ content = updateSchema(content, camelCaseJobSlug, inputSchema, outputSchema)
84
+ if (verboseLogs) {
85
+ payload.logger.info(`[payload-mcp] Schema updated successfully`)
86
+ }
87
+ break
88
+
89
+ case 'replace_handler':
90
+ if (!handlerCode) {
91
+ throw new Error('handlerCode must be provided for replace_handler')
92
+ }
93
+
94
+ content = updateHandler(content, handlerCode, jobType)
95
+ if (verboseLogs) {
96
+ payload.logger.info(`[payload-mcp] Handler code replaced successfully`)
97
+ }
98
+ break
99
+
100
+ case 'update_tasks':
101
+ if (!taskSequence) {
102
+ throw new Error('taskSequence must be provided for update_tasks')
103
+ }
104
+
105
+ if (jobType !== 'workflow') {
106
+ throw new Error('update_tasks is only supported for workflow jobs')
107
+ }
108
+
109
+ content = updateWorkflowTasks(content, taskSequence)
110
+ if (verboseLogs) {
111
+ payload.logger.info(`[payload-mcp] Workflow tasks updated successfully`)
112
+ }
113
+ break
114
+ }
115
+
116
+ // Only write if content changed
117
+ if (content !== originalContent) {
118
+ if (verboseLogs) {
119
+ payload.logger.info(`[payload-mcp] Writing updated content to file`)
120
+ }
121
+
122
+ // Write the updated content
123
+ writeFileSync(filePath, content)
124
+
125
+ // Validate the updated file
126
+ const fileName = `${camelCaseJobSlug}.ts`
127
+ const validationType = jobType === 'task' ? 'task' : 'workflow'
128
+
129
+ try {
130
+ const validationResult = await validatePayloadFile(fileName, validationType)
131
+
132
+ if (!validationResult.success) {
133
+ if (verboseLogs) {
134
+ payload.logger.warn(`[payload-mcp] Validation warning: ${validationResult.error}`)
135
+ }
136
+
137
+ return {
138
+ content: [
139
+ {
140
+ type: 'text' as const,
141
+ text: `⚠️ **Warning**: Job updated but validation failed:\n\n${validationResult.error}\n\nPlease review the generated code for any syntax errors.`,
142
+ },
143
+ ],
144
+ }
145
+ }
146
+
147
+ if (verboseLogs) {
148
+ payload.logger.info(`[payload-mcp] File validation successful`)
149
+ }
150
+ } catch (validationError) {
151
+ if (verboseLogs) {
152
+ payload.logger.warn(`[payload-mcp] Validation error: ${validationError}`)
153
+ }
154
+
155
+ return {
156
+ content: [
157
+ {
158
+ type: 'text' as const,
159
+ text: `⚠️ **Warning**: Job updated but validation could not be completed:\n\n${validationError}\n\nPlease review the generated code manually.`,
160
+ },
161
+ ],
162
+ }
163
+ }
164
+
165
+ return {
166
+ content: [
167
+ {
168
+ type: 'text' as const,
169
+ text: `✅ **Job updated successfully!**\n\n**Job**: \`${jobSlug}\`\n**Type**: \`${jobType}\`\n**Update**: \`${updateType}\`\n**File**: \`${fileName}\`\n\n**Next steps**:\n1. Restart your development server to load the updated job\n2. Test the updated functionality\n3. Verify the changes meet your requirements`,
170
+ },
171
+ ],
172
+ }
173
+ } else {
174
+ if (verboseLogs) {
175
+ payload.logger.info(`[payload-mcp] No changes detected, file not modified`)
176
+ }
177
+
178
+ return {
179
+ content: [
180
+ {
181
+ type: 'text' as const,
182
+ text: `ℹ️ **No changes made**: The job file was not modified as no changes were detected.\n\n**Job**: \`${jobSlug}\`\n**Type**: \`${jobType}\`\n**Update**: \`${updateType}\``,
183
+ },
184
+ ],
185
+ }
186
+ }
187
+ } catch (error) {
188
+ const errorMessage = (error as Error).message
189
+ payload.logger.error(`[payload-mcp] Error updating job: ${errorMessage}`)
190
+
191
+ return {
192
+ content: [
193
+ {
194
+ type: 'text' as const,
195
+ text: `❌ **Error updating job**: ${errorMessage}`,
196
+ },
197
+ ],
198
+ }
199
+ }
200
+ }
201
+
202
+ // Helper function to convert to camel case
203
+ function toCamelCase(str: string): string {
204
+ return str
205
+ .replace(/[-_\s]+(.)?/g, (_, chr) => (chr ? chr.toUpperCase() : ''))
206
+ .replace(/^(.)/, (_, chr) => chr.toLowerCase())
207
+ }
208
+
209
+ // Helper functions for different update types
210
+ function updateSchema(
211
+ content: string,
212
+ camelCaseJobSlug: string,
213
+ inputSchema?: SchemaField[],
214
+ outputSchema?: SchemaField[],
215
+ ): string {
216
+ // TODO: Implementation for schema updates
217
+ // This would modify the inputSchema and outputSchema in the job file
218
+ return content
219
+ }
220
+
221
+ function updateWorkflowTasks(content: string, taskSequence: TaskSequenceItem[]): string {
222
+ // TODO: Implementation for updating workflow tasks
223
+ // This would modify the steps array in the workflow
224
+ return content
225
+ }
226
+
227
+ function updateConfig(content: string, jobSlug: string, configUpdate: JobConfigUpdate): string {
228
+ // TODO: Implementation for updating job configuration
229
+ // This would modify various config properties
230
+ return content
231
+ }
232
+
233
+ function updateHandler(content: string, handlerCode: string, jobType: 'task' | 'workflow'): string {
234
+ // TODO: Implementation for replacing handler code
235
+ // This would replace the handler function in the job file
236
+ return content
237
+ }
238
+
239
+ export const updateJobTool = (
240
+ server: McpServer,
241
+ req: PayloadRequest,
242
+ verboseLogs: boolean,
243
+ jobsDir: string,
244
+ ) => {
245
+ const tool = async (
246
+ jobSlug: string,
247
+ updateType: string,
248
+ inputSchema?: SchemaField[],
249
+ outputSchema?: SchemaField[],
250
+ taskSequence?: TaskSequenceItem[],
251
+ configUpdate?: JobConfigUpdate,
252
+ handlerCode?: string,
253
+ ) => {
254
+ if (verboseLogs) {
255
+ req.payload.logger.info(
256
+ `[payload-mcp] Update Job Tool called with: ${jobSlug}, ${updateType}`,
257
+ )
258
+ }
259
+
260
+ try {
261
+ const result = await updateJob(
262
+ req,
263
+ verboseLogs,
264
+ jobsDir,
265
+ jobSlug,
266
+ updateType,
267
+ inputSchema,
268
+ outputSchema,
269
+ taskSequence,
270
+ configUpdate,
271
+ handlerCode,
272
+ )
273
+
274
+ if (verboseLogs) {
275
+ req.payload.logger.info(`[payload-mcp] Update Job Tool completed successfully`)
276
+ }
277
+
278
+ return result
279
+ } catch (error) {
280
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error'
281
+ req.payload.logger.error(`[payload-mcp] Error in Update Job Tool: ${errorMessage}`)
282
+
283
+ return {
284
+ content: [
285
+ {
286
+ type: 'text' as const,
287
+ text: `❌ **Error in Update Job Tool**: ${errorMessage}`,
288
+ },
289
+ ],
290
+ }
291
+ }
292
+ }
293
+
294
+ server.tool(
295
+ 'updateJob',
296
+ 'Updates an existing Payload job with new configuration, schema, or handler code',
297
+ toolSchemas.updateJob.parameters.shape,
298
+ async (args) => {
299
+ const {
300
+ configUpdate,
301
+ handlerCode,
302
+ inputSchema,
303
+ jobSlug,
304
+ outputSchema,
305
+ taskSequence,
306
+ updateType,
307
+ } = args
308
+ return await tool(
309
+ jobSlug,
310
+ updateType,
311
+ inputSchema as unknown as SchemaField[],
312
+ outputSchema as unknown as SchemaField[],
313
+ taskSequence,
314
+ configUpdate,
315
+ handlerCode,
316
+ )
317
+ },
318
+ )
319
+ }
@@ -0,0 +1,121 @@
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 type { PluginMCPServerConfig } from '../../../types.js'
6
+
7
+ import { toCamelCase } from '../../../utils/camelCase.js'
8
+ import { convertCollectionSchemaToZod } from '../../../utils/convertCollectionSchemaToZod.js'
9
+ import { toolSchemas } from '../schemas.js'
10
+ export const createResourceTool = (
11
+ server: McpServer,
12
+ req: PayloadRequest,
13
+ user: TypedUser,
14
+ verboseLogs: boolean,
15
+ collectionSlug: string,
16
+ collections: PluginMCPServerConfig['collections'],
17
+ schema: JSONSchema4,
18
+ ) => {
19
+ const tool = async (
20
+ data: 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(`[payload-mcp] Creating resource in collection: ${collectionSlug}`)
31
+ }
32
+
33
+ try {
34
+ // Parse the data JSON
35
+ let parsedData: Record<string, unknown>
36
+ try {
37
+ parsedData = JSON.parse(data)
38
+ if (verboseLogs) {
39
+ payload.logger.info(
40
+ `[payload-mcp] Parsed data for ${collectionSlug}: ${JSON.stringify(parsedData)}`,
41
+ )
42
+ }
43
+ } catch (_parseError) {
44
+ payload.logger.error(`[payload-mcp] Invalid JSON data provided: ${data}`)
45
+ return {
46
+ content: [{ type: 'text' as const, text: 'Error: Invalid JSON data provided' }],
47
+ }
48
+ }
49
+
50
+ // Create the resource
51
+ const result = await payload.create({
52
+ collection: collectionSlug,
53
+ // TODO: Move the override to a `beforeChange` hook and extend the payloadAPI context req to include MCP request info.
54
+ data: collections?.[collectionSlug]?.override?.(parsedData, req) || parsedData,
55
+ user,
56
+ })
57
+
58
+ if (verboseLogs) {
59
+ payload.logger.info(
60
+ `[payload-mcp] Successfully created resource in ${collectionSlug} with ID: ${result.id}`,
61
+ )
62
+ }
63
+
64
+ const response = {
65
+ content: [
66
+ {
67
+ type: 'text' as const,
68
+ text: `Resource created successfully in collection "${collectionSlug}"!
69
+ Created resource:
70
+ \`\`\`json
71
+ ${JSON.stringify(result, null, 2)}
72
+ \`\`\``,
73
+ },
74
+ ],
75
+ }
76
+
77
+ return (collections?.[collectionSlug]?.overrideResponse?.(response, result, req) ||
78
+ response) as {
79
+ content: Array<{
80
+ text: string
81
+ type: 'text'
82
+ }>
83
+ }
84
+ } catch (error) {
85
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error'
86
+ payload.logger.error(
87
+ `[payload-mcp] Error creating resource in ${collectionSlug}: ${errorMessage}`,
88
+ )
89
+
90
+ const response = {
91
+ content: [
92
+ {
93
+ type: 'text' as const,
94
+ text: `Error creating resource in collection "${collectionSlug}": ${errorMessage}`,
95
+ },
96
+ ],
97
+ }
98
+
99
+ return (collections?.[collectionSlug]?.overrideResponse?.(response, {}, req) || response) as {
100
+ content: Array<{
101
+ text: string
102
+ type: 'text'
103
+ }>
104
+ }
105
+ }
106
+ }
107
+
108
+ if (collections?.[collectionSlug]?.enabled) {
109
+ const convertedFields = convertCollectionSchemaToZod(schema)
110
+
111
+ server.tool(
112
+ `create${collectionSlug.charAt(0).toUpperCase() + toCamelCase(collectionSlug).slice(1)}`,
113
+ `${toolSchemas.createResource.description.trim()}\n\n${collections?.[collectionSlug]?.description || ''}`,
114
+ convertedFields.shape,
115
+ async (params: Record<string, unknown>) => {
116
+ const data = JSON.stringify(params)
117
+ return await tool(data)
118
+ },
119
+ )
120
+ }
121
+ }