@strapi/upload 5.58.0 → 5.59.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (226) hide show
  1. package/dist/admin/components/MediaLibraryInput/AddAssetMenu.js +141 -0
  2. package/dist/admin/components/MediaLibraryInput/AddAssetMenu.js.map +1 -0
  3. package/dist/admin/components/MediaLibraryInput/AddAssetMenu.mjs +139 -0
  4. package/dist/admin/components/MediaLibraryInput/AddAssetMenu.mjs.map +1 -0
  5. package/dist/admin/components/MediaLibraryInput/AssetDropZone.js +129 -0
  6. package/dist/admin/components/MediaLibraryInput/AssetDropZone.js.map +1 -0
  7. package/dist/admin/components/MediaLibraryInput/AssetDropZone.mjs +127 -0
  8. package/dist/admin/components/MediaLibraryInput/AssetDropZone.mjs.map +1 -0
  9. package/dist/admin/components/MediaLibraryInput/AssetRow.js +144 -0
  10. package/dist/admin/components/MediaLibraryInput/AssetRow.js.map +1 -0
  11. package/dist/admin/components/MediaLibraryInput/AssetRow.mjs +142 -0
  12. package/dist/admin/components/MediaLibraryInput/AssetRow.mjs.map +1 -0
  13. package/dist/admin/components/MediaLibraryInput/MediaLibraryInput.js +397 -0
  14. package/dist/admin/components/MediaLibraryInput/MediaLibraryInput.js.map +1 -0
  15. package/dist/admin/components/MediaLibraryInput/MediaLibraryInput.mjs +395 -0
  16. package/dist/admin/components/MediaLibraryInput/MediaLibraryInput.mjs.map +1 -0
  17. package/dist/admin/components/MediaLibraryInput/NoLibraryAccessDialog.js +45 -0
  18. package/dist/admin/components/MediaLibraryInput/NoLibraryAccessDialog.js.map +1 -0
  19. package/dist/admin/components/MediaLibraryInput/NoLibraryAccessDialog.mjs +43 -0
  20. package/dist/admin/components/MediaLibraryInput/NoLibraryAccessDialog.mjs.map +1 -0
  21. package/dist/admin/hooks/useAIMetadataAvailability.js +5 -1
  22. package/dist/admin/hooks/useAIMetadataAvailability.js.map +1 -1
  23. package/dist/admin/hooks/useAIMetadataAvailability.mjs +5 -1
  24. package/dist/admin/hooks/useAIMetadataAvailability.mjs.map +1 -1
  25. package/dist/admin/hooks/useAIMetadataEnabled.js +7 -1
  26. package/dist/admin/hooks/useAIMetadataEnabled.js.map +1 -1
  27. package/dist/admin/hooks/useAIMetadataEnabled.mjs +7 -1
  28. package/dist/admin/hooks/useAIMetadataEnabled.mjs.map +1 -1
  29. package/dist/admin/hooks/useMediaLibraryPermissions.js +4 -0
  30. package/dist/admin/hooks/useMediaLibraryPermissions.js.map +1 -1
  31. package/dist/admin/hooks/useMediaLibraryPermissions.mjs +4 -0
  32. package/dist/admin/hooks/useMediaLibraryPermissions.mjs.map +1 -1
  33. package/dist/admin/hooks/useTracking.js +5 -1
  34. package/dist/admin/hooks/useTracking.js.map +1 -1
  35. package/dist/admin/hooks/useTracking.mjs +5 -1
  36. package/dist/admin/hooks/useTracking.mjs.map +1 -1
  37. package/dist/admin/index.js +7 -2
  38. package/dist/admin/index.js.map +1 -1
  39. package/dist/admin/index.mjs +7 -2
  40. package/dist/admin/index.mjs.map +1 -1
  41. package/dist/admin/legacy/components/AssetCard/AssetCardBase.js.map +1 -1
  42. package/dist/admin/legacy/components/AssetCard/AssetCardBase.mjs.map +1 -1
  43. package/dist/admin/legacy/components/AssetCard/VideoPreview.js +2 -1
  44. package/dist/admin/legacy/components/AssetCard/VideoPreview.js.map +1 -1
  45. package/dist/admin/legacy/components/AssetCard/VideoPreview.mjs +2 -1
  46. package/dist/admin/legacy/components/AssetCard/VideoPreview.mjs.map +1 -1
  47. package/dist/admin/legacy/components/AssetDialog/AssetDialog.js +3 -1
  48. package/dist/admin/legacy/components/AssetDialog/AssetDialog.js.map +1 -1
  49. package/dist/admin/legacy/components/AssetDialog/AssetDialog.mjs +3 -1
  50. package/dist/admin/legacy/components/AssetDialog/AssetDialog.mjs.map +1 -1
  51. package/dist/admin/legacy/components/AssetDialog/BrowseStep/BrowseStep.js +3 -1
  52. package/dist/admin/legacy/components/AssetDialog/BrowseStep/BrowseStep.js.map +1 -1
  53. package/dist/admin/legacy/components/AssetDialog/BrowseStep/BrowseStep.mjs +3 -1
  54. package/dist/admin/legacy/components/AssetDialog/BrowseStep/BrowseStep.mjs.map +1 -1
  55. package/dist/admin/legacy/components/AssetDialog/BrowseStep/Filters.js.map +1 -1
  56. package/dist/admin/legacy/components/AssetDialog/BrowseStep/Filters.mjs.map +1 -1
  57. package/dist/admin/legacy/components/AssetGridList/AssetGridList.js +7 -6
  58. package/dist/admin/legacy/components/AssetGridList/AssetGridList.js.map +1 -1
  59. package/dist/admin/legacy/components/AssetGridList/AssetGridList.mjs +7 -6
  60. package/dist/admin/legacy/components/AssetGridList/AssetGridList.mjs.map +1 -1
  61. package/dist/admin/legacy/components/Breadcrumbs/CrumbSimpleMenuAsync.js +2 -1
  62. package/dist/admin/legacy/components/Breadcrumbs/CrumbSimpleMenuAsync.js.map +1 -1
  63. package/dist/admin/legacy/components/Breadcrumbs/CrumbSimpleMenuAsync.mjs +2 -1
  64. package/dist/admin/legacy/components/Breadcrumbs/CrumbSimpleMenuAsync.mjs.map +1 -1
  65. package/dist/admin/legacy/components/BulkMoveDialog/BulkMoveDialog.js +2 -1
  66. package/dist/admin/legacy/components/BulkMoveDialog/BulkMoveDialog.js.map +1 -1
  67. package/dist/admin/legacy/components/BulkMoveDialog/BulkMoveDialog.mjs +2 -1
  68. package/dist/admin/legacy/components/BulkMoveDialog/BulkMoveDialog.mjs.map +1 -1
  69. package/dist/admin/legacy/components/EditAssetDialog/EditAssetContent.js +3 -1
  70. package/dist/admin/legacy/components/EditAssetDialog/EditAssetContent.js.map +1 -1
  71. package/dist/admin/legacy/components/EditAssetDialog/EditAssetContent.mjs +3 -1
  72. package/dist/admin/legacy/components/EditAssetDialog/EditAssetContent.mjs.map +1 -1
  73. package/dist/admin/legacy/components/FilterList/FilterList.js.map +1 -1
  74. package/dist/admin/legacy/components/FilterList/FilterList.mjs.map +1 -1
  75. package/dist/admin/legacy/components/FilterPopover/FilterPopover.js.map +1 -1
  76. package/dist/admin/legacy/components/FilterPopover/FilterPopover.mjs.map +1 -1
  77. package/dist/admin/legacy/components/FilterPopover/FilterValueInput.js +2 -1
  78. package/dist/admin/legacy/components/FilterPopover/FilterValueInput.js.map +1 -1
  79. package/dist/admin/legacy/components/FilterPopover/FilterValueInput.mjs +2 -1
  80. package/dist/admin/legacy/components/FilterPopover/FilterValueInput.mjs.map +1 -1
  81. package/dist/admin/legacy/components/MediaLibraryDialog/MediaLibraryDialog.js +4 -2
  82. package/dist/admin/legacy/components/MediaLibraryDialog/MediaLibraryDialog.js.map +1 -1
  83. package/dist/admin/legacy/components/MediaLibraryDialog/MediaLibraryDialog.mjs +4 -2
  84. package/dist/admin/legacy/components/MediaLibraryDialog/MediaLibraryDialog.mjs.map +1 -1
  85. package/dist/admin/legacy/components/MediaLibraryInput/MediaLibraryInput.js +2 -1
  86. package/dist/admin/legacy/components/MediaLibraryInput/MediaLibraryInput.js.map +1 -1
  87. package/dist/admin/legacy/components/MediaLibraryInput/MediaLibraryInput.mjs +2 -1
  88. package/dist/admin/legacy/components/MediaLibraryInput/MediaLibraryInput.mjs.map +1 -1
  89. package/dist/admin/legacy/components/SelectTree/SelectTree.js +3 -1
  90. package/dist/admin/legacy/components/SelectTree/SelectTree.js.map +1 -1
  91. package/dist/admin/legacy/components/SelectTree/SelectTree.mjs +3 -1
  92. package/dist/admin/legacy/components/SelectTree/SelectTree.mjs.map +1 -1
  93. package/dist/admin/legacy/components/TableList/TableList.js +3 -1
  94. package/dist/admin/legacy/components/TableList/TableList.js.map +1 -1
  95. package/dist/admin/legacy/components/TableList/TableList.mjs +3 -1
  96. package/dist/admin/legacy/components/TableList/TableList.mjs.map +1 -1
  97. package/dist/admin/legacy/components/TableList/TableRows.js +3 -1
  98. package/dist/admin/legacy/components/TableList/TableRows.js.map +1 -1
  99. package/dist/admin/legacy/components/TableList/TableRows.mjs +3 -1
  100. package/dist/admin/legacy/components/TableList/TableRows.mjs.map +1 -1
  101. package/dist/admin/legacy/components/UploadAssetDialog/UploadAssetDialog.js +3 -1
  102. package/dist/admin/legacy/components/UploadAssetDialog/UploadAssetDialog.js.map +1 -1
  103. package/dist/admin/legacy/components/UploadAssetDialog/UploadAssetDialog.mjs +3 -1
  104. package/dist/admin/legacy/components/UploadAssetDialog/UploadAssetDialog.mjs.map +1 -1
  105. package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.js.map +1 -1
  106. package/dist/admin/legacy/pages/App/ConfigureTheView/ConfigureTheView.mjs.map +1 -1
  107. package/dist/admin/legacy/pages/App/components/BulkActions.js +2 -1
  108. package/dist/admin/legacy/pages/App/components/BulkActions.js.map +1 -1
  109. package/dist/admin/legacy/pages/App/components/BulkActions.mjs +2 -1
  110. package/dist/admin/legacy/pages/App/components/BulkActions.mjs.map +1 -1
  111. package/dist/admin/legacy/pages/App/components/BulkMoveButton.js +2 -1
  112. package/dist/admin/legacy/pages/App/components/BulkMoveButton.js.map +1 -1
  113. package/dist/admin/legacy/pages/App/components/BulkMoveButton.mjs +2 -1
  114. package/dist/admin/legacy/pages/App/components/BulkMoveButton.mjs.map +1 -1
  115. package/dist/admin/pages/Assets/components/AssetsGrid.js +2 -1
  116. package/dist/admin/pages/Assets/components/AssetsGrid.js.map +1 -1
  117. package/dist/admin/pages/Assets/components/AssetsGrid.mjs +2 -1
  118. package/dist/admin/pages/Assets/components/AssetsGrid.mjs.map +1 -1
  119. package/dist/admin/pages/Assets/components/AssetsTable.js +2 -1
  120. package/dist/admin/pages/Assets/components/AssetsTable.js.map +1 -1
  121. package/dist/admin/pages/Assets/components/AssetsTable.mjs +2 -1
  122. package/dist/admin/pages/Assets/components/AssetsTable.mjs.map +1 -1
  123. package/dist/admin/pages/Assets/components/BulkActionsBar.js +3 -1
  124. package/dist/admin/pages/Assets/components/BulkActionsBar.js.map +1 -1
  125. package/dist/admin/pages/Assets/components/BulkActionsBar.mjs +3 -1
  126. package/dist/admin/pages/Assets/components/BulkActionsBar.mjs.map +1 -1
  127. package/dist/admin/pages/Assets/components/DateRangeCalendar.js +10 -7
  128. package/dist/admin/pages/Assets/components/DateRangeCalendar.js.map +1 -1
  129. package/dist/admin/pages/Assets/components/DateRangeCalendar.mjs +10 -7
  130. package/dist/admin/pages/Assets/components/DateRangeCalendar.mjs.map +1 -1
  131. package/dist/admin/pages/Assets/components/DropZone/UploadDropZoneContext.js +4 -2
  132. package/dist/admin/pages/Assets/components/DropZone/UploadDropZoneContext.js.map +1 -1
  133. package/dist/admin/pages/Assets/components/DropZone/UploadDropZoneContext.mjs +5 -3
  134. package/dist/admin/pages/Assets/components/DropZone/UploadDropZoneContext.mjs.map +1 -1
  135. package/dist/admin/pages/Assets/components/FilterBadges.js +32 -3
  136. package/dist/admin/pages/Assets/components/FilterBadges.js.map +1 -1
  137. package/dist/admin/pages/Assets/components/FilterBadges.mjs +33 -4
  138. package/dist/admin/pages/Assets/components/FilterBadges.mjs.map +1 -1
  139. package/dist/admin/services/api.js +15 -5
  140. package/dist/admin/services/api.js.map +1 -1
  141. package/dist/admin/services/api.mjs +15 -5
  142. package/dist/admin/services/api.mjs.map +1 -1
  143. package/dist/admin/src/components/MediaLibraryInput/AddAssetMenu.d.ts +19 -0
  144. package/dist/admin/src/components/MediaLibraryInput/AssetDropZone.d.ts +13 -0
  145. package/dist/admin/src/components/MediaLibraryInput/AssetRow.d.ts +15 -0
  146. package/dist/admin/src/components/MediaLibraryInput/MediaLibraryInput.d.ts +26 -0
  147. package/dist/admin/src/components/MediaLibraryInput/NoLibraryAccessDialog.d.ts +10 -0
  148. package/dist/admin/src/hooks/useMediaLibraryPermissions.d.ts +1 -0
  149. package/dist/admin/src/legacy/components/BulkMoveDialog/BulkMoveDialog.d.ts +1 -1
  150. package/dist/admin/src/legacy/pages/App/components/BulkActions.d.ts +1 -1
  151. package/dist/admin/src/services/api.d.ts +6 -0
  152. package/dist/admin/src/utils/allowedMediaTypes.d.ts +16 -0
  153. package/dist/admin/translations/ca.json.js +3 -3
  154. package/dist/admin/translations/ca.json.mjs +3 -3
  155. package/dist/admin/translations/da.json.js +1 -1
  156. package/dist/admin/translations/da.json.mjs +1 -1
  157. package/dist/admin/translations/de.json.js +1 -1
  158. package/dist/admin/translations/de.json.mjs +1 -1
  159. package/dist/admin/translations/en.json.js +9 -0
  160. package/dist/admin/translations/en.json.js.map +1 -1
  161. package/dist/admin/translations/en.json.mjs +9 -0
  162. package/dist/admin/translations/en.json.mjs.map +1 -1
  163. package/dist/admin/translations/es.json.js +1 -1
  164. package/dist/admin/translations/es.json.mjs +1 -1
  165. package/dist/admin/translations/sk.json.js +2 -2
  166. package/dist/admin/translations/sk.json.mjs +2 -2
  167. package/dist/admin/translations/tr.json.js +2 -2
  168. package/dist/admin/translations/tr.json.mjs +2 -2
  169. package/dist/admin/translations/zh-Hans.json.js +1 -1
  170. package/dist/admin/translations/zh-Hans.json.mjs +1 -1
  171. package/dist/admin/translations/zh.json.js +2 -2
  172. package/dist/admin/translations/zh.json.mjs +2 -2
  173. package/dist/admin/utils/allowedMediaTypes.js +42 -0
  174. package/dist/admin/utils/allowedMediaTypes.js.map +1 -0
  175. package/dist/admin/utils/allowedMediaTypes.mjs +39 -0
  176. package/dist/admin/utils/allowedMediaTypes.mjs.map +1 -0
  177. package/dist/server/config.js +6 -0
  178. package/dist/server/config.js.map +1 -1
  179. package/dist/server/config.mjs +6 -0
  180. package/dist/server/config.mjs.map +1 -1
  181. package/dist/server/controllers/validation/admin/upload.js.map +1 -1
  182. package/dist/server/controllers/validation/admin/upload.mjs.map +1 -1
  183. package/dist/server/controllers/validation/content-api/upload.js.map +1 -1
  184. package/dist/server/controllers/validation/content-api/upload.mjs.map +1 -1
  185. package/dist/server/mcp/ambient-instance.js +7 -5
  186. package/dist/server/mcp/ambient-instance.js.map +1 -1
  187. package/dist/server/mcp/ambient-instance.mjs +7 -5
  188. package/dist/server/mcp/ambient-instance.mjs.map +1 -1
  189. package/dist/server/mcp/handlers/folder-handlers.js +4 -16
  190. package/dist/server/mcp/handlers/folder-handlers.js.map +1 -1
  191. package/dist/server/mcp/handlers/folder-handlers.mjs +5 -17
  192. package/dist/server/mcp/handlers/folder-handlers.mjs.map +1 -1
  193. package/dist/server/mcp/handlers/read-handlers.js +6 -14
  194. package/dist/server/mcp/handlers/read-handlers.js.map +1 -1
  195. package/dist/server/mcp/handlers/read-handlers.mjs +7 -15
  196. package/dist/server/mcp/handlers/read-handlers.mjs.map +1 -1
  197. package/dist/server/mcp/handlers/write-handlers.js +3 -13
  198. package/dist/server/mcp/handlers/write-handlers.js.map +1 -1
  199. package/dist/server/mcp/handlers/write-handlers.mjs +3 -13
  200. package/dist/server/mcp/handlers/write-handlers.mjs.map +1 -1
  201. package/dist/server/mcp/register-upload-mcp-tools.js +215 -195
  202. package/dist/server/mcp/register-upload-mcp-tools.js.map +1 -1
  203. package/dist/server/mcp/register-upload-mcp-tools.mjs +215 -195
  204. package/dist/server/mcp/register-upload-mcp-tools.mjs.map +1 -1
  205. package/dist/server/services/image-manipulation.js +33 -14
  206. package/dist/server/services/image-manipulation.js.map +1 -1
  207. package/dist/server/services/image-manipulation.mjs +33 -14
  208. package/dist/server/services/image-manipulation.mjs.map +1 -1
  209. package/dist/server/src/config.d.ts +3 -0
  210. package/dist/server/src/config.d.ts.map +1 -1
  211. package/dist/server/src/controllers/validation/admin/upload.d.ts.map +1 -1
  212. package/dist/server/src/controllers/validation/content-api/upload.d.ts.map +1 -1
  213. package/dist/server/src/index.d.ts +3 -0
  214. package/dist/server/src/index.d.ts.map +1 -1
  215. package/dist/server/src/mcp/ambient-instance.d.ts +7 -5
  216. package/dist/server/src/mcp/ambient-instance.d.ts.map +1 -1
  217. package/dist/server/src/mcp/handlers/folder-handlers.d.ts.map +1 -1
  218. package/dist/server/src/mcp/handlers/read-handlers.d.ts +3 -2
  219. package/dist/server/src/mcp/handlers/read-handlers.d.ts.map +1 -1
  220. package/dist/server/src/mcp/handlers/write-handlers.d.ts.map +1 -1
  221. package/dist/server/src/mcp/register-upload-mcp-tools.d.ts +9 -14
  222. package/dist/server/src/mcp/register-upload-mcp-tools.d.ts.map +1 -1
  223. package/dist/server/src/services/image-manipulation.d.ts.map +1 -1
  224. package/dist/server/src/types.d.ts +1 -0
  225. package/dist/server/src/types.d.ts.map +1 -1
  226. package/package.json +6 -6
