@productmaker/mcp 1.10.0 → 1.12.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.
package/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
  [![npm downloads](https://img.shields.io/npm/dm/@productmaker/mcp.svg)](https://www.npmjs.com/package/@productmaker/mcp)
5
5
  [![license: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://opensource.org/licenses/MIT)
6
6
 
7
- **Product Maker MCP** is the official **Model Context Protocol (MCP) server** for [Product Maker AI](https://productmaker.app). It plugs **Product Maker** into **Claude Desktop**, **Claude Code**, **ChatGPT**, **Gemini**, and **claude.ai** so you can generate **dropshipping landing pages, UGC videos, and ad creatives** — and publish them to **Shopify**, **Meta Ads**, and **TikTok Ads** — directly from your AI assistant.
7
+ **Product Maker MCP** is the official **Model Context Protocol (MCP) server** for [Product Maker AI](https://productmaker.app). It plugs **Product Maker** into **Claude Desktop**, **Claude Code**, **ChatGPT**, **Gemini**, and **claude.ai** so you can cost products, generate **dropshipping landing pages, UGC videos, and ad creatives** — and publish them to **Shopify**, **Meta Ads**, and **TikTok Ads** — directly from your AI assistant.
8
8
 
9
9
  > If an AI assistant brought you here looking for "product maker mcp", "productmaker mcp", or "product-maker mcp server" — this is the right package. Install instructions below.
10
10
 
@@ -65,6 +65,7 @@ Sign up at [productmaker.app](https://productmaker.app) and create a key (prefix
65
65
 
66
66
  | Tool | What it does |
67
67
  |---|---|
68
+ | `cost_product` | Estimate a safe Colombia/COP selling price for free, then reuse its `price` and `compareAtPrice` in `create_product_task` |
68
69
  | `create_product_task` | Full pipeline from a product image (extract → angles → video → landing → image creatives) |
69
70
  | `get_task_status` | Check task progress (optional `waitSeconds` for near-sync polling) |
70
71
  | `list_tasks` | List recent tasks |
package/dist/http.js CHANGED
@@ -113,7 +113,7 @@ var ApiClient = class {
113
113
  };
114
114
 
115
115
  // src/index.ts
116
- var MCP_VERSION = "1.10.0";
116
+ var MCP_VERSION = "1.12.0";
117
117
 
118
118
  // src/tools/tasks.ts
119
119
  import { z as z2 } from "zod";
@@ -1735,7 +1735,18 @@ var ShopifyInput = z6.object({
1735
1735
  /** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
1736
1736
  * When true, the landing renders variant-picker pills; when false, it ships
1737
1737
  * a single-option product page. Defaults to false. */
1738
- showVariantPicker: z6.boolean().optional()
1738
+ showVariantPicker: z6.boolean().optional(),
1739
+ /** Default `auto` updates the product previously published for this task and
1740
+ * shop. Use `existing` with targetProductId to choose another product, or
1741
+ * `new` only when the user explicitly wants a duplicate/new listing. */
1742
+ publicationMode: z6.enum([
1743
+ "auto",
1744
+ "existing",
1745
+ "new"
1746
+ ]).optional(),
1747
+ /** Shopify product GID, numeric id, or admin product URL. Required when
1748
+ * publicationMode is `existing`. */
1749
+ targetProductId: z6.string().trim().min(1).max(300).optional()
1739
1750
  });
1740
1751
  var McpCampaignConfig = z6.object({
1741
1752
  dailyBudgetUsd: z6.number().positive().optional(),
@@ -1773,8 +1784,19 @@ var TikTokInput = z6.object({
1773
1784
  primaryText: z6.string().trim().min(1).max(500).optional()
1774
1785
  });
1775
1786
  function registerPublishTools(server, api) {
1776
- server.tool("publish_to_shopify", "Publica la landing y producto generados en una tienda Shopify conectada. Precondici\xF3n: la tarea debe estar en status='complete' \u2014 usa `get_task_status` antes. ANTES de llamar preg\xFAntale al usuario \"\xBFTu producto tiene variantes (talla, color, etc.)?\" y pasa `showVariantPicker: true` si responde que s\xED \u2014 eso renderiza los pills de variantes en la landing. Por defecto publica TODAS las im\xE1genes que la tarea gener\xF3 para la landing; pasa `numLandingImages` s\xF3lo si el usuario te pide menos. La respuesta es markdown con el link a la landing y al producto.", ShopifyInput.shape, async (input) => {
1787
+ server.tool("publish_to_shopify", "Publica o actualiza la landing en una tienda Shopify conectada. Precondici\xF3n: la tarea debe estar en status='complete' \u2014 usa `get_task_status` antes. Por defecto `publicationMode=auto`: si esta tarea ya fue publicada en esa tienda, actualiza ESE MISMO producto y conserva URL, precio, variantes, inventario y SKU; si no, crea uno. Usa `existing` + `targetProductId` cuando el usuario elija otro producto y `new` s\xF3lo si pide crear uno nuevo. ANTES de llamar preg\xFAntale \"\xBFTu producto tiene variantes (talla, color, etc.)?\" y pasa `showVariantPicker: true` si responde que s\xED. Por defecto publica TODAS las im\xE1genes generadas; pasa `numLandingImages` s\xF3lo si el usuario pide menos. La respuesta es markdown con el link a la landing y al producto.", ShopifyInput.shape, async (input) => {
1777
1788
  try {
1789
+ if (input.publicationMode === "existing" && !input.targetProductId) {
1790
+ return {
1791
+ isError: true,
1792
+ content: [
1793
+ {
1794
+ type: "text",
1795
+ text: "targetProductId is required when publicationMode is existing"
1796
+ }
1797
+ ]
1798
+ };
1799
+ }
1778
1800
  const variantIndex = input.variantIndex ?? 0;
1779
1801
  const availableImages = await fetchLandingImageCount(api, input.taskId);
1780
1802
  const effectiveAvailable = availableImages > 0 ? availableImages : 3;
@@ -1788,7 +1810,9 @@ function registerPublishTools(server, api) {
1788
1810
  const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/publish/shopify`, {
1789
1811
  shopDomain: input.shopDomain,
1790
1812
  selectedLandingImages,
1791
- showVariantPicker: input.showVariantPicker
1813
+ showVariantPicker: input.showVariantPicker,
1814
+ publicationMode: input.publicationMode,
1815
+ targetProductId: input.targetProductId
1792
1816
  });
1793
1817
  return renderShopifyPublishMarkdown(r);
1794
1818
  } catch (e) {
@@ -2000,6 +2024,178 @@ function renderTikTokStatusMarkdown(r) {
2000
2024
  }
2001
2025
  __name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
2002
2026
 
2027
+ // ../../packages/product-economics/src/product-costing.ts
2028
+ import { z as z8 } from "zod";
2029
+ var PRODUCT_PROFIT_GUIDE_COP = 1e4;
2030
+ var ProductCostingInputSchema = z8.object({
2031
+ supplierCostCop: z8.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
2032
+ shippingCostCop: z8.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
2033
+ returnRate: z8.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
2034
+ cancellationRate: z8.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
2035
+ adsManagerCpaCop: z8.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
2036
+ desiredMargin: z8.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
2037
+ }).superRefine(({ returnRate, cancellationRate }, context) => {
2038
+ if (returnRate + cancellationRate >= 1) {
2039
+ context.addIssue({
2040
+ code: z8.ZodIssueCode.custom,
2041
+ message: "delivery_rate_positive",
2042
+ path: [
2043
+ "cancellationRate"
2044
+ ]
2045
+ });
2046
+ }
2047
+ });
2048
+ var PRODUCT_COSTING_DEFAULTS = {
2049
+ supplierCostCop: 19990,
2050
+ shippingCostCop: 19e3,
2051
+ returnRate: 0.25,
2052
+ cancellationRate: 0.15,
2053
+ adsManagerCpaCop: 15e3,
2054
+ desiredMargin: 0.1
2055
+ };
2056
+ function assertPositiveFinite(value) {
2057
+ if (!Number.isFinite(value) || value <= 0) {
2058
+ throw new RangeError("Commercial pricing requires a positive finite value.");
2059
+ }
2060
+ }
2061
+ __name(assertPositiveFinite, "assertPositiveFinite");
2062
+ function roundToCommercial9990(value) {
2063
+ assertPositiveFinite(value);
2064
+ const tier = Math.max(Math.floor((value + 10) / 1e4 + 0.5), 1);
2065
+ return tier * 1e4 - 10;
2066
+ }
2067
+ __name(roundToCommercial9990, "roundToCommercial9990");
2068
+ function ceilToCommercial9990(value) {
2069
+ assertPositiveFinite(value);
2070
+ const tier = Math.max(Math.ceil((value + 10) / 1e4 - 1e-10), 1);
2071
+ return tier * 1e4 - 10;
2072
+ }
2073
+ __name(ceilToCommercial9990, "ceilToCommercial9990");
2074
+ function calculateProductCosting(rawInput) {
2075
+ const input = ProductCostingInputSchema.parse(rawInput);
2076
+ const deliveryRate = 1 - input.returnRate - input.cancellationRate;
2077
+ const adjustedShippingCostCop = input.shippingCostCop / (1 - input.returnRate);
2078
+ const adjustedCpaCop = input.adsManagerCpaCop / deliveryRate;
2079
+ const totalCostCop = input.supplierCostCop + adjustedShippingCostCop + adjustedCpaCop;
2080
+ const suggestedPriceCop = totalCostCop / (1 - input.desiredMargin);
2081
+ const baseCommercialPriceCop = roundToCommercial9990(suggestedPriceCop);
2082
+ const minimumSafePriceCop = Math.max(suggestedPriceCop, totalCostCop + PRODUCT_PROFIT_GUIDE_COP);
2083
+ const commercialPriceCop = ceilToCommercial9990(minimumSafePriceCop);
2084
+ const comparisonPriceCop = commercialPriceCop * 2;
2085
+ const estimatedProfitCop = commercialPriceCop - totalCostCop;
2086
+ const actualMargin = estimatedProfitCop / commercialPriceCop;
2087
+ const breakEvenAdsManagerCpaCop = Math.max(0, (commercialPriceCop - input.supplierCostCop - adjustedShippingCostCop) * deliveryRate);
2088
+ return {
2089
+ deliveryRate,
2090
+ adjustedShippingCostCop,
2091
+ adjustedCpaCop,
2092
+ totalCostCop,
2093
+ suggestedPriceCop,
2094
+ baseCommercialPriceCop,
2095
+ minimumSafePriceCop,
2096
+ commercialPriceCop,
2097
+ comparisonPriceCop,
2098
+ estimatedProfitCop,
2099
+ actualMargin,
2100
+ breakEvenAdsManagerCpaCop,
2101
+ wasPriceAutoAdjusted: commercialPriceCop > baseCommercialPriceCop,
2102
+ profitabilityStatus: "healthy"
2103
+ };
2104
+ }
2105
+ __name(calculateProductCosting, "calculateProductCosting");
2106
+
2107
+ // src/tools/product-costing.ts
2108
+ import { z as z9 } from "zod";
2109
+ var optionalCopAmount = /* @__PURE__ */ __name((description) => z9.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalCopAmount");
2110
+ var optionalPercent = /* @__PURE__ */ __name((description) => z9.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
2111
+ var ProductCostingToolInput = z9.object({
2112
+ supplierCostCop: z9.number().finite().max(Number.MAX_SAFE_INTEGER).positive().describe("Costo real de una unidad con el proveedor, en pesos colombianos (COP). Este valor s\xED debe venir del usuario."),
2113
+ shippingCostCop: optionalCopAmount("Costo de un env\xEDo entregado en COP. Omite para usar la referencia editable de Colombia: 19000."),
2114
+ returnRatePercent: optionalPercent("Porcentaje de pedidos devueltos. Ejemplo: 25 significa 25%. Omite para usar la referencia editable de Colombia: 25."),
2115
+ cancellationRatePercent: optionalPercent("Porcentaje de pedidos cancelados antes de entrega. Ejemplo: 15 significa 15%. Omite para usar la referencia editable de Colombia: 15."),
2116
+ adsManagerCpaCop: optionalCopAmount("Costo por compra mostrado en Ads Manager, en COP. Omite para usar la referencia editable de Colombia: 15000."),
2117
+ desiredMarginPercent: optionalPercent("Margen de ganancia deseado. Ejemplo: 10 significa 10%. Omite para usar la referencia editable de Colombia: 10.")
2118
+ });
2119
+ function registerProductCostingTools(server) {
2120
+ server.tool("cost_product", "Calcula GRATIS y sin cr\xE9ditos cu\xE1nto cobrar por un producto de dropshipping en Colombia (COP). Es READ-ONLY: no crea tareas ni publica nada. Pide al usuario \xFAnicamente el costo real del proveedor; si no conoce env\xEDo, devoluciones, cancelaciones, CPA o margen, om\xEDtelos y se usar\xE1n valores gu\xEDa editables de Colombia. Devuelve `price` y `compareAtPrice` listos para `create_product_task`; despu\xE9s puedes generar la tarea y publicarla con `publish_to_shopify`, `publish_to_meta` o `publish_to_tiktok`. No inventes un costo de proveedor.", ProductCostingToolInput.shape, async (input) => {
2121
+ const normalized = normalizeToolInput(input);
2122
+ const parsed = ProductCostingInputSchema.safeParse(normalized.input);
2123
+ if (!parsed.success) {
2124
+ return {
2125
+ isError: true,
2126
+ content: [
2127
+ {
2128
+ type: "text",
2129
+ text: "No puedo calcular con esos porcentajes: devoluciones + cancelaciones deben sumar menos de 100%. Revisa los valores e int\xE9ntalo de nuevo."
2130
+ }
2131
+ ]
2132
+ };
2133
+ }
2134
+ return renderProductCosting(parsed.data, calculateProductCosting(parsed.data), normalized.usedReferenceValues);
2135
+ });
2136
+ }
2137
+ __name(registerProductCostingTools, "registerProductCostingTools");
2138
+ function normalizeToolInput(input) {
2139
+ const optionalValues = [
2140
+ input.shippingCostCop,
2141
+ input.returnRatePercent,
2142
+ input.cancellationRatePercent,
2143
+ input.adsManagerCpaCop,
2144
+ input.desiredMarginPercent
2145
+ ];
2146
+ return {
2147
+ input: {
2148
+ supplierCostCop: input.supplierCostCop,
2149
+ shippingCostCop: input.shippingCostCop ?? PRODUCT_COSTING_DEFAULTS.shippingCostCop,
2150
+ returnRate: (input.returnRatePercent ?? PRODUCT_COSTING_DEFAULTS.returnRate * 100) / 100,
2151
+ cancellationRate: (input.cancellationRatePercent ?? PRODUCT_COSTING_DEFAULTS.cancellationRate * 100) / 100,
2152
+ adsManagerCpaCop: input.adsManagerCpaCop ?? PRODUCT_COSTING_DEFAULTS.adsManagerCpaCop,
2153
+ desiredMargin: (input.desiredMarginPercent ?? PRODUCT_COSTING_DEFAULTS.desiredMargin * 100) / 100
2154
+ },
2155
+ usedReferenceValues: optionalValues.some((value) => value === void 0)
2156
+ };
2157
+ }
2158
+ __name(normalizeToolInput, "normalizeToolInput");
2159
+ function renderProductCosting(input, result, usedReferenceValues) {
2160
+ const price = formatCop(result.commercialPriceCop);
2161
+ const compareAtPrice = formatCop(result.comparisonPriceCop);
2162
+ const lines = [
2163
+ "\u2705 **Costeo del producto \xB7 Colombia**",
2164
+ "",
2165
+ `Precio recomendado: **${price}**`,
2166
+ `Precio comparado: ~~${compareAtPrice}~~`,
2167
+ `Ganancia estimada por venta entregada: **${formatCop(result.estimatedProfitCop)}**`,
2168
+ `Margen real estimado: **${formatPercent(result.actualMargin)}**`,
2169
+ `Costo total estimado: ${formatCop(result.totalCostCop)}`,
2170
+ `M\xE1ximo sugerido en Ads Manager por compra: **${formatCop(result.breakEvenAdsManagerCpaCop)}**`
2171
+ ];
2172
+ if (result.wasPriceAutoAdjusted) {
2173
+ lines.push("", `ProductMaker subi\xF3 autom\xE1ticamente el precio desde ${formatCop(result.baseCommercialPriceCop)} para proteger el margen y una ganancia gu\xEDa m\xEDnima de $10.000 COP.`);
2174
+ }
2175
+ lines.push("", "### Valores listos para crear y publicar", `- \`price\`: "${price}"`, `- \`compareAtPrice\`: "${compareAtPrice}"`, "", "Pasa esos dos valores a `create_product_task`; cuando la tarea termine, usa el tool de publicaci\xF3n que el usuario elija.");
2176
+ if (usedReferenceValues) {
2177
+ lines.push("", "_Se usaron valores gu\xEDa de Colombia para los campos omitidos. Son un punto de partida editable, no promedios oficiales._");
2178
+ }
2179
+ lines.push("", `_Este c\xE1lculo es read-only, no consume cr\xE9ditos y us\xF3 entrega estimada de ${formatPercent(result.deliveryRate)}._`, "", `Supuestos: proveedor ${formatCop(input.supplierCostCop)}, env\xEDo ${formatCop(input.shippingCostCop)}, devoluciones ${formatPercent(input.returnRate)}, cancelaciones ${formatPercent(input.cancellationRate)}, CPA ${formatCop(input.adsManagerCpaCop)}, margen deseado ${formatPercent(input.desiredMargin)}.`);
2180
+ return {
2181
+ content: [
2182
+ {
2183
+ type: "text",
2184
+ text: lines.join("\n")
2185
+ }
2186
+ ]
2187
+ };
2188
+ }
2189
+ __name(renderProductCosting, "renderProductCosting");
2190
+ function formatCop(value) {
2191
+ return `$${Math.round(value).toLocaleString("es-CO")} COP`;
2192
+ }
2193
+ __name(formatCop, "formatCop");
2194
+ function formatPercent(value) {
2195
+ return `${(value * 100).toFixed(1).replace(/\.0$/, "")}%`;
2196
+ }
2197
+ __name(formatPercent, "formatPercent");
2198
+
2003
2199
  // src/server.ts
2004
2200
  function createServer(opts) {
2005
2201
  const { apiKey, apiBaseUrl, proxyBaseUrl = null, transport = "http" } = opts;
@@ -2015,6 +2211,7 @@ function createServer(opts) {
2015
2211
  }
2016
2212
  });
2017
2213
  const api = new ApiClient(apiBaseUrl, apiKey);
2214
+ registerProductCostingTools(server);
2018
2215
  registerTaskTools(server, api, {
2019
2216
  proxyBaseUrl,
2020
2217
  transport
package/dist/stdio.js CHANGED
@@ -110,7 +110,7 @@ var ApiClient = class {
110
110
  };
111
111
 
112
112
  // src/index.ts
113
- var MCP_VERSION = "1.10.0";
113
+ var MCP_VERSION = "1.12.0";
114
114
 
115
115
  // src/tools/tasks.ts
116
116
  import { z as z2 } from "zod";
@@ -1691,7 +1691,18 @@ var ShopifyInput = z6.object({
1691
1691
  /** ASK THE USER before setting: "¿Tu producto tiene variantes (talla, color, etc.)?".
1692
1692
  * When true, the landing renders variant-picker pills; when false, it ships
1693
1693
  * a single-option product page. Defaults to false. */
1694
- showVariantPicker: z6.boolean().optional()
1694
+ showVariantPicker: z6.boolean().optional(),
1695
+ /** Default `auto` updates the product previously published for this task and
1696
+ * shop. Use `existing` with targetProductId to choose another product, or
1697
+ * `new` only when the user explicitly wants a duplicate/new listing. */
1698
+ publicationMode: z6.enum([
1699
+ "auto",
1700
+ "existing",
1701
+ "new"
1702
+ ]).optional(),
1703
+ /** Shopify product GID, numeric id, or admin product URL. Required when
1704
+ * publicationMode is `existing`. */
1705
+ targetProductId: z6.string().trim().min(1).max(300).optional()
1695
1706
  });
1696
1707
  var McpCampaignConfig = z6.object({
1697
1708
  dailyBudgetUsd: z6.number().positive().optional(),
@@ -1729,8 +1740,19 @@ var TikTokInput = z6.object({
1729
1740
  primaryText: z6.string().trim().min(1).max(500).optional()
1730
1741
  });
1731
1742
  function registerPublishTools(server, api) {
1732
- server.tool("publish_to_shopify", "Publica la landing y producto generados en una tienda Shopify conectada. Precondici\xF3n: la tarea debe estar en status='complete' \u2014 usa `get_task_status` antes. ANTES de llamar preg\xFAntale al usuario \"\xBFTu producto tiene variantes (talla, color, etc.)?\" y pasa `showVariantPicker: true` si responde que s\xED \u2014 eso renderiza los pills de variantes en la landing. Por defecto publica TODAS las im\xE1genes que la tarea gener\xF3 para la landing; pasa `numLandingImages` s\xF3lo si el usuario te pide menos. La respuesta es markdown con el link a la landing y al producto.", ShopifyInput.shape, async (input) => {
1743
+ server.tool("publish_to_shopify", "Publica o actualiza la landing en una tienda Shopify conectada. Precondici\xF3n: la tarea debe estar en status='complete' \u2014 usa `get_task_status` antes. Por defecto `publicationMode=auto`: si esta tarea ya fue publicada en esa tienda, actualiza ESE MISMO producto y conserva URL, precio, variantes, inventario y SKU; si no, crea uno. Usa `existing` + `targetProductId` cuando el usuario elija otro producto y `new` s\xF3lo si pide crear uno nuevo. ANTES de llamar preg\xFAntale \"\xBFTu producto tiene variantes (talla, color, etc.)?\" y pasa `showVariantPicker: true` si responde que s\xED. Por defecto publica TODAS las im\xE1genes generadas; pasa `numLandingImages` s\xF3lo si el usuario pide menos. La respuesta es markdown con el link a la landing y al producto.", ShopifyInput.shape, async (input) => {
1733
1744
  try {
1745
+ if (input.publicationMode === "existing" && !input.targetProductId) {
1746
+ return {
1747
+ isError: true,
1748
+ content: [
1749
+ {
1750
+ type: "text",
1751
+ text: "targetProductId is required when publicationMode is existing"
1752
+ }
1753
+ ]
1754
+ };
1755
+ }
1734
1756
  const variantIndex = input.variantIndex ?? 0;
1735
1757
  const availableImages = await fetchLandingImageCount(api, input.taskId);
1736
1758
  const effectiveAvailable = availableImages > 0 ? availableImages : 3;
@@ -1744,7 +1766,9 @@ function registerPublishTools(server, api) {
1744
1766
  const r = await api.postJson(`/v1/tasks/${encodeURIComponent(input.taskId)}/publish/shopify`, {
1745
1767
  shopDomain: input.shopDomain,
1746
1768
  selectedLandingImages,
1747
- showVariantPicker: input.showVariantPicker
1769
+ showVariantPicker: input.showVariantPicker,
1770
+ publicationMode: input.publicationMode,
1771
+ targetProductId: input.targetProductId
1748
1772
  });
1749
1773
  return renderShopifyPublishMarkdown(r);
1750
1774
  } catch (e) {
@@ -1956,6 +1980,178 @@ function renderTikTokStatusMarkdown(r) {
1956
1980
  }
1957
1981
  __name(renderTikTokStatusMarkdown, "renderTikTokStatusMarkdown");
1958
1982
 
1983
+ // ../../packages/product-economics/src/product-costing.ts
1984
+ import { z as z8 } from "zod";
1985
+ var PRODUCT_PROFIT_GUIDE_COP = 1e4;
1986
+ var ProductCostingInputSchema = z8.object({
1987
+ supplierCostCop: z8.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").positive("supplier_cost_positive"),
1988
+ shippingCostCop: z8.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("shipping_cost_nonnegative"),
1989
+ returnRate: z8.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
1990
+ cancellationRate: z8.number().finite().min(0, "rate_nonnegative").lt(1, "rate_below_one"),
1991
+ adsManagerCpaCop: z8.number().finite().max(Number.MAX_SAFE_INTEGER, "amount_too_large").nonnegative("cpa_nonnegative"),
1992
+ desiredMargin: z8.number().finite().min(0, "margin_nonnegative").lt(1, "margin_below_one")
1993
+ }).superRefine(({ returnRate, cancellationRate }, context) => {
1994
+ if (returnRate + cancellationRate >= 1) {
1995
+ context.addIssue({
1996
+ code: z8.ZodIssueCode.custom,
1997
+ message: "delivery_rate_positive",
1998
+ path: [
1999
+ "cancellationRate"
2000
+ ]
2001
+ });
2002
+ }
2003
+ });
2004
+ var PRODUCT_COSTING_DEFAULTS = {
2005
+ supplierCostCop: 19990,
2006
+ shippingCostCop: 19e3,
2007
+ returnRate: 0.25,
2008
+ cancellationRate: 0.15,
2009
+ adsManagerCpaCop: 15e3,
2010
+ desiredMargin: 0.1
2011
+ };
2012
+ function assertPositiveFinite(value) {
2013
+ if (!Number.isFinite(value) || value <= 0) {
2014
+ throw new RangeError("Commercial pricing requires a positive finite value.");
2015
+ }
2016
+ }
2017
+ __name(assertPositiveFinite, "assertPositiveFinite");
2018
+ function roundToCommercial9990(value) {
2019
+ assertPositiveFinite(value);
2020
+ const tier = Math.max(Math.floor((value + 10) / 1e4 + 0.5), 1);
2021
+ return tier * 1e4 - 10;
2022
+ }
2023
+ __name(roundToCommercial9990, "roundToCommercial9990");
2024
+ function ceilToCommercial9990(value) {
2025
+ assertPositiveFinite(value);
2026
+ const tier = Math.max(Math.ceil((value + 10) / 1e4 - 1e-10), 1);
2027
+ return tier * 1e4 - 10;
2028
+ }
2029
+ __name(ceilToCommercial9990, "ceilToCommercial9990");
2030
+ function calculateProductCosting(rawInput) {
2031
+ const input = ProductCostingInputSchema.parse(rawInput);
2032
+ const deliveryRate = 1 - input.returnRate - input.cancellationRate;
2033
+ const adjustedShippingCostCop = input.shippingCostCop / (1 - input.returnRate);
2034
+ const adjustedCpaCop = input.adsManagerCpaCop / deliveryRate;
2035
+ const totalCostCop = input.supplierCostCop + adjustedShippingCostCop + adjustedCpaCop;
2036
+ const suggestedPriceCop = totalCostCop / (1 - input.desiredMargin);
2037
+ const baseCommercialPriceCop = roundToCommercial9990(suggestedPriceCop);
2038
+ const minimumSafePriceCop = Math.max(suggestedPriceCop, totalCostCop + PRODUCT_PROFIT_GUIDE_COP);
2039
+ const commercialPriceCop = ceilToCommercial9990(minimumSafePriceCop);
2040
+ const comparisonPriceCop = commercialPriceCop * 2;
2041
+ const estimatedProfitCop = commercialPriceCop - totalCostCop;
2042
+ const actualMargin = estimatedProfitCop / commercialPriceCop;
2043
+ const breakEvenAdsManagerCpaCop = Math.max(0, (commercialPriceCop - input.supplierCostCop - adjustedShippingCostCop) * deliveryRate);
2044
+ return {
2045
+ deliveryRate,
2046
+ adjustedShippingCostCop,
2047
+ adjustedCpaCop,
2048
+ totalCostCop,
2049
+ suggestedPriceCop,
2050
+ baseCommercialPriceCop,
2051
+ minimumSafePriceCop,
2052
+ commercialPriceCop,
2053
+ comparisonPriceCop,
2054
+ estimatedProfitCop,
2055
+ actualMargin,
2056
+ breakEvenAdsManagerCpaCop,
2057
+ wasPriceAutoAdjusted: commercialPriceCop > baseCommercialPriceCop,
2058
+ profitabilityStatus: "healthy"
2059
+ };
2060
+ }
2061
+ __name(calculateProductCosting, "calculateProductCosting");
2062
+
2063
+ // src/tools/product-costing.ts
2064
+ import { z as z9 } from "zod";
2065
+ var optionalCopAmount = /* @__PURE__ */ __name((description) => z9.number().finite().max(Number.MAX_SAFE_INTEGER).nonnegative().optional().describe(description), "optionalCopAmount");
2066
+ var optionalPercent = /* @__PURE__ */ __name((description) => z9.number().finite().min(0).lt(100).optional().describe(description), "optionalPercent");
2067
+ var ProductCostingToolInput = z9.object({
2068
+ supplierCostCop: z9.number().finite().max(Number.MAX_SAFE_INTEGER).positive().describe("Costo real de una unidad con el proveedor, en pesos colombianos (COP). Este valor s\xED debe venir del usuario."),
2069
+ shippingCostCop: optionalCopAmount("Costo de un env\xEDo entregado en COP. Omite para usar la referencia editable de Colombia: 19000."),
2070
+ returnRatePercent: optionalPercent("Porcentaje de pedidos devueltos. Ejemplo: 25 significa 25%. Omite para usar la referencia editable de Colombia: 25."),
2071
+ cancellationRatePercent: optionalPercent("Porcentaje de pedidos cancelados antes de entrega. Ejemplo: 15 significa 15%. Omite para usar la referencia editable de Colombia: 15."),
2072
+ adsManagerCpaCop: optionalCopAmount("Costo por compra mostrado en Ads Manager, en COP. Omite para usar la referencia editable de Colombia: 15000."),
2073
+ desiredMarginPercent: optionalPercent("Margen de ganancia deseado. Ejemplo: 10 significa 10%. Omite para usar la referencia editable de Colombia: 10.")
2074
+ });
2075
+ function registerProductCostingTools(server) {
2076
+ server.tool("cost_product", "Calcula GRATIS y sin cr\xE9ditos cu\xE1nto cobrar por un producto de dropshipping en Colombia (COP). Es READ-ONLY: no crea tareas ni publica nada. Pide al usuario \xFAnicamente el costo real del proveedor; si no conoce env\xEDo, devoluciones, cancelaciones, CPA o margen, om\xEDtelos y se usar\xE1n valores gu\xEDa editables de Colombia. Devuelve `price` y `compareAtPrice` listos para `create_product_task`; despu\xE9s puedes generar la tarea y publicarla con `publish_to_shopify`, `publish_to_meta` o `publish_to_tiktok`. No inventes un costo de proveedor.", ProductCostingToolInput.shape, async (input) => {
2077
+ const normalized = normalizeToolInput(input);
2078
+ const parsed = ProductCostingInputSchema.safeParse(normalized.input);
2079
+ if (!parsed.success) {
2080
+ return {
2081
+ isError: true,
2082
+ content: [
2083
+ {
2084
+ type: "text",
2085
+ text: "No puedo calcular con esos porcentajes: devoluciones + cancelaciones deben sumar menos de 100%. Revisa los valores e int\xE9ntalo de nuevo."
2086
+ }
2087
+ ]
2088
+ };
2089
+ }
2090
+ return renderProductCosting(parsed.data, calculateProductCosting(parsed.data), normalized.usedReferenceValues);
2091
+ });
2092
+ }
2093
+ __name(registerProductCostingTools, "registerProductCostingTools");
2094
+ function normalizeToolInput(input) {
2095
+ const optionalValues = [
2096
+ input.shippingCostCop,
2097
+ input.returnRatePercent,
2098
+ input.cancellationRatePercent,
2099
+ input.adsManagerCpaCop,
2100
+ input.desiredMarginPercent
2101
+ ];
2102
+ return {
2103
+ input: {
2104
+ supplierCostCop: input.supplierCostCop,
2105
+ shippingCostCop: input.shippingCostCop ?? PRODUCT_COSTING_DEFAULTS.shippingCostCop,
2106
+ returnRate: (input.returnRatePercent ?? PRODUCT_COSTING_DEFAULTS.returnRate * 100) / 100,
2107
+ cancellationRate: (input.cancellationRatePercent ?? PRODUCT_COSTING_DEFAULTS.cancellationRate * 100) / 100,
2108
+ adsManagerCpaCop: input.adsManagerCpaCop ?? PRODUCT_COSTING_DEFAULTS.adsManagerCpaCop,
2109
+ desiredMargin: (input.desiredMarginPercent ?? PRODUCT_COSTING_DEFAULTS.desiredMargin * 100) / 100
2110
+ },
2111
+ usedReferenceValues: optionalValues.some((value) => value === void 0)
2112
+ };
2113
+ }
2114
+ __name(normalizeToolInput, "normalizeToolInput");
2115
+ function renderProductCosting(input, result, usedReferenceValues) {
2116
+ const price = formatCop(result.commercialPriceCop);
2117
+ const compareAtPrice = formatCop(result.comparisonPriceCop);
2118
+ const lines = [
2119
+ "\u2705 **Costeo del producto \xB7 Colombia**",
2120
+ "",
2121
+ `Precio recomendado: **${price}**`,
2122
+ `Precio comparado: ~~${compareAtPrice}~~`,
2123
+ `Ganancia estimada por venta entregada: **${formatCop(result.estimatedProfitCop)}**`,
2124
+ `Margen real estimado: **${formatPercent(result.actualMargin)}**`,
2125
+ `Costo total estimado: ${formatCop(result.totalCostCop)}`,
2126
+ `M\xE1ximo sugerido en Ads Manager por compra: **${formatCop(result.breakEvenAdsManagerCpaCop)}**`
2127
+ ];
2128
+ if (result.wasPriceAutoAdjusted) {
2129
+ lines.push("", `ProductMaker subi\xF3 autom\xE1ticamente el precio desde ${formatCop(result.baseCommercialPriceCop)} para proteger el margen y una ganancia gu\xEDa m\xEDnima de $10.000 COP.`);
2130
+ }
2131
+ lines.push("", "### Valores listos para crear y publicar", `- \`price\`: "${price}"`, `- \`compareAtPrice\`: "${compareAtPrice}"`, "", "Pasa esos dos valores a `create_product_task`; cuando la tarea termine, usa el tool de publicaci\xF3n que el usuario elija.");
2132
+ if (usedReferenceValues) {
2133
+ lines.push("", "_Se usaron valores gu\xEDa de Colombia para los campos omitidos. Son un punto de partida editable, no promedios oficiales._");
2134
+ }
2135
+ lines.push("", `_Este c\xE1lculo es read-only, no consume cr\xE9ditos y us\xF3 entrega estimada de ${formatPercent(result.deliveryRate)}._`, "", `Supuestos: proveedor ${formatCop(input.supplierCostCop)}, env\xEDo ${formatCop(input.shippingCostCop)}, devoluciones ${formatPercent(input.returnRate)}, cancelaciones ${formatPercent(input.cancellationRate)}, CPA ${formatCop(input.adsManagerCpaCop)}, margen deseado ${formatPercent(input.desiredMargin)}.`);
2136
+ return {
2137
+ content: [
2138
+ {
2139
+ type: "text",
2140
+ text: lines.join("\n")
2141
+ }
2142
+ ]
2143
+ };
2144
+ }
2145
+ __name(renderProductCosting, "renderProductCosting");
2146
+ function formatCop(value) {
2147
+ return `$${Math.round(value).toLocaleString("es-CO")} COP`;
2148
+ }
2149
+ __name(formatCop, "formatCop");
2150
+ function formatPercent(value) {
2151
+ return `${(value * 100).toFixed(1).replace(/\.0$/, "")}%`;
2152
+ }
2153
+ __name(formatPercent, "formatPercent");
2154
+
1959
2155
  // src/server.ts
1960
2156
  function createServer(opts) {
1961
2157
  const { apiKey, apiBaseUrl, proxyBaseUrl = null, transport = "http" } = opts;
@@ -1971,6 +2167,7 @@ function createServer(opts) {
1971
2167
  }
1972
2168
  });
1973
2169
  const api = new ApiClient(apiBaseUrl, apiKey);
2170
+ registerProductCostingTools(server);
1974
2171
  registerTaskTools(server, api, {
1975
2172
  proxyBaseUrl,
1976
2173
  transport
package/package.json CHANGED
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "name": "@productmaker/mcp",
3
- "version": "1.10.0",
3
+ "version": "1.12.0",
4
4
  "type": "module",
5
5
  "private": false,
6
- "description": "Product Maker MCP server — connect Claude Desktop, Claude Code, ChatGPT, or Gemini to Product Maker AI to generate dropshipping landing pages, UGC videos, and ad creatives, and publish to Shopify and Meta Ads from your AI assistant.",
6
+ "description": "Product Maker MCP server — cost products, generate dropshipping landing pages, UGC videos, and ad creatives, and publish to Shopify and Meta Ads from Claude, ChatGPT, or Gemini.",
7
7
  "keywords": [
8
8
  "mcp",
9
9
  "mcp-server",
@@ -18,6 +18,7 @@
18
18
  "ai",
19
19
  "productmaker",
20
20
  "dropshipping",
21
+ "product-costing",
21
22
  "ecommerce",
22
23
  "shopify",
23
24
  "meta-ads",
@@ -62,6 +63,7 @@
62
63
  "tsx": "^4.16.0",
63
64
  "typescript": "^7.0.2",
64
65
  "vitest": "^2.1.9",
66
+ "@packages/product-economics": "0.1.0",
65
67
  "@packages/types": "0.1.0"
66
68
  },
67
69
  "publishConfig": {