@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,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
|
+
}
|