@@ -1 +1 @@
1
- {"version":3,"file":"folder-handlers.js","sources":["../../../../server/src/mcp/handlers/folder-handlers.ts"],"sourcesContent":["import { errors } from '@strapi/utils';\nimport type { Core, Modules } from '@strapi/types';\n\nimport { assertAmbientInstance, getFolderService } from '../ambient-instance';\nimport { ACTIONS, FOLDER_MODEL_UID, FILE_MODEL_UID } from '../../constants';\nimport { isFolderOrChild } from '../../controllers/utils/folders';\nimport { assertMediaPermission } from '../permissions';\nimport { sanitizeMediaFolder } from '../sanitizers/sanitize-media';\nimport {\n MCP_NOT_FOUND_FOLDER,\n MCP_FOLDER_NAME_TAKEN,\n MCP_PARENT_FOLDER_NOT_FOUND,\n MCP_FOLDER_MOVE_INTO_SELF,\n MCP_DELETE_FOLDER_UNRESOLVED_IDS,\n} from './constants';\nimport { ok } from '../utils';\n\nimport type { Folder } from '../../types';\n\n// Arg types are type-level only: the MCP SDK validates `args` against the tool's strict Zod\n// input schema before the handler runs, so unknown keys never reach here.\ntype CreateFolderArgs = {\n name: string;\n parent?: number | null;\n};\n\ntype RenameFolderArgs = {\n id: number;\n name: string;\n};\n\ntype MoveFolderArgs = {\n id: number;\n parent: number | null;\n};\n\ntype DeleteFolderArgs = {\n ids: number[];\n dryRun?: boolean;\n};\n\n/** The folder fields the handlers need; `path` drives the cascade and subtree checks. */\ntype FolderRow = Pick<Folder, 'id' | 'name' | 'path'>;\n\n/**\n * `withParent` populates the parent relation in the same round trip. Rename needs the parent id\n * to scope its sibling-uniqueness check, so it would otherwise read the same row twice; the\n * subtree and cascade callers do not, and skip the join.\n */\nconst findFolderById = async (\n strapi: Core.Strapi,\n id: number,\n { withParent = false }: { withParent?: boolean } = {}\n): Promise<(FolderRow & { parent?: { id: number } | null }) | null> =>\n strapi.db.query(FOLDER_MODEL_UID).findOne({\n select: ['id', 'name', 'path'],\n where: { id },\n ...(withParent ? { populate: { parent: { select: ['id'] } } } : {}),\n });\n\n/**\n * Re-reads a folder with its parent populated, so the write tools can echo back the same shape\n * regardless of what the service returned.\n *\n * `folder.update()` resolves the row it wrote without the `parent` relation, and on a pure rename\n * it never touches the join table at all — so the parent has to be read back to be reported.\n */\nconst readFolderForOutput = async (\n strapi: Core.Strapi,\n id: number,\n fallback: Record<string, unknown>\n) => {\n const row = await strapi.db.query(FOLDER_MODEL_UID).findOne({\n where: { id },\n populate: { parent: { select: ['id', 'name'] } },\n });\n\n // The write succeeded, so report it even if the read-back comes back empty rather than\n // failing a completed operation on a follow-up query.\n return sanitizeMediaFolder(row ?? fallback);\n};\n\n/**\n * Replicates the admin folder controller's `parent` check: a named parent must exist.\n *\n * null is the media library root and always valid, so only a numeric id is looked up.\n */\nconst assertParentExists = async (strapi: Core.Strapi, parent: number | null | undefined) => {\n if (parent === null || parent === undefined) {\n return;\n }\n\n const exists = await getFolderService(strapi).exists({ id: parent });\n\n if (!exists) {\n throw new errors.ValidationError(MCP_PARENT_FOLDER_NOT_FOUND);\n }\n};\n\n/**\n * Replicates `is-folder-unique` from the admin folder validation: a folder name must be unique\n * among its siblings.\n *\n * `excludeId` is the folder being renamed — without it a rename to the folder's current name\n * would collide with itself.\n */\nconst assertNameAvailable = async (\n strapi: Core.Strapi,\n name: string,\n parent: number | null,\n excludeId?: number\n) => {\n const filters: Record<string, unknown> = { name, parent: parent ?? null };\n\n if (excludeId !== undefined) {\n filters.id = { $ne: excludeId };\n }\n\n if (await getFolderService(strapi).exists(filters)) {\n throw new errors.ValidationError(MCP_FOLDER_NAME_TAKEN);\n }\n};\n\n/**\n * Replicates `dont-move-inside-self`: a folder cannot become its own descendant.\n *\n * Without this the folder service would rewrite the subtree's materialized paths into a cycle,\n * orphaning every descendant — the tree has no root to walk back to afterwards.\n */\nconst assertNotMovedIntoOwnSubtree = async (\n strapi: Core.Strapi,\n folder: FolderRow,\n parent: number | null\n) => {\n if (parent === null) {\n return;\n }\n\n const destination = await findFolderById(strapi, parent);\n\n // A missing destination is already reported by `assertParentExists`.\n if (destination === null) {\n return;\n }\n\n if (isFolderOrChild(destination, folder)) {\n throw new errors.ValidationError(MCP_FOLDER_MOVE_INTO_SELF);\n }\n};\n\n/**\n * Counts the folders and files a `deleteByIds` of these paths would remove.\n *\n * Mirrors the two `$or` predicates in `folder.deleteByIds` exactly — self path plus the\n * `${path}/` prefix — so the dry-run number is the same set the destructive branch acts on\n * rather than an independent estimate that could drift from it.\n */\nconst countCascade = async (strapi: Core.Strapi, paths: string[]) => {\n const pathPredicate = (field: string) => ({\n $or: paths.flatMap((path) => [\n { [field]: { $eq: path } },\n { [field]: { $startsWith: `${path}/` } },\n ]),\n });\n\n const [totalFolderNumber, totalFileNumber] = await Promise.all([\n strapi.db.query(FOLDER_MODEL_UID).count({ where: pathPredicate('path') }),\n strapi.db.query(FILE_MODEL_UID).count({ where: pathPredicate('folderPath') }),\n ]);\n\n return { totalFolderNumber, totalFileNumber };\n};\n\n/**\n * `media_create_folder` — a new folder, optionally nested.\n *\n * Gated on `plugin::upload.assets.create` and mirrors `POST /upload/folders`: the same\n * permission, the same uniqueness and parent-existence validation, the same `folder.create()`\n * call, and the same `createdBy` attribution from the session user.\n *\n * `create` rather than the `update` the other folder tools use: creating a folder is the one\n * folder operation whose admin route gates on `assets.create`.\n */\nexport const createMediaCreateFolderHandler = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext\n) => {\n assertAmbientInstance(strapi);\n\n return async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { name, parent = null } = args as CreateFolderArgs;\n\n assertMediaPermission(strapi, context, ACTIONS.create, FOLDER_MODEL_UID);\n\n await assertParentExists(strapi, parent);\n await assertNameAvailable(strapi, name, parent);\n\n const created = await getFolderService(strapi).create({ name, parent }, { user: context.user });\n\n return ok({ data: await readFolderForOutput(strapi, created.id, created) });\n };\n};\n\n/**\n * `media_rename_folder` — changes a folder's name and nothing else.\n *\n * Deliberately does not forward `parent` to `folder.update()`: the service branches on\n * `isUndefined(parent)`, and the name-only branch skips the transaction that rewrites descendant\n * paths. Passing the folder's existing parent would take the move branch and rewrite the whole\n * subtree to compute the identical paths.\n */\nexport const createMediaRenameFolderHandler = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext\n) => {\n assertAmbientInstance(strapi);\n\n return async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { id, name } = args as RenameFolderArgs;\n\n assertMediaPermission(strapi, context, ACTIONS.update, FOLDER_MODEL_UID);\n\n const folder = await findFolderById(strapi, id, { withParent: true });\n\n if (folder === null) {\n throw new errors.NotFoundError(MCP_NOT_FOUND_FOLDER);\n }\n\n // Siblings are the folders sharing this folder's parent, so uniqueness is checked against\n // the current location — a rename never changes it.\n const parentId = folder.parent?.id ?? null;\n\n await assertNameAvailable(strapi, name, parentId, id);\n\n // `parent` is omitted on purpose — see the note above.\n const renamed = await getFolderService(strapi).update(id, { name }, { user: context.user });\n\n return ok({ data: await readFolderForOutput(strapi, id, renamed ?? { ...folder, name }) });\n };\n};\n\n/**\n * `media_move_folder` — re-parents a folder, carrying its whole subtree with it.\n *\n * `folder.update()` recalculates the materialized `path` of every descendant folder and the\n * `folderPath` of every contained file inside a transaction, so the move is atomic and no\n * separate bookkeeping is needed here.\n */\nexport const createMediaMoveFolderHandler = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext\n) => {\n assertAmbientInstance(strapi);\n\n return async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { id, parent } = args as MoveFolderArgs;\n\n assertMediaPermission(strapi, context, ACTIONS.update, FOLDER_MODEL_UID);\n\n const folder = await findFolderById(strapi, id);\n\n if (folder === null) {\n throw new errors.NotFoundError(MCP_NOT_FOUND_FOLDER);\n }\n\n await assertParentExists(strapi, parent);\n await assertNotMovedIntoOwnSubtree(strapi, folder, parent);\n\n // A folder keeps its name across a move, so uniqueness must hold in the destination.\n await assertNameAvailable(strapi, folder.name, parent, id);\n\n const moved = await getFolderService(strapi).update(\n id,\n { name: folder.name, parent },\n { user: context.user }\n );\n\n return ok({ data: await readFolderForOutput(strapi, id, moved ?? folder) });\n };\n};\n\n/**\n * `media_delete_folder` — previews or performs a cascading folder deletion.\n *\n * Two branches behind one tool, because the preview and the deletion must agree on what the\n * cascade covers; splitting them across tools would let the two drift apart, and an agent could\n * reach the destructive one without ever seeing a count.\n *\n * `dryRun` defaults to true (see the input schema): omitting the flag previews, and deleting\n * takes an explicit `dryRun: false`.\n */\nexport const createMediaDeleteFolderHandler = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext\n) => {\n assertAmbientInstance(strapi);\n\n return async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { ids, dryRun = true } = args as DeleteFolderArgs;\n\n assertMediaPermission(strapi, context, ACTIONS.update, FOLDER_MODEL_UID);\n\n const matched: FolderRow[] = await strapi.db.query(FOLDER_MODEL_UID).findMany({\n select: ['id', 'name', 'path'],\n where: { id: { $in: ids } },\n });\n\n /**\n * All-or-nothing: any id that does not resolve to a folder rejects the whole call, before\n * anything is deleted.\n *\n * Folder ids and asset ids are indistinguishable integers, so a list mixing the two is the\n * mistake this tool exists to catch — and deleting the folders that did match while\n * reporting the rest back would be exactly the silent confusion that requirement forbids,\n * with the cascade already gone by the time the agent reads the response.\n *\n * An unresolvable id cannot be diagnosed further from here (an asset id and a deleted\n * folder id look identical), and it does not need to be: either way it is unusable.\n *\n * The dry run rejects on the same rule. A preview that reported a cascade for a request the\n * executing call would refuse is worse than no preview — it is a confirmation an agent\n * cannot act on.\n */\n const matchedIds = new Set(matched.map((folder) => folder.id));\n const unresolvedIds = ids.filter((id) => matchedIds.has(id) === false);\n\n if (unresolvedIds.length > 0) {\n throw new errors.ValidationError(MCP_DELETE_FOLDER_UNRESOLVED_IDS(unresolvedIds));\n }\n\n const folders = matched.map((folder) => sanitizeMediaFolder(folder));\n\n if (dryRun) {\n const counts = await countCascade(\n strapi,\n matched.map((folder) => folder.path)\n );\n\n return ok({ dryRun: true, folders, ...counts });\n }\n\n const { totalFolderNumber, totalFileNumber } = await getFolderService(strapi).deleteByIds(\n matched.map((folder) => folder.id)\n );\n\n return ok({\n dryRun: false,\n folders,\n totalFolderNumber,\n totalFileNumber,\n });\n };\n};\n"],"names":["findFolderById","strapi","id","withParent","db","query","FOLDER_MODEL_UID","findOne","select","where","populate","parent","readFolderForOutput","fallback","row","sanitizeMediaFolder","assertParentExists","undefined","exists","getFolderService","errors","ValidationError","MCP_PARENT_FOLDER_NOT_FOUND","assertNameAvailable","name","excludeId","filters","$ne","MCP_FOLDER_NAME_TAKEN","assertNotMovedIntoOwnSubtree","folder","destination","isFolderOrChild","MCP_FOLDER_MOVE_INTO_SELF","countCascade","paths","pathPredicate","field","$or","flatMap","path","$eq","$startsWith","totalFolderNumber","totalFileNumber","Promise","all","count","FILE_MODEL_UID","createMediaCreateFolderHandler","context","assertAmbientInstance","args","assertMediaPermission","ACTIONS","create","created","user","ok","data","createMediaRenameFolderHandler","update","NotFoundError","MCP_NOT_FOUND_FOLDER","parentId","renamed","createMediaMoveFolderHandler","moved","createMediaDeleteFolderHandler","ids","dryRun","matched","findMany","$in","matchedIds","Set","map","unresolvedIds","filter","has","length","MCP_DELETE_FOLDER_UNRESOLVED_IDS","folders","counts","deleteByIds"],"mappings":";;;;;;;;;;;AA4CA;;;;IAKA,MAAMA,iBAAiB,OACrBC,MAAAA,EACAC,IACA,EAAEC,UAAAA,GAAa,KAAK,EAA4B,GAAG,EAAE,GAErDF,OAAOG,EAAE,CAACC,KAAK,CAACC,4BAAAA,CAAAA,CAAkBC,OAAO,CAAC;QACxCC,MAAAA,EAAQ;AAAC,YAAA,IAAA;AAAM,YAAA,MAAA;AAAQ,YAAA;AAAO,SAAA;QAC9BC,KAAAA,EAAO;AAAEP,YAAAA;AAAG,SAAA;AACZ,QAAA,GAAIC,UAAAA,GAAa;YAAEO,QAAAA,EAAU;gBAAEC,MAAAA,EAAQ;oBAAEH,MAAAA,EAAQ;AAAC,wBAAA;AAAK;AAAC;AAAE;AAAE,SAAA,GAAI;AAClE,KAAA,CAAA;AAEF;;;;;;AAMC,IACD,MAAMI,mBAAAA,GAAsB,OAC1BX,MAAAA,EACAC,EAAAA,EACAW,QAAAA,GAAAA;IAEA,MAAMC,GAAAA,GAAM,MAAMb,MAAAA,CAAOG,EAAE,CAACC,KAAK,CAACC,4BAAAA,CAAAA,CAAkBC,OAAO,CAAC;QAC1DE,KAAAA,EAAO;AAAEP,YAAAA;AAAG,SAAA;QACZQ,QAAAA,EAAU;YAAEC,MAAAA,EAAQ;gBAAEH,MAAAA,EAAQ;AAAC,oBAAA,IAAA;AAAM,oBAAA;AAAO;AAAC;AAAE;AACjD,KAAA,CAAA;;;AAIA,IAAA,OAAOO,kCAAoBD,GAAAA,IAAOD,QAAAA,CAAAA;AACpC,CAAA;AAEA;;;;IAKA,MAAMG,kBAAAA,GAAqB,OAAOf,MAAAA,EAAqBU,MAAAA,GAAAA;IACrD,IAAIA,MAAAA,KAAW,IAAA,IAAQA,MAAAA,KAAWM,SAAAA,EAAW;AAC3C,QAAA;AACF,IAAA;AAEA,IAAA,MAAMC,MAAAA,GAAS,MAAMC,gCAAAA,CAAiBlB,MAAAA,CAAAA,CAAQiB,MAAM,CAAC;QAAEhB,EAAAA,EAAIS;AAAO,KAAA,CAAA;AAElE,IAAA,IAAI,CAACO,MAAAA,EAAQ;QACX,MAAM,IAAIE,cAAAA,CAAOC,eAAe,CAACC,qCAAAA,CAAAA;AACnC,IAAA;AACF,CAAA;AAEA;;;;;;AAMC,IACD,MAAMC,mBAAAA,GAAsB,OAC1BtB,MAAAA,EACAuB,MACAb,MAAAA,EACAc,SAAAA,GAAAA;AAEA,IAAA,MAAMC,OAAAA,GAAmC;AAAEF,QAAAA,IAAAA;AAAMb,QAAAA,MAAAA,EAAQA,MAAAA,IAAU;AAAK,KAAA;AAExE,IAAA,IAAIc,cAAcR,SAAAA,EAAW;AAC3BS,QAAAA,OAAAA,CAAQxB,EAAE,GAAG;YAAEyB,GAAAA,EAAKF;AAAU,SAAA;AAChC,IAAA;AAEA,IAAA,IAAI,MAAMN,gCAAAA,CAAiBlB,MAAAA,CAAAA,CAAQiB,MAAM,CAACQ,OAAAA,CAAAA,EAAU;QAClD,MAAM,IAAIN,cAAAA,CAAOC,eAAe,CAACO,+BAAAA,CAAAA;AACnC,IAAA;AACF,CAAA;AAEA;;;;;AAKC,IACD,MAAMC,4BAAAA,GAA+B,OACnC5B,MAAAA,EACA6B,MAAAA,EACAnB,MAAAA,GAAAA;AAEA,IAAA,IAAIA,WAAW,IAAA,EAAM;AACnB,QAAA;AACF,IAAA;IAEA,MAAMoB,WAAAA,GAAc,MAAM/B,cAAAA,CAAeC,MAAAA,EAAQU,MAAAA,CAAAA;;AAGjD,IAAA,IAAIoB,gBAAgB,IAAA,EAAM;AACxB,QAAA;AACF,IAAA;IAEA,IAAIC,uBAAAA,CAAgBD,aAAaD,MAAAA,CAAAA,EAAS;QACxC,MAAM,IAAIV,cAAAA,CAAOC,eAAe,CAACY,mCAAAA,CAAAA;AACnC,IAAA;AACF,CAAA;AAEA;;;;;;IAOA,MAAMC,YAAAA,GAAe,OAAOjC,MAAAA,EAAqBkC,KAAAA,GAAAA;IAC/C,MAAMC,aAAAA,GAAgB,CAACC,KAAAA,IAAmB;AACxCC,YAAAA,GAAAA,EAAKH,KAAAA,CAAMI,OAAO,CAAC,CAACC,IAAAA,GAAS;AAC3B,oBAAA;AAAE,wBAAA,CAACH,QAAQ;4BAAEI,GAAAA,EAAKD;AAAK;AAAE,qBAAA;AACzB,oBAAA;AAAE,wBAAA,CAACH,QAAQ;4BAAEK,WAAAA,EAAa,CAAA,EAAGF,IAAAA,CAAK,CAAC;AAAE;AAAE;AACxC,iBAAA;SACH,CAAA;AAEA,IAAA,MAAM,CAACG,iBAAAA,EAAmBC,eAAAA,CAAgB,GAAG,MAAMC,OAAAA,CAAQC,GAAG,CAAC;AAC7D7C,QAAAA,MAAAA,CAAOG,EAAE,CAACC,KAAK,CAACC,4BAAAA,CAAAA,CAAkByC,KAAK,CAAC;AAAEtC,YAAAA,KAAAA,EAAO2B,aAAAA,CAAc,MAAA;AAAQ,SAAA,CAAA;AACvEnC,QAAAA,MAAAA,CAAOG,EAAE,CAACC,KAAK,CAAC2C,0BAAAA,CAAAA,CAAgBD,KAAK,CAAC;AAAEtC,YAAAA,KAAAA,EAAO2B,aAAAA,CAAc,YAAA;AAAc,SAAA;AAC5E,KAAA,CAAA;IAED,OAAO;AAAEO,QAAAA,iBAAAA;AAAmBC,QAAAA;AAAgB,KAAA;AAC9C,CAAA;AAEA;;;;;;;;;AASC,IACM,MAAMK,8BAAAA,GAAiC,CAC5ChD,MAAAA,EACAiD,OAAAA,GAAAA;IAEAC,qCAAAA,CAAsBlD,MAAAA,CAAAA;IAEtB,OAAO,OAAO,EACZmD,IAAI,EAGL,GAAA;AACC,QAAA,MAAM,EAAE5B,IAAI,EAAEb,MAAAA,GAAS,IAAI,EAAE,GAAGyC,IAAAA;AAEhCC,QAAAA,iCAAAA,CAAsBpD,MAAAA,EAAQiD,OAAAA,EAASI,mBAAAA,CAAQC,MAAM,EAAEjD,4BAAAA,CAAAA;AAEvD,QAAA,MAAMU,mBAAmBf,MAAAA,EAAQU,MAAAA,CAAAA;QACjC,MAAMY,mBAAAA,CAAoBtB,QAAQuB,IAAAA,EAAMb,MAAAA,CAAAA;AAExC,QAAA,MAAM6C,OAAAA,GAAU,MAAMrC,gCAAAA,CAAiBlB,MAAAA,CAAAA,CAAQsD,MAAM,CAAC;AAAE/B,YAAAA,IAAAA;AAAMb,YAAAA;SAAO,EAAG;AAAE8C,YAAAA,IAAAA,EAAMP,QAAQO;AAAK,SAAA,CAAA;AAE7F,QAAA,OAAOC,QAAAA,CAAG;AAAEC,YAAAA,IAAAA,EAAM,MAAM/C,mBAAAA,CAAoBX,MAAAA,EAAQuD,OAAAA,CAAQtD,EAAE,EAAEsD,OAAAA;AAAS,SAAA,CAAA;AAC3E,IAAA,CAAA;AACF;AAEA;;;;;;;AAOC,IACM,MAAMI,8BAAAA,GAAiC,CAC5C3D,MAAAA,EACAiD,OAAAA,GAAAA;IAEAC,qCAAAA,CAAsBlD,MAAAA,CAAAA;IAEtB,OAAO,OAAO,EACZmD,IAAI,EAGL,GAAA;AACC,QAAA,MAAM,EAAElD,EAAE,EAAEsB,IAAI,EAAE,GAAG4B,IAAAA;AAErBC,QAAAA,iCAAAA,CAAsBpD,MAAAA,EAAQiD,OAAAA,EAASI,mBAAAA,CAAQO,MAAM,EAAEvD,4BAAAA,CAAAA;AAEvD,QAAA,MAAMwB,MAAAA,GAAS,MAAM9B,cAAAA,CAAeC,MAAAA,EAAQC,EAAAA,EAAI;YAAEC,UAAAA,EAAY;AAAK,SAAA,CAAA;AAEnE,QAAA,IAAI2B,WAAW,IAAA,EAAM;YACnB,MAAM,IAAIV,cAAAA,CAAO0C,aAAa,CAACC,8BAAAA,CAAAA;AACjC,QAAA;;;AAIA,QAAA,MAAMC,QAAAA,GAAWlC,MAAAA,CAAOnB,MAAM,EAAET,EAAAA,IAAM,IAAA;QAEtC,MAAMqB,mBAAAA,CAAoBtB,MAAAA,EAAQuB,IAAAA,EAAMwC,QAAAA,EAAU9D,EAAAA,CAAAA;;AAGlD,QAAA,MAAM+D,UAAU,MAAM9C,gCAAAA,CAAiBlB,MAAAA,CAAAA,CAAQ4D,MAAM,CAAC3D,EAAAA,EAAI;AAAEsB,YAAAA;SAAK,EAAG;AAAEiC,YAAAA,IAAAA,EAAMP,QAAQO;AAAK,SAAA,CAAA;AAEzF,QAAA,OAAOC,QAAAA,CAAG;AAAEC,YAAAA,IAAAA,EAAM,MAAM/C,mBAAAA,CAAoBX,MAAAA,EAAQC,EAAAA,EAAI+D,OAAAA,IAAW;AAAE,gBAAA,GAAGnC,MAAM;AAAEN,gBAAAA;AAAK,aAAA;AAAG,SAAA,CAAA;AAC1F,IAAA,CAAA;AACF;AAEA;;;;;;AAMC,IACM,MAAM0C,4BAAAA,GAA+B,CAC1CjE,MAAAA,EACAiD,OAAAA,GAAAA;IAEAC,qCAAAA,CAAsBlD,MAAAA,CAAAA;IAEtB,OAAO,OAAO,EACZmD,IAAI,EAGL,GAAA;AACC,QAAA,MAAM,EAAElD,EAAE,EAAES,MAAM,EAAE,GAAGyC,IAAAA;AAEvBC,QAAAA,iCAAAA,CAAsBpD,MAAAA,EAAQiD,OAAAA,EAASI,mBAAAA,CAAQO,MAAM,EAAEvD,4BAAAA,CAAAA;QAEvD,MAAMwB,MAAAA,GAAS,MAAM9B,cAAAA,CAAeC,MAAAA,EAAQC,EAAAA,CAAAA;AAE5C,QAAA,IAAI4B,WAAW,IAAA,EAAM;YACnB,MAAM,IAAIV,cAAAA,CAAO0C,aAAa,CAACC,8BAAAA,CAAAA;AACjC,QAAA;AAEA,QAAA,MAAM/C,mBAAmBf,MAAAA,EAAQU,MAAAA,CAAAA;QACjC,MAAMkB,4BAAAA,CAA6B5B,QAAQ6B,MAAAA,EAAQnB,MAAAA,CAAAA;;AAGnD,QAAA,MAAMY,mBAAAA,CAAoBtB,MAAAA,EAAQ6B,MAAAA,CAAON,IAAI,EAAEb,MAAAA,EAAQT,EAAAA,CAAAA;AAEvD,QAAA,MAAMiE,QAAQ,MAAMhD,gCAAAA,CAAiBlB,MAAAA,CAAAA,CAAQ4D,MAAM,CACjD3D,EAAAA,EACA;AAAEsB,YAAAA,IAAAA,EAAMM,OAAON,IAAI;AAAEb,YAAAA;SAAO,EAC5B;AAAE8C,YAAAA,IAAAA,EAAMP,QAAQO;AAAK,SAAA,CAAA;AAGvB,QAAA,OAAOC,QAAAA,CAAG;AAAEC,YAAAA,IAAAA,EAAM,MAAM/C,mBAAAA,CAAoBX,MAAAA,EAAQC,EAAAA,EAAIiE,KAAAA,IAASrC,MAAAA;AAAQ,SAAA,CAAA;AAC3E,IAAA,CAAA;AACF;AAEA;;;;;;;;;AASC,IACM,MAAMsC,8BAAAA,GAAiC,CAC5CnE,MAAAA,EACAiD,OAAAA,GAAAA;IAEAC,qCAAAA,CAAsBlD,MAAAA,CAAAA;IAEtB,OAAO,OAAO,EACZmD,IAAI,EAGL,GAAA;AACC,QAAA,MAAM,EAAEiB,GAAG,EAAEC,MAAAA,GAAS,IAAI,EAAE,GAAGlB,IAAAA;AAE/BC,QAAAA,iCAAAA,CAAsBpD,MAAAA,EAAQiD,OAAAA,EAASI,mBAAAA,CAAQO,MAAM,EAAEvD,4BAAAA,CAAAA;QAEvD,MAAMiE,OAAAA,GAAuB,MAAMtE,MAAAA,CAAOG,EAAE,CAACC,KAAK,CAACC,4BAAAA,CAAAA,CAAkBkE,QAAQ,CAAC;YAC5EhE,MAAAA,EAAQ;AAAC,gBAAA,IAAA;AAAM,gBAAA,MAAA;AAAQ,gBAAA;AAAO,aAAA;YAC9BC,KAAAA,EAAO;gBAAEP,EAAAA,EAAI;oBAAEuE,GAAAA,EAAKJ;AAAI;AAAE;AAC5B,SAAA,CAAA;AAEA;;;;;;;;;;;;;;;QAgBA,MAAMK,UAAAA,GAAa,IAAIC,GAAAA,CAAIJ,OAAAA,CAAQK,GAAG,CAAC,CAAC9C,MAAAA,GAAWA,MAAAA,CAAO5B,EAAE,CAAA,CAAA;QAC5D,MAAM2E,aAAAA,GAAgBR,IAAIS,MAAM,CAAC,CAAC5E,EAAAA,GAAOwE,UAAAA,CAAWK,GAAG,CAAC7E,EAAAA,CAAAA,KAAQ,KAAA,CAAA;QAEhE,IAAI2E,aAAAA,CAAcG,MAAM,GAAG,CAAA,EAAG;AAC5B,YAAA,MAAM,IAAI5D,cAAAA,CAAOC,eAAe,CAAC4D,0CAAAA,CAAiCJ,aAAAA,CAAAA,CAAAA;AACpE,QAAA;AAEA,QAAA,MAAMK,UAAUX,OAAAA,CAAQK,GAAG,CAAC,CAAC9C,SAAWf,iCAAAA,CAAoBe,MAAAA,CAAAA,CAAAA;AAE5D,QAAA,IAAIwC,MAAAA,EAAQ;YACV,MAAMa,MAAAA,GAAS,MAAMjD,YAAAA,CACnBjC,MAAAA,EACAsE,OAAAA,CAAQK,GAAG,CAAC,CAAC9C,MAAAA,GAAWA,MAAAA,CAAOU,IAAI,CAAA,CAAA;AAGrC,YAAA,OAAOkB,QAAAA,CAAG;gBAAEY,MAAAA,EAAQ,IAAA;AAAMY,gBAAAA,OAAAA;AAAS,gBAAA,GAAGC;AAAO,aAAA,CAAA;AAC/C,QAAA;AAEA,QAAA,MAAM,EAAExC,iBAAiB,EAAEC,eAAe,EAAE,GAAG,MAAMzB,gCAAAA,CAAiBlB,MAAAA,CAAAA,CAAQmF,WAAW,CACvFb,OAAAA,CAAQK,GAAG,CAAC,CAAC9C,MAAAA,GAAWA,OAAO5B,EAAE,CAAA,CAAA;AAGnC,QAAA,OAAOwD,QAAAA,CAAG;YACRY,MAAAA,EAAQ,KAAA;AACRY,YAAAA,OAAAA;AACAvC,YAAAA,iBAAAA;AACAC,YAAAA;AACF,SAAA,CAAA;AACF,IAAA,CAAA;AACF;;;;;;;"}
1
+ {"version":3,"file":"folder-handlers.js","sources":["../../../../server/src/mcp/handlers/folder-handlers.ts"],"sourcesContent":["import { errors } from '@strapi/utils';\nimport type { Core, Modules } from '@strapi/types';\n\nimport { getFolderService } from '../ambient-instance';\nimport { ACTIONS, FOLDER_MODEL_UID, FILE_MODEL_UID } from '../../constants';\nimport { isFolderOrChild } from '../../controllers/utils/folders';\nimport { assertMediaPermission } from '../permissions';\nimport { sanitizeMediaFolder } from '../sanitizers/sanitize-media';\nimport {\n MCP_NOT_FOUND_FOLDER,\n MCP_FOLDER_NAME_TAKEN,\n MCP_PARENT_FOLDER_NOT_FOUND,\n MCP_FOLDER_MOVE_INTO_SELF,\n MCP_DELETE_FOLDER_UNRESOLVED_IDS,\n} from './constants';\nimport { ok } from '../utils';\n\nimport type { Folder } from '../../types';\n\n// Arg types are type-level only: the MCP SDK validates `args` against the tool's strict Zod\n// input schema before the handler runs, so unknown keys never reach here.\ntype CreateFolderArgs = {\n name: string;\n parent?: number | null;\n};\n\ntype RenameFolderArgs = {\n id: number;\n name: string;\n};\n\ntype MoveFolderArgs = {\n id: number;\n parent: number | null;\n};\n\ntype DeleteFolderArgs = {\n ids: number[];\n dryRun?: boolean;\n};\n\n/** The folder fields the handlers need; `path` drives the cascade and subtree checks. */\ntype FolderRow = Pick<Folder, 'id' | 'name' | 'path'>;\n\n/**\n * `withParent` populates the parent relation in the same round trip. Rename needs the parent id\n * to scope its sibling-uniqueness check, so it would otherwise read the same row twice; the\n * subtree and cascade callers do not, and skip the join.\n */\nconst findFolderById = async (\n strapi: Core.Strapi,\n id: number,\n { withParent = false }: { withParent?: boolean } = {}\n): Promise<(FolderRow & { parent?: { id: number } | null }) | null> =>\n strapi.db.query(FOLDER_MODEL_UID).findOne({\n select: ['id', 'name', 'path'],\n where: { id },\n ...(withParent ? { populate: { parent: { select: ['id'] } } } : {}),\n });\n\n/**\n * Re-reads a folder with its parent populated, so the write tools can echo back the same shape\n * regardless of what the service returned.\n *\n * `folder.update()` resolves the row it wrote without the `parent` relation, and on a pure rename\n * it never touches the join table at all — so the parent has to be read back to be reported.\n */\nconst readFolderForOutput = async (\n strapi: Core.Strapi,\n id: number,\n fallback: Record<string, unknown>\n) => {\n const row = await strapi.db.query(FOLDER_MODEL_UID).findOne({\n where: { id },\n populate: { parent: { select: ['id', 'name'] } },\n });\n\n // The write succeeded, so report it even if the read-back comes back empty rather than\n // failing a completed operation on a follow-up query.\n return sanitizeMediaFolder(row ?? fallback);\n};\n\n/**\n * Replicates the admin folder controller's `parent` check: a named parent must exist.\n *\n * null is the media library root and always valid, so only a numeric id is looked up.\n */\nconst assertParentExists = async (strapi: Core.Strapi, parent: number | null | undefined) => {\n if (parent === null || parent === undefined) {\n return;\n }\n\n const exists = await getFolderService(strapi).exists({ id: parent });\n\n if (!exists) {\n throw new errors.ValidationError(MCP_PARENT_FOLDER_NOT_FOUND);\n }\n};\n\n/**\n * Replicates `is-folder-unique` from the admin folder validation: a folder name must be unique\n * among its siblings.\n *\n * `excludeId` is the folder being renamed — without it a rename to the folder's current name\n * would collide with itself.\n */\nconst assertNameAvailable = async (\n strapi: Core.Strapi,\n name: string,\n parent: number | null,\n excludeId?: number\n) => {\n const filters: Record<string, unknown> = { name, parent: parent ?? null };\n\n if (excludeId !== undefined) {\n filters.id = { $ne: excludeId };\n }\n\n if (await getFolderService(strapi).exists(filters)) {\n throw new errors.ValidationError(MCP_FOLDER_NAME_TAKEN);\n }\n};\n\n/**\n * Replicates `dont-move-inside-self`: a folder cannot become its own descendant.\n *\n * Without this the folder service would rewrite the subtree's materialized paths into a cycle,\n * orphaning every descendant — the tree has no root to walk back to afterwards.\n */\nconst assertNotMovedIntoOwnSubtree = async (\n strapi: Core.Strapi,\n folder: FolderRow,\n parent: number | null\n) => {\n if (parent === null) {\n return;\n }\n\n const destination = await findFolderById(strapi, parent);\n\n // A missing destination is already reported by `assertParentExists`.\n if (destination === null) {\n return;\n }\n\n if (isFolderOrChild(destination, folder)) {\n throw new errors.ValidationError(MCP_FOLDER_MOVE_INTO_SELF);\n }\n};\n\n/**\n * Counts the folders and files a `deleteByIds` of these paths would remove.\n *\n * Mirrors the two `$or` predicates in `folder.deleteByIds` exactly — self path plus the\n * `${path}/` prefix — so the dry-run number is the same set the destructive branch acts on\n * rather than an independent estimate that could drift from it.\n */\nconst countCascade = async (strapi: Core.Strapi, paths: string[]) => {\n const pathPredicate = (field: string) => ({\n $or: paths.flatMap((path) => [\n { [field]: { $eq: path } },\n { [field]: { $startsWith: `${path}/` } },\n ]),\n });\n\n const [totalFolderNumber, totalFileNumber] = await Promise.all([\n strapi.db.query(FOLDER_MODEL_UID).count({ where: pathPredicate('path') }),\n strapi.db.query(FILE_MODEL_UID).count({ where: pathPredicate('folderPath') }),\n ]);\n\n return { totalFolderNumber, totalFileNumber };\n};\n\n/**\n * `media_create_folder` — a new folder, optionally nested.\n *\n * Gated on `plugin::upload.assets.create` and mirrors `POST /upload/folders`: the same\n * permission, the same uniqueness and parent-existence validation, the same `folder.create()`\n * call, and the same `createdBy` attribution from the session user.\n *\n * `create` rather than the `update` the other folder tools use: creating a folder is the one\n * folder operation whose admin route gates on `assets.create`.\n */\nexport const createMediaCreateFolderHandler =\n (strapi: Core.Strapi, context: Modules.MCP.McpHandlerContext) =>\n async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { name, parent = null } = args as CreateFolderArgs;\n\n assertMediaPermission(strapi, context, ACTIONS.create, FOLDER_MODEL_UID);\n\n await assertParentExists(strapi, parent);\n await assertNameAvailable(strapi, name, parent);\n\n const created = await getFolderService(strapi).create({ name, parent }, { user: context.user });\n\n return ok({ data: await readFolderForOutput(strapi, created.id, created) });\n };\n\n/**\n * `media_rename_folder` — changes a folder's name and nothing else.\n *\n * Deliberately does not forward `parent` to `folder.update()`: the service branches on\n * `isUndefined(parent)`, and the name-only branch skips the transaction that rewrites descendant\n * paths. Passing the folder's existing parent would take the move branch and rewrite the whole\n * subtree to compute the identical paths.\n */\nexport const createMediaRenameFolderHandler =\n (strapi: Core.Strapi, context: Modules.MCP.McpHandlerContext) =>\n async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { id, name } = args as RenameFolderArgs;\n\n assertMediaPermission(strapi, context, ACTIONS.update, FOLDER_MODEL_UID);\n\n const folder = await findFolderById(strapi, id, { withParent: true });\n\n if (folder === null) {\n throw new errors.NotFoundError(MCP_NOT_FOUND_FOLDER);\n }\n\n // Siblings are the folders sharing this folder's parent, so uniqueness is checked against\n // the current location — a rename never changes it.\n const parentId = folder.parent?.id ?? null;\n\n await assertNameAvailable(strapi, name, parentId, id);\n\n // `parent` is omitted on purpose — see the note above.\n const renamed = await getFolderService(strapi).update(id, { name }, { user: context.user });\n\n return ok({ data: await readFolderForOutput(strapi, id, renamed ?? { ...folder, name }) });\n };\n\n/**\n * `media_move_folder` — re-parents a folder, carrying its whole subtree with it.\n *\n * `folder.update()` recalculates the materialized `path` of every descendant folder and the\n * `folderPath` of every contained file inside a transaction, so the move is atomic and no\n * separate bookkeeping is needed here.\n */\nexport const createMediaMoveFolderHandler =\n (strapi: Core.Strapi, context: Modules.MCP.McpHandlerContext) =>\n async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { id, parent } = args as MoveFolderArgs;\n\n assertMediaPermission(strapi, context, ACTIONS.update, FOLDER_MODEL_UID);\n\n const folder = await findFolderById(strapi, id);\n\n if (folder === null) {\n throw new errors.NotFoundError(MCP_NOT_FOUND_FOLDER);\n }\n\n await assertParentExists(strapi, parent);\n await assertNotMovedIntoOwnSubtree(strapi, folder, parent);\n\n // A folder keeps its name across a move, so uniqueness must hold in the destination.\n await assertNameAvailable(strapi, folder.name, parent, id);\n\n const moved = await getFolderService(strapi).update(\n id,\n { name: folder.name, parent },\n { user: context.user }\n );\n\n return ok({ data: await readFolderForOutput(strapi, id, moved ?? folder) });\n };\n\n/**\n * `media_delete_folder` — previews or performs a cascading folder deletion.\n *\n * Two branches behind one tool, because the preview and the deletion must agree on what the\n * cascade covers; splitting them across tools would let the two drift apart, and an agent could\n * reach the destructive one without ever seeing a count.\n *\n * `dryRun` defaults to true (see the input schema): omitting the flag previews, and deleting\n * takes an explicit `dryRun: false`.\n */\nexport const createMediaDeleteFolderHandler =\n (strapi: Core.Strapi, context: Modules.MCP.McpHandlerContext) =>\n async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { ids, dryRun = true } = args as DeleteFolderArgs;\n\n assertMediaPermission(strapi, context, ACTIONS.update, FOLDER_MODEL_UID);\n\n const matched: FolderRow[] = await strapi.db.query(FOLDER_MODEL_UID).findMany({\n select: ['id', 'name', 'path'],\n where: { id: { $in: ids } },\n });\n\n /**\n * All-or-nothing: any id that does not resolve to a folder rejects the whole call, before\n * anything is deleted.\n *\n * Folder ids and asset ids are indistinguishable integers, so a list mixing the two is the\n * mistake this tool exists to catch — and deleting the folders that did match while\n * reporting the rest back would be exactly the silent confusion that requirement forbids,\n * with the cascade already gone by the time the agent reads the response.\n *\n * An unresolvable id cannot be diagnosed further from here (an asset id and a deleted\n * folder id look identical), and it does not need to be: either way it is unusable.\n *\n * The dry run rejects on the same rule. A preview that reported a cascade for a request the\n * executing call would refuse is worse than no preview — it is a confirmation an agent\n * cannot act on.\n */\n const matchedIds = new Set(matched.map((folder) => folder.id));\n const unresolvedIds = ids.filter((id) => matchedIds.has(id) === false);\n\n if (unresolvedIds.length > 0) {\n throw new errors.ValidationError(MCP_DELETE_FOLDER_UNRESOLVED_IDS(unresolvedIds));\n }\n\n const folders = matched.map((folder) => sanitizeMediaFolder(folder));\n\n if (dryRun) {\n const counts = await countCascade(\n strapi,\n matched.map((folder) => folder.path)\n );\n\n return ok({ dryRun: true, folders, ...counts });\n }\n\n const { totalFolderNumber, totalFileNumber } = await getFolderService(strapi).deleteByIds(\n matched.map((folder) => folder.id)\n );\n\n return ok({\n dryRun: false,\n folders,\n totalFolderNumber,\n totalFileNumber,\n });\n };\n"],"names":["findFolderById","strapi","id","withParent","db","query","FOLDER_MODEL_UID","findOne","select","where","populate","parent","readFolderForOutput","fallback","row","sanitizeMediaFolder","assertParentExists","undefined","exists","getFolderService","errors","ValidationError","MCP_PARENT_FOLDER_NOT_FOUND","assertNameAvailable","name","excludeId","filters","$ne","MCP_FOLDER_NAME_TAKEN","assertNotMovedIntoOwnSubtree","folder","destination","isFolderOrChild","MCP_FOLDER_MOVE_INTO_SELF","countCascade","paths","pathPredicate","field","$or","flatMap","path","$eq","$startsWith","totalFolderNumber","totalFileNumber","Promise","all","count","FILE_MODEL_UID","createMediaCreateFolderHandler","context","args","assertMediaPermission","ACTIONS","create","created","user","ok","data","createMediaRenameFolderHandler","update","NotFoundError","MCP_NOT_FOUND_FOLDER","parentId","renamed","createMediaMoveFolderHandler","moved","createMediaDeleteFolderHandler","ids","dryRun","matched","findMany","$in","matchedIds","Set","map","unresolvedIds","filter","has","length","MCP_DELETE_FOLDER_UNRESOLVED_IDS","folders","counts","deleteByIds"],"mappings":"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"}
@@ -1,5 +1,5 @@
1
1
  import { errors } from '@strapi/utils';
