@dynamic-mockups/mcp 1.0.6 → 1.0.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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +101 -17
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.7",
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
@@ -1285,7 +1340,7 @@ async function handleGetCatalogs(args, extra) {
1285
1340
  if (error) return error;
1286
1341
 
1287
1342
  try {
1288
- const response = await createApiClient(apiKey).get("/catalogs");
1343
+ const response = await createApiClient(apiKey, "get_catalogs").get("/catalogs");
1289
1344
  return ResponseFormatter.fromApiResponse(response);
1290
1345
  } catch (err) {
1291
1346
  return ResponseFormatter.fromError(err, "Failed to get catalogs");
@@ -1304,7 +1359,7 @@ async function handleGetCollections(args = {}, extra) {
1304
1359
  params.append("include_all_catalogs", args.include_all_catalogs);
1305
1360
  }
1306
1361
 
1307
- const response = await createApiClient(apiKey).get(`/collections?${params}`);
1362
+ const response = await createApiClient(apiKey, "get_collections").get(`/collections?${params}`);
1308
1363
  return ResponseFormatter.fromApiResponse(response);
1309
1364
  } catch (err) {
1310
1365
  return ResponseFormatter.fromError(err, "Failed to get collections");
@@ -1320,7 +1375,7 @@ async function handleCreateCollection(args, extra) {
1320
1375
  const payload = { name: args.name };
1321
1376
  if (args.catalog_uuid) payload.catalog_uuid = args.catalog_uuid;
1322
1377
 
1323
- const response = await createApiClient(apiKey).post("/collections", payload);
1378
+ const response = await createApiClient(apiKey, "create_collection").post("/collections", payload);
1324
1379
  return ResponseFormatter.fromApiResponse(response, `Collection "${args.name}" created`);
1325
1380
  } catch (err) {
1326
1381
  return ResponseFormatter.fromError(err, "Failed to create collection");
@@ -1341,7 +1396,7 @@ async function handleGetMockups(args = {}, extra) {
1341
1396
  }
1342
1397
  if (args.name) params.append("name", args.name);
1343
1398
 
1344
- const response = await createApiClient(apiKey).get(`/mockups?${params}`);
1399
+ const response = await createApiClient(apiKey, "get_mockups").get(`/mockups?${params}`);
1345
1400
  return ResponseFormatter.fromApiResponse(response);
1346
1401
  } catch (err) {
1347
1402
  return ResponseFormatter.fromError(err, "Failed to get mockups");
@@ -1354,7 +1409,7 @@ async function handleGetMockupByUuid(args, extra) {
1354
1409
  if (error) return error;
1355
1410
 
1356
1411
  try {
1357
- const response = await createApiClient(apiKey).get(`/mockup/${args.uuid}`);
1412
+ const response = await createApiClient(apiKey, "get_mockup_by_uuid").get(`/mockup/${args.uuid}`);
1358
1413
  return ResponseFormatter.fromApiResponse(response);
1359
1414
  } catch (err) {
1360
1415
  return ResponseFormatter.fromError(err, "Failed to get mockup");
@@ -1375,7 +1430,7 @@ async function handleCreateRender(args, extra) {
1375
1430
  if (args.export_options) payload.export_options = args.export_options;
1376
1431
  if (args.text_layers) payload.text_layers = args.text_layers;
1377
1432
 
1378
- const response = await createApiClient(apiKey).post("/renders", payload);
1433
+ const response = await createApiClient(apiKey, "create_render").post("/renders", payload);
1379
1434
  return ResponseFormatter.fromApiResponse(response, "Render created (1 credit used)");
1380
1435
  } catch (err) {
1381
1436
  return ResponseFormatter.fromError(err, "Failed to create render");
@@ -1391,7 +1446,7 @@ async function handleCreateBatchRender(args, extra) {
1391
1446
  const payload = { renders: args.renders };
1392
1447
  if (args.export_options) payload.export_options = args.export_options;
1393
1448
 
1394
- const response = await createApiClient(apiKey).post("/renders/batch", payload);
1449
+ const response = await createApiClient(apiKey, "create_batch_render").post("/renders/batch", payload);
1395
1450
  const count = args.renders?.length || 0;
1396
1451
  return ResponseFormatter.fromApiResponse(response, `Batch render complete (${count} credits used)`);
1397
1452
  } catch (err) {
@@ -1413,7 +1468,7 @@ async function handleExportPrintFiles(args, extra) {
1413
1468
  if (args.export_options) payload.export_options = args.export_options;
1414
1469
  if (args.text_layers) payload.text_layers = args.text_layers;
1415
1470
 
1416
- const response = await createApiClient(apiKey).post("/renders/print-files", payload);
1471
+ const response = await createApiClient(apiKey, "export_print_files").post("/renders/print-files", payload);
1417
1472
  return ResponseFormatter.fromApiResponse(response, "Print files exported");
1418
1473
  } catch (err) {
1419
1474
  return ResponseFormatter.fromError(err, "Failed to export print files");
@@ -1431,7 +1486,7 @@ async function handleUploadPsd(args, extra) {
1431
1486
  if (args.psd_category_id) payload.psd_category_id = args.psd_category_id;
1432
1487
  if (args.mockup_template) payload.mockup_template = args.mockup_template;
1433
1488
 
1434
- const response = await createApiClient(apiKey).post("/psd/upload", payload);
1489
+ const response = await createApiClient(apiKey, "upload_psd").post("/psd/upload", payload);
1435
1490
  return ResponseFormatter.fromApiResponse(response, "PSD uploaded successfully");
1436
1491
  } catch (err) {
1437
1492
  return ResponseFormatter.fromError(err, "Failed to upload PSD");
@@ -1449,7 +1504,7 @@ async function handleDeletePsd(args, extra) {
1449
1504
  payload.delete_related_mockups = args.delete_related_mockups;
1450
1505
  }
1451
1506
 
1452
- const response = await createApiClient(apiKey).post("/psd/delete", payload);
1507
+ const response = await createApiClient(apiKey, "delete_psd").post("/psd/delete", payload);
1453
1508
  return ResponseFormatter.fromApiResponse(response, "PSD deleted successfully");
1454
1509
  } catch (err) {
1455
1510
  return ResponseFormatter.fromError(err, "Failed to delete PSD");
@@ -1483,17 +1538,29 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
1483
1538
 
1484
1539
  server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
1485
1540
  const { name, arguments: args } = request.params;
1541
+ const apiKey = getApiKey(extra);
1486
1542
 
1487
1543
  const handler = toolHandlers[name];
1488
1544
  if (!handler) {
1489
- return ResponseFormatter.error(`Unknown tool: ${name}`);
1545
+ const result = ResponseFormatter.error(`Unknown tool: ${name}`);
1546
+ trackToolUsage(name, apiKey, { success: false, error: `Unknown tool: ${name}` });
1547
+ return result;
1490
1548
  }
1491
1549
 
1492
1550
  try {
1493
1551
  // Pass extra context (contains requestInfo with headers for HTTP transport)
1494
- return await handler(args || {}, extra);
1552
+ const result = await handler(args || {}, extra);
1553
+
1554
+ // Track tool usage (fire and forget)
1555
+ const isError = result.isError || false;
1556
+ const errorMessage = isError && result.content?.[0]?.text ? result.content[0].text : null;
1557
+ trackToolUsage(name, apiKey, { success: !isError, error: errorMessage });
1558
+
1559
+ return result;
1495
1560
  } catch (err) {
1496
- return ResponseFormatter.fromError(err, `Error executing ${name}`);
1561
+ const result = ResponseFormatter.fromError(err, `Error executing ${name}`);
1562
+ trackToolUsage(name, apiKey, { success: false, error: err.message || `Error executing ${name}` });
1563
+ return result;
1497
1564
  }
1498
1565
  });
1499
1566
 
@@ -1506,6 +1573,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
1506
1573
  * Used by: Claude Desktop, Claude Code, Cursor, Windsurf
1507
1574
  */
1508
1575
  async function startStdioServer() {
1576
+ currentTransportMode = "stdio";
1509
1577
  const transport = new StdioServerTransport();
1510
1578
  await server.connect(transport);
1511
1579
  console.error(`Dynamic Mockups MCP Server v${SERVER_VERSION} running (stdio)`);
@@ -1525,6 +1593,8 @@ async function startStdioServer() {
1525
1593
  * @returns {Promise<{app: Express, httpServer: Server}>}
1526
1594
  */
1527
1595
  async function startHttpServer(options = {}) {
1596
+ currentTransportMode = "http";
1597
+
1528
1598
  const {
1529
1599
  port = process.env.PORT || 3000,
1530
1600
  host = process.env.HOST || "0.0.0.0",
@@ -1606,15 +1676,29 @@ async function startHttpServer(options = {}) {
1606
1676
  connectionServer.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
1607
1677
  connectionServer.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
1608
1678
  const { name, arguments: args } = request.params;
1679
+ const apiKey = getApiKey(extra);
1680
+
1609
1681
  const handler = toolHandlers[name];
1610
1682
  if (!handler) {
1611
- return ResponseFormatter.error(`Unknown tool: ${name}`);
1683
+ const result = ResponseFormatter.error(`Unknown tool: ${name}`);
1684
+ trackToolUsage(name, apiKey, { success: false, error: `Unknown tool: ${name}` });
1685
+ return result;
1612
1686
  }
1687
+
1613
1688
  try {
1614
1689
  // Pass extra context (contains requestInfo with headers for API key extraction)
1615
- return await handler(args || {}, extra);
1690
+ const result = await handler(args || {}, extra);
1691
+
1692
+ // Track tool usage (fire and forget)
1693
+ const isError = result.isError || false;
1694
+ const errorMessage = isError && result.content?.[0]?.text ? result.content[0].text : null;
1695
+ trackToolUsage(name, apiKey, { success: !isError, error: errorMessage });
1696
+
1697
+ return result;
1616
1698
  } catch (err) {
1617
- return ResponseFormatter.fromError(err, `Error executing ${name}`);
1699
+ const result = ResponseFormatter.fromError(err, `Error executing ${name}`);
1700
+ trackToolUsage(name, apiKey, { success: false, error: err.message || `Error executing ${name}` });
1701
+ return result;
1618
1702
  }
1619
1703
  });
1620
1704