@dynamic-mockups/mcp 1.0.5 → 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 (3) hide show
  1. package/README.md +2 -48
  2. package/package.json +1 -1
  3. package/src/index.js +486 -18
package/README.md CHANGED
@@ -64,6 +64,7 @@ If you want to connect via HTTP instead of NPX, use:
64
64
  | Tool | Description |
65
65
  |------|-------------|
66
66
  | `get_api_info` | Get API knowledge base (billing, rate limits, formats, best practices, support) |
67
+ | `embed_mockup_editor` | Implement embeddable mockup editor in your app |
67
68
  | `get_catalogs` | Retrieve all available catalogs |
68
69
  | `get_collections` | Retrieve collections (optionally filter by catalog) |
69
70
  | `create_collection` | Create a new collection |
@@ -81,6 +82,7 @@ Ask your AI assistant:
81
82
 
82
83
  | Use Case | Example Prompt |
83
84
  |----------|----------------|
85
+ | Embed editor | "Add the full mockup editor to my web application" |
84
86
  | List catalogs | "Get my Dynamic Mockups catalogs" |
85
87
  | Browse mockups | "Show me all mockups in my T-shirt collection" |
86
88
  | Single render | "Create a mockup render using any T-shirt mockup with my artwork from url: https://example.com/my-design.png" |
@@ -90,54 +92,6 @@ Ask your AI assistant:
90
92
  | API info | "What are the rate limits and supported file formats for Dynamic Mockups?" |
91
93
  | Print files | "Export print-ready files at 300 DPI for my poster mockup" |
92
94
 
93
- ## Development
94
-
95
- ### Local Installation
96
-
97
- ```bash
98
- git clone https://github.com/dynamic-mockups/mcp.git
99
- cd mcp-server
100
- npm install
101
- ```
102
-
103
- ### Run Locally (stdio mode - default)
104
-
105
- ```bash
106
- DYNAMIC_MOCKUPS_API_KEY=your_key npm start
107
- ```
108
-
109
- ### Run Locally (HTTP mode)
110
-
111
- ```bash
112
- DYNAMIC_MOCKUPS_API_KEY=your_key npm run start:http
113
- ```
114
-
115
- ### Development Mode (with auto-reload)
116
-
117
- ```bash
118
- # stdio mode
119
- DYNAMIC_MOCKUPS_API_KEY=your_key npm run dev
120
-
121
- # HTTP mode
122
- DYNAMIC_MOCKUPS_API_KEY=your_key npm run dev:http
123
- ```
124
-
125
- ### Use Local Version in MCP Client
126
-
127
- ```json
128
- {
129
- "mcpServers": {
130
- "dynamic-mockups": {
131
- "command": "node",
132
- "args": ["/path/to/mcp-server/src/index.js"],
133
- "env": {
134
- "DYNAMIC_MOCKUPS_API_KEY": "your_api_key_here"
135
- }
136
- }
137
- }
138
- }
139
- ```
140
-
141
95
  ## Error Handling
142
96
 
143
97
  The server returns clear error messages for common issues:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dynamic-mockups/mcp",
3
- "version": "1.0.5",
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
  // =============================================================================
@@ -110,6 +113,290 @@ const API_KNOWLEDGE_BASE = {
110
113
  },
111
114
  };
112
115
 