2
- import { assertAmbientInstance, getFolderService } from '../ambient-instance.mjs';
2
+ import { getFolderService } from '../ambient-instance.mjs';
3
3
  import { FOLDER_MODEL_UID, FILE_MODEL_UID, ACTIONS } from '../../constants.mjs';
4
4
  import { isFolderOrChild } from '../../controllers/utils/folders.mjs';
5
5
  import { assertMediaPermission } from '../permissions.mjs';
@@ -150,9 +150,7 @@ import { ok } from '../utils.mjs';
150
150
  *
151
151
  * `create` rather than the `update` the other folder tools use: creating a folder is the one
152
152
  * folder operation whose admin route gates on `assets.create`.
153
- */ const createMediaCreateFolderHandler = (strapi, context)=>{
154
- assertAmbientInstance(strapi);
155
- return async ({ args })=>{
153
+ */ const createMediaCreateFolderHandler = (strapi, context)=>async ({ args })=>{
156
154
  const { name, parent = null } = args;
157
155
  assertMediaPermission(strapi, context, ACTIONS.create, FOLDER_MODEL_UID);
158
156
  await assertParentExists(strapi, parent);
@@ -167,7 +165,6 @@ import { ok } from '../utils.mjs';
167
165
  data: await readFolderForOutput(strapi, created.id, created)
168
166
  });
169
167
  };
