@nbakka/mcp-appium 2.0.47 → 2.0.48
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 +90 -134
- package/package.json +5 -2
package/lib/server.js
CHANGED
|
@@ -15,6 +15,8 @@ const png_1 = require("./png");
|
|
|
15
15
|
const image_utils_1 = require("./image-utils");
|
|
16
16
|
const { google } = require('googleapis');
|
|
17
17
|
const axios = require('axios');
|
|
18
|
+
import OpenAI from "openai";
|
|
19
|
+
import { tool } from "ai";
|
|
18
20
|
const getAgentVersion = () => {
|
|
19
21
|
const json = require("../package.json");
|
|
20
22
|
return json.version;
|
|
@@ -474,179 +476,133 @@ const createMcpServer = () => {
|
|
|
474
476
|
return [...new Set(figmaLinks)];
|
|
475
477
|
}
|
|
476
478
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
+
// ----------------------
|
|
480
|
+
// Helper: Extract File ID
|
|
481
|
+
// ----------------------
|
|
482
|
+
function extractFileIdFromUrl(url) {
|
|
483
|
+
const patterns = [
|
|
484
|
+
/figma\.com\/file\/([a-zA-Z0-9]+)/,
|
|
485
|
+
/figma\.com\/design\/([a-zA-Z0-9]+)/,
|
|
486
|
+
/figma\.com\/proto\/([a-zA-Z0-9]+)/
|
|
487
|
+
];
|
|
488
|
+
|
|
489
|
+
for (const pattern of patterns) {
|
|
490
|
+
const match = url.match(pattern);
|
|
491
|
+
if (match) return match[1];
|
|
492
|
+
}
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// ----------------------
|
|
497
|
+
// TOOL 1: Export Figma to PDF
|
|
498
|
+
// ----------------------
|
|
499
|
+
export const mobile_export_figma_pdf = tool(
|
|
500
|
+
"mobile_export_figma_pdf",
|
|
479
501
|
"Export Figma file as PDF",
|
|
480
502
|
{
|
|
481
503
|
figmaUrl: zod_1.z.string().describe("The Figma file URL to export as PDF")
|
|
482
504
|
},
|
|
483
505
|
async ({ figmaUrl }) => {
|
|
484
506
|
try {
|
|
485
|
-
//
|
|
486
|
-
const figmaConfigPath = path.join(os.homedir(),
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
try {
|
|
490
|
-
const configContent = await fs.readFile(figmaConfigPath, 'utf-8');
|
|
491
|
-
figmaConfig = JSON.parse(configContent);
|
|
492
|
-
} catch (error) {
|
|
493
|
-
throw new Error(`Failed to read Figma config from ${figmaConfigPath}: ${error.message}`);
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// Extract API token from config
|
|
497
|
-
const { token: figmaToken } = figmaConfig;
|
|
507
|
+
// Load Figma token from Desktop/figma.json
|
|
508
|
+
const figmaConfigPath = path.join(os.homedir(), "Desktop", "figma.json");
|
|
509
|
+
const configContent = await fs.readFile(figmaConfigPath, "utf-8");
|
|
510
|
+
const { token: figmaToken } = JSON.parse(configContent);
|
|
498
511
|
|
|
499
|
-
if (!figmaToken)
|
|
500
|
-
throw new Error('Figma API token not found in figma.json file. Please ensure the file contains "token" field.');
|
|
501
|
-
}
|
|
512
|
+
if (!figmaToken) throw new Error("Figma API token missing in figma.json");
|
|
502
513
|
|
|
503
|
-
// Extract file ID from
|
|
514
|
+
// Extract file ID from URL
|
|
504
515
|
const fileId = extractFileIdFromUrl(figmaUrl);
|
|
505
|
-
if (!fileId)
|
|
506
|
-
throw new Error('Invalid Figma URL. Unable to extract file ID.');
|
|
507
|
-
}
|
|
516
|
+
if (!fileId) throw new Error("Invalid Figma URL - cannot extract fileId");
|
|
508
517
|
|
|
509
|
-
//
|
|
510
|
-
const exportPath = path.join(os.homedir(), 'Desktop', 'figma');
|
|
511
|
-
|
|
512
|
-
// Create export directory if it doesn't exist
|
|
513
|
-
await fs.mkdir(exportPath, { recursive: true });
|
|
514
|
-
|
|
515
|
-
// Clear existing PDF files in the directory
|
|
516
|
-
try {
|
|
517
|
-
const existingFiles = await fs.readdir(exportPath);
|
|
518
|
-
const pdfFiles = existingFiles.filter(file => file.endsWith('.pdf'));
|
|
519
|
-
for (const file of pdfFiles) {
|
|
520
|
-
await fs.unlink(path.join(exportPath, file));
|
|
521
|
-
}
|
|
522
|
-
} catch (cleanupError) {
|
|
523
|
-
// Continue if cleanup fails
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
// First, get the file structure to find all top-level frames
|
|
518
|
+
// Get file structure to find frames
|
|
527
519
|
const fileResponse = await axios.get(
|
|
528
520
|
`https://api.figma.com/v1/files/${fileId}`,
|
|
529
|
-
{
|
|
530
|
-
headers: {
|
|
531
|
-
'X-Figma-Token': figmaToken
|
|
532
|
-
}
|
|
533
|
-
}
|
|
521
|
+
{ headers: { "X-Figma-Token": figmaToken } }
|
|
534
522
|
);
|
|
535
523
|
|
|
536
|
-
const fileData = fileResponse.data;
|
|
537
|
-
|
|
538
|
-
// Extract all top-level frame IDs
|
|
539
524
|
const frameIds = [];
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
if (
|
|
543
|
-
page.children.forEach(child => {
|
|
544
|
-
if (child.type === 'FRAME') {
|
|
545
|
-
frameIds.push(child.id);
|
|
546
|
-
}
|
|
547
|
-
});
|
|
548
|
-
}
|
|
525
|
+
fileResponse.data.document.children?.forEach(page => {
|
|
526
|
+
page.children?.forEach(child => {
|
|
527
|
+
if (child.type === "FRAME") frameIds.push(child.id);
|
|
549
528
|
});
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
if (frameIds.length === 0) {
|
|
553
|
-
throw new Error('No frames found in the Figma file');
|
|
554
|
-
}
|
|
529
|
+
});
|
|
555
530
|
|
|
556
|
-
|
|
531
|
+
if (frameIds.length === 0) throw new Error("No frames found in Figma file");
|
|
557
532
|
|
|
558
|
-
// Request PDF export
|
|
533
|
+
// Request PDF export
|
|
559
534
|
const exportResponse = await axios.get(
|
|
560
535
|
`https://api.figma.com/v1/images/${fileId}`,
|
|
561
536
|
{
|
|
562
|
-
headers: {
|
|
563
|
-
|
|
564
|
-
},
|
|
565
|
-
params: {
|
|
566
|
-
ids: frameIds.join(','),
|
|
567
|
-
format: 'pdf',
|
|
568
|
-
use_absolute_bounds: false
|
|
569
|
-
}
|
|
537
|
+
headers: { "X-Figma-Token": figmaToken },
|
|
538
|
+
params: { ids: frameIds.join(","), format: "pdf" }
|
|
570
539
|
}
|
|
571
540
|
);
|
|
572
541
|
|
|
573
|
-
|
|
574
|
-
|
|
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
|
-
}
|
|
583
|
-
|
|
584
|
-
if (!pdfUrl) {
|
|
585
|
-
throw new Error('Failed to get PDF export URL from Figma');
|
|
586
|
-
}
|
|
542
|
+
const pdfUrl = Object.values(exportResponse.data.images)[0];
|
|
543
|
+
if (!pdfUrl) throw new Error("No PDF export URL returned from Figma");
|
|
587
544
|
|
|
588
545
|
// Download PDF
|
|
589
|
-
const pdfResponse = await axios.get(pdfUrl, {
|
|
590
|
-
|
|
591
|
-
|
|
546
|
+
const pdfResponse = await axios.get(pdfUrl, { responseType: "arraybuffer" });
|
|
547
|
+
|
|
548
|
+
const exportPath = path.join(os.homedir(), "Desktop", "figma");
|
|
549
|
+
await fs.mkdir(exportPath, { recursive: true });
|
|
592
550
|
|
|
593
|
-
|
|
594
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5);
|
|
551
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-").slice(0, -5);
|
|
595
552
|
const pdfPath = path.join(exportPath, `figma-export-${timestamp}.pdf`);
|
|
596
553
|
await fs.writeFile(pdfPath, pdfResponse.data);
|
|
597
554
|
|
|
598
|
-
return
|
|
599
|
-
|
|
600
|
-
|
|
555
|
+
return `✅ PDF Export Complete: ${pdfPath}`;
|
|
556
|
+
} catch (err) {
|
|
557
|
+
return `❌ Error exporting Figma PDF: ${err.message}`;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
);
|
|
601
561
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
562
|
+
// ----------------------
|
|
563
|
+
// TOOL 2: Upload PDF to OpenAI with Jira Info
|
|
564
|
+
// ----------------------
|
|
565
|
+
export const upload_pdf_to_openai = tool(
|
|
566
|
+
"upload_pdf_to_openai",
|
|
567
|
+
"Upload a PDF to OpenAI API along with Jira summary & description",
|
|
568
|
+
{
|
|
569
|
+
jiraSummary: zod_1.z.string().describe("Jira issue summary"),
|
|
570
|
+
jiraDescription: zod_1.z.string().describe("Jira issue description"),
|
|
571
|
+
pdfPath: zod_1.z.string().describe("Path to PDF file from tool 1")
|
|
572
|
+
},
|
|
573
|
+
async ({ jiraSummary, jiraDescription, pdfPath }) => {
|
|
574
|
+
try {
|
|
575
|
+
// Load OpenAI API key
|
|
576
|
+
const openaiConfigPath = path.join(os.homedir(), "Desktop", "openai.json");
|
|
577
|
+
const configContent = await fs.readFile(openaiConfigPath, "utf-8");
|
|
578
|
+
const { apiKey: openaiKey } = JSON.parse(configContent);
|
|
605
579
|
|
|
606
|
-
if (
|
|
607
|
-
const status = error.response.status;
|
|
608
|
-
const responseData = error.response.data;
|
|
580
|
+
if (!openaiKey) throw new Error("OpenAI API key missing in openai.json");
|
|
609
581
|
|
|
610
|
-
|
|
611
|
-
status,
|
|
612
|
-
statusText: error.response.statusText,
|
|
613
|
-
data: responseData,
|
|
614
|
-
url: error.config?.url,
|
|
615
|
-
params: error.config?.params
|
|
616
|
-
});
|
|
582
|
+
const client = new OpenAI({ apiKey: openaiKey });
|
|
617
583
|
|
|
618
|
-
|
|
619
|
-
|
|
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
|
-
}
|
|
627
|
-
}
|
|
584
|
+
// Read PDF file
|
|
585
|
+
const pdfData = await fs.readFile(pdfPath);
|
|
628
586
|
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
587
|
+
// Upload file to OpenAI
|
|
588
|
+
const fileUpload = await client.files.create({
|
|
589
|
+
file: new Blob([pdfData], { type: "application/pdf" }),
|
|
590
|
+
purpose: "assistants"
|
|
591
|
+
});
|
|
633
592
|
|
|
634
|
-
//
|
|
635
|
-
|
|
636
|
-
const patterns = [
|
|
637
|
-
/figma\.com\/file\/([a-zA-Z0-9]+)/,
|
|
638
|
-
/figma\.com\/design\/([a-zA-Z0-9]+)/,
|
|
639
|
-
/figma\.com\/proto\/([a-zA-Z0-9]+)/
|
|
640
|
-
];
|
|
593
|
+
// Create a random prompt with Jira details
|
|
594
|
+
const prompt = `Analyze this design based on Jira details:\n\nSummary: ${jiraSummary}\nDescription: ${jiraDescription}\n\nProvide suggestions and insights.`;
|
|
641
595
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
596
|
+
return {
|
|
597
|
+
message: "✅ PDF uploaded to OpenAI successfully!",
|
|
598
|
+
fileId: fileUpload.id,
|
|
599
|
+
promptUsed: prompt
|
|
600
|
+
};
|
|
601
|
+
} catch (err) {
|
|
602
|
+
return `❌ Error uploading PDF to OpenAI: ${err.message}`;
|
|
646
603
|
}
|
|
647
604
|
}
|
|
648
|
-
|
|
649
|
-
}
|
|
605
|
+
);
|
|
650
606
|
return server;
|
|
651
607
|
};
|
|
652
608
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nbakka/mcp-appium",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.48",
|
|
4
4
|
"description": "Appium MCP",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=18"
|
|
@@ -22,7 +22,10 @@
|
|
|
22
22
|
"fast-xml-parser": "^5.0.9",
|
|
23
23
|
"googleapis": "^149.0.0",
|
|
24
24
|
"zod-to-json-schema": "^3.24.4",
|
|
25
|
-
"axios": "^1.6.0"
|
|
25
|
+
"axios": "^1.6.0",
|
|
26
|
+
"openai": "^5.20.0",
|
|
27
|
+
"zod": "^4.1.0",
|
|
28
|
+
"form-data": "^4.0.0"
|
|
26
29
|
},
|
|
27
30
|
"devDependencies": {
|
|
28
31
|
"@eslint/eslintrc": "^3.2.0",
|