@payloadcms/plugin-mcp 4.0.0-internal.4cbdd50 → 4.0.0-internal.543da65

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/LICENSE.md +1 -1
  2. package/dist/endpoint/access.d.ts +2 -7
  3. package/dist/endpoint/access.d.ts.map +1 -1
  4. package/dist/endpoint/access.js +4 -11
  5. package/dist/endpoint/access.js.map +1 -1
  6. package/dist/endpoint/index.d.ts +1 -1
  7. package/dist/endpoint/index.d.ts.map +1 -1
  8. package/dist/endpoint/index.js +57 -17
  9. package/dist/endpoint/index.js.map +1 -1
  10. package/dist/index.d.ts +2 -2
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +2 -2
  13. package/dist/index.js.map +1 -1
  14. package/dist/mcp/buildMcpServer.d.ts +4 -4
  15. package/dist/mcp/buildMcpServer.d.ts.map +1 -1
  16. package/dist/mcp/buildMcpServer.js +32 -10
  17. package/dist/mcp/buildMcpServer.js.map +1 -1
  18. package/dist/mcp/builtin/collections/countTool.d.ts.map +1 -1
  19. package/dist/mcp/builtin/collections/countTool.js +1 -2
  20. package/dist/mcp/builtin/collections/countTool.js.map +1 -1
  21. package/dist/mcp/builtin/collections/countVersionsTool.d.ts.map +1 -1
  22. package/dist/mcp/builtin/collections/countVersionsTool.js +1 -2
  23. package/dist/mcp/builtin/collections/countVersionsTool.js.map +1 -1
  24. package/dist/mcp/builtin/collections/createTool.d.ts.map +1 -1
  25. package/dist/mcp/builtin/collections/createTool.js +1 -2
  26. package/dist/mcp/builtin/collections/createTool.js.map +1 -1
  27. package/dist/mcp/builtin/collections/deleteTool.d.ts.map +1 -1
  28. package/dist/mcp/builtin/collections/deleteTool.js +1 -2
  29. package/dist/mcp/builtin/collections/deleteTool.js.map +1 -1
  30. package/dist/mcp/builtin/collections/duplicateTool.d.ts.map +1 -1
  31. package/dist/mcp/builtin/collections/duplicateTool.js +1 -2
  32. package/dist/mcp/builtin/collections/duplicateTool.js.map +1 -1
  33. package/dist/mcp/builtin/collections/findDistinctTool.d.ts.map +1 -1
  34. package/dist/mcp/builtin/collections/findDistinctTool.js +1 -2
  35. package/dist/mcp/builtin/collections/findDistinctTool.js.map +1 -1
  36. package/dist/mcp/builtin/collections/findTool.d.ts.map +1 -1
  37. package/dist/mcp/builtin/collections/findTool.js +2 -3
  38. package/dist/mcp/builtin/collections/findTool.js.map +1 -1
  39. package/dist/mcp/builtin/collections/findVersionByIDTool.d.ts.map +1 -1
  40. package/dist/mcp/builtin/collections/findVersionByIDTool.js +1 -2
  41. package/dist/mcp/builtin/collections/findVersionByIDTool.js.map +1 -1
  42. package/dist/mcp/builtin/collections/findVersionsTool.d.ts.map +1 -1
  43. package/dist/mcp/builtin/collections/findVersionsTool.js +1 -2
  44. package/dist/mcp/builtin/collections/findVersionsTool.js.map +1 -1
  45. package/dist/mcp/builtin/collections/getCollectionSchemaTool.d.ts.map +1 -1
  46. package/dist/mcp/builtin/collections/getCollectionSchemaTool.js +21 -3
  47. package/dist/mcp/builtin/collections/getCollectionSchemaTool.js.map +1 -1
  48. package/dist/mcp/builtin/collections/restoreVersionTool.d.ts.map +1 -1
  49. package/dist/mcp/builtin/collections/restoreVersionTool.js +1 -2
  50. package/dist/mcp/builtin/collections/restoreVersionTool.js.map +1 -1
  51. package/dist/mcp/builtin/collections/updateTool.d.ts.map +1 -1
  52. package/dist/mcp/builtin/collections/updateTool.js +2 -3
  53. package/dist/mcp/builtin/collections/updateTool.js.map +1 -1
  54. package/dist/mcp/builtin/getConfigInfoTool.d.ts.map +1 -1
  55. package/dist/mcp/builtin/getConfigInfoTool.js +2 -5
  56. package/dist/mcp/builtin/getConfigInfoTool.js.map +1 -1
  57. package/dist/mcp/builtin/globals/countVersionsTool.d.ts.map +1 -1
  58. package/dist/mcp/builtin/globals/countVersionsTool.js +1 -2
  59. package/dist/mcp/builtin/globals/countVersionsTool.js.map +1 -1
  60. package/dist/mcp/builtin/globals/findTool.d.ts.map +1 -1
  61. package/dist/mcp/builtin/globals/findTool.js +2 -2
  62. package/dist/mcp/builtin/globals/findTool.js.map +1 -1
  63. package/dist/mcp/builtin/globals/findVersionByIDTool.d.ts.map +1 -1
  64. package/dist/mcp/builtin/globals/findVersionByIDTool.js +1 -2
  65. package/dist/mcp/builtin/globals/findVersionByIDTool.js.map +1 -1
  66. package/dist/mcp/builtin/globals/findVersionsTool.d.ts.map +1 -1
  67. package/dist/mcp/builtin/globals/findVersionsTool.js +1 -2
  68. package/dist/mcp/builtin/globals/findVersionsTool.js.map +1 -1
  69. package/dist/mcp/builtin/globals/getGlobalSchemaTool.d.ts.map +1 -1
  70. package/dist/mcp/builtin/globals/getGlobalSchemaTool.js +21 -3
  71. package/dist/mcp/builtin/globals/getGlobalSchemaTool.js.map +1 -1
  72. package/dist/mcp/builtin/globals/restoreVersionTool.d.ts.map +1 -1
  73. package/dist/mcp/builtin/globals/restoreVersionTool.js +1 -2
  74. package/dist/mcp/builtin/globals/restoreVersionTool.js.map +1 -1
  75. package/dist/mcp/builtin/globals/updateTool.d.ts.map +1 -1
  76. package/dist/mcp/builtin/globals/updateTool.js +2 -2
  77. package/dist/mcp/builtin/globals/updateTool.js.map +1 -1
  78. package/dist/mcp/builtin/validateEntityData.js +1 -1
  79. package/dist/mcp/builtin/validateEntityData.js.map +1 -1
  80. package/dist/mcp/sanitizeMCPConfig.d.ts.map +1 -1
  81. package/dist/mcp/sanitizeMCPConfig.js +1 -0
  82. package/dist/mcp/sanitizeMCPConfig.js.map +1 -1
  83. package/dist/stdio.d.ts +3 -4
  84. package/dist/stdio.d.ts.map +1 -1
  85. package/dist/stdio.js +59 -41
  86. package/dist/stdio.js.map +1 -1
  87. package/dist/types.d.ts +57 -9
  88. package/dist/types.d.ts.map +1 -1
  89. package/dist/types.js +2 -4
  90. package/dist/types.js.map +1 -1
  91. package/dist/utils/schemaConversion/filterFieldsByAccess.d.ts +28 -0
  92. package/dist/utils/schemaConversion/filterFieldsByAccess.d.ts.map +1 -0
  93. package/dist/utils/schemaConversion/filterFieldsByAccess.js +71 -0
  94. package/dist/utils/schemaConversion/filterFieldsByAccess.js.map +1 -0
  95. package/dist/utils/schemaConversion/getEntityInputSchema.d.ts +5 -3
  96. package/dist/utils/schemaConversion/getEntityInputSchema.d.ts.map +1 -1
  97. package/dist/utils/schemaConversion/getEntityInputSchema.js +44 -12
  98. package/dist/utils/schemaConversion/getEntityInputSchema.js.map +1 -1
  99. package/dist/utils/schemaConversion/sanitizeEntitySchema.d.ts +5 -10
  100. package/dist/utils/schemaConversion/sanitizeEntitySchema.d.ts.map +1 -1
  101. package/dist/utils/schemaConversion/sanitizeEntitySchema.js +15 -95
  102. package/dist/utils/schemaConversion/sanitizeEntitySchema.js.map +1 -1
  103. package/dist/utils/schemaConversion/sanitizeEntitySchema.spec.js +15 -40
  104. package/dist/utils/schemaConversion/sanitizeEntitySchema.spec.js.map +1 -1
  105. package/package.json +8 -10
  106. package/src/endpoint/access.ts +3 -10
  107. package/src/endpoint/index.ts +45 -16
  108. package/src/index.ts +4 -4
  109. package/src/mcp/buildMcpServer.ts +40 -20
  110. package/src/mcp/builtin/collections/countTool.ts +1 -2
  111. package/src/mcp/builtin/collections/countVersionsTool.ts +1 -2
  112. package/src/mcp/builtin/collections/createTool.ts +1 -2
  113. package/src/mcp/builtin/collections/deleteTool.ts +1 -2
  114. package/src/mcp/builtin/collections/duplicateTool.ts +1 -2
  115. package/src/mcp/builtin/collections/findDistinctTool.ts +1 -2
  116. package/src/mcp/builtin/collections/findTool.ts +2 -3
  117. package/src/mcp/builtin/collections/findVersionByIDTool.ts +1 -2
  118. package/src/mcp/builtin/collections/findVersionsTool.ts +1 -2
  119. package/src/mcp/builtin/collections/getCollectionSchemaTool.ts +25 -8
  120. package/src/mcp/builtin/collections/restoreVersionTool.ts +1 -2
  121. package/src/mcp/builtin/collections/updateTool.ts +2 -3
  122. package/src/mcp/builtin/getConfigInfoTool.ts +2 -4
  123. package/src/mcp/builtin/globals/countVersionsTool.ts +1 -2
  124. package/src/mcp/builtin/globals/findTool.ts +2 -2
  125. package/src/mcp/builtin/globals/findVersionByIDTool.ts +1 -2
  126. package/src/mcp/builtin/globals/findVersionsTool.ts +1 -2
  127. package/src/mcp/builtin/globals/getGlobalSchemaTool.ts +25 -3
  128. package/src/mcp/builtin/globals/restoreVersionTool.ts +1 -2
  129. package/src/mcp/builtin/globals/updateTool.ts +2 -2
  130. package/src/mcp/builtin/validateEntityData.ts +1 -1
  131. package/src/mcp/sanitizeMCPConfig.ts +1 -0
  132. package/src/stdio.ts +59 -42
  133. package/src/types.ts +57 -9
  134. package/src/utils/schemaConversion/filterFieldsByAccess.ts +123 -0
  135. package/src/utils/schemaConversion/getEntityInputSchema.ts +45 -27
  136. package/src/utils/schemaConversion/sanitizeEntitySchema.spec.ts +12 -19
  137. package/src/utils/schemaConversion/sanitizeEntitySchema.ts +17 -114
  138. package/dist/@types/assets.d.js +0 -2
  139. package/dist/@types/assets.d.js.map +0 -1
  140. package/dist/utils/localAPIDefaults.d.ts +0 -20
  141. package/dist/utils/localAPIDefaults.d.ts.map +0 -1
  142. package/dist/utils/localAPIDefaults.js +0 -19
  143. package/dist/utils/localAPIDefaults.js.map +0 -1
  144. package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.d.ts +0 -7
  145. package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.d.ts.map +0 -1
  146. package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.js +0 -20
  147. package/dist/utils/schemaConversion/removeVirtualFieldsFromSchema.js.map +0 -1
  148. package/src/@types/assets.d.ts +0 -3
  149. package/src/utils/localAPIDefaults.ts +0 -22
  150. package/src/utils/schemaConversion/removeVirtualFieldsFromSchema.ts +0 -27
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../src/utils/schemaConversion/sanitizeEntitySchema.ts"],"sourcesContent":["import type { JsonSchemaType } from '../../types.js'\n\n/**\n * Turns the JSON Schema that Payload generates for a collection or global into the input schema for\n * an MCP create/update tool. In short, it:\n *\n * - drops fields a client can't set (`id`, `createdAt`, the draft `_status`, …),\n * - rewrites Payload-specific field shapes (points, relationships) into plain JSON the model can fill,\n * - and shrinks the result so it's cheaper for the model to read,\n *\n * while keeping every node valid JSON Schema draft 2020-12. Each step below is tagged with why it runs -\n * **Correctness** (valid input the API accepts), **Size** (equivalence-preserving shrink), or **LLM\n * ergonomics** (easier for the model to read/fill) - and carries a before/after example on its definition.\n */\nexport const sanitizeEntitySchema = (schema: JsonSchemaType): JsonSchemaType => {\n // Work on a copy — the caller reuses the original schema elsewhere (e.g. when listing tools).\n let result = structuredClone(schema)\n\n // Correctness — drop the fields a client can't set (id, createdAt, updatedAt, draft _status) + collapse nullable types.\n result = removeManagedFields(result)\n\n // LLM ergonomics — rewrite point fields from a `[number, number]` tuple into a `{ longitude, latitude }` object.\n result = pointFieldsToObjects(result)\n\n // Correctness — a relationship value can be an ID or a populated doc; on input only the ID is valid, so keep that.\n result = relationshipsToIds(result)\n\n // Size — strip inert type-gen leftovers that only bloat the schema (`tsType`, block-collision notes).\n result = removeTypeGenArtifacts(result)\n\n // Size — where a `const` already pins a value, the sibling `type` is redundant; remove it.\n result = dropRedundantConstType(result)\n\n // Size — fold per-collection relationship/upload variants (identical but for `relationTo`) into one `enum`.\n result = mergeConstDiscriminatedUnions(result)\n\n // Size — pull any subschema that appears more than once into a single shared `$defs` entry.\n result = deduplicateIntoDefinitions(result)\n\n // LLM ergonomics — give `$defs` short, readable names (`Code`, `paragraph`, `node`) so the `$ref`s read nicely.\n result = shortenDefinitionNames(result)\n\n return result\n}\n\n/**\n * Rebuilds a schema bottom-up, calling `visit` on each node after its children. Shared by the\n * transforms that need to touch every node in the tree.\n */\nconst mapNodes = (node: unknown, visit: (node: JsonSchemaType) => JsonSchemaType): unknown => {\n if (Array.isArray(node)) {\n return node.map((child) => mapNodes(child, visit))\n }\n if (!node || typeof node !== 'object') {\n return node\n }\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(node)) {\n out[key] = mapNodes(value, visit)\n }\n return visit(out as JsonSchemaType)\n}\n\n// Payload sets these on every document, so an MCP client never provides them when creating/updating.\nconst PAYLOAD_MANAGED_FIELDS = new Set(['_status', 'createdAt', 'id', 'updatedAt'])\n\n/**\n * **Correctness.** Removes the fields a client can't set on create/update - `id`, `createdAt`, `updatedAt`, and the draft\n * `_status` - from every field object's `properties` and `required` (recursing into nested objects and\n * array items). Along the way it also collapses optional array/object types via {@link collapseNullableType}.\n *\n * @example\n * { properties: { id: { type: 'string' }, tags: { type: ['array', 'null'], items: {} } }, required: ['id', 'tags'] }\n * → { properties: { tags: { type: 'array', items: {} } }, required: ['tags'] }\n */\nconst removeManagedFields = (schema: JsonSchemaType): JsonSchemaType => {\n if (schema.properties && typeof schema.properties === 'object') {\n for (const field of PAYLOAD_MANAGED_FIELDS) {\n delete schema.properties[field]\n }\n for (const key of Object.keys(schema.properties)) {\n const prop = schema.properties[key] as JsonSchemaType\n if (!prop || typeof prop !== 'object') {\n continue\n }\n const isRequired = Array.isArray(schema.required) && schema.required.includes(key)\n collapseNullableType(prop, isRequired)\n if (prop.properties) {\n removeManagedFields(prop)\n }\n if (prop.items && typeof prop.items === 'object' && !Array.isArray(prop.items)) {\n removeManagedFields(prop.items)\n }\n }\n }\n\n if (Array.isArray(schema.required)) {\n schema.required = schema.required.filter((name) => !PAYLOAD_MANAGED_FIELDS.has(name))\n if (schema.required.length === 0) {\n delete schema.required\n }\n }\n\n return schema\n}\n\n/**\n * Drops the `'null'` from an optional array/object type (`['array', 'null']` → `'array'`, likewise\n * `'object'`). Payload marks a field optional by unioning its type with `'null'`, but a create/update\n * tool already conveys \"optional\" through the `required` list - the client just omits the field - so the\n * `'null'` is redundant here. Dropping it shows the model a plain `type: 'array'` instead of implying it\n * should send a literal `null`.\n *\n * We never touch the `required` list, so this can't change whether a field is required. And we skip fields\n * that ARE required (`isRequired`): there a `'null'` is a value the field genuinely accepts, not optionality\n * encoding. Nullable scalars (`['string', 'null']`) are also left alone - they read fine as `string | null`.\n *\n * @example\n * optional { type: ['array', 'null'] } → { type: 'array' } (still optional, just no longer null-valued)\n * required { type: ['array', 'null'] } → unchanged\n * { type: ['string', 'null'] } → unchanged\n */\nconst collapseNullableType = (schema: JsonSchemaType, isRequired: boolean): void => {\n if (isRequired || !Array.isArray(schema.type)) {\n return\n }\n const nonNullTypes = schema.type.filter((t) => t !== 'null')\n if (nonNullTypes.length === 1 && (nonNullTypes[0] === 'array' || nonNullTypes[0] === 'object')) {\n schema.type = nonNullTypes[0]\n }\n}\n\n/**\n * **LLM ergonomics.** Rewrites a point field (stored as a two-number tuple) into a `{ longitude, latitude }` object, which\n * is far easier for the model to fill in than a positional array. This is input-only and lossless: the\n * create/update handler converts the object back to the `[longitude, latitude]` tuple Payload stores,\n * via `transformPointDataToPayload`.\n *\n * @example\n * { type: 'array', items: [{ type: 'number' }, { type: 'number' }] }\n * → { type: 'object', properties: { longitude: { type: 'number' }, latitude: { type: 'number' } }, required: ['longitude', 'latitude'] }\n */\nconst pointFieldsToObjects = (schema: JsonSchemaType): JsonSchemaType => {\n if (!schema || typeof schema !== 'object') {\n return schema\n }\n\n const transformed = { ...schema }\n\n if (transformed.properties && typeof transformed.properties === 'object') {\n transformed.properties = Object.fromEntries(\n Object.entries(transformed.properties).map(([key, value]) => {\n if (!value || typeof value !== 'object') {\n return [key, value]\n }\n const isArrayType =\n value.type === 'array' || (Array.isArray(value.type) && value.type.includes('array'))\n const isPointField =\n isArrayType &&\n Array.isArray(value.items) &&\n value.items.length === 2 &&\n value.items.every((item) => item && typeof item === 'object' && item.type === 'number')\n\n if (isPointField) {\n const isNullable = Array.isArray(value.type) && value.type.includes('null')\n return [\n key,\n {\n type: isNullable ? ['object', 'null'] : 'object',\n description: value.description || 'Geographic coordinates (longitude, latitude)',\n properties: {\n latitude: { type: 'number', description: 'Latitude coordinate' },\n longitude: { type: 'number', description: 'Longitude coordinate' },\n },\n required: ['longitude', 'latitude'],\n },\n ]\n }\n\n return [key, pointFieldsToObjects(value)]\n }),\n )\n }\n\n if (\n transformed.items &&\n typeof transformed.items === 'object' &&\n !Array.isArray(transformed.items)\n ) {\n transformed.items = pointFieldsToObjects(transformed.items)\n }\n\n return transformed\n}\n\n/**\n * **Correctness.** Reduces relationship/upload fields to the IDs a client actually sends. Payload types the value as\n * \"an ID or the full related document\" - but the populated-document form only appears in read responses;\n * on create/update you always reference a relationship by its ID. So we drop that `$ref` option, leaving\n * the bare ID. A single remaining target collapses inline (with a description naming the target\n * collection); several targets become an `anyOf` of IDs.\n *\n * @example\n * { oneOf: [{ type: 'string' }, { $ref: '#/$defs/posts' }] }\n * → { type: 'string', description: 'The ID of the related \"posts\" document.' }\n */\nconst relationshipsToIds = (schema: JsonSchemaType): JsonSchemaType => {\n if (!schema || typeof schema !== 'object') {\n return schema\n }\n\n const processed = { ...schema }\n\n if (Array.isArray(processed.oneOf)) {\n const isRelatedDocRef = (option: unknown): option is { $ref: string } =>\n !!option && typeof option === 'object' && '$ref' in option\n\n if (processed.oneOf.some(isRelatedDocRef)) {\n // A relationship value is \"an ID, or the populated related document\". Keep the ID option(s) and\n // drop the `$ref`s to the related collections, since a client only ever sends the ID.\n const idOptions = processed.oneOf\n .filter((option) => !isRelatedDocRef(option))\n .map((option) => (typeof option === 'object' ? relationshipsToIds(option) : option))\n const targetCollections = processed.oneOf\n .filter(isRelatedDocRef)\n .map((option) => option.$ref.replace('#/$defs/', ''))\n\n if (idOptions.length === 1) {\n delete processed.oneOf\n Object.assign(processed, idOptions[0])\n if (targetCollections.length > 0 && !processed.description) {\n processed.description = `The ID of the related \"${targetCollections.join('\" or \"')}\" document.`\n }\n } else if (idOptions.length > 1) {\n delete processed.oneOf\n processed.anyOf = idOptions\n }\n } else {\n processed.oneOf = processed.oneOf.map((option) =>\n typeof option === 'object' ? relationshipsToIds(option) : option,\n )\n }\n }\n\n if (processed.properties && typeof processed.properties === 'object') {\n processed.properties = Object.fromEntries(\n Object.entries(processed.properties).map(([key, value]) => [\n key,\n typeof value === 'object' ? relationshipsToIds(value) : value,\n ]),\n )\n }\n\n if (processed.items && typeof processed.items === 'object' && !Array.isArray(processed.items)) {\n processed.items = relationshipsToIds(processed.items)\n }\n\n // Lexical node unions and blocks live under `$defs` and have their own relationship fields, so walk\n // those too — otherwise their `$ref`s would dangle once we don't bundle the related collections.\n if (processed.$defs && typeof processed.$defs === 'object') {\n processed.$defs = Object.fromEntries(\n Object.entries(processed.$defs).map(([key, value]) => [\n key,\n typeof value === 'object' ? relationshipsToIds(value) : value,\n ]),\n )\n }\n\n return processed\n}\n\n/**\n * **Size.** Strips type-generation leftovers that bloat the schema without helping the model: the `tsType`\n * hint (a `json-schema-to-typescript` extension; JSON Schema validators just ignore it, they don't reject it)\n * and the block-interface-collision note Payload adds to some block descriptions (the\n * `block-interface-name-collisions` docs link set in `registerBlockInterface`, see configToJSONSchema.ts).\n * Both are inert here - removing them only shrinks the schema (and spares the model irrelevant noise).\n *\n * @example\n * { type: 'object', tsType: 'SerializedBlockNode', description: 'see …#block-interface-name-collisions' }\n * → { type: 'object' }\n */\nconst removeTypeGenArtifacts = (schema: JsonSchemaType): JsonSchemaType =>\n mapNodes(schema, (node) => {\n delete (node as { tsType?: unknown }).tsType\n const { description } = node as { description?: string }\n if (\n typeof description === 'string' &&\n description.includes('block-interface-name-collisions')\n ) {\n delete (node as { description?: unknown }).description\n }\n return node\n }) as JsonSchemaType\n\n/**\n * **Size.** Removes `type` whenever a `const` sits next to it — the constant already fixes the value.\n *\n * @example\n * { type: 'string', const: 'paragraph' } → { const: 'paragraph' }\n */\nconst dropRedundantConstType = (schema: JsonSchemaType): JsonSchemaType =>\n mapNodes(schema, (node) => {\n if ('const' in node && 'type' in node) {\n delete (node as { type?: unknown }).type\n }\n return node\n }) as JsonSchemaType\n\n/**\n * **Size.** Merges the members of a `oneOf`/`anyOf` that are identical except for one `const`-valued property\n * into a single member with that property as an `enum`. Since the members differ only by that one\n * constant, the `enum` form accepts exactly the same values - it's just smaller. This folds the\n * per-collection relationship and upload variants, which differ only in their `relationTo` constant.\n *\n * @example\n * { oneOf: [\n * { properties: { relationTo: { const: 'posts' }, value: { type: 'string' } } },\n * { properties: { relationTo: { const: 'pages' }, value: { type: 'string' } } },\n * ] }\n * → { properties: { relationTo: { enum: ['posts', 'pages'] }, value: { type: 'string' } } }\n */\nconst mergeConstDiscriminatedUnions = (schema: JsonSchemaType): JsonSchemaType =>\n mapNodes(schema, (node) => {\n for (const keyword of ['oneOf', 'anyOf'] as const) {\n const members = node[keyword]\n if (Array.isArray(members) && members.length > 1) {\n const merged = mergeMembersByConst(members)\n if (merged) {\n delete node[keyword]\n Object.assign(node, merged)\n }\n }\n }\n return node\n }) as JsonSchemaType\n\n/** Returns the merged member for {@link mergeConstDiscriminatedUnions}, or `null` if they can't merge. */\nconst mergeMembersByConst = (members: Array<boolean | JsonSchemaType>): JsonSchemaType | null => {\n const objects = members.filter(\n (member): member is JsonSchemaType =>\n typeof member === 'object' && member !== null && typeof member.properties === 'object',\n )\n if (objects.length < 2 || objects.length !== members.length) {\n return null\n }\n\n for (const discriminator of Object.keys(objects[0]!.properties!)) {\n // Each member must pin this property to a `const`...\n const constValues: unknown[] = []\n const everyMemberPinsConst = objects.every((member) => {\n const prop = member.properties![discriminator]\n if (prop && typeof prop === 'object' && 'const' in prop) {\n constValues.push(prop.const)\n return true\n }\n return false\n })\n if (!everyMemberPinsConst) {\n continue\n }\n\n // ...and be otherwise identical (compare each member with the discriminator removed).\n const fingerprintWithoutDiscriminator = (member: JsonSchemaType): string => {\n const { [discriminator]: _discriminator, ...otherProperties } = member.properties!\n return JSON.stringify({ ...member, properties: otherProperties })\n }\n if (new Set(objects.map(fingerprintWithoutDiscriminator)).size !== 1) {\n continue\n }\n\n const uniqueConstValues = [...new Set(constValues)]\n if (uniqueConstValues.length < 2) {\n continue\n }\n\n // Replace the per-member `const` with a single `enum` of every value.\n const merged = structuredClone(objects[0]!)\n const discriminatorProp = merged.properties![discriminator]\n if (discriminatorProp && typeof discriminatorProp === 'object') {\n delete discriminatorProp.const\n discriminatorProp.enum = uniqueConstValues as NonNullable<typeof discriminatorProp.enum>\n }\n return merged\n }\n return null\n}\n\n// A `$ref` costs ~30 characters once names are shortened, so sharing a subschema only pays off when\n// it's larger than that and appears more than once.\nconst MIN_SHARED_SIZE = 64\n\n/** A subschema worth sharing: a standalone object/union/described schema, not a `$ref` or a primitive. */\nconst isShareable = (node: unknown): node is Record<string, unknown> => {\n if (!node || typeof node !== 'object' || Array.isArray(node) || '$ref' in node) {\n return false\n }\n const schema = node as JsonSchemaType\n return (\n Array.isArray(schema.oneOf) ||\n Array.isArray(schema.anyOf) ||\n Boolean(schema.properties) ||\n Boolean(schema.items) ||\n typeof schema.description === 'string'\n )\n}\n\n/**\n * **Size.** Replaces any subschema that appears more than once with a single shared `$defs` entry referenced by\n * `$ref`. Lossless — only the serialized size shrinks. A collection with several rich-text fields, for\n * example, inlines the same large lexical node schema once per field; this collapses them into one.\n *\n * @example\n * { properties: { billing: address, shipping: address } }\n * → { properties: { billing: { $ref: '#/$defs/shared_0' }, shipping: { $ref: '#/$defs/shared_0' } }, $defs: { shared_0: address } }\n */\nconst deduplicateIntoDefinitions = (schema: JsonSchemaType): JsonSchemaType => {\n // Count how often each shareable subschema appears, keyed by its serialized form.\n const counts = new Map<string, number>()\n const count = (node: unknown): void => {\n if (isShareable(node)) {\n const key = JSON.stringify(node)\n if (key.length >= MIN_SHARED_SIZE) {\n counts.set(key, (counts.get(key) ?? 0) + 1)\n }\n }\n if (Array.isArray(node)) {\n node.forEach(count)\n } else if (node && typeof node === 'object') {\n Object.values(node).forEach(count)\n }\n }\n count(schema)\n\n if (![...counts.values()].some((n) => n >= 2)) {\n return schema\n }\n\n // Replace each subschema seen 2+ times with a `$ref`. The first time we meet one it becomes a shared\n // entry; we never descend into a stored entry, so shared entries never reference one another.\n const sharedEntries: Record<string, JsonSchemaType> = {}\n const nameByKey = new Map<string, string>()\n const share = (node: unknown): unknown => {\n if (isShareable(node)) {\n const key = JSON.stringify(node)\n if ((counts.get(key) ?? 0) >= 2) {\n let name = nameByKey.get(key)\n if (!name) {\n name = `shared_${nameByKey.size}`\n nameByKey.set(key, name)\n sharedEntries[name] = node as JsonSchemaType\n }\n return { $ref: `#/$defs/${name}` }\n }\n }\n if (Array.isArray(node)) {\n return node.map(share)\n }\n if (node && typeof node === 'object') {\n return Object.fromEntries(\n Object.entries(node).map(([childKey, value]) => [childKey, share(value)]),\n )\n }\n return node\n }\n\n const result = share(schema) as JsonSchemaType\n result.$defs = { ...result.$defs, ...sharedEntries }\n return result\n}\n\n/**\n * **LLM ergonomics.** Renames `$defs` entries to short, readable names so the `$ref`s stay legible to the model: a block's\n * `blockType` (`Code`), a node's `type` (`paragraph`), `node` for a rich-text node union, otherwise the\n * generated name with its disambiguating hash dropped. A numeric suffix keeps collisions unique.\n *\n * @example\n * { properties: { body: { $ref: '#/$defs/LexicalNodes_9FBEC708' } }, $defs: { LexicalNodes_9FBEC708: {} } }\n * → { properties: { body: { $ref: '#/$defs/node' } }, $defs: { node: {} } }\n */\nconst shortenDefinitionNames = (schema: JsonSchemaType): JsonSchemaType => {\n const definitions = schema.$defs\n if (!definitions || Object.keys(definitions).length === 0) {\n return schema\n }\n\n const shortNameFor = (name: string, definition: boolean | JsonSchemaType): string => {\n const properties = (typeof definition === 'object' && definition.properties) || {}\n const constString = (key: string): string | undefined => {\n const prop = properties[key]\n return prop && typeof prop === 'object' && typeof prop.const === 'string'\n ? prop.const\n : undefined\n }\n const members =\n typeof definition === 'object' ? (definition.anyOf ?? definition.oneOf) : undefined\n const isNodeUnion =\n Array.isArray(members) &&\n members.some((m) => typeof m === 'object' && Boolean(m.$ref || m.properties?.type))\n return (\n constString('blockType') ??\n constString('type') ??\n (isNodeUnion ? 'node' : undefined) ??\n name.replace(/_[0-9A-F]{6,}$/i, '')\n )\n }\n\n const prefix = '#/$defs/'\n const usedNames = new Map<string, number>()\n const rename = new Map<string, string>()\n for (const [name, definition] of Object.entries(definitions)) {\n const shortName = shortNameFor(name, definition)\n const used = usedNames.get(shortName) ?? 0\n usedNames.set(shortName, used + 1)\n rename.set(name, used === 0 ? shortName : `${shortName}${used + 1}`)\n }\n\n const result = mapNodes(schema, (node) => {\n if (typeof node.$ref === 'string' && node.$ref.startsWith(prefix)) {\n const name = node.$ref.slice(prefix.length)\n node.$ref = `${prefix}${rename.get(name) ?? name}`\n }\n return node\n }) as JsonSchemaType\n result.$defs = Object.fromEntries(\n Object.entries(result.$defs!).map(([name, body]) => [rename.get(name) ?? name, body]),\n )\n return result\n}\n"],"names":["sanitizeEntitySchema","schema","result","structuredClone","removeManagedFields","pointFieldsToObjects","relationshipsToIds","removeTypeGenArtifacts","dropRedundantConstType","mergeConstDiscriminatedUnions","deduplicateIntoDefinitions","shortenDefinitionNames","mapNodes","node","visit","Array","isArray","map","child","out","key","value","Object","entries","PAYLOAD_MANAGED_FIELDS","Set","properties","field","keys","prop","isRequired","required","includes","collapseNullableType","items","filter","name","has","length","type","nonNullTypes","t","transformed","fromEntries","isArrayType","isPointField","every","item","isNullable","description","latitude","longitude","processed","oneOf","isRelatedDocRef","option","some","idOptions","targetCollections","$ref","replace","assign","join","anyOf","$defs","tsType","keyword","members","merged","mergeMembersByConst","objects","member","discriminator","constValues","everyMemberPinsConst","push","const","fingerprintWithoutDiscriminator","_discriminator","otherProperties","JSON","stringify","size","uniqueConstValues","discriminatorProp","enum","MIN_SHARED_SIZE","isShareable","Boolean","counts","Map","count","set","get","forEach","values","n","sharedEntries","nameByKey","share","childKey","definitions","shortNameFor","definition","constString","undefined","isNodeUnion","m","prefix","usedNames","rename","shortName","used","startsWith","slice","body"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../src/utils/schemaConversion/sanitizeEntitySchema.ts"],"sourcesContent":["import type { JsonSchemaType } from '../../types.js'\n\n/**\n * Refines Payload's `input`-variant JSON Schema into an MCP create/update tool's input schema. The\n * variant already covers correctness (ID-only relationships, no managed/virtual/join fields); the\n * passes here are MCP-specific - reshaping fields for the model and shrinking the result - and keep\n * every node valid JSON Schema draft 2020-12. Each is tagged **Correctness**, **Size**, or **LLM\n * ergonomics**, with a before/after example on its definition.\n */\nexport const sanitizeEntitySchema = (schema: JsonSchemaType): JsonSchemaType => {\n // Work on a copy — the caller reuses the original schema elsewhere (e.g. when listing tools).\n let result = structuredClone(schema)\n\n // LLM ergonomics — drop the redundant `'null'` from optional arrays/objects (`['array','null']` → `'array'`).\n result = collapseOptionalContainerTypes(result)\n\n // LLM ergonomics — rewrite point fields from a `[number, number]` tuple into a `{ longitude, latitude }` object.\n result = pointFieldsToObjects(result)\n\n // Size — strip inert type-gen leftovers that only bloat the schema (`tsType`, block-collision notes).\n result = removeTypeGenArtifacts(result)\n\n // Size — where a `const` already pins a value, the sibling `type` is redundant; remove it.\n result = dropRedundantConstType(result)\n\n // Size — fold per-collection relationship/upload variants (identical but for `relationTo`) into one `enum`.\n result = mergeConstDiscriminatedUnions(result)\n\n // Size — pull any subschema that appears more than once into a single shared `$defs` entry.\n result = deduplicateIntoDefinitions(result)\n\n // LLM ergonomics — give `$defs` short, readable names (`Code`, `paragraph`, `node`) so the `$ref`s read nicely.\n result = shortenDefinitionNames(result)\n\n return result\n}\n\n/**\n * Rebuilds a schema bottom-up, calling `visit` on each node after its children. Shared by the\n * transforms that need to touch every node in the tree.\n */\nconst mapNodes = (node: unknown, visit: (node: JsonSchemaType) => JsonSchemaType): unknown => {\n if (Array.isArray(node)) {\n return node.map((child) => mapNodes(child, visit))\n }\n if (!node || typeof node !== 'object') {\n return node\n }\n const out: Record<string, unknown> = {}\n for (const [key, value] of Object.entries(node)) {\n out[key] = mapNodes(value, visit)\n }\n return visit(out)\n}\n\n/**\n * **LLM ergonomics.** Collapses optional array/object types (`['array', 'null']` → `'array'`) via\n * {@link collapseNullableType}, recursing through `properties` and array items. The `required` list\n * already conveys optionality, so the `'null'` is redundant noise for the model.\n *\n * @example\n * { properties: { tags: { type: ['array', 'null'], items: {} } }, required: [] }\n * → { properties: { tags: { type: 'array', items: {} } }, required: [] }\n */\nconst collapseOptionalContainerTypes = (schema: JsonSchemaType): JsonSchemaType => {\n if (schema.properties && typeof schema.properties === 'object') {\n for (const key of Object.keys(schema.properties)) {\n const prop = schema.properties[key] as JsonSchemaType\n if (!prop || typeof prop !== 'object') {\n continue\n }\n const isRequired = Array.isArray(schema.required) && schema.required.includes(key)\n collapseNullableType(prop, isRequired)\n if (prop.properties) {\n collapseOptionalContainerTypes(prop)\n }\n if (prop.items && typeof prop.items === 'object' && !Array.isArray(prop.items)) {\n collapseOptionalContainerTypes(prop.items)\n }\n }\n }\n\n return schema\n}\n\n/**\n * Drops the `'null'` from an optional array/object type (`['array', 'null']` → `'array'`, likewise\n * `'object'`). Payload marks a field optional by unioning its type with `'null'`, but a create/update\n * tool already conveys \"optional\" through the `required` list - the client just omits the field - so the\n * `'null'` is redundant here. Dropping it shows the model a plain `type: 'array'` instead of implying it\n * should send a literal `null`.\n *\n * We never touch the `required` list, so this can't change whether a field is required. And we skip fields\n * that ARE required (`isRequired`): there a `'null'` is a value the field genuinely accepts, not optionality\n * encoding. Nullable scalars (`['string', 'null']`) are also left alone - they read fine as `string | null`.\n *\n * @example\n * optional { type: ['array', 'null'] } → { type: 'array' } (still optional, just no longer null-valued)\n * required { type: ['array', 'null'] } → unchanged\n * { type: ['string', 'null'] } → unchanged\n */\nconst collapseNullableType = (schema: JsonSchemaType, isRequired: boolean): void => {\n if (isRequired || !Array.isArray(schema.type)) {\n return\n }\n const nonNullTypes = schema.type.filter((t) => t !== 'null')\n if (nonNullTypes.length === 1 && (nonNullTypes[0] === 'array' || nonNullTypes[0] === 'object')) {\n schema.type = nonNullTypes[0]\n }\n}\n\n/**\n * **LLM ergonomics.** Rewrites a point field (stored as a two-number tuple) into a `{ longitude, latitude }` object, which\n * is far easier for the model to fill in than a positional array. This is input-only and lossless: the\n * create/update handler converts the object back to the `[longitude, latitude]` tuple Payload stores,\n * via `transformPointDataToPayload`.\n *\n * @example\n * { type: 'array', items: [{ type: 'number' }, { type: 'number' }] }\n * → { type: 'object', properties: { longitude: { type: 'number' }, latitude: { type: 'number' } }, required: ['longitude', 'latitude'] }\n */\nconst pointFieldsToObjects = (schema: JsonSchemaType): JsonSchemaType => {\n if (!schema || typeof schema !== 'object') {\n return schema\n }\n\n const transformed = { ...schema }\n\n if (transformed.properties && typeof transformed.properties === 'object') {\n transformed.properties = Object.fromEntries(\n Object.entries(transformed.properties).map(([key, value]) => {\n if (!value || typeof value !== 'object') {\n return [key, value]\n }\n const isArrayType =\n value.type === 'array' || (Array.isArray(value.type) && value.type.includes('array'))\n const isPointField =\n isArrayType &&\n Array.isArray(value.items) &&\n value.items.length === 2 &&\n value.items.every((item) => item && typeof item === 'object' && item.type === 'number')\n\n if (isPointField) {\n const isNullable = Array.isArray(value.type) && value.type.includes('null')\n return [\n key,\n {\n type: isNullable ? ['object', 'null'] : 'object',\n description: value.description || 'Geographic coordinates (longitude, latitude)',\n properties: {\n latitude: { type: 'number', description: 'Latitude coordinate' },\n longitude: { type: 'number', description: 'Longitude coordinate' },\n },\n required: ['longitude', 'latitude'],\n },\n ]\n }\n\n return [key, pointFieldsToObjects(value)]\n }),\n )\n }\n\n if (\n transformed.items &&\n typeof transformed.items === 'object' &&\n !Array.isArray(transformed.items)\n ) {\n transformed.items = pointFieldsToObjects(transformed.items)\n }\n\n return transformed\n}\n\n/**\n * **Size.** Strips type-generation leftovers that bloat the schema without helping the model: the `tsType`\n * hint (a `json-schema-to-typescript` extension; JSON Schema validators just ignore it, they don't reject it)\n * and the block-interface-collision note Payload adds to some block descriptions (the\n * `block-interface-name-collisions` docs link set in `registerBlockInterface`, see configToJSONSchema.ts).\n * Both are inert here - removing them only shrinks the schema (and spares the model irrelevant noise).\n *\n * @example\n * { type: 'object', tsType: 'SerializedBlockNode', description: 'see …#block-interface-name-collisions' }\n * → { type: 'object' }\n */\nconst removeTypeGenArtifacts = (schema: JsonSchemaType): JsonSchemaType =>\n mapNodes(schema, (node) => {\n delete (node as { tsType?: unknown }).tsType\n const { description } = node as { description?: string }\n if (\n typeof description === 'string' &&\n description.includes('block-interface-name-collisions')\n ) {\n delete (node as { description?: unknown }).description\n }\n return node\n }) as JsonSchemaType\n\n/**\n * **Size.** Removes `type` whenever a `const` sits next to it — the constant already fixes the value.\n *\n * @example\n * { type: 'string', const: 'paragraph' } → { const: 'paragraph' }\n */\nconst dropRedundantConstType = (schema: JsonSchemaType): JsonSchemaType =>\n mapNodes(schema, (node) => {\n if ('const' in node && 'type' in node) {\n delete (node as { type?: unknown }).type\n }\n return node\n }) as JsonSchemaType\n\n/**\n * **Size.** Merges the members of a `oneOf`/`anyOf` that are identical except for one `const`-valued property\n * into a single member with that property as an `enum`. Since the members differ only by that one\n * constant, the `enum` form accepts exactly the same values - it's just smaller. This folds the\n * per-collection relationship and upload variants, which differ only in their `relationTo` constant.\n *\n * @example\n * { oneOf: [\n * { properties: { relationTo: { const: 'posts' }, value: { type: 'string' } } },\n * { properties: { relationTo: { const: 'pages' }, value: { type: 'string' } } },\n * ] }\n * → { properties: { relationTo: { enum: ['posts', 'pages'] }, value: { type: 'string' } } }\n */\nconst mergeConstDiscriminatedUnions = (schema: JsonSchemaType): JsonSchemaType =>\n mapNodes(schema, (node) => {\n for (const keyword of ['oneOf', 'anyOf'] as const) {\n const members = node[keyword]\n if (Array.isArray(members) && members.length > 1) {\n const merged = mergeMembersByConst(members)\n if (merged) {\n delete node[keyword]\n Object.assign(node, merged)\n }\n }\n }\n return node\n }) as JsonSchemaType\n\n/** Returns the merged member for {@link mergeConstDiscriminatedUnions}, or `null` if they can't merge. */\nconst mergeMembersByConst = (members: Array<boolean | JsonSchemaType>): JsonSchemaType | null => {\n const objects = members.filter(\n (member): member is JsonSchemaType =>\n typeof member === 'object' && member !== null && typeof member.properties === 'object',\n )\n if (objects.length < 2 || objects.length !== members.length) {\n return null\n }\n\n for (const discriminator of Object.keys(objects[0]!.properties!)) {\n // Each member must pin this property to a `const`...\n const constValues: unknown[] = []\n const everyMemberPinsConst = objects.every((member) => {\n const prop = member.properties![discriminator]\n if (prop && typeof prop === 'object' && 'const' in prop) {\n constValues.push(prop.const)\n return true\n }\n return false\n })\n if (!everyMemberPinsConst) {\n continue\n }\n\n // ...and be otherwise identical (compare each member with the discriminator removed).\n const fingerprintWithoutDiscriminator = (member: JsonSchemaType): string => {\n const { [discriminator]: _discriminator, ...otherProperties } = member.properties!\n return JSON.stringify({ ...member, properties: otherProperties })\n }\n if (new Set(objects.map(fingerprintWithoutDiscriminator)).size !== 1) {\n continue\n }\n\n const uniqueConstValues = [...new Set(constValues)]\n if (uniqueConstValues.length < 2) {\n continue\n }\n\n // Replace the per-member `const` with a single `enum` of every value.\n const merged = structuredClone(objects[0]!)\n const discriminatorProp = merged.properties![discriminator]\n if (discriminatorProp && typeof discriminatorProp === 'object') {\n delete discriminatorProp.const\n discriminatorProp.enum = uniqueConstValues as NonNullable<typeof discriminatorProp.enum>\n }\n return merged\n }\n return null\n}\n\n// A `$ref` costs ~30 characters once names are shortened, so sharing a subschema only pays off when\n// it's larger than that and appears more than once.\nconst MIN_SHARED_SIZE = 64\n\n/** A subschema worth sharing: a standalone object/union/described schema, not a `$ref` or a primitive. */\nconst isShareable = (node: unknown): node is Record<string, unknown> => {\n if (!node || typeof node !== 'object' || Array.isArray(node) || '$ref' in node) {\n return false\n }\n const schema = node as JsonSchemaType\n return (\n Array.isArray(schema.oneOf) ||\n Array.isArray(schema.anyOf) ||\n Boolean(schema.properties) ||\n Boolean(schema.items) ||\n typeof schema.description === 'string'\n )\n}\n\n/**\n * **Size.** Replaces any subschema that appears more than once with a single shared `$defs` entry referenced by\n * `$ref`. Lossless — only the serialized size shrinks. A collection with several rich-text fields, for\n * example, inlines the same large lexical node schema once per field; this collapses them into one.\n *\n * @example\n * { properties: { billing: address, shipping: address } }\n * → { properties: { billing: { $ref: '#/$defs/shared_0' }, shipping: { $ref: '#/$defs/shared_0' } }, $defs: { shared_0: address } }\n */\nconst deduplicateIntoDefinitions = (schema: JsonSchemaType): JsonSchemaType => {\n // Count how often each shareable subschema appears, keyed by its serialized form.\n const counts = new Map<string, number>()\n const count = (node: unknown): void => {\n if (isShareable(node)) {\n const key = JSON.stringify(node)\n if (key.length >= MIN_SHARED_SIZE) {\n counts.set(key, (counts.get(key) ?? 0) + 1)\n }\n }\n if (Array.isArray(node)) {\n node.forEach(count)\n } else if (node && typeof node === 'object') {\n Object.values(node).forEach(count)\n }\n }\n count(schema)\n\n if (![...counts.values()].some((n) => n >= 2)) {\n return schema\n }\n\n // Replace each subschema seen 2+ times with a `$ref`. The first time we meet one it becomes a shared\n // entry; we never descend into a stored entry, so shared entries never reference one another.\n const sharedEntries: Record<string, JsonSchemaType> = {}\n const nameByKey = new Map<string, string>()\n const share = (node: unknown): unknown => {\n if (isShareable(node)) {\n const key = JSON.stringify(node)\n if ((counts.get(key) ?? 0) >= 2) {\n let name = nameByKey.get(key)\n if (!name) {\n name = `shared_${nameByKey.size}`\n nameByKey.set(key, name)\n sharedEntries[name] = node\n }\n return { $ref: `#/$defs/${name}` }\n }\n }\n if (Array.isArray(node)) {\n return node.map(share)\n }\n if (node && typeof node === 'object') {\n return Object.fromEntries(\n Object.entries(node).map(([childKey, value]) => [childKey, share(value)]),\n )\n }\n return node\n }\n\n const result = share(schema) as JsonSchemaType\n result.$defs = { ...result.$defs, ...sharedEntries }\n return result\n}\n\n/**\n * **LLM ergonomics.** Renames `$defs` entries to short, readable names so the `$ref`s stay legible to the model: a block's\n * `blockType` (`Code`), a node's `type` (`paragraph`), `node` for a rich-text node union, otherwise the\n * generated name with its disambiguating hash dropped. A numeric suffix keeps collisions unique.\n *\n * @example\n * { properties: { body: { $ref: '#/$defs/LexicalNodes_9FBEC708' } }, $defs: { LexicalNodes_9FBEC708: {} } }\n * → { properties: { body: { $ref: '#/$defs/node' } }, $defs: { node: {} } }\n */\nconst shortenDefinitionNames = (schema: JsonSchemaType): JsonSchemaType => {\n const definitions = schema.$defs\n if (!definitions || Object.keys(definitions).length === 0) {\n return schema\n }\n\n const shortNameFor = (name: string, definition: boolean | JsonSchemaType): string => {\n const properties = (typeof definition === 'object' && definition.properties) || {}\n const constString = (key: string): string | undefined => {\n const prop = properties[key]\n return prop && typeof prop === 'object' && typeof prop.const === 'string'\n ? prop.const\n : undefined\n }\n const members =\n typeof definition === 'object' ? (definition.anyOf ?? definition.oneOf) : undefined\n const isNodeUnion =\n Array.isArray(members) &&\n members.some((m) => typeof m === 'object' && Boolean(m.$ref || m.properties?.type))\n return (\n constString('blockType') ??\n constString('type') ??\n (isNodeUnion ? 'node' : undefined) ??\n name.replace(/_[0-9A-F]{6,}$/i, '')\n )\n }\n\n const prefix = '#/$defs/'\n const usedNames = new Map<string, number>()\n const rename = new Map<string, string>()\n for (const [name, definition] of Object.entries(definitions)) {\n const shortName = shortNameFor(name, definition)\n const used = usedNames.get(shortName) ?? 0\n usedNames.set(shortName, used + 1)\n rename.set(name, used === 0 ? shortName : `${shortName}${used + 1}`)\n }\n\n const result = mapNodes(schema, (node) => {\n if (typeof node.$ref === 'string' && node.$ref.startsWith(prefix)) {\n const name = node.$ref.slice(prefix.length)\n node.$ref = `${prefix}${rename.get(name) ?? name}`\n }\n return node\n }) as JsonSchemaType\n result.$defs = Object.fromEntries(\n Object.entries(result.$defs!).map(([name, body]) => [rename.get(name) ?? name, body]),\n )\n return result\n}\n"],"names":["sanitizeEntitySchema","schema","result","structuredClone","collapseOptionalContainerTypes","pointFieldsToObjects","removeTypeGenArtifacts","dropRedundantConstType","mergeConstDiscriminatedUnions","deduplicateIntoDefinitions","shortenDefinitionNames","mapNodes","node","visit","Array","isArray","map","child","out","key","value","Object","entries","properties","keys","prop","isRequired","required","includes","collapseNullableType","items","type","nonNullTypes","filter","t","length","transformed","fromEntries","isArrayType","isPointField","every","item","isNullable","description","latitude","longitude","tsType","keyword","members","merged","mergeMembersByConst","assign","objects","member","discriminator","constValues","everyMemberPinsConst","push","const","fingerprintWithoutDiscriminator","_discriminator","otherProperties","JSON","stringify","Set","size","uniqueConstValues","discriminatorProp","enum","MIN_SHARED_SIZE","isShareable","oneOf","anyOf","Boolean","counts","Map","count","set","get","forEach","values","some","n","sharedEntries","nameByKey","share","name","$ref","childKey","$defs","definitions","shortNameFor","definition","constString","undefined","isNodeUnion","m","replace","prefix","usedNames","rename","shortName","used","startsWith","slice","body"],"mappings":"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"}
@@ -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('keeps a Lexical node union strict (oneOf) while simplifying relationship values to IDs', ()=>{
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) where a relationship node's `value` is either
7
- // an ID or a `$ref` to the populated related collection.
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
- oneOf: [
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
- // ...while the relationship `value` is simplified to a bare ID (the populated-doc `$ref` is dropped).
93
+ // The value is already ID-only from the input variant - left as-is.
111
94
  value: {
112
95
  type: 'string',
113
- description: 'The ID of the related "posts" document.'
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
- // `id` is dropped - it's a Payload-managed field MCP clients never set.
107
+ // Managed fields (createdAt/updatedAt/_status) are excluded upstream by the `input` variant;
108
+ // `id` stays because it's 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('keeps a Lexical node union strict (oneOf) while simplifying relationship values to IDs', () => {\n // Shaped like the schema the MCP server prepares for a collection with a Lexical richText field:\n // a `$defs` node union (a `oneOf` of node shapes) where a relationship node's `value` is either\n // an ID or a `$ref` to the populated related collection.\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: { oneOf: [{ type: 'string' }, { $ref: '#/$defs/posts' }] },\n },\n required: ['type'],\n },\n ],\n },\n posts: {\n type: 'object',\n additionalProperties: false,\n properties: { id: { type: 'string' }, title: { type: 'string' } },\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 // ...while the relationship `value` is simplified to a bare ID (the populated-doc `$ref` is dropped).\n value: { type: 'string', description: 'The ID of the related \"posts\" document.' },\n },\n required: ['type'],\n },\n ],\n },\n posts: {\n type: 'object',\n additionalProperties: false,\n properties: { id: { type: 'string' }, title: { type: 'string' } },\n },\n },\n properties: {\n // `id` is dropped - it's a Payload-managed field MCP clients never set.\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","$ref","posts","id","title","content","root","children","items","toStrictEqual","node","description"],"mappings":"AAAA,SAASA,QAAQ,EAAEC,MAAM,EAAEC,EAAE,QAAQ,SAAQ;AAI7C,SAASC,oBAAoB,QAAQ,4BAA2B;AAEhEH,SAAS,wBAAwB;IAC/BE,GAAG,0FAA0F;QAC3F,iGAAiG;QACjG,gGAAgG;QAChG,yDAAyD;QACzD,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;oCAAEL,OAAO;wCAAC;4CAAEH,MAAM;wCAAS;wCAAG;4CAAES,MAAM;wCAAgB;qCAAE;gCAAC;4BAClE;4BACAF,UAAU;gCAAC;6BAAO;wBACpB;qBACD;gBACH;gBACAG,OAAO;oBACLV,MAAM;oBACNI,sBAAsB;oBACtBC,YAAY;wBAAEM,IAAI;4BAAEX,MAAM;wBAAS;wBAAGY,OAAO;4BAAEZ,MAAM;wBAAS;oBAAE;gBAClE;YACF;YACAK,YAAY;gBACVM,IAAI;oBAAEX,MAAM;gBAAS;gBACrBa,SAAS;oBACPb,MAAM;oBACNK,YAAY;wBACVS,MAAM;4BACJd,MAAM;4BACNK,YAAY;gCACVU,UAAU;oCAAEf,MAAM;oCAASgB,OAAO;wCAAEP,MAAM;oCAAgC;gCAAE;4BAC9E;wBACF;oBACF;gBACF;YACF;QACF;QAEAb,OAAOE,qBAAqBC,aAAakB,aAAa,CAAC;YACrDjB,MAAM;YACNC,OAAO;gBACL,0FAA0F;gBAC1F,uCAAuC;gBACvCiB,MAAM;oBACJf,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,sGAAsG;gCACtGE,OAAO;oCAAER,MAAM;oCAAUmB,aAAa;gCAA0C;4BAClF;4BACAZ,UAAU;gCAAC;6BAAO;wBACpB;qBACD;gBACH;gBACAG,OAAO;oBACLV,MAAM;oBACNI,sBAAsB;oBACtBC,YAAY;wBAAEM,IAAI;4BAAEX,MAAM;wBAAS;wBAAGY,OAAO;4BAAEZ,MAAM;wBAAS;oBAAE;gBAClE;YACF;YACAK,YAAY;gBACV,wEAAwE;gBACxEQ,SAAS;oBACPb,MAAM;oBACNK,YAAY;wBACVS,MAAM;4BACJd,MAAM;4BACNK,YAAY;gCACVU,UAAU;oCAAEf,MAAM;oCAASgB,OAAO;wCAAEP,MAAM;oCAAe;gCAAE;4BAC7D;wBACF;oBACF;gBACF;YACF;QACF;IACF;AACF"}
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 fields (createdAt/updatedAt/_status) are excluded upstream by the `input` variant;\n // `id` stays because it's 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,6FAA6F;gBAC7F,oFAAoF;gBACpFK,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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payloadcms/plugin-mcp",
3
- "version": "4.0.0-internal.4cbdd50",
3
+ "version": "4.0.0-internal.543da65",
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
- "@cfworker/json-schema": "^4.1.1",
59
- "@modelcontextprotocol/server": "2.0.0-alpha.2",
58
+ "@modelcontextprotocol/server": "2.0.0-beta.2",
60
59
  "tsx": "4.22.4",
61
- "zod": "^4.0.0"
60
+ "zod": "^4.2.0"
62
61
  },
63
62
  "devDependencies": {
64
63
  "@payloadcms/eslint-config": "3.28.0",
65
- "payload": "4.0.0-internal.4cbdd50"
64
+ "payload": "4.0.0-internal.543da65"
66
65
  },
67
66
  "peerDependencies": {
68
- "payload": "4.0.0-internal.4cbdd50"
67
+ "payload": "4.0.0-internal.543da65"
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",
@@ -12,13 +12,8 @@ export type GetAuthorizedMCPArgs = {
12
12
  }
13
13
 
14
14
  /**
15
- * Resolves the MCP caller and returns the MCP surface authorized for that request.
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 instead of
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
  }
@@ -1,5 +1,11 @@
1
- import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/server'
2
- import { APIError, type PayloadHandler } from 'payload'
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
- const server = buildMcpServer({ authorizedMCP, pluginConfig, req })
32
-
33
- const transport = new WebStandardStreamableHTTPServerTransport({
34
- enableJsonResponse: true,
35
- sessionIdGenerator: undefined, // stateless mode
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
- await server.connect(transport)
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 mcpRequest = new Request(req.url, {
41
- body: req.body,
42
- duplex: 'half',
43
- headers: req.headers,
44
- method: req.method,
45
- } as { duplex: 'half' } & RequestInit)
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
- return await transport.handleRequest(mcpRequest)
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 PayloadRequest {
10
- payloadAPI: 'GraphQL' | 'local' | 'MCP' | 'REST'
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, plugins, ...rawConfig }) => {
26
- const pluginConfig = sanitizeMCPConfig({ config, pluginConfig: rawConfig })
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
- * Transport-agnostic core: registers every authorized MCP item onto a fresh
21
- * `McpServer` and returns it. The caller is responsible for picking a transport
22
- * (`WebStandardStreamableHTTPServerTransport`, `StdioServerTransport`, …) and
23
- * calling `server.connect(transport)`.
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
- response: MCPToolResponse,
52
- overrideResponse?: MCPResponseOverride,
53
- ): MCPToolResponse => {
54
- const overridden = overrideResponse?.(response, response.doc ?? {}, req) ?? response
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(response, match.tool.overrideResponse)
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
- finalizeToolResponse(
208
- await tool.handler({
209
- authorizedMCP,
210
- input: (input ?? {}) as Record<string, unknown>,
211
- req,
212
- serverContext: ctx,
213
- }),
214
- tool.overrideResponse,
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,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 { validateCollectionData } from '../validateEntityData.js'
15
14
  import { formatCollectionError } from './formatCollectionError.js'
@@ -86,8 +85,8 @@ export const createDocumentTool = defineCollectionTool({
86
85
  data: parsedData,
87
86
  depth,
88
87
  draft,
88
+ overrideAccess: authorizedMCP.overrideAccess,
89
89
  req,
90
- ...localAPIDefaults(authorizedMCP),
91
90
  ...(locale ? { locale } : {}),
92
91
  ...(fallbackLocale ? { fallbackLocale } : {}),
93
92
  ...(select ? { select: select as SelectType } : {}),