@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.
- package/README.md +18 -16
- package/package.json +1 -1
- 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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
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
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
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
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|