@nbakka/mcp-appium 2.0.45 → 2.0.47

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 (2) hide show
  1. package/lib/server.js +87 -151
  2. 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 only main parent frames from Figma file as PNG images",
479
+ "Export Figma file as PDF",
480
480
  {
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)")
481
+ figmaUrl: zod_1.z.string().describe("The Figma file URL to export as PDF")
483
482
  },
484
- async ({ figmaUrl, nodeId }) => {
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,36 +500,30 @@ 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 and node ID from Figma URL
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 PNG files in the directory
515
+ // Clear existing PDF files in the directory
523
516
  try {
524
517
  const existingFiles = await fs.readdir(exportPath);
525
- const pngFiles = existingFiles.filter(file => file.endsWith('.png'));
526
- for (const file of pngFiles) {
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
- // Get file information
526
+ // First, get the file structure to find all top-level frames
534
527
  const fileResponse = await axios.get(
535
528
  `https://api.figma.com/v1/files/${fileId}`,
536
529
  {
@@ -542,97 +535,98 @@ tool(
542
535
 
543
536
  const fileData = fileResponse.data;
544
537
 
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);
538
+ // Extract all top-level frame IDs
539
+ const frameIds = [];
540
+ if (fileData.document.children && Array.isArray(fileData.document.children)) {
541
+ fileData.document.children.forEach(page => {
542
+ if (page.children && Array.isArray(page.children)) {
543
+ page.children.forEach(child => {
544
+ if (child.type === 'FRAME') {
545
+ frameIds.push(child.id);
546
+ }
547
+ });
548
+ }
549
+ });
558
550
  }
559
551
 
560
- if (framesToExport.length === 0) {
561
- return `No main frames found in Figma file.`;
552
+ if (frameIds.length === 0) {
553
+ throw new Error('No frames found in the Figma file');
562
554
  }
563
555
 
564
- // Limit to reasonable number to avoid API issues
565
- const limitedFrameIds = framesToExport.slice(0, 10);
556
+ console.log(`Found ${frameIds.length} top-level frames:`, frameIds.slice(0, 5));
566
557
 
567
- let successCount = 0;
568
- const batchSize = 5; // Smaller batches for main frames
558
+ // Request PDF export with specific frame IDs
559
+ const exportResponse = await axios.get(
560
+ `https://api.figma.com/v1/images/${fileId}`,
561
+ {
562
+ headers: {
563
+ 'X-Figma-Token': figmaToken
564
+ },
565
+ params: {
566
+ ids: frameIds.join(','),
567
+ format: 'pdf',
568
+ use_absolute_bounds: false
569
+ }
570
+ }
571
+ );
569
572
 
570
- // Process frames in batches
571
- for (let batchStart = 0; batchStart < limitedFrameIds.length; batchStart += batchSize) {
572
- const batchEnd = Math.min(batchStart + batchSize, limitedFrameIds.length);
573
- const batchIds = limitedFrameIds.slice(batchStart, batchEnd);
573
+ console.log('Export response:', exportResponse.data);
574
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
- );
575
+ // For PDF export, the response structure might be different
576
+ let pdfUrl = null;
577
+ if (exportResponse.data.images) {
578
+ // Try to get PDF URL - might be under file ID or first frame ID
579
+ pdfUrl = exportResponse.data.images[fileId] ||
580
+ exportResponse.data.images[frameIds[0]] ||
581
+ Object.values(exportResponse.data.images)[0];
582
+ }
590
583
 
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
- }
584
+ if (!pdfUrl) {
585
+ throw new Error('Failed to get PDF export URL from Figma');
620
586
  }
621
587
 
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(', ')}`;
588
+ // Download PDF
589
+ const pdfResponse = await axios.get(pdfUrl, {
590
+ responseType: 'arraybuffer'
591
+ });
592
+
593
+ // Save PDF to file with timestamp
594
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
595
+ const pdfPath = path.join(exportPath, `figma-export-${timestamp}.pdf`);
596
+ await fs.writeFile(pdfPath, pdfResponse.data);
597
+
598
+ return `Figma PDF Export Complete!
599
+ Export Path: ${pdfPath}
600
+ File: figma-export-${timestamp}.pdf`;
627
601
 
628
602
  } catch (error) {
629
- if (error.response && error.response.status === 403) {
630
- return `Error: Access denied. Please check your Figma API token and file permissions.`;
631
- } else if (error.response && error.response.status === 404) {
632
- return `Error: Figma file not found. Please check the URL and ensure the file is accessible.`;
633
- } else {
634
- return `Error exporting from Figma: ${error.message}`;
603
+ // Enhanced error logging
604
+ let errorMessage = error.message;
605
+
606
+ if (error.response) {
607
+ const status = error.response.status;
608
+ const responseData = error.response.data;
609
+
610
+ console.error('Figma API Error:', {
611
+ status,
612
+ statusText: error.response.statusText,
613
+ data: responseData,
614
+ url: error.config?.url,
615
+ params: error.config?.params
616
+ });
617
+
618
+ if (status === 400) {
619
+ errorMessage = `Bad Request (400): ${responseData?.message || responseData?.err || 'Invalid request parameters'}. This might be due to PDF export limitations or invalid frame IDs.`;
620
+ } else if (status === 403) {
621
+ return `Error: Access denied. Please check your Figma API token and file permissions.`;
622
+ } else if (status === 404) {
623
+ return `Error: Figma file not found. Please check the URL and ensure the file is accessible.`;
624
+ } else {
625
+ errorMessage = `HTTP ${status}: ${responseData?.message || responseData?.err || error.response.statusText}`;
626
+ }
635
627
  }
628
+
629
+ return `Error exporting from Figma: ${errorMessage}`;
636
630
  }
637
631
  }
638
632
  );
@@ -653,64 +647,6 @@ function extractFileIdFromUrl(url) {
653
647
  }
654
648
  return null;
655
649
  }
656
-
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
-
714
650
  return server;
715
651
  };
716
652
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nbakka/mcp-appium",
3
- "version": "2.0.45",
3
+ "version": "2.0.47",
4
4
  "description": "Appium MCP",
5
5
  "engines": {
6
6
  "node": ">=18"