@nbakka/mcp-appium 2.0.45 → 2.0.46
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/lib/server.js +27 -152
- package/package.json +1 -1
package/lib/server.js
CHANGED
|
@@ -476,12 +476,11 @@ const createMcpServer = () => {
|
|
|
476
476
|
|
|
477
477
|
tool(
|
|
478
478
|
"mobile_export_figma_images",
|
|
479
|
-
"Export
|
|
479
|
+
"Export Figma file as PDF",
|
|
480
480
|
{
|
|
481
|
-
figmaUrl: zod_1.z.string().describe("The Figma file URL to export
|
|
482
|
-
nodeId: zod_1.z.string().optional().describe("Specific node ID from URL (e.g., 13223-1724)")
|
|
481
|
+
figmaUrl: zod_1.z.string().describe("The Figma file URL to export as PDF")
|
|
483
482
|
},
|
|
484
|
-
async ({ figmaUrl
|
|
483
|
+
async ({ figmaUrl }) => {
|
|
485
484
|
try {
|
|
486
485
|
// Read Figma credentials from desktop/figma.json file
|
|
487
486
|
const figmaConfigPath = path.join(os.homedir(), 'Desktop', 'figma.json');
|
|
@@ -501,129 +500,61 @@ tool(
|
|
|
501
500
|
throw new Error('Figma API token not found in figma.json file. Please ensure the file contains "token" field.');
|
|
502
501
|
}
|
|
503
502
|
|
|
504
|
-
// Extract file ID
|
|
503
|
+
// Extract file ID from Figma URL
|
|
505
504
|
const fileId = extractFileIdFromUrl(figmaUrl);
|
|
506
505
|
if (!fileId) {
|
|
507
506
|
throw new Error('Invalid Figma URL. Unable to extract file ID.');
|
|
508
507
|
}
|
|
509
508
|
|
|
510
|
-
// Extract node ID from URL if not provided separately
|
|
511
|
-
let targetNodeId = nodeId;
|
|
512
|
-
if (!targetNodeId) {
|
|
513
|
-
targetNodeId = extractNodeIdFromUrl(figmaUrl);
|
|
514
|
-
}
|
|
515
|
-
|
|
516
509
|
// Set up export directory
|
|
517
510
|
const exportPath = path.join(os.homedir(), 'Desktop', 'figma');
|
|
518
511
|
|
|
519
512
|
// Create export directory if it doesn't exist
|
|
520
513
|
await fs.mkdir(exportPath, { recursive: true });
|
|
521
514
|
|
|
522
|
-
// Clear existing
|
|
515
|
+
// Clear existing PDF files in the directory
|
|
523
516
|
try {
|
|
524
517
|
const existingFiles = await fs.readdir(exportPath);
|
|
525
|
-
const
|
|
526
|
-
for (const file of
|
|
518
|
+
const pdfFiles = existingFiles.filter(file => file.endsWith('.pdf'));
|
|
519
|
+
for (const file of pdfFiles) {
|
|
527
520
|
await fs.unlink(path.join(exportPath, file));
|
|
528
521
|
}
|
|
529
522
|
} catch (cleanupError) {
|
|
530
523
|
// Continue if cleanup fails
|
|
531
524
|
}
|
|
532
525
|
|
|
533
|
-
//
|
|
534
|
-
const
|
|
535
|
-
`https://api.figma.com/v1/
|
|
526
|
+
// Request PDF export of entire file
|
|
527
|
+
const exportResponse = await axios.get(
|
|
528
|
+
`https://api.figma.com/v1/images/${fileId}`,
|
|
536
529
|
{
|
|
537
530
|
headers: {
|
|
538
531
|
'X-Figma-Token': figmaToken
|
|
532
|
+
},
|
|
533
|
+
params: {
|
|
534
|
+
format: 'pdf'
|
|
539
535
|
}
|
|
540
536
|
}
|
|
541
537
|
);
|
|
542
538
|
|
|
543
|
-
const
|
|
544
|
-
|
|
545
|
-
let framesToExport = [];
|
|
546
|
-
|
|
547
|
-
if (targetNodeId) {
|
|
548
|
-
// If specific node ID provided, find that node and export it
|
|
549
|
-
const specificNode = findNodeById(fileData.document, targetNodeId);
|
|
550
|
-
if (specificNode && specificNode.type === 'FRAME') {
|
|
551
|
-
framesToExport = [specificNode.id];
|
|
552
|
-
} else {
|
|
553
|
-
return `Node ID ${targetNodeId} not found or is not a frame.`;
|
|
554
|
-
}
|
|
555
|
-
} else {
|
|
556
|
-
// Get only top-level frames (direct children of pages)
|
|
557
|
-
framesToExport = extractMainFrames(fileData.document);
|
|
558
|
-
}
|
|
539
|
+
const pdfUrl = exportResponse.data.images[fileId];
|
|
559
540
|
|
|
560
|
-
if (
|
|
561
|
-
|
|
541
|
+
if (!pdfUrl) {
|
|
542
|
+
throw new Error('Failed to get PDF export URL from Figma');
|
|
562
543
|
}
|
|
563
544
|
|
|
564
|
-
//
|
|
565
|
-
const
|
|
545
|
+
// Download PDF
|
|
546
|
+
const pdfResponse = await axios.get(pdfUrl, {
|
|
547
|
+
responseType: 'arraybuffer'
|
|
548
|
+
});
|
|
566
549
|
|
|
567
|
-
|
|
568
|
-
const
|
|
550
|
+
// Save PDF to file with timestamp
|
|
551
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
|
|
552
|
+
const pdfPath = path.join(exportPath, `figma-export-${timestamp}.pdf`);
|
|
553
|
+
await fs.writeFile(pdfPath, pdfResponse.data);
|
|
569
554
|
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
const batchIds = limitedFrameIds.slice(batchStart, batchEnd);
|
|
574
|
-
|
|
575
|
-
try {
|
|
576
|
-
// Request image exports for current batch
|
|
577
|
-
const exportResponse = await axios.get(
|
|
578
|
-
`https://api.figma.com/v1/images/${fileId}`,
|
|
579
|
-
{
|
|
580
|
-
headers: {
|
|
581
|
-
'X-Figma-Token': figmaToken
|
|
582
|
-
},
|
|
583
|
-
params: {
|
|
584
|
-
ids: batchIds.join(','),
|
|
585
|
-
format: 'png',
|
|
586
|
-
scale: 2
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
);
|
|
590
|
-
|
|
591
|
-
const imageUrls = exportResponse.data.images;
|
|
592
|
-
|
|
593
|
-
// Download and save each image in the batch
|
|
594
|
-
for (let i = 0; i < batchIds.length; i++) {
|
|
595
|
-
const nodeId = batchIds[i];
|
|
596
|
-
const imageUrl = imageUrls[nodeId];
|
|
597
|
-
|
|
598
|
-
if (imageUrl) {
|
|
599
|
-
try {
|
|
600
|
-
const frameNumber = batchStart + i + 1;
|
|
601
|
-
const filename = `${frameNumber}.png`;
|
|
602
|
-
const filepath = path.join(exportPath, filename);
|
|
603
|
-
|
|
604
|
-
// Download image
|
|
605
|
-
const imageResponse = await axios.get(imageUrl, {
|
|
606
|
-
responseType: 'arraybuffer'
|
|
607
|
-
});
|
|
608
|
-
|
|
609
|
-
// Save image to file
|
|
610
|
-
await fs.writeFile(filepath, imageResponse.data);
|
|
611
|
-
successCount++;
|
|
612
|
-
} catch (downloadError) {
|
|
613
|
-
// Continue with next image if one fails
|
|
614
|
-
}
|
|
615
|
-
}
|
|
616
|
-
}
|
|
617
|
-
} catch (batchError) {
|
|
618
|
-
// Continue with next batch if one fails
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
|
|
622
|
-
return `Figma Export Complete!
|
|
623
|
-
Export Path: ${exportPath}
|
|
624
|
-
Total main frames found: ${framesToExport.length}
|
|
625
|
-
Exported: ${successCount} main frames as PNG files
|
|
626
|
-
Files: ${Array.from({length: successCount}, (_, i) => `${i + 1}.png`).join(', ')}`;
|
|
555
|
+
return `Figma PDF Export Complete!
|
|
556
|
+
Export Path: ${pdfPath}
|
|
557
|
+
File: figma-export-${timestamp}.pdf`;
|
|
627
558
|
|
|
628
559
|
} catch (error) {
|
|
629
560
|
if (error.response && error.response.status === 403) {
|
|
@@ -654,62 +585,6 @@ function extractFileIdFromUrl(url) {
|
|
|
654
585
|
return null;
|
|
655
586
|
}
|
|
656
587
|
|
|
657
|
-
// Helper function to extract node ID from Figma URL
|
|
658
|
-
function extractNodeIdFromUrl(url) {
|
|
659
|
-
const match = url.match(/node-id=([^&]+)/);
|
|
660
|
-
if (match) {
|
|
661
|
-
return match[1];
|
|
662
|
-
}
|
|
663
|
-
return null;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
// Helper function to find a specific node by ID
|
|
667
|
-
function findNodeById(document, nodeId) {
|
|
668
|
-
let foundNode = null;
|
|
669
|
-
|
|
670
|
-
function traverse(node) {
|
|
671
|
-
if (node.id === nodeId) {
|
|
672
|
-
foundNode = node;
|
|
673
|
-
return;
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
if (node.children && Array.isArray(node.children)) {
|
|
677
|
-
node.children.forEach(traverse);
|
|
678
|
-
}
|
|
679
|
-
}
|
|
680
|
-
|
|
681
|
-
if (document.children && Array.isArray(document.children)) {
|
|
682
|
-
document.children.forEach(page => {
|
|
683
|
-
if (page.children && Array.isArray(page.children)) {
|
|
684
|
-
page.children.forEach(traverse);
|
|
685
|
-
}
|
|
686
|
-
});
|
|
687
|
-
}
|
|
688
|
-
|
|
689
|
-
return foundNode;
|
|
690
|
-
}
|
|
691
|
-
|
|
692
|
-
// Simple function to extract only top-level frames (direct children of pages)
|
|
693
|
-
function extractMainFrames(document) {
|
|
694
|
-
const topLevelFrames = [];
|
|
695
|
-
|
|
696
|
-
// Only get direct children of pages - no traversing deeper
|
|
697
|
-
if (document.children && Array.isArray(document.children)) {
|
|
698
|
-
document.children.forEach(page => {
|
|
699
|
-
if (page.children && Array.isArray(page.children)) {
|
|
700
|
-
// Only look at direct children of the page
|
|
701
|
-
page.children.forEach(child => {
|
|
702
|
-
if (child.type === 'FRAME') {
|
|
703
|
-
topLevelFrames.push(child.id);
|
|
704
|
-
}
|
|
705
|
-
});
|
|
706
|
-
}
|
|
707
|
-
});
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
return topLevelFrames;
|
|
711
|
-
}
|
|
712
|
-
|
|
713
588
|
|
|
714
589
|
return server;
|
|
715
590
|
};
|