@payloadcms/plugin-mcp 4.0.0-internal.bc5cf8c → 4.0.0-internal.c7d5de4
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/bin.js +7 -0
- 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 +4 -11
- 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 +57 -17
- 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 +4 -4
- package/dist/mcp/buildMcpServer.d.ts.map +1 -1
- package/dist/mcp/buildMcpServer.js +32 -10
- package/dist/mcp/buildMcpServer.js.map +1 -1
- package/dist/mcp/builtin/collections/countTool.d.ts.map +1 -1
- package/dist/mcp/builtin/collections/countTool.js +1 -2
- 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 +1 -2
- package/dist/mcp/builtin/collections/countVersionsTool.js.map +1 -1
- package/dist/mcp/builtin/collections/createTool.d.ts.map +1 -1
- package/dist/mcp/builtin/collections/createTool.js +14 -5
- 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 +1 -2
- 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 +1 -2
- package/dist/mcp/builtin/collections/duplicateTool.js.map +1 -1
- package/dist/mcp/builtin/collections/fileInput.d.ts +20 -0
- package/dist/mcp/builtin/collections/fileInput.d.ts.map +1 -0
- package/dist/mcp/builtin/collections/fileInput.js +100 -0
- package/dist/mcp/builtin/collections/fileInput.js.map +1 -0
- package/dist/mcp/builtin/collections/fileInput.spec.js +72 -0
- package/dist/mcp/builtin/collections/fileInput.spec.js.map +1 -0
- package/dist/mcp/builtin/collections/findDistinctTool.d.ts.map +1 -1
- package/dist/mcp/builtin/collections/findDistinctTool.js +1 -2
- 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 +3 -4
- 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 +1 -2
- 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 +1 -2
- package/dist/mcp/builtin/collections/findVersionsTool.js.map +1 -1
- package/dist/mcp/builtin/collections/formatCollectionError.d.ts +1 -1
- package/dist/mcp/builtin/collections/formatCollectionError.d.ts.map +1 -1
- package/dist/mcp/builtin/collections/getCollectionSchemaTool.d.ts.map +1 -1
- package/dist/mcp/builtin/collections/getCollectionSchemaTool.js +44 -5
- 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 +1 -2
- 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 +23 -11
- package/dist/mcp/builtin/collections/updateTool.js.map +1 -1
- package/dist/mcp/builtin/getConfigInfoTool.d.ts.map +1 -1
- package/dist/mcp/builtin/getConfigInfoTool.js +2 -5
- 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 +1 -2
- 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 +2 -2
- 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 +1 -2
- 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 +1 -2
- 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 +21 -3
- 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 +1 -2
- 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 +2 -2
- package/dist/mcp/builtin/globals/updateTool.js.map +1 -1
- package/dist/mcp/builtin/validateEntityData.d.ts.map +1 -1
- package/dist/mcp/builtin/validateEntityData.js +1 -1
- package/dist/mcp/builtin/validateEntityData.js.map +1 -1
- package/dist/mcp/builtinTools.d.ts.map +1 -1
- package/dist/mcp/sanitizeMCPConfig.d.ts.map +1 -1
- package/dist/mcp/sanitizeMCPConfig.js +1 -0
- 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 +59 -41
- package/dist/stdio.js.map +1 -1
- package/dist/types.d.ts +57 -9
- 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/schemaConversion/filterFieldsByAccess.d.ts +28 -0
- package/dist/utils/schemaConversion/filterFieldsByAccess.d.ts.map +1 -0
- package/dist/utils/schemaConversion/filterFieldsByAccess.js +71 -0
- package/dist/utils/schemaConversion/filterFieldsByAccess.js.map +1 -0
- package/dist/utils/schemaConversion/getEntityInputSchema.d.ts +5 -3
- package/dist/utils/schemaConversion/getEntityInputSchema.d.ts.map +1 -1
- package/dist/utils/schemaConversion/getEntityInputSchema.js +44 -12
- package/dist/utils/schemaConversion/getEntityInputSchema.js.map +1 -1
- 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/toStandardSchema.d.ts.map +1 -1
- package/package.json +9 -11
- package/src/endpoint/access.ts +3 -10
- package/src/endpoint/index.ts +45 -16
- package/src/index.ts +4 -4
- package/src/mcp/buildMcpServer.ts +40 -20
- package/src/mcp/builtin/collections/countTool.ts +1 -2
- package/src/mcp/builtin/collections/countVersionsTool.ts +1 -2
- package/src/mcp/builtin/collections/createTool.ts +14 -5
- package/src/mcp/builtin/collections/deleteTool.ts +1 -2
- package/src/mcp/builtin/collections/duplicateTool.ts +1 -2
- package/src/mcp/builtin/collections/fileInput.spec.ts +79 -0
- package/src/mcp/builtin/collections/fileInput.ts +139 -0
- package/src/mcp/builtin/collections/findDistinctTool.ts +1 -2
- package/src/mcp/builtin/collections/findTool.ts +3 -4
- package/src/mcp/builtin/collections/findVersionByIDTool.ts +1 -2
- package/src/mcp/builtin/collections/findVersionsTool.ts +1 -2
- package/src/mcp/builtin/collections/getCollectionSchemaTool.ts +39 -9
- package/src/mcp/builtin/collections/restoreVersionTool.ts +1 -2
- package/src/mcp/builtin/collections/updateTool.ts +25 -9
- package/src/mcp/builtin/getConfigInfoTool.ts +2 -4
- package/src/mcp/builtin/globals/countVersionsTool.ts +1 -2
- package/src/mcp/builtin/globals/findTool.ts +2 -2
- package/src/mcp/builtin/globals/findVersionByIDTool.ts +1 -2
- package/src/mcp/builtin/globals/findVersionsTool.ts +1 -2
- package/src/mcp/builtin/globals/getGlobalSchemaTool.ts +25 -3
- package/src/mcp/builtin/globals/restoreVersionTool.ts +1 -2
- package/src/mcp/builtin/globals/updateTool.ts +2 -2
- package/src/mcp/builtin/validateEntityData.ts +1 -1
- package/src/mcp/sanitizeMCPConfig.ts +1 -0
- package/src/stdio.ts +59 -42
- package/src/types.ts +57 -9
- package/src/utils/schemaConversion/filterFieldsByAccess.ts +123 -0
- package/src/utils/schemaConversion/getEntityInputSchema.ts +45 -27
- package/src/utils/schemaConversion/sanitizeEntitySchema.spec.ts +12 -19
- package/src/utils/schemaConversion/sanitizeEntitySchema.ts +17 -114
- package/dist/@types/assets.d.js +0 -2
- package/dist/@types/assets.d.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/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/@types/assets.d.ts +0 -3
- package/src/utils/localAPIDefaults.ts +0 -22
- package/src/utils/schemaConversion/removeVirtualFieldsFromSchema.ts +0 -27
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
import { sanitizeEntitySchema } from './sanitizeEntitySchema.js';
|
|
3
3
|
describe('sanitizeEntitySchema', ()=>{
|
|
4
|
-
it('
|
|
5
|
-
// Shaped like the schema the MCP server prepares for a collection with a Lexical richText field
|
|
6
|
-
// a `$defs` node union (a `oneOf` of node shapes)
|
|
7
|
-
//
|
|
4
|
+
it('renames the Lexical node-union definition to a short name and keeps it a strict oneOf', ()=>{
|
|
5
|
+
// Shaped like the schema the MCP server prepares for a collection with a Lexical richText field
|
|
6
|
+
// (Payload's `input` variant): a `$defs` node union (a `oneOf` of node shapes). The input variant
|
|
7
|
+
// already types the relationship node's `value` as a bare ID - there is no populated-doc `$ref` to
|
|
8
|
+
// reduce here (the variant did that upstream), so sanitize leaves the value untouched.
|
|
8
9
|
const standalone = {
|
|
9
10
|
type: 'object',
|
|
10
11
|
$defs: {
|
|
@@ -30,14 +31,8 @@ describe('sanitizeEntitySchema', ()=>{
|
|
|
30
31
|
const: 'relationship'
|
|
31
32
|
},
|
|
32
33
|
value: {
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
type: 'string'
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
$ref: '#/$defs/posts'
|
|
39
|
-
}
|
|
40
|
-
]
|
|
34
|
+
type: 'string',
|
|
35
|
+
description: 'The related document ID.'
|
|
41
36
|
}
|
|
42
37
|
},
|
|
43
38
|
required: [
|
|
@@ -45,18 +40,6 @@ describe('sanitizeEntitySchema', ()=>{
|
|
|
45
40
|
]
|
|
46
41
|
}
|
|
47
42
|
]
|
|
48
|
-
},
|
|
49
|
-
posts: {
|
|
50
|
-
type: 'object',
|
|
51
|
-
additionalProperties: false,
|
|
52
|
-
properties: {
|
|
53
|
-
id: {
|
|
54
|
-
type: 'string'
|
|
55
|
-
},
|
|
56
|
-
title: {
|
|
57
|
-
type: 'string'
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
43
|
}
|
|
61
44
|
},
|
|
62
45
|
properties: {
|
|
@@ -85,7 +68,7 @@ describe('sanitizeEntitySchema', ()=>{
|
|
|
85
68
|
type: 'object',
|
|
86
69
|
$defs: {
|
|
87
70
|
// The node union is renamed to a short, readable `node`, and stays a strict discriminated
|
|
88
|
-
// `oneOf` - not loosened to `anyOf
|
|
71
|
+
// `oneOf` - not loosened to `anyOf`.
|
|
89
72
|
node: {
|
|
90
73
|
oneOf: [
|
|
91
74
|
{
|
|
@@ -107,10 +90,10 @@ describe('sanitizeEntitySchema', ()=>{
|
|
|
107
90
|
type: {
|
|
108
91
|
const: 'relationship'
|
|
109
92
|
},
|
|
110
|
-
//
|
|
93
|
+
// The value is already ID-only from the input variant - left as-is.
|
|
111
94
|
value: {
|
|
112
95
|
type: 'string',
|
|
113
|
-
description: 'The
|
|
96
|
+
description: 'The related document ID.'
|
|
114
97
|
}
|
|
115
98
|
},
|
|
116
99
|
required: [
|
|
@@ -118,22 +101,14 @@ describe('sanitizeEntitySchema', ()=>{
|
|
|
118
101
|
]
|
|
119
102
|
}
|
|
120
103
|
]
|
|
121
|
-
},
|
|
122
|
-
posts: {
|
|
123
|
-
type: 'object',
|
|
124
|
-
additionalProperties: false,
|
|
125
|
-
properties: {
|
|
126
|
-
id: {
|
|
127
|
-
type: 'string'
|
|
128
|
-
},
|
|
129
|
-
title: {
|
|
130
|
-
type: 'string'
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
104
|
}
|
|
134
105
|
},
|
|
135
106
|
properties: {
|
|
136
|
-
//
|
|
107
|
+
// Managed timestamps are excluded upstream by the `input` variant. `id` stays because it's
|
|
108
|
+
// a valid optional input (a client may supply a custom ID).
|
|
109
|
+
id: {
|
|
110
|
+
type: 'string'
|
|
111
|
+
},
|
|
137
112
|
content: {
|
|
138
113
|
type: 'object',
|
|
139
114
|
properties: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/utils/schemaConversion/sanitizeEntitySchema.spec.ts"],"sourcesContent":["import { describe, expect, it } from 'vitest'\n\nimport type { JsonSchemaType } from '../../types.js'\n\nimport { sanitizeEntitySchema } from './sanitizeEntitySchema.js'\n\ndescribe('sanitizeEntitySchema', () => {\n it('
|
|
1
|
+
{"version":3,"sources":["../../../src/utils/schemaConversion/sanitizeEntitySchema.spec.ts"],"sourcesContent":["import { describe, expect, it } from 'vitest'\n\nimport type { JsonSchemaType } from '../../types.js'\n\nimport { sanitizeEntitySchema } from './sanitizeEntitySchema.js'\n\ndescribe('sanitizeEntitySchema', () => {\n it('renames the Lexical node-union definition to a short name and keeps it a strict oneOf', () => {\n // Shaped like the schema the MCP server prepares for a collection with a Lexical richText field\n // (Payload's `input` variant): a `$defs` node union (a `oneOf` of node shapes). The input variant\n // already types the relationship node's `value` as a bare ID - there is no populated-doc `$ref` to\n // reduce here (the variant did that upstream), so sanitize leaves the value untouched.\n const standalone: JsonSchemaType = {\n type: 'object',\n $defs: {\n LexicalNodes_ABCDEF12: {\n oneOf: [\n {\n type: 'object',\n additionalProperties: false,\n properties: { type: { const: 'paragraph' } },\n required: ['type'],\n },\n {\n type: 'object',\n additionalProperties: false,\n properties: {\n type: { const: 'relationship' },\n value: { type: 'string', description: 'The related document ID.' },\n },\n required: ['type'],\n },\n ],\n },\n },\n properties: {\n id: { type: 'string' },\n content: {\n type: 'object',\n properties: {\n root: {\n type: 'object',\n properties: {\n children: { type: 'array', items: { $ref: '#/$defs/LexicalNodes_ABCDEF12' } },\n },\n },\n },\n },\n },\n }\n\n expect(sanitizeEntitySchema(standalone)).toStrictEqual({\n type: 'object',\n $defs: {\n // The node union is renamed to a short, readable `node`, and stays a strict discriminated\n // `oneOf` - not loosened to `anyOf`.\n node: {\n oneOf: [\n {\n type: 'object',\n additionalProperties: false,\n properties: { type: { const: 'paragraph' } },\n required: ['type'],\n },\n {\n type: 'object',\n additionalProperties: false,\n properties: {\n type: { const: 'relationship' },\n // The value is already ID-only from the input variant - left as-is.\n value: { type: 'string', description: 'The related document ID.' },\n },\n required: ['type'],\n },\n ],\n },\n },\n properties: {\n // Managed timestamps are excluded upstream by the `input` variant. `id` stays because it's\n // a valid optional input (a client may supply a custom ID).\n id: { type: 'string' },\n content: {\n type: 'object',\n properties: {\n root: {\n type: 'object',\n properties: {\n children: { type: 'array', items: { $ref: '#/$defs/node' } },\n },\n },\n },\n },\n },\n })\n })\n})\n"],"names":["describe","expect","it","sanitizeEntitySchema","standalone","type","$defs","LexicalNodes_ABCDEF12","oneOf","additionalProperties","properties","const","required","value","description","id","content","root","children","items","$ref","toStrictEqual","node"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,QAAQ,SAAQ;AAI7C,SAASC,oBAAoB,QAAQ,4BAA2B;AAEhEH,SAAS,wBAAwB;IAC/BE,GAAG,yFAAyF;QAC1F,gGAAgG;QAChG,kGAAkG;QAClG,mGAAmG;QACnG,uFAAuF;QACvF,MAAME,aAA6B;YACjCC,MAAM;YACNC,OAAO;gBACLC,uBAAuB;oBACrBC,OAAO;wBACL;4BACEH,MAAM;4BACNI,sBAAsB;4BACtBC,YAAY;gCAAEL,MAAM;oCAAEM,OAAO;gCAAY;4BAAE;4BAC3CC,UAAU;gCAAC;6BAAO;wBACpB;wBACA;4BACEP,MAAM;4BACNI,sBAAsB;4BACtBC,YAAY;gCACVL,MAAM;oCAAEM,OAAO;gCAAe;gCAC9BE,OAAO;oCAAER,MAAM;oCAAUS,aAAa;gCAA2B;4BACnE;4BACAF,UAAU;gCAAC;6BAAO;wBACpB;qBACD;gBACH;YACF;YACAF,YAAY;gBACVK,IAAI;oBAAEV,MAAM;gBAAS;gBACrBW,SAAS;oBACPX,MAAM;oBACNK,YAAY;wBACVO,MAAM;4BACJZ,MAAM;4BACNK,YAAY;gCACVQ,UAAU;oCAAEb,MAAM;oCAASc,OAAO;wCAAEC,MAAM;oCAAgC;gCAAE;4BAC9E;wBACF;oBACF;gBACF;YACF;QACF;QAEAnB,OAAOE,qBAAqBC,aAAaiB,aAAa,CAAC;YACrDhB,MAAM;YACNC,OAAO;gBACL,0FAA0F;gBAC1F,qCAAqC;gBACrCgB,MAAM;oBACJd,OAAO;wBACL;4BACEH,MAAM;4BACNI,sBAAsB;4BACtBC,YAAY;gCAAEL,MAAM;oCAAEM,OAAO;gCAAY;4BAAE;4BAC3CC,UAAU;gCAAC;6BAAO;wBACpB;wBACA;4BACEP,MAAM;4BACNI,sBAAsB;4BACtBC,YAAY;gCACVL,MAAM;oCAAEM,OAAO;gCAAe;gCAC9B,oEAAoE;gCACpEE,OAAO;oCAAER,MAAM;oCAAUS,aAAa;gCAA2B;4BACnE;4BACAF,UAAU;gCAAC;6BAAO;wBACpB;qBACD;gBACH;YACF;YACAF,YAAY;gBACV,2FAA2F;gBAC3F,4DAA4D;gBAC5DK,IAAI;oBAAEV,MAAM;gBAAS;gBACrBW,SAAS;oBACPX,MAAM;oBACNK,YAAY;wBACVO,MAAM;4BACJZ,MAAM;4BACNK,YAAY;gCACVQ,UAAU;oCAAEb,MAAM;oCAASc,OAAO;wCAAEC,MAAM;oCAAe;gCAAE;4BAC7D;wBACF;oBACF;gBACF;YACF;QACF;IACF;AACF"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"toStandardSchema.d.ts","sourceRoot":"","sources":["../../src/utils/toStandardSchema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,sBAAsB,EAAE,MAAM,8BAA8B,CAAA;AAE1F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAElD,gIAAgI;AAChI,eAAO,MAAM,gBAAgB,
|
|
1
|
+
{"version":3,"file":"toStandardSchema.d.ts","sourceRoot":"","sources":["../../src/utils/toStandardSchema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAkB,KAAK,sBAAsB,EAAE,MAAM,8BAA8B,CAAA;AAE1F,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAA;AAElD,gIAAgI;AAChI,eAAO,MAAM,gBAAgB,WAAY,eAAe,KAAG,sBAG/B,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@payloadcms/plugin-mcp",
|
|
3
|
-
"version": "4.0.0-internal.
|
|
3
|
+
"version": "4.0.0-internal.c7d5de4",
|
|
4
4
|
"description": "MCP (Model Context Protocol) capabilities with Payload",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"plugin",
|
|
@@ -39,9 +39,9 @@
|
|
|
39
39
|
"default": "./dist/stdio.js"
|
|
40
40
|
},
|
|
41
41
|
"./internal": {
|
|
42
|
-
"import": "./dist/internal.js",
|
|
43
|
-
"types": "./dist/internal.d.ts",
|
|
44
|
-
"default": "./dist/internal.js"
|
|
42
|
+
"import": "./dist/exports/internal.js",
|
|
43
|
+
"types": "./dist/exports/internal.d.ts",
|
|
44
|
+
"default": "./dist/exports/internal.js"
|
|
45
45
|
}
|
|
46
46
|
},
|
|
47
47
|
"main": "./dist/index.js",
|
|
@@ -55,21 +55,19 @@
|
|
|
55
55
|
"bin.js"
|
|
56
56
|
],
|
|
57
57
|
"dependencies": {
|
|
58
|
-
"@
|
|
59
|
-
"@modelcontextprotocol/server": "2.0.0-alpha.2",
|
|
58
|
+
"@modelcontextprotocol/server": "2.0.0-beta.3",
|
|
60
59
|
"tsx": "4.22.4",
|
|
61
|
-
"zod": "^4.
|
|
60
|
+
"zod": "^4.2.0"
|
|
62
61
|
},
|
|
63
62
|
"devDependencies": {
|
|
64
|
-
"
|
|
65
|
-
"
|
|
63
|
+
"payload": "4.0.0-internal.c7d5de4",
|
|
64
|
+
"@payloadcms/eslint-config": "3.28.0"
|
|
66
65
|
},
|
|
67
66
|
"peerDependencies": {
|
|
68
|
-
"payload": "4.0.0-internal.
|
|
67
|
+
"payload": "4.0.0-internal.c7d5de4"
|
|
69
68
|
},
|
|
70
69
|
"//deps_notes": {
|
|
71
70
|
"zod": "zod is a hard dependency of @modelcontextprotocol/server, thus we can safely use it without it impacting bundle size. Make extra sure the zod version here matches exactly what's defined in the dependencies of @modelcontextprotocol/server to avoid duplicate versions being installed.",
|
|
72
|
-
"@cfworker/json-schema": "Should be able to remove in next @modelcontextprotocol/server alpha release. Marked as an optional peer of @modelcontextprotocol/server, but its bundle does an eager top-level `import { Validator } from '@cfworker/json-schema'`, so it must be installed even when only zod-registered tools are used. Drop once upstream lazy-loads the validator. See: https://github.com/modelcontextprotocol/typescript-sdk",
|
|
73
71
|
"tsx": "Required by the `bin.js` stdio mcp. Keep the version pinned to whatever `payload` ships to avoid duplicate installs."
|
|
74
72
|
},
|
|
75
73
|
"homepage:": "https://payloadcms.com",
|
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
|
}
|
package/src/endpoint/index.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import type { PayloadHandler, PayloadRequest } from 'payload'
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
createMcpHandler,
|
|
5
|
+
isLegacyRequest,
|
|
6
|
+
WebStandardStreamableHTTPServerTransport,
|
|
7
|
+
} from '@modelcontextprotocol/server'
|
|
8
|
+
import { APIError } from 'payload'
|
|
3
9
|
|
|
4
10
|
import { buildMcpServer } from '../mcp/buildMcpServer.js'
|
|
5
11
|
import { getPluginConfig } from '../utils/getPluginConfig.js'
|
|
@@ -27,22 +33,45 @@ export const mcpEndpoint: PayloadHandler = async (req) => {
|
|
|
27
33
|
}
|
|
28
34
|
|
|
29
35
|
const authorizedMCP = await getAuthorizedMCP({ overrideAccess, req })
|
|
36
|
+
// Payload augments the original web-standard Request in place.
|
|
37
|
+
const mcpRequest = req as PayloadRequest & Request
|
|
30
38
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
39
|
+
// Keep the old JSON-only, stateless behavior because the SDK's 2025 fallback uses SSE.
|
|
40
|
+
if (await isLegacyRequest(mcpRequest)) {
|
|
41
|
+
const server = buildMcpServer({ authorizedMCP, pluginConfig, req })
|
|
42
|
+
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
43
|
+
enableJsonResponse: true,
|
|
44
|
+
sessionIdGenerator: undefined, // stateless mode
|
|
45
|
+
})
|
|
46
|
+
transport.onerror = (err) => {
|
|
47
|
+
req.payload.logger.error({ err, msg: 'Error serving legacy MCP request' })
|
|
48
|
+
}
|
|
37
49
|
|
|
38
|
-
|
|
50
|
+
try {
|
|
51
|
+
await server.connect(transport)
|
|
52
|
+
return await transport.handleRequest(mcpRequest)
|
|
53
|
+
} finally {
|
|
54
|
+
await server.close().catch((err) => {
|
|
55
|
+
req.payload.logger.error({ err, msg: 'Error closing MCP server' })
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
}
|
|
39
59
|
|
|
40
|
-
const
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
60
|
+
const handler = createMcpHandler(() => buildMcpServer({ authorizedMCP, pluginConfig, req }), {
|
|
61
|
+
legacy: 'reject',
|
|
62
|
+
// SDK subscriptions always use SSE, so disable them to keep every response JSON-only.
|
|
63
|
+
maxSubscriptions: 0,
|
|
64
|
+
onerror: (err) => {
|
|
65
|
+
req.payload.logger.error({ err, msg: 'Error serving modern MCP request' })
|
|
66
|
+
},
|
|
67
|
+
responseMode: 'json',
|
|
68
|
+
})
|
|
46
69
|
|
|
47
|
-
|
|
70
|
+
try {
|
|
71
|
+
return await handler.fetch(mcpRequest)
|
|
72
|
+
} finally {
|
|
73
|
+
await handler.close().catch((err) => {
|
|
74
|
+
req.payload.logger.error({ err, msg: 'Error closing modern MCP handler' })
|
|
75
|
+
})
|
|
76
|
+
}
|
|
48
77
|
}
|
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']
|
|
@@ -17,10 +17,10 @@ import { getLogger } from '../utils/getLogger.js'
|
|
|
17
17
|
import { toStandardSchema } from '../utils/toStandardSchema.js'
|
|
18
18
|
|
|
19
19
|
/**
|
|
20
|
-
*
|
|
21
|
-
* `McpServer` and returns it. The
|
|
22
|
-
*
|
|
23
|
-
*
|
|
20
|
+
* Serving-entry-agnostic core: registers every authorized MCP item onto a fresh
|
|
21
|
+
* `McpServer` and returns it. The HTTP and stdio entry point callers provide fresh
|
|
22
|
+
* instances from this builder while they own the transport and protocol-era
|
|
23
|
+
* decision.
|
|
24
24
|
*
|
|
25
25
|
* `req` is the request context handlers see. For HTTP it's the live
|
|
26
26
|
* `PayloadRequest` derived from the incoming HTTP request; for stdio it's a
|
|
@@ -47,11 +47,21 @@ export const buildMcpServer = ({
|
|
|
47
47
|
* Wrap a tool handler's response with the tool's `overrideResponse`, then
|
|
48
48
|
* strip the internal `doc` field so it doesn't leak onto the wire.
|
|
49
49
|
*/
|
|
50
|
-
const finalizeToolResponse = (
|
|
51
|
-
|
|
52
|
-
overrideResponse
|
|
53
|
-
|
|
54
|
-
|
|
50
|
+
const finalizeToolResponse = async ({
|
|
51
|
+
input,
|
|
52
|
+
overrideResponse,
|
|
53
|
+
response,
|
|
54
|
+
toolName,
|
|
55
|
+
}: {
|
|
56
|
+
input: unknown
|
|
57
|
+
overrideResponse?: MCPResponseOverride
|
|
58
|
+
response: MCPToolResponse
|
|
59
|
+
toolName: string
|
|
60
|
+
}): Promise<MCPToolResponse> => {
|
|
61
|
+
let overridden = overrideResponse?.(response, response.doc ?? {}, req) ?? response
|
|
62
|
+
for (const hook of pluginConfig.hooks?.afterToolCall ?? []) {
|
|
63
|
+
overridden = await hook({ input, req, response: overridden, toolName })
|
|
64
|
+
}
|
|
55
65
|
const { doc: _doc, ...rest } = overridden
|
|
56
66
|
return rest
|
|
57
67
|
}
|
|
@@ -119,7 +129,12 @@ export const buildMcpServer = ({
|
|
|
119
129
|
? match.tool.handler({ ...handlerArgs, collectionSlug: slug })
|
|
120
130
|
: match.tool.handler({ ...handlerArgs, globalSlug: slug }))
|
|
121
131
|
|
|
122
|
-
return finalizeToolResponse(
|
|
132
|
+
return finalizeToolResponse({
|
|
133
|
+
input: toolInput,
|
|
134
|
+
overrideResponse: match.tool.overrideResponse,
|
|
135
|
+
response,
|
|
136
|
+
toolName: match.mcpName,
|
|
137
|
+
})
|
|
123
138
|
}
|
|
124
139
|
|
|
125
140
|
try {
|
|
@@ -203,16 +218,21 @@ export const buildMcpServer = ({
|
|
|
203
218
|
description: tool.description,
|
|
204
219
|
inputSchema: tool.input ? toStandardSchema(tool.input) : undefined,
|
|
205
220
|
},
|
|
206
|
-
async (input: unknown, ctx: ServerContext) =>
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
221
|
+
async (input: unknown, ctx: ServerContext) => {
|
|
222
|
+
const toolInput = (input ?? {}) as Record<string, unknown>
|
|
223
|
+
const response = await tool.handler({
|
|
224
|
+
authorizedMCP,
|
|
225
|
+
input: toolInput,
|
|
226
|
+
req,
|
|
227
|
+
serverContext: ctx,
|
|
228
|
+
})
|
|
229
|
+
return finalizeToolResponse({
|
|
230
|
+
input: toolInput,
|
|
231
|
+
overrideResponse: tool.overrideResponse,
|
|
232
|
+
response,
|
|
233
|
+
toolName: item.mcpName,
|
|
234
|
+
})
|
|
235
|
+
},
|
|
216
236
|
)
|
|
217
237
|
logger.info(`✅ Tool: ${item.mcpName} Registered.`)
|
|
218
238
|
break
|
|
@@ -3,7 +3,6 @@ import { z } from 'zod'
|
|
|
3
3
|
import { defaultAccess } from '../../../defaultAccess.js'
|
|
4
4
|
import { defineCollectionTool } from '../../../defineTool.js'
|
|
5
5
|
import { getLogger } from '../../../utils/getLogger.js'
|
|
6
|
-
import { localAPIDefaults } from '../../../utils/localAPIDefaults.js'
|
|
7
6
|
import { whereSchema } from '../../../utils/whereSchema.js'
|
|
8
7
|
|
|
9
8
|
const DEFAULT_DESCRIPTION =
|
|
@@ -42,8 +41,8 @@ export const countDocumentsTool = defineCollectionTool({
|
|
|
42
41
|
try {
|
|
43
42
|
const result = await payload.count({
|
|
44
43
|
collection: collectionSlug,
|
|
44
|
+
overrideAccess: authorizedMCP.overrideAccess,
|
|
45
45
|
req,
|
|
46
|
-
...localAPIDefaults(authorizedMCP),
|
|
47
46
|
...(locale ? { locale } : {}),
|
|
48
47
|
...(trash !== undefined ? { trash } : {}),
|
|
49
48
|
...(where ? { where } : {}),
|
|
@@ -3,7 +3,6 @@ import { z } from 'zod'
|
|
|
3
3
|
import { defaultAccess } from '../../../defaultAccess.js'
|
|
4
4
|
import { defineCollectionTool } from '../../../defineTool.js'
|
|
5
5
|
import { getLogger } from '../../../utils/getLogger.js'
|
|
6
|
-
import { localAPIDefaults } from '../../../utils/localAPIDefaults.js'
|
|
7
6
|
import { whereSchema } from '../../../utils/whereSchema.js'
|
|
8
7
|
|
|
9
8
|
const DEFAULT_DESCRIPTION =
|
|
@@ -39,8 +38,8 @@ export const countVersionsTool = defineCollectionTool({
|
|
|
39
38
|
try {
|
|
40
39
|
const result = await payload.countVersions({
|
|
41
40
|
collection: collectionSlug,
|
|
41
|
+
overrideAccess: authorizedMCP.overrideAccess,
|
|
42
42
|
req,
|
|
43
|
-
...localAPIDefaults(authorizedMCP),
|
|
44
43
|
...(locale ? { locale } : {}),
|
|
45
44
|
...(where ? { where } : {}),
|
|
46
45
|
})
|
|
@@ -9,9 +9,9 @@ import {
|
|
|
9
9
|
getCollectionVirtualFieldNames,
|
|
10
10
|
stripVirtualFields,
|
|
11
11
|
} from '../../../utils/getVirtualFieldNames.js'
|
|
12
|
-
import { localAPIDefaults } from '../../../utils/localAPIDefaults.js'
|
|
13
12
|
import { transformPointDataToPayload } from '../../../utils/transformPointDataToPayload.js'
|
|
14
13
|
import { validateCollectionData } from '../validateEntityData.js'
|
|
14
|
+
import { fileInputSchema, resolveFileInput } from './fileInput.js'
|
|
15
15
|
import { formatCollectionError } from './formatCollectionError.js'
|
|
16
16
|
|
|
17
17
|
const DEFAULT_DESCRIPTION =
|
|
@@ -29,7 +29,11 @@ export const createDocumentTool = defineCollectionTool({
|
|
|
29
29
|
},
|
|
30
30
|
description: DEFAULT_DESCRIPTION,
|
|
31
31
|
input: z.object({
|
|
32
|
-
data: z
|
|
32
|
+
data: z
|
|
33
|
+
.record(z.string(), z.unknown())
|
|
34
|
+
.describe(
|
|
35
|
+
'The document fields to create. Only include fields permitted by the schema returned by getCollectionSchema.',
|
|
36
|
+
),
|
|
33
37
|
depth: z
|
|
34
38
|
.number()
|
|
35
39
|
.int()
|
|
@@ -40,13 +44,16 @@ export const createDocumentTool = defineCollectionTool({
|
|
|
40
44
|
.default(0),
|
|
41
45
|
draft: z
|
|
42
46
|
.boolean()
|
|
43
|
-
.describe(
|
|
47
|
+
.describe(
|
|
48
|
+
'Only if getCollectionSchema includes _status; otherwise _status does not exist. true forces data._status to "draft"; with false, data._status controls draft or published.',
|
|
49
|
+
)
|
|
44
50
|
.optional()
|
|
45
51
|
.default(false),
|
|
46
52
|
fallbackLocale: z
|
|
47
53
|
.string()
|
|
48
54
|
.describe('Optional: fallback locale code to use when requested locale is not available')
|
|
49
55
|
.optional(),
|
|
56
|
+
file: fileInputSchema.optional(),
|
|
50
57
|
locale: z
|
|
51
58
|
.string()
|
|
52
59
|
.describe(
|
|
@@ -64,7 +71,7 @@ export const createDocumentTool = defineCollectionTool({
|
|
|
64
71
|
const payload = req.payload
|
|
65
72
|
const logger = getLogger({ payload })
|
|
66
73
|
|
|
67
|
-
const { data, depth, draft, fallbackLocale, locale, select } = input
|
|
74
|
+
const { data, depth, draft, fallbackLocale, file: fileInput, locale, select } = input
|
|
68
75
|
|
|
69
76
|
logger.info(
|
|
70
77
|
`Creating document in collection: ${collectionSlug}${locale ? ` with locale: ${locale}` : ''}`,
|
|
@@ -80,14 +87,16 @@ export const createDocumentTool = defineCollectionTool({
|
|
|
80
87
|
}
|
|
81
88
|
|
|
82
89
|
const parsedData = transformPointDataToPayload(inputData)
|
|
90
|
+
const file = await resolveFileInput({ collectionSlug, input: fileInput, req })
|
|
83
91
|
|
|
84
92
|
const result = await payload.create({
|
|
85
93
|
collection: collectionSlug,
|
|
86
94
|
data: parsedData,
|
|
87
95
|
depth,
|
|
88
96
|
draft,
|
|
97
|
+
overrideAccess: authorizedMCP.overrideAccess,
|
|
89
98
|
req,
|
|
90
|
-
...
|
|
99
|
+
...(file ? { file } : {}),
|
|
91
100
|
...(locale ? { locale } : {}),
|
|
92
101
|
...(fallbackLocale ? { fallbackLocale } : {}),
|
|
93
102
|
...(select ? { select: select as SelectType } : {}),
|
|
@@ -3,7 +3,6 @@ import { z } from 'zod'
|
|
|
3
3
|
import { defaultAccess } from '../../../defaultAccess.js'
|
|
4
4
|
import { defineCollectionTool } from '../../../defineTool.js'
|
|
5
5
|
import { getLogger } from '../../../utils/getLogger.js'
|
|
6
|
-
import { localAPIDefaults } from '../../../utils/localAPIDefaults.js'
|
|
7
6
|
import { whereSchema } from '../../../utils/whereSchema.js'
|
|
8
7
|
|
|
9
8
|
const DEFAULT_DESCRIPTION =
|
|
@@ -69,8 +68,8 @@ export const deleteDocumentsTool = defineCollectionTool({
|
|
|
69
68
|
const deleteOptions: Record<string, unknown> = {
|
|
70
69
|
collection: collectionSlug,
|
|
71
70
|
depth,
|
|
71
|
+
overrideAccess: authorizedMCP.overrideAccess,
|
|
72
72
|
req,
|
|
73
|
-
...localAPIDefaults(authorizedMCP),
|
|
74
73
|
...(locale && { locale }),
|
|
75
74
|
...(fallbackLocale && { fallbackLocale }),
|
|
76
75
|
}
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
getCollectionVirtualFieldNames,
|
|
10
10
|
stripVirtualFields,
|
|
11
11
|
} from '../../../utils/getVirtualFieldNames.js'
|
|
12
|
-
import { localAPIDefaults } from '../../../utils/localAPIDefaults.js'
|
|
13
12
|
import { transformPointDataToPayload } from '../../../utils/transformPointDataToPayload.js'
|
|
14
13
|
import { formatCollectionError } from './formatCollectionError.js'
|
|
15
14
|
|
|
@@ -105,8 +104,8 @@ export const duplicateDocumentTool = defineCollectionTool({
|
|
|
105
104
|
collection: collectionSlug,
|
|
106
105
|
depth,
|
|
107
106
|
draft,
|
|
107
|
+
overrideAccess: authorizedMCP.overrideAccess,
|
|
108
108
|
req,
|
|
109
|
-
...localAPIDefaults(authorizedMCP),
|
|
110
109
|
...(parsedData ? { data: parsedData } : {}),
|
|
111
110
|
...(locale ? { locale } : {}),
|
|
112
111
|
...(fallbackLocale ? { fallbackLocale } : {}),
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { PayloadRequest } from 'payload'
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from 'vitest'
|
|
4
|
+
|
|
5
|
+
import { fileInputSchema, resolveFileInput } from './fileInput.js'
|
|
6
|
+
|
|
7
|
+
describe('MCP file input', () => {
|
|
8
|
+
it('should reject malformed MIME types', () => {
|
|
9
|
+
const result = fileInputSchema.safeParse({
|
|
10
|
+
data: 'aGVsbG8=',
|
|
11
|
+
mimeType: 'text/plain\r\nx-test: value',
|
|
12
|
+
name: 'hello.txt',
|
|
13
|
+
source: 'base64',
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
expect(result.success).toBe(false)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should reject oversized base64 files', async () => {
|
|
20
|
+
const req = createRequest({ maxFileSize: 4 })
|
|
21
|
+
|
|
22
|
+
await expect(
|
|
23
|
+
resolveFileInput({
|
|
24
|
+
collectionSlug: 'media',
|
|
25
|
+
input: {
|
|
26
|
+
data: Buffer.from('hello').toString('base64'),
|
|
27
|
+
mimeType: 'text/plain',
|
|
28
|
+
name: 'hello.txt',
|
|
29
|
+
source: 'base64',
|
|
30
|
+
},
|
|
31
|
+
req,
|
|
32
|
+
}),
|
|
33
|
+
).rejects.toThrow('File exceeds the 4 byte upload limit.')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('should reject URLs outside the collection allowlist', async () => {
|
|
37
|
+
const req = createRequest({
|
|
38
|
+
allowList: [{ hostname: 'assets.example.com', protocol: 'https' }],
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
await expect(
|
|
42
|
+
resolveFileInput({
|
|
43
|
+
collectionSlug: 'media',
|
|
44
|
+
input: {
|
|
45
|
+
source: 'url',
|
|
46
|
+
url: 'https://example.com/image.png',
|
|
47
|
+
},
|
|
48
|
+
req,
|
|
49
|
+
}),
|
|
50
|
+
).rejects.toThrow('The provided file URL is not allowed.')
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
function createRequest({
|
|
55
|
+
allowList,
|
|
56
|
+
maxFileSize,
|
|
57
|
+
}: {
|
|
58
|
+
allowList?: Array<{ hostname: string; protocol: 'http' | 'https' }>
|
|
59
|
+
maxFileSize?: number
|
|
60
|
+
}): PayloadRequest {
|
|
61
|
+
return {
|
|
62
|
+
payload: {
|
|
63
|
+
collections: {
|
|
64
|
+
media: {
|
|
65
|
+
config: {
|
|
66
|
+
upload: allowList ? { pasteURL: { allowList } } : {},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
},
|
|
70
|
+
config: {
|
|
71
|
+
upload: {
|
|
72
|
+
limits: {
|
|
73
|
+
fileSize: maxFileSize,
|
|
74
|
+
},
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
} as unknown as PayloadRequest
|
|
79
|
+
}
|