@strapi/upload 5.53.0 → 5.54.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 (171) hide show
  1. package/dist/admin/future/components/UploadProgressDialog.js +9 -8
  2. package/dist/admin/future/components/UploadProgressDialog.js.map +1 -1
  3. package/dist/admin/future/components/UploadProgressDialog.mjs +9 -8
  4. package/dist/admin/future/components/UploadProgressDialog.mjs.map +1 -1
  5. package/dist/admin/future/pages/Assets/AssetsPage.js +0 -2
  6. package/dist/admin/future/pages/Assets/AssetsPage.js.map +1 -1
  7. package/dist/admin/future/pages/Assets/AssetsPage.mjs +0 -2
  8. package/dist/admin/future/pages/Assets/AssetsPage.mjs.map +1 -1
  9. package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.js +7 -0
  10. package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.js.map +1 -1
  11. package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.mjs +7 -0
  12. package/dist/admin/future/pages/Assets/components/Dnd/AssetsDndProvider.mjs.map +1 -1
  13. package/dist/admin/future/pages/Assets/components/Dnd/DragOverlayChip.js +4 -1
  14. package/dist/admin/future/pages/Assets/components/Dnd/DragOverlayChip.js.map +1 -1
  15. package/dist/admin/future/pages/Assets/components/Dnd/DragOverlayChip.mjs +4 -1
  16. package/dist/admin/future/pages/Assets/components/Dnd/DragOverlayChip.mjs.map +1 -1
  17. package/dist/admin/future/services/api.js +17 -0
  18. package/dist/admin/future/services/api.js.map +1 -1
  19. package/dist/admin/future/services/api.mjs +18 -1
  20. package/dist/admin/future/services/api.mjs.map +1 -1
  21. package/dist/admin/future/store/uploadProgress.js +31 -22
  22. package/dist/admin/future/store/uploadProgress.js.map +1 -1
  23. package/dist/admin/future/store/uploadProgress.mjs +31 -22
  24. package/dist/admin/future/store/uploadProgress.mjs.map +1 -1
  25. package/dist/admin/index.js +11 -10
  26. package/dist/admin/index.js.map +1 -1
  27. package/dist/admin/index.mjs +11 -10
  28. package/dist/admin/index.mjs.map +1 -1
  29. package/dist/admin/src/future/store/uploadProgress.d.ts +14 -20
  30. package/dist/admin/translations/en.json.js +0 -3
  31. package/dist/admin/translations/en.json.js.map +1 -1
  32. package/dist/admin/translations/en.json.mjs +0 -3
  33. package/dist/admin/translations/en.json.mjs.map +1 -1
  34. package/dist/server/bootstrap.js +11 -0
  35. package/dist/server/bootstrap.js.map +1 -1
  36. package/dist/server/bootstrap.mjs +11 -0
  37. package/dist/server/bootstrap.mjs.map +1 -1
  38. package/dist/server/controllers/admin-upload.js +31 -1
  39. package/dist/server/controllers/admin-upload.js.map +1 -1
  40. package/dist/server/controllers/admin-upload.mjs +31 -1
  41. package/dist/server/controllers/admin-upload.mjs.map +1 -1
  42. package/dist/server/controllers/utils/find-entity-and-check-permissions.js +15 -4
  43. package/dist/server/controllers/utils/find-entity-and-check-permissions.js.map +1 -1
  44. package/dist/server/controllers/utils/find-entity-and-check-permissions.mjs +15 -4
  45. package/dist/server/controllers/utils/find-entity-and-check-permissions.mjs.map +1 -1
  46. package/dist/server/controllers/utils/folders.js +1 -1
  47. package/dist/server/controllers/utils/folders.js.map +1 -1
  48. package/dist/server/controllers/utils/folders.mjs +1 -1
  49. package/dist/server/controllers/utils/folders.mjs.map +1 -1
  50. package/dist/server/mcp/ambient-instance.js +46 -0
  51. package/dist/server/mcp/ambient-instance.js.map +1 -0
  52. package/dist/server/mcp/ambient-instance.mjs +43 -0
  53. package/dist/server/mcp/ambient-instance.mjs.map +1 -0
  54. package/dist/server/mcp/handlers/constants.js +60 -0
  55. package/dist/server/mcp/handlers/constants.js.map +1 -0
  56. package/dist/server/mcp/handlers/constants.mjs +45 -0
  57. package/dist/server/mcp/handlers/constants.mjs.map +1 -0
  58. package/dist/server/mcp/handlers/folder-handlers.js +308 -0
  59. package/dist/server/mcp/handlers/folder-handlers.js.map +1 -0
  60. package/dist/server/mcp/handlers/folder-handlers.mjs +303 -0
  61. package/dist/server/mcp/handlers/folder-handlers.mjs.map +1 -0
  62. package/dist/server/mcp/handlers/read-handlers.js +134 -0
  63. package/dist/server/mcp/handlers/read-handlers.js.map +1 -0
  64. package/dist/server/mcp/handlers/read-handlers.mjs +130 -0
  65. package/dist/server/mcp/handlers/read-handlers.mjs.map +1 -0
  66. package/dist/server/mcp/handlers/write-handlers.js +315 -0
  67. package/dist/server/mcp/handlers/write-handlers.js.map +1 -0
  68. package/dist/server/mcp/handlers/write-handlers.mjs +311 -0
  69. package/dist/server/mcp/handlers/write-handlers.mjs.map +1 -0
  70. package/dist/server/mcp/permissions.js +36 -0
  71. package/dist/server/mcp/permissions.js.map +1 -0
  72. package/dist/server/mcp/permissions.mjs +33 -0
  73. package/dist/server/mcp/permissions.mjs.map +1 -0
  74. package/dist/server/mcp/register-upload-mcp-tools.js +235 -0
  75. package/dist/server/mcp/register-upload-mcp-tools.js.map +1 -0
  76. package/dist/server/mcp/register-upload-mcp-tools.mjs +232 -0
  77. package/dist/server/mcp/register-upload-mcp-tools.mjs.map +1 -0
  78. package/dist/server/mcp/sanitizers/sanitize-media.js +97 -0
  79. package/dist/server/mcp/sanitizers/sanitize-media.js.map +1 -0
  80. package/dist/server/mcp/sanitizers/sanitize-media.mjs +93 -0
  81. package/dist/server/mcp/sanitizers/sanitize-media.mjs.map +1 -0
  82. package/dist/server/mcp/schemas/input-schemas.js +206 -0
  83. package/dist/server/mcp/schemas/input-schemas.js.map +1 -0
  84. package/dist/server/mcp/schemas/input-schemas.mjs +190 -0
  85. package/dist/server/mcp/schemas/input-schemas.mjs.map +1 -0
  86. package/dist/server/mcp/schemas/output-schemas.js +158 -0
  87. package/dist/server/mcp/schemas/output-schemas.js.map +1 -0
  88. package/dist/server/mcp/schemas/output-schemas.mjs +142 -0
  89. package/dist/server/mcp/schemas/output-schemas.mjs.map +1 -0
  90. package/dist/server/mcp/utils.js +14 -0
  91. package/dist/server/mcp/utils.js.map +1 -0
  92. package/dist/server/mcp/utils.mjs +12 -0
  93. package/dist/server/mcp/utils.mjs.map +1 -0
  94. package/dist/server/media-library-default-notice.js +42 -0
  95. package/dist/server/media-library-default-notice.js.map +1 -0
  96. package/dist/server/media-library-default-notice.mjs +40 -0
  97. package/dist/server/media-library-default-notice.mjs.map +1 -0
  98. package/dist/server/register.js +5 -0
  99. package/dist/server/register.js.map +1 -1
  100. package/dist/server/register.mjs +5 -0
  101. package/dist/server/register.mjs.map +1 -1
  102. package/dist/server/services/folder.js.map +1 -1
  103. package/dist/server/services/folder.mjs.map +1 -1
  104. package/dist/server/services/upload.js +14 -2
  105. package/dist/server/services/upload.js.map +1 -1
  106. package/dist/server/services/upload.mjs +14 -2
  107. package/dist/server/services/upload.mjs.map +1 -1
  108. package/dist/server/src/bootstrap.d.ts.map +1 -1
  109. package/dist/server/src/controllers/admin-upload.d.ts +1 -0
  110. package/dist/server/src/controllers/admin-upload.d.ts.map +1 -1
  111. package/dist/server/src/controllers/utils/find-entity-and-check-permissions.d.ts +14 -1
  112. package/dist/server/src/controllers/utils/find-entity-and-check-permissions.d.ts.map +1 -1
  113. package/dist/server/src/controllers/utils/folders.d.ts +2 -1
  114. package/dist/server/src/controllers/utils/folders.d.ts.map +1 -1
  115. package/dist/server/src/index.d.ts +8 -2
  116. package/dist/server/src/index.d.ts.map +1 -1
  117. package/dist/server/src/mcp/ambient-instance.d.ts +65 -0
  118. package/dist/server/src/mcp/ambient-instance.d.ts.map +1 -0
  119. package/dist/server/src/mcp/handlers/constants.d.ts +48 -0
  120. package/dist/server/src/mcp/handlers/constants.d.ts.map +1 -0
  121. package/dist/server/src/mcp/handlers/folder-handlers.d.ts +49 -0
  122. package/dist/server/src/mcp/handlers/folder-handlers.d.ts.map +1 -0
  123. package/dist/server/src/mcp/handlers/index.d.ts +5 -0
  124. package/dist/server/src/mcp/handlers/index.d.ts.map +1 -0
  125. package/dist/server/src/mcp/handlers/read-handlers.d.ts +35 -0
  126. package/dist/server/src/mcp/handlers/read-handlers.d.ts.map +1 -0
  127. package/dist/server/src/mcp/handlers/write-handlers.d.ts +66 -0
  128. package/dist/server/src/mcp/handlers/write-handlers.d.ts.map +1 -0
  129. package/dist/server/src/mcp/index.d.ts +3 -0
  130. package/dist/server/src/mcp/index.d.ts.map +1 -0
  131. package/dist/server/src/mcp/permissions.d.ts +22 -0
  132. package/dist/server/src/mcp/permissions.d.ts.map +1 -0
  133. package/dist/server/src/mcp/register-upload-mcp-tools.d.ts +29 -0
  134. package/dist/server/src/mcp/register-upload-mcp-tools.d.ts.map +1 -0
  135. package/dist/server/src/mcp/sanitizers/sanitize-media.d.ts +63 -0
  136. package/dist/server/src/mcp/sanitizers/sanitize-media.d.ts.map +1 -0
  137. package/dist/server/src/mcp/schemas/index.d.ts +3 -0
  138. package/dist/server/src/mcp/schemas/index.d.ts.map +1 -0
  139. package/dist/server/src/mcp/schemas/input-schemas.d.ts +148 -0
  140. package/dist/server/src/mcp/schemas/input-schemas.d.ts.map +1 -0
  141. package/dist/server/src/mcp/schemas/output-schemas.d.ts +276 -0
  142. package/dist/server/src/mcp/schemas/output-schemas.d.ts.map +1 -0
  143. package/dist/server/src/mcp/types.d.ts +19 -0
  144. package/dist/server/src/mcp/types.d.ts.map +1 -0
  145. package/dist/server/src/mcp/utils.d.ts +4 -0
  146. package/dist/server/src/mcp/utils.d.ts.map +1 -0
  147. package/dist/server/src/media-library-default-notice.d.ts +19 -0
  148. package/dist/server/src/media-library-default-notice.d.ts.map +1 -0
  149. package/dist/server/src/register.d.ts.map +1 -1
  150. package/dist/server/src/services/folder.d.ts +6 -2
  151. package/dist/server/src/services/folder.d.ts.map +1 -1
  152. package/dist/server/src/services/index.d.ts +8 -2
  153. package/dist/server/src/services/index.d.ts.map +1 -1
  154. package/dist/server/src/services/upload.d.ts +8 -1
  155. package/dist/server/src/services/upload.d.ts.map +1 -1
  156. package/dist/server/src/types.d.ts +2 -2
  157. package/dist/server/src/types.d.ts.map +1 -1
  158. package/dist/server/src/utils/index.d.ts +2 -1
  159. package/dist/server/src/utils/index.d.ts.map +1 -1
  160. package/dist/server/utils/index.js +2 -2
  161. package/dist/server/utils/index.js.map +1 -1
  162. package/dist/server/utils/index.mjs +2 -2
  163. package/dist/server/utils/index.mjs.map +1 -1
  164. package/dist/shared/contracts/files.d.ts +22 -0
  165. package/dist/shared/contracts/files.d.ts.map +1 -1
  166. package/package.json +7 -6
  167. package/dist/admin/future/components/BetaNotice.js +0 -72
  168. package/dist/admin/future/components/BetaNotice.js.map +0 -1
  169. package/dist/admin/future/components/BetaNotice.mjs +0 -70
  170. package/dist/admin/future/components/BetaNotice.mjs.map +0 -1
  171. package/dist/admin/src/future/components/BetaNotice.d.ts +0 -11
