@unchainedshop/api 4.0.0-rc.5 → 4.0.0-rc.7

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 (205) hide show
  1. package/README.md +3 -1
  2. package/lib/errors.d.ts +2 -0
  3. package/lib/errors.d.ts.map +1 -1
  4. package/lib/errors.js +2 -0
  5. package/lib/errors.js.map +1 -1
  6. package/lib/loaders/index.d.ts +5 -0
  7. package/lib/loaders/index.d.ts.map +1 -1
  8. package/lib/loaders/index.js +2 -0
  9. package/lib/loaders/index.js.map +1 -1
  10. package/lib/loaders/productProxiesLoader.d.ts +10 -0
  11. package/lib/loaders/productProxiesLoader.d.ts.map +1 -0
  12. package/lib/loaders/productProxiesLoader.js +30 -0
  13. package/lib/loaders/productProxiesLoader.js.map +1 -0
  14. package/lib/mcp/tools/assortment/addAssortmentMediaUploadHandler.d.ts.map +1 -1
  15. package/lib/mcp/tools/assortment/addAssortmentMediaUploadHandler.js +2 -1
  16. package/lib/mcp/tools/assortment/addAssortmentMediaUploadHandler.js.map +1 -1
  17. package/lib/mcp/tools/assortment/assortmentMediaHandler.d.ts +32 -0
  18. package/lib/mcp/tools/assortment/assortmentMediaHandler.d.ts.map +1 -0
  19. package/lib/mcp/tools/assortment/assortmentMediaHandler.js +48 -0
  20. package/lib/mcp/tools/assortment/assortmentMediaHandler.js.map +1 -0
  21. package/lib/mcp/tools/assortment/index.d.ts.map +1 -1
  22. package/lib/mcp/tools/assortment/index.js +3 -1
  23. package/lib/mcp/tools/assortment/index.js.map +1 -1
  24. package/lib/mcp/tools/assortment/removeAssortmentMediaHandler.d.ts.map +1 -1
  25. package/lib/mcp/tools/assortment/removeAssortmentMediaHandler.js +3 -1
  26. package/lib/mcp/tools/assortment/removeAssortmentMediaHandler.js.map +1 -1
  27. package/lib/mcp/tools/assortment/reorderAssortmentMediaHandler.js +2 -2
  28. package/lib/mcp/tools/assortment/reorderAssortmentMediaHandler.js.map +1 -1
  29. package/lib/mcp/tools/assortment/translatedAssortmentMediaTextsHandler.d.ts.map +1 -1
  30. package/lib/mcp/tools/assortment/translatedAssortmentMediaTextsHandler.js +7 -1
  31. package/lib/mcp/tools/assortment/translatedAssortmentMediaTextsHandler.js.map +1 -1
  32. package/lib/mcp/tools/assortment/updateAssortmentMediaTextsHandler.d.ts.map +1 -1
  33. package/lib/mcp/tools/assortment/updateAssortmentMediaTextsHandler.js +4 -1
  34. package/lib/mcp/tools/assortment/updateAssortmentMediaTextsHandler.js.map +1 -1
  35. package/lib/mcp/tools/product/addProductAssignmentHandler.d.ts.map +1 -1
  36. package/lib/mcp/tools/product/addProductAssignmentHandler.js +1 -1
  37. package/lib/mcp/tools/product/addProductAssignmentHandler.js.map +1 -1
  38. package/lib/mcp/tools/product/addProductBundleItemHandler.d.ts +48 -0
  39. package/lib/mcp/tools/product/addProductBundleItemHandler.d.ts.map +1 -0
  40. package/lib/mcp/tools/product/addProductBundleItemHandler.js +52 -0
  41. package/lib/mcp/tools/product/addProductBundleItemHandler.js.map +1 -0
  42. package/lib/mcp/tools/product/addProductMediaUploadHandler.d.ts.map +1 -1
  43. package/lib/mcp/tools/product/addProductMediaUploadHandler.js +3 -2
  44. package/lib/mcp/tools/product/addProductMediaUploadHandler.js.map +1 -1
  45. package/lib/mcp/tools/product/catalogPriceHandler.d.ts +28 -0
  46. package/lib/mcp/tools/product/catalogPriceHandler.d.ts.map +1 -0
  47. package/lib/mcp/tools/product/catalogPriceHandler.js +57 -0
  48. package/lib/mcp/tools/product/catalogPriceHandler.js.map +1 -0
  49. package/lib/mcp/tools/product/index.d.ts.map +1 -1
  50. package/lib/mcp/tools/product/index.js +27 -3
  51. package/lib/mcp/tools/product/index.js.map +1 -1
  52. package/lib/mcp/tools/product/productAssignmentsHandler.d.ts +24 -0
  53. package/lib/mcp/tools/product/productAssignmentsHandler.d.ts.map +1 -0
  54. package/lib/mcp/tools/product/productAssignmentsHandler.js +79 -0
  55. package/lib/mcp/tools/product/productAssignmentsHandler.js.map +1 -0
  56. package/lib/mcp/tools/product/productBundleItemsHandler.d.ts +20 -0
  57. package/lib/mcp/tools/product/productBundleItemsHandler.d.ts.map +1 -0
  58. package/lib/mcp/tools/product/productBundleItemsHandler.js +45 -0
  59. package/lib/mcp/tools/product/productBundleItemsHandler.js.map +1 -0
  60. package/lib/mcp/tools/product/productMediaHandler.d.ts +32 -0
  61. package/lib/mcp/tools/product/productMediaHandler.d.ts.map +1 -0
  62. package/lib/mcp/tools/product/productMediaHandler.js +46 -0
  63. package/lib/mcp/tools/product/productMediaHandler.js.map +1 -0
  64. package/lib/mcp/tools/product/productSiblingsHandler.d.ts +36 -0
  65. package/lib/mcp/tools/product/productSiblingsHandler.d.ts.map +1 -0
  66. package/lib/mcp/tools/product/productSiblingsHandler.js +66 -0
  67. package/lib/mcp/tools/product/productSiblingsHandler.js.map +1 -0
  68. package/lib/mcp/tools/product/productVariationsHandler.d.ts +20 -0
  69. package/lib/mcp/tools/product/productVariationsHandler.d.ts.map +1 -0
  70. package/lib/mcp/tools/product/productVariationsHandler.js +47 -0
  71. package/lib/mcp/tools/product/productVariationsHandler.js.map +1 -0
  72. package/lib/mcp/tools/product/removeProductBundleItemHandler.d.ts +24 -0
  73. package/lib/mcp/tools/product/removeProductBundleItemHandler.d.ts.map +1 -0
  74. package/lib/mcp/tools/product/removeProductBundleItemHandler.js +46 -0
  75. package/lib/mcp/tools/product/removeProductBundleItemHandler.js.map +1 -0
  76. package/lib/mcp/tools/product/removeProductMediaHandler.js +1 -1
  77. package/lib/mcp/tools/product/removeProductMediaHandler.js.map +1 -1
  78. package/lib/mcp/tools/product/reorderProductMediaHandler.js +2 -2
  79. package/lib/mcp/tools/product/reorderProductMediaHandler.js.map +1 -1
  80. package/lib/mcp/tools/product/simulatedPriceHandler.d.ts +70 -0
  81. package/lib/mcp/tools/product/simulatedPriceHandler.d.ts.map +1 -0
  82. package/lib/mcp/tools/product/simulatedPriceHandler.js +74 -0
  83. package/lib/mcp/tools/product/simulatedPriceHandler.js.map +1 -0
  84. package/lib/mcp/tools/product/simulatedPriceRangeHandler.d.ts +74 -0
  85. package/lib/mcp/tools/product/simulatedPriceRangeHandler.d.ts.map +1 -0
  86. package/lib/mcp/tools/product/simulatedPriceRangeHandler.js +103 -0
  87. package/lib/mcp/tools/product/simulatedPriceRangeHandler.js.map +1 -0
  88. package/lib/mcp/tools/product/translatedProductMediaTextsHandler.d.ts.map +1 -1
  89. package/lib/mcp/tools/product/translatedProductMediaTextsHandler.js +7 -1
  90. package/lib/mcp/tools/product/translatedProductMediaTextsHandler.js.map +1 -1
  91. package/lib/mcp/tools/product/translatedProductVariationTextsHandler.d.ts.map +1 -1
  92. package/lib/mcp/tools/product/translatedProductVariationTextsHandler.js +7 -1
  93. package/lib/mcp/tools/product/translatedProductVariationTextsHandler.js.map +1 -1
  94. package/lib/mcp/tools/product/updateProductMediaTextsHandler.js +1 -1
  95. package/lib/mcp/tools/product/updateProductMediaTextsHandler.js.map +1 -1
  96. package/lib/mcp/tools/product/updateProductPlanHandler.d.ts +47 -26
  97. package/lib/mcp/tools/product/updateProductPlanHandler.d.ts.map +1 -1
  98. package/lib/mcp/tools/product/updateProductPlanHandler.js +23 -7
  99. package/lib/mcp/tools/product/updateProductPlanHandler.js.map +1 -1
  100. package/lib/mcp/tools/product/updateProductVariationTextsHandler.d.ts +73 -0
  101. package/lib/mcp/tools/product/updateProductVariationTextsHandler.d.ts.map +1 -0
  102. package/lib/mcp/tools/product/updateProductVariationTextsHandler.js +52 -0
  103. package/lib/mcp/tools/product/updateProductVariationTextsHandler.js.map +1 -0
  104. package/lib/mcp/tools/product/variationProductsHandler.d.ts +52 -0
  105. package/lib/mcp/tools/product/variationProductsHandler.d.ts.map +1 -0
  106. package/lib/mcp/tools/product/variationProductsHandler.js +54 -0
  107. package/lib/mcp/tools/product/variationProductsHandler.js.map +1 -0
  108. package/lib/mcp/tools/shopInfoHandler.d.ts.map +1 -1
  109. package/lib/mcp/tools/shopInfoHandler.js +4 -2
  110. package/lib/mcp/tools/shopInfoHandler.js.map +1 -1
  111. package/lib/mcp/utils/getNormalizedProductDetails.d.ts +1 -1
  112. package/lib/mcp/utils/getNormalizedProductDetails.d.ts.map +1 -1
  113. package/lib/mcp/utils/getNormalizedProductDetails.js +25 -0
  114. package/lib/mcp/utils/getNormalizedProductDetails.js.map +1 -1
  115. package/lib/resolvers/mutations/filters/createFilter.d.ts.map +1 -1
  116. package/lib/resolvers/mutations/filters/createFilter.js +14 -5
  117. package/lib/resolvers/mutations/filters/createFilter.js.map +1 -1
  118. package/lib/resolvers/mutations/products/createProductBundleItem.d.ts.map +1 -1
  119. package/lib/resolvers/mutations/products/createProductBundleItem.js +7 -2
  120. package/lib/resolvers/mutations/products/createProductBundleItem.js.map +1 -1
  121. package/lib/resolvers/type/index.d.ts +9 -0
  122. package/lib/resolvers/type/index.d.ts.map +1 -1
  123. package/lib/resolvers/type/index.js +2 -0
  124. package/lib/resolvers/type/index.js.map +1 -1
  125. package/lib/resolvers/type/product/product-bundle-types.d.ts +1 -0
  126. package/lib/resolvers/type/product/product-bundle-types.d.ts.map +1 -1
  127. package/lib/resolvers/type/product/product-configurable-or-bundle-product-types.d.ts +5 -0
  128. package/lib/resolvers/type/product/product-configurable-or-bundle-product-types.d.ts.map +1 -0
  129. package/lib/resolvers/type/product/product-configurable-or-bundle-product-types.js +9 -0
  130. package/lib/resolvers/type/product/product-configurable-or-bundle-product-types.js.map +1 -0
  131. package/lib/resolvers/type/product/product-configurable-types.d.ts +1 -0
  132. package/lib/resolvers/type/product/product-configurable-types.d.ts.map +1 -1
  133. package/lib/resolvers/type/product/product-plan-types.d.ts +1 -0
  134. package/lib/resolvers/type/product/product-plan-types.d.ts.map +1 -1
  135. package/lib/resolvers/type/product/product-simple-types.d.ts +1 -0
  136. package/lib/resolvers/type/product/product-simple-types.d.ts.map +1 -1
  137. package/lib/resolvers/type/product/product-tokenized-types.d.ts +1 -0
  138. package/lib/resolvers/type/product/product-tokenized-types.d.ts.map +1 -1
  139. package/lib/resolvers/type/product/product-types.d.ts +1 -0
  140. package/lib/resolvers/type/product/product-types.d.ts.map +1 -1
  141. package/lib/resolvers/type/product/product-types.js +3 -0
  142. package/lib/resolvers/type/product/product-types.js.map +1 -1
  143. package/lib/schema/types/product/bundle-product.d.ts.map +1 -1
  144. package/lib/schema/types/product/bundle-product.js +2 -1
  145. package/lib/schema/types/product/bundle-product.js.map +1 -1
  146. package/lib/schema/types/product/configurable-product.d.ts.map +1 -1
  147. package/lib/schema/types/product/configurable-product.js +1 -0
  148. package/lib/schema/types/product/configurable-product.js.map +1 -1
  149. package/lib/schema/types/product/plan-product.d.ts.map +1 -1
  150. package/lib/schema/types/product/plan-product.js +1 -0
  151. package/lib/schema/types/product/plan-product.js.map +1 -1
  152. package/lib/schema/types/product/product.d.ts.map +1 -1
  153. package/lib/schema/types/product/product.js +3 -0
  154. package/lib/schema/types/product/product.js.map +1 -1
  155. package/lib/schema/types/product/simple-product.d.ts.map +1 -1
  156. package/lib/schema/types/product/simple-product.js +1 -0
  157. package/lib/schema/types/product/simple-product.js.map +1 -1
  158. package/lib/schema/types/product/tokenized-product.d.ts.map +1 -1
  159. package/lib/schema/types/product/tokenized-product.js +1 -0
  160. package/lib/schema/types/product/tokenized-product.js.map +1 -1
  161. package/package.json +6 -6
  162. package/src/errors.ts +10 -0
  163. package/src/loaders/index.ts +2 -0
  164. package/src/loaders/productProxiesLoader.ts +37 -0
  165. package/src/mcp/tools/assortment/addAssortmentMediaUploadHandler.ts +2 -1
  166. package/src/mcp/tools/assortment/assortmentMediaHandler.ts +57 -0
  167. package/src/mcp/tools/assortment/index.ts +9 -1
  168. package/src/mcp/tools/assortment/removeAssortmentMediaHandler.ts +3 -1
  169. package/src/mcp/tools/assortment/reorderAssortmentMediaHandler.ts +2 -2
  170. package/src/mcp/tools/assortment/translatedAssortmentMediaTextsHandler.ts +8 -1
  171. package/src/mcp/tools/assortment/updateAssortmentMediaTextsHandler.ts +4 -1
  172. package/src/mcp/tools/product/addProductAssignmentHandler.ts +3 -1
  173. package/src/mcp/tools/product/addProductBundleItemHandler.ts +64 -0
  174. package/src/mcp/tools/product/addProductMediaUploadHandler.ts +3 -2
  175. package/src/mcp/tools/product/catalogPriceHandler.ts +61 -0
  176. package/src/mcp/tools/product/index.ts +104 -3
  177. package/src/mcp/tools/product/productAssignmentsHandler.ts +86 -0
  178. package/src/mcp/tools/product/productBundleItemsHandler.ts +50 -0
  179. package/src/mcp/tools/product/productMediaHandler.ts +53 -0
  180. package/src/mcp/tools/product/productSiblingsHandler.ts +82 -0
  181. package/src/mcp/tools/product/productVariationsHandler.ts +53 -0
  182. package/src/mcp/tools/product/removeProductBundleItemHandler.ts +55 -0
  183. package/src/mcp/tools/product/removeProductMediaHandler.ts +1 -1
  184. package/src/mcp/tools/product/reorderProductMediaHandler.ts +2 -2
  185. package/src/mcp/tools/product/simulatedPriceHandler.ts +82 -0
  186. package/src/mcp/tools/product/simulatedPriceRangeHandler.ts +126 -0
  187. package/src/mcp/tools/product/translatedProductMediaTextsHandler.ts +6 -2
  188. package/src/mcp/tools/product/translatedProductVariationTextsHandler.ts +6 -1
  189. package/src/mcp/tools/product/updateProductMediaTextsHandler.ts +1 -1
  190. package/src/mcp/tools/product/updateProductPlanHandler.ts +37 -7
  191. package/src/mcp/tools/product/updateProductVariationTextsHandler.ts +65 -0
  192. package/src/mcp/tools/product/variationProductsHandler.ts +61 -0
  193. package/src/mcp/tools/shopInfoHandler.ts +4 -2
  194. package/src/mcp/utils/getNormalizedProductDetails.ts +28 -0
  195. package/src/resolvers/mutations/filters/createFilter.ts +11 -6
  196. package/src/resolvers/mutations/products/createProductBundleItem.ts +13 -3
  197. package/src/resolvers/type/index.ts +2 -0
  198. package/src/resolvers/type/product/product-configurable-or-bundle-product-types.ts +10 -0
  199. package/src/resolvers/type/product/product-types.ts +4 -0
  200. package/src/schema/types/product/bundle-product.ts +2 -1
  201. package/src/schema/types/product/configurable-product.ts +1 -0
  202. package/src/schema/types/product/plan-product.ts +1 -0
  203. package/src/schema/types/product/product.ts +3 -0
  204. package/src/schema/types/product/simple-product.ts +1 -0
  205. package/src/schema/types/product/tokenized-product.ts +1 -0
