@nbakka/mcp-appium 2.0.42 → 2.0.43
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 +108 -23
- package/package.json +1 -1
package/lib/server.js
CHANGED
|
@@ -476,11 +476,12 @@ const createMcpServer = () => {
|
|
|
476
476
|
|
|
477
477
|
tool(
|
|
478
478
|
"mobile_export_figma_images",
|
|
479
|
-
"Export
|
|
479
|
+
"Export only main parent frames from Figma file as PNG images",
|
|
480
480
|
{
|
|
481
|
-
figmaUrl: zod_1.z.string().describe("The Figma file URL to export frames from")
|
|
481
|
+
figmaUrl: zod_1.z.string().describe("The Figma file URL to export frames from"),
|
|
482
|
+
nodeId: zod_1.z.string().optional().describe("Specific node ID from URL (e.g., 13223-1724)")
|
|
482
483
|
},
|
|
483
|
-
async ({ figmaUrl }) => {
|
|
484
|
+
async ({ figmaUrl, nodeId }) => {
|
|
484
485
|
try {
|
|
485
486
|
// Read Figma credentials from desktop/figma.json file
|
|
486
487
|
const figmaConfigPath = path.join(os.homedir(), 'Desktop', 'figma.json');
|
|
@@ -500,12 +501,18 @@ const createMcpServer = () => {
|
|
|
500
501
|
throw new Error('Figma API token not found in figma.json file. Please ensure the file contains "token" field.');
|
|
501
502
|
}
|
|
502
503
|
|
|
503
|
-
// Extract file ID from Figma URL
|
|
504
|
+
// Extract file ID and node ID from Figma URL
|
|
504
505
|
const fileId = extractFileIdFromUrl(figmaUrl);
|
|
505
506
|
if (!fileId) {
|
|
506
507
|
throw new Error('Invalid Figma URL. Unable to extract file ID.');
|
|
507
508
|
}
|
|
508
509
|
|
|
510
|
+
// Extract node ID from URL if not provided separately
|
|
511
|
+
let targetNodeId = nodeId;
|
|
512
|
+
if (!targetNodeId) {
|
|
513
|
+
targetNodeId = extractNodeIdFromUrl(figmaUrl);
|
|
514
|
+
}
|
|
515
|
+
|
|
509
516
|
// Set up export directory
|
|
510
517
|
const exportPath = path.join(os.homedir(), 'Desktop', 'figma');
|
|
511
518
|
|
|
@@ -523,7 +530,6 @@ const createMcpServer = () => {
|
|
|
523
530
|
// Continue if cleanup fails
|
|
524
531
|
}
|
|
525
532
|
|
|
526
|
-
// Get file information
|
|
527
533
|
// Get file information
|
|
528
534
|
const fileResponse = await axios.get(
|
|
529
535
|
`https://api.figma.com/v1/files/${fileId}`,
|
|
@@ -536,18 +542,30 @@ const createMcpServer = () => {
|
|
|
536
542
|
|
|
537
543
|
const fileData = fileResponse.data;
|
|
538
544
|
|
|
539
|
-
|
|
540
|
-
const frameIds = extractFrameNodes(fileData.document);
|
|
545
|
+
let framesToExport = [];
|
|
541
546
|
|
|
542
|
-
if (
|
|
543
|
-
|
|
547
|
+
if (targetNodeId) {
|
|
548
|
+
// If specific node ID provided, find that node and export it (not its children)
|
|
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
|
+
// Extract only main/parent frames (large frames, likely screens/pages)
|
|
557
|
+
framesToExport = extractMainFrames(fileData.document);
|
|
544
558
|
}
|
|
545
559
|
|
|
546
|
-
|
|
547
|
-
|
|
560
|
+
if (framesToExport.length === 0) {
|
|
561
|
+
return `No main frames found in Figma file.`;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// Limit to reasonable number to avoid API issues
|
|
565
|
+
const limitedFrameIds = framesToExport.slice(0, 10);
|
|
548
566
|
|
|
549
567
|
let successCount = 0;
|
|
550
|
-
const batchSize =
|
|
568
|
+
const batchSize = 5; // Smaller batches for main frames
|
|
551
569
|
|
|
552
570
|
// Process frames in batches
|
|
553
571
|
for (let batchStart = 0; batchStart < limitedFrameIds.length; batchStart += batchSize) {
|
|
@@ -603,8 +621,8 @@ const createMcpServer = () => {
|
|
|
603
621
|
|
|
604
622
|
return `Figma Export Complete!
|
|
605
623
|
Export Path: ${exportPath}
|
|
606
|
-
Total frames
|
|
607
|
-
Exported
|
|
624
|
+
Total main frames found: ${framesToExport.length}
|
|
625
|
+
Exported: ${successCount} main frames as PNG files
|
|
608
626
|
Files: ${Array.from({length: successCount}, (_, i) => `${i + 1}.png`).join(', ')}`;
|
|
609
627
|
|
|
610
628
|
} catch (error) {
|
|
@@ -636,23 +654,30 @@ const createMcpServer = () => {
|
|
|
636
654
|
return null;
|
|
637
655
|
}
|
|
638
656
|
|
|
639
|
-
// Helper function to extract
|
|
640
|
-
function
|
|
641
|
-
const
|
|
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;
|
|
642
669
|
|
|
643
670
|
function traverse(node) {
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
671
|
+
if (node.id === nodeId) {
|
|
672
|
+
foundNode = node;
|
|
673
|
+
return;
|
|
647
674
|
}
|
|
648
675
|
|
|
649
|
-
// Continue traversing children
|
|
650
676
|
if (node.children && Array.isArray(node.children)) {
|
|
651
677
|
node.children.forEach(traverse);
|
|
652
678
|
}
|
|
653
679
|
}
|
|
654
680
|
|
|
655
|
-
// Start traversal from document children (pages)
|
|
656
681
|
if (document.children && Array.isArray(document.children)) {
|
|
657
682
|
document.children.forEach(page => {
|
|
658
683
|
if (page.children && Array.isArray(page.children)) {
|
|
@@ -661,7 +686,67 @@ const createMcpServer = () => {
|
|
|
661
686
|
});
|
|
662
687
|
}
|
|
663
688
|
|
|
664
|
-
return
|
|
689
|
+
return foundNode;
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
// Helper function to extract only main/parent frames (not small UI components)
|
|
693
|
+
function extractMainFrames(document) {
|
|
694
|
+
const mainFrames = [];
|
|
695
|
+
|
|
696
|
+
function traversePage(node, depth = 0) {
|
|
697
|
+
if (node.type === 'FRAME') {
|
|
698
|
+
// Consider frames as "main" if they are:
|
|
699
|
+
// 1. Top-level frames on a page (depth 0)
|
|
700
|
+
// 2. Large frames (width > 300 or height > 300)
|
|
701
|
+
// 3. Frames that don't have many small children (likely screens, not component libraries)
|
|
702
|
+
|
|
703
|
+
const isTopLevel = depth === 0;
|
|
704
|
+
const isLargeFrame = (node.absoluteBoundingBox?.width > 300) || (node.absoluteBoundingBox?.height > 300);
|
|
705
|
+
const childFrameCount = countChildFrames(node);
|
|
706
|
+
const isNotComponentLibrary = childFrameCount < 50; // Avoid component libraries with many small frames
|
|
707
|
+
|
|
708
|
+
if (isTopLevel || (isLargeFrame && isNotComponentLibrary)) {
|
|
709
|
+
mainFrames.push(node.id);
|
|
710
|
+
return; // Don't traverse children of main frames to avoid getting sub-components
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
// Continue traversing children for non-main-frame nodes
|
|
715
|
+
if (node.children && Array.isArray(node.children)) {
|
|
716
|
+
node.children.forEach(child => traversePage(child, depth + 1));
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// Start traversal from document children (pages)
|
|
721
|
+
if (document.children && Array.isArray(document.children)) {
|
|
722
|
+
document.children.forEach(page => {
|
|
723
|
+
if (page.children && Array.isArray(page.children)) {
|
|
724
|
+
page.children.forEach(child => traversePage(child, 0));
|
|
725
|
+
}
|
|
726
|
+
});
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
return mainFrames;
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// Helper function to count child frames (to detect component libraries)
|
|
733
|
+
function countChildFrames(node) {
|
|
734
|
+
let count = 0;
|
|
735
|
+
|
|
736
|
+
function traverse(n) {
|
|
737
|
+
if (n.type === 'FRAME') {
|
|
738
|
+
count++;
|
|
739
|
+
}
|
|
740
|
+
if (n.children && Array.isArray(n.children)) {
|
|
741
|
+
n.children.forEach(traverse);
|
|
742
|
+
}
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
if (node.children && Array.isArray(node.children)) {
|
|
746
|
+
node.children.forEach(traverse);
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
return count;
|
|
665
750
|
}
|
|
666
751
|
|
|
667
752
|
return server;
|