@payloadcms/plugin-mcp 4.0.0-internal.4804abd → 4.0.0-internal.4c2ee12
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 +1 -1
- package/dist/defaultAccess.d.ts.map +1 -1
- package/dist/defineTool.d.ts +8 -8
- package/dist/defineTool.d.ts.map +1 -1
- package/dist/endpoint/access.d.ts +2 -7
- package/dist/endpoint/access.d.ts.map +1 -1
- package/dist/endpoint/access.js +6 -13
- package/dist/endpoint/access.js.map +1 -1
- package/dist/endpoint/index.d.ts +1 -1
- package/dist/endpoint/index.d.ts.map +1 -1
- package/dist/endpoint/index.js +85 -16
- package/dist/endpoint/index.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp/buildMcpServer.d.ts +6 -6
- package/dist/mcp/buildMcpServer.d.ts.map +1 -1
- package/dist/mcp/buildMcpServer.js +68 -34
- package/dist/mcp/buildMcpServer.js.map +1 -1
- package/dist/mcp/builtin/collections/authTools.d.ts.map +1 -1
- package/dist/mcp/builtin/collections/authTools.js +80 -56
- package/dist/mcp/builtin/collections/authTools.js.map +1 -1
- package/dist/mcp/builtin/collections/countTool.d.ts.map +1 -1
- package/dist/mcp/builtin/collections/countTool.js +10 -16
- package/dist/mcp/builtin/collections/countTool.js.map +1 -1
- package/dist/mcp/builtin/collections/countVersionsTool.d.ts.map +1 -1
- package/dist/mcp/builtin/collections/countVersionsTool.js +10 -15
- package/dist/mcp/builtin/collections/countVersionsTool.js.map +1 -1
- package/dist/mcp/builtin/collections/createTool.d.ts +1 -1
- package/dist/mcp/builtin/collections/createTool.d.ts.map +1 -1
- package/dist/mcp/builtin/collections/createTool.js +104 -54
- package/dist/mcp/builtin/collections/createTool.js.map +1 -1
- package/dist/mcp/builtin/collections/deleteTool.d.ts.map +1 -1
- package/dist/mcp/builtin/collections/deleteTool.js +45 -45
- package/dist/mcp/builtin/collections/deleteTool.js.map +1 -1
- package/dist/mcp/builtin/collections/duplicateTool.d.ts.map +1 -1
- package/dist/mcp/builtin/collections/duplicateTool.js +32 -39
- package/dist/mcp/builtin/collections/duplicateTool.js.map +1 -1
- package/dist/mcp/builtin/collections/fileInput.d.ts +28 -0
- package/dist/mcp/builtin/collections/fileInput.d.ts.map +1 -0
- package/dist/mcp/builtin/collections/fileInput.js +88 -0
- package/dist/mcp/builtin/collections/fileInput.js.map +1 -0
- package/dist/mcp/builtin/collections/findDistinctTool.d.ts.map +1 -1
- package/dist/mcp/builtin/collections/findDistinctTool.js +12 -28
- package/dist/mcp/builtin/collections/findDistinctTool.js.map +1 -1
- package/dist/mcp/builtin/collections/findTool.d.ts.map +1 -1
- package/dist/mcp/builtin/collections/findTool.js +29 -48
- package/dist/mcp/builtin/collections/findTool.js.map +1 -1
- package/dist/mcp/builtin/collections/findVersionByIDTool.d.ts.map +1 -1
- package/dist/mcp/builtin/collections/findVersionByIDTool.js +18 -28
- package/dist/mcp/builtin/collections/findVersionByIDTool.js.map +1 -1
- package/dist/mcp/builtin/collections/findVersionsTool.d.ts.map +1 -1
- package/dist/mcp/builtin/collections/findVersionsTool.js +17 -32
- package/dist/mcp/builtin/collections/findVersionsTool.js.map +1 -1
- package/dist/mcp/builtin/collections/getCollectionSchemaTool.d.ts.map +1 -1
- package/dist/mcp/builtin/collections/getCollectionSchemaTool.js +68 -12
- package/dist/mcp/builtin/collections/getCollectionSchemaTool.js.map +1 -1
- package/dist/mcp/builtin/collections/restoreVersionTool.d.ts.map +1 -1
- package/dist/mcp/builtin/collections/restoreVersionTool.js +15 -28
- package/dist/mcp/builtin/collections/restoreVersionTool.js.map +1 -1
- package/dist/mcp/builtin/collections/updateTool.d.ts.map +1 -1
- package/dist/mcp/builtin/collections/updateTool.js +67 -87
- package/dist/mcp/builtin/collections/updateTool.js.map +1 -1
- package/dist/mcp/builtin/collections/uploadInstructionsTool.d.ts +2 -0
- package/dist/mcp/builtin/collections/uploadInstructionsTool.d.ts.map +1 -0
- package/dist/mcp/builtin/collections/uploadInstructionsTool.js +55 -0
- package/dist/mcp/builtin/collections/uploadInstructionsTool.js.map +1 -0
- package/dist/mcp/builtin/formatEntityError.d.ts +16 -0
- package/dist/mcp/builtin/formatEntityError.d.ts.map +1 -0
- package/dist/mcp/builtin/formatEntityError.js +55 -0
- package/dist/mcp/builtin/formatEntityError.js.map +1 -0
- package/dist/mcp/builtin/getConfigInfoTool.d.ts.map +1 -1
- package/dist/mcp/builtin/getConfigInfoTool.js +5 -7
- package/dist/mcp/builtin/getConfigInfoTool.js.map +1 -1
- package/dist/mcp/builtin/globals/countVersionsTool.d.ts.map +1 -1
- package/dist/mcp/builtin/globals/countVersionsTool.js +10 -15
- package/dist/mcp/builtin/globals/countVersionsTool.js.map +1 -1
- package/dist/mcp/builtin/globals/findTool.d.ts.map +1 -1
- package/dist/mcp/builtin/globals/findTool.js +12 -18
- package/dist/mcp/builtin/globals/findTool.js.map +1 -1
- package/dist/mcp/builtin/globals/findVersionByIDTool.d.ts.map +1 -1
- package/dist/mcp/builtin/globals/findVersionByIDTool.js +14 -26
- package/dist/mcp/builtin/globals/findVersionByIDTool.js.map +1 -1
- package/dist/mcp/builtin/globals/findVersionsTool.d.ts.map +1 -1
- package/dist/mcp/builtin/globals/findVersionsTool.js +14 -31
- package/dist/mcp/builtin/globals/findVersionsTool.js.map +1 -1
- package/dist/mcp/builtin/globals/getGlobalSchemaTool.d.ts.map +1 -1
- package/dist/mcp/builtin/globals/getGlobalSchemaTool.js +46 -12
- package/dist/mcp/builtin/globals/getGlobalSchemaTool.js.map +1 -1
- package/dist/mcp/builtin/globals/restoreVersionTool.d.ts.map +1 -1
- package/dist/mcp/builtin/globals/restoreVersionTool.js +16 -24
- package/dist/mcp/builtin/globals/restoreVersionTool.js.map +1 -1
- package/dist/mcp/builtin/globals/updateTool.d.ts.map +1 -1
- package/dist/mcp/builtin/globals/updateTool.js +28 -37
- package/dist/mcp/builtin/globals/updateTool.js.map +1 -1
- package/dist/mcp/builtinTools.d.ts +6 -0
- package/dist/mcp/builtinTools.d.ts.map +1 -1
- package/dist/mcp/builtinTools.js +9 -3
- package/dist/mcp/builtinTools.js.map +1 -1
- package/dist/mcp/sanitizeMCPConfig.d.ts.map +1 -1
- package/dist/mcp/sanitizeMCPConfig.js +5 -1
- package/dist/mcp/sanitizeMCPConfig.js.map +1 -1
- package/dist/stdio.d.ts +3 -4
- package/dist/stdio.d.ts.map +1 -1
- package/dist/stdio.js +65 -45
- package/dist/stdio.js.map +1 -1
- package/dist/types.d.ts +71 -16
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +2 -4
- package/dist/types.js.map +1 -1
- package/dist/utils/camelCase.d.ts.map +1 -1
- package/dist/utils/resolveProjectRoot.d.ts.map +1 -1
- package/dist/utils/toStandardSchema.d.ts.map +1 -1
- package/package.json +9 -13
- package/src/endpoint/access.ts +5 -13
- package/src/endpoint/index.ts +73 -14
- package/src/index.ts +4 -4
- package/src/mcp/buildMcpServer.ts +80 -50
- package/src/mcp/builtin/collections/authTools.ts +80 -69
- package/src/mcp/builtin/collections/countTool.ts +10 -23
- package/src/mcp/builtin/collections/countVersionsTool.ts +10 -20
- package/src/mcp/builtin/collections/createTool.ts +110 -77
- package/src/mcp/builtin/collections/deleteTool.ts +36 -65
- package/src/mcp/builtin/collections/duplicateTool.ts +36 -87
- package/src/mcp/builtin/collections/fileInput.spec.ts +93 -0
- package/src/mcp/builtin/collections/fileInput.ts +127 -0
- package/src/mcp/builtin/collections/findDistinctTool.ts +12 -65
- package/src/mcp/builtin/collections/findTool.ts +25 -108
- package/src/mcp/builtin/collections/findVersionByIDTool.ts +16 -60
- package/src/mcp/builtin/collections/findVersionsTool.ts +15 -87
- package/src/mcp/builtin/collections/getCollectionSchemaTool.ts +60 -15
- package/src/mcp/builtin/collections/restoreVersionTool.ts +15 -58
- package/src/mcp/builtin/collections/updateTool.ts +63 -101
- package/src/mcp/builtin/collections/uploadInstructionsTool.ts +64 -0
- package/src/mcp/builtin/formatEntityError.ts +77 -0
- package/src/mcp/builtin/getConfigInfoTool.ts +4 -5
- package/src/mcp/builtin/globals/countVersionsTool.ts +10 -19
- package/src/mcp/builtin/globals/findTool.ts +14 -53
- package/src/mcp/builtin/globals/findVersionByIDTool.ts +14 -54
- package/src/mcp/builtin/globals/findVersionsTool.ts +15 -94
- package/src/mcp/builtin/globals/getGlobalSchemaTool.ts +41 -9
- package/src/mcp/builtin/globals/restoreVersionTool.ts +15 -47
- package/src/mcp/builtin/globals/updateTool.ts +39 -62
- package/src/mcp/builtinTools.ts +9 -2
- package/src/mcp/sanitizeMCPConfig.ts +5 -1
- package/src/stdio.ts +63 -45
- package/src/types.ts +72 -16
- package/dist/@types/assets.d.js +0 -2
- package/dist/@types/assets.d.js.map +0 -1
- package/dist/mcp/builtin/collections/formatCollectionError.d.ts +0 -9
- package/dist/mcp/builtin/collections/formatCollectionError.d.ts.map +0 -1
- package/dist/mcp/builtin/collections/formatCollectionError.js +0 -60
- package/dist/mcp/builtin/collections/formatCollectionError.js.map +0 -1
- package/dist/mcp/builtin/validateEntityData.d.ts +0 -14
- package/dist/mcp/builtin/validateEntityData.d.ts.map +0 -1
- package/dist/mcp/builtin/validateEntityData.js +0 -82
- package/dist/mcp/builtin/validateEntityData.js.map +0 -1
- package/dist/utils/getVirtualFieldNames.d.ts +0 -14
- package/dist/utils/getVirtualFieldNames.d.ts.map +0 -1
- package/dist/utils/getVirtualFieldNames.js +0 -35
- package/dist/utils/getVirtualFieldNames.js.map +0 -1
- package/dist/utils/localAPIDefaults.d.ts +0 -20
- package/dist/utils/localAPIDefaults.d.ts.map +0 -1
- package/dist/utils/localAPIDefaults.js +0 -19
- package/dist/utils/localAPIDefaults.js.map +0 -1
- package/dist/utils/schemaConversion/getEntityInputSchema.d.ts +0 -11
- package/dist/utils/schemaConversion/getEntityInputSchema.d.ts.map +0 -1
- package/dist/utils/schemaConversion/getEntityInputSchema.js +0 -32
- package/dist/utils/schemaConversion/getEntityInputSchema.js.map +0 -1
- package/dist/utils/schemaConversion/sanitizeEntitySchema.d.ts +0 -10
- package/dist/utils/schemaConversion/sanitizeEntitySchema.d.ts.map +0 -1
- package/dist/utils/schemaConversion/sanitizeEntitySchema.js +0 -384
- package/dist/utils/schemaConversion/sanitizeEntitySchema.js.map +0 -1
- package/dist/utils/schemaConversion/sanitizeEntitySchema.spec.js +0 -133
- package/dist/utils/schemaConversion/sanitizeEntitySchema.spec.js.map +0 -1
- package/dist/utils/transformPointDataToPayload.d.ts +0 -7
- package/dist/utils/transformPointDataToPayload.d.ts.map +0 -1
- package/dist/utils/transformPointDataToPayload.js +0 -28
- package/dist/utils/transformPointDataToPayload.js.map +0 -1
- package/dist/utils/whereSchema.d.ts +0 -9
- package/dist/utils/whereSchema.d.ts.map +0 -1
- package/dist/utils/whereSchema.js +0 -13
- package/dist/utils/whereSchema.js.map +0 -1
- package/src/@types/assets.d.ts +0 -3
- package/src/mcp/builtin/collections/formatCollectionError.ts +0 -84
- package/src/mcp/builtin/validateEntityData.ts +0 -132
- package/src/utils/getVirtualFieldNames.ts +0 -53
- package/src/utils/localAPIDefaults.ts +0 -22
- package/src/utils/schemaConversion/getEntityInputSchema.ts +0 -62
- package/src/utils/schemaConversion/sanitizeEntitySchema.spec.ts +0 -96
- package/src/utils/schemaConversion/sanitizeEntitySchema.ts +0 -432
- package/src/utils/transformPointDataToPayload.ts +0 -40
- package/src/utils/whereSchema.ts +0 -24
package/src/endpoint/access.ts
CHANGED
|
@@ -12,13 +12,8 @@ export type GetAuthorizedMCPArgs = {
|
|
|
12
12
|
}
|
|
13
13
|
|
|
14
14
|
/**
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* Authorization has two layers:
|
|
18
|
-
* 1. Payload collection/global permissions determine whether built-in operation tools are shown.
|
|
19
|
-
* 2. MCP `access` callbacks can further hide any configured tool, prompt, or resource.
|
|
20
|
-
*
|
|
21
|
-
* Like Payload core operations, `overrideAccess` skips both layers.
|
|
15
|
+
* Authenticates an MCP request and removes items the user cannot access.
|
|
16
|
+
* `overrideAccess` skips Payload and MCP item access checks.
|
|
22
17
|
*/
|
|
23
18
|
export const getAuthorizedMCP: (args: GetAuthorizedMCPArgs) => Promise<AuthorizedMCP> = async ({
|
|
24
19
|
overrideAccess,
|
|
@@ -53,7 +48,6 @@ export const getAuthorizedMCP: (args: GetAuthorizedMCPArgs) => Promise<Authorize
|
|
|
53
48
|
req,
|
|
54
49
|
}),
|
|
55
50
|
overrideAccess,
|
|
56
|
-
user: req.user,
|
|
57
51
|
}
|
|
58
52
|
}
|
|
59
53
|
|
|
@@ -66,8 +60,7 @@ export const filterMCPItems = async ({
|
|
|
66
60
|
overrideAccess: boolean
|
|
67
61
|
req: PayloadRequest
|
|
68
62
|
}): Promise<MCPItem[]> => {
|
|
69
|
-
// Match Payload core: overrideAccess bypasses access evaluation
|
|
70
|
-
// forcing each access function to return true.
|
|
63
|
+
// Match Payload core: overrideAccess bypasses access evaluation
|
|
71
64
|
if (overrideAccess) {
|
|
72
65
|
return items
|
|
73
66
|
}
|
|
@@ -102,12 +95,11 @@ const checkItemAccess = async ({
|
|
|
102
95
|
case 'collectionTool':
|
|
103
96
|
return (
|
|
104
97
|
!item.tool.access ||
|
|
105
|
-
(await item.tool.access({
|
|
98
|
+
(await item.tool.access({ slug: item.collectionSlug, permissions, req }))
|
|
106
99
|
)
|
|
107
100
|
case 'globalTool':
|
|
108
101
|
return (
|
|
109
|
-
!item.tool.access ||
|
|
110
|
-
(await item.tool.access({ globalSlug: item.globalSlug, permissions, req }))
|
|
102
|
+
!item.tool.access || (await item.tool.access({ slug: item.globalSlug, permissions, req }))
|
|
111
103
|
)
|
|
112
104
|
case 'prompt':
|
|
113
105
|
return !item.prompt.access || (await item.prompt.access({ permissions, req }))
|
package/src/endpoint/index.ts
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import type { PayloadHandler, PayloadRequest } from 'payload'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createMcpHandler,
|
|
5
|
+
isLegacyRequest,
|
|
6
|
+
readRequestBody,
|
|
7
|
+
WebStandardStreamableHTTPServerTransport,
|
|
8
|
+
} from '@modelcontextprotocol/server'
|
|
9
|
+
import { APIError } from 'payload'
|
|
3
10
|
|
|
4
11
|
import { buildMcpServer } from '../mcp/buildMcpServer.js'
|
|
5
12
|
import { getPluginConfig } from '../utils/getPluginConfig.js'
|
|
@@ -27,22 +34,74 @@ export const mcpEndpoint: PayloadHandler = async (req) => {
|
|
|
27
34
|
}
|
|
28
35
|
|
|
29
36
|
const authorizedMCP = await getAuthorizedMCP({ overrideAccess, req })
|
|
37
|
+
const maxRequestBodySize = pluginConfig.mcp?.maxRequestBodySize
|
|
38
|
+
// Payload augments the original web-standard Request in place.
|
|
39
|
+
const mcpRequest = req as PayloadRequest & Request
|
|
40
|
+
const parsedBody = await parseRequestBody({ maxRequestBodySize, request: mcpRequest })
|
|
41
|
+
|
|
42
|
+
// Keep the old JSON-only, stateless behavior because the SDK's 2025 fallback uses SSE.
|
|
43
|
+
if (await isLegacyRequest(mcpRequest, parsedBody, { maxRequestBodySize })) {
|
|
44
|
+
const server = buildMcpServer({ authorizedMCP, pluginConfig, req })
|
|
45
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
46
|
+
enableJsonResponse: true,
|
|
47
|
+
maxRequestBodySize,
|
|
48
|
+
sessionIdGenerator: undefined, // stateless mode
|
|
49
|
+
})
|
|
50
|
+
transport.onerror = (err) => {
|
|
51
|
+
req.payload.logger.error({ err, msg: 'Error serving legacy MCP request' })
|
|
52
|
+
}
|
|
30
53
|
|
|
31
|
-
|
|
54
|
+
try {
|
|
55
|
+
await server.connect(transport)
|
|
56
|
+
return await transport.handleRequest(mcpRequest, { parsedBody })
|
|
57
|
+
} finally {
|
|
58
|
+
await server.close().catch((err) => {
|
|
59
|
+
req.payload.logger.error({ err, msg: 'Error closing MCP server' })
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
}
|
|
32
63
|
|
|
33
|
-
const
|
|
34
|
-
|
|
35
|
-
|
|
64
|
+
const handler = createMcpHandler(() => buildMcpServer({ authorizedMCP, pluginConfig, req }), {
|
|
65
|
+
legacy: 'reject',
|
|
66
|
+
maxRequestBodySize,
|
|
67
|
+
// SDK subscriptions always use SSE, so disable them to keep every response JSON-only.
|
|
68
|
+
maxSubscriptions: 0,
|
|
69
|
+
onerror: (err) => {
|
|
70
|
+
req.payload.logger.error({ err, msg: 'Error serving modern MCP request' })
|
|
71
|
+
},
|
|
72
|
+
responseMode: 'json',
|
|
36
73
|
})
|
|
37
74
|
|
|
38
|
-
|
|
75
|
+
try {
|
|
76
|
+
return await handler.fetch(mcpRequest, { parsedBody })
|
|
77
|
+
} finally {
|
|
78
|
+
await handler.close().catch((err) => {
|
|
79
|
+
req.payload.logger.error({ err, msg: 'Error closing modern MCP handler' })
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
}
|
|
39
83
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
84
|
+
/**
|
|
85
|
+
* Parses a POST body once so the era check and the SDK handler can share it. The SDK
|
|
86
|
+
* skips its size limit for a pre-parsed body, so this reads with the same limit. It reads
|
|
87
|
+
* a clone and returns `undefined` for any body the SDK should reject (too large, unreadable,
|
|
88
|
+
* or not JSON), leaving the original request for the SDK to read and answer.
|
|
89
|
+
*/
|
|
90
|
+
const parseRequestBody = async ({
|
|
91
|
+
maxRequestBodySize,
|
|
92
|
+
request,
|
|
93
|
+
}: {
|
|
94
|
+
maxRequestBodySize?: number
|
|
95
|
+
request: Request
|
|
96
|
+
}): Promise<unknown> => {
|
|
97
|
+
if (request.method.toUpperCase() !== 'POST') {
|
|
98
|
+
return undefined
|
|
99
|
+
}
|
|
46
100
|
|
|
47
|
-
|
|
101
|
+
try {
|
|
102
|
+
const body = await readRequestBody(request.clone(), maxRequestBodySize)
|
|
103
|
+
return body.tooLarge ? undefined : JSON.parse(body.text)
|
|
104
|
+
} catch {
|
|
105
|
+
return undefined
|
|
106
|
+
}
|
|
48
107
|
}
|
package/src/index.ts
CHANGED
|
@@ -6,8 +6,8 @@ import { mcpEndpoint } from './endpoint/index.js'
|
|
|
6
6
|
import { sanitizeMCPConfig } from './mcp/sanitizeMCPConfig.js'
|
|
7
7
|
|
|
8
8
|
declare module 'payload' {
|
|
9
|
-
export interface
|
|
10
|
-
|
|
9
|
+
export interface PayloadRequestAPI {
|
|
10
|
+
MCP: true
|
|
11
11
|
}
|
|
12
12
|
interface RegisteredPlugins {
|
|
13
13
|
/** After the plugin's `plugin` callback runs, `options` holds the sanitized config. */
|
|
@@ -22,8 +22,8 @@ export { defineCollectionTool, defineGlobalTool, definePrompt, defineTool } from
|
|
|
22
22
|
export const mcpPlugin = definePlugin<MCPPluginConfig>({
|
|
23
23
|
slug: '@payloadcms/plugin-mcp',
|
|
24
24
|
order: 10,
|
|
25
|
-
plugin: ({ config,
|
|
26
|
-
const pluginConfig = sanitizeMCPConfig({ config, pluginConfig:
|
|
25
|
+
plugin: ({ config, options, plugins }) => {
|
|
26
|
+
const pluginConfig = sanitizeMCPConfig({ config, pluginConfig: options })
|
|
27
27
|
|
|
28
28
|
// Stash the sanitized config on plugin options so `getPluginConfig()` reads it.
|
|
29
29
|
const registered = plugins['@payloadcms/plugin-mcp']
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import type { PayloadRequest } from 'payload'
|
|
2
|
+
|
|
1
3
|
import { McpServer, type ServerContext } from '@modelcontextprotocol/server'
|
|
2
|
-
import { APIError,
|
|
3
|
-
import { z } from 'zod'
|
|
4
|
+
import { APIError, strictObject, z } from 'payload'
|
|
4
5
|
|
|
5
6
|
import type {
|
|
6
7
|
AuthorizedMCP,
|
|
@@ -17,14 +18,14 @@ import { getLogger } from '../utils/getLogger.js'
|
|
|
17
18
|
import { toStandardSchema } from '../utils/toStandardSchema.js'
|
|
18
19
|
|
|
19
20
|
/**
|
|
20
|
-
*
|
|
21
|
-
* `McpServer` and returns it. The
|
|
22
|
-
*
|
|
23
|
-
*
|
|
21
|
+
* Serving-entry-agnostic core: registers every authorized MCP item onto a fresh
|
|
22
|
+
* `McpServer` and returns it. The HTTP and stdio entry point callers provide fresh
|
|
23
|
+
* instances from this builder while they own the transport and protocol-era
|
|
24
|
+
* decision.
|
|
24
25
|
*
|
|
25
26
|
* `req` is the request context handlers see. For HTTP it's the live
|
|
26
27
|
* `PayloadRequest` derived from the incoming HTTP request; for stdio it's a
|
|
27
|
-
* synthesized one built via `
|
|
28
|
+
* synthesized one built via `createPayloadRequest`.
|
|
28
29
|
*/
|
|
29
30
|
export const buildMcpServer = ({
|
|
30
31
|
authorizedMCP,
|
|
@@ -35,6 +36,8 @@ export const buildMcpServer = ({
|
|
|
35
36
|
pluginConfig: SanitizedMCPPluginConfig
|
|
36
37
|
req: PayloadRequest
|
|
37
38
|
}): McpServer => {
|
|
39
|
+
z.config(z.locales.en())
|
|
40
|
+
|
|
38
41
|
const serverOptions = pluginConfig.mcp?.serverOptions || {}
|
|
39
42
|
const server = new McpServer(
|
|
40
43
|
{ name: 'Payload MCP Server', version: '1.0.0', ...serverOptions.serverInfo },
|
|
@@ -47,18 +50,28 @@ export const buildMcpServer = ({
|
|
|
47
50
|
* Wrap a tool handler's response with the tool's `overrideResponse`, then
|
|
48
51
|
* strip the internal `doc` field so it doesn't leak onto the wire.
|
|
49
52
|
*/
|
|
50
|
-
const finalizeToolResponse = (
|
|
51
|
-
|
|
52
|
-
overrideResponse
|
|
53
|
-
|
|
54
|
-
|
|
53
|
+
const finalizeToolResponse = async ({
|
|
54
|
+
input,
|
|
55
|
+
overrideResponse,
|
|
56
|
+
response,
|
|
57
|
+
toolName,
|
|
58
|
+
}: {
|
|
59
|
+
input: unknown
|
|
60
|
+
overrideResponse?: MCPResponseOverride
|
|
61
|
+
response: MCPToolResponse
|
|
62
|
+
toolName: string
|
|
63
|
+
}): Promise<MCPToolResponse> => {
|
|
64
|
+
let overridden = overrideResponse?.(response, response.doc ?? {}, req) ?? response
|
|
65
|
+
for (const hook of pluginConfig.hooks?.afterToolCall ?? []) {
|
|
66
|
+
overridden = await hook({ input, req, response: overridden, toolName })
|
|
67
|
+
}
|
|
55
68
|
const { doc: _doc, ...rest } = overridden
|
|
56
69
|
return rest
|
|
57
70
|
}
|
|
58
71
|
|
|
59
72
|
/**
|
|
60
73
|
* Runs a collection/global tool call:
|
|
61
|
-
* - reads `
|
|
74
|
+
* - reads `slug` from the input
|
|
62
75
|
* - runs access control: errors if `authorizedMCP.items` has no entry for this tool + slug
|
|
63
76
|
* - runs the tool handler and finalizes its response
|
|
64
77
|
*/
|
|
@@ -72,16 +85,15 @@ export const buildMcpServer = ({
|
|
|
72
85
|
serverContext: ServerContext
|
|
73
86
|
}): Promise<MCPToolResponse> => {
|
|
74
87
|
const entity = item.type === 'collectionTool' ? 'collection' : 'global'
|
|
75
|
-
const slugKey = item.type === 'collectionTool' ? 'collectionSlug' : 'globalSlug'
|
|
76
88
|
const toolInput = (input ?? {}) as Record<string, unknown>
|
|
77
|
-
const slug = toolInput
|
|
89
|
+
const slug = toolInput.slug as string | undefined
|
|
78
90
|
|
|
79
91
|
if (!slug) {
|
|
80
92
|
return {
|
|
81
93
|
content: [
|
|
82
94
|
{
|
|
83
95
|
type: 'text',
|
|
84
|
-
text: `Error: "${item.mcpName}" requires
|
|
96
|
+
text: `Error: "${item.mcpName}" requires slug. Use getConfigInfo to inspect ${entity} slugs.`,
|
|
85
97
|
},
|
|
86
98
|
],
|
|
87
99
|
isError: true,
|
|
@@ -116,10 +128,15 @@ export const buildMcpServer = ({
|
|
|
116
128
|
serverContext,
|
|
117
129
|
}
|
|
118
130
|
const response = await (match.type === 'collectionTool'
|
|
119
|
-
? match.tool.handler({ ...handlerArgs,
|
|
120
|
-
: match.tool.handler({ ...handlerArgs,
|
|
131
|
+
? match.tool.handler({ ...handlerArgs, slug })
|
|
132
|
+
: match.tool.handler({ ...handlerArgs, slug }))
|
|
121
133
|
|
|
122
|
-
return finalizeToolResponse(
|
|
134
|
+
return finalizeToolResponse({
|
|
135
|
+
input: toolInput,
|
|
136
|
+
overrideResponse: match.tool.overrideResponse,
|
|
137
|
+
response,
|
|
138
|
+
toolName: match.mcpName,
|
|
139
|
+
})
|
|
123
140
|
}
|
|
124
141
|
|
|
125
142
|
try {
|
|
@@ -134,10 +151,7 @@ export const buildMcpServer = ({
|
|
|
134
151
|
}
|
|
135
152
|
registeredEntityTools.add(item.mcpName)
|
|
136
153
|
|
|
137
|
-
const inputSchema = withSlugInput({
|
|
138
|
-
name: item.type === 'collectionTool' ? 'collectionSlug' : 'globalSlug',
|
|
139
|
-
input: item.tool.input,
|
|
140
|
-
})
|
|
154
|
+
const inputSchema = withSlugInput({ input: item.tool.input })
|
|
141
155
|
|
|
142
156
|
server.registerTool(
|
|
143
157
|
item.mcpName,
|
|
@@ -203,16 +217,21 @@ export const buildMcpServer = ({
|
|
|
203
217
|
description: tool.description,
|
|
204
218
|
inputSchema: tool.input ? toStandardSchema(tool.input) : undefined,
|
|
205
219
|
},
|
|
206
|
-
async (input: unknown, ctx: ServerContext) =>
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
220
|
+
async (input: unknown, ctx: ServerContext) => {
|
|
221
|
+
const toolInput = (input ?? {}) as Record<string, unknown>
|
|
222
|
+
const response = await tool.handler({
|
|
223
|
+
authorizedMCP,
|
|
224
|
+
input: toolInput,
|
|
225
|
+
req,
|
|
226
|
+
serverContext: ctx,
|
|
227
|
+
})
|
|
228
|
+
return finalizeToolResponse({
|
|
229
|
+
input: toolInput,
|
|
230
|
+
overrideResponse: tool.overrideResponse,
|
|
231
|
+
response,
|
|
232
|
+
toolName: item.mcpName,
|
|
233
|
+
})
|
|
234
|
+
},
|
|
216
235
|
)
|
|
217
236
|
logger.info(`✅ Tool: ${item.mcpName} Registered.`)
|
|
218
237
|
break
|
|
@@ -226,39 +245,50 @@ export const buildMcpServer = ({
|
|
|
226
245
|
return server
|
|
227
246
|
}
|
|
228
247
|
|
|
229
|
-
const withSlugInput = ({
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
}: {
|
|
233
|
-
input?: ToolInputSchema
|
|
234
|
-
name: 'collectionSlug' | 'globalSlug'
|
|
235
|
-
}): ToolInputSchema => {
|
|
236
|
-
const description = name === 'collectionSlug' ? 'The collection slug' : 'The global slug'
|
|
237
|
-
const slugSchema = z.string().describe(description)
|
|
248
|
+
const withSlugInput = ({ input }: { input?: ToolInputSchema }): ToolInputSchema => {
|
|
249
|
+
const description = 'The target slug.'
|
|
250
|
+
const slugSchema = z.string().check(z.describe(description))
|
|
238
251
|
|
|
239
252
|
if (!input) {
|
|
240
|
-
return
|
|
253
|
+
return strictObject({ slug: slugSchema }) as unknown as ToolInputSchema
|
|
241
254
|
}
|
|
242
255
|
|
|
243
|
-
if (input
|
|
244
|
-
|
|
256
|
+
if (typeof input === 'object' && input !== null && '~standard' in input) {
|
|
257
|
+
const schema = input['~standard'].jsonSchema.input({ target: 'draft-2020-12' })
|
|
258
|
+
const properties = schema.properties as Record<string, unknown> | undefined
|
|
259
|
+
|
|
260
|
+
if (properties?.slug) {
|
|
261
|
+
return input
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
return addSlugToJSONSchema({ description, schema: schema as JsonSchemaType })
|
|
245
265
|
}
|
|
246
266
|
|
|
247
|
-
|
|
267
|
+
return addSlugToJSONSchema({ description, schema: input })
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const addSlugToJSONSchema = ({
|
|
271
|
+
description,
|
|
272
|
+
schema,
|
|
273
|
+
}: {
|
|
274
|
+
description: string
|
|
275
|
+
schema: JsonSchemaType
|
|
276
|
+
}): JsonSchemaType => {
|
|
277
|
+
const objectSchema = schema as {
|
|
248
278
|
properties?: Record<string, JsonSchemaType>
|
|
249
279
|
required?: string[]
|
|
250
280
|
} & JsonSchemaType
|
|
251
281
|
|
|
252
282
|
return {
|
|
253
|
-
...
|
|
283
|
+
...objectSchema,
|
|
254
284
|
type: 'object',
|
|
255
285
|
properties: {
|
|
256
|
-
...
|
|
257
|
-
|
|
286
|
+
...objectSchema.properties,
|
|
287
|
+
slug: {
|
|
258
288
|
type: 'string',
|
|
259
289
|
description,
|
|
260
290
|
},
|
|
261
291
|
},
|
|
262
|
-
required: Array.from(new Set([
|
|
292
|
+
required: Array.from(new Set(['slug', ...(objectSchema.required ?? [])])),
|
|
263
293
|
}
|
|
264
294
|
}
|