116
+ // =============================================================================
117
+ // Embed Editor Knowledge Base
118
+ // =============================================================================
119
+
120
+ const EMBED_EDITOR_KNOWLEDGE_BASE = {
121
+ overview: `Dynamic Mockups Embed Editor lets you add a powerful mockup editor directly to your website or app via iFrame.
122
+ Choose between the Classic Editor (template-based mockups) or MockAnything Editor (AI-powered, turn any image into a mockup).
123
+ No API implementation required - just embed with a few lines of JavaScript.`,
124
+
125
+ editor_types: {
126
+ classic: {
127
+ description: "Template-based mockup editor with catalog of pre-made mockup templates",
128
+ use_case: "Best for consistent, professional product mockups from your template library",
129
+ features: ["Browse mockup catalog", "Upload artwork", "Customize colors", "Export mockups"],
130
+ },
131
+ mockanything: {
132
+ description: "AI-powered editor that turns any image into a customizable mockup",
133
+ use_case: "Best for creative flexibility - generate mockups from any product photo",
134
+ features: ["AI scene generation", "Ethnicity/pose changes", "Environment replacement", "AI photoshoot", "Smart product detection"],
135
+ ai_credits: {
136
+ prompt_generation: "3 credits (SeeDream 4.0)",
137
+ ai_tools: "5 credits (NanoBanana) - scene change, ethnicity, camera angle, environment",
138
+ ai_photoshoot: "3 credits per variation",
139
+ free_features: ["Artwork placement", "Product color changes", "Smart detection", "Exports"],
140
+ },
141
+ },
142
+ },
143
+
144
+ quick_start: {
145
+ step_1_iframe: `<iframe
146
+ id="dm-iframe"
147
+ src="https://embed.dynamicmockups.com"
148
+ style="width: 100%; height: 90vh"
149
+ ></iframe>`,
150
+ step_2_cdn_script: `<script src="https://cdn.jsdelivr.net/npm/@dynamic-mockups/mockup-editor-sdk@latest/dist/index.js"></script>`,
151
+ step_3_init: `<script>
152
+ document.addEventListener("DOMContentLoaded", function () {
153
+ DynamicMockups.initDynamicMockupsIframe({
154
+ iframeId: "dm-iframe",
155
+ data: { "x-website-key": "YOUR_WEBSITE_KEY" },
156
+ mode: "download",
157
+ });
158
+ });
159
+ </script>`,
160
+ get_website_key: "https://app.dynamicmockups.com/mockup-editor-embed-integrations",
161
+ },
162
+
163
+ npm_integration: {
164
+ install: "npm install @dynamic-mockups/mockup-editor-sdk@latest",
165
+ usage: `import { initDynamicMockupsIframe } from "@dynamic-mockups/mockup-editor-sdk";
166
+
167
+ initDynamicMockupsIframe({
168
+ iframeId: "dm-iframe",
169
+ data: { "x-website-key": "YOUR_WEBSITE_KEY" },
170
+ mode: "download",
171
+ });`,
172
+ },
173
+
174
+ specific_mockup: {
175
+ description: "Open a specific mockup directly instead of showing the full catalog",
176
+ iframe_src: "https://embed.dynamicmockups.com/mockup/{MOCKUP_UUID}/",
177
+ example: `<iframe
178
+ id="dm-iframe"
179
+ src="https://embed.dynamicmockups.com/mockup/43981bf4-3f1a-46cd-985e-3d9bb40cef36/"
180
+ style="width: 100%; height: 90vh"
181
+ ></iframe>`,
182
+ get_uuid: "Use get_mockups API or find in the web app editor URL",
183
+ },
184
+
185
+ init_function_params: {
186
+ iframeId: { type: "string", required: true, default: "dm-iframe", description: "ID of the iframe element" },
187
+ data: { type: "object", required: true, description: "Configuration object for editor behavior" },
188
+ mode: { type: "string", required: true, options: ["download", "custom"], description: "download: user downloads image directly. custom: use callback to handle export" },
189
+ callback: { type: "function", required: false, description: "Required when mode='custom'. Receives export data when user exports mockup" },
190
+ },
191
+
192
+ data_options: {
193
+ "x-website-key": { type: "string", required: true, description: "Your website key from Dynamic Mockups dashboard" },
194
+ editorType: { type: "string", required: false, default: "classic", options: ["classic", "mockanything"], description: "Editor type to display" },
195
+ themeAppearance: { type: "string", required: false, default: "light", options: ["light", "dark"], description: "UI theme" },
196
+ showColorPicker: { type: "boolean", required: false, default: true, description: "Show color picker" },
197
+ showColorPresets: { type: "boolean", required: false, default: false, description: "Show color presets from your account" },
198
+ showCollectionsWidget: { type: "boolean", required: false, default: true, description: "Show collections widget" },
199
+ showSmartObjectArea: { type: "boolean", required: false, default: false, description: "Display smart object boundaries" },
200
+ showTransformControls: { type: "boolean", required: false, default: true, description: "Show width/height/rotate inputs" },
201
+ showArtworkLibrary: { type: "boolean", required: false, default: false, description: "Show artwork library" },
202
+ showUploadYourArtwork: { type: "boolean", required: false, default: true, description: "Show 'Upload your artwork' button" },
203
+ showArtworkEditor: { type: "boolean", required: false, default: true, description: "Show artwork editor" },
204
+ oneColorPerSmartObject: { type: "boolean", required: false, default: false, description: "Restrict to one color per smart object" },
205
+ enableColorOptions: { type: "boolean", required: false, default: true, description: "Display color options" },
206
+ enableCreatePrintFiles: { type: "boolean", required: false, default: false, description: "Enable print file export" },
207
+ enableCollectionExport: { type: "boolean", required: false, default: false, description: "Export all mockups in collection at once" },
208
+ exportMockupsButtonText: { type: "string", required: false, default: "Export Mockups", description: "Custom export button text" },
209
+ designUrl: { type: "string", required: false, description: "Pre-load design URL (disables user upload)" },
210
+ customFields: { type: "object", required: false, description: "Custom data to receive back in callback" },
211
+ mockupExportOptions: {
212
+ image_format: { type: "string", default: "webp", options: ["webp", "jpg", "png"] },
213
+ image_size: { type: "number", default: 1080, description: "Output width in pixels" },
214
+ mode: { type: "string", default: "download", options: ["download", "view"] },
215
+ },
216
+ colorPresets: {
217
+ type: "array",
218
+ required: false,
219
+ description: "Custom color presets for the color picker",
220
+ structure: {
221
+ name: "string (optional) - preset name",
222
+ autoApplyColors: "boolean (optional) - auto-apply colors when selected",
223
+ colors: "array (required) - array of { hex: string, name?: string }",
224
+ },
225
+ example: `[
226
+ {
227
+ name: "Brand Colors",
228
+ autoApplyColors: true,
229
+ colors: [
230
+ { hex: "#FF5733", name: "Primary" },
231
+ { hex: "#33FF57", name: "Secondary" }
232
+ ]
233
+ }
234
+ ]`,
235
+ },
236
+ },
237
+
238
+ integration_steps: {
239
+ classic_cdn: {
240
+ description: "Classic Editor with CDN (simplest setup)",
241
+ steps: [
242
+ "1. Add iframe element with id='dm-iframe' and src='https://embed.dynamicmockups.com'",
243
+ "2. Add SDK script tag from CDN: https://cdn.jsdelivr.net/npm/@dynamic-mockups/mockup-editor-sdk@latest/dist/index.js",
244
+ "3. Call DynamicMockups.initDynamicMockupsIframe() after DOMContentLoaded",
245
+ "4. Pass your x-website-key in the data object",
246
+ ],
247
+ },
248
+ classic_npm: {
249
+ description: "Classic Editor with NPM (for React, Vue, etc.)",
250
+ steps: [
251
+ "1. Install package: npm install @dynamic-mockups/mockup-editor-sdk@latest",
252
+ "2. Add iframe element with id='dm-iframe' and src='https://embed.dynamicmockups.com'",
253
+ "3. Import and call initDynamicMockupsIframe() after component mounts",
254
+ "4. Pass your x-website-key in the data object",
255
+ ],
256
+ },
257
+ mockanything_static: {
258
+ description: "MockAnything Editor with static iframe (same as Classic but with editorType)",
259
+ steps: [
260
+ "1. Add iframe element with id='dm-iframe' and src='https://embed.dynamicmockups.com'",
261
+ "2. Add SDK script or install NPM package",
262
+ "3. Call initDynamicMockupsIframe() with editorType: 'mockanything' in data object",
263
+ ],
264
+ example: `DynamicMockups.initDynamicMockupsIframe({
265
+ iframeId: "dm-iframe",
266
+ data: {
267
+ "x-website-key": "YOUR_WEBSITE_KEY",
268
+ editorType: "mockanything",
269
+ themeAppearance: "dark"
270
+ },
271
+ mode: "download"
272
+ });`,
273
+ },
274
+ mockanything_api: {
275
+ description: "MockAnything Editor with API initialization (dynamic, supports prompts)",
276
+ steps: [
277
+ "1. Install SDK: npm install @dynamic-mockups/mockup-editor-sdk@latest",
278
+ "2. IMPORTANT: Expose initDynamicMockupsIframe globally: window.initDynamicMockupsIframe = initDynamicMockupsIframe",
279
+ "3. Call POST /api/v1/mock-anything/embed/initialize with prompt/image_url/artwork_url",
280
+ "4. Inject the returned iframe_editor HTML into your DOM",
281
+ "5. Execute the returned init_function (use eval() or new Function())",
282
+ "6. Set up window message event listener using the returned event_listener_name",
283
+ ],
284
+ critical_note: "You MUST expose initDynamicMockupsIframe globally (step 2) before executing init_function, otherwise the editor won't initialize.",
285
+ },
286
+ },
287
+
288
+ callback_response: {
289
+ description: "When mode='custom', the callback receives this data on export",
290
+ fields: {
291
+ "mockupsExport[].export_label": "Export identifier/label",
292
+ "mockupsExport[].export_path": "URL to the rendered mockup image",
293
+ "customFields": "Your custom fields echoed back",
294
+ },
295
+ example: `initDynamicMockupsIframe({
296
+ iframeId: "dm-iframe",
297
+ data: {
298
+ "x-website-key": "YOUR_KEY",
299
+ customFields: { userId: "123", productId: "456" }
300
+ },
301
+ mode: "custom",
302
+ callback: (callbackData) => {
303
+ console.log(callbackData.mockupsExport[0].export_path); // Image URL
304
+ console.log(callbackData.customFields); // { userId: "123", productId: "456" }
305
+ },
306
+ });`,
307
+ },
308
+
309
+ mockanything_api_integration: {
310
+ description: "Initialize MockAnything editor dynamically via API (for React, Vue, etc.)",
311
+ endpoint: "POST https://app.dynamicmockups.com/api/v1/mock-anything/embed/initialize",
312
+ headers: { "Content-Type": "application/json", "x-api-key": "YOUR_API_KEY" },
313
+ request_body: {
314
+ prompt: "Optional. AI prompt to generate initial mockup (e.g., 'A guy wearing a Gildan 5000 in Belgrade')",
315
+ image_url: "Optional. URL to product image to use as base",
316
+ artwork_url: "Optional. URL to artwork/logo to pre-load",
317
+ },
318
+ response: {
319
+ iframe_editor: "Complete iframe HTML to inject into DOM",
320
+ init_function: "JavaScript code to initialize the editor",
321
+ event_listener_name: "Unique event name for this editor instance",
322
+ },
323
+ usage: `// 1. Call API
324
+ const response = await fetch("https://app.dynamicmockups.com/api/v1/mock-anything/embed/initialize", {
325
+ method: "POST",
326
+ headers: { "Content-Type": "application/json", "x-api-key": "YOUR_API_KEY" },
327
+ body: JSON.stringify({
328
+ prompt: "A guy wearing a Gildan 5000 t-shirt",
329
+ artwork_url: "https://example.com/logo.png"
330
+ })
331
+ });
332
+ const { data } = await response.json();
333
+
334
+ // 2. Inject iframe
335
+ document.getElementById("editor-container").innerHTML = data.iframe_editor;
336
+
337
+ // 3. Initialize editor
338
+ eval(data.init_function);
339
+
340
+ // 4. Listen for events
341
+ window.addEventListener("message", (event) => {
342
+ if (event.data.eventListenerName === data.event_listener_name) {
343
+ console.log("Editor event:", event.data.data);
344
+ }
345
+ });`,
346
+ },
347
+
348
+ mockanything_events: {
349
+ description: "Events emitted by MockAnything editor via postMessage",
350
+ events: ["editor_ready", "export_completed", "photoshoot_completed", "artwork_updated", "variant_changed"],
351
+ listening: `window.addEventListener("message", (event) => {
352
+ if (event.data.eventListenerName === "YOUR_EVENT_LISTENER_NAME") {
353
+ const payload = event.data.data;
354
+ console.log("Event received:", payload);
355
+ }
356
+ });`,
357
+ },
358
+
359
+ react_example: `import { useState, useEffect } from "react";
360
+ import { initDynamicMockupsIframe } from "@dynamic-mockups/mockup-editor-sdk";
361
+
362
+ // Expose globally for iframe communication
363
+ window.initDynamicMockupsIframe = initDynamicMockupsIframe;
364
+
365
+ export default function MockAnythingEditor() {
366
+ const [iframeData, setIframeData] = useState({ event_listener_name: "", iframe_editor: "", init_function: "" });
367
+
368
+ const launchEditor = async () => {
369
+ const response = await fetch("https://app.dynamicmockups.com/api/v1/mock-anything/embed/initialize", {
370
+ method: "POST",
371
+ headers: { "Content-Type": "application/json", "x-api-key": "YOUR_API_KEY" },
372
+ body: JSON.stringify({ prompt: "A person wearing a t-shirt", artwork_url: "https://example.com/logo.png" })
373
+ });
374
+ const { data } = await response.json();
375
+ setIframeData(data);
376
+ setTimeout(() => eval(data.init_function), 100);
377
+ };
378
+
379
+ useEffect(() => {
380
+ const handleMessage = (event) => {
381
+ if (event.data?.eventListenerName === iframeData.event_listener_name) {
382
+ console.log("Editor event:", event.data.data);
383
+ }
384
+ };
385
+ window.addEventListener("message", handleMessage);
386
+ return () => window.removeEventListener("message", handleMessage);
387
+ }, [iframeData.event_listener_name]);
388
+
389
+ return (
390
+ <div>
391
+ <button onClick={launchEditor}>Load MockAnything Editor</button>
392
+ <div dangerouslySetInnerHTML={{ __html: iframeData.iframe_editor }} />
393
+ </div>
394
+ );
395
+ }`,
396
+
397
+ docs_url: "https://docs.dynamicmockups.com/mockup-editor-sdk",
398
+ };
399
+
113
400
  // =============================================================================
