@productmaker/mcp 3.1.1 → 3.1.2

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/dist/http.js CHANGED
@@ -11368,13 +11368,12 @@ var ApiClient = class {
11368
11368
  };
11369
11369
 
11370
11370
  // src/index.ts
11371
- var MCP_VERSION = "3.1.1";
11371
+ var MCP_VERSION = "3.1.2";
11372
11372
 
11373
11373
  // src/meta-upload-app.ts
11374
11374
  import { readFileSync } from "fs";
11375
11375
  import { registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
11376
11376
  var MEDIA_UPLOAD_APP_RESOURCE_URI = "ui://media-upload/v1.html";
11377
- var MEDIA_UPLOAD_APP_DOMAIN = "https://productmaker.app";
11378
11377
  var MEDIA_UPLOAD_APP_NAME = "ProductMaker media upload";
11379
11378
  var MEDIA_UPLOAD_APP_HTML_URL = new URL("./ui/meta-upload-app.html", import.meta.url);
11380
11379
  var MEDIA_UPLOAD_APP_CSP = {
@@ -11420,7 +11419,6 @@ function registerMediaUploadAppResource(server, resolveEndpoint) {
11420
11419
  mimeType: RESOURCE_MIME_TYPE,
11421
11420
  _meta: {
11422
11421
  ui: {
11423
- domain: MEDIA_UPLOAD_APP_DOMAIN,
11424
11422
  csp
11425
11423
  }
11426
11424
  }
@@ -11432,7 +11430,6 @@ function registerMediaUploadAppResource(server, resolveEndpoint) {
11432
11430
  text: html,
11433
11431
  _meta: {
11434
11432
  ui: {
11435
- domain: MEDIA_UPLOAD_APP_DOMAIN,
11436
11433
  csp
11437
11434
  }
11438
11435
  }
package/dist/stdio.js CHANGED
@@ -264,13 +264,12 @@ var ApiClient = class {
264
264
  };
265
265
 
266
266
  // src/index.ts
267
- var MCP_VERSION = "3.1.1";
267
+ var MCP_VERSION = "3.1.2";
268
268
 
269
269
  // src/meta-upload-app.ts
270
270
  import { readFileSync } from "fs";
271
271
  import { registerAppResource, RESOURCE_MIME_TYPE } from "@modelcontextprotocol/ext-apps/server";
272
272
  var MEDIA_UPLOAD_APP_RESOURCE_URI = "ui://media-upload/v1.html";
273
- var MEDIA_UPLOAD_APP_DOMAIN = "https://productmaker.app";
274
273
  var MEDIA_UPLOAD_APP_NAME = "ProductMaker media upload";
275
274
  var MEDIA_UPLOAD_APP_HTML_URL = new URL("./ui/meta-upload-app.html", import.meta.url);
276
275
  var MEDIA_UPLOAD_APP_CSP = {
@@ -316,7 +315,6 @@ function registerMediaUploadAppResource(server, resolveEndpoint) {
316
315
  mimeType: RESOURCE_MIME_TYPE,
317
316
  _meta: {
318
317
  ui: {
319
- domain: MEDIA_UPLOAD_APP_DOMAIN,
320
318
  csp
321
319
  }
322
320
  }
@@ -328,7 +326,6 @@ function registerMediaUploadAppResource(server, resolveEndpoint) {
328
326
  text: html,
329
327
  _meta: {
330
328
  ui: {
331
- domain: MEDIA_UPLOAD_APP_DOMAIN,
332
329
  csp
333
330
  }
334
331
  }
@@ -548,6 +548,7 @@
548
548
  const IMAGE_COMPRESSION_THRESHOLD_BYTES = 1.8 * 1024 * 1024;
549
549
  const IMAGE_COMPRESSION_MAX_BYTES = 1.8 * 1024 * 1024;
550
550
  const IMAGE_MAX_DIMENSION = 2048;
551
+ const PREVIEW_MAX_DIMENSION = 256;
551
552
  const MAX_UPLOAD_ATTEMPTS = 3;
552
553
  const TOOL_RESULT_TEXT_LIMIT = 100000;
553
554
  const RPC_TIMEOUT_MS = 15000;
@@ -1560,6 +1561,8 @@
1560
1561
  const item = createItem(file, mediaKind);
1561
1562
  state.items.push(item);
1562
1563
  renderItems();
1564
+ // Thumbnail is cosmetic and must never delay or block the upload.
1565
+ void upgradePreviewToDataUrl(item);
1563
1566
  await prepareAndUpload(item);
1564
1567
  }
1565
1568
  state.busyBatch = false;
@@ -1685,39 +1688,77 @@
1685
1688
  return inspection;
1686
1689
  }
1687
1690
 
1688
- function inspectImage(file) {
1689
- return new Promise((resolve, reject) => {
1691
+ /**
1692
+ * Decodes `file` into a canvas-drawable source WITHOUT a `blob:` URL.
1693
+ *
1694
+ * Host sandboxes restrict `img-src`, and ChatGPT's does not allow `blob:`
1695
+ * ("Loading the image 'blob:…' violates the following Content Security Policy
1696
+ * directive: img-src 'self' data: …"), so `<img>.src = URL.createObjectURL(file)`
1697
+ * is blocked, fires onerror, and every image was rejected as unreadable —
1698
+ * regardless of its format. `createImageBitmap` reads the File directly, so no
1699
+ * URL and no CSP is involved. The object-URL path stays only as a fallback for
1700
+ * hosts without it, where nothing blocked us in the first place.
1701
+ *
1702
+ * Callers own the result: call releaseImageSource() when done.
1703
+ */
1704
+ async function decodeImageSource(file) {
1705
+ if (typeof createImageBitmap === "function") {
1706
+ try {
1707
+ return await createImageBitmap(file);
1708
+ } catch {
1709
+ // fall through to the object-URL path
1710
+ }
1711
+ }
1712
+ return await new Promise((resolve, reject) => {
1690
1713
  const image = new Image();
1691
1714
  const objectUrl = URL.createObjectURL(file);
1692
- const cleanup = () => URL.revokeObjectURL(objectUrl);
1693
1715
  image.onload = () => {
1694
- cleanup();
1695
- const width = image.naturalWidth;
1696
- const height = image.naturalHeight;
1697
- if (!validDimension(width) || !validDimension(height)) {
1698
- reject(
1699
- validationError(
1700
- "invalid_metadata",
1701
- "No se pudo leer la imagen.",
1702
- ),
1703
- );
1704
- return;
1705
- }
1706
- resolve({ mediaKind: "image", width, height, image });
1716
+ URL.revokeObjectURL(objectUrl);
1717
+ resolve(image);
1707
1718
  };
1708
1719
  image.onerror = () => {
1709
- cleanup();
1710
- reject(
1711
- validationError(
1712
- "invalid_metadata",
1713
- "No se pudo leer la imagen.",
1714
- ),
1715
- );
1720
+ URL.revokeObjectURL(objectUrl);
1721
+ reject(new Error("decode_failed"));
1716
1722
  };
1717
1723
  image.src = objectUrl;
1718
1724
  });
1719
1725
  }
1720
1726
 
1727
+ function releaseImageSource(source) {
1728
+ if (source && typeof source.close === "function") source.close();
1729
+ }
1730
+
1731
+ function sourceDimensions(source) {
1732
+ return {
1733
+ width: source.width ?? source.naturalWidth,
1734
+ height: source.height ?? source.naturalHeight,
1735
+ };
1736
+ }
1737
+
1738
+ async function inspectImage(file) {
1739
+ let source;
1740
+ try {
1741
+ source = await decodeImageSource(file);
1742
+ } catch {
1743
+ throw validationError(
1744
+ "invalid_metadata",
1745
+ "No se pudo leer la imagen.",
1746
+ );
1747
+ }
1748
+ try {
1749
+ const { width, height } = sourceDimensions(source);
1750
+ if (!validDimension(width) || !validDimension(height)) {
1751
+ throw validationError(
1752
+ "invalid_metadata",
1753
+ "No se pudo leer la imagen.",
1754
+ );
1755
+ }
1756
+ return { mediaKind: "image", width, height };
1757
+ } finally {
1758
+ releaseImageSource(source);
1759
+ }
1760
+ }
1761
+
1721
1762
  function inspectVideo(file) {
1722
1763
  return new Promise((resolve, reject) => {
1723
1764
  const video = document.createElement("video");
@@ -1772,6 +1813,15 @@
1772
1813
 
1773
1814
  async function compressImage(file, inspection) {
1774
1815
  if (file.size <= IMAGE_COMPRESSION_THRESHOLD_BYTES) return file;
1816
+ // Decoded here rather than carried over from inspectImage so the source's
1817
+ // lifetime never spans two functions: an ImageBitmap must be closed, and only
1818
+ // oversized files reach this path at all.
1819
+ let source;
1820
+ try {
1821
+ source = await decodeImageSource(file);
1822
+ } catch {
1823
+ return file;
1824
+ }
1775
1825
  try {
1776
1826
  const scale = Math.min(
1777
1827
  1,
@@ -1791,13 +1841,7 @@
1791
1841
  context.fillRect(0, 0, canvas.width, canvas.height);
1792
1842
  context.restore();
1793
1843
  }
1794
- context.drawImage(
1795
- inspection.image,
1796
- 0,
1797
- 0,
1798
- canvas.width,
1799
- canvas.height,
1800
- );
1844
+ context.drawImage(source, 0, 0, canvas.width, canvas.height);
1801
1845
  let blob = await canvasBlob(canvas, file.type);
1802
1846
  if (
1803
1847
  file.type === "image/png" &&
@@ -1830,6 +1874,8 @@
1830
1874
  safeError.safeMessage = "No se pudo comprimir la imagen.";
1831
1875
  safeError.safeStage = "compression";
1832
1876
  throw safeError;
1877
+ } finally {
1878
+ releaseImageSource(source);
1833
1879
  }
1834
1880
  }
1835
1881
 
@@ -2112,21 +2158,24 @@
2112
2158
 
2113
2159
  function revokePreviewUrl(item) {
2114
2160
  if (
2115
- item.previewUrl &&
2161
+ item.previewUrl.startsWith("blob:") &&
2116
2162
  typeof window.URL.revokeObjectURL === "function"
2117
2163
  ) {
2118
2164
  window.URL.revokeObjectURL(item.previewUrl);
2119
- item.previewUrl = "";
2120
2165
  }
2166
+ item.previewUrl = "";
2167
+ }
2168
+
2169
+ function previewableImage(item) {
2170
+ return Boolean(
2171
+ item.file &&
2172
+ item.mediaKind === "image" &&
2173
+ IMAGE_TYPES.has(item.file.type),
2174
+ );
2121
2175
  }
2122
2176
 
2123
2177
  function previewUrlFor(item) {
2124
- if (
2125
- !item.file ||
2126
- item.mediaKind !== "image" ||
2127
- !IMAGE_TYPES.has(item.file.type)
2128
- )
2129
- return "";
2178
+ if (!previewableImage(item)) return "";
2130
2179
  if (
2131
2180
  !item.previewUrl &&
2132
2181
  typeof window.URL.createObjectURL === "function"
@@ -2136,6 +2185,44 @@
2136
2185
  return item.previewUrl;
2137
2186
  }
2138
2187
 
2188
+ /**
2189
+ * Upgrades the thumbnail from the `blob:` object URL to a small `data:` URL.
2190
+ *
2191
+ * ChatGPT's sandbox blocks `blob:` in `img-src` (see decodeImageSource), so the
2192
+ * object URL renders as a broken image there; `data:` is allowed. This only ever
2193
+ * replaces a working-or-broken preview with a working one, so a failure here is
2194
+ * cosmetic and leaves the existing behaviour untouched on hosts that allow
2195
+ * `blob:`. Downscaled first so the inline URL stays a few KB, not the whole file.
2196
+ */
2197
+ async function upgradePreviewToDataUrl(item) {
2198
+ if (!previewableImage(item)) return;
2199
+ let source;
2200
+ try {
2201
+ source = await decodeImageSource(item.file);
2202
+ const { width, height } = sourceDimensions(source);
2203
+ if (!validDimension(width) || !validDimension(height)) return;
2204
+ const scale = Math.min(
2205
+ 1,
2206
+ PREVIEW_MAX_DIMENSION / Math.max(width, height),
2207
+ );
2208
+ const canvas = document.createElement("canvas");
2209
+ canvas.width = Math.max(1, Math.round(width * scale));
2210
+ canvas.height = Math.max(1, Math.round(height * scale));
2211
+ const context = canvas.getContext("2d");
2212
+ if (!context) return;
2213
+ context.drawImage(source, 0, 0, canvas.width, canvas.height);
2214
+ const dataUrl = canvas.toDataURL("image/jpeg", 0.7);
2215
+ if (!dataUrl.startsWith("data:image/")) return;
2216
+ revokePreviewUrl(item);
2217
+ item.previewUrl = dataUrl;
2218
+ renderItems();
2219
+ } catch {
2220
+ // Cosmetic only: the object-URL preview and the upload both stand on their own.
2221
+ } finally {
2222
+ releaseImageSource(source);
2223
+ }
2224
+ }
2225
+
2139
2226
  function renderItems() {
2140
2227
  const fragment = document.createDocumentFragment();
2141
2228
  state.items.forEach((item, index) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@productmaker/mcp",
3
- "version": "3.1.1",
3
+ "version": "3.1.2",
4
4
  "type": "module",
5
5
  "private": false,
6
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.",