@dynamic-mockups/mcp 1.0.6 → 1.0.8

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 (3) hide show
  1. package/README.md +18 -16
  2. package/package.json +1 -1
  3. package/src/index.js +171 -18
package/README.md CHANGED
@@ -13,15 +13,15 @@ Add the following to your MCP client configuration file:
13
13
 
14
14
  ```json
15
15
  {
16
- "mcpServers": {
17
- "dynamic-mockups": {
18
- "command": "npx",
19
- "args": ["-y", "@dynamic-mockups/mcp"],
20
- "env": {
21
- "DYNAMIC_MOCKUPS_API_KEY": "your_api_key_here"
22
- }
16
+ "mcpServers": {
17
+ "dynamic-mockups": {
18
+ "command": "npx",
19
+ "args": ["-y", "@dynamic-mockups/mcp"],
20
+ "env": {
21
+ "DYNAMIC_MOCKUPS_API_KEY": "your_api_key_here"
23
22
  }
24
- }
23
+ }
24
+ }
25
25
  }
26
26
  ```
27
27
 
@@ -37,15 +37,15 @@ If you want to connect via HTTP instead of NPX, use:
37
37
 
38
38
  ```json
39
39
  {
40
- "mcpServers": {
41
- "dynamic-mockups": {
42
- "type": "http",
43
- "url": "https://mcp.dynamicmockups.com",
44
- "headers": {
45
- "x-api-key": "your_api_key_here"
46
- }
40
+ "mcpServers": {
41
+ "dynamic-mockups": {
42
+ "type": "http",
43
+ "url": "https://mcp.dynamicmockups.com",
44
+ "headers": {
45
+ "x-api-key": "your_api_key_here"
47
46
  }
48
- }
47
+ }
48
+ }
49
49
  }
50
50
  ```
51
51
 
@@ -75,6 +75,7 @@ If you want to connect via HTTP instead of NPX, use:
75
75
  | `export_print_files` | Export high-resolution print files for production |
76
76
  | `upload_psd` | Upload a PSD file and optionally create a mockup template |
77
77
  | `delete_psd` | Delete a PSD file with optional related mockups deletion |
78
+ | `tool_create_embroidery_effect` | Transform any image into a realistic embroidery/stitched effect |
78
79
 
79
80
  ## Usage Examples
80
81
 
@@ -91,6 +92,7 @@ Ask your AI assistant:
91
92
  | Upload PSD | "Upload my PSD mockup from url: https://example.com/my-mockup.psd and create a template from it" |
92
93
  | API info | "What are the rate limits and supported file formats for Dynamic Mockups?" |
93
94
  | Print files | "Export print-ready files at 300 DPI for my poster mockup" |
95
+ | Embroidery effect | "Transform my logo into an embroidery effect from url: https://example.com/my-logo.png" |
94
96
 
95
97
  ## Error Handling
96
98
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynamic-mockups/mcp",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Official Dynamic Mockups MCP Server - Generate product mockups with AI assistants",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/index.js CHANGED
@@ -29,6 +29,9 @@ const API_KEY = process.env.DYNAMIC_MOCKUPS_API_KEY;
29
29
  const SERVER_NAME = "dynamic-mockups-mcp";
30
30
  const SERVER_VERSION = "1.0.0";
31
31
 
32
+ // Transport mode tracking (set during server startup)
33
+ let currentTransportMode = "stdio";
34
+
32
35
  // =============================================================================
33
36
  // API Knowledge Base
34
37
  // =============================================================================