170
- };
171
168
  /**
172
169
  * `media_rename_folder` — changes a folder's name and nothing else.
173
170
  *
@@ -175,9 +172,7 @@ import { ok } from '../utils.mjs';
175
172
  * `isUndefined(parent)`, and the name-only branch skips the transaction that rewrites descendant
176
173
  * paths. Passing the folder's existing parent would take the move branch and rewrite the whole
177
174
  * subtree to compute the identical paths.
178
- */ const createMediaRenameFolderHandler = (strapi, context)=>{
179
- assertAmbientInstance(strapi);
180
- return async ({ args })=>{
175
+ */ const createMediaRenameFolderHandler = (strapi, context)=>async ({ args })=>{
181
176
  const { id, name } = args;
182
177
  assertMediaPermission(strapi, context, ACTIONS.update, FOLDER_MODEL_UID);
183
178
  const folder = await findFolderById(strapi, id, {
@@ -203,16 +198,13 @@ import { ok } from '../utils.mjs';
203
198
  })
204
199
  });
205
200
  };
206
- };
207
201
  /**
208
202
  * `media_move_folder` — re-parents a folder, carrying its whole subtree with it.
209
203
  *
210
204
  * `folder.update()` recalculates the materialized `path` of every descendant folder and the
211
205
  * `folderPath` of every contained file inside a transaction, so the move is atomic and no
212
206
  * separate bookkeeping is needed here.
213
- */ const createMediaMoveFolderHandler = (strapi, context)=>{
214
- assertAmbientInstance(strapi);
215
- return async ({ args })=>{
207
+ */ const createMediaMoveFolderHandler = (strapi, context)=>async ({ args })=>{
216
208
  const { id, parent } = args;
217
209
  assertMediaPermission(strapi, context, ACTIONS.update, FOLDER_MODEL_UID);
218
210
  const folder = await findFolderById(strapi, id);
@@ -233,7 +225,6 @@ import { ok } from '../utils.mjs';
233
225
  data: await readFolderForOutput(strapi, id, moved ?? folder)
234
226
  });
235
227
  };