114
401
  // Server Initialization
115
402
  // =============================================================================
@@ -123,20 +410,72 @@ const server = new Server(
123
410
  // HTTP Client
124
411
  // =============================================================================
125
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
+
126
463
  /**
127
464
  * Creates an API client with the provided API key.
128
465
  * For stdio transport: uses environment variable
129
466
  * For HTTP transport: uses client-provided API key from Authorization header
130
467
  *
131
468
  * @param {string} apiKey - The API key to use for requests
469
+ * @param {string} toolName - The name of the MCP tool (for tracking)
132
470
  */
133
- function createApiClient(apiKey) {
471
+ function createApiClient(apiKey, toolName) {
134
472
  return axios.create({
135
473
  baseURL: API_BASE_URL,
136
474
  headers: {
137
475
  "Accept": "application/json",
138
476
  "Content-Type": "application/json",
139
477
  "x-api-key": apiKey || "",
478
+ ...getMcpTrackingHeaders(toolName),
140
479
  },
141
480
  timeout: 60000, // 60 second timeout for render operations
142
481
  validateStatus: (status) => status < 500, // Only throw on 5xx errors
@@ -203,6 +542,7 @@ function getApiKey(extra) {
203
542
  //
204
543
  // WHEN TO USE EACH TOOL:
205
544
  // - get_api_info: First call when user asks about limits, pricing, or capabilities
545
+ // - embed_mockup_editor: When user wants to embed the mockup editor in their website/app
206
546
  // - get_catalogs: When user wants to see their workspace organization
207
547
  // - get_collections: When user wants to browse mockup groups or find mockups by category
208
548
  // - get_mockups: PRIMARY tool - lists templates WITH smart_object UUIDs ready for rendering
@@ -251,6 +591,49 @@ This tool does NOT require an API call - returns cached knowledge instantly.`,
251
591
  },
252
592
  },
253
593
  },
594
+ {
595
+ name: "embed_mockup_editor",
596
+ description: `Get comprehensive knowledge for embedding the Dynamic Mockups Editor into websites and apps.
597
+
598
+ WHEN TO USE: Call this when user asks about:
599
+ - Embedding a mockup editor in their website/app
600
+ - Adding product customization/personalization features
601
+ - Integrating the Classic Editor or MockAnything (AI) Editor
602
+ - iFrame integration for mockup editing
603
+ - Handling editor events and callbacks
604
+ - React/Vue/JavaScript integration examples
605
+
606
+ TWO EDITOR TYPES:
607
+ 1. Classic Editor: Template-based mockups from your catalog
608
+ 2. MockAnything Editor: AI-powered - turn any image into a mockup
609
+
610
+ INTEGRATION APPROACHES:
611
+ - CDN: Quick setup with script tag from jsdelivr
612
+ - NPM: @dynamic-mockups/mockup-editor-sdk package for frameworks
613
+ - API: Dynamic initialization via /mock-anything/embed/initialize endpoint
614
+
615
+ TOPICS AVAILABLE:
616
+ - quick_start: Basic iframe + SDK setup (CDN method)
617
+ - npm_integration: NPM package installation and usage
618
+ - data_options: All configuration options for customizing editor behavior
619
+ - callback_response: Handling export events when mode="custom"
620
+ - mockanything_api: Dynamic editor initialization via API
621
+ - mockanything_events: Event system for MockAnything editor
622
+ - react_example: Complete React component example
623
+ - all: Complete knowledge base
624
+
625
+ This tool does NOT require an API call - returns cached knowledge instantly.`,
626
+ inputSchema: {
627
+ type: "object",
628
+ properties: {
629
+ topic: {
630
+ type: "string",
631
+ enum: ["all", "quick_start", "npm_integration", "data_options", "callback_response", "mockanything_api", "mockanything_events", "react_example", "editor_types", "specific_mockup", "integration_steps"],
632
+ description: "Specific topic to retrieve. Use 'quick_start' for basic setup, 'integration_steps' for step-by-step guides, 'data_options' for configuration, 'mockanything_api' for AI editor API integration, 'react_example' for React code. Use 'all' for complete knowledge base.",
633
+ },
634
+ },
635
+ },
636
+ },
254
637
 
255
638
  // ─────────────────────────────────────────────────────────────────────────────
256
639
  // CATALOG & ORGANIZATION TOOLS
@@ -851,7 +1234,7 @@ RETURNS: {uuid, name} of the uploaded PSD file.`,
851
1234
 
852
1235
  API: POST /psd/delete
853
1236
 
854
- WHEN TO USE: When user wants to:
1237
+ WHEN TO USE: When user wants to:
855
1238
  - Remove an uploaded PSD file
856
1239
  - Clean up unused PSD files
857
1240
  - Optionally remove all mockups derived from the PSD
@@ -896,13 +1279,68 @@ async function handleGetApiInfo(args) {
896
1279
  return ResponseFormatter.ok(topicMap[topic] || API_KNOWLEDGE_BASE);
897
1280
  }
898
1281
 
1282
+ async function handleGetEmbedEditorInfo(args) {
1283
+ const topic = args?.topic || "all";
1284
+
1285
+ const topicMap = {
1286
+ quick_start: {
1287
+ overview: EMBED_EDITOR_KNOWLEDGE_BASE.overview,
1288
+ quick_start: EMBED_EDITOR_KNOWLEDGE_BASE.quick_start,
1289
+ init_function_params: EMBED_EDITOR_KNOWLEDGE_BASE.init_function_params,
1290
+ integration_steps: EMBED_EDITOR_KNOWLEDGE_BASE.integration_steps,
1291
+ },
1292
+ npm_integration: {
1293
+ npm_integration: EMBED_EDITOR_KNOWLEDGE_BASE.npm_integration,
1294
+ init_function_params: EMBED_EDITOR_KNOWLEDGE_BASE.init_function_params,
1295
+ integration_steps: {
1296
+ classic_npm: EMBED_EDITOR_KNOWLEDGE_BASE.integration_steps.classic_npm,
1297
+ },
1298
+ },
1299
+ data_options: {
1300
+ data_options: EMBED_EDITOR_KNOWLEDGE_BASE.data_options,
1301
+ },
1302
+ callback_response: {
1303
+ callback_response: EMBED_EDITOR_KNOWLEDGE_BASE.callback_response,
1304
+ },
1305
+ mockanything_api: {
1306
+ mockanything_api_integration: EMBED_EDITOR_KNOWLEDGE_BASE.mockanything_api_integration,
1307
+ mockanything_events: EMBED_EDITOR_KNOWLEDGE_BASE.mockanything_events,
1308
+ integration_steps: {
1309
+ mockanything_static: EMBED_EDITOR_KNOWLEDGE_BASE.integration_steps.mockanything_static,
1310
+ mockanything_api: EMBED_EDITOR_KNOWLEDGE_BASE.integration_steps.mockanything_api,
1311
+ },
1312
+ },
1313
+ mockanything_events: {
1314
+ mockanything_events: EMBED_EDITOR_KNOWLEDGE_BASE.mockanything_events,
1315
+ },
1316
+ react_example: {
1317
+ react_example: EMBED_EDITOR_KNOWLEDGE_BASE.react_example,
1318
+ integration_steps: {
1319
+ mockanything_api: EMBED_EDITOR_KNOWLEDGE_BASE.integration_steps.mockanything_api,
1320
+ },
1321
+ },
1322
+ editor_types: {
1323
+ editor_types: EMBED_EDITOR_KNOWLEDGE_BASE.editor_types,
1324
+ },
1325
+ specific_mockup: {
1326
+ specific_mockup: EMBED_EDITOR_KNOWLEDGE_BASE.specific_mockup,
1327
+ },
1328
+ integration_steps: {
1329
+ integration_steps: EMBED_EDITOR_KNOWLEDGE_BASE.integration_steps,
1330
+ },
1331
+ all: EMBED_EDITOR_KNOWLEDGE_BASE,
1332
+ };
1333
+
1334
+ return ResponseFormatter.ok(topicMap[topic] || EMBED_EDITOR_KNOWLEDGE_BASE);
1335
+ }
1336
+
899
1337
  async function handleGetCatalogs(args, extra) {
900
1338
  const apiKey = getApiKey(extra);
901
1339
  const error = validateApiKey(apiKey);
902
1340
  if (error) return error;
903
1341
 
904
1342
  try {
905
- const response = await createApiClient(apiKey).get("/catalogs");
1343
+ const response = await createApiClient(apiKey, "get_catalogs").get("/catalogs");
906
1344
  return ResponseFormatter.fromApiResponse(response);
907
1345
  } catch (err) {
908
1346
  return ResponseFormatter.fromError(err, "Failed to get catalogs");
@@ -921,7 +1359,7 @@ async function handleGetCollections(args = {}, extra) {
921
1359
  params.append("include_all_catalogs", args.include_all_catalogs);
922
1360
  }
923
1361
 
924
- const response = await createApiClient(apiKey).get(`/collections?${params}`);
1362
+ const response = await createApiClient(apiKey, "get_collections").get(`/collections?${params}`);
925
1363
  return ResponseFormatter.fromApiResponse(response);
926
1364
  } catch (err) {
927
1365
  return ResponseFormatter.fromError(err, "Failed to get collections");
@@ -937,7 +1375,7 @@ async function handleCreateCollection(args, extra) {
937
1375
  const payload = { name: args.name };
938
1376
  if (args.catalog_uuid) payload.catalog_uuid = args.catalog_uuid;
939
1377
 
940
- const response = await createApiClient(apiKey).post("/collections", payload);
1378
+ const response = await createApiClient(apiKey, "create_collection").post("/collections", payload);
941
1379
  return ResponseFormatter.fromApiResponse(response, `Collection "${args.name}" created`);
942
1380
  } catch (err) {
943
1381
  return ResponseFormatter.fromError(err, "Failed to create collection");
@@ -958,7 +1396,7 @@ async function handleGetMockups(args = {}, extra) {
958
1396
  }
959
1397
  if (args.name) params.append("name", args.name);
960
1398
 
961
- const response = await createApiClient(apiKey).get(`/mockups?${params}`);
1399
+ const response = await createApiClient(apiKey, "get_mockups").get(`/mockups?${params}`);
962
1400
  return ResponseFormatter.fromApiResponse(response);
963
1401
  } catch (err) {
964
1402
  return ResponseFormatter.fromError(err, "Failed to get mockups");
@@ -971,7 +1409,7 @@ async function handleGetMockupByUuid(args, extra) {
971
1409
  if (error) return error;
972
1410
 
973
1411
  try {
974
- const response = await createApiClient(apiKey).get(`/mockup/${args.uuid}`);
1412
+ const response = await createApiClient(apiKey, "get_mockup_by_uuid").get(`/mockup/${args.uuid}`);
975
1413
  return ResponseFormatter.fromApiResponse(response);
976
1414
  } catch (err) {
977
1415
  return ResponseFormatter.fromError(err, "Failed to get mockup");
@@ -992,7 +1430,7 @@ async function handleCreateRender(args, extra) {
992
1430
  if (args.export_options) payload.export_options = args.export_options;
993
1431
  if (args.text_layers) payload.text_layers = args.text_layers;
994
1432
 
995
- const response = await createApiClient(apiKey).post("/renders", payload);
1433
+ const response = await createApiClient(apiKey, "create_render").post("/renders", payload);
996
1434
  return ResponseFormatter.fromApiResponse(response, "Render created (1 credit used)");
997
1435
  } catch (err) {
998
1436
  return ResponseFormatter.fromError(err, "Failed to create render");
@@ -1008,7 +1446,7 @@ async function handleCreateBatchRender(args, extra) {
1008
1446
  const payload = { renders: args.renders };
1009
1447
  if (args.export_options) payload.export_options = args.export_options;
1010
1448
 
1011
- const response = await createApiClient(apiKey).post("/renders/batch", payload);
1449
+ const response = await createApiClient(apiKey, "create_batch_render").post("/renders/batch", payload);
1012
1450
  const count = args.renders?.length || 0;
1013
1451
  return ResponseFormatter.fromApiResponse(response, `Batch render complete (${count} credits used)`);
1014
1452
  } catch (err) {
@@ -1030,7 +1468,7 @@ async function handleExportPrintFiles(args, extra) {
1030
1468
  if (args.export_options) payload.export_options = args.export_options;
1031
1469
  if (args.text_layers) payload.text_layers = args.text_layers;
1032
1470
 
1033
- const response = await createApiClient(apiKey).post("/renders/print-files", payload);
1471
+ const response = await createApiClient(apiKey, "export_print_files").post("/renders/print-files", payload);
1034
1472
  return ResponseFormatter.fromApiResponse(response, "Print files exported");
1035
1473
  } catch (err) {
1036
1474
  return ResponseFormatter.fromError(err, "Failed to export print files");
@@ -1048,7 +1486,7 @@ async function handleUploadPsd(args, extra) {
1048
1486
  if (args.psd_category_id) payload.psd_category_id = args.psd_category_id;
1049
1487
  if (args.mockup_template) payload.mockup_template = args.mockup_template;
1050
1488
 
1051
- const response = await createApiClient(apiKey).post("/psd/upload", payload);
1489
+ const response = await createApiClient(apiKey, "upload_psd").post("/psd/upload", payload);
1052
1490
  return ResponseFormatter.fromApiResponse(response, "PSD uploaded successfully");
1053
1491
  } catch (err) {
1054
1492
  return ResponseFormatter.fromError(err, "Failed to upload PSD");
@@ -1066,7 +1504,7 @@ async function handleDeletePsd(args, extra) {
1066
1504
  payload.delete_related_mockups = args.delete_related_mockups;
1067
1505
  }
1068
1506
 
1069
- const response = await createApiClient(apiKey).post("/psd/delete", payload);
1507
+ const response = await createApiClient(apiKey, "delete_psd").post("/psd/delete", payload);
1070
1508
  return ResponseFormatter.fromApiResponse(response, "PSD deleted successfully");
1071
1509
  } catch (err) {
1072
1510
  return ResponseFormatter.fromError(err, "Failed to delete PSD");
@@ -1079,6 +1517,7 @@ async function handleDeletePsd(args, extra) {
1079
1517
 
1080
1518
  const toolHandlers = {
1081
1519
  get_api_info: handleGetApiInfo,
1520
+ embed_mockup_editor: handleGetEmbedEditorInfo,
1082
1521
  get_catalogs: handleGetCatalogs,
1083
1522
  get_collections: handleGetCollections,
1084
1523
  create_collection: handleCreateCollection,
@@ -1099,17 +1538,29 @@ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
1099
1538
 
1100
1539
  server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
1101
1540
  const { name, arguments: args } = request.params;
1541
+ const apiKey = getApiKey(extra);
1102
1542
 
1103
1543
  const handler = toolHandlers[name];
1104
1544
  if (!handler) {
1105
- 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;
1106
1548
  }
1107
1549
 
1108
1550
  try {
1109
1551
  // Pass extra context (contains requestInfo with headers for HTTP transport)
1110
- 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;
1111
1560
  } catch (err) {
1112
- 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;
1113
1564
  }
1114
1565
  });
1115
1566
 
@@ -1122,6 +1573,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
1122
1573
  * Used by: Claude Desktop, Claude Code, Cursor, Windsurf
1123
1574
  */
1124
1575
  async function startStdioServer() {
1576
+ currentTransportMode = "stdio";
1125
1577
  const transport = new StdioServerTransport();
1126
1578
  await server.connect(transport);
1127
1579
  console.error(`Dynamic Mockups MCP Server v${SERVER_VERSION} running (stdio)`);
@@ -1141,6 +1593,8 @@ async function startStdioServer() {
1141
1593
  * @returns {Promise<{app: Express, httpServer: Server}>}
1142
1594
  */
1143
1595
  async function startHttpServer(options = {}) {
1596
+ currentTransportMode = "http";
1597
+
1144
1598
  const {
1145
1599
  port = process.env.PORT || 3000,
1146
1600
  host = process.env.HOST || "0.0.0.0",
@@ -1222,15 +1676,29 @@ async function startHttpServer(options = {}) {
1222
1676
  connectionServer.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
1223
1677
  connectionServer.setRequestHandler(CallToolRequestSchema, async (request, extra) => {
1224
1678
  const { name, arguments: args } = request.params;
1679
+ const apiKey = getApiKey(extra);
1680
+
1225
1681
  const handler = toolHandlers[name];
1226
1682
  if (!handler) {
1227
- 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;
1228
1686
  }
1687
+
1229
1688
  try {
1230
1689
  // Pass extra context (contains requestInfo with headers for API key extraction)
1231
- 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;
1232
1698
  } catch (err) {
1233
- 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;
1234
1702
  }
1235
1703
  });
1236
1704