@@ -407,20 +410,72 @@ const server = new Server(
407
410
  // HTTP Client
408
411
  // =============================================================================
409
412
 
413
+ /**
414
+ * Returns MCP tracking headers for API requests.
415
+ * @param {string} toolName - The name of the MCP tool being called
416
+ * @returns {Object} Headers object with tracking information
417
+ */
418
+ function getMcpTrackingHeaders(toolName) {
419
+ return {
420
+ "x-mcp-server": "true",
421
+ "x-mcp-tool": toolName,
422
+ "x-mcp-transport-mode": currentTransportMode,
423
+ };
424
+ }
425
+
426
+ /**
427
+ * Tracks MCP tool usage by sending event to /mcp/track endpoint (fire and forget).
428
+ * Called after every tool execution with success/error status.
429
+ *
430
+ * @param {string} toolName - The name of the MCP tool
431
+ * @param {string} apiKey - The API key for the request
432
+ * @param {Object} result - The tool execution result
433
+ * @param {boolean} result.success - Whether the tool executed successfully
434
+ * @param {string|null} result.error - Error message if failed, null otherwise
435
+ */
436
+ function trackToolUsage(toolName, apiKey, { success, error }) {
437
+ if (!apiKey) return; // Skip tracking if no API key
438
+
439
+ try {
440
+ // Fire and forget - don't await or handle errors
441
+ axios.post(
442
+ `${API_BASE_URL}/mcp/track`,
443
+ {
444
+ tool: toolName,
445
+ success,
446
+ error: error || null,
447
+ },
448
+ {
449
+ headers: {
450
+ "Accept": "application/json",
451
+ "Content-Type": "application/json",
452
+ "x-api-key": apiKey,
453
+ ...getMcpTrackingHeaders(toolName),
454
+ },
455
+ timeout: 5000, // Short timeout for tracking
456
+ }
457
+ ).catch(() => {}); // Silently ignore errors
458
+ } catch {
459
+ // Silently ignore any errors - tracking should never break functionality
460
+ }
461
+ }
462
+
410
463
  /**
411
464
  * Creates an API client with the provided API key.
412
465
  * For stdio transport: uses environment variable
413
466
  * For HTTP transport: uses client-provided API key from Authorization header
414
467
  *
415
468
  * @param {string} apiKey - The API key to use for requests
469
+ * @param {string} toolName - The name of the MCP tool (for tracking)
416
470
  */
417
- function createApiClient(apiKey) {
471
+ function createApiClient(apiKey, toolName) {
418
472
  return axios.create({
419
473
  baseURL: API_BASE_URL,
420
474
  headers: {
421
475
  "Accept": "application/json",
422
476
  "Content-Type": "application/json",
423
477
  "x-api-key": apiKey || "",
478
+ ...getMcpTrackingHeaders(toolName),
424
479
  },
425
480
  timeout: 60000, // 60 second timeout for render operations
426
481
  validateStatus: (status) => status < 500, // Only throw on 5xx errors
@@ -498,6 +553,7 @@ function getApiKey(extra) {
498
553
  // - upload_psd: When user wants to add their own PSD mockup template
499
554
  // - delete_psd: When user wants to remove an uploaded PSD
500
555
  // - create_collection: When user wants to organize mockups into groups
556
+ // - tool_create_embroidery_effect: When user wants to transform an image into embroidery/stitched effect
501
557
  //
502
558
  // =============================================================================
503
559
 
@@ -1179,7 +1235,7 @@ RETURNS: {uuid, name} of the uploaded PSD file.`,
1179
1235
 
1180
1236
  API: POST /psd/delete
1181
1237
 
1182
- WHEN TO USE: When user wants to:
1238
+ WHEN TO USE: When user wants to:
1183
1239
  - Remove an uploaded PSD file
1184
1240
  - Clean up unused PSD files
1185
1241
  - Optionally remove all mockups derived from the PSD
@@ -1202,6 +1258,48 @@ RETURNS: Success confirmation message.`,
1202
1258
  required: ["psd_uuid"],
1203
1259
  },
1204
1260
  },
1261
+
1262
+ // ─────────────────────────────────────────────────────────────────────────────
1263
+ // EFFECT TOOLS
1264
+ // ─────────────────────────────────────────────────────────────────────────────
1265
+ {
1266
+ name: "tool_create_embroidery_effect",
1267
+ description: `Transform any image into a realistic embroidery/stitched effect.
1268
+
1269
+ API: POST /tools/embroidery
1270
+ COST: 6 credits per request
1271
+
1272
+ WHEN TO USE: When user wants to:
1273
+ - Convert artwork/designs into embroidery style
1274
+ - Create stitched/embroidered versions of logos or images
1275
+ - Prepare designs for print-on-demand embroidery products
1276
+ - Transform existing artwork to look like embroidery before rendering on mockups
1277
+
1278
+ INPUT: Provide image via EITHER:
1279
+ - image_url: Public URL to the image (PNG, JPG, WEBP supported)
1280
+ - image_data_b64: Base64-encoded image data
1281
+ Only ONE input method is required per request.
1282
+
1283
+ TIPS FOR BEST RESULTS:
1284
+ - Use high-contrast images with clean edges
1285
+ - Simpler designs with fewer colors produce more realistic embroidery effects
1286
+ - The output can be used directly in mockup renders or saved to asset library
1287
+
1288
+ RETURNS: {export_path} - URL to the generated embroidery image (temporary, should be downloaded or saved to permanent storage).`,
1289
+ inputSchema: {
1290
+ type: "object",
1291
+ properties: {
1292
+ image_url: {
1293
+ type: "string",
1294
+ description: "Public URL to the image to transform. Supported formats: PNG, JPG, WEBP. Either image_url OR image_data_b64 must be provided.",
1295
+ },
1296
+ image_data_b64: {
1297
+ type: "string",
1298
+ description: "Base64-encoded image data. Either image_url OR image_data_b64 must be provided.",
1299
+ },
1300
+ },
1301
+ },
1302
+ },
1205
1303
  ];
1206
1304
 
1207
1305
  // =============================================================================
@@ -1285,7 +1383,7 @@ async function handleGetCatalogs(args, extra) {
1285
1383
  if (error) return error;
1286
1384
 
1287
1385
  try {
1288
- const response = await createApiClient(apiKey).get("/catalogs");
1386
+ const response = await createApiClient(apiKey, "get_catalogs").get("/catalogs");
1289
1387
  return ResponseFormatter.fromApiResponse(response);
1290
1388
  } catch (err) {
1291
1389
  return ResponseFormatter.fromError(err, "Failed to get catalogs");
@@ -1304,7 +1402,7 @@ async function handleGetCollections(args = {}, extra) {
1304
1402
  params.append("include_all_catalogs", args.include_all_catalogs);
1305
1403
  }
1306
1404
 
1307
- const response = await createApiClient(apiKey).get(`/collections?${params}`);
1405
+ const response = await createApiClient(apiKey, "get_collections").get(`/collections?${params}`);
1308
1406
  return ResponseFormatter.fromApiResponse(response);
1309
1407
  } catch (err) {
1310
1408
  return ResponseFormatter.fromError(err, "Failed to get collections");
@@ -1320,7 +1418,7 @@ async function handleCreateCollection(args, extra) {
1320
1418
  const payload = { name: args.name };
1321
1419
  if (args.catalog_uuid) payload.catalog_uuid = args.catalog_uuid;
1322
1420
 
1323
- const response = await createApiClient(apiKey).post("/collections", payload);
1421
+ const response = await createApiClient(apiKey, "create_collection").post("/collections", payload);
1324
1422
  return ResponseFormatter.fromApiResponse(response, `Collection "${args.name}" created`);
1325
1423
  } catch (err) {
1326
1424
  return ResponseFormatter.fromError(err, "Failed to create collection");
@@ -1341,7 +1439,7 @@ async function handleGetMockups(args = {}, extra) {
1341
1439
  }
1342
1440
  if (args.name) params.append("name", args.name);
1343
1441
 
1344
- const response = await createApiClient(apiKey).get(`/mockups?${params}`);
1442
+ const response = await createApiClient(apiKey, "get_mockups").get(`/mockups?${params}`);
1345
1443
  return ResponseFormatter.fromApiResponse(response);
1346
1444
  } catch (err) {
1347
1445
  return ResponseFormatter.fromError(err, "Failed to get mockups");
@@ -1354,7 +1452,7 @@ async function handleGetMockupByUuid(args, extra) {
1354
1452
  if (error) return error;
1355
1453
 
1356
1454
  try {
1357
- const response = await createApiClient(apiKey).get(`/mockup/${args.uuid}`);
1455
+ const response = await createApiClient(apiKey, "get_mockup_by_uuid").get(`/mockup/${args.uuid}`);
1358
1456
  return ResponseFormatter.fromApiResponse(response);
1359
1457
  } catch (err) {
1360
1458
  return ResponseFormatter.fromError(err, "Failed to get mockup");
@@ -1375,7 +1473,7 @@ async function handleCreateRender(args, extra) {
1375
1473
  if (args.export_options) payload.export_options = args.export_options;
1376
1474
  if (args.text_layers) payload.text_layers = args.text_layers;
1377
1475
 
1378
- const response = await createApiClient(apiKey).post("/renders", payload);
1476
+ const response = await createApiClient(apiKey, "create_render").post("/renders", payload);
1379
1477
  return ResponseFormatter.fromApiResponse(response, "Render created (1 credit used)");
1380
1478
  } catch (err) {
1381
1479
  return ResponseFormatter.fromError(err, "Failed to create render");
@@ -1391,7 +1489,7 @@ async function handleCreateBatchRender(args, extra) {
1391
1489
  const payload = { renders: args.renders };
1392
1490
  if (args.export_options) payload.export_options = args.export_options;
1393
1491
 
1394
- const response = await createApiClient(apiKey).post("/renders/batch", payload);
1492
+ const response = await createApiClient(apiKey, "create_batch_render").post("/renders/batch", payload);
1395
1493
  const count = args.renders?.length || 0;
1396
1494
  return ResponseFormatter.fromApiResponse(response, `Batch render complete (${count} credits used)`);
1397
1495
  } catch (err) {
@@ -1413,7 +1511,7 @@ async function handleExportPrintFiles(args, extra) {
1413
1511
  if (args.export_options) payload.export_options = args.export_options;
1414
1512
  if (args.text_layers) payload.text_layers = args.text_layers;
1415
1513
 
1416
- const response = await createApiClient(apiKey).post("/renders/print-files", payload);
1514
+ const response = await createApiClient(apiKey, "export_print_files").post("/renders/print-files", payload);
1417
1515
  return ResponseFormatter.fromApiResponse(response, "Print files exported");
1418
1516
  } catch (err) {
1419
1517
  return ResponseFormatter.fromError(err, "Failed to export print files");
@@ -1431,7 +1529,7 @@ async function handleUploadPsd(args, extra) {
1431
1529
  if (args.psd_category_id) payload.psd_category_id = args.psd_category_id;
1432
1530
  if (args.mockup_template) payload.mockup_template = args.mockup_template;
1433
1531
 
1434
- const response = await createApiClient(apiKey).post("/psd/upload", payload);
1532
+ const response = await createApiClient(apiKey, "upload_psd").post("/psd/upload", payload);
1435
1533
  return ResponseFormatter.fromApiResponse(response, "PSD uploaded successfully");
1436
1534
  } catch (err) {
1437
1535
  return ResponseFormatter.fromError(err, "Failed to upload PSD");
@@ -1449,13 +1547,38 @@ async function handleDeletePsd(args, extra) {
1449
1547
  payload.delete_related_mockups = args.delete_related_mockups;
1450
1548
  }
1451
1549
 
1452
- const response = await createApiClient(apiKey).post("/psd/delete", payload);
1550
+ const response = await createApiClient(apiKey, "delete_psd").post("/psd/delete", payload);
1453
1551
  return ResponseFormatter.fromApiResponse(response, "PSD deleted successfully");
1454
1552
  } catch (err) {
1455
1553
  return ResponseFormatter.fromError(err, "Failed to delete PSD");
1456
1554
  }
1457
1555
  }
1458
1556
 
1557
+ async function handleCreateEmbroideryEffect(args, extra) {
1558
+ const apiKey = getApiKey(extra);
1559
+ const error = validateApiKey(apiKey);
1560
+ if (error) return error;
1561
+
1562
+ // Validate that at least one input method is provided
1563
+ if (!args.image_url && !args.image_data_b64) {
1564
+ return ResponseFormatter.error(
1565
+ "Missing required input",
1566
+ { solution: "Provide either image_url (public URL) or image_data_b64 (base64-encoded image data)." }
1567
+ );
1568
+ }
1569
+
1570
+ try {
1571
+ const payload = {};
1572
+ if (args.image_url) payload.image_url = args.image_url;
1573
+ if (args.image_data_b64) payload.image_data_b64 = args.image_data_b64;
1574
+
1575
+ const response = await createApiClient(apiKey, "tool_create_embroidery_effect").post("/tools/embroidery", payload);
1576
+ return ResponseFormatter.fromApiResponse(response, "Embroidery effect created (6 credits used)");
1577
+ } catch (err) {
1578
+ return ResponseFormatter.fromError(err, "Failed to create embroidery effect");
1579
+ }
1580
+ }
1581
+
1459
1582
  // =============================================================================
1460
1583
  // Tool Router
1461
1584
  // =============================================================================
@@ -1473,6 +1596,7 @@ const toolHandlers = {
1473
1596
  export_print_files: handleExportPrintFiles,
1474
1597
  upload_psd: handleUploadPsd,
1475
1598
  delete_psd: handleDeletePsd,
1599
+ tool_create_embroidery_effect: handleCreateEmbroideryEffect,
1476
1600
  };
1477
1601
 
1478
1602
  // =============================================================================
@@ -1483,17 +1607,29 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
1483
1607
 
1484
1608
  server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
1485
1609
  const { name, arguments: args } = request.params;
1610
+ const apiKey = getApiKey(extra);
1486
1611
 
1487
1612
  const handler = toolHandlers[name];
1488
1613
  if (!handler) {
1489
- return ResponseFormatter.error(`Unknown tool: ${name}`);
1614
+ const result = ResponseFormatter.error(`Unknown tool: ${name}`);
1615
+ trackToolUsage(name, apiKey, { success: false, error: `Unknown tool: ${name}` });
1616
+ return result;
1490
1617
  }
1491
1618
 
1492
1619
  try {
1493
1620
  // Pass extra context (contains requestInfo with headers for HTTP transport)
1494
- return await handler(args || {}, extra);
1621
+ const result = await handler(args || {}, extra);
1622
+
1623
+ // Track tool usage (fire and forget)
1624
+ const isError = result.isError || false;
1625
+ const errorMessage = isError && result.content?.[0]?.text ? result.content[0].text : null;
1626
+ trackToolUsage(name, apiKey, { success: !isError, error: errorMessage });
1627
+
1628
+ return result;
1495
1629
  } catch (err) {
1496
- return ResponseFormatter.fromError(err, `Error executing ${name}`);
1630
+ const result = ResponseFormatter.fromError(err, `Error executing ${name}`);
1631
+ trackToolUsage(name, apiKey, { success: false, error: err.message || `Error executing ${name}` });
1632
+ return result;
1497
1633
  }
1498
1634
  });
1499
1635
 
@@ -1506,6 +1642,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
1506
1642
  * Used by: Claude Desktop, Claude Code, Cursor, Windsurf
1507
1643
  */
1508
1644
  async function startStdioServer() {
1645
+ currentTransportMode = "stdio";
1509
1646
  const transport = new StdioServerTransport();
1510
1647
  await server.connect(transport);
1511
1648
  console.error(`Dynamic Mockups MCP Server v${SERVER_VERSION} running (stdio)`);
@@ -1525,6 +1662,8 @@ async function startStdioServer() {
1525
1662
  * @returns {Promise<{app: Express, httpServer: Server}>}
1526
1663
  */
1527
1664
  async function startHttpServer(options = {}) {
1665
+ currentTransportMode = "http";
1666
+
1528
1667
  const {
1529
1668
  port = process.env.PORT || 3000,
1530
1669
  host = process.env.HOST || "0.0.0.0",
@@ -1606,15 +1745,29 @@ async function startHttpServer(options = {}) {
1606
1745
  connectionServer.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
1607
1746
  connectionServer.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
1608
1747
  const { name, arguments: args } = request.params;
1748
+ const apiKey = getApiKey(extra);
1749
+
1609
1750
  const handler = toolHandlers[name];
1610
1751
  if (!handler) {
1611
- return ResponseFormatter.error(`Unknown tool: ${name}`);
1752
+ const result = ResponseFormatter.error(`Unknown tool: ${name}`);
1753
+ trackToolUsage(name, apiKey, { success: false, error: `Unknown tool: ${name}` });
1754
+ return result;
1612
1755
  }
1756
+
1613
1757
  try {
1614
1758
  // Pass extra context (contains requestInfo with headers for API key extraction)
1615
- return await handler(args || {}, extra);
1759
+ const result = await handler(args || {}, extra);
1760
+
1761
+ // Track tool usage (fire and forget)
1762
+ const isError = result.isError || false;
1763
+ const errorMessage = isError && result.content?.[0]?.text ? result.content[0].text : null;
1764
+ trackToolUsage(name, apiKey, { success: !isError, error: errorMessage });
1765
+
1766
+ return result;
1616
1767
  } catch (err) {
1617
- return ResponseFormatter.fromError(err, `Error executing ${name}`);
1768
+ const result = ResponseFormatter.fromError(err, `Error executing ${name}`);
1769
+ trackToolUsage(name, apiKey, { success: false, error: err.message || `Error executing ${name}` });
1770
+ return result;
1618
1771
  }
1619
1772
  });
1620
1773