package/src/errors.ts CHANGED
@@ -230,6 +230,11 @@ export const CyclicAssortmentLinkNotSupportedError = createError(
230
230
  'Cyclic assortment link detected, make sure child assortment is not assigned as a parent on the assortment graph',
231
231
  );
232
232
 
233
+ export const CyclicProductBundlingNotSupportedError = createError(
234
+ 'CyclicBundlingNotSupportedError',
235
+ 'Cyclic bundling detected, make sure bundled product is not the same as the bundle product itself',
236
+ );
237
+
233
238
  export const EmailAlreadyExistsError = createError(
234
239
  'EmailAlreadyExists',
235
240
  'Email already exists or is invalid',
@@ -319,3 +324,8 @@ export const ImpersonatingAdminUserError = createError(
319
324
  'ImpersonatingAdminUserError',
320
325
  'Can not impersonate a admin user account',
321
326
  );
327
+
328
+ export const DuplicateFilterKeyError = createError(
329
+ 'DuplicateFilterKeyError',
330
+ 'Key already registered for another filter',
331
+ );
@@ -14,6 +14,7 @@ import productLoaderBySKU from './productLoaderBySKU.js';
14
14
  import productTextLoader from './productTextLoader.js';
15
15
  import productMediaTextLoader from './productMediaTextLoader.js';
16
16
  import productMediasLoader from './productMediasLoader.js';
17
+ import productProxiesLoader from './productProxiesLoader.js';
17
18
  import fileLoader from './fileLoader.js';
18
19
  import userLoader from './userLoader.js';
19
20
  import countryLoader from './countryLoader.js';
@@ -42,6 +43,7 @@ const loaders = (unchainedAPI: UnchainedCore) => {
42
43
  productTextLoader: productTextLoader(unchainedAPI),
43
44
  productMediaTextLoader: productMediaTextLoader(unchainedAPI),
44
45
  productMediasLoader: productMediasLoader(unchainedAPI),
46
+ productProxiesLoader: productProxiesLoader(unchainedAPI),
45
47
 
46
48
  fileLoader: fileLoader(unchainedAPI),
47
49
 
@@ -0,0 +1,37 @@
1
+ import { UnchainedCore } from '@unchainedshop/core';
2
+ import { Product } from '@unchainedshop/core-products';
3
+ import DataLoader from 'dataloader';
4
+
5
+ export default (unchainedAPI: UnchainedCore) =>
6
+ new DataLoader<{ productId?: string }, Product[]>(async (queries) => {
7
+ const productIds = [...new Set(queries.map((q) => q.productId).filter(Boolean))];
8
+
9
+ const productProxies = await unchainedAPI.modules.products.findProducts({
10
+ includeDrafts: true,
11
+ productSelector: {
12
+ $or: [
13
+ {
14
+ 'bundleItems.productId': { $in: productIds },
15
+ },
16
+ {
17
+ 'proxy.assignments.productId': { $in: productIds },
18
+ },
19
+ ],
20
+ },
21
+ });
22
+
23
+ const productProxyMap = {};
24
+ for (const productProxy of productProxies) {
25
+ const bundleItemIds = productProxy.bundleItems?.map((item) => item.productId) || [];
26
+ const proxyAssignmentIds =
27
+ productProxy.proxy?.assignments?.map((assignment) => assignment.productId) || [];
28
+
29
+ for (const productId of [...bundleItemIds, ...proxyAssignmentIds]) {
30
+ if (!productProxyMap[productId]) {
31
+ productProxyMap[productId] = [];
32
+ }
33
+ productProxyMap[productId].push(productProxy);
34
+ }
35
+ }
36
+ return queries.map((q) => productProxyMap[q.productId] || []);
37
+ });
@@ -2,6 +2,7 @@ import { z } from 'zod';
2
2
  import { Context } from '../../../context.js';
3
3
  import { log } from '@unchainedshop/logger';
4
4
  import { FileNotFoundError, FileUploadExpiredError } from '../../../errors.js';
5
+ import normalizeMediaUrl from '../../utils/normalizeMediaUrl.js';
5
6
 
6
7
  export const AddAssortmentMediaUploadSchema = {
7
8
  mediaName: z
@@ -61,7 +62,7 @@ export async function addAssortmentMediaUploadHandler(
61
62
  {
62
63
  type: 'text' as const,
63
64
  text: JSON.stringify({
64
- file: linked,
65
+ file: await normalizeMediaUrl([{ ...linked, mediaId: linked._id }], context),
65
66
  }),
66
67
  },
67
68
  ],
@@ -0,0 +1,57 @@
1
+ import { Context } from '../../../context.js';
2
+ import { log } from '@unchainedshop/logger';
3
+ import { z } from 'zod';
4
+ import normalizeMediaUrl from '../../utils/normalizeMediaUrl.js';
5
+
6
+ export const AssortmentMediaSchema = {
7
+ assortmentId: z.string().min(1).describe('ID of the Assortment to retrieve media for'),
8
+ limit: z.number().int().min(1).max(100).default(10).describe('Number of media items to return'),
9
+ offset: z.number().int().min(0).default(0).describe('Pagination offset'),
10
+ tags: z.array(z.string().min(1)).optional().describe('Filter media by lowercase tags'),
11
+ };
12
+
13
+ export const AssortmentMediaZodSchema = z
14
+ .object(AssortmentMediaSchema)
15
+ .describe(
16
+ 'Retrieve media assets associated with a assortment, optionally filtered by tags and paginated.',
17
+ );
18
+
19
+ export type assortmentMediaParams = z.infer<typeof AssortmentMediaZodSchema>;
20
+
21
+ export async function assortmentMediaHandler(context: Context, params: assortmentMediaParams) {
22
+ const { assortmentId, limit, offset, tags } = params;
23
+ const { modules, loaders, userId } = context;
24
+
25
+ try {
26
+ log('handler assortmentMediaHandler', { userId, params });
27
+ let media = [];
28
+ if (offset || tags) {
29
+ media = await modules.assortments.media.findAssortmentMedias({
30
+ assortmentId,
31
+ ...params,
32
+ });
33
+ } else {
34
+ media = (await loaders.assortmentMediasLoader.load({ assortmentId })).slice(
35
+ offset,
36
+ offset + limit,
37
+ );
38
+ }
39
+ return {
40
+ content: [
41
+ {
42
+ type: 'text' as const,
43
+ text: JSON.stringify({ media: await normalizeMediaUrl(media, context) }),
44
+ },
45
+ ],
46
+ };
47
+ } catch (error) {
48
+ return {
49
+ content: [
50
+ {
51
+ type: 'text' as const,
52
+ text: `Error retrieving assortment media: ${(error as Error).message}`,
53
+ },
54
+ ],
55
+ };
56
+ }
57
+ }
@@ -73,6 +73,7 @@ import {
73
73
  UpdateAssortmentTextsSchema,
74
74
  updateAssortmentTextsHandler,
75
75
  } from './updateAssortmentTextsHandler.js';
76
+ import { assortmentMediaHandler, AssortmentMediaSchema } from './assortmentMediaHandler.js';
76
77
 
77
78
  export const registerAssortmentTools = (server: McpServer, context: Context) => {
78
79
  server.tool(
@@ -202,7 +203,7 @@ export const registerAssortmentTools = (server: McpServer, context: Context) =>
202
203
  );
203
204
 
204
205
  server.tool(
205
- 'assortment_reorderMedia',
206
+ 'assortment_reorderMedias',
206
207
  'Reorder assortment media assets with new sorting keys; first item becomes primary media.',
207
208
  ReorderAssortmentMediaSchema,
208
209
  async (params) => reorderAssortmentMediaHandler(context, params),
@@ -270,4 +271,11 @@ export const registerAssortmentTools = (server: McpServer, context: Context) =>
270
271
  ReorderAssortmentFiltersSchema,
271
272
  async (params) => reorderAssortmentFiltersHandler(context, params),
272
273
  );
274
+
275
+ server.tool(
276
+ 'assortment_getMedias',
277
+ 'Retrieve media assets associated with a assortment, optionally filtered by tags and paginated.',
278
+ AssortmentMediaSchema,
279
+ async (params) => assortmentMediaHandler(context, params),
280
+ );
273
281
  };
@@ -36,7 +36,9 @@ export async function removeAssortmentMediaHandler(
36
36
  content: [
37
37
  {
38
38
  type: 'text' as const,
39
- text: JSON.stringify({ assortmentMedia }),
39
+ text: JSON.stringify({
40
+ success: true,
41
+ }),
40
42
  },
41
43
  ],
42
44
  };
@@ -30,7 +30,7 @@ export async function reorderAssortmentMediaHandler(
30
30
  try {
31
31
  log('handler reorderAssortmentMediaHandler', { userId, params });
32
32
 
33
- await modules.assortments.media.updateManualOrder({
33
+ const media = await modules.assortments.media.updateManualOrder({
34
34
  sortKeys,
35
35
  } as any);
36
36
 
@@ -38,7 +38,7 @@ export async function reorderAssortmentMediaHandler(
38
38
  content: [
39
39
  {
40
40
  type: 'text' as const,
41
- text: JSON.stringify({ success: true }),
41
+ text: JSON.stringify({ media }),
42
42
  },
43
43
  ],
44
44
  };
@@ -1,6 +1,7 @@
1
1
  import { z } from 'zod';
2
2
  import { Context } from '../../../context.js';
3
3
  import { log } from '@unchainedshop/logger';
4
+ import { AssortmentMediaNotFoundError } from '../../../errors.js';
4
5
 
5
6
  export const TranslatedAssortmentMediaTextsSchema = {
6
7
  assortmentMediaId: z.string().min(1).describe('ID of the assortment media to retrieve texts for'),
@@ -22,6 +23,12 @@ export async function translatedAssortmentMediaTextsHandler(
22
23
  try {
23
24
  log('handler translatedAssortmentMediaTextsHandler', { userId, params });
24
25
 
26
+ const assortmentMedia = await modules.assortments.media.findAssortmentMedia({
27
+ assortmentMediaId,
28
+ });
29
+
30
+ if (!assortmentMedia) throw new AssortmentMediaNotFoundError({ assortmentMediaId });
31
+
25
32
  const texts = await modules.assortments.media.texts.findMediaTexts({
26
33
  assortmentMediaId,
27
34
  });
@@ -30,7 +37,7 @@ export async function translatedAssortmentMediaTextsHandler(
30
37
  content: [
31
38
  {
32
39
  type: 'text' as const,
33
- text: JSON.stringify({ texts }),
40
+ text: JSON.stringify({ texts, assortmentId: assortmentMedia.assortmentId }),
34
41
  },
35
42
  ],
36
43
  };
@@ -47,7 +47,10 @@ export async function updateAssortmentMediaTextsHandler(
47
47
  content: [
48
48
  {
49
49
  type: 'text' as const,
50
- text: JSON.stringify({ assortmentMediaTexts }),
50
+ text: JSON.stringify({
51
+ texts: assortmentMediaTexts,
52
+ assortmentId: assortmentMedia?.assortmentId,
53
+ }),
51
54
  },
52
55
  ],
53
56
  };
@@ -51,7 +51,9 @@ export const AssignProductVariationSchema = {
51
51
  vectors: z
52
52
  .array(ProductAssignmentVectorSchema)
53
53
  .min(1)
54
- .describe('Combination of attributes uniquely identifying the variant'),
54
+ .describe(
55
+ 'Provide one option for **each** variation of the parent product (e.g., Color and Size). if it is not complete request the user to add all options or offer to create it. Do not call this tool unless all variation options have been selected. Otherwise, this will fail.',
56
+ ),
55
57
  };
56
58
 
57
59
  export const AddProductAssignmentZodSchema = z.object(AssignProductVariationSchema);
@@ -0,0 +1,64 @@
1
+ import { z } from 'zod';
2
+ import { Context } from '../../../context.js';
3
+ import { ProductNotFoundError, ProductWrongTypeError } from '../../../errors.js';
4
+ import { log } from '@unchainedshop/logger';
5
+ import { getNormalizedProductDetails } from '../../utils/getNormalizedProductDetails.js';
6
+ import { ProductTypes } from '@unchainedshop/core-products';
7
+
8
+ const AddProductBundleItemInputSchema = z.object({
9
+ productId: z.string().min(1),
10
+ quantity: z.number().int().min(1),
11
+ });
12
+
13
+ export const AddProductBundleItemSchema = {
14
+ productId: z.string().min(1).describe('ID of the BUNDLE_PRODUCT to which the item will be added'),
15
+ item: AddProductBundleItemInputSchema,
16
+ };
17
+
18
+ export const AddProductBundleItemZodSchema = z.object(AddProductBundleItemSchema);
19
+
20
+ export type AddProductBundleItemParams = z.infer<typeof AddProductBundleItemZodSchema>;
21
+ export const addProductBundleItemHandler = async (
22
+ context: Context,
23
+ params: AddProductBundleItemParams,
24
+ ) => {
25
+ const { modules, userId } = context;
26
+ const { productId, item } = params;
27
+
28
+ log('handler addProductBundleItemHandler', { userId, params });
29
+
30
+ try {
31
+ const product = await modules.products.findProduct({ productId });
32
+ if (!product) throw new ProductNotFoundError({ productId });
33
+
34
+ if (product.type !== ProductTypes.BundleProduct)
35
+ throw new ProductWrongTypeError({
36
+ productId,
37
+ received: product.type,
38
+ required: ProductTypes.BundleProduct,
39
+ });
40
+
41
+ if (!(await modules.products.productExists({ productId: item.productId })))
42
+ throw new ProductNotFoundError({ productId: item.productId });
43
+
44
+ await modules.products.bundleItems.addBundleItem(productId, item as any);
45
+
46
+ return {
47
+ content: [
48
+ {
49
+ type: 'text' as const,
50
+ text: JSON.stringify({ product: await getNormalizedProductDetails(productId, context) }),
51
+ },
52
+ ],
53
+ };
54
+ } catch (error) {
55
+ return {
56
+ content: [
57
+ {
58
+ type: 'text' as const,
59
+ text: `Error adding bundle item: ${(error as Error).message}`,
60
+ },
61
+ ],
62
+ };
63
+ }
64
+ };
@@ -2,6 +2,7 @@ import { z } from 'zod';
2
2
  import { Context } from '../../../context.js';
3
3
  import { log } from '@unchainedshop/logger';
4
4
  import { FileNotFoundError, FileUploadExpiredError } from '../../../errors.js';
5
+ import normalizeMediaUrl from '../../utils/normalizeMediaUrl.js';
5
6
 
6
7
  export const AddProductMediaUploadSchema = {
7
8
  mediaName: z
@@ -24,7 +25,7 @@ export async function addProductMediaUploadHandler(
24
25
  const { modules, services, userId } = context;
25
26
 
26
27
  try {
27
- log('handler addProductAssignmentHandler', { userId, params });
28
+ log('handler addProductMediaUploadHandler', { userId, params });
28
29
  const {
29
30
  _id: fileId,
30
31
  putURL,
@@ -61,7 +62,7 @@ export async function addProductMediaUploadHandler(
61
62
  {
62
63
  type: 'text' as const,
63
64
  text: JSON.stringify({
64
- file: linked,
65
+ file: await normalizeMediaUrl([{ ...linked, mediaId: linked._id }], context),
65
66
  }),
66
67
  },
67
68
  ],
@@ -0,0 +1,61 @@
1
+ import { Context } from '../../../context.js';
2
+ import { ProductNotFoundError } from '../../../errors.js';
3
+ import { getNormalizedProductDetails } from '../../utils/getNormalizedProductDetails.js';
4
+ import { log } from '@unchainedshop/logger';
5
+ import { z } from 'zod';
6
+
7
+ export const CatalogPriceSchema = {
8
+ productId: z.string().min(1).describe('ID of the product to retrieve catalog price for'),
9
+ quantity: z
10
+ .number()
11
+ .int()
12
+ .positive()
13
+ .default(1)
14
+ .describe('Quantity to calculate catalog price for (default is 1)'),
15
+ currencyCode: z
16
+ .string()
17
+ .optional()
18
+ .describe('Optional currency code (e.g., "USD") for price calculation'),
19
+ };
20
+
21
+ export const CatalogPriceZodSchema = z.object(CatalogPriceSchema);
22
+ export type CatalogPriceParams = z.infer<typeof CatalogPriceZodSchema>;
23
+
24
+ export async function catalogPriceHandler(context: Context, params: CatalogPriceParams) {
25
+ const { productId, quantity, currencyCode: forcedCurrencyCode } = params;
26
+ const { modules, countryCode, currencyCode: defaultCurrencyCode, userId } = context;
27
+
28
+ try {
29
+ log('handler catalogPriceHandler', { userId, params });
30
+ const product = await getNormalizedProductDetails(productId, context);
31
+ if (!product) throw new ProductNotFoundError({ productId });
32
+ const currencyCode = forcedCurrencyCode || defaultCurrencyCode;
33
+ const catalogPrice = await modules.products.prices.price(product, {
34
+ countryCode,
35
+ currencyCode,
36
+ quantity,
37
+ });
38
+ const leveledCatalogPrice = await modules.products.prices.catalogPricesLeveled(product, {
39
+ currencyCode,
40
+ countryCode,
41
+ });
42
+ return {
43
+ content: [
44
+ {
45
+ type: 'text' as const,
46
+ text: JSON.stringify({ product, price: catalogPrice, leveledCatalogPrice }),
47
+ },
48
+ ],
49
+ };
50
+ } catch (error) {
51
+ return {
52
+ content: [
53
+ {
54
+ type: 'text' as const,
55
+
56
+ text: `Error retrieving catalog price: ${(error as Error).message}`,
57
+ },
58
+ ],
59
+ };
60
+ }
61
+ }
@@ -71,6 +71,27 @@ import {
71
71
  translatedProductVariationTextsHandler,
72
72
  TranslatedProductVariationTextsSchema,
73
73
  } from './translatedProductVariationTextsHandler.js';
74
+ import {
75
+ updateProductVariationTextsHandler,
76
+ UpdateProductVariationTextsSchema,
77
+ } from './updateProductVariationTextsHandler.js';
78
+ import { productSiblingsHandler, ProductSiblingsSchema } from './productSiblingsHandler.js';
79
+ import { productMediaHandler, ProductMediaSchema } from './productMediaHandler.js';
80
+ import { variationProductsHandler, VariationProductsSchema } from './variationProductsHandler.js';
81
+ import { productVariationsHandler, ProductVariationsSchema } from './productVariationsHandler.js';
82
+ import {
83
+ addProductBundleItemHandler,
84
+ AddProductBundleItemSchema,
85
+ } from './addProductBundleItemHandler.js';
86
+ import {
87
+ removeProductBundleItemHandler,
88
+ RemoveProductBundleItemSchema,
89
+ } from './removeProductBundleItemHandler.js';
90
+ import { productAssignmentsHandler, ProductAssignmentsSchema } from './productAssignmentsHandler.js';
91
+ import { productBundleItemsHandler, ProductBundleItemsSchema } from './productBundleItemsHandler.js';
92
+ import { simulatedPriceRangeHandler, SimulatedPriceRangeSchema } from './simulatedPriceRangeHandler.js';
93
+ import { simulatedPriceHandler, SimulatedPriceSchema } from './simulatedPriceHandler.js';
94
+ import { catalogPriceHandler, CatalogPriceSchema } from './catalogPriceHandler.js';
74
95
 
75
96
  export const registerProductTools = (server: McpServer, context: Context) => {
76
97
  server.tool(
@@ -161,7 +182,7 @@ export const registerProductTools = (server: McpServer, context: Context) => {
161
182
  );
162
183
  server.tool(
163
184
  'product_updatePlan',
164
- 'Update the plan details for a PLAN_PRODUCT type product',
185
+ 'Update the subscription plan configuration for a product of type PLAN. This includes usage calculation type (LICENSED or METERED), billing interval settings, and optional trial period configuration. This operation is only valid for products with type PLAN_PRODUCT.',
165
186
  UpdateProductPlanSchema,
166
187
  async (params) => updateProductPlanHandler(context, params),
167
188
  );
@@ -182,7 +203,7 @@ export const registerProductTools = (server: McpServer, context: Context) => {
182
203
 
183
204
  server.tool(
184
205
  'product_assignVariation',
185
- 'Assign a product variant to a configurable product with a unique combination of attribute vectors.',
206
+ 'Assign a product variant to a configurable product with a unique combination of option vectors. assigning with incomplete vector will fail. make sure all variation options are selected before calling this tool.',
186
207
  AssignProductVariationSchema,
187
208
  async (params) => addProductAssignmentHandler(context, params),
188
209
  );
@@ -230,7 +251,7 @@ export const registerProductTools = (server: McpServer, context: Context) => {
230
251
  );
231
252
 
232
253
  server.tool(
233
- 'product_media_reorder',
254
+ 'product_reorderMedias',
234
255
  'Reorder media assets of a product by providing new sort keys.',
235
256
  ReorderProductMediaSchema,
236
257
  async (params) => reorderProductMediaHandler(context, params),
@@ -269,4 +290,84 @@ export const registerProductTools = (server: McpServer, context: Context) => {
269
290
  TranslatedProductVariationTextsSchema,
270
291
  async (params) => translatedProductVariationTextsHandler(context, params),
271
292
  );
293
+ server.tool(
294
+ 'productVariation_updateTexts',
295
+ 'Update product variation texts with the specified locales for product variations that match the provided variation ID and production option value.',
296
+ UpdateProductVariationTextsSchema,
297
+ async (params) => updateProductVariationTextsHandler(context, params),
298
+ );
299
+ server.tool(
300
+ 'product_siblings',
301
+ 'Retrieve sibling products of a given product, optionally filtered by assortment ID, with support for pagination and inclusion of inactive products.',
302
+ ProductSiblingsSchema,
303
+ async (params) => productSiblingsHandler(context, params),
304
+ );
305
+
306
+ server.tool(
307
+ 'product_getMedias',
308
+ 'Retrieve media assets associated with a product, optionally filtered by tags and paginated.',
309
+ ProductMediaSchema,
310
+ async (params) => productMediaHandler(context, params),
311
+ );
312
+ server.tool(
313
+ 'product_variationProducts',
314
+ 'Retrieve all variant products of a configurable product that exactly match the provided combination of variation key-value pairs (e.g., Color: Red, Size: M). You must provide at least one vector. Optionally include inactive variants in the result.',
315
+ VariationProductsSchema,
316
+ async (params) => variationProductsHandler(context, params),
317
+ );
318
+
319
+ server.tool(
320
+ 'product_variations',
321
+ 'Retrieve all defined variation attributes (e.g., Color, Size) and their possible values for a configurable product.',
322
+ ProductVariationsSchema,
323
+ async (params) => productVariationsHandler(context, params),
324
+ );
325
+
326
+ server.tool(
327
+ 'product_addBundleItem',
328
+ 'Adds a product as a bundled item to another product. Only works if the target product is of type BUNDLE_PRODUCT. You must provide the ID of the bundle and the ID and quantity of the item to include.',
329
+ AddProductBundleItemSchema,
330
+ async (params) => addProductBundleItemHandler(context, params),
331
+ );
332
+
333
+ server.tool(
334
+ 'product_removeBundleItem',
335
+ 'Removes a bundled product from a BUNDLE_PRODUCT using its 0-based index in the bundle list. The product must be of type BUNDLE_PRODUCT.',
336
+ RemoveProductBundleItemSchema,
337
+ async (params) => removeProductBundleItemHandler(context, params),
338
+ );
339
+
340
+ server.tool(
341
+ 'product_assignments',
342
+ 'Retrieve the full assignment matrix of a configurable product, including all assigned variants by their variation vectors. Optionally include inactive variants.',
343
+ ProductAssignmentsSchema,
344
+ async (params) => productAssignmentsHandler(context, params),
345
+ );
346
+
347
+ server.tool(
348
+ 'product_bundleItems',
349
+ 'Retrieve all bundle items from a product of type BUNDLE. Each item includes the configuration and quantity.',
350
+ ProductBundleItemsSchema,
351
+ async (params) => productBundleItemsHandler(context, params),
352
+ );
353
+
354
+ server.tool(
355
+ 'product_simulatedPriceRange',
356
+ 'Simulate the price range for a CONFIGURABLE_PRODUCT and its variant combinations based on quantity, variation selections, and pricing rules. Only applicable to configurable products.',
357
+ SimulatedPriceRangeSchema,
358
+ async (params) => simulatedPriceRangeHandler(context, params),
359
+ );
360
+ server.tool(
361
+ 'product_simulatedPrice',
362
+ 'Simulate the price for a product given a complete configuration vector and quantity. Useful for previewing dynamic pricing rules.',
363
+ SimulatedPriceSchema,
364
+ async (params) => simulatedPriceHandler(context, params),
365
+ );
366
+
367
+ server.tool(
368
+ 'product_catalogPrice',
369
+ 'Retrieve the catalog price for any product type, optionally for a specific quantity and currency. when ask for product price without specifying what, always use this tool',
370
+ CatalogPriceSchema,
371
+ async (params) => catalogPriceHandler(context, params),
372
+ );
272
373
  };
@@ -0,0 +1,86 @@
1
+ import { Context } from '../../../context.js';
2
+ import { ProductNotFoundError, ProductWrongTypeError } from '../../../errors.js';
3
+ import { ProductTypes } from '@unchainedshop/core-products';
4
+ import { log } from '@unchainedshop/logger';
5
+ import { z } from 'zod';
6
+ import normalizeMediaUrl from '../../utils/normalizeMediaUrl.js';
7
+ import { getNormalizedProductDetails } from '../../utils/getNormalizedProductDetails.js';
8
+
9
+ export const ProductAssignmentsSchema = {
10
+ productId: z
11
+ .string()
12
+ .min(1)
13
+ .describe('ID of the CONFIGURABLE_PRODUCT whose assignments should be fetched'),
14
+ includeInactive: z
15
+ .boolean()
16
+ .optional()
17
+ .default(false)
18
+ .describe('Whether to include inactive assigned products'),
19
+ };
20
+
21
+ export const ProductAssignmentsZodSchema = z.object(ProductAssignmentsSchema);
22
+ export type ProductAssignmentsParams = z.infer<typeof ProductAssignmentsZodSchema>;
23
+
24
+ export const productAssignmentsHandler = async (context: Context, params: ProductAssignmentsParams) => {
25
+ const { modules, userId, loaders, locale } = context;
26
+ const { productId, includeInactive } = params;
27
+
28
+ log('handler productAssignmentsHandler', { userId, params });
29
+ try {
30
+ const product = await getNormalizedProductDetails(productId, context);
31
+ if (!product) throw new ProductNotFoundError({ productId });
32
+
33
+ if (product.type !== ProductTypes.ConfigurableProduct) {
34
+ throw new ProductWrongTypeError({
35
+ productId,
36
+ received: product.type,
37
+ required: ProductTypes.ConfigurableProduct,
38
+ });
39
+ }
40
+
41
+ const assignments = await modules.products.proxyAssignments(product, { includeInactive });
42
+ const normalizedAssignments = await Promise.all(
43
+ assignments.map(async ({ assignment, ...rest }) => {
44
+ const product = await loaders.productLoader.load({
45
+ productId: assignment.productId,
46
+ });
47
+ const productMedias = await modules.products.media.findProductMedias({
48
+ productId: assignment.productId,
49
+ });
50
+ const media = await normalizeMediaUrl(productMedias, context);
51
+ const texts = await loaders.productTextLoader.load({
52
+ productId: assignment.productId,
53
+ locale,
54
+ });
55
+ return {
56
+ assignment: {
57
+ ...assignment,
58
+ product: {
59
+ ...product,
60
+ media,
61
+ texts,
62
+ },
63
+ },
64
+ ...rest,
65
+ };
66
+ }),
67
+ );
68
+ return {
69
+ content: [
70
+ {
71
+ type: 'text' as const,
72
+ text: JSON.stringify({ product, assignments: normalizedAssignments }),
73
+ },
74
+ ],
75
+ };
76
+ } catch (error) {
77
+ return {
78
+ content: [
79
+ {
80
+ type: 'text' as const,
81
+ text: `Error fetching assignments: ${(error as Error).message}`,
82
+ },
83
+ ],
84
+ };
85
+ }
86
+ };