@@ -0,0 +1,46 @@
1
+ 'use strict';
2
+
3
+ var index = require('../utils/index.js');
4
+
5
+ /**
6
+ * Asserts that an MCP handler's Strapi instance is the ambient `global.strapi`.
7
+ *
8
+ * Two of the upload plugin's services — `folder` and `file` — are registered as plain object
9
+ * literals rather than factories (see `services/index.ts`), so their methods close over the
10
+ * module-scope ambient `strapi` and query `global.strapi.db` regardless of which instance is
11
+ * passed to `getService`. Passing an instance therefore selects the right plugin registry but
12
+ * does *not* bind the queries to it.
13
+ *
14
+ * The folder tools reach `folder` directly. The asset write tools reach `file` transitively:
15
+ * `upload.updateFileInfo` resolves it with a bare `getService('file')` (`services/upload.ts`),
16
+ * and `file.getFolderPath` then queries the ambient `strapi.db`. So a handler that looks
17
+ * instance-bound can still mix ambient reads with instance-bound writes.
18
+ *
19
+ * Threading an instance through both services is a broader refactor than the MCP surface should
20
+ * carry: `create`, `update` and `deleteByIds` reach for `strapi.db`, `strapi.eventHub` and
21
+ * `strapi.getModel`, `update` runs a multi-statement transaction, and every admin controller and
22
+ * validator already depends on the ambient form.
23
+ *
24
+ * So the dependency is made explicit and asserted instead. Handlers call this in their factory
25
+ * body — at handler construction, not per call — so a mismatch fails while the tool is being
26
+ * registered rather than surfacing to an agent as tool output mid-conversation.
27
+ *
28
+ * A single-app server is unaffected: `createStrapi` assigns its instance to `global.strapi`, so
29
+ * the two agree. If they ever diverge — a second Strapi instance in one process — this throws
30
+ * rather than silently reading or writing the wrong app's media library.
31
+ */ const assertAmbientInstance = (strapi)=>{
32
+ if (strapi !== global.strapi) {
33
+ throw new Error('The upload `folder` and `file` services are bound to the ambient `global.strapi`, but an ' + 'MCP handler was constructed with a different Strapi instance. Media queries would run ' + 'against the wrong app. Convert those services to factories before serving MCP from a ' + 'non-global instance.');
34
+ }
35
+ };
36
+ /**
37
+ * Resolves the `folder` service for an MCP handler.
38
+ *
39
+ * A bare `getService('folder', strapi)` reads as instance-bound but is not — see
40
+ * `assertAmbientInstance` for why. Handlers assert the instance once at construction; this keeps
41
+ * the resolution behind a named helper so the call sites do not reintroduce the misleading form.
42
+ */ const getFolderService = (strapi)=>index.getService('folder', strapi);
43
+
44
+ exports.assertAmbientInstance = assertAmbientInstance;
45
+ exports.getFolderService = getFolderService;
46
+ //# sourceMappingURL=ambient-instance.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ambient-instance.js","sources":["../../../server/src/mcp/ambient-instance.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\n\nimport { getService } from '../utils';\n\n/**\n * Asserts that an MCP handler's Strapi instance is the ambient `global.strapi`.\n *\n * Two of the upload plugin's services — `folder` and `file` — are registered as plain object\n * literals rather than factories (see `services/index.ts`), so their methods close over the\n * module-scope ambient `strapi` and query `global.strapi.db` regardless of which instance is\n * passed to `getService`. Passing an instance therefore selects the right plugin registry but\n * does *not* bind the queries to it.\n *\n * The folder tools reach `folder` directly. The asset write tools reach `file` transitively:\n * `upload.updateFileInfo` resolves it with a bare `getService('file')` (`services/upload.ts`),\n * and `file.getFolderPath` then queries the ambient `strapi.db`. So a handler that looks\n * instance-bound can still mix ambient reads with instance-bound writes.\n *\n * Threading an instance through both services is a broader refactor than the MCP surface should\n * carry: `create`, `update` and `deleteByIds` reach for `strapi.db`, `strapi.eventHub` and\n * `strapi.getModel`, `update` runs a multi-statement transaction, and every admin controller and\n * validator already depends on the ambient form.\n *\n * So the dependency is made explicit and asserted instead. Handlers call this in their factory\n * body — at handler construction, not per call — so a mismatch fails while the tool is being\n * registered rather than surfacing to an agent as tool output mid-conversation.\n *\n * A single-app server is unaffected: `createStrapi` assigns its instance to `global.strapi`, so\n * the two agree. If they ever diverge — a second Strapi instance in one process — this throws\n * rather than silently reading or writing the wrong app's media library.\n */\nexport const assertAmbientInstance = (strapi: Core.Strapi) => {\n if (strapi !== global.strapi) {\n throw new Error(\n 'The upload `folder` and `file` services are bound to the ambient `global.strapi`, but an ' +\n 'MCP handler was constructed with a different Strapi instance. Media queries would run ' +\n 'against the wrong app. Convert those services to factories before serving MCP from a ' +\n 'non-global instance.'\n );\n }\n};\n\n/**\n * Resolves the `folder` service for an MCP handler.\n *\n * A bare `getService('folder', strapi)` reads as instance-bound but is not — see\n * `assertAmbientInstance` for why. Handlers assert the instance once at construction; this keeps\n * the resolution behind a named helper so the call sites do not reintroduce the misleading form.\n */\nexport const getFolderService = (strapi: Core.Strapi) => getService('folder', strapi);\n"],"names":["assertAmbientInstance","strapi","global","Error","getFolderService","getService"],"mappings":";;;;AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;IA2BO,MAAMA,qBAAAA,GAAwB,CAACC,MAAAA,GAAAA;IACpC,IAAIA,MAAAA,KAAWC,MAAAA,CAAOD,MAAM,EAAE;AAC5B,QAAA,MAAM,IAAIE,KAAAA,CACR,2FAAA,GACE,wFAAA,GACA,uFAAA,GACA,sBAAA,CAAA;AAEN,IAAA;AACF;AAEA;;;;;;AAMC,IACM,MAAMC,gBAAAA,GAAmB,CAACH,MAAAA,GAAwBI,gBAAAA,CAAW,UAAUJ,MAAAA;;;;;"}
@@ -0,0 +1,43 @@
1
+ import { getService } from '../utils/index.mjs';
2
+
3
+ /**
4
+ * Asserts that an MCP handler's Strapi instance is the ambient `global.strapi`.
5
+ *
6
+ * Two of the upload plugin's services — `folder` and `file` — are registered as plain object
7
+ * literals rather than factories (see `services/index.ts`), so their methods close over the
8
+ * module-scope ambient `strapi` and query `global.strapi.db` regardless of which instance is
9
+ * passed to `getService`. Passing an instance therefore selects the right plugin registry but
10
+ * does *not* bind the queries to it.
11
+ *
12
+ * The folder tools reach `folder` directly. The asset write tools reach `file` transitively:
13
+ * `upload.updateFileInfo` resolves it with a bare `getService('file')` (`services/upload.ts`),
14
+ * and `file.getFolderPath` then queries the ambient `strapi.db`. So a handler that looks
15
+ * instance-bound can still mix ambient reads with instance-bound writes.
16
+ *
17
+ * Threading an instance through both services is a broader refactor than the MCP surface should
18
+ * carry: `create`, `update` and `deleteByIds` reach for `strapi.db`, `strapi.eventHub` and
19
+ * `strapi.getModel`, `update` runs a multi-statement transaction, and every admin controller and
20
+ * validator already depends on the ambient form.
21
+ *
22
+ * So the dependency is made explicit and asserted instead. Handlers call this in their factory
23
+ * body — at handler construction, not per call — so a mismatch fails while the tool is being
24
+ * registered rather than surfacing to an agent as tool output mid-conversation.
25
+ *
26
+ * A single-app server is unaffected: `createStrapi` assigns its instance to `global.strapi`, so
27
+ * the two agree. If they ever diverge — a second Strapi instance in one process — this throws
28
+ * rather than silently reading or writing the wrong app's media library.
29
+ */ const assertAmbientInstance = (strapi)=>{
30
+ if (strapi !== global.strapi) {
31
+ throw new Error('The upload `folder` and `file` services are bound to the ambient `global.strapi`, but an ' + 'MCP handler was constructed with a different Strapi instance. Media queries would run ' + 'against the wrong app. Convert those services to factories before serving MCP from a ' + 'non-global instance.');
32
+ }
33
+ };
34
+ /**
35
+ * Resolves the `folder` service for an MCP handler.
36
+ *
37
+ * A bare `getService('folder', strapi)` reads as instance-bound but is not — see
38
+ * `assertAmbientInstance` for why. Handlers assert the instance once at construction; this keeps
39
+ * the resolution behind a named helper so the call sites do not reintroduce the misleading form.
40
+ */ const getFolderService = (strapi)=>getService('folder', strapi);
41
+
42
+ export { assertAmbientInstance, getFolderService };
43
+ //# sourceMappingURL=ambient-instance.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ambient-instance.mjs","sources":["../../../server/src/mcp/ambient-instance.ts"],"sourcesContent":["import type { Core } from '@strapi/types';\n\nimport { getService } from '../utils';\n\n/**\n * Asserts that an MCP handler's Strapi instance is the ambient `global.strapi`.\n *\n * Two of the upload plugin's services — `folder` and `file` — are registered as plain object\n * literals rather than factories (see `services/index.ts`), so their methods close over the\n * module-scope ambient `strapi` and query `global.strapi.db` regardless of which instance is\n * passed to `getService`. Passing an instance therefore selects the right plugin registry but\n * does *not* bind the queries to it.\n *\n * The folder tools reach `folder` directly. The asset write tools reach `file` transitively:\n * `upload.updateFileInfo` resolves it with a bare `getService('file')` (`services/upload.ts`),\n * and `file.getFolderPath` then queries the ambient `strapi.db`. So a handler that looks\n * instance-bound can still mix ambient reads with instance-bound writes.\n *\n * Threading an instance through both services is a broader refactor than the MCP surface should\n * carry: `create`, `update` and `deleteByIds` reach for `strapi.db`, `strapi.eventHub` and\n * `strapi.getModel`, `update` runs a multi-statement transaction, and every admin controller and\n * validator already depends on the ambient form.\n *\n * So the dependency is made explicit and asserted instead. Handlers call this in their factory\n * body — at handler construction, not per call — so a mismatch fails while the tool is being\n * registered rather than surfacing to an agent as tool output mid-conversation.\n *\n * A single-app server is unaffected: `createStrapi` assigns its instance to `global.strapi`, so\n * the two agree. If they ever diverge — a second Strapi instance in one process — this throws\n * rather than silently reading or writing the wrong app's media library.\n */\nexport const assertAmbientInstance = (strapi: Core.Strapi) => {\n if (strapi !== global.strapi) {\n throw new Error(\n 'The upload `folder` and `file` services are bound to the ambient `global.strapi`, but an ' +\n 'MCP handler was constructed with a different Strapi instance. Media queries would run ' +\n 'against the wrong app. Convert those services to factories before serving MCP from a ' +\n 'non-global instance.'\n );\n }\n};\n\n/**\n * Resolves the `folder` service for an MCP handler.\n *\n * A bare `getService('folder', strapi)` reads as instance-bound but is not — see\n * `assertAmbientInstance` for why. Handlers assert the instance once at construction; this keeps\n * the resolution behind a named helper so the call sites do not reintroduce the misleading form.\n */\nexport const getFolderService = (strapi: Core.Strapi) => getService('folder', strapi);\n"],"names":["assertAmbientInstance","strapi","global","Error","getFolderService","getService"],"mappings":";;AAIA;;;;;;;;;;;;;;;;;;;;;;;;;;IA2BO,MAAMA,qBAAAA,GAAwB,CAACC,MAAAA,GAAAA;IACpC,IAAIA,MAAAA,KAAWC,MAAAA,CAAOD,MAAM,EAAE;AAC5B,QAAA,MAAM,IAAIE,KAAAA,CACR,2FAAA,GACE,wFAAA,GACA,uFAAA,GACA,sBAAA,CAAA;AAEN,IAAA;AACF;AAEA;;;;;;AAMC,IACM,MAAMC,gBAAAA,GAAmB,CAACH,MAAAA,GAAwBI,UAAAA,CAAW,UAAUJ,MAAAA;;;;"}
@@ -0,0 +1,60 @@
1
+ 'use strict';
2
+
3
+ const MCP_NOT_FOUND_ASSET = 'Media asset not found.';
4
+ const MCP_UPDATE_ASSET_NO_FIELDS = 'Provide at least one field to update: name, alternativeText or caption. To move an asset between folders use media_move_assets; url, mime, size and the file contents are owned by the upload provider and cannot be edited over MCP.';
5
+ const MCP_NOT_FOUND_FOLDER = 'Media folder not found.';
6
+ const MCP_FOLDER_NAME_TAKEN = 'A folder with this name already exists in the same parent folder. Folder names must be unique among siblings.';
7
+ const MCP_PARENT_FOLDER_NOT_FOUND = 'The parent folder does not exist. Use media_list_folders to discover valid folder ids, or pass null for the media library root.';
8
+ const MCP_FOLDER_MOVE_INTO_SELF = 'A folder cannot be moved into itself or into one of its own descendants.';
9
+ /**
10
+ * Returned when `media_delete_folder` is handed any id that does not resolve to a folder.
11
+ *
12
+ * Names the offending ids, because the call rejects as a whole: the agent has to know which
13
+ * entries to drop or correct, and cannot infer that from a generic failure. Folder ids and
14
+ * asset ids are indistinguishable integers, so an asset id is the likeliest cause and is named
15
+ * first — but a deleted or never-existent folder id looks the same from here, so the message
16
+ * covers both rather than asserting one.
17
+ */ const MCP_DELETE_FOLDER_UNRESOLVED_IDS = (ids)=>`These ids do not match any media folder: ${ids.join(', ')}. Nothing was deleted — media_delete_folder rejects the whole request rather than deleting the folders that did match, because folder ids and asset ids are indistinguishable integers. If these are asset ids, use media_delete_assets instead; otherwise the folders may already be gone. Use media_list_folders to discover valid folder ids.`;
18
+ const MCP_MOVE_ASSETS_DESTINATION_NOT_FOUND = 'The destination folder does not exist. Use media_list_folders to discover valid folder ids, or pass null for the media library root.';
19
+ /** Per-id `failed` reasons for `media_move_assets`. Both are per asset, so the wording names the id's fate, not the call's. */ const MCP_MOVE_ASSETS_ID_NOT_FOUND = 'No media asset has this id, so nothing was moved for it. If this is a folder id, use media_move_folder — and note that a folder id only fails like this when no asset happens to share the number; when one does, media_move_assets moves that asset instead. Otherwise the asset may already be deleted — use media_list_assets to discover valid asset ids.';
20
+ const MCP_MOVE_ASSETS_ID_FORBIDDEN = 'This token is not allowed to edit this asset. A permission condition on plugin::upload.assets.update excludes it.';
21
+ /**
22
+ * Returned when the move of one asset failed for a reason that is not a missing asset or a
23
+ * permission denial — a DB error, say, or an upload-provider fault.
24
+ *
25
+ * Reported per id rather than thrown: a tool error carries no `structuredContent`, so throwing
26
+ * would discard the report naming the assets that had already moved in the same call.
27
+ * `cause` is the underlying message, kept verbatim so the real fault stays legible.
28
+ */ const MCP_MOVE_ASSETS_ID_FAILED = (cause)=>`Moving this asset failed: ${cause}. This is not a problem with the id itself — the asset exists and this token may edit it — so retrying may succeed. Any assets listed under \`moved\` were still moved.`;
29
+ /**
30
+ * Per-id `failed` reasons for `media_delete_assets`, and the dry-run's `failed` reason.
31
+ *
32
+ * Reported per id rather than thrown, because a delete is irreversible: an error carries no
33
+ * `structuredContent`, so throwing on the third id would discard the report saying the first
34
+ * two are already gone — and no re-read can recover what was deleted.
35
+ */ const MCP_DELETE_ASSETS_ID_NOT_FOUND = 'No media asset has this id, so nothing was deleted for it. If this is a folder id, use media_delete_folder — and note that a folder id only fails like this when no asset happens to share the number; when one does, media_delete_assets deletes that asset instead. Otherwise the asset may already be deleted; use media_list_assets to discover valid asset ids.';
36
+ const MCP_DELETE_ASSETS_ID_FORBIDDEN = 'This token is not allowed to delete this asset. A permission condition on plugin::upload.assets.update excludes it.';
37
+ /**
38
+ * Returned when the deletion of one asset failed for a reason that is not a missing asset or a
39
+ * permission denial — a DB error, say, or an upload-provider fault.
40
+ *
41
+ * `cause` is the underlying message, kept verbatim so the real fault stays legible. The asset
42
+ * may be half-removed (the provider file gone, the row still present, or the reverse), so the
43
+ * wording tells the agent to re-read rather than assume either outcome.
44
+ */ const MCP_DELETE_ASSETS_ID_FAILED = (cause)=>`Deleting this asset failed: ${cause}. The asset may be partially removed — re-read it with media_get_asset before retrying. Any assets listed under \`deleted\` are gone for good.`;
45
+
46
+ exports.MCP_DELETE_ASSETS_ID_FAILED = MCP_DELETE_ASSETS_ID_FAILED;
47
+ exports.MCP_DELETE_ASSETS_ID_FORBIDDEN = MCP_DELETE_ASSETS_ID_FORBIDDEN;
48
+ exports.MCP_DELETE_ASSETS_ID_NOT_FOUND = MCP_DELETE_ASSETS_ID_NOT_FOUND;
49
+ exports.MCP_DELETE_FOLDER_UNRESOLVED_IDS = MCP_DELETE_FOLDER_UNRESOLVED_IDS;
50
+ exports.MCP_FOLDER_MOVE_INTO_SELF = MCP_FOLDER_MOVE_INTO_SELF;
51
+ exports.MCP_FOLDER_NAME_TAKEN = MCP_FOLDER_NAME_TAKEN;
52
+ exports.MCP_MOVE_ASSETS_DESTINATION_NOT_FOUND = MCP_MOVE_ASSETS_DESTINATION_NOT_FOUND;
53
+ exports.MCP_MOVE_ASSETS_ID_FAILED = MCP_MOVE_ASSETS_ID_FAILED;
54
+ exports.MCP_MOVE_ASSETS_ID_FORBIDDEN = MCP_MOVE_ASSETS_ID_FORBIDDEN;
55
+ exports.MCP_MOVE_ASSETS_ID_NOT_FOUND = MCP_MOVE_ASSETS_ID_NOT_FOUND;
56
+ exports.MCP_NOT_FOUND_ASSET = MCP_NOT_FOUND_ASSET;
57
+ exports.MCP_NOT_FOUND_FOLDER = MCP_NOT_FOUND_FOLDER;
58
+ exports.MCP_PARENT_FOLDER_NOT_FOUND = MCP_PARENT_FOLDER_NOT_FOUND;
59
+ exports.MCP_UPDATE_ASSET_NO_FIELDS = MCP_UPDATE_ASSET_NO_FIELDS;
60
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.js","sources":["../../../../server/src/mcp/handlers/constants.ts"],"sourcesContent":["export const MCP_NOT_FOUND_ASSET = 'Media asset not found.';\n\nexport const MCP_UPDATE_ASSET_NO_FIELDS =\n 'Provide at least one field to update: name, alternativeText or caption. To move an asset between folders use media_move_assets; url, mime, size and the file contents are owned by the upload provider and cannot be edited over MCP.';\n\nexport const MCP_NOT_FOUND_FOLDER = 'Media folder not found.';\n\nexport const MCP_FOLDER_NAME_TAKEN =\n 'A folder with this name already exists in the same parent folder. Folder names must be unique among siblings.';\n\nexport const MCP_PARENT_FOLDER_NOT_FOUND =\n 'The parent folder does not exist. Use media_list_folders to discover valid folder ids, or pass null for the media library root.';\n\nexport const MCP_FOLDER_MOVE_INTO_SELF =\n 'A folder cannot be moved into itself or into one of its own descendants.';\n\n/**\n * Returned when `media_delete_folder` is handed any id that does not resolve to a folder.\n *\n * Names the offending ids, because the call rejects as a whole: the agent has to know which\n * entries to drop or correct, and cannot infer that from a generic failure. Folder ids and\n * asset ids are indistinguishable integers, so an asset id is the likeliest cause and is named\n * first — but a deleted or never-existent folder id looks the same from here, so the message\n * covers both rather than asserting one.\n */\nexport const MCP_DELETE_FOLDER_UNRESOLVED_IDS = (ids: number[]) =>\n `These ids do not match any media folder: ${ids.join(', ')}. Nothing was deleted — media_delete_folder rejects the whole request rather than deleting the folders that did match, because folder ids and asset ids are indistinguishable integers. If these are asset ids, use media_delete_assets instead; otherwise the folders may already be gone. Use media_list_folders to discover valid folder ids.`;\n\nexport const MCP_MOVE_ASSETS_DESTINATION_NOT_FOUND =\n 'The destination folder does not exist. Use media_list_folders to discover valid folder ids, or pass null for the media library root.';\n\n/** Per-id `failed` reasons for `media_move_assets`. Both are per asset, so the wording names the id's fate, not the call's. */\nexport const MCP_MOVE_ASSETS_ID_NOT_FOUND =\n 'No media asset has this id, so nothing was moved for it. If this is a folder id, use media_move_folder — and note that a folder id only fails like this when no asset happens to share the number; when one does, media_move_assets moves that asset instead. Otherwise the asset may already be deleted — use media_list_assets to discover valid asset ids.';\n\nexport const MCP_MOVE_ASSETS_ID_FORBIDDEN =\n 'This token is not allowed to edit this asset. A permission condition on plugin::upload.assets.update excludes it.';\n\n/**\n * Returned when the move of one asset failed for a reason that is not a missing asset or a\n * permission denial — a DB error, say, or an upload-provider fault.\n *\n * Reported per id rather than thrown: a tool error carries no `structuredContent`, so throwing\n * would discard the report naming the assets that had already moved in the same call.\n * `cause` is the underlying message, kept verbatim so the real fault stays legible.\n */\nexport const MCP_MOVE_ASSETS_ID_FAILED = (cause: string) =>\n `Moving this asset failed: ${cause}. This is not a problem with the id itself — the asset exists and this token may edit it — so retrying may succeed. Any assets listed under \\`moved\\` were still moved.`;\n\n/**\n * Per-id `failed` reasons for `media_delete_assets`, and the dry-run's `failed` reason.\n *\n * Reported per id rather than thrown, because a delete is irreversible: an error carries no\n * `structuredContent`, so throwing on the third id would discard the report saying the first\n * two are already gone — and no re-read can recover what was deleted.\n */\nexport const MCP_DELETE_ASSETS_ID_NOT_FOUND =\n 'No media asset has this id, so nothing was deleted for it. If this is a folder id, use media_delete_folder — and note that a folder id only fails like this when no asset happens to share the number; when one does, media_delete_assets deletes that asset instead. Otherwise the asset may already be deleted; use media_list_assets to discover valid asset ids.';\n\nexport const MCP_DELETE_ASSETS_ID_FORBIDDEN =\n 'This token is not allowed to delete this asset. A permission condition on plugin::upload.assets.update excludes it.';\n\n/**\n * Returned when the deletion of one asset failed for a reason that is not a missing asset or a\n * permission denial — a DB error, say, or an upload-provider fault.\n *\n * `cause` is the underlying message, kept verbatim so the real fault stays legible. The asset\n * may be half-removed (the provider file gone, the row still present, or the reverse), so the\n * wording tells the agent to re-read rather than assume either outcome.\n */\nexport const MCP_DELETE_ASSETS_ID_FAILED = (cause: string) =>\n `Deleting this asset failed: ${cause}. The asset may be partially removed — re-read it with media_get_asset before retrying. Any assets listed under \\`deleted\\` are gone for good.`;\n"],"names":["MCP_NOT_FOUND_ASSET","MCP_UPDATE_ASSET_NO_FIELDS","MCP_NOT_FOUND_FOLDER","MCP_FOLDER_NAME_TAKEN","MCP_PARENT_FOLDER_NOT_FOUND","MCP_FOLDER_MOVE_INTO_SELF","MCP_DELETE_FOLDER_UNRESOLVED_IDS","ids","join","MCP_MOVE_ASSETS_DESTINATION_NOT_FOUND","MCP_MOVE_ASSETS_ID_NOT_FOUND","MCP_MOVE_ASSETS_ID_FORBIDDEN","MCP_MOVE_ASSETS_ID_FAILED","cause","MCP_DELETE_ASSETS_ID_NOT_FOUND","MCP_DELETE_ASSETS_ID_FORBIDDEN","MCP_DELETE_ASSETS_ID_FAILED"],"mappings":";;AAAO,MAAMA,sBAAsB;AAE5B,MAAMC,6BACX;AAEK,MAAMC,uBAAuB;AAE7B,MAAMC,wBACX;AAEK,MAAMC,8BACX;AAEK,MAAMC,4BACX;AAEF;;;;;;;;AAQC,IACM,MAAMC,gCAAAA,GAAmC,CAACC,MAC/C,CAAC,yCAAyC,EAAEA,GAAAA,CAAIC,IAAI,CAAC,IAAA,CAAA,CAAM,gVAAgV;AAEtY,MAAMC,wCACX;AAEF,gIACO,MAAMC,4BAAAA,GACX;AAEK,MAAMC,+BACX;AAEF;;;;;;;IAQO,MAAMC,yBAAAA,GAA4B,CAACC,KAAAA,GACxC,CAAC,0BAA0B,EAAEA,KAAAA,CAAM,uKAAuK;AAE5M;;;;;;IAOO,MAAMC,8BAAAA,GACX;AAEK,MAAMC,iCACX;AAEF;;;;;;;IAQO,MAAMC,2BAAAA,GAA8B,CAACH,KAAAA,GAC1C,CAAC,4BAA4B,EAAEA,KAAAA,CAAM,8IAA8I;;;;;;;;;;;;;;;;;"}
@@ -0,0 +1,45 @@
1
+ const MCP_NOT_FOUND_ASSET = 'Media asset not found.';
2
+ const MCP_UPDATE_ASSET_NO_FIELDS = 'Provide at least one field to update: name, alternativeText or caption. To move an asset between folders use media_move_assets; url, mime, size and the file contents are owned by the upload provider and cannot be edited over MCP.';
3
+ const MCP_NOT_FOUND_FOLDER = 'Media folder not found.';
4
+ const MCP_FOLDER_NAME_TAKEN = 'A folder with this name already exists in the same parent folder. Folder names must be unique among siblings.';
5
+ const MCP_PARENT_FOLDER_NOT_FOUND = 'The parent folder does not exist. Use media_list_folders to discover valid folder ids, or pass null for the media library root.';
6
+ const MCP_FOLDER_MOVE_INTO_SELF = 'A folder cannot be moved into itself or into one of its own descendants.';
7
+ /**
8
+ * Returned when `media_delete_folder` is handed any id that does not resolve to a folder.
9
+ *
10
+ * Names the offending ids, because the call rejects as a whole: the agent has to know which
11
+ * entries to drop or correct, and cannot infer that from a generic failure. Folder ids and
12
+ * asset ids are indistinguishable integers, so an asset id is the likeliest cause and is named
13
+ * first — but a deleted or never-existent folder id looks the same from here, so the message
14
+ * covers both rather than asserting one.
15
+ */ const MCP_DELETE_FOLDER_UNRESOLVED_IDS = (ids)=>`These ids do not match any media folder: ${ids.join(', ')}. Nothing was deleted — media_delete_folder rejects the whole request rather than deleting the folders that did match, because folder ids and asset ids are indistinguishable integers. If these are asset ids, use media_delete_assets instead; otherwise the folders may already be gone. Use media_list_folders to discover valid folder ids.`;
16
+ const MCP_MOVE_ASSETS_DESTINATION_NOT_FOUND = 'The destination folder does not exist. Use media_list_folders to discover valid folder ids, or pass null for the media library root.';
17
+ /** Per-id `failed` reasons for `media_move_assets`. Both are per asset, so the wording names the id's fate, not the call's. */ const MCP_MOVE_ASSETS_ID_NOT_FOUND = 'No media asset has this id, so nothing was moved for it. If this is a folder id, use media_move_folder — and note that a folder id only fails like this when no asset happens to share the number; when one does, media_move_assets moves that asset instead. Otherwise the asset may already be deleted — use media_list_assets to discover valid asset ids.';
18
+ const MCP_MOVE_ASSETS_ID_FORBIDDEN = 'This token is not allowed to edit this asset. A permission condition on plugin::upload.assets.update excludes it.';
19
+ /**
20
+ * Returned when the move of one asset failed for a reason that is not a missing asset or a
21
+ * permission denial — a DB error, say, or an upload-provider fault.
22
+ *
23
+ * Reported per id rather than thrown: a tool error carries no `structuredContent`, so throwing
24
+ * would discard the report naming the assets that had already moved in the same call.
25
+ * `cause` is the underlying message, kept verbatim so the real fault stays legible.
26
+ */ const MCP_MOVE_ASSETS_ID_FAILED = (cause)=>`Moving this asset failed: ${cause}. This is not a problem with the id itself — the asset exists and this token may edit it — so retrying may succeed. Any assets listed under \`moved\` were still moved.`;
27
+ /**
28
+ * Per-id `failed` reasons for `media_delete_assets`, and the dry-run's `failed` reason.
29
+ *
30
+ * Reported per id rather than thrown, because a delete is irreversible: an error carries no
31
+ * `structuredContent`, so throwing on the third id would discard the report saying the first
32
+ * two are already gone — and no re-read can recover what was deleted.
33
+ */ const MCP_DELETE_ASSETS_ID_NOT_FOUND = 'No media asset has this id, so nothing was deleted for it. If this is a folder id, use media_delete_folder — and note that a folder id only fails like this when no asset happens to share the number; when one does, media_delete_assets deletes that asset instead. Otherwise the asset may already be deleted; use media_list_assets to discover valid asset ids.';
34
+ const MCP_DELETE_ASSETS_ID_FORBIDDEN = 'This token is not allowed to delete this asset. A permission condition on plugin::upload.assets.update excludes it.';
35
+ /**
36
+ * Returned when the deletion of one asset failed for a reason that is not a missing asset or a
37
+ * permission denial — a DB error, say, or an upload-provider fault.
38
+ *
39
+ * `cause` is the underlying message, kept verbatim so the real fault stays legible. The asset
40
+ * may be half-removed (the provider file gone, the row still present, or the reverse), so the
41
+ * wording tells the agent to re-read rather than assume either outcome.
42
+ */ const MCP_DELETE_ASSETS_ID_FAILED = (cause)=>`Deleting this asset failed: ${cause}. The asset may be partially removed — re-read it with media_get_asset before retrying. Any assets listed under \`deleted\` are gone for good.`;
43
+
44
+ export { MCP_DELETE_ASSETS_ID_FAILED, MCP_DELETE_ASSETS_ID_FORBIDDEN, MCP_DELETE_ASSETS_ID_NOT_FOUND, MCP_DELETE_FOLDER_UNRESOLVED_IDS, MCP_FOLDER_MOVE_INTO_SELF, MCP_FOLDER_NAME_TAKEN, MCP_MOVE_ASSETS_DESTINATION_NOT_FOUND, MCP_MOVE_ASSETS_ID_FAILED, MCP_MOVE_ASSETS_ID_FORBIDDEN, MCP_MOVE_ASSETS_ID_NOT_FOUND, MCP_NOT_FOUND_ASSET, MCP_NOT_FOUND_FOLDER, MCP_PARENT_FOLDER_NOT_FOUND, MCP_UPDATE_ASSET_NO_FIELDS };
45
+ //# sourceMappingURL=constants.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"constants.mjs","sources":["../../../../server/src/mcp/handlers/constants.ts"],"sourcesContent":["export const MCP_NOT_FOUND_ASSET = 'Media asset not found.';\n\nexport const MCP_UPDATE_ASSET_NO_FIELDS =\n 'Provide at least one field to update: name, alternativeText or caption. To move an asset between folders use media_move_assets; url, mime, size and the file contents are owned by the upload provider and cannot be edited over MCP.';\n\nexport const MCP_NOT_FOUND_FOLDER = 'Media folder not found.';\n\nexport const MCP_FOLDER_NAME_TAKEN =\n 'A folder with this name already exists in the same parent folder. Folder names must be unique among siblings.';\n\nexport const MCP_PARENT_FOLDER_NOT_FOUND =\n 'The parent folder does not exist. Use media_list_folders to discover valid folder ids, or pass null for the media library root.';\n\nexport const MCP_FOLDER_MOVE_INTO_SELF =\n 'A folder cannot be moved into itself or into one of its own descendants.';\n\n/**\n * Returned when `media_delete_folder` is handed any id that does not resolve to a folder.\n *\n * Names the offending ids, because the call rejects as a whole: the agent has to know which\n * entries to drop or correct, and cannot infer that from a generic failure. Folder ids and\n * asset ids are indistinguishable integers, so an asset id is the likeliest cause and is named\n * first — but a deleted or never-existent folder id looks the same from here, so the message\n * covers both rather than asserting one.\n */\nexport const MCP_DELETE_FOLDER_UNRESOLVED_IDS = (ids: number[]) =>\n `These ids do not match any media folder: ${ids.join(', ')}. Nothing was deleted — media_delete_folder rejects the whole request rather than deleting the folders that did match, because folder ids and asset ids are indistinguishable integers. If these are asset ids, use media_delete_assets instead; otherwise the folders may already be gone. Use media_list_folders to discover valid folder ids.`;\n\nexport const MCP_MOVE_ASSETS_DESTINATION_NOT_FOUND =\n 'The destination folder does not exist. Use media_list_folders to discover valid folder ids, or pass null for the media library root.';\n\n/** Per-id `failed` reasons for `media_move_assets`. Both are per asset, so the wording names the id's fate, not the call's. */\nexport const MCP_MOVE_ASSETS_ID_NOT_FOUND =\n 'No media asset has this id, so nothing was moved for it. If this is a folder id, use media_move_folder — and note that a folder id only fails like this when no asset happens to share the number; when one does, media_move_assets moves that asset instead. Otherwise the asset may already be deleted — use media_list_assets to discover valid asset ids.';\n\nexport const MCP_MOVE_ASSETS_ID_FORBIDDEN =\n 'This token is not allowed to edit this asset. A permission condition on plugin::upload.assets.update excludes it.';\n\n/**\n * Returned when the move of one asset failed for a reason that is not a missing asset or a\n * permission denial — a DB error, say, or an upload-provider fault.\n *\n * Reported per id rather than thrown: a tool error carries no `structuredContent`, so throwing\n * would discard the report naming the assets that had already moved in the same call.\n * `cause` is the underlying message, kept verbatim so the real fault stays legible.\n */\nexport const MCP_MOVE_ASSETS_ID_FAILED = (cause: string) =>\n `Moving this asset failed: ${cause}. This is not a problem with the id itself — the asset exists and this token may edit it — so retrying may succeed. Any assets listed under \\`moved\\` were still moved.`;\n\n/**\n * Per-id `failed` reasons for `media_delete_assets`, and the dry-run's `failed` reason.\n *\n * Reported per id rather than thrown, because a delete is irreversible: an error carries no\n * `structuredContent`, so throwing on the third id would discard the report saying the first\n * two are already gone — and no re-read can recover what was deleted.\n */\nexport const MCP_DELETE_ASSETS_ID_NOT_FOUND =\n 'No media asset has this id, so nothing was deleted for it. If this is a folder id, use media_delete_folder — and note that a folder id only fails like this when no asset happens to share the number; when one does, media_delete_assets deletes that asset instead. Otherwise the asset may already be deleted; use media_list_assets to discover valid asset ids.';\n\nexport const MCP_DELETE_ASSETS_ID_FORBIDDEN =\n 'This token is not allowed to delete this asset. A permission condition on plugin::upload.assets.update excludes it.';\n\n/**\n * Returned when the deletion of one asset failed for a reason that is not a missing asset or a\n * permission denial — a DB error, say, or an upload-provider fault.\n *\n * `cause` is the underlying message, kept verbatim so the real fault stays legible. The asset\n * may be half-removed (the provider file gone, the row still present, or the reverse), so the\n * wording tells the agent to re-read rather than assume either outcome.\n */\nexport const MCP_DELETE_ASSETS_ID_FAILED = (cause: string) =>\n `Deleting this asset failed: ${cause}. The asset may be partially removed — re-read it with media_get_asset before retrying. Any assets listed under \\`deleted\\` are gone for good.`;\n"],"names":["MCP_NOT_FOUND_ASSET","MCP_UPDATE_ASSET_NO_FIELDS","MCP_NOT_FOUND_FOLDER","MCP_FOLDER_NAME_TAKEN","MCP_PARENT_FOLDER_NOT_FOUND","MCP_FOLDER_MOVE_INTO_SELF","MCP_DELETE_FOLDER_UNRESOLVED_IDS","ids","join","MCP_MOVE_ASSETS_DESTINATION_NOT_FOUND","MCP_MOVE_ASSETS_ID_NOT_FOUND","MCP_MOVE_ASSETS_ID_FORBIDDEN","MCP_MOVE_ASSETS_ID_FAILED","cause","MCP_DELETE_ASSETS_ID_NOT_FOUND","MCP_DELETE_ASSETS_ID_FORBIDDEN","MCP_DELETE_ASSETS_ID_FAILED"],"mappings":"AAAO,MAAMA,sBAAsB;AAE5B,MAAMC,6BACX;AAEK,MAAMC,uBAAuB;AAE7B,MAAMC,wBACX;AAEK,MAAMC,8BACX;AAEK,MAAMC,4BACX;AAEF;;;;;;;;AAQC,IACM,MAAMC,gCAAAA,GAAmC,CAACC,MAC/C,CAAC,yCAAyC,EAAEA,GAAAA,CAAIC,IAAI,CAAC,IAAA,CAAA,CAAM,gVAAgV;AAEtY,MAAMC,wCACX;AAEF,gIACO,MAAMC,4BAAAA,GACX;AAEK,MAAMC,+BACX;AAEF;;;;;;;IAQO,MAAMC,yBAAAA,GAA4B,CAACC,KAAAA,GACxC,CAAC,0BAA0B,EAAEA,KAAAA,CAAM,uKAAuK;AAE5M;;;;;;IAOO,MAAMC,8BAAAA,GACX;AAEK,MAAMC,iCACX;AAEF;;;;;;;IAQO,MAAMC,2BAAAA,GAA8B,CAACH,KAAAA,GAC1C,CAAC,4BAA4B,EAAEA,KAAAA,CAAM,8IAA8I;;;;"}
@@ -0,0 +1,308 @@
1
+ 'use strict';
2
+
3
+ var utils$1 = require('@strapi/utils');
4
+ var ambientInstance = require('../ambient-instance.js');
5
+ var constants$1 = require('../../constants.js');
6
+ var folders = require('../../controllers/utils/folders.js');
7
+ var permissions = require('../permissions.js');
8
+ var sanitizeMedia = require('../sanitizers/sanitize-media.js');
9
+ var constants = require('./constants.js');
10
+ var utils = require('../utils.js');
11
+
12
+ /**
13
+ * `withParent` populates the parent relation in the same round trip. Rename needs the parent id
14
+ * to scope its sibling-uniqueness check, so it would otherwise read the same row twice; the
15
+ * subtree and cascade callers do not, and skip the join.
16
+ */ const findFolderById = async (strapi, id, { withParent = false } = {})=>strapi.db.query(constants$1.FOLDER_MODEL_UID).findOne({
17
+ select: [
18
+ 'id',
19
+ 'name',
20
+ 'path'
21
+ ],
22
+ where: {
23
+ id
24
+ },
25
+ ...withParent ? {
26
+ populate: {
27
+ parent: {
28
+ select: [
29
+ 'id'
30
+ ]
31
+ }
32
+ }
33
+ } : {}
34
+ });
35
+ /**
36
+ * Re-reads a folder with its parent populated, so the write tools can echo back the same shape
37
+ * regardless of what the service returned.
38
+ *
39
+ * `folder.update()` resolves the row it wrote without the `parent` relation, and on a pure rename
40
+ * it never touches the join table at all — so the parent has to be read back to be reported.
41
+ */ const readFolderForOutput = async (strapi, id, fallback)=>{
42
+ const row = await strapi.db.query(constants$1.FOLDER_MODEL_UID).findOne({
43
+ where: {
44
+ id
45
+ },
46
+ populate: {
47
+ parent: {
48
+ select: [
49
+ 'id',
50
+ 'name'
51
+ ]
52
+ }
53
+ }
54
+ });
55
+ // The write succeeded, so report it even if the read-back comes back empty rather than
56
+ // failing a completed operation on a follow-up query.
57
+ return sanitizeMedia.sanitizeMediaFolder(row ?? fallback);
58
+ };
59
+ /**
60
+ * Replicates the admin folder controller's `parent` check: a named parent must exist.
61
+ *
62
+ * null is the media library root and always valid, so only a numeric id is looked up.
63
+ */ const assertParentExists = async (strapi, parent)=>{
64
+ if (parent === null || parent === undefined) {
65
+ return;
66
+ }
67
+ const exists = await ambientInstance.getFolderService(strapi).exists({
68
+ id: parent
69
+ });
70
+ if (!exists) {
71
+ throw new utils$1.errors.ValidationError(constants.MCP_PARENT_FOLDER_NOT_FOUND);
72
+ }
73
+ };
74
+ /**
75
+ * Replicates `is-folder-unique` from the admin folder validation: a folder name must be unique
76
+ * among its siblings.
77
+ *
78
+ * `excludeId` is the folder being renamed — without it a rename to the folder's current name
79
+ * would collide with itself.
80
+ */ const assertNameAvailable = async (strapi, name, parent, excludeId)=>{
81
+ const filters = {
82
+ name,
83
+ parent: parent ?? null
84
+ };
85
+ if (excludeId !== undefined) {
86
+ filters.id = {
87
+ $ne: excludeId
88
+ };
89
+ }
90
+ if (await ambientInstance.getFolderService(strapi).exists(filters)) {
91
+ throw new utils$1.errors.ValidationError(constants.MCP_FOLDER_NAME_TAKEN);
92
+ }
93
+ };
94
+ /**
95
+ * Replicates `dont-move-inside-self`: a folder cannot become its own descendant.
96
+ *
97
+ * Without this the folder service would rewrite the subtree's materialized paths into a cycle,
98
+ * orphaning every descendant — the tree has no root to walk back to afterwards.
99
+ */ const assertNotMovedIntoOwnSubtree = async (strapi, folder, parent)=>{
100
+ if (parent === null) {
101
+ return;
102
+ }
103
+ const destination = await findFolderById(strapi, parent);
104
+ // A missing destination is already reported by `assertParentExists`.
105
+ if (destination === null) {
106
+ return;
107
+ }
108
+ if (folders.isFolderOrChild(destination, folder)) {
109
+ throw new utils$1.errors.ValidationError(constants.MCP_FOLDER_MOVE_INTO_SELF);
110
+ }
111
+ };
112
+ /**
113
+ * Counts the folders and files a `deleteByIds` of these paths would remove.
114
+ *
115
+ * Mirrors the two `$or` predicates in `folder.deleteByIds` exactly — self path plus the
116
+ * `${path}/` prefix — so the dry-run number is the same set the destructive branch acts on
117
+ * rather than an independent estimate that could drift from it.
118
+ */ const countCascade = async (strapi, paths)=>{
119
+ const pathPredicate = (field)=>({
120
+ $or: paths.flatMap((path)=>[
121
+ {
122
+ [field]: {
123
+ $eq: path
124
+ }
125
+ },
126
+ {
127
+ [field]: {
128
+ $startsWith: `${path}/`
129
+ }
130
+ }
131
+ ])
132
+ });
133
+ const [totalFolderNumber, totalFileNumber] = await Promise.all([
134
+ strapi.db.query(constants$1.FOLDER_MODEL_UID).count({
135
+ where: pathPredicate('path')
136
+ }),
137
+ strapi.db.query(constants$1.FILE_MODEL_UID).count({
138
+ where: pathPredicate('folderPath')
139
+ })
140
+ ]);
141
+ return {
142
+ totalFolderNumber,
143
+ totalFileNumber
144
+ };
145
+ };
146
+ /**
147
+ * `media_create_folder` — a new folder, optionally nested.
148
+ *
149
+ * Gated on `plugin::upload.assets.create` and mirrors `POST /upload/folders`: the same
150
+ * permission, the same uniqueness and parent-existence validation, the same `folder.create()`
151
+ * call, and the same `createdBy` attribution from the session user.
152
+ *
153
+ * `create` rather than the `update` the other folder tools use: creating a folder is the one
154
+ * folder operation whose admin route gates on `assets.create`.
155
+ */ const createMediaCreateFolderHandler = (strapi, context)=>{
156
+ ambientInstance.assertAmbientInstance(strapi);
157
+ return async ({ args })=>{
158
+ const { name, parent = null } = args;
159
+ permissions.assertMediaPermission(strapi, context, constants$1.ACTIONS.create, constants$1.FOLDER_MODEL_UID);
160
+ await assertParentExists(strapi, parent);
161
+ await assertNameAvailable(strapi, name, parent);
162
+ const created = await ambientInstance.getFolderService(strapi).create({
163
+ name,
164
+ parent
165
+ }, {
166
+ user: context.user
167
+ });
168
+ return utils.ok({
169
+ data: await readFolderForOutput(strapi, created.id, created)
170
+ });
171
+ };
172
+ };
173
+ /**
174
+ * `media_rename_folder` — changes a folder's name and nothing else.
175
+ *
176
+ * Deliberately does not forward `parent` to `folder.update()`: the service branches on
177
+ * `isUndefined(parent)`, and the name-only branch skips the transaction that rewrites descendant
178
+ * paths. Passing the folder's existing parent would take the move branch and rewrite the whole
179
+ * subtree to compute the identical paths.
180
+ */ const createMediaRenameFolderHandler = (strapi, context)=>{
181
+ ambientInstance.assertAmbientInstance(strapi);
182
+ return async ({ args })=>{
183
+ const { id, name } = args;
184
+ permissions.assertMediaPermission(strapi, context, constants$1.ACTIONS.update, constants$1.FOLDER_MODEL_UID);
185
+ const folder = await findFolderById(strapi, id, {
186
+ withParent: true
187
+ });
188
+ if (folder === null) {
189
+ throw new utils$1.errors.NotFoundError(constants.MCP_NOT_FOUND_FOLDER);
190
+ }
191
+ // Siblings are the folders sharing this folder's parent, so uniqueness is checked against
192
+ // the current location — a rename never changes it.
193
+ const parentId = folder.parent?.id ?? null;
194
+ await assertNameAvailable(strapi, name, parentId, id);
195
+ // `parent` is omitted on purpose — see the note above.
196
+ const renamed = await ambientInstance.getFolderService(strapi).update(id, {
197
+ name
198
+ }, {
199
+ user: context.user
200
+ });
201
+ return utils.ok({
202
+ data: await readFolderForOutput(strapi, id, renamed ?? {
203
+ ...folder,
204
+ name
205
+ })
206
+ });
207
+ };
208
+ };
209
+ /**
210
+ * `media_move_folder` — re-parents a folder, carrying its whole subtree with it.
211
+ *
212
+ * `folder.update()` recalculates the materialized `path` of every descendant folder and the
213
+ * `folderPath` of every contained file inside a transaction, so the move is atomic and no
214
+ * separate bookkeeping is needed here.
215
+ */ const createMediaMoveFolderHandler = (strapi, context)=>{
216
+ ambientInstance.assertAmbientInstance(strapi);
217
+ return async ({ args })=>{
218
+ const { id, parent } = args;
219
+ permissions.assertMediaPermission(strapi, context, constants$1.ACTIONS.update, constants$1.FOLDER_MODEL_UID);
220
+ const folder = await findFolderById(strapi, id);
221
+ if (folder === null) {
222
+ throw new utils$1.errors.NotFoundError(constants.MCP_NOT_FOUND_FOLDER);
223
+ }
224
+ await assertParentExists(strapi, parent);
225
+ await assertNotMovedIntoOwnSubtree(strapi, folder, parent);
226
+ // A folder keeps its name across a move, so uniqueness must hold in the destination.
227
+ await assertNameAvailable(strapi, folder.name, parent, id);
228
+ const moved = await ambientInstance.getFolderService(strapi).update(id, {
229
+ name: folder.name,
230
+ parent
231
+ }, {
232
+ user: context.user
233
+ });
234
+ return utils.ok({
235
+ data: await readFolderForOutput(strapi, id, moved ?? folder)
236
+ });
237
+ };
238
+ };
239
+ /**
240
+ * `media_delete_folder` — previews or performs a cascading folder deletion.
241
+ *
242
+ * Two branches behind one tool, because the preview and the deletion must agree on what the
243
+ * cascade covers; splitting them across tools would let the two drift apart, and an agent could
244
+ * reach the destructive one without ever seeing a count.
245
+ *
246
+ * `dryRun` defaults to true (see the input schema): omitting the flag previews, and deleting
247
+ * takes an explicit `dryRun: false`.
248
+ */ const createMediaDeleteFolderHandler = (strapi, context)=>{
249
+ ambientInstance.assertAmbientInstance(strapi);
250
+ return async ({ args })=>{
251
+ const { ids, dryRun = true } = args;
252
+ permissions.assertMediaPermission(strapi, context, constants$1.ACTIONS.update, constants$1.FOLDER_MODEL_UID);
253
+ const matched = await strapi.db.query(constants$1.FOLDER_MODEL_UID).findMany({
254
+ select: [
255
+ 'id',
256
+ 'name',
257
+ 'path'
258
+ ],
259
+ where: {
260
+ id: {
261
+ $in: ids
262
+ }
263
+ }
264
+ });
265
+ /**
266
+ * All-or-nothing: any id that does not resolve to a folder rejects the whole call, before
267
+ * anything is deleted.
268
+ *
269
+ * Folder ids and asset ids are indistinguishable integers, so a list mixing the two is the
270
+ * mistake this tool exists to catch — and deleting the folders that did match while
271
+ * reporting the rest back would be exactly the silent confusion that requirement forbids,
272
+ * with the cascade already gone by the time the agent reads the response.
273
+ *
274
+ * An unresolvable id cannot be diagnosed further from here (an asset id and a deleted
275
+ * folder id look identical), and it does not need to be: either way it is unusable.
276
+ *
277
+ * The dry run rejects on the same rule. A preview that reported a cascade for a request the
278
+ * executing call would refuse is worse than no preview — it is a confirmation an agent
279
+ * cannot act on.
280
+ */ const matchedIds = new Set(matched.map((folder)=>folder.id));
281
+ const unresolvedIds = ids.filter((id)=>matchedIds.has(id) === false);
282
+ if (unresolvedIds.length > 0) {
283
+ throw new utils$1.errors.ValidationError(constants.MCP_DELETE_FOLDER_UNRESOLVED_IDS(unresolvedIds));
284
+ }
285
+ const folders = matched.map((folder)=>sanitizeMedia.sanitizeMediaFolder(folder));
286
+ if (dryRun) {
287
+ const counts = await countCascade(strapi, matched.map((folder)=>folder.path));
288
+ return utils.ok({
289
+ dryRun: true,
290
+ folders,
291
+ ...counts
292
+ });
293
+ }
294
+ const { totalFolderNumber, totalFileNumber } = await ambientInstance.getFolderService(strapi).deleteByIds(matched.map((folder)=>folder.id));
295
+ return utils.ok({
296
+ dryRun: false,
297
+ folders,
298
+ totalFolderNumber,
299
+ totalFileNumber
300
+ });
301
+ };
302
+ };
303
+
304
+ exports.createMediaCreateFolderHandler = createMediaCreateFolderHandler;
305
+ exports.createMediaDeleteFolderHandler = createMediaDeleteFolderHandler;
306
+ exports.createMediaMoveFolderHandler = createMediaMoveFolderHandler;
307
+ exports.createMediaRenameFolderHandler = createMediaRenameFolderHandler;
308
+ //# sourceMappingURL=folder-handlers.js.map
@@ -0,0 +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":"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"}