236
- };
237
228
  /**
238
229
  * `media_delete_folder` — previews or performs a cascading folder deletion.
239
230
  *
@@ -243,9 +234,7 @@ import { ok } from '../utils.mjs';
243
234
  *
244
235
  * `dryRun` defaults to true (see the input schema): omitting the flag previews, and deleting
245
236
  * takes an explicit `dryRun: false`.
246
- */ const createMediaDeleteFolderHandler = (strapi, context)=>{
247
- assertAmbientInstance(strapi);
248
- return async ({ args })=>{
237
+ */ const createMediaDeleteFolderHandler = (strapi, context)=>async ({ args })=>{
249
238
  const { ids, dryRun = true } = args;
250
239
  assertMediaPermission(strapi, context, ACTIONS.update, FOLDER_MODEL_UID);
251
240
  const matched = await strapi.db.query(FOLDER_MODEL_UID).findMany({
@@ -297,7 +286,6 @@ import { ok } from '../utils.mjs';
297
286
  totalFileNumber
298
287
  });
299
288
  };
300
- };
301
289
 
302
290
  export { createMediaCreateFolderHandler, createMediaDeleteFolderHandler, createMediaMoveFolderHandler, createMediaRenameFolderHandler };
303
291
  //# sourceMappingURL=folder-handlers.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"folder-handlers.mjs","sources":["../../../../server/src/mcp/handlers/folder-handlers.ts"],"sourcesContent":["import { errors } from '@strapi/utils';\nimport type { Core, Modules } from '@strapi/types';\n\nimport { assertAmbientInstance, getFolderService } from '../ambient-instance';\nimport { ACTIONS, FOLDER_MODEL_UID, FILE_MODEL_UID } from '../../constants';\nimport { isFolderOrChild } from '../../controllers/utils/folders';\nimport { assertMediaPermission } from '../permissions';\nimport { sanitizeMediaFolder } from '../sanitizers/sanitize-media';\nimport {\n MCP_NOT_FOUND_FOLDER,\n MCP_FOLDER_NAME_TAKEN,\n MCP_PARENT_FOLDER_NOT_FOUND,\n MCP_FOLDER_MOVE_INTO_SELF,\n MCP_DELETE_FOLDER_UNRESOLVED_IDS,\n} from './constants';\nimport { ok } from '../utils';\n\nimport type { Folder } from '../../types';\n\n// Arg types are type-level only: the MCP SDK validates `args` against the tool's strict Zod\n// input schema before the handler runs, so unknown keys never reach here.\ntype CreateFolderArgs = {\n name: string;\n parent?: number | null;\n};\n\ntype RenameFolderArgs = {\n id: number;\n name: string;\n};\n\ntype MoveFolderArgs = {\n id: number;\n parent: number | null;\n};\n\ntype DeleteFolderArgs = {\n ids: number[];\n dryRun?: boolean;\n};\n\n/** The folder fields the handlers need; `path` drives the cascade and subtree checks. */\ntype FolderRow = Pick<Folder, 'id' | 'name' | 'path'>;\n\n/**\n * `withParent` populates the parent relation in the same round trip. Rename needs the parent id\n * to scope its sibling-uniqueness check, so it would otherwise read the same row twice; the\n * subtree and cascade callers do not, and skip the join.\n */\nconst findFolderById = async (\n strapi: Core.Strapi,\n id: number,\n { withParent = false }: { withParent?: boolean } = {}\n): Promise<(FolderRow & { parent?: { id: number } | null }) | null> =>\n strapi.db.query(FOLDER_MODEL_UID).findOne({\n select: ['id', 'name', 'path'],\n where: { id },\n ...(withParent ? { populate: { parent: { select: ['id'] } } } : {}),\n });\n\n/**\n * Re-reads a folder with its parent populated, so the write tools can echo back the same shape\n * regardless of what the service returned.\n *\n * `folder.update()` resolves the row it wrote without the `parent` relation, and on a pure rename\n * it never touches the join table at all — so the parent has to be read back to be reported.\n */\nconst readFolderForOutput = async (\n strapi: Core.Strapi,\n id: number,\n fallback: Record<string, unknown>\n) => {\n const row = await strapi.db.query(FOLDER_MODEL_UID).findOne({\n where: { id },\n populate: { parent: { select: ['id', 'name'] } },\n });\n\n // The write succeeded, so report it even if the read-back comes back empty rather than\n // failing a completed operation on a follow-up query.\n return sanitizeMediaFolder(row ?? fallback);\n};\n\n/**\n * Replicates the admin folder controller's `parent` check: a named parent must exist.\n *\n * null is the media library root and always valid, so only a numeric id is looked up.\n */\nconst assertParentExists = async (strapi: Core.Strapi, parent: number | null | undefined) => {\n if (parent === null || parent === undefined) {\n return;\n }\n\n const exists = await getFolderService(strapi).exists({ id: parent });\n\n if (!exists) {\n throw new errors.ValidationError(MCP_PARENT_FOLDER_NOT_FOUND);\n }\n};\n\n/**\n * Replicates `is-folder-unique` from the admin folder validation: a folder name must be unique\n * among its siblings.\n *\n * `excludeId` is the folder being renamed — without it a rename to the folder's current name\n * would collide with itself.\n */\nconst assertNameAvailable = async (\n strapi: Core.Strapi,\n name: string,\n parent: number | null,\n excludeId?: number\n) => {\n const filters: Record<string, unknown> = { name, parent: parent ?? null };\n\n if (excludeId !== undefined) {\n filters.id = { $ne: excludeId };\n }\n\n if (await getFolderService(strapi).exists(filters)) {\n throw new errors.ValidationError(MCP_FOLDER_NAME_TAKEN);\n }\n};\n\n/**\n * Replicates `dont-move-inside-self`: a folder cannot become its own descendant.\n *\n * Without this the folder service would rewrite the subtree's materialized paths into a cycle,\n * orphaning every descendant — the tree has no root to walk back to afterwards.\n */\nconst assertNotMovedIntoOwnSubtree = async (\n strapi: Core.Strapi,\n folder: FolderRow,\n parent: number | null\n) => {\n if (parent === null) {\n return;\n }\n\n const destination = await findFolderById(strapi, parent);\n\n // A missing destination is already reported by `assertParentExists`.\n if (destination === null) {\n return;\n }\n\n if (isFolderOrChild(destination, folder)) {\n throw new errors.ValidationError(MCP_FOLDER_MOVE_INTO_SELF);\n }\n};\n\n/**\n * Counts the folders and files a `deleteByIds` of these paths would remove.\n *\n * Mirrors the two `$or` predicates in `folder.deleteByIds` exactly — self path plus the\n * `${path}/` prefix — so the dry-run number is the same set the destructive branch acts on\n * rather than an independent estimate that could drift from it.\n */\nconst countCascade = async (strapi: Core.Strapi, paths: string[]) => {\n const pathPredicate = (field: string) => ({\n $or: paths.flatMap((path) => [\n { [field]: { $eq: path } },\n { [field]: { $startsWith: `${path}/` } },\n ]),\n });\n\n const [totalFolderNumber, totalFileNumber] = await Promise.all([\n strapi.db.query(FOLDER_MODEL_UID).count({ where: pathPredicate('path') }),\n strapi.db.query(FILE_MODEL_UID).count({ where: pathPredicate('folderPath') }),\n ]);\n\n return { totalFolderNumber, totalFileNumber };\n};\n\n/**\n * `media_create_folder` — a new folder, optionally nested.\n *\n * Gated on `plugin::upload.assets.create` and mirrors `POST /upload/folders`: the same\n * permission, the same uniqueness and parent-existence validation, the same `folder.create()`\n * call, and the same `createdBy` attribution from the session user.\n *\n * `create` rather than the `update` the other folder tools use: creating a folder is the one\n * folder operation whose admin route gates on `assets.create`.\n */\nexport const createMediaCreateFolderHandler = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext\n) => {\n assertAmbientInstance(strapi);\n\n return async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { name, parent = null } = args as CreateFolderArgs;\n\n assertMediaPermission(strapi, context, ACTIONS.create, FOLDER_MODEL_UID);\n\n await assertParentExists(strapi, parent);\n await assertNameAvailable(strapi, name, parent);\n\n const created = await getFolderService(strapi).create({ name, parent }, { user: context.user });\n\n return ok({ data: await readFolderForOutput(strapi, created.id, created) });\n };\n};\n\n/**\n * `media_rename_folder` — changes a folder's name and nothing else.\n *\n * Deliberately does not forward `parent` to `folder.update()`: the service branches on\n * `isUndefined(parent)`, and the name-only branch skips the transaction that rewrites descendant\n * paths. Passing the folder's existing parent would take the move branch and rewrite the whole\n * subtree to compute the identical paths.\n */\nexport const createMediaRenameFolderHandler = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext\n) => {\n assertAmbientInstance(strapi);\n\n return async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { id, name } = args as RenameFolderArgs;\n\n assertMediaPermission(strapi, context, ACTIONS.update, FOLDER_MODEL_UID);\n\n const folder = await findFolderById(strapi, id, { withParent: true });\n\n if (folder === null) {\n throw new errors.NotFoundError(MCP_NOT_FOUND_FOLDER);\n }\n\n // Siblings are the folders sharing this folder's parent, so uniqueness is checked against\n // the current location — a rename never changes it.\n const parentId = folder.parent?.id ?? null;\n\n await assertNameAvailable(strapi, name, parentId, id);\n\n // `parent` is omitted on purpose — see the note above.\n const renamed = await getFolderService(strapi).update(id, { name }, { user: context.user });\n\n return ok({ data: await readFolderForOutput(strapi, id, renamed ?? { ...folder, name }) });\n };\n};\n\n/**\n * `media_move_folder` — re-parents a folder, carrying its whole subtree with it.\n *\n * `folder.update()` recalculates the materialized `path` of every descendant folder and the\n * `folderPath` of every contained file inside a transaction, so the move is atomic and no\n * separate bookkeeping is needed here.\n */\nexport const createMediaMoveFolderHandler = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext\n) => {\n assertAmbientInstance(strapi);\n\n return async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { id, parent } = args as MoveFolderArgs;\n\n assertMediaPermission(strapi, context, ACTIONS.update, FOLDER_MODEL_UID);\n\n const folder = await findFolderById(strapi, id);\n\n if (folder === null) {\n throw new errors.NotFoundError(MCP_NOT_FOUND_FOLDER);\n }\n\n await assertParentExists(strapi, parent);\n await assertNotMovedIntoOwnSubtree(strapi, folder, parent);\n\n // A folder keeps its name across a move, so uniqueness must hold in the destination.\n await assertNameAvailable(strapi, folder.name, parent, id);\n\n const moved = await getFolderService(strapi).update(\n id,\n { name: folder.name, parent },\n { user: context.user }\n );\n\n return ok({ data: await readFolderForOutput(strapi, id, moved ?? folder) });\n };\n};\n\n/**\n * `media_delete_folder` — previews or performs a cascading folder deletion.\n *\n * Two branches behind one tool, because the preview and the deletion must agree on what the\n * cascade covers; splitting them across tools would let the two drift apart, and an agent could\n * reach the destructive one without ever seeing a count.\n *\n * `dryRun` defaults to true (see the input schema): omitting the flag previews, and deleting\n * takes an explicit `dryRun: false`.\n */\nexport const createMediaDeleteFolderHandler = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext\n) => {\n assertAmbientInstance(strapi);\n\n return async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { ids, dryRun = true } = args as DeleteFolderArgs;\n\n assertMediaPermission(strapi, context, ACTIONS.update, FOLDER_MODEL_UID);\n\n const matched: FolderRow[] = await strapi.db.query(FOLDER_MODEL_UID).findMany({\n select: ['id', 'name', 'path'],\n where: { id: { $in: ids } },\n });\n\n /**\n * All-or-nothing: any id that does not resolve to a folder rejects the whole call, before\n * anything is deleted.\n *\n * Folder ids and asset ids are indistinguishable integers, so a list mixing the two is the\n * mistake this tool exists to catch — and deleting the folders that did match while\n * reporting the rest back would be exactly the silent confusion that requirement forbids,\n * with the cascade already gone by the time the agent reads the response.\n *\n * An unresolvable id cannot be diagnosed further from here (an asset id and a deleted\n * folder id look identical), and it does not need to be: either way it is unusable.\n *\n * The dry run rejects on the same rule. A preview that reported a cascade for a request the\n * executing call would refuse is worse than no preview — it is a confirmation an agent\n * cannot act on.\n */\n const matchedIds = new Set(matched.map((folder) => folder.id));\n const unresolvedIds = ids.filter((id) => matchedIds.has(id) === false);\n\n if (unresolvedIds.length > 0) {\n throw new errors.ValidationError(MCP_DELETE_FOLDER_UNRESOLVED_IDS(unresolvedIds));\n }\n\n const folders = matched.map((folder) => sanitizeMediaFolder(folder));\n\n if (dryRun) {\n const counts = await countCascade(\n strapi,\n matched.map((folder) => folder.path)\n );\n\n return ok({ dryRun: true, folders, ...counts });\n }\n\n const { totalFolderNumber, totalFileNumber } = await getFolderService(strapi).deleteByIds(\n matched.map((folder) => folder.id)\n );\n\n return ok({\n dryRun: false,\n folders,\n totalFolderNumber,\n totalFileNumber,\n });\n };\n};\n"],"names":["findFolderById","strapi","id","withParent","db","query","FOLDER_MODEL_UID","findOne","select","where","populate","parent","readFolderForOutput","fallback","row","sanitizeMediaFolder","assertParentExists","undefined","exists","getFolderService","errors","ValidationError","MCP_PARENT_FOLDER_NOT_FOUND","assertNameAvailable","name","excludeId","filters","$ne","MCP_FOLDER_NAME_TAKEN","assertNotMovedIntoOwnSubtree","folder","destination","isFolderOrChild","MCP_FOLDER_MOVE_INTO_SELF","countCascade","paths","pathPredicate","field","$or","flatMap","path","$eq","$startsWith","totalFolderNumber","totalFileNumber","Promise","all","count","FILE_MODEL_UID","createMediaCreateFolderHandler","context","assertAmbientInstance","args","assertMediaPermission","ACTIONS","create","created","user","ok","data","createMediaRenameFolderHandler","update","NotFoundError","MCP_NOT_FOUND_FOLDER","parentId","renamed","createMediaMoveFolderHandler","moved","createMediaDeleteFolderHandler","ids","dryRun","matched","findMany","$in","matchedIds","Set","map","unresolvedIds","filter","has","length","MCP_DELETE_FOLDER_UNRESOLVED_IDS","folders","counts","deleteByIds"],"mappings":"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"}
1
+ {"version":3,"file":"folder-handlers.mjs","sources":["../../../../server/src/mcp/handlers/folder-handlers.ts"],"sourcesContent":["import { errors } from '@strapi/utils';\nimport type { Core, Modules } from '@strapi/types';\n\nimport { getFolderService } from '../ambient-instance';\nimport { ACTIONS, FOLDER_MODEL_UID, FILE_MODEL_UID } from '../../constants';\nimport { isFolderOrChild } from '../../controllers/utils/folders';\nimport { assertMediaPermission } from '../permissions';\nimport { sanitizeMediaFolder } from '../sanitizers/sanitize-media';\nimport {\n MCP_NOT_FOUND_FOLDER,\n MCP_FOLDER_NAME_TAKEN,\n MCP_PARENT_FOLDER_NOT_FOUND,\n MCP_FOLDER_MOVE_INTO_SELF,\n MCP_DELETE_FOLDER_UNRESOLVED_IDS,\n} from './constants';\nimport { ok } from '../utils';\n\nimport type { Folder } from '../../types';\n\n// Arg types are type-level only: the MCP SDK validates `args` against the tool's strict Zod\n// input schema before the handler runs, so unknown keys never reach here.\ntype CreateFolderArgs = {\n name: string;\n parent?: number | null;\n};\n\ntype RenameFolderArgs = {\n id: number;\n name: string;\n};\n\ntype MoveFolderArgs = {\n id: number;\n parent: number | null;\n};\n\ntype DeleteFolderArgs = {\n ids: number[];\n dryRun?: boolean;\n};\n\n/** The folder fields the handlers need; `path` drives the cascade and subtree checks. */\ntype FolderRow = Pick<Folder, 'id' | 'name' | 'path'>;\n\n/**\n * `withParent` populates the parent relation in the same round trip. Rename needs the parent id\n * to scope its sibling-uniqueness check, so it would otherwise read the same row twice; the\n * subtree and cascade callers do not, and skip the join.\n */\nconst findFolderById = async (\n strapi: Core.Strapi,\n id: number,\n { withParent = false }: { withParent?: boolean } = {}\n): Promise<(FolderRow & { parent?: { id: number } | null }) | null> =>\n strapi.db.query(FOLDER_MODEL_UID).findOne({\n select: ['id', 'name', 'path'],\n where: { id },\n ...(withParent ? { populate: { parent: { select: ['id'] } } } : {}),\n });\n\n/**\n * Re-reads a folder with its parent populated, so the write tools can echo back the same shape\n * regardless of what the service returned.\n *\n * `folder.update()` resolves the row it wrote without the `parent` relation, and on a pure rename\n * it never touches the join table at all — so the parent has to be read back to be reported.\n */\nconst readFolderForOutput = async (\n strapi: Core.Strapi,\n id: number,\n fallback: Record<string, unknown>\n) => {\n const row = await strapi.db.query(FOLDER_MODEL_UID).findOne({\n where: { id },\n populate: { parent: { select: ['id', 'name'] } },\n });\n\n // The write succeeded, so report it even if the read-back comes back empty rather than\n // failing a completed operation on a follow-up query.\n return sanitizeMediaFolder(row ?? fallback);\n};\n\n/**\n * Replicates the admin folder controller's `parent` check: a named parent must exist.\n *\n * null is the media library root and always valid, so only a numeric id is looked up.\n */\nconst assertParentExists = async (strapi: Core.Strapi, parent: number | null | undefined) => {\n if (parent === null || parent === undefined) {\n return;\n }\n\n const exists = await getFolderService(strapi).exists({ id: parent });\n\n if (!exists) {\n throw new errors.ValidationError(MCP_PARENT_FOLDER_NOT_FOUND);\n }\n};\n\n/**\n * Replicates `is-folder-unique` from the admin folder validation: a folder name must be unique\n * among its siblings.\n *\n * `excludeId` is the folder being renamed — without it a rename to the folder's current name\n * would collide with itself.\n */\nconst assertNameAvailable = async (\n strapi: Core.Strapi,\n name: string,\n parent: number | null,\n excludeId?: number\n) => {\n const filters: Record<string, unknown> = { name, parent: parent ?? null };\n\n if (excludeId !== undefined) {\n filters.id = { $ne: excludeId };\n }\n\n if (await getFolderService(strapi).exists(filters)) {\n throw new errors.ValidationError(MCP_FOLDER_NAME_TAKEN);\n }\n};\n\n/**\n * Replicates `dont-move-inside-self`: a folder cannot become its own descendant.\n *\n * Without this the folder service would rewrite the subtree's materialized paths into a cycle,\n * orphaning every descendant — the tree has no root to walk back to afterwards.\n */\nconst assertNotMovedIntoOwnSubtree = async (\n strapi: Core.Strapi,\n folder: FolderRow,\n parent: number | null\n) => {\n if (parent === null) {\n return;\n }\n\n const destination = await findFolderById(strapi, parent);\n\n // A missing destination is already reported by `assertParentExists`.\n if (destination === null) {\n return;\n }\n\n if (isFolderOrChild(destination, folder)) {\n throw new errors.ValidationError(MCP_FOLDER_MOVE_INTO_SELF);\n }\n};\n\n/**\n * Counts the folders and files a `deleteByIds` of these paths would remove.\n *\n * Mirrors the two `$or` predicates in `folder.deleteByIds` exactly — self path plus the\n * `${path}/` prefix — so the dry-run number is the same set the destructive branch acts on\n * rather than an independent estimate that could drift from it.\n */\nconst countCascade = async (strapi: Core.Strapi, paths: string[]) => {\n const pathPredicate = (field: string) => ({\n $or: paths.flatMap((path) => [\n { [field]: { $eq: path } },\n { [field]: { $startsWith: `${path}/` } },\n ]),\n });\n\n const [totalFolderNumber, totalFileNumber] = await Promise.all([\n strapi.db.query(FOLDER_MODEL_UID).count({ where: pathPredicate('path') }),\n strapi.db.query(FILE_MODEL_UID).count({ where: pathPredicate('folderPath') }),\n ]);\n\n return { totalFolderNumber, totalFileNumber };\n};\n\n/**\n * `media_create_folder` — a new folder, optionally nested.\n *\n * Gated on `plugin::upload.assets.create` and mirrors `POST /upload/folders`: the same\n * permission, the same uniqueness and parent-existence validation, the same `folder.create()`\n * call, and the same `createdBy` attribution from the session user.\n *\n * `create` rather than the `update` the other folder tools use: creating a folder is the one\n * folder operation whose admin route gates on `assets.create`.\n */\nexport const createMediaCreateFolderHandler =\n (strapi: Core.Strapi, context: Modules.MCP.McpHandlerContext) =>\n async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { name, parent = null } = args as CreateFolderArgs;\n\n assertMediaPermission(strapi, context, ACTIONS.create, FOLDER_MODEL_UID);\n\n await assertParentExists(strapi, parent);\n await assertNameAvailable(strapi, name, parent);\n\n const created = await getFolderService(strapi).create({ name, parent }, { user: context.user });\n\n return ok({ data: await readFolderForOutput(strapi, created.id, created) });\n };\n\n/**\n * `media_rename_folder` — changes a folder's name and nothing else.\n *\n * Deliberately does not forward `parent` to `folder.update()`: the service branches on\n * `isUndefined(parent)`, and the name-only branch skips the transaction that rewrites descendant\n * paths. Passing the folder's existing parent would take the move branch and rewrite the whole\n * subtree to compute the identical paths.\n */\nexport const createMediaRenameFolderHandler =\n (strapi: Core.Strapi, context: Modules.MCP.McpHandlerContext) =>\n async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { id, name } = args as RenameFolderArgs;\n\n assertMediaPermission(strapi, context, ACTIONS.update, FOLDER_MODEL_UID);\n\n const folder = await findFolderById(strapi, id, { withParent: true });\n\n if (folder === null) {\n throw new errors.NotFoundError(MCP_NOT_FOUND_FOLDER);\n }\n\n // Siblings are the folders sharing this folder's parent, so uniqueness is checked against\n // the current location — a rename never changes it.\n const parentId = folder.parent?.id ?? null;\n\n await assertNameAvailable(strapi, name, parentId, id);\n\n // `parent` is omitted on purpose — see the note above.\n const renamed = await getFolderService(strapi).update(id, { name }, { user: context.user });\n\n return ok({ data: await readFolderForOutput(strapi, id, renamed ?? { ...folder, name }) });\n };\n\n/**\n * `media_move_folder` — re-parents a folder, carrying its whole subtree with it.\n *\n * `folder.update()` recalculates the materialized `path` of every descendant folder and the\n * `folderPath` of every contained file inside a transaction, so the move is atomic and no\n * separate bookkeeping is needed here.\n */\nexport const createMediaMoveFolderHandler =\n (strapi: Core.Strapi, context: Modules.MCP.McpHandlerContext) =>\n async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { id, parent } = args as MoveFolderArgs;\n\n assertMediaPermission(strapi, context, ACTIONS.update, FOLDER_MODEL_UID);\n\n const folder = await findFolderById(strapi, id);\n\n if (folder === null) {\n throw new errors.NotFoundError(MCP_NOT_FOUND_FOLDER);\n }\n\n await assertParentExists(strapi, parent);\n await assertNotMovedIntoOwnSubtree(strapi, folder, parent);\n\n // A folder keeps its name across a move, so uniqueness must hold in the destination.\n await assertNameAvailable(strapi, folder.name, parent, id);\n\n const moved = await getFolderService(strapi).update(\n id,\n { name: folder.name, parent },\n { user: context.user }\n );\n\n return ok({ data: await readFolderForOutput(strapi, id, moved ?? folder) });\n };\n\n/**\n * `media_delete_folder` — previews or performs a cascading folder deletion.\n *\n * Two branches behind one tool, because the preview and the deletion must agree on what the\n * cascade covers; splitting them across tools would let the two drift apart, and an agent could\n * reach the destructive one without ever seeing a count.\n *\n * `dryRun` defaults to true (see the input schema): omitting the flag previews, and deleting\n * takes an explicit `dryRun: false`.\n */\nexport const createMediaDeleteFolderHandler =\n (strapi: Core.Strapi, context: Modules.MCP.McpHandlerContext) =>\n async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { ids, dryRun = true } = args as DeleteFolderArgs;\n\n assertMediaPermission(strapi, context, ACTIONS.update, FOLDER_MODEL_UID);\n\n const matched: FolderRow[] = await strapi.db.query(FOLDER_MODEL_UID).findMany({\n select: ['id', 'name', 'path'],\n where: { id: { $in: ids } },\n });\n\n /**\n * All-or-nothing: any id that does not resolve to a folder rejects the whole call, before\n * anything is deleted.\n *\n * Folder ids and asset ids are indistinguishable integers, so a list mixing the two is the\n * mistake this tool exists to catch — and deleting the folders that did match while\n * reporting the rest back would be exactly the silent confusion that requirement forbids,\n * with the cascade already gone by the time the agent reads the response.\n *\n * An unresolvable id cannot be diagnosed further from here (an asset id and a deleted\n * folder id look identical), and it does not need to be: either way it is unusable.\n *\n * The dry run rejects on the same rule. A preview that reported a cascade for a request the\n * executing call would refuse is worse than no preview — it is a confirmation an agent\n * cannot act on.\n */\n const matchedIds = new Set(matched.map((folder) => folder.id));\n const unresolvedIds = ids.filter((id) => matchedIds.has(id) === false);\n\n if (unresolvedIds.length > 0) {\n throw new errors.ValidationError(MCP_DELETE_FOLDER_UNRESOLVED_IDS(unresolvedIds));\n }\n\n const folders = matched.map((folder) => sanitizeMediaFolder(folder));\n\n if (dryRun) {\n const counts = await countCascade(\n strapi,\n matched.map((folder) => folder.path)\n );\n\n return ok({ dryRun: true, folders, ...counts });\n }\n\n const { totalFolderNumber, totalFileNumber } = await getFolderService(strapi).deleteByIds(\n matched.map((folder) => folder.id)\n );\n\n return ok({\n dryRun: false,\n folders,\n totalFolderNumber,\n totalFileNumber,\n });\n };\n"],"names":["findFolderById","strapi","id","withParent","db","query","FOLDER_MODEL_UID","findOne","select","where","populate","parent","readFolderForOutput","fallback","row","sanitizeMediaFolder","assertParentExists","undefined","exists","getFolderService","errors","ValidationError","MCP_PARENT_FOLDER_NOT_FOUND","assertNameAvailable","name","excludeId","filters","$ne","MCP_FOLDER_NAME_TAKEN","assertNotMovedIntoOwnSubtree","folder","destination","isFolderOrChild","MCP_FOLDER_MOVE_INTO_SELF","countCascade","paths","pathPredicate","field","$or","flatMap","path","$eq","$startsWith","totalFolderNumber","totalFileNumber","Promise","all","count","FILE_MODEL_UID","createMediaCreateFolderHandler","context","args","assertMediaPermission","ACTIONS","create","created","user","ok","data","createMediaRenameFolderHandler","update","NotFoundError","MCP_NOT_FOUND_FOLDER","parentId","renamed","createMediaMoveFolderHandler","moved","createMediaDeleteFolderHandler","ids","dryRun","matched","findMany","$in","matchedIds","Set","map","unresolvedIds","filter","has","length","MCP_DELETE_FOLDER_UNRESOLVED_IDS","folders","counts","deleteByIds"],"mappings":"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"}
@@ -48,9 +48,7 @@ const DEFAULT_SORT = 'createdAt:DESC';
48
48
  * Permission conditions are applied through the permissions manager
49
49
  * (`addPermissionsQueryTo`) so a token restricted by a condition — e.g. own-assets-only —
50
50
  * sees the same subset it would through the admin API.
51
- */ const createMediaListAssetsHandler = (strapi, context)=>{
52
- ambientInstance.assertAmbientInstance(strapi);
53
- return async ({ args })=>{
51
+ */ const createMediaListAssetsHandler = (strapi, context)=>async ({ args })=>{
54
52
  const { folderId, mime, name, page, pageSize, sort } = args;
55
53
  const pm = permissions.assertMediaPermission(strapi, context, constants.ACTIONS.read, constants.FILE_MODEL_UID);
56
54
  const query = await pm.addPermissionsQueryTo({
@@ -77,7 +75,6 @@ const DEFAULT_SORT = 'createdAt:DESC';
77
75
  pagination
78
76
  });
79
77
  };
80
- };
81
78
  /**
82
79
  * `media_get_asset` — a single asset by numeric id.
83
80
  *
@@ -86,9 +83,7 @@ const DEFAULT_SORT = 'createdAt:DESC';
86
83
  * before building the CASL subject, which is what the admin conditions read. Checking a subject
87
84
  * without those fields would deny a conditioned grant its own assets, and would disagree with
88
85
  * `media_list_assets`, where the condition is pushed into the query instead.
89
- */ const createMediaGetAssetHandler = (strapi, context)=>{
90
- ambientInstance.assertAmbientInstance(strapi);
91
- return async ({ args })=>{
86
+ */ const createMediaGetAssetHandler = (strapi, context)=>async ({ args })=>{
92
87
  const { id } = args;
93
88
  // Model-level gate first, so a token without upload read at all is refused before a lookup.
94
89
  permissions.assertMediaPermission(strapi, context, constants.ACTIONS.read, constants.FILE_MODEL_UID);
@@ -107,7 +102,6 @@ const DEFAULT_SORT = 'createdAt:DESC';
107
102
  data: sanitizeMedia.sanitizeMediaAsset(asset)
108
103
  });
109
104
  };
110
- };
111
105
  /**
112
106
  * `media_list_folders` — the nested folder structure, reusing `folder.getStructure()`.
113
107
  *
@@ -115,18 +109,16 @@ const DEFAULT_SORT = 'createdAt:DESC';
115
109
  * filtering, so this is gated on the model-level read permission only — matching
116
110
  * `GET /upload/folder-structure` in the admin API.
117
111
  *
118
- * The factory asserts the handler's instance is the ambient `global.strapi` the folder service
119
- * actually queries — see `assertAmbientInstance` for why.
120
- */ const createMediaListFoldersHandler = (strapi, context)=>{
121
- ambientInstance.assertAmbientInstance(strapi);
122
- return async ()=>{
112
+ * The folder service queries the ambient `global.strapi` regardless of the instance passed to
113
+ * `getService`; `buildUploadMcpToolDefinitions` asserts the two agree before this handler is
114
+ * ever constructed — see `assertAmbientInstance` for why.
115
+ */ const createMediaListFoldersHandler = (strapi, context)=>async ()=>{
123
116
  permissions.assertMediaPermission(strapi, context, constants.ACTIONS.read, constants.FOLDER_MODEL_UID);
124
117
  const structure = await ambientInstance.getFolderService(strapi).getStructure();
125
118
  return utils.ok({
126
119
  data: sanitizeMedia.sanitizeMediaFolderTree(structure)
127
120
  });
128
121
  };
129
- };
130
122
 
131
123
  exports.createMediaGetAssetHandler = createMediaGetAssetHandler;
132
124
  exports.createMediaListAssetsHandler = createMediaListAssetsHandler;
@@ -1 +1 @@
1
- {"version":3,"file":"read-handlers.js","sources":["../../../../server/src/mcp/handlers/read-handlers.ts"],"sourcesContent":["import { errors } from '@strapi/utils';\nimport type { Core, Modules } from '@strapi/types';\n\nimport { getService } from '../../utils';\nimport { assertAmbientInstance, getFolderService } from '../ambient-instance';\nimport { ACTIONS, FILE_MODEL_UID, FOLDER_MODEL_UID } from '../../constants';\nimport { assertMediaPermission } from '../permissions';\nimport { findEntityAndCheckPermissions } from '../../controllers/utils/find-entity-and-check-permissions';\nimport { sanitizeMediaAsset, sanitizeMediaFolderTree } from '../sanitizers/sanitize-media';\nimport { MCP_NOT_FOUND_ASSET } from './constants';\nimport { ok } from '../utils';\n\nconst DEFAULT_SORT = 'createdAt:DESC';\n\n// Arg types are type-level only: the MCP SDK validates `args` against the tool's Zod input\n// schema before the handler runs, so handlers accept the erased `Record<string, unknown>` the\n// registry hands them and narrow it here.\ntype ListMediaArgs = {\n folderId?: number | null;\n mime?: string;\n name?: string;\n page?: number;\n pageSize?: number;\n sort?: string;\n};\n\ntype GetMediaArgs = {\n id: number;\n};\n\n/**\n * Builds the `filters` clause for `media_list_assets`.\n *\n * `folderId: null` is meaningfully different from an omitted `folderId`: null means \"assets at\n * the media library root\" (no folder relation), while omitting it means \"any folder\".\n *\n * `mime` accepts both a full type and a bare prefix. An exact match on \"image\" would never hit,\n * so a value without a slash is matched as a prefix (\"image\" → every \"image/*\").\n */\nconst buildAssetFilters = (args: ListMediaArgs): Record<string, unknown> => {\n const filters: Record<string, unknown> = {};\n\n if (args.folderId === null) {\n filters.folder = null;\n } else if (args.folderId !== undefined) {\n filters.folder = { id: args.folderId };\n }\n\n if (args.mime !== undefined) {\n filters.mime = args.mime.includes('/')\n ? { $eqi: args.mime }\n : { $startsWithi: `${args.mime}/` };\n }\n\n if (args.name !== undefined) {\n filters.name = { $containsi: args.name };\n }\n\n return filters;\n};\n\n/**\n * `media_list_assets` — paginated, filtered listing of media files.\n *\n * Permission conditions are applied through the permissions manager\n * (`addPermissionsQueryTo`) so a token restricted by a condition — e.g. own-assets-only —\n * sees the same subset it would through the admin API.\n */\nexport const createMediaListAssetsHandler = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext\n) => {\n assertAmbientInstance(strapi);\n\n return async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { folderId, mime, name, page, pageSize, sort } = args as ListMediaArgs;\n const pm = assertMediaPermission(strapi, context, ACTIONS.read, FILE_MODEL_UID);\n\n const query = await pm.addPermissionsQueryTo({\n filters: buildAssetFilters({ folderId, mime, name }),\n sort: sort ?? DEFAULT_SORT,\n page: page ?? 1,\n pageSize: pageSize ?? 25,\n populate: { folder: { fields: ['id', 'name'] } },\n });\n\n const { results, pagination } = await getService('upload', strapi).findPage(query);\n\n return ok({\n results: results.map(sanitizeMediaAsset),\n pagination,\n });\n };\n};\n\n/**\n * `media_get_asset` — a single asset by numeric id.\n *\n * Row-level permission *conditions* are enforced by `findEntityAndCheckPermissions`, the same\n * helper the admin `findOne` controller uses: it populates `createdBy` and the creator's roles\n * before building the CASL subject, which is what the admin conditions read. Checking a subject\n * without those fields would deny a conditioned grant its own assets, and would disagree with\n * `media_list_assets`, where the condition is pushed into the query instead.\n */\nexport const createMediaGetAssetHandler = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext\n) => {\n assertAmbientInstance(strapi);\n\n return async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { id } = args as GetMediaArgs;\n\n // Model-level gate first, so a token without upload read at all is refused before a lookup.\n assertMediaPermission(strapi, context, ACTIONS.read, FILE_MODEL_UID);\n\n let asset;\n try {\n ({ file: asset } = await findEntityAndCheckPermissions(\n context.userAbility,\n ACTIONS.read,\n FILE_MODEL_UID,\n id,\n strapi\n ));\n } catch (error) {\n // The helper is written for HTTP, where a bare NotFoundError is enough. MCP answers an\n // agent, so restate it with the message the other media tools use.\n if (error instanceof errors.NotFoundError) {\n throw new errors.NotFoundError(MCP_NOT_FOUND_ASSET);\n }\n throw error;\n }\n\n return ok({ data: sanitizeMediaAsset(asset) });\n };\n};\n\n/**\n * `media_list_folders` — the nested folder structure, reusing `folder.getStructure()`.\n *\n * `getStructure()` returns the whole tree in one query and has no permission-condition\n * filtering, so this is gated on the model-level read permission only — matching\n * `GET /upload/folder-structure` in the admin API.\n *\n * The factory asserts the handler's instance is the ambient `global.strapi` the folder service\n * actually queries — see `assertAmbientInstance` for why.\n */\nexport const createMediaListFoldersHandler = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext\n) => {\n assertAmbientInstance(strapi);\n\n return async (): Promise<Modules.MCP.McpToolHandlerReturn> => {\n assertMediaPermission(strapi, context, ACTIONS.read, FOLDER_MODEL_UID);\n\n const structure = await getFolderService(strapi).getStructure();\n\n return ok({ data: sanitizeMediaFolderTree(structure) });\n };\n};\n"],"names":["DEFAULT_SORT","buildAssetFilters","args","filters","folderId","folder","undefined","id","mime","includes","$eqi","$startsWithi","name","$containsi","createMediaListAssetsHandler","strapi","context","assertAmbientInstance","page","pageSize","sort","pm","assertMediaPermission","ACTIONS","read","FILE_MODEL_UID","query","addPermissionsQueryTo","populate","fields","results","pagination","getService","findPage","ok","map","sanitizeMediaAsset","createMediaGetAssetHandler","asset","file","findEntityAndCheckPermissions","userAbility","error","errors","NotFoundError","MCP_NOT_FOUND_ASSET","data","createMediaListFoldersHandler","FOLDER_MODEL_UID","structure","getFolderService","getStructure","sanitizeMediaFolderTree"],"mappings":"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"}
1
+ {"version":3,"file":"read-handlers.js","sources":["../../../../server/src/mcp/handlers/read-handlers.ts"],"sourcesContent":["import { errors } from '@strapi/utils';\nimport type { Core, Modules } from '@strapi/types';\n\nimport { getService } from '../../utils';\nimport { getFolderService } from '../ambient-instance';\nimport { ACTIONS, FILE_MODEL_UID, FOLDER_MODEL_UID } from '../../constants';\nimport { assertMediaPermission } from '../permissions';\nimport { findEntityAndCheckPermissions } from '../../controllers/utils/find-entity-and-check-permissions';\nimport { sanitizeMediaAsset, sanitizeMediaFolderTree } from '../sanitizers/sanitize-media';\nimport { MCP_NOT_FOUND_ASSET } from './constants';\nimport { ok } from '../utils';\n\nconst DEFAULT_SORT = 'createdAt:DESC';\n\n// Arg types are type-level only: the MCP SDK validates `args` against the tool's Zod input\n// schema before the handler runs, so handlers accept the erased `Record<string, unknown>` the\n// registry hands them and narrow it here.\ntype ListMediaArgs = {\n folderId?: number | null;\n mime?: string;\n name?: string;\n page?: number;\n pageSize?: number;\n sort?: string;\n};\n\ntype GetMediaArgs = {\n id: number;\n};\n\n/**\n * Builds the `filters` clause for `media_list_assets`.\n *\n * `folderId: null` is meaningfully different from an omitted `folderId`: null means \"assets at\n * the media library root\" (no folder relation), while omitting it means \"any folder\".\n *\n * `mime` accepts both a full type and a bare prefix. An exact match on \"image\" would never hit,\n * so a value without a slash is matched as a prefix (\"image\" → every \"image/*\").\n */\nconst buildAssetFilters = (args: ListMediaArgs): Record<string, unknown> => {\n const filters: Record<string, unknown> = {};\n\n if (args.folderId === null) {\n filters.folder = null;\n } else if (args.folderId !== undefined) {\n filters.folder = { id: args.folderId };\n }\n\n if (args.mime !== undefined) {\n filters.mime = args.mime.includes('/')\n ? { $eqi: args.mime }\n : { $startsWithi: `${args.mime}/` };\n }\n\n if (args.name !== undefined) {\n filters.name = { $containsi: args.name };\n }\n\n return filters;\n};\n\n/**\n * `media_list_assets` — paginated, filtered listing of media files.\n *\n * Permission conditions are applied through the permissions manager\n * (`addPermissionsQueryTo`) so a token restricted by a condition — e.g. own-assets-only —\n * sees the same subset it would through the admin API.\n */\nexport const createMediaListAssetsHandler =\n (strapi: Core.Strapi, context: Modules.MCP.McpHandlerContext) =>\n async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { folderId, mime, name, page, pageSize, sort } = args as ListMediaArgs;\n const pm = assertMediaPermission(strapi, context, ACTIONS.read, FILE_MODEL_UID);\n\n const query = await pm.addPermissionsQueryTo({\n filters: buildAssetFilters({ folderId, mime, name }),\n sort: sort ?? DEFAULT_SORT,\n page: page ?? 1,\n pageSize: pageSize ?? 25,\n populate: { folder: { fields: ['id', 'name'] } },\n });\n\n const { results, pagination } = await getService('upload', strapi).findPage(query);\n\n return ok({\n results: results.map(sanitizeMediaAsset),\n pagination,\n });\n };\n\n/**\n * `media_get_asset` — a single asset by numeric id.\n *\n * Row-level permission *conditions* are enforced by `findEntityAndCheckPermissions`, the same\n * helper the admin `findOne` controller uses: it populates `createdBy` and the creator's roles\n * before building the CASL subject, which is what the admin conditions read. Checking a subject\n * without those fields would deny a conditioned grant its own assets, and would disagree with\n * `media_list_assets`, where the condition is pushed into the query instead.\n */\nexport const createMediaGetAssetHandler =\n (strapi: Core.Strapi, context: Modules.MCP.McpHandlerContext) =>\n async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { id } = args as GetMediaArgs;\n\n // Model-level gate first, so a token without upload read at all is refused before a lookup.\n assertMediaPermission(strapi, context, ACTIONS.read, FILE_MODEL_UID);\n\n let asset;\n try {\n ({ file: asset } = await findEntityAndCheckPermissions(\n context.userAbility,\n ACTIONS.read,\n FILE_MODEL_UID,\n id,\n strapi\n ));\n } catch (error) {\n // The helper is written for HTTP, where a bare NotFoundError is enough. MCP answers an\n // agent, so restate it with the message the other media tools use.\n if (error instanceof errors.NotFoundError) {\n throw new errors.NotFoundError(MCP_NOT_FOUND_ASSET);\n }\n throw error;\n }\n\n return ok({ data: sanitizeMediaAsset(asset) });\n };\n\n/**\n * `media_list_folders` — the nested folder structure, reusing `folder.getStructure()`.\n *\n * `getStructure()` returns the whole tree in one query and has no permission-condition\n * filtering, so this is gated on the model-level read permission only — matching\n * `GET /upload/folder-structure` in the admin API.\n *\n * The folder service queries the ambient `global.strapi` regardless of the instance passed to\n * `getService`; `buildUploadMcpToolDefinitions` asserts the two agree before this handler is\n * ever constructed — see `assertAmbientInstance` for why.\n */\nexport const createMediaListFoldersHandler =\n (strapi: Core.Strapi, context: Modules.MCP.McpHandlerContext) =>\n async (): Promise<Modules.MCP.McpToolHandlerReturn> => {\n assertMediaPermission(strapi, context, ACTIONS.read, FOLDER_MODEL_UID);\n\n const structure = await getFolderService(strapi).getStructure();\n\n return ok({ data: sanitizeMediaFolderTree(structure) });\n };\n"],"names":["DEFAULT_SORT","buildAssetFilters","args","filters","folderId","folder","undefined","id","mime","includes","$eqi","$startsWithi","name","$containsi","createMediaListAssetsHandler","strapi","context","page","pageSize","sort","pm","assertMediaPermission","ACTIONS","read","FILE_MODEL_UID","query","addPermissionsQueryTo","populate","fields","results","pagination","getService","findPage","ok","map","sanitizeMediaAsset","createMediaGetAssetHandler","asset","file","findEntityAndCheckPermissions","userAbility","error","errors","NotFoundError","MCP_NOT_FOUND_ASSET","data","createMediaListFoldersHandler","FOLDER_MODEL_UID","structure","getFolderService","getStructure","sanitizeMediaFolderTree"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
1
  import { errors } from '@strapi/utils';
2
2
  import { getService } from '../../utils/index.mjs';
3
- import { assertAmbientInstance, getFolderService } from '../ambient-instance.mjs';
3
+ import { getFolderService } from '../ambient-instance.mjs';
4
4
  import { ACTIONS, FILE_MODEL_UID, FOLDER_MODEL_UID } from '../../constants.mjs';
5
5
  import { assertMediaPermission } from '../permissions.mjs';
6
6
  import { findEntityAndCheckPermissions } from '../../controllers/utils/find-entity-and-check-permissions.mjs';
@@ -46,9 +46,7 @@ const DEFAULT_SORT = 'createdAt:DESC';
46
46
  * Permission conditions are applied through the permissions manager
47
47
  * (`addPermissionsQueryTo`) so a token restricted by a condition — e.g. own-assets-only —
48
48
  * sees the same subset it would through the admin API.
49
- */ const createMediaListAssetsHandler = (strapi, context)=>{
50
- assertAmbientInstance(strapi);
51
- return async ({ args })=>{
49
+ */ const createMediaListAssetsHandler = (strapi, context)=>async ({ args })=>{
52
50
  const { folderId, mime, name, page, pageSize, sort } = args;
53
51
  const pm = assertMediaPermission(strapi, context, ACTIONS.read, FILE_MODEL_UID);
54
52
  const query = await pm.addPermissionsQueryTo({
@@ -75,7 +73,6 @@ const DEFAULT_SORT = 'createdAt:DESC';
75
73
  pagination
76
74
  });
77
75
  };
78
- };
79
76
  /**
80
77
  * `media_get_asset` — a single asset by numeric id.
81
78
  *
@@ -84,9 +81,7 @@ const DEFAULT_SORT = 'createdAt:DESC';
84
81
  * before building the CASL subject, which is what the admin conditions read. Checking a subject
85
82
  * without those fields would deny a conditioned grant its own assets, and would disagree with
86
83
  * `media_list_assets`, where the condition is pushed into the query instead.
87
- */ const createMediaGetAssetHandler = (strapi, context)=>{
88
- assertAmbientInstance(strapi);
89
- return async ({ args })=>{
84
+ */ const createMediaGetAssetHandler = (strapi, context)=>async ({ args })=>{
90
85
  const { id } = args;
91
86
  // Model-level gate first, so a token without upload read at all is refused before a lookup.
92
87
  assertMediaPermission(strapi, context, ACTIONS.read, FILE_MODEL_UID);
@@ -105,7 +100,6 @@ const DEFAULT_SORT = 'createdAt:DESC';
105
100
  data: sanitizeMediaAsset(asset)
106
101
  });
107
102
  };
108
- };
109
103
  /**
110
104
  * `media_list_folders` — the nested folder structure, reusing `folder.getStructure()`.
111
105
  *
@@ -113,18 +107,16 @@ const DEFAULT_SORT = 'createdAt:DESC';
113
107
  * filtering, so this is gated on the model-level read permission only — matching
114
108
  * `GET /upload/folder-structure` in the admin API.
115
109
  *
116
- * The factory asserts the handler's instance is the ambient `global.strapi` the folder service
117
- * actually queries — see `assertAmbientInstance` for why.
118
- */ const createMediaListFoldersHandler = (strapi, context)=>{
119
- assertAmbientInstance(strapi);
120
- return async ()=>{
110
+ * The folder service queries the ambient `global.strapi` regardless of the instance passed to
111
+ * `getService`; `buildUploadMcpToolDefinitions` asserts the two agree before this handler is
112
+ * ever constructed — see `assertAmbientInstance` for why.
113
+ */ const createMediaListFoldersHandler = (strapi, context)=>async ()=>{
121
114
  assertMediaPermission(strapi, context, ACTIONS.read, FOLDER_MODEL_UID);
122
115
  const structure = await getFolderService(strapi).getStructure();
123
116
  return ok({
124
117
  data: sanitizeMediaFolderTree(structure)
125
118
  });
126
119
  };
127
- };
128
120
 
129
121
  export { createMediaGetAssetHandler, createMediaListAssetsHandler, createMediaListFoldersHandler };
130
122
  //# sourceMappingURL=read-handlers.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"read-handlers.mjs","sources":["../../../../server/src/mcp/handlers/read-handlers.ts"],"sourcesContent":["import { errors } from '@strapi/utils';\nimport type { Core, Modules } from '@strapi/types';\n\nimport { getService } from '../../utils';\nimport { assertAmbientInstance, getFolderService } from '../ambient-instance';\nimport { ACTIONS, FILE_MODEL_UID, FOLDER_MODEL_UID } from '../../constants';\nimport { assertMediaPermission } from '../permissions';\nimport { findEntityAndCheckPermissions } from '../../controllers/utils/find-entity-and-check-permissions';\nimport { sanitizeMediaAsset, sanitizeMediaFolderTree } from '../sanitizers/sanitize-media';\nimport { MCP_NOT_FOUND_ASSET } from './constants';\nimport { ok } from '../utils';\n\nconst DEFAULT_SORT = 'createdAt:DESC';\n\n// Arg types are type-level only: the MCP SDK validates `args` against the tool's Zod input\n// schema before the handler runs, so handlers accept the erased `Record<string, unknown>` the\n// registry hands them and narrow it here.\ntype ListMediaArgs = {\n folderId?: number | null;\n mime?: string;\n name?: string;\n page?: number;\n pageSize?: number;\n sort?: string;\n};\n\ntype GetMediaArgs = {\n id: number;\n};\n\n/**\n * Builds the `filters` clause for `media_list_assets`.\n *\n * `folderId: null` is meaningfully different from an omitted `folderId`: null means \"assets at\n * the media library root\" (no folder relation), while omitting it means \"any folder\".\n *\n * `mime` accepts both a full type and a bare prefix. An exact match on \"image\" would never hit,\n * so a value without a slash is matched as a prefix (\"image\" → every \"image/*\").\n */\nconst buildAssetFilters = (args: ListMediaArgs): Record<string, unknown> => {\n const filters: Record<string, unknown> = {};\n\n if (args.folderId === null) {\n filters.folder = null;\n } else if (args.folderId !== undefined) {\n filters.folder = { id: args.folderId };\n }\n\n if (args.mime !== undefined) {\n filters.mime = args.mime.includes('/')\n ? { $eqi: args.mime }\n : { $startsWithi: `${args.mime}/` };\n }\n\n if (args.name !== undefined) {\n filters.name = { $containsi: args.name };\n }\n\n return filters;\n};\n\n/**\n * `media_list_assets` — paginated, filtered listing of media files.\n *\n * Permission conditions are applied through the permissions manager\n * (`addPermissionsQueryTo`) so a token restricted by a condition — e.g. own-assets-only —\n * sees the same subset it would through the admin API.\n */\nexport const createMediaListAssetsHandler = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext\n) => {\n assertAmbientInstance(strapi);\n\n return async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { folderId, mime, name, page, pageSize, sort } = args as ListMediaArgs;\n const pm = assertMediaPermission(strapi, context, ACTIONS.read, FILE_MODEL_UID);\n\n const query = await pm.addPermissionsQueryTo({\n filters: buildAssetFilters({ folderId, mime, name }),\n sort: sort ?? DEFAULT_SORT,\n page: page ?? 1,\n pageSize: pageSize ?? 25,\n populate: { folder: { fields: ['id', 'name'] } },\n });\n\n const { results, pagination } = await getService('upload', strapi).findPage(query);\n\n return ok({\n results: results.map(sanitizeMediaAsset),\n pagination,\n });\n };\n};\n\n/**\n * `media_get_asset` — a single asset by numeric id.\n *\n * Row-level permission *conditions* are enforced by `findEntityAndCheckPermissions`, the same\n * helper the admin `findOne` controller uses: it populates `createdBy` and the creator's roles\n * before building the CASL subject, which is what the admin conditions read. Checking a subject\n * without those fields would deny a conditioned grant its own assets, and would disagree with\n * `media_list_assets`, where the condition is pushed into the query instead.\n */\nexport const createMediaGetAssetHandler = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext\n) => {\n assertAmbientInstance(strapi);\n\n return async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { id } = args as GetMediaArgs;\n\n // Model-level gate first, so a token without upload read at all is refused before a lookup.\n assertMediaPermission(strapi, context, ACTIONS.read, FILE_MODEL_UID);\n\n let asset;\n try {\n ({ file: asset } = await findEntityAndCheckPermissions(\n context.userAbility,\n ACTIONS.read,\n FILE_MODEL_UID,\n id,\n strapi\n ));\n } catch (error) {\n // The helper is written for HTTP, where a bare NotFoundError is enough. MCP answers an\n // agent, so restate it with the message the other media tools use.\n if (error instanceof errors.NotFoundError) {\n throw new errors.NotFoundError(MCP_NOT_FOUND_ASSET);\n }\n throw error;\n }\n\n return ok({ data: sanitizeMediaAsset(asset) });\n };\n};\n\n/**\n * `media_list_folders` — the nested folder structure, reusing `folder.getStructure()`.\n *\n * `getStructure()` returns the whole tree in one query and has no permission-condition\n * filtering, so this is gated on the model-level read permission only — matching\n * `GET /upload/folder-structure` in the admin API.\n *\n * The factory asserts the handler's instance is the ambient `global.strapi` the folder service\n * actually queries — see `assertAmbientInstance` for why.\n */\nexport const createMediaListFoldersHandler = (\n strapi: Core.Strapi,\n context: Modules.MCP.McpHandlerContext\n) => {\n assertAmbientInstance(strapi);\n\n return async (): Promise<Modules.MCP.McpToolHandlerReturn> => {\n assertMediaPermission(strapi, context, ACTIONS.read, FOLDER_MODEL_UID);\n\n const structure = await getFolderService(strapi).getStructure();\n\n return ok({ data: sanitizeMediaFolderTree(structure) });\n };\n};\n"],"names":["DEFAULT_SORT","buildAssetFilters","args","filters","folderId","folder","undefined","id","mime","includes","$eqi","$startsWithi","name","$containsi","createMediaListAssetsHandler","strapi","context","assertAmbientInstance","page","pageSize","sort","pm","assertMediaPermission","ACTIONS","read","FILE_MODEL_UID","query","addPermissionsQueryTo","populate","fields","results","pagination","getService","findPage","ok","map","sanitizeMediaAsset","createMediaGetAssetHandler","asset","file","findEntityAndCheckPermissions","userAbility","error","errors","NotFoundError","MCP_NOT_FOUND_ASSET","data","createMediaListFoldersHandler","FOLDER_MODEL_UID","structure","getFolderService","getStructure","sanitizeMediaFolderTree"],"mappings":"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"}
1
+ {"version":3,"file":"read-handlers.mjs","sources":["../../../../server/src/mcp/handlers/read-handlers.ts"],"sourcesContent":["import { errors } from '@strapi/utils';\nimport type { Core, Modules } from '@strapi/types';\n\nimport { getService } from '../../utils';\nimport { getFolderService } from '../ambient-instance';\nimport { ACTIONS, FILE_MODEL_UID, FOLDER_MODEL_UID } from '../../constants';\nimport { assertMediaPermission } from '../permissions';\nimport { findEntityAndCheckPermissions } from '../../controllers/utils/find-entity-and-check-permissions';\nimport { sanitizeMediaAsset, sanitizeMediaFolderTree } from '../sanitizers/sanitize-media';\nimport { MCP_NOT_FOUND_ASSET } from './constants';\nimport { ok } from '../utils';\n\nconst DEFAULT_SORT = 'createdAt:DESC';\n\n// Arg types are type-level only: the MCP SDK validates `args` against the tool's Zod input\n// schema before the handler runs, so handlers accept the erased `Record<string, unknown>` the\n// registry hands them and narrow it here.\ntype ListMediaArgs = {\n folderId?: number | null;\n mime?: string;\n name?: string;\n page?: number;\n pageSize?: number;\n sort?: string;\n};\n\ntype GetMediaArgs = {\n id: number;\n};\n\n/**\n * Builds the `filters` clause for `media_list_assets`.\n *\n * `folderId: null` is meaningfully different from an omitted `folderId`: null means \"assets at\n * the media library root\" (no folder relation), while omitting it means \"any folder\".\n *\n * `mime` accepts both a full type and a bare prefix. An exact match on \"image\" would never hit,\n * so a value without a slash is matched as a prefix (\"image\" → every \"image/*\").\n */\nconst buildAssetFilters = (args: ListMediaArgs): Record<string, unknown> => {\n const filters: Record<string, unknown> = {};\n\n if (args.folderId === null) {\n filters.folder = null;\n } else if (args.folderId !== undefined) {\n filters.folder = { id: args.folderId };\n }\n\n if (args.mime !== undefined) {\n filters.mime = args.mime.includes('/')\n ? { $eqi: args.mime }\n : { $startsWithi: `${args.mime}/` };\n }\n\n if (args.name !== undefined) {\n filters.name = { $containsi: args.name };\n }\n\n return filters;\n};\n\n/**\n * `media_list_assets` — paginated, filtered listing of media files.\n *\n * Permission conditions are applied through the permissions manager\n * (`addPermissionsQueryTo`) so a token restricted by a condition — e.g. own-assets-only —\n * sees the same subset it would through the admin API.\n */\nexport const createMediaListAssetsHandler =\n (strapi: Core.Strapi, context: Modules.MCP.McpHandlerContext) =>\n async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { folderId, mime, name, page, pageSize, sort } = args as ListMediaArgs;\n const pm = assertMediaPermission(strapi, context, ACTIONS.read, FILE_MODEL_UID);\n\n const query = await pm.addPermissionsQueryTo({\n filters: buildAssetFilters({ folderId, mime, name }),\n sort: sort ?? DEFAULT_SORT,\n page: page ?? 1,\n pageSize: pageSize ?? 25,\n populate: { folder: { fields: ['id', 'name'] } },\n });\n\n const { results, pagination } = await getService('upload', strapi).findPage(query);\n\n return ok({\n results: results.map(sanitizeMediaAsset),\n pagination,\n });\n };\n\n/**\n * `media_get_asset` — a single asset by numeric id.\n *\n * Row-level permission *conditions* are enforced by `findEntityAndCheckPermissions`, the same\n * helper the admin `findOne` controller uses: it populates `createdBy` and the creator's roles\n * before building the CASL subject, which is what the admin conditions read. Checking a subject\n * without those fields would deny a conditioned grant its own assets, and would disagree with\n * `media_list_assets`, where the condition is pushed into the query instead.\n */\nexport const createMediaGetAssetHandler =\n (strapi: Core.Strapi, context: Modules.MCP.McpHandlerContext) =>\n async ({\n args,\n }: {\n args: Record<string, unknown>;\n }): Promise<Modules.MCP.McpToolHandlerReturn> => {\n const { id } = args as GetMediaArgs;\n\n // Model-level gate first, so a token without upload read at all is refused before a lookup.\n assertMediaPermission(strapi, context, ACTIONS.read, FILE_MODEL_UID);\n\n let asset;\n try {\n ({ file: asset } = await findEntityAndCheckPermissions(\n context.userAbility,\n ACTIONS.read,\n FILE_MODEL_UID,\n id,\n strapi\n ));\n } catch (error) {\n // The helper is written for HTTP, where a bare NotFoundError is enough. MCP answers an\n // agent, so restate it with the message the other media tools use.\n if (error instanceof errors.NotFoundError) {\n throw new errors.NotFoundError(MCP_NOT_FOUND_ASSET);\n }\n throw error;\n }\n\n return ok({ data: sanitizeMediaAsset(asset) });\n };\n\n/**\n * `media_list_folders` — the nested folder structure, reusing `folder.getStructure()`.\n *\n * `getStructure()` returns the whole tree in one query and has no permission-condition\n * filtering, so this is gated on the model-level read permission only — matching\n * `GET /upload/folder-structure` in the admin API.\n *\n * The folder service queries the ambient `global.strapi` regardless of the instance passed to\n * `getService`; `buildUploadMcpToolDefinitions` asserts the two agree before this handler is\n * ever constructed — see `assertAmbientInstance` for why.\n */\nexport const createMediaListFoldersHandler =\n (strapi: Core.Strapi, context: Modules.MCP.McpHandlerContext) =>\n async (): Promise<Modules.MCP.McpToolHandlerReturn> => {\n assertMediaPermission(strapi, context, ACTIONS.read, FOLDER_MODEL_UID);\n\n const structure = await getFolderService(strapi).getStructure();\n\n return ok({ data: sanitizeMediaFolderTree(structure) });\n };\n"],"names":["DEFAULT_SORT","buildAssetFilters","args","filters","folderId","folder","undefined","id","mime","includes","$eqi","$startsWithi","name","$containsi","createMediaListAssetsHandler","strapi","context","page","pageSize","sort","pm","assertMediaPermission","ACTIONS","read","FILE_MODEL_UID","query","addPermissionsQueryTo","populate","fields","results","pagination","getService","findPage","ok","map","sanitizeMediaAsset","createMediaGetAssetHandler","asset","file","findEntityAndCheckPermissions","userAbility","error","errors","NotFoundError","MCP_NOT_FOUND_ASSET","data","createMediaListFoldersHandler","FOLDER_MODEL_UID","structure","getFolderService","getStructure","sanitizeMediaFolderTree"],"mappings":"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"}
@@ -4,7 +4,6 @@ var utils = require('@strapi/utils');
4
4
  var index = require('../../utils/index.js');
5
5
  var constants$1 = require('../../constants.js');
6
6
  var findEntityAndCheckPermissions = require('../../controllers/utils/find-entity-and-check-permissions.js');
7
- var ambientInstance = require('../ambient-instance.js');
8
7
  var permissions = require('../permissions.js');
9
8
  var sanitizeMedia = require('../sanitizers/sanitize-media.js');
10
9
  var constants = require('./constants.js');
@@ -42,9 +41,7 @@ var utils$1 = require('../utils.js');
42
41
  * The response carries the updated asset through the read sanitizer, so a client can confirm
43
42
  * the write without a second `media_get_asset` round-trip — and so provider fields stay
44
43
  * invisible on the write path too.
45
- */ const createMediaUpdateAssetHandler = (strapi, context)=>{
46
- ambientInstance.assertAmbientInstance(strapi);
47
- return async ({ args })=>{
44
+ */ const createMediaUpdateAssetHandler = (strapi, context)=>async ({ args })=>{
48
45
  const { id, ...metadata } = args;
49
46
  const fileInfo = buildFileInfo(metadata);
50
47
  // A patch with no writable field is a caller error, not a no-op success: the schema cannot
@@ -79,7 +76,6 @@ var utils$1 = require('../utils.js');
79
76
  data: sanitizeMedia.sanitizeMediaAsset(updated ?? written)
80
77
  });
81
78
  };
82
- };
83
79
  /**
84
80
  * Resolves the destination folder for `media_move_assets`, and rejects one that does not exist.
85
81
  *
@@ -129,9 +125,7 @@ var utils$1 = require('../utils.js');
129
125
  * The moves are sequential on purpose. Each one writes the join table and `folderPath` for its
130
126
  * asset, and a bounded reorganisation (100 ids max, per the input schema) is not worth the
131
127
  * connection-pool contention of firing them in parallel.
132
- */ const createMediaMoveAssetsHandler = (strapi, context)=>{
133
- ambientInstance.assertAmbientInstance(strapi);
134
- return async ({ args })=>{
128
+ */ const createMediaMoveAssetsHandler = (strapi, context)=>async ({ args })=>{
135
129
  const { ids, folder } = args;
136
130
  // Model-level gate first, so a token without the action is refused before any DB read.
137
131
  permissions.assertMediaPermission(strapi, context, constants$1.ACTIONS.update, constants$1.FILE_MODEL_UID);
@@ -208,7 +202,6 @@ var utils$1 = require('../utils.js');
208
202
  failed
209
203
  });
210
204
  };
211
- };
212
205
  /**
213
206
  * `media_delete_assets` — previews or performs the permanent deletion of assets, in bulk.
214
207
  *
@@ -233,9 +226,7 @@ var utils$1 = require('../utils.js');
233
226
  *
234
227
  * The deletions are sequential on purpose: each one performs provider I/O, and a bounded batch
235
228
  * (100 ids max, per the input schema) is not worth firing at a provider in parallel.
236
- */ const createMediaDeleteAssetsHandler = (strapi, context)=>{
237
- ambientInstance.assertAmbientInstance(strapi);
238
- return async ({ args })=>{
229
+ */ const createMediaDeleteAssetsHandler = (strapi, context)=>async ({ args })=>{
239
230
  const { ids, dryRun = true } = args;
240
231
  // Model-level gate first, so a token without the action is refused before any DB read.
241
232
  // The preview takes the same gate: it reveals which assets exist and what they are.
@@ -307,7 +298,6 @@ var utils$1 = require('../utils.js');
307
298
  totalFileNumber: deleted.length
308
299
  });
309
300
  };
310
- };
311
301
 
312
302
  exports.createMediaDeleteAssetsHandler = createMediaDeleteAssetsHandler;
313
303
  exports.createMediaMoveAssetsHandler = createMediaMoveAssetsHandler;