@payloadcms/plugin-mcp 4.0.0-internal.40de3ec → 4.0.0-internal.4804abd
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/dist/defaultAccess.d.ts +3 -0
- package/dist/defaultAccess.d.ts.map +1 -0
- package/dist/defaultAccess.js +3 -0
- package/dist/defaultAccess.js.map +1 -0
- package/dist/endpoint/access.d.ts +17 -5
- package/dist/endpoint/access.d.ts.map +1 -1
- package/dist/endpoint/access.js +82 -88
- package/dist/endpoint/access.js.map +1 -1
- package/dist/endpoint/index.d.ts.map +1 -1
- package/dist/endpoint/index.js +11 -0
- package/dist/endpoint/index.js.map +1 -1
- package/dist/exports/client.d.ts +1 -1
- package/dist/exports/client.d.ts.map +1 -1
- package/dist/exports/client.js +1 -1
- package/dist/exports/client.js.map +1 -1
- package/dist/exports/internal.d.ts +2 -0
- package/dist/exports/internal.d.ts.map +1 -0
- package/dist/exports/internal.js +3 -0
- package/dist/exports/internal.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -18
- package/dist/index.js.map +1 -1
- package/dist/mcp/buildMcpServer.d.ts.map +1 -1
- package/dist/mcp/buildMcpServer.js +102 -71
- 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 +44 -6
- package/dist/mcp/builtin/collections/authTools.js.map +1 -1
- package/dist/mcp/builtin/collections/countTool.d.ts +2 -0
- package/dist/mcp/builtin/collections/countTool.d.ts.map +1 -0
- package/dist/mcp/builtin/collections/countTool.js +69 -0
- package/dist/mcp/builtin/collections/countTool.js.map +1 -0
- package/dist/mcp/builtin/collections/countVersionsTool.d.ts +2 -0
- package/dist/mcp/builtin/collections/countVersionsTool.d.ts.map +1 -0
- package/dist/mcp/builtin/collections/countVersionsTool.js +65 -0
- package/dist/mcp/builtin/collections/countVersionsTool.js.map +1 -0
- 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 +38 -42
- package/dist/mcp/builtin/collections/createTool.js.map +1 -1
- package/dist/mcp/builtin/collections/deleteTool.d.ts +1 -1
- package/dist/mcp/builtin/collections/deleteTool.d.ts.map +1 -1
- package/dist/mcp/builtin/collections/deleteTool.js +14 -20
- package/dist/mcp/builtin/collections/deleteTool.js.map +1 -1
- package/dist/mcp/builtin/collections/duplicateTool.d.ts +2 -0
- package/dist/mcp/builtin/collections/duplicateTool.d.ts.map +1 -0
- package/dist/mcp/builtin/collections/duplicateTool.js +97 -0
- package/dist/mcp/builtin/collections/duplicateTool.js.map +1 -0
- package/dist/mcp/builtin/collections/findDistinctTool.d.ts +2 -0
- package/dist/mcp/builtin/collections/findDistinctTool.d.ts.map +1 -0
- package/dist/mcp/builtin/collections/findDistinctTool.js +93 -0
- package/dist/mcp/builtin/collections/findDistinctTool.js.map +1 -0
- package/dist/mcp/builtin/collections/findTool.d.ts +1 -1
- package/dist/mcp/builtin/collections/findTool.d.ts.map +1 -1
- package/dist/mcp/builtin/collections/findTool.js +49 -43
- package/dist/mcp/builtin/collections/findTool.js.map +1 -1
- package/dist/mcp/builtin/collections/findVersionByIDTool.d.ts +2 -0
- package/dist/mcp/builtin/collections/findVersionByIDTool.d.ts.map +1 -0
- package/dist/mcp/builtin/collections/findVersionByIDTool.js +84 -0
- package/dist/mcp/builtin/collections/findVersionByIDTool.js.map +1 -0
- package/dist/mcp/builtin/collections/findVersionsTool.d.ts +2 -0
- package/dist/mcp/builtin/collections/findVersionsTool.d.ts.map +1 -0
- package/dist/mcp/builtin/collections/findVersionsTool.js +99 -0
- package/dist/mcp/builtin/collections/findVersionsTool.js.map +1 -0
- package/dist/mcp/builtin/collections/formatCollectionError.d.ts +9 -0
- package/dist/mcp/builtin/collections/formatCollectionError.d.ts.map +1 -0
- package/dist/mcp/builtin/collections/formatCollectionError.js +60 -0
- package/dist/mcp/builtin/collections/formatCollectionError.js.map +1 -0
- package/dist/mcp/builtin/collections/getCollectionSchemaTool.d.ts +2 -0
- package/dist/mcp/builtin/collections/getCollectionSchemaTool.d.ts.map +1 -0
- package/dist/mcp/builtin/collections/getCollectionSchemaTool.js +47 -0
- package/dist/mcp/builtin/collections/getCollectionSchemaTool.js.map +1 -0
- package/dist/mcp/builtin/collections/restoreVersionTool.d.ts +2 -0
- package/dist/mcp/builtin/collections/restoreVersionTool.d.ts.map +1 -0
- package/dist/mcp/builtin/collections/restoreVersionTool.js +82 -0
- package/dist/mcp/builtin/collections/restoreVersionTool.js.map +1 -0
- package/dist/mcp/builtin/collections/updateTool.d.ts +1 -1
- package/dist/mcp/builtin/collections/updateTool.d.ts.map +1 -1
- package/dist/mcp/builtin/collections/updateTool.js +87 -86
- package/dist/mcp/builtin/collections/updateTool.js.map +1 -1
- package/dist/mcp/builtin/getConfigInfoTool.d.ts +2 -0
- package/dist/mcp/builtin/getConfigInfoTool.d.ts.map +1 -0
- package/dist/mcp/builtin/getConfigInfoTool.js +71 -0
- package/dist/mcp/builtin/getConfigInfoTool.js.map +1 -0
- package/dist/mcp/builtin/globals/countVersionsTool.d.ts +2 -0
- package/dist/mcp/builtin/globals/countVersionsTool.d.ts.map +1 -0
- package/dist/mcp/builtin/globals/countVersionsTool.js +65 -0
- package/dist/mcp/builtin/globals/countVersionsTool.js.map +1 -0
- package/dist/mcp/builtin/globals/findTool.d.ts.map +1 -1
- package/dist/mcp/builtin/globals/findTool.js +18 -21
- package/dist/mcp/builtin/globals/findTool.js.map +1 -1
- package/dist/mcp/builtin/globals/findVersionByIDTool.d.ts +2 -0
- package/dist/mcp/builtin/globals/findVersionByIDTool.d.ts.map +1 -0
- package/dist/mcp/builtin/globals/findVersionByIDTool.js +80 -0
- package/dist/mcp/builtin/globals/findVersionByIDTool.js.map +1 -0
- package/dist/mcp/builtin/globals/findVersionsTool.d.ts +2 -0
- package/dist/mcp/builtin/globals/findVersionsTool.d.ts.map +1 -0
- package/dist/mcp/builtin/globals/findVersionsTool.js +95 -0
- package/dist/mcp/builtin/globals/findVersionsTool.js.map +1 -0
- package/dist/mcp/builtin/globals/getGlobalSchemaTool.d.ts +2 -0
- package/dist/mcp/builtin/globals/getGlobalSchemaTool.d.ts.map +1 -0
- package/dist/mcp/builtin/globals/getGlobalSchemaTool.js +47 -0
- package/dist/mcp/builtin/globals/getGlobalSchemaTool.js.map +1 -0
- package/dist/mcp/builtin/globals/restoreVersionTool.d.ts +2 -0
- package/dist/mcp/builtin/globals/restoreVersionTool.d.ts.map +1 -0
- package/dist/mcp/builtin/globals/restoreVersionTool.js +78 -0
- package/dist/mcp/builtin/globals/restoreVersionTool.js.map +1 -0
- package/dist/mcp/builtin/globals/updateTool.d.ts.map +1 -1
- package/dist/mcp/builtin/globals/updateTool.js +31 -40
- package/dist/mcp/builtin/globals/updateTool.js.map +1 -1
- package/dist/mcp/builtin/validateEntityData.d.ts +14 -0
- package/dist/mcp/builtin/validateEntityData.d.ts.map +1 -0
- package/dist/mcp/builtin/validateEntityData.js +82 -0
- package/dist/mcp/builtin/validateEntityData.js.map +1 -0
- package/dist/mcp/builtinTools.d.ts +144 -19
- package/dist/mcp/builtinTools.d.ts.map +1 -1
- package/dist/mcp/builtinTools.js +119 -20
- package/dist/mcp/builtinTools.js.map +1 -1
- package/dist/mcp/sanitizeMCPConfig.d.ts +0 -1
- package/dist/mcp/sanitizeMCPConfig.d.ts.map +1 -1
- package/dist/mcp/sanitizeMCPConfig.js +120 -58
- package/dist/mcp/sanitizeMCPConfig.js.map +1 -1
- package/dist/stdio.d.ts +3 -2
- package/dist/stdio.d.ts.map +1 -1
- package/dist/stdio.js +27 -8
- package/dist/stdio.js.map +1 -1
- package/dist/types.d.ts +62 -108
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -4
- package/dist/types.js.map +1 -1
- package/dist/utils/getPluginConfig.d.ts +1 -1
- package/dist/utils/getPluginConfig.js +1 -1
- package/dist/utils/getPluginConfig.js.map +1 -1
- package/dist/utils/schemaConversion/getEntityInputSchema.d.ts +11 -0
- package/dist/utils/schemaConversion/getEntityInputSchema.d.ts.map +1 -0
- package/dist/utils/schemaConversion/getEntityInputSchema.js +32 -0
- package/dist/utils/schemaConversion/getEntityInputSchema.js.map +1 -0
- package/dist/utils/schemaConversion/sanitizeEntitySchema.d.ts +5 -10
- package/dist/utils/schemaConversion/sanitizeEntitySchema.d.ts.map +1 -1
- package/dist/utils/schemaConversion/sanitizeEntitySchema.js +15 -95
- package/dist/utils/schemaConversion/sanitizeEntitySchema.js.map +1 -1
- package/dist/utils/schemaConversion/sanitizeEntitySchema.spec.js +15 -40
- package/dist/utils/schemaConversion/sanitizeEntitySchema.spec.js.map +1 -1
- package/dist/utils/whereSchema.d.ts +9 -0
- package/dist/utils/whereSchema.d.ts.map +1 -0
- package/dist/utils/whereSchema.js +13 -0
- package/dist/utils/whereSchema.js.map +1 -0
- package/package.json +8 -11
- package/src/defaultAccess.ts +3 -0
- package/src/endpoint/access.ts +88 -101
- package/src/endpoint/index.ts +14 -1
- package/src/exports/client.ts +2 -1
- package/src/exports/internal.ts +1 -0
- package/src/index.ts +8 -16
- package/src/mcp/buildMcpServer.ts +131 -96
- package/src/mcp/builtin/collections/authTools.ts +45 -9
- package/src/mcp/builtin/collections/countTool.ts +74 -0
- package/src/mcp/builtin/collections/countVersionsTool.ts +70 -0
- package/src/mcp/builtin/collections/createTool.ts +55 -59
- package/src/mcp/builtin/collections/deleteTool.ts +19 -16
- package/src/mcp/builtin/collections/duplicateTool.ts +135 -0
- package/src/mcp/builtin/collections/findDistinctTool.ts +120 -0
- package/src/mcp/builtin/collections/findTool.ts +63 -33
- package/src/mcp/builtin/collections/findVersionByIDTool.ts +110 -0
- package/src/mcp/builtin/collections/findVersionsTool.ts +155 -0
- package/src/mcp/builtin/collections/formatCollectionError.ts +84 -0
- package/src/mcp/builtin/collections/getCollectionSchemaTool.ts +46 -0
- package/src/mcp/builtin/collections/restoreVersionTool.ts +108 -0
- package/src/mcp/builtin/collections/updateTool.ts +111 -110
- package/src/mcp/builtin/getConfigInfoTool.ts +69 -0
- package/src/mcp/builtin/globals/countVersionsTool.ts +69 -0
- package/src/mcp/builtin/globals/findTool.ts +26 -17
- package/src/mcp/builtin/globals/findVersionByIDTool.ts +104 -0
- package/src/mcp/builtin/globals/findVersionsTool.ts +148 -0
- package/src/mcp/builtin/globals/getGlobalSchemaTool.ts +41 -0
- package/src/mcp/builtin/globals/restoreVersionTool.ts +100 -0
- package/src/mcp/builtin/globals/updateTool.ts +50 -55
- package/src/mcp/builtin/validateEntityData.ts +132 -0
- package/src/mcp/builtinTools.ts +111 -41
- package/src/mcp/sanitizeMCPConfig.ts +116 -78
- package/src/stdio.ts +23 -8
- package/src/types.ts +77 -112
- package/src/utils/getPluginConfig.ts +1 -1
- package/src/utils/schemaConversion/getEntityInputSchema.ts +62 -0
- package/src/utils/schemaConversion/sanitizeEntitySchema.spec.ts +12 -19
- package/src/utils/schemaConversion/sanitizeEntitySchema.ts +17 -114
- package/src/utils/whereSchema.ts +24 -0
- package/dist/collection/getAccessField.d.ts +0 -12
- package/dist/collection/getAccessField.d.ts.map +0 -1
- package/dist/collection/getAccessField.js +0 -57
- package/dist/collection/getAccessField.js.map +0 -1
- package/dist/collection/index.d.ts +0 -6
- package/dist/collection/index.d.ts.map +0 -1
- package/dist/collection/index.js +0 -60
- package/dist/collection/index.js.map +0 -1
- package/dist/components/AccessField/index.client.d.ts +0 -10
- package/dist/components/AccessField/index.client.d.ts.map +0 -1
- package/dist/components/AccessField/index.client.js +0 -305
- package/dist/components/AccessField/index.client.js.map +0 -1
- package/dist/components/AccessField/index.css +0 -93
- package/dist/utils/schemaConversion/buildToolInput.d.ts +0 -29
- package/dist/utils/schemaConversion/buildToolInput.d.ts.map +0 -1
- package/dist/utils/schemaConversion/buildToolInput.js +0 -51
- package/dist/utils/schemaConversion/buildToolInput.js.map +0 -1
- package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.d.ts +0 -7
- package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.d.ts.map +0 -1
- package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.js +0 -20
- package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.js.map +0 -1
- package/src/collection/getAccessField.ts +0 -64
- package/src/collection/index.ts +0 -64
- package/src/components/AccessField/index.client.tsx +0 -344
- package/src/components/AccessField/index.css +0 -93
- package/src/utils/schemaConversion/buildToolInput.ts +0 -68
- package/src/utils/schemaConversion/removeVirtualFieldsFromSchema.ts +0 -27
package/src/types.ts
CHANGED
|
@@ -6,18 +6,19 @@ import type {
|
|
|
6
6
|
ResourceTemplate,
|
|
7
7
|
ServerContext,
|
|
8
8
|
StandardSchemaWithJSON,
|
|
9
|
+
ToolAnnotations,
|
|
9
10
|
} from '@modelcontextprotocol/server'
|
|
10
11
|
import type {
|
|
11
12
|
AuthCollectionSlug,
|
|
12
|
-
CollectionConfig,
|
|
13
13
|
CollectionSlug,
|
|
14
|
-
DefaultDocumentIDType,
|
|
15
14
|
GlobalSlug,
|
|
16
15
|
MaybePromise,
|
|
17
16
|
PayloadRequest,
|
|
17
|
+
SanitizedPermissions,
|
|
18
18
|
TypedUser,
|
|
19
19
|
} from 'payload'
|
|
20
20
|
|
|
21
|
+
import type { GetAuthorizedMCPArgs } from './endpoint/access.js'
|
|
21
22
|
import type {
|
|
22
23
|
MCPCollectionAuthToolName,
|
|
23
24
|
MCPCollectionBuiltinName,
|
|
@@ -26,8 +27,8 @@ import type {
|
|
|
26
27
|
|
|
27
28
|
export type { MCPCollectionAuthToolName, MCPCollectionBuiltinName, MCPGlobalBuiltinName }
|
|
28
29
|
|
|
29
|
-
/** Re-exported from `@modelcontextprotocol/server` —
|
|
30
|
-
export type { JsonSchemaType, StandardSchemaWithJSON }
|
|
30
|
+
/** Re-exported from `@modelcontextprotocol/server` — common MCP types used in plugin config. */
|
|
31
|
+
export type { JsonSchemaType, StandardSchemaWithJSON, ToolAnnotations }
|
|
31
32
|
|
|
32
33
|
/**
|
|
33
34
|
* What a tool's `input` (or a prompt's `argsSchema`) can be — either a raw
|
|
@@ -35,25 +36,6 @@ export type { JsonSchemaType, StandardSchemaWithJSON }
|
|
|
35
36
|
*/
|
|
36
37
|
export type ToolInputSchema = JsonSchemaType | StandardSchemaWithJSON
|
|
37
38
|
|
|
38
|
-
/**
|
|
39
|
-
* Serializable mirror of `SanitizedMCPPluginConfig` for client components —
|
|
40
|
-
* the full sanitized config carries functions (tool handlers, etc.) that can't
|
|
41
|
-
* cross the server→client boundary. Built by `sanitizeClientPluginConfig` and
|
|
42
|
-
* passed to the `AccessField` component via `clientProps`.
|
|
43
|
-
*
|
|
44
|
-
* @internal
|
|
45
|
-
*/
|
|
46
|
-
export type ClientMCPPluginConfig = {
|
|
47
|
-
items: Array<{
|
|
48
|
-
collectionSlug?: string
|
|
49
|
-
description: string
|
|
50
|
-
globalSlug?: string
|
|
51
|
-
key: string
|
|
52
|
-
label: string
|
|
53
|
-
type: 'collectionTool' | 'globalTool' | 'prompt' | 'resource' | 'tool'
|
|
54
|
-
}>
|
|
55
|
-
}
|
|
56
|
-
|
|
57
39
|
export type MCPToolResponse = {
|
|
58
40
|
content: ContentBlock[]
|
|
59
41
|
/**
|
|
@@ -80,6 +62,19 @@ export type TypedInput<TSchema> = TSchema extends StandardSchemaWithJSON
|
|
|
80
62
|
: StandardSchemaWithJSON.InferOutput<TSchema>
|
|
81
63
|
: Record<string, unknown>
|
|
82
64
|
|
|
65
|
+
export type MCPAccessArgs = {
|
|
66
|
+
permissions?: SanitizedPermissions
|
|
67
|
+
req: PayloadRequest
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export type CollectionMCPAccessArgs = {
|
|
71
|
+
collectionSlug: CollectionSlug
|
|
72
|
+
} & MCPAccessArgs
|
|
73
|
+
|
|
74
|
+
export type GlobalMCPAccessArgs = {
|
|
75
|
+
globalSlug: GlobalSlug
|
|
76
|
+
} & MCPAccessArgs
|
|
77
|
+
|
|
83
78
|
export type ToolHandlerArgs<TSchema = undefined> = {
|
|
84
79
|
authorizedMCP: AuthorizedMCP
|
|
85
80
|
input: TypedInput<TSchema>
|
|
@@ -96,6 +91,8 @@ export type GlobalToolHandlerArgs<TSchema = undefined> = {
|
|
|
96
91
|
} & ToolHandlerArgs<TSchema>
|
|
97
92
|
|
|
98
93
|
export type Tool<TSchema extends ToolInputSchema | undefined = ToolInputSchema | undefined> = {
|
|
94
|
+
access?: (args: MCPAccessArgs) => MaybePromise<boolean>
|
|
95
|
+
annotations?: ToolAnnotations
|
|
99
96
|
description: string
|
|
100
97
|
handler: (args: ToolHandlerArgs<TSchema>) => MaybePromise<MCPToolResponse>
|
|
101
98
|
input?: TSchema
|
|
@@ -105,30 +102,37 @@ export type Tool<TSchema extends ToolInputSchema | undefined = ToolInputSchema |
|
|
|
105
102
|
overrideResponse?: MCPResponseOverride
|
|
106
103
|
}
|
|
107
104
|
|
|
108
|
-
/**
|
|
109
|
-
* `TSchema` is the schema itself (Standard Schema, raw JSON Schema, or undefined).
|
|
110
|
-
* The function-form variant of `input` carries a concrete `{ collectionSchema: JsonSchemaType }`
|
|
111
|
-
* parameter type so callers can write `({ collectionSchema }) => …` without annotating it.
|
|
112
|
-
*/
|
|
113
105
|
export type CollectionTool<
|
|
114
106
|
TSchema extends ToolInputSchema | undefined = ToolInputSchema | undefined,
|
|
115
107
|
> = {
|
|
108
|
+
access?: (args: CollectionMCPAccessArgs) => MaybePromise<boolean>
|
|
116
109
|
handler: (args: CollectionToolHandlerArgs<TSchema>) => MaybePromise<MCPToolResponse>
|
|
117
|
-
input?:
|
|
118
|
-
} & Pick<Tool, 'description' | 'overrideResponse'>
|
|
110
|
+
input?: TSchema
|
|
111
|
+
} & Pick<Tool, 'annotations' | 'description' | 'overrideResponse'>
|
|
119
112
|
|
|
120
113
|
export type GlobalTool<TSchema extends ToolInputSchema | undefined = ToolInputSchema | undefined> =
|
|
121
114
|
{
|
|
115
|
+
access?: (args: GlobalMCPAccessArgs) => MaybePromise<boolean>
|
|
122
116
|
handler: (args: GlobalToolHandlerArgs<TSchema>) => MaybePromise<MCPToolResponse>
|
|
123
|
-
input?:
|
|
124
|
-
} & Pick<Tool, 'description' | 'overrideResponse'>
|
|
117
|
+
input?: TSchema
|
|
118
|
+
} & Pick<Tool, 'annotations' | 'description' | 'overrideResponse'>
|
|
125
119
|
|
|
126
120
|
/**
|
|
127
121
|
* Configures (or disables) a built-in tool without replacing it.
|
|
128
122
|
* `handler?: never` prevents a full `CollectionTool`/`GlobalTool` (which has a
|
|
129
123
|
* required handler) from being silently accepted at a built-in key slot.
|
|
130
124
|
*/
|
|
131
|
-
export type
|
|
125
|
+
export type MCPBuiltInCollectionToolOverride = {
|
|
126
|
+
access?: (args: CollectionMCPAccessArgs) => MaybePromise<boolean>
|
|
127
|
+
annotations?: ToolAnnotations
|
|
128
|
+
description?: string
|
|
129
|
+
handler?: never
|
|
130
|
+
overrideResponse?: MCPResponseOverride
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export type MCPBuiltInGlobalToolOverride = {
|
|
134
|
+
access?: (args: GlobalMCPAccessArgs) => MaybePromise<boolean>
|
|
135
|
+
annotations?: ToolAnnotations
|
|
132
136
|
description?: string
|
|
133
137
|
handler?: never
|
|
134
138
|
overrideResponse?: MCPResponseOverride
|
|
@@ -142,13 +146,13 @@ export type MCPBuiltInToolOverride = {
|
|
|
142
146
|
export type MCPTopLevelToolEntry = Tool
|
|
143
147
|
|
|
144
148
|
export type MCPCollectionToolsMap = {
|
|
145
|
-
[customToolName: string]: boolean | CollectionTool |
|
|
149
|
+
[customToolName: string]: boolean | CollectionTool | MCPBuiltInCollectionToolOverride | undefined
|
|
146
150
|
} & {
|
|
147
|
-
[K in MCPCollectionBuiltinName]?: false |
|
|
151
|
+
[K in MCPCollectionBuiltinName]?: false | MCPBuiltInCollectionToolOverride
|
|
148
152
|
}
|
|
149
153
|
|
|
150
154
|
export type MCPAuthCollectionToolsMap = {
|
|
151
|
-
[K in MCPCollectionAuthToolName]?:
|
|
155
|
+
[K in MCPCollectionAuthToolName]?: MCPBuiltInCollectionToolOverride | true
|
|
152
156
|
} & MCPCollectionToolsMap
|
|
153
157
|
|
|
154
158
|
/** Auth-enabled collections get auth-tool name autocomplete; others get CRUD-only. */
|
|
@@ -157,9 +161,9 @@ export type MCPToolsMapForCollection<Slug extends CollectionSlug> = Slug extends
|
|
|
157
161
|
: MCPCollectionToolsMap
|
|
158
162
|
|
|
159
163
|
export type MCPGlobalToolsMap = {
|
|
160
|
-
[customToolName: string]: boolean | GlobalTool |
|
|
164
|
+
[customToolName: string]: boolean | GlobalTool | MCPBuiltInGlobalToolOverride | undefined
|
|
161
165
|
} & {
|
|
162
|
-
[K in MCPGlobalBuiltinName]?: false |
|
|
166
|
+
[K in MCPGlobalBuiltinName]?: false | MCPBuiltInGlobalToolOverride
|
|
163
167
|
}
|
|
164
168
|
|
|
165
169
|
export type MCPTopLevelToolsMap = Record<string, Tool>
|
|
@@ -171,6 +175,7 @@ export type PromptHandlerArgs<TSchema = undefined> = {
|
|
|
171
175
|
}
|
|
172
176
|
|
|
173
177
|
export type Prompt<TSchema extends ToolInputSchema = ToolInputSchema> = {
|
|
178
|
+
access?: (args: MCPAccessArgs) => MaybePromise<boolean>
|
|
174
179
|
argsSchema: TSchema
|
|
175
180
|
description: string
|
|
176
181
|
handler: (args: PromptHandlerArgs<TSchema>) => MaybePromise<{
|
|
@@ -188,6 +193,7 @@ export type ResourceHandlerArgs = {
|
|
|
188
193
|
}
|
|
189
194
|
|
|
190
195
|
export type Resource = {
|
|
196
|
+
access?: (args: MCPAccessArgs) => MaybePromise<boolean>
|
|
191
197
|
description: string
|
|
192
198
|
handler: (args: ResourceHandlerArgs) => MaybePromise<{
|
|
193
199
|
contents: Array<{ text: string; uri: string }>
|
|
@@ -221,7 +227,7 @@ export type MCPPluginConfig = {
|
|
|
221
227
|
collections?: {
|
|
222
228
|
[Slug in CollectionSlug]?: MCPPluginCollectionConfig<Slug>
|
|
223
229
|
}
|
|
224
|
-
/** Skip MCP registration.
|
|
230
|
+
/** Skip MCP endpoint registration. */
|
|
225
231
|
disabled?: boolean
|
|
226
232
|
globals?: {
|
|
227
233
|
[Slug in GlobalSlug]?: MCPPluginGlobalConfig
|
|
@@ -230,25 +236,21 @@ export type MCPPluginConfig = {
|
|
|
230
236
|
serverOptions?: MCPServerOptions
|
|
231
237
|
verboseLogs?: boolean
|
|
232
238
|
}
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
req: PayloadRequest
|
|
240
|
-
}) => MaybePromise<AuthorizedMCP>
|
|
239
|
+
/** Replace the default MCP authorization resolver. */
|
|
240
|
+
overrideGetAuthorizedMCP?: (
|
|
241
|
+
args: {
|
|
242
|
+
pluginConfig: SanitizedMCPPluginConfig
|
|
243
|
+
} & GetAuthorizedMCPArgs,
|
|
244
|
+
) => MaybePromise<AuthorizedMCP>
|
|
241
245
|
prompts?: Record<string, Prompt>
|
|
242
246
|
resources?: Record<string, Resource>
|
|
243
247
|
/** Cross-cutting tools (not scoped to any collection or global). */
|
|
244
248
|
tools?: MCPTopLevelToolsMap
|
|
245
|
-
userCollection?: CollectionSlug
|
|
246
249
|
}
|
|
247
250
|
|
|
248
251
|
export type SanitizedMCPPluginConfig = {
|
|
249
252
|
items: MCPItem[]
|
|
250
|
-
|
|
251
|
-
} & Pick<MCPPluginConfig, 'disabled' | 'mcp' | 'overrideApiKeyCollection' | 'overrideAuth'>
|
|
253
|
+
} & Pick<MCPPluginConfig, 'disabled' | 'mcp' | 'overrideGetAuthorizedMCP'>
|
|
252
254
|
|
|
253
255
|
export type MCPServerOptions = {
|
|
254
256
|
options?: ConstructorParameters<typeof McpServer>[1]
|
|
@@ -256,71 +258,32 @@ export type MCPServerOptions = {
|
|
|
256
258
|
}
|
|
257
259
|
|
|
258
260
|
/**
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
* defaults (built-in CRUD is on, opt-in tools are off).
|
|
262
|
-
*/
|
|
263
|
-
export type MCPAPIKeysDocAccessTree = {
|
|
264
|
-
collections?: {
|
|
265
|
-
[CollectionSlug: CollectionSlug]: {
|
|
266
|
-
[ToolKey: string]: boolean
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
globals?: {
|
|
270
|
-
[GlobalSlug: GlobalSlug]: {
|
|
271
|
-
[ToolKey: string]: boolean
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
prompts?: {
|
|
275
|
-
[PromptKey: string]: boolean
|
|
276
|
-
}
|
|
277
|
-
resources?: {
|
|
278
|
-
[ResourceKey: string]: boolean
|
|
279
|
-
}
|
|
280
|
-
tools?: {
|
|
281
|
-
[ToolKey: string]: boolean
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
/**
|
|
286
|
-
* Stored on `payload-mcp-api-keys` docs
|
|
287
|
-
*/
|
|
288
|
-
export type MCPAPIKeysDoc = {
|
|
289
|
-
access: MCPAPIKeysDocAccessTree
|
|
290
|
-
id: DefaultDocumentIDType
|
|
291
|
-
overrideAccess?: boolean
|
|
292
|
-
user: null | TypedUser
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* One MCP primitive — tool, prompt, or resource — paired with the metadata both
|
|
297
|
-
* the endpoint and the API key collection need. Built by `sanitizeMCPConfig`,
|
|
298
|
-
* filtered by `getAuthorizedMCP`, registered by the MCP endpoint.
|
|
261
|
+
* One MCP primitive plus the metadata needed for config-driven filtering and
|
|
262
|
+
* registration.
|
|
299
263
|
*
|
|
300
|
-
*
|
|
301
|
-
*
|
|
302
|
-
*
|
|
303
|
-
* registration time.
|
|
304
|
-
* - `label`: human-readable admin-UI display text for the checkbox.
|
|
305
|
-
* - `tool` / `prompt` / `resource`: the live primitive (its own
|
|
306
|
-
* `description` is what both MCP clients and the admin UI surface).
|
|
264
|
+
* - `configKey`: the config identifier, e.g. `find` or `echo`.
|
|
265
|
+
* - `mcpName`: the MCP wire name, e.g. `findDocuments` or `echo`.
|
|
266
|
+
* - `label`: human-readable display text.
|
|
307
267
|
*/
|
|
308
268
|
export type MCPItemBase = {
|
|
309
|
-
|
|
269
|
+
configKey: string
|
|
310
270
|
label: string
|
|
271
|
+
mcpName: string
|
|
311
272
|
}
|
|
312
273
|
|
|
274
|
+
export type CollectionMCPItem = {
|
|
275
|
+
collectionSlug: CollectionSlug
|
|
276
|
+
tool: CollectionTool
|
|
277
|
+
type: 'collectionTool'
|
|
278
|
+
} & MCPItemBase
|
|
279
|
+
|
|
280
|
+
export type GlobalMCPItem = {
|
|
281
|
+
globalSlug: GlobalSlug
|
|
282
|
+
tool: GlobalTool
|
|
283
|
+
type: 'globalTool'
|
|
284
|
+
} & MCPItemBase
|
|
285
|
+
|
|
313
286
|
export type MCPItem =
|
|
314
|
-
| ({
|
|
315
|
-
collectionSlug: CollectionSlug
|
|
316
|
-
tool: CollectionTool
|
|
317
|
-
type: 'collectionTool'
|
|
318
|
-
} & MCPItemBase)
|
|
319
|
-
| ({
|
|
320
|
-
globalSlug: GlobalSlug
|
|
321
|
-
tool: GlobalTool
|
|
322
|
-
type: 'globalTool'
|
|
323
|
-
} & MCPItemBase)
|
|
324
287
|
| ({
|
|
325
288
|
prompt: Prompt
|
|
326
289
|
type: 'prompt'
|
|
@@ -333,12 +296,14 @@ export type MCPItem =
|
|
|
333
296
|
tool: Tool
|
|
334
297
|
type: 'tool'
|
|
335
298
|
} & MCPItemBase)
|
|
299
|
+
| CollectionMCPItem
|
|
300
|
+
| GlobalMCPItem
|
|
336
301
|
|
|
337
302
|
/**
|
|
338
|
-
* The caller's identity + the MCP items
|
|
339
|
-
*
|
|
340
|
-
* receive this via `args.authorizedMCP` so
|
|
341
|
-
* `localAPIDefaults(authorizedMCP)` into every local API call.
|
|
303
|
+
* The caller's identity + the MCP items authorized for this request. Disabled
|
|
304
|
+
* items and items blocked by access callbacks or Payload operation access are
|
|
305
|
+
* absent from `items`. Tool handlers receive this via `args.authorizedMCP` so
|
|
306
|
+
* they can spread `localAPIDefaults(authorizedMCP)` into every local API call.
|
|
342
307
|
*/
|
|
343
308
|
export type AuthorizedMCP = {
|
|
344
309
|
items: MCPItem[]
|
|
@@ -5,7 +5,7 @@ import type { SanitizedMCPPluginConfig } from '../types.js'
|
|
|
5
5
|
/**
|
|
6
6
|
* Returns the sanitized plugin config — the form produced by
|
|
7
7
|
* `sanitizeMCPConfig` and stashed back on the plugin's `options` during init.
|
|
8
|
-
* Callers downstream of plugin init can rely on `items
|
|
8
|
+
* Callers downstream of plugin init can rely on `items` and other defaults
|
|
9
9
|
* being fully resolved.
|
|
10
10
|
*/
|
|
11
11
|
export const getPluginConfig: (args: { config: SanitizedConfig }) => SanitizedMCPPluginConfig = ({
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type CollectionSlug,
|
|
3
|
+
entityToStandaloneJSONSchema,
|
|
4
|
+
type GlobalSlug,
|
|
5
|
+
type PayloadRequest,
|
|
6
|
+
type SanitizedCollectionConfig,
|
|
7
|
+
type SanitizedGlobalConfig,
|
|
8
|
+
} from 'payload'
|
|
9
|
+
|
|
10
|
+
import type { JsonSchemaType } from '../../types.js'
|
|
11
|
+
|
|
12
|
+
import { sanitizeEntitySchema } from './sanitizeEntitySchema.js'
|
|
13
|
+
|
|
14
|
+
export const getCollectionInputSchema = ({
|
|
15
|
+
collectionSlug,
|
|
16
|
+
req,
|
|
17
|
+
}: {
|
|
18
|
+
collectionSlug: CollectionSlug
|
|
19
|
+
req: PayloadRequest
|
|
20
|
+
}): JsonSchemaType | null => {
|
|
21
|
+
const collection = req.payload.collections[collectionSlug]?.config
|
|
22
|
+
|
|
23
|
+
if (!collection) {
|
|
24
|
+
return null
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
return buildEntityInputSchema({ entity: collection, req })
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export const getGlobalInputSchema = ({
|
|
31
|
+
globalSlug,
|
|
32
|
+
req,
|
|
33
|
+
}: {
|
|
34
|
+
globalSlug: GlobalSlug
|
|
35
|
+
req: PayloadRequest
|
|
36
|
+
}): JsonSchemaType | null => {
|
|
37
|
+
const global = req.payload.config.globals.find((globalConfig) => globalConfig.slug === globalSlug)
|
|
38
|
+
|
|
39
|
+
if (!global) {
|
|
40
|
+
return null
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return buildEntityInputSchema({ entity: global, req })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const buildEntityInputSchema = ({
|
|
47
|
+
entity,
|
|
48
|
+
req,
|
|
49
|
+
}: {
|
|
50
|
+
entity: SanitizedCollectionConfig | SanitizedGlobalConfig
|
|
51
|
+
req: PayloadRequest
|
|
52
|
+
}): JsonSchemaType =>
|
|
53
|
+
// The `input` variant is already the write shape; sanitizeEntitySchema only adds MCP-specific passes.
|
|
54
|
+
sanitizeEntitySchema(
|
|
55
|
+
entityToStandaloneJSONSchema({
|
|
56
|
+
config: req.payload.config,
|
|
57
|
+
defaultIDType: req.payload.db.defaultIDType,
|
|
58
|
+
entity,
|
|
59
|
+
i18n: req.i18n,
|
|
60
|
+
variant: 'input',
|
|
61
|
+
}) as unknown as JsonSchemaType,
|
|
62
|
+
)
|
|
@@ -5,10 +5,11 @@ import type { JsonSchemaType } from '../../types.js'
|
|
|
5
5
|
import { sanitizeEntitySchema } from './sanitizeEntitySchema.js'
|
|
6
6
|
|
|
7
7
|
describe('sanitizeEntitySchema', () => {
|
|
8
|
-
it('
|
|
9
|
-
// Shaped like the schema the MCP server prepares for a collection with a Lexical richText field
|
|
10
|
-
// a `$defs` node union (a `oneOf` of node shapes)
|
|
11
|
-
//
|
|
8
|
+
it('renames the Lexical node-union definition to a short name and keeps it a strict oneOf', () => {
|
|
9
|
+
// Shaped like the schema the MCP server prepares for a collection with a Lexical richText field
|
|
10
|
+
// (Payload's `input` variant): a `$defs` node union (a `oneOf` of node shapes). The input variant
|
|
11
|
+
// already types the relationship node's `value` as a bare ID - there is no populated-doc `$ref` to
|
|
12
|
+
// reduce here (the variant did that upstream), so sanitize leaves the value untouched.
|
|
12
13
|
const standalone: JsonSchemaType = {
|
|
13
14
|
type: 'object',
|
|
14
15
|
$defs: {
|
|
@@ -25,17 +26,12 @@ describe('sanitizeEntitySchema', () => {
|
|
|
25
26
|
additionalProperties: false,
|
|
26
27
|
properties: {
|
|
27
28
|
type: { const: 'relationship' },
|
|
28
|
-
value: {
|
|
29
|
+
value: { type: 'string', description: 'The related document ID.' },
|
|
29
30
|
},
|
|
30
31
|
required: ['type'],
|
|
31
32
|
},
|
|
32
33
|
],
|
|
33
34
|
},
|
|
34
|
-
posts: {
|
|
35
|
-
type: 'object',
|
|
36
|
-
additionalProperties: false,
|
|
37
|
-
properties: { id: { type: 'string' }, title: { type: 'string' } },
|
|
38
|
-
},
|
|
39
35
|
},
|
|
40
36
|
properties: {
|
|
41
37
|
id: { type: 'string' },
|
|
@@ -57,7 +53,7 @@ describe('sanitizeEntitySchema', () => {
|
|
|
57
53
|
type: 'object',
|
|
58
54
|
$defs: {
|
|
59
55
|
// The node union is renamed to a short, readable `node`, and stays a strict discriminated
|
|
60
|
-
// `oneOf` - not loosened to `anyOf
|
|
56
|
+
// `oneOf` - not loosened to `anyOf`.
|
|
61
57
|
node: {
|
|
62
58
|
oneOf: [
|
|
63
59
|
{
|
|
@@ -71,21 +67,18 @@ describe('sanitizeEntitySchema', () => {
|
|
|
71
67
|
additionalProperties: false,
|
|
72
68
|
properties: {
|
|
73
69
|
type: { const: 'relationship' },
|
|
74
|
-
//
|
|
75
|
-
value: { type: 'string', description: 'The
|
|
70
|
+
// The value is already ID-only from the input variant - left as-is.
|
|
71
|
+
value: { type: 'string', description: 'The related document ID.' },
|
|
76
72
|
},
|
|
77
73
|
required: ['type'],
|
|
78
74
|
},
|
|
79
75
|
],
|
|
80
76
|
},
|
|
81
|
-
posts: {
|
|
82
|
-
type: 'object',
|
|
83
|
-
additionalProperties: false,
|
|
84
|
-
properties: { id: { type: 'string' }, title: { type: 'string' } },
|
|
85
|
-
},
|
|
86
77
|
},
|
|
87
78
|
properties: {
|
|
88
|
-
//
|
|
79
|
+
// Managed fields (createdAt/updatedAt/_status) are excluded upstream by the `input` variant;
|
|
80
|
+
// `id` stays because it's a valid optional input (a client may supply a custom ID).
|
|
81
|
+
id: { type: 'string' },
|
|
89
82
|
content: {
|
|
90
83
|
type: 'object',
|
|
91
84
|
properties: {
|
|
@@ -1,30 +1,22 @@
|
|
|
1
1
|
import type { JsonSchemaType } from '../../types.js'
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* - and shrinks the result so it's cheaper for the model to read,
|
|
10
|
-
*
|
|
11
|
-
* while keeping every node valid JSON Schema draft 2020-12. Each step below is tagged with why it runs -
|
|
12
|
-
* **Correctness** (valid input the API accepts), **Size** (equivalence-preserving shrink), or **LLM
|
|
13
|
-
* ergonomics** (easier for the model to read/fill) - and carries a before/after example on its definition.
|
|
4
|
+
* Refines Payload's `input`-variant JSON Schema into an MCP create/update tool's input schema. The
|
|
5
|
+
* variant already covers correctness (ID-only relationships, no managed/virtual/join fields); the
|
|
6
|
+
* passes here are MCP-specific - reshaping fields for the model and shrinking the result - and keep
|
|
7
|
+
* every node valid JSON Schema draft 2020-12. Each is tagged **Correctness**, **Size**, or **LLM
|
|
8
|
+
* ergonomics**, with a before/after example on its definition.
|
|
14
9
|
*/
|
|
15
10
|
export const sanitizeEntitySchema = (schema: JsonSchemaType): JsonSchemaType => {
|
|
16
11
|
// Work on a copy — the caller reuses the original schema elsewhere (e.g. when listing tools).
|
|
17
12
|
let result = structuredClone(schema)
|
|
18
13
|
|
|
19
|
-
//
|
|
20
|
-
result =
|
|
14
|
+
// LLM ergonomics — drop the redundant `'null'` from optional arrays/objects (`['array','null']` → `'array'`).
|
|
15
|
+
result = collapseOptionalContainerTypes(result)
|
|
21
16
|
|
|
22
17
|
// LLM ergonomics — rewrite point fields from a `[number, number]` tuple into a `{ longitude, latitude }` object.
|
|
23
18
|
result = pointFieldsToObjects(result)
|
|
24
19
|
|
|
25
|
-
// Correctness — a relationship value can be an ID or a populated doc; on input only the ID is valid, so keep that.
|
|
26
|
-
result = relationshipsToIds(result)
|
|
27
|
-
|
|
28
20
|
// Size — strip inert type-gen leftovers that only bloat the schema (`tsType`, block-collision notes).
|
|
29
21
|
result = removeTypeGenArtifacts(result)
|
|
30
22
|
|
|
@@ -58,26 +50,20 @@ const mapNodes = (node: unknown, visit: (node: JsonSchemaType) => JsonSchemaType
|
|
|
58
50
|
for (const [key, value] of Object.entries(node)) {
|
|
59
51
|
out[key] = mapNodes(value, visit)
|
|
60
52
|
}
|
|
61
|
-
return visit(out
|
|
53
|
+
return visit(out)
|
|
62
54
|
}
|
|
63
55
|
|
|
64
|
-
// Payload sets these on every document, so an MCP client never provides them when creating/updating.
|
|
65
|
-
const PAYLOAD_MANAGED_FIELDS = new Set(['_status', 'createdAt', 'id', 'updatedAt'])
|
|
66
|
-
|
|
67
56
|
/**
|
|
68
|
-
* **
|
|
69
|
-
*
|
|
70
|
-
*
|
|
57
|
+
* **LLM ergonomics.** Collapses optional array/object types (`['array', 'null']` → `'array'`) via
|
|
58
|
+
* {@link collapseNullableType}, recursing through `properties` and array items. The `required` list
|
|
59
|
+
* already conveys optionality, so the `'null'` is redundant noise for the model.
|
|
71
60
|
*
|
|
72
61
|
* @example
|
|
73
|
-
* { properties: {
|
|
74
|
-
* → { properties: { tags: { type: 'array', items: {} } }, required: [
|
|
62
|
+
* { properties: { tags: { type: ['array', 'null'], items: {} } }, required: [] }
|
|
63
|
+
* → { properties: { tags: { type: 'array', items: {} } }, required: [] }
|
|
75
64
|
*/
|
|
76
|
-
const
|
|
65
|
+
const collapseOptionalContainerTypes = (schema: JsonSchemaType): JsonSchemaType => {
|
|
77
66
|
if (schema.properties && typeof schema.properties === 'object') {
|
|
78
|
-
for (const field of PAYLOAD_MANAGED_FIELDS) {
|
|
79
|
-
delete schema.properties[field]
|
|
80
|
-
}
|
|
81
67
|
for (const key of Object.keys(schema.properties)) {
|
|
82
68
|
const prop = schema.properties[key] as JsonSchemaType
|
|
83
69
|
if (!prop || typeof prop !== 'object') {
|
|
@@ -86,21 +72,14 @@ const removeManagedFields = (schema: JsonSchemaType): JsonSchemaType => {
|
|
|
86
72
|
const isRequired = Array.isArray(schema.required) && schema.required.includes(key)
|
|
87
73
|
collapseNullableType(prop, isRequired)
|
|
88
74
|
if (prop.properties) {
|
|
89
|
-
|
|
75
|
+
collapseOptionalContainerTypes(prop)
|
|
90
76
|
}
|
|
91
77
|
if (prop.items && typeof prop.items === 'object' && !Array.isArray(prop.items)) {
|
|
92
|
-
|
|
78
|
+
collapseOptionalContainerTypes(prop.items)
|
|
93
79
|
}
|
|
94
80
|
}
|
|
95
81
|
}
|
|
96
82
|
|
|
97
|
-
if (Array.isArray(schema.required)) {
|
|
98
|
-
schema.required = schema.required.filter((name) => !PAYLOAD_MANAGED_FIELDS.has(name))
|
|
99
|
-
if (schema.required.length === 0) {
|
|
100
|
-
delete schema.required
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
83
|
return schema
|
|
105
84
|
}
|
|
106
85
|
|
|
@@ -193,82 +172,6 @@ const pointFieldsToObjects = (schema: JsonSchemaType): JsonSchemaType => {
|
|
|
193
172
|
return transformed
|
|
194
173
|
}
|
|
195
174
|
|
|
196
|
-
/**
|
|
197
|
-
* **Correctness.** Reduces relationship/upload fields to the IDs a client actually sends. Payload types the value as
|
|
198
|
-
* "an ID or the full related document" - but the populated-document form only appears in read responses;
|
|
199
|
-
* on create/update you always reference a relationship by its ID. So we drop that `$ref` option, leaving
|
|
200
|
-
* the bare ID. A single remaining target collapses inline (with a description naming the target
|
|
201
|
-
* collection); several targets become an `anyOf` of IDs.
|
|
202
|
-
*
|
|
203
|
-
* @example
|
|
204
|
-
* { oneOf: [{ type: 'string' }, { $ref: '#/$defs/posts' }] }
|
|
205
|
-
* → { type: 'string', description: 'The ID of the related "posts" document.' }
|
|
206
|
-
*/
|
|
207
|
-
const relationshipsToIds = (schema: JsonSchemaType): JsonSchemaType => {
|
|
208
|
-
if (!schema || typeof schema !== 'object') {
|
|
209
|
-
return schema
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
const processed = { ...schema }
|
|
213
|
-
|
|
214
|
-
if (Array.isArray(processed.oneOf)) {
|
|
215
|
-
const isRelatedDocRef = (option: unknown): option is { $ref: string } =>
|
|
216
|
-
!!option && typeof option === 'object' && '$ref' in option
|
|
217
|
-
|
|
218
|
-
if (processed.oneOf.some(isRelatedDocRef)) {
|
|
219
|
-
// A relationship value is "an ID, or the populated related document". Keep the ID option(s) and
|
|
220
|
-
// drop the `$ref`s to the related collections, since a client only ever sends the ID.
|
|
221
|
-
const idOptions = processed.oneOf
|
|
222
|
-
.filter((option) => !isRelatedDocRef(option))
|
|
223
|
-
.map((option) => (typeof option === 'object' ? relationshipsToIds(option) : option))
|
|
224
|
-
const targetCollections = processed.oneOf
|
|
225
|
-
.filter(isRelatedDocRef)
|
|
226
|
-
.map((option) => option.$ref.replace('#/$defs/', ''))
|
|
227
|
-
|
|
228
|
-
if (idOptions.length === 1) {
|
|
229
|
-
delete processed.oneOf
|
|
230
|
-
Object.assign(processed, idOptions[0])
|
|
231
|
-
if (targetCollections.length > 0 && !processed.description) {
|
|
232
|
-
processed.description = `The ID of the related "${targetCollections.join('" or "')}" document.`
|
|
233
|
-
}
|
|
234
|
-
} else if (idOptions.length > 1) {
|
|
235
|
-
delete processed.oneOf
|
|
236
|
-
processed.anyOf = idOptions
|
|
237
|
-
}
|
|
238
|
-
} else {
|
|
239
|
-
processed.oneOf = processed.oneOf.map((option) =>
|
|
240
|
-
typeof option === 'object' ? relationshipsToIds(option) : option,
|
|
241
|
-
)
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
if (processed.properties && typeof processed.properties === 'object') {
|
|
246
|
-
processed.properties = Object.fromEntries(
|
|
247
|
-
Object.entries(processed.properties).map(([key, value]) => [
|
|
248
|
-
key,
|
|
249
|
-
typeof value === 'object' ? relationshipsToIds(value) : value,
|
|
250
|
-
]),
|
|
251
|
-
)
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
if (processed.items && typeof processed.items === 'object' && !Array.isArray(processed.items)) {
|
|
255
|
-
processed.items = relationshipsToIds(processed.items)
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
// Lexical node unions and blocks live under `$defs` and have their own relationship fields, so walk
|
|
259
|
-
// those too — otherwise their `$ref`s would dangle once we don't bundle the related collections.
|
|
260
|
-
if (processed.$defs && typeof processed.$defs === 'object') {
|
|
261
|
-
processed.$defs = Object.fromEntries(
|
|
262
|
-
Object.entries(processed.$defs).map(([key, value]) => [
|
|
263
|
-
key,
|
|
264
|
-
typeof value === 'object' ? relationshipsToIds(value) : value,
|
|
265
|
-
]),
|
|
266
|
-
)
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
return processed
|
|
270
|
-
}
|
|
271
|
-
|
|
272
175
|
/**
|
|
273
176
|
* **Size.** Strips type-generation leftovers that bloat the schema without helping the model: the `tsType`
|
|
274
177
|
* hint (a `json-schema-to-typescript` extension; JSON Schema validators just ignore it, they don't reject it)
|
|
@@ -448,7 +351,7 @@ const deduplicateIntoDefinitions = (schema: JsonSchemaType): JsonSchemaType => {
|
|
|
448
351
|
if (!name) {
|
|
449
352
|
name = `shared_${nameByKey.size}`
|
|
450
353
|
nameByKey.set(key, name)
|
|
451
|
-
sharedEntries[name] = node
|
|
354
|
+
sharedEntries[name] = node
|
|
452
355
|
}
|
|
453
356
|
return { $ref: `#/$defs/${name}` }
|
|
454
357
|
}
|