@dynamic-mockups/mcp 1.0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Dynamic Mockups
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,204 @@
1
+ # Dynamic Mockups MCP Server
2
+
3
+ Official MCP (Model Context Protocol) server for the [Dynamic Mockups API](https://dynamicmockups.com). Generate product mockups directly from AI assistants like Claude, Cursor, Windsurf, and more.
4
+
5
+ ## Installation
6
+
7
+ ### Quick Start with npx
8
+
9
+ No installation required - just configure your MCP client:
10
+
11
+ ```json
12
+ {
13
+ "mcpServers": {
14
+ "dynamic-mockups": {
15
+ "command": "npx",
16
+ "args": ["-y", "@dynamic-mockups/mcp"],
17
+ "env": {
18
+ "DYNAMIC_MOCKUPS_API_KEY": "your_api_key_here"
19
+ }
20
+ }
21
+ }
22
+ }
23
+ ```
24
+
25
+ ### Get Your API Key
26
+
27
+ 1. Go to [Dynamic Mockups Dashboard](https://app.dynamicmockups.com/account/api-keys)
28
+ 2. Create a new API key
29
+ 3. Add it to your MCP client configuration
30
+
31
+ ## Configuration by Client
32
+
33
+ ### Claude Desktop
34
+
35
+ Edit `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `%APPDATA%\Claude\claude_desktop_config.json` (Windows):
36
+
37
+ ```json
38
+ {
39
+ "mcpServers": {
40
+ "dynamic-mockups": {
41
+ "command": "npx",
42
+ "args": ["-y", "@dynamic-mockups/mcp"],
43
+ "env": {
44
+ "DYNAMIC_MOCKUPS_API_KEY": "your_api_key_here"
45
+ }
46
+ }
47
+ }
48
+ }
49
+ ```
50
+
51
+ ### Claude Code (CLI)
52
+
53
+ Add to `.mcp.json` in your project root:
54
+
55
+ ```json
56
+ {
57
+ "mcpServers": {
58
+ "dynamic-mockups": {
59
+ "command": "npx",
60
+ "args": ["-y", "@dynamic-mockups/mcp"],
61
+ "env": {
62
+ "DYNAMIC_MOCKUPS_API_KEY": "your_api_key_here"
63
+ }
64
+ }
65
+ }
66
+ }
67
+ ```
68
+
69
+ ### Cursor
70
+
71
+ Add to `.cursor/mcp.json` in your project:
72
+
73
+ ```json
74
+ {
75
+ "mcpServers": {
76
+ "dynamic-mockups": {
77
+ "command": "npx",
78
+ "args": ["-y", "@dynamic-mockups/mcp"],
79
+ "env": {
80
+ "DYNAMIC_MOCKUPS_API_KEY": "your_api_key_here"
81
+ }
82
+ }
83
+ }
84
+ }
85
+ ```
86
+
87
+ ### Windsurf
88
+
89
+ Add to `~/.codeium/windsurf/mcp_config.json`:
90
+
91
+ ```json
92
+ {
93
+ "mcpServers": {
94
+ "dynamic-mockups": {
95
+ "command": "npx",
96
+ "args": ["-y", "@dynamic-mockups/mcp"],
97
+ "env": {
98
+ "DYNAMIC_MOCKUPS_API_KEY": "your_api_key_here"
99
+ }
100
+ }
101
+ }
102
+ }
103
+ ```
104
+
105
+ ## Available Tools
106
+
107
+ ### API Information
108
+ - **`get_api_info`** - Get API knowledge base (billing, rate limits, formats, best practices, support)
109
+
110
+ ### Catalogs
111
+ - **`get_catalogs`** - Retrieve all available catalogs
112
+
113
+ ### Collections
114
+ - **`get_collections`** - Retrieve collections (optionally filter by catalog)
115
+ - **`create_collection`** - Create a new collection
116
+
117
+ ### Mockups
118
+ - **`get_mockups`** - Get list of available mockups with optional filters
119
+ - **`get_mockup_by_uuid`** - Retrieve a specific mockup by UUID
120
+
121
+ ### Rendering
122
+ - **`create_render`** - Create a single mockup render with design assets
123
+ - **`create_batch_render`** - Render multiple mockups in one request
124
+ - **`export_print_files`** - Export print files for smart objects
125
+
126
+ ### PSD Files
127
+ - **`upload_psd`** - Upload a PSD file with optional mockup template creation
128
+ - **`delete_psd`** - Delete a PSD file with optional related mockups deletion
129
+
130
+ ## Usage Examples
131
+
132
+ ### Get Your Catalogs
133
+
134
+ Ask your AI assistant:
135
+ > "Get my Dynamic Mockups catalogs"
136
+
137
+ ### Get Mockups from a Collection
138
+
139
+ > "Show me all mockups in my T-shirt collection"
140
+
141
+ ### Create a Render
142
+
143
+ > "Create a mockup render using mockup UUID abc123 with my logo from https://example.com/logo.png"
144
+
145
+ ### Batch Render
146
+
147
+ > "Render my design on all mockups in the Summer collection"
148
+
149
+ ## Development
150
+
151
+ ### Local Installation
152
+
153
+ ```bash
154
+ git clone https://github.com/dynamicmockups/mcp-server.git
155
+ cd mcp-server
156
+ npm install
157
+ ```
158
+
159
+ ### Run Locally
160
+
161
+ ```bash
162
+ DYNAMIC_MOCKUPS_API_KEY=your_key npm start
163
+ ```
164
+
165
+ ### Development Mode (with auto-reload)
166
+
167
+ ```bash
168
+ DYNAMIC_MOCKUPS_API_KEY=your_key npm run dev
169
+ ```
170
+
171
+ ### Use Local Version in MCP Client
172
+
173
+ ```json
174
+ {
175
+ "mcpServers": {
176
+ "dynamic-mockups": {
177
+ "command": "node",
178
+ "args": ["/path/to/mcp-server/src/index.js"],
179
+ "env": {
180
+ "DYNAMIC_MOCKUPS_API_KEY": "your_api_key_here"
181
+ }
182
+ }
183
+ }
184
+ }
185
+ ```
186
+
187
+ ## Error Handling
188
+
189
+ The server returns clear error messages for common issues:
190
+
191
+ - **API key not configured** - Set `DYNAMIC_MOCKUPS_API_KEY` in your environment
192
+ - **Invalid UUID** - Ensure UUIDs are in correct format
193
+ - **API errors** - Check the returned message for details
194
+
195
+ ## Links
196
+
197
+ - [Dynamic Mockups Website](https://dynamicmockups.com)
198
+ - [API Documentation](https://docs.dynamicmockups.com)
199
+ - [Get API Key](https://app.dynamicmockups.com/account/api-keys)
200
+ - [GitHub Issues](https://github.com/dynamicmockups/mcp-server/issues)
201
+
202
+ ## License
203
+
204
+ MIT
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@dynamic-mockups/mcp",
3
+ "version": "1.0.0",
4
+ "description": "Official Dynamic Mockups MCP Server - Generate product mockups with AI assistants",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "dynamic-mockups-mcp": "src/index.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node src/index.js",
12
+ "dev": "node --watch src/index.js"
13
+ },
14
+ "keywords": [
15
+ "mcp",
16
+ "model-context-protocol",
17
+ "dynamic-mockups",
18
+ "mockups",
19
+ "ai",
20
+ "claude",
21
+ "anthropic",
22
+ "design",
23
+ "product-mockups"
24
+ ],
25
+ "author": "Dynamic Mockups <support@dynamicmockups.com>",
26
+ "license": "MIT",
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/dynamic-mockups/mcp.git"
30
+ },
31
+ "homepage": "https://dynamicmockups.com",
32
+ "bugs": {
33
+ "url": "https://github.com/dynamic-mockups/mcp/issues"
34
+ },
35
+ "dependencies": {
36
+ "@modelcontextprotocol/sdk": "^1.0.0",
37
+ "axios": "^1.6.0"
38
+ },
39
+ "engines": {
40
+ "node": ">=18.0.0"
41
+ },
42
+ "files": [
43
+ "src/**/*"
44
+ ]
45
+ }
package/src/index.js ADDED
@@ -0,0 +1,713 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Dynamic Mockups MCP Server
5
+ * Official MCP server for the Dynamic Mockups API
6
+ * https://dynamicmockups.com
7
+ */
8
+
9
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
10
+ import { CallToolRequestSchema, ListToolsRequestSchema } from "@modelcontextprotocol/sdk/types.js";
11
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
12
+ import axios from "axios";
13
+ import { ResponseFormatter } from "./response-formatter.js";
14
+
15
+ // =============================================================================
16
+ // Configuration
17
+ // =============================================================================
18
+
19
+ const API_BASE_URL = "https://app.dynamicmockups.com/api/v1";
20
+ const API_KEY = process.env.DYNAMIC_MOCKUPS_API_KEY;
21
+ const SERVER_NAME = "dynamic-mockups-mcp";
22
+ const SERVER_VERSION = "1.0.0";
23
+
24
+ // =============================================================================
25
+ // API Knowledge Base
26
+ // =============================================================================
27
+
28
+ const API_KNOWLEDGE_BASE = {
29
+ overview: "Dynamic Mockups API allows you to generate product mockups programmatically.",
30
+
31
+ billing: {
32
+ credits_per_image: 1,
33
+ free_credits: 50,
34
+ free_tier_watermark: true,
35
+ pro_subscription_removes_watermark: true,
36
+ },
37
+
38
+ rate_limits: {
39
+ requests_per_minute: 300,
40
+ },
41
+
42
+ rendered_images: {
43
+ availability_hours: 24,
44
+ note: "Rendered image links expire after 24 hours. Contact support to extend.",
45
+ },
46
+
47
+ supported_formats: {
48
+ input: ["jpg", "jpeg", "png", "webp", "gif"],
49
+ output: ["jpg", "png", "webp"],
50
+ },
51
+
52
+ asset_upload: {
53
+ methods: ["URL", "binary file (form-data)"],
54
+ note: "Binary files must be sent as multipart/form-data in render requests.",
55
+ },
56
+
57
+ best_practices: [
58
+ "Use create_batch_render for multiple images (more efficient than single renders)",
59
+ "Always include Accept: application/json header (handled automatically by this MCP)",
60
+ "Store rendered image URLs promptly as they expire in 24 hours",
61
+ ],
62
+
63
+ support: {
64
+ email: "support@dynamicmockups.com",
65
+ tutorials: "https://docs.dynamicmockups.com/knowledge-base/tutorials",
66
+ api_docs: "https://docs.dynamicmockups.com",
67
+ },
68
+ };
69
+
70
+ // =============================================================================
71
+ // Server Initialization
72
+ // =============================================================================
73
+
74
+ const server = new Server(
75
+ { name: SERVER_NAME, version: SERVER_VERSION },
76
+ { capabilities: { tools: {} } }
77
+ );
78
+
79
+ // =============================================================================
80
+ // HTTP Client
81
+ // =============================================================================
82
+
83
+ function createApiClient() {
84
+ return axios.create({
85
+ baseURL: API_BASE_URL,
86
+ headers: {
87
+ "Accept": "application/json",
88
+ "Content-Type": "application/json",
89
+ "x-api-key": API_KEY || "",
90
+ },
91
+ timeout: 60000, // 60 second timeout for render operations
92
+ validateStatus: (status) => status < 500, // Only throw on 5xx errors
93
+ });
94
+ }
95
+
96
+ function validateApiKey() {
97
+ if (!API_KEY) {
98
+ return ResponseFormatter.error(
99
+ "API key not configured",
100
+ {
101
+ solution: "Set the DYNAMIC_MOCKUPS_API_KEY environment variable in your MCP client configuration.",
102
+ get_key_at: "https://app.dynamicmockups.com/account/api-keys",
103
+ }
104
+ );
105
+ }
106
+ return null;
107
+ }
108
+
109
+ // =============================================================================
110
+ // Tool Definitions
111
+ // =============================================================================
112
+
113
+ const tools = [
114
+ // Knowledge Base Tool
115
+ {
116
+ name: "get_api_info",
117
+ description: "Get Dynamic Mockups API information including billing, rate limits, supported formats, and best practices. Use this to understand API capabilities and constraints.",
118
+ inputSchema: {
119
+ type: "object",
120
+ properties: {
121
+ topic: {
122
+ type: "string",
123
+ enum: ["all", "billing", "rate_limits", "formats", "best_practices", "support"],
124
+ description: "Specific topic to get info about, or 'all' for complete knowledge base",
125
+ },
126
+ },
127
+ },
128
+ },
129
+
130
+ // Catalog Tools
131
+ {
132
+ name: "get_catalogs",
133
+ description: "Retrieve all available catalogs. Catalogs are top-level containers for organizing collections and mockups.",
134
+ inputSchema: {
135
+ type: "object",
136
+ properties: {},
137
+ },
138
+ },
139
+
140
+ // Collection Tools
141
+ {
142
+ name: "get_collections",
143
+ description: "Retrieve collections, optionally filtered by catalog. Collections group related mockups together.",
144
+ inputSchema: {
145
+ type: "object",
146
+ properties: {
147
+ catalog_uuid: {
148
+ type: "string",
149
+ description: "Filter collections by catalog UUID",
150
+ },
151
+ include_all_catalogs: {
152
+ type: "boolean",
153
+ description: "Include collections from all catalogs (default: false, returns only default catalog)",
154
+ },
155
+ },
156
+ },
157
+ },
158
+ {
159
+ name: "create_collection",
160
+ description: "Create a new collection to organize mockups",
161
+ inputSchema: {
162
+ type: "object",
163
+ properties: {
164
+ name: {
165
+ type: "string",
166
+ description: "Name for the new collection",
167
+ },
168
+ catalog_uuid: {
169
+ type: "string",
170
+ description: "Catalog UUID to create collection in (uses default catalog if not specified)",
171
+ },
172
+ },
173
+ required: ["name"],
174
+ },
175
+ },
176
+
177
+ // Mockup Tools
178
+ {
179
+ name: "get_mockups",
180
+ description: "Retrieve mockups from My Templates with optional filtering. Returns mockup UUIDs needed for rendering.",
181
+ inputSchema: {
182
+ type: "object",
183
+ properties: {
184
+ catalog_uuid: {
185
+ type: "string",
186
+ description: "Filter by catalog UUID",
187
+ },
188
+ collection_uuid: {
189
+ type: "string",
190
+ description: "Filter by collection UUID",
191
+ },
192
+ include_all_catalogs: {
193
+ type: "boolean",
194
+ description: "Include mockups from all catalogs (default: false)",
195
+ },
196
+ name: {
197
+ type: "string",
198
+ description: "Filter mockups by name (partial match)",
199
+ },
200
+ },
201
+ },
202
+ },
203
+ {
204
+ name: "get_mockup_by_uuid",
205
+ description: "Get detailed information about a specific mockup including its smart objects and configuration",
206
+ inputSchema: {
207
+ type: "object",
208
+ properties: {
209
+ uuid: {
210
+ type: "string",
211
+ description: "The mockup UUID",
212
+ },
213
+ },
214
+ required: ["uuid"],
215
+ },
216
+ },
217
+
218
+ // Render Tools
219
+ {
220
+ name: "create_render",
221
+ description: "Render a single mockup with design assets. Costs 1 credit per render. For multiple renders, use create_batch_render instead.",
222
+ inputSchema: {
223
+ type: "object",
224
+ properties: {
225
+ mockup_uuid: {
226
+ type: "string",
227
+ description: "UUID of the mockup template to render",
228
+ },
229
+ smart_objects: {
230
+ type: "array",
231
+ description: "Smart objects configuration with design assets",
232
+ items: {
233
+ type: "object",
234
+ properties: {
235
+ uuid: {
236
+ type: "string",
237
+ description: "Smart object UUID (get from mockup details)",
238
+ },
239
+ asset: {
240
+ type: "object",
241
+ description: "Design asset configuration",
242
+ properties: {
243
+ url: {
244
+ type: "string",
245
+ description: "URL to the design image (jpg, jpeg, png, webp, gif)",
246
+ },
247
+ fit: {
248
+ type: "string",
249
+ enum: ["stretch", "contain", "cover"],
250
+ description: "How to fit the asset in the smart object area",
251
+ },
252
+ size: {
253
+ type: "object",
254
+ properties: {
255
+ width: { type: "integer" },
256
+ height: { type: "integer" },
257
+ },
258
+ },
259
+ position: {
260
+ type: "object",
261
+ properties: {
262
+ top: { type: "integer" },
263
+ left: { type: "integer" },
264
+ },
265
+ },
266
+ rotate: {
267
+ type: "number",
268
+ description: "Rotation angle in degrees",
269
+ },
270
+ },
271
+ },
272
+ color: {
273
+ type: "string",
274
+ description: "Color overlay in hex format (e.g., #FF0000)",
275
+ },
276
+ print_area_preset_uuid: {
277
+ type: "string",
278
+ description: "Print area preset UUID for automatic positioning",
279
+ },
280
+ },
281
+ },
282
+ },
283
+ export_label: {
284
+ type: "string",
285
+ description: "Label for the exported image (appears in filename)",
286
+ },
287
+ export_options: {
288
+ type: "object",
289
+ properties: {
290
+ image_format: {
291
+ type: "string",
292
+ enum: ["jpg", "png", "webp"],
293
+ description: "Output image format (default: jpg)",
294
+ },
295
+ image_size: {
296
+ type: "integer",
297
+ description: "Output image size in pixels (default: 1000)",
298
+ },
299
+ mode: {
300
+ type: "string",
301
+ enum: ["view", "download"],
302
+ description: "URL mode - 'view' for browser display, 'download' for attachment",
303
+ },
304
+ },
305
+ },
306
+ text_layers: {
307
+ type: "array",
308
+ description: "Text layer customizations",
309
+ items: {
310
+ type: "object",
311
+ properties: {
312
+ uuid: { type: "string", description: "Text layer UUID" },
313
+ text: { type: "string", description: "Text content" },
314
+ font_family: { type: "string" },
315
+ font_size: { type: "number" },
316
+ font_color: { type: "string", description: "Hex color code" },
317
+ },
318
+ },
319
+ },
320
+ },
321
+ required: ["mockup_uuid", "smart_objects"],
322
+ },
323
+ },
324
+ {
325
+ name: "create_batch_render",
326
+ description: "Render multiple mockups in a single request. RECOMMENDED for rendering more than one image - more efficient and faster than individual renders. Costs 1 credit per image.",
327
+ inputSchema: {
328
+ type: "object",
329
+ properties: {
330
+ renders: {
331
+ type: "array",
332
+ description: "Array of render configurations",
333
+ items: {
334
+ type: "object",
335
+ properties: {
336
+ mockup_uuid: {
337
+ type: "string",
338
+ description: "UUID of the mockup template",
339
+ },
340
+ smart_objects: {
341
+ type: "array",
342
+ description: "Smart objects configuration (same as create_render)",
343
+ },
344
+ text_layers: {
345
+ type: "array",
346
+ description: "Text layer customizations",
347
+ },
348
+ export_label: {
349
+ type: "string",
350
+ description: "Label for this specific render",
351
+ },
352
+ },
353
+ required: ["mockup_uuid", "smart_objects"],
354
+ },
355
+ },
356
+ export_options: {
357
+ type: "object",
358
+ description: "Export options applied to all renders in the batch",
359
+ properties: {
360
+ image_format: {
361
+ type: "string",
362
+ enum: ["jpg", "png", "webp"],
363
+ },
364
+ image_size: {
365
+ type: "integer",
366
+ },
367
+ mode: {
368
+ type: "string",
369
+ enum: ["view", "download"],
370
+ },
371
+ },
372
+ },
373
+ },
374
+ required: ["renders"],
375
+ },
376
+ },
377
+ {
378
+ name: "export_print_files",
379
+ description: "Export high-resolution print files for production. Supports custom DPI settings.",
380
+ inputSchema: {
381
+ type: "object",
382
+ properties: {
383
+ mockup_uuid: {
384
+ type: "string",
385
+ description: "UUID of the mockup template",
386
+ },
387
+ smart_objects: {
388
+ type: "array",
389
+ description: "Smart objects configuration",
390
+ },
391
+ text_layers: {
392
+ type: "array",
393
+ description: "Text layer customizations",
394
+ },
395
+ export_label: {
396
+ type: "string",
397
+ description: "Label for the export",
398
+ },
399
+ export_options: {
400
+ type: "object",
401
+ properties: {
402
+ image_format: { type: "string", enum: ["jpg", "png", "webp"] },
403
+ image_size: { type: "integer" },
404
+ image_dpi: { type: "integer", description: "DPI for print (e.g., 300)" },
405
+ mode: { type: "string", enum: ["view", "download"] },
406
+ },
407
+ },
408
+ },
409
+ required: ["mockup_uuid", "smart_objects"],
410
+ },
411
+ },
412
+
413
+ // PSD Management Tools
414
+ {
415
+ name: "upload_psd",
416
+ description: "Upload a PSD file to create custom mockup templates. The PSD should contain smart object layers.",
417
+ inputSchema: {
418
+ type: "object",
419
+ properties: {
420
+ psd_file_url: {
421
+ type: "string",
422
+ description: "Public URL to the PSD file",
423
+ },
424
+ psd_name: {
425
+ type: "string",
426
+ description: "Name for the uploaded PSD",
427
+ },
428
+ psd_category_id: {
429
+ type: "integer",
430
+ description: "Category ID for organization",
431
+ },
432
+ mockup_template: {
433
+ type: "object",
434
+ description: "Automatically create a mockup template from the PSD",
435
+ properties: {
436
+ create_after_upload: {
437
+ type: "boolean",
438
+ description: "Create mockup template after upload",
439
+ },
440
+ collections: {
441
+ type: "array",
442
+ items: { type: "string" },
443
+ description: "Collection UUIDs to add the mockup to",
444
+ },
445
+ catalog_uuid: {
446
+ type: "string",
447
+ description: "Catalog UUID for the mockup",
448
+ },
449
+ },
450
+ },
451
+ },
452
+ required: ["psd_file_url"],
453
+ },
454
+ },
455
+ {
456
+ name: "delete_psd",
457
+ description: "Delete a PSD file and optionally all mockups created from it",
458
+ inputSchema: {
459
+ type: "object",
460
+ properties: {
461
+ psd_uuid: {
462
+ type: "string",
463
+ description: "UUID of the PSD to delete",
464
+ },
465
+ delete_related_mockups: {
466
+ type: "boolean",
467
+ description: "Also delete all mockups created from this PSD (default: false)",
468
+ },
469
+ },
470
+ required: ["psd_uuid"],
471
+ },
472
+ },
473
+ ];
474
+
475
+ // =============================================================================
476
+ // Tool Handlers
477
+ // =============================================================================
478
+
479
+ async function handleGetApiInfo(args) {
480
+ const topic = args?.topic || "all";
481
+
482
+ const topicMap = {
483
+ billing: { billing: API_KNOWLEDGE_BASE.billing },
484
+ rate_limits: { rate_limits: API_KNOWLEDGE_BASE.rate_limits },
485
+ formats: { supported_formats: API_KNOWLEDGE_BASE.supported_formats, asset_upload: API_KNOWLEDGE_BASE.asset_upload },
486
+ best_practices: { best_practices: API_KNOWLEDGE_BASE.best_practices },
487
+ support: { support: API_KNOWLEDGE_BASE.support },
488
+ all: API_KNOWLEDGE_BASE,
489
+ };
490
+
491
+ return ResponseFormatter.ok(topicMap[topic] || API_KNOWLEDGE_BASE);
492
+ }
493
+
494
+ async function handleGetCatalogs() {
495
+ const error = validateApiKey();
496
+ if (error) return error;
497
+
498
+ try {
499
+ const response = await createApiClient().get("/catalogs");
500
+ return ResponseFormatter.fromApiResponse(response);
501
+ } catch (err) {
502
+ return ResponseFormatter.fromError(err, "Failed to get catalogs");
503
+ }
504
+ }
505
+
506
+ async function handleGetCollections(args = {}) {
507
+ const error = validateApiKey();
508
+ if (error) return error;
509
+
510
+ try {
511
+ const params = new URLSearchParams();
512
+ if (args.catalog_uuid) params.append("catalog_uuid", args.catalog_uuid);
513
+ if (args.include_all_catalogs !== undefined) {
514
+ params.append("include_all_catalogs", args.include_all_catalogs);
515
+ }
516
+
517
+ const response = await createApiClient().get(`/collections?${params}`);
518
+ return ResponseFormatter.fromApiResponse(response);
519
+ } catch (err) {
520
+ return ResponseFormatter.fromError(err, "Failed to get collections");
521
+ }
522
+ }
523
+
524
+ async function handleCreateCollection(args) {
525
+ const error = validateApiKey();
526
+ if (error) return error;
527
+
528
+ try {
529
+ const payload = { name: args.name };
530
+ if (args.catalog_uuid) payload.catalog_uuid = args.catalog_uuid;
531
+
532
+ const response = await createApiClient().post("/collections", payload);
533
+ return ResponseFormatter.fromApiResponse(response, `Collection "${args.name}" created`);
534
+ } catch (err) {
535
+ return ResponseFormatter.fromError(err, "Failed to create collection");
536
+ }
537
+ }
538
+
539
+ async function handleGetMockups(args = {}) {
540
+ const error = validateApiKey();
541
+ if (error) return error;
542
+
543
+ try {
544
+ const params = new URLSearchParams();
545
+ if (args.catalog_uuid) params.append("catalog_uuid", args.catalog_uuid);
546
+ if (args.collection_uuid) params.append("collection_uuid", args.collection_uuid);
547
+ if (args.include_all_catalogs !== undefined) {
548
+ params.append("include_all_catalogs", args.include_all_catalogs);
549
+ }
550
+ if (args.name) params.append("name", args.name);
551
+
552
+ const response = await createApiClient().get(`/mockups?${params}`);
553
+ return ResponseFormatter.fromApiResponse(response);
554
+ } catch (err) {
555
+ return ResponseFormatter.fromError(err, "Failed to get mockups");
556
+ }
557
+ }
558
+
559
+ async function handleGetMockupByUuid(args) {
560
+ const error = validateApiKey();
561
+ if (error) return error;
562
+
563
+ try {
564
+ const response = await createApiClient().get(`/mockup/${args.uuid}`);
565
+ return ResponseFormatter.fromApiResponse(response);
566
+ } catch (err) {
567
+ return ResponseFormatter.fromError(err, "Failed to get mockup");
568
+ }
569
+ }
570
+
571
+ async function handleCreateRender(args) {
572
+ const error = validateApiKey();
573
+ if (error) return error;
574
+
575
+ try {
576
+ const payload = {
577
+ mockup_uuid: args.mockup_uuid,
578
+ smart_objects: args.smart_objects,
579
+ };
580
+ if (args.export_label) payload.export_label = args.export_label;
581
+ if (args.export_options) payload.export_options = args.export_options;
582
+ if (args.text_layers) payload.text_layers = args.text_layers;
583
+
584
+ const response = await createApiClient().post("/renders", payload);
585
+ return ResponseFormatter.fromApiResponse(response, "Render created (1 credit used)");
586
+ } catch (err) {
587
+ return ResponseFormatter.fromError(err, "Failed to create render");
588
+ }
589
+ }
590
+
591
+ async function handleCreateBatchRender(args) {
592
+ const error = validateApiKey();
593
+ if (error) return error;
594
+
595
+ try {
596
+ const payload = { renders: args.renders };
597
+ if (args.export_options) payload.export_options = args.export_options;
598
+
599
+ const response = await createApiClient().post("/renders/batch", payload);
600
+ const count = args.renders?.length || 0;
601
+ return ResponseFormatter.fromApiResponse(response, `Batch render complete (${count} credits used)`);
602
+ } catch (err) {
603
+ return ResponseFormatter.fromError(err, "Failed to create batch render");
604
+ }
605
+ }
606
+
607
+ async function handleExportPrintFiles(args) {
608
+ const error = validateApiKey();
609
+ if (error) return error;
610
+
611
+ try {
612
+ const payload = {
613
+ mockup_uuid: args.mockup_uuid,
614
+ smart_objects: args.smart_objects,
615
+ };
616
+ if (args.export_label) payload.export_label = args.export_label;
617
+ if (args.export_options) payload.export_options = args.export_options;
618
+ if (args.text_layers) payload.text_layers = args.text_layers;
619
+
620
+ const response = await createApiClient().post("/renders/print-files", payload);
621
+ return ResponseFormatter.fromApiResponse(response, "Print files exported");
622
+ } catch (err) {
623
+ return ResponseFormatter.fromError(err, "Failed to export print files");
624
+ }
625
+ }
626
+
627
+ async function handleUploadPsd(args) {
628
+ const error = validateApiKey();
629
+ if (error) return error;
630
+
631
+ try {
632
+ const payload = { psd_file_url: args.psd_file_url };
633
+ if (args.psd_name) payload.psd_name = args.psd_name;
634
+ if (args.psd_category_id) payload.psd_category_id = args.psd_category_id;
635
+ if (args.mockup_template) payload.mockup_template = args.mockup_template;
636
+
637
+ const response = await createApiClient().post("/psd/upload", payload);
638
+ return ResponseFormatter.fromApiResponse(response, "PSD uploaded successfully");
639
+ } catch (err) {
640
+ return ResponseFormatter.fromError(err, "Failed to upload PSD");
641
+ }
642
+ }
643
+
644
+ async function handleDeletePsd(args) {
645
+ const error = validateApiKey();
646
+ if (error) return error;
647
+
648
+ try {
649
+ const payload = { psd_uuid: args.psd_uuid };
650
+ if (args.delete_related_mockups !== undefined) {
651
+ payload.delete_related_mockups = args.delete_related_mockups;
652
+ }
653
+
654
+ const response = await createApiClient().post("/psd/delete", payload);
655
+ return ResponseFormatter.fromApiResponse(response, "PSD deleted successfully");
656
+ } catch (err) {
657
+ return ResponseFormatter.fromError(err, "Failed to delete PSD");
658
+ }
659
+ }
660
+
661
+ // =============================================================================
662
+ // Tool Router
663
+ // =============================================================================
664
+
665
+ const toolHandlers = {
666
+ get_api_info: handleGetApiInfo,
667
+ get_catalogs: handleGetCatalogs,
668
+ get_collections: handleGetCollections,
669
+ create_collection: handleCreateCollection,
670
+ get_mockups: handleGetMockups,
671
+ get_mockup_by_uuid: handleGetMockupByUuid,
672
+ create_render: handleCreateRender,
673
+ create_batch_render: handleCreateBatchRender,
674
+ export_print_files: handleExportPrintFiles,
675
+ upload_psd: handleUploadPsd,
676
+ delete_psd: handleDeletePsd,
677
+ };
678
+
679
+ // =============================================================================
680
+ // MCP Request Handlers
681
+ // =============================================================================
682
+
683
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools }));
684
+
685
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
686
+ const { name, arguments: args } = request.params;
687
+
688
+ const handler = toolHandlers[name];
689
+ if (!handler) {
690
+ return ResponseFormatter.error(`Unknown tool: ${name}`);
691
+ }
692
+
693
+ try {
694
+ return await handler(args || {});
695
+ } catch (err) {
696
+ return ResponseFormatter.fromError(err, `Error executing ${name}`);
697
+ }
698
+ });
699
+
700
+ // =============================================================================
701
+ // Server Startup
702
+ // =============================================================================
703
+
704
+ async function main() {
705
+ const transport = new StdioServerTransport();
706
+ await server.connect(transport);
707
+ console.error(`Dynamic Mockups MCP Server v${SERVER_VERSION} running`);
708
+ }
709
+
710
+ main().catch((err) => {
711
+ console.error("Fatal error:", err);
712
+ process.exit(1);
713
+ });
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Response Formatter for Dynamic Mockups MCP Server
3
+ * Provides consistent MCP-compliant response formatting
4
+ */
5
+
6
+ export class ResponseFormatter {
7
+ /**
8
+ * Create a successful MCP response with data
9
+ * @param {any} data - Data to include in response
10
+ * @returns {object} MCP-compliant response
11
+ */
12
+ static ok(data) {
13
+ const text = typeof data === "string" ? data : JSON.stringify(data, null, 2);
14
+ return {
15
+ content: [{ type: "text", text }],
16
+ };
17
+ }
18
+
19
+ /**
20
+ * Create an error MCP response
21
+ * @param {string} message - Error message
22
+ * @param {object} details - Additional error details
23
+ * @returns {object} MCP-compliant error response
24
+ */
25
+ static error(message, details = {}) {
26
+ return {
27
+ content: [
28
+ {
29
+ type: "text",
30
+ text: JSON.stringify({ error: { message, ...details } }, null, 2),
31
+ },
32
+ ],
33
+ isError: true,
34
+ };
35
+ }
36
+
37
+ /**
38
+ * Format an API response, handling both success and error cases
39
+ * @param {object} response - Axios response object
40
+ * @param {string} successMessage - Optional success message prefix
41
+ * @returns {object} MCP-compliant response
42
+ */
43
+ static fromApiResponse(response, successMessage = null) {
44
+ const { status, data } = response;
45
+
46
+ // Handle API errors (4xx responses)
47
+ if (status >= 400) {
48
+ const errorMessage = data?.message || data?.error || `API error (${status})`;
49
+ return this.error(errorMessage, {
50
+ status,
51
+ details: data,
52
+ });
53
+ }
54
+
55
+ // Handle successful responses
56
+ if (successMessage) {
57
+ return this.ok({
58
+ message: successMessage,
59
+ ...data,
60
+ });
61
+ }
62
+
63
+ return this.ok(data);
64
+ }
65
+
66
+ /**
67
+ * Format an error from a caught exception
68
+ * @param {Error} error - The caught error
69
+ * @param {string} context - Context description for the error
70
+ * @returns {object} MCP-compliant error response
71
+ */
72
+ static fromError(error, context = "Operation failed") {
73
+ // Handle Axios errors
74
+ if (error.response) {
75
+ return this.error(context, {
76
+ status: error.response.status,
77
+ message: error.response.data?.message || error.message,
78
+ details: error.response.data,
79
+ });
80
+ }
81
+
82
+ // Handle network errors
83
+ if (error.code === "ECONNABORTED") {
84
+ return this.error("Request timeout", {
85
+ context,
86
+ suggestion: "The API request took too long. Try again or use smaller batch sizes.",
87
+ });
88
+ }
89
+
90
+ if (error.code === "ENOTFOUND" || error.code === "ECONNREFUSED") {
91
+ return this.error("Network error", {
92
+ context,
93
+ suggestion: "Unable to reach the API. Check your internet connection.",
94
+ });
95
+ }
96
+
97
+ // Handle generic errors
98
+ return this.error(context, {
99
+ message: error.message,
100
+ });
101
+ }
102
+ }
103
+
104
+ export default ResponseFormatter;