FigmaMind MCP Server

by joao-loker
Verified
// FigmaMind MCP Server with full processing capabilities // This is the main entry point for the MCP server const readline = require('readline'); const fs = require('fs-extra'); const path = require('path'); const dotenv = require('dotenv'); // Load environment variables from .env file if present dotenv.config(); // Import our modules const utils = require('./utils'); const figmaService = require('./figmaService'); const processor = require('./processor'); // Create logs directory const logsDir = path.join(__dirname, 'logs'); if (!fs.existsSync(logsDir)) { fs.mkdirSync(logsDir, { recursive: true }); } // Log function function log(message) { const timestamp = new Date().toISOString(); fs.appendFileSync(path.join(logsDir, 'server.log'), `[${timestamp}] ${message}\n`); } // Define the tools available const TOOLS = [ { name: 'figmamind.transform', description: 'Transforma componentes do Figma em formato padronizado', parameters: { type: 'object', properties: { figmaUrl: { type: 'string', description: 'URL do arquivo Figma para processar' } }, required: ['figmaUrl'] } }, { name: 'figmamind.info', description: 'Retorna informações sobre o serviço FigmaMind', parameters: { type: 'object', properties: {}, required: [] } } ]; // Function to send JSON-RPC messages function sendJsonRpcMessage(method, params = {}, id = null) { const message = JSON.stringify({ jsonrpc: "2.0", method, params, id }); console.log(message); } // Create readline interface for stdin/stdout const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false }); // Process Figma URL through the transform tool async function processFigmaTransform(url) { try { // Verify token is configured if (!process.env.FIGMA_TOKEN || process.env.FIGMA_TOKEN === 'DEFAULT_TOKEN_PLEASE_REPLACE') { throw new Error('FIGMA_TOKEN not configured. Please set the FIGMA_TOKEN environment variable.'); } // Set token (it might already be set during module initialization, but ensure it is set) figmaService.setToken(process.env.FIGMA_TOKEN); // Fetch data from Figma utils.log(`Processing Figma URL: ${url}`, 'debug'); const figmaResult = await figmaService.fetchFigmaFromUrl(url); // Process the data const processed = await processor.processData(figmaResult.data, figmaResult.fileKey); // Save processed and raw data for debugging await fs.writeJson( path.join(utils.paths.OUTPUT_DIR, 'figma-raw.json'), figmaResult.data, { spaces: 2 } ); await fs.writeJson( path.join(utils.paths.OUTPUT_DIR, 'figma-processed.json'), processed, { spaces: 2 } ); return { success: true, message: `Processed ${processed.componentsCount} components`, source: url, data: processed }; } catch (error) { utils.log(`Error in processFigmaTransform: ${error.message}`, 'error'); return { success: false, message: error.message || 'Error processing Figma data', error: error.message }; } } // Get service information function getServiceInfo() { return { success: true, message: 'Service information', data: { name: 'FigmaMind', version: '1.0.0', description: 'Transforms and standardizes Figma components into specific formats', capabilities: [ 'Component extraction', 'Asset processing', 'Design system transformation' ], status: { figmaApiReady: !!process.env.FIGMA_TOKEN && process.env.FIGMA_TOKEN !== "DEFAULT_TOKEN_PLEASE_REPLACE", serverRunning: true } } }; } // Handle incoming JSON-RPC messages rl.on('line', async (line) => { utils.log(`Received: ${line}`, 'debug'); try { const request = JSON.parse(line); if (!request.id) { utils.log('No request ID found, ignoring', 'warn'); return; } switch (request.method) { case 'initialize': sendJsonRpcMessage('response', { jsonrpc: '2.0', id: request.id, result: { protocolVersion: "1.0", name: "FigmaMind", version: "1.0.0", capabilities: { tools: { listChanged: false } } } }); break; case 'tools.list': sendJsonRpcMessage('response', { jsonrpc: '2.0', id: request.id, result: { tools: TOOLS } }); break; case 'tools.get': if (request.params && request.params.name) { const tool = TOOLS.find(t => t.name === request.params.name); if (tool) { sendJsonRpcMessage('response', { jsonrpc: '2.0', id: request.id, result: { tool } }); } else { sendJsonRpcMessage('response', { jsonrpc: '2.0', id: request.id, error: { code: 404, message: `Tool '${request.params.name}' not found` } }); } } break; case 'tools.run': if (request.params && request.params.name) { let result; if (request.params.name === 'figmamind.info') { result = getServiceInfo(); } else if (request.params.name === 'figmamind.transform') { if (!request.params.arguments || !request.params.arguments.figmaUrl) { sendJsonRpcMessage('response', { jsonrpc: '2.0', id: request.id, error: { code: 400, message: 'Missing required parameter: figmaUrl' } }); return; } // Process the Figma URL result = await processFigmaTransform(request.params.arguments.figmaUrl); } else { sendJsonRpcMessage('response', { jsonrpc: '2.0', id: request.id, error: { code: 404, message: `Tool '${request.params.name}' not found` } }); return; } sendJsonRpcMessage('response', { jsonrpc: '2.0', id: request.id, result }); } break; default: sendJsonRpcMessage('response', { jsonrpc: '2.0', id: request.id, error: { code: -32601, message: `Method '${request.method}' not found` } }); } } catch (error) { utils.log(`Error processing message: ${error.message}`, 'error'); try { sendJsonRpcMessage('response', { jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } }); } catch (e) { utils.log(`Failed to send error response: ${e.message}`, 'error'); } } }); // Send startup message utils.log('FigmaMind MCP Server starting up...', 'info'); sendJsonRpcMessage('startup.complete', { status: "ok", timestamp: new Date().toISOString() }); // Handle process exit process.on('SIGINT', () => { utils.log('Server shutting down...', 'info'); process.exit(0); }); process.on('uncaughtException', (error) => { utils.log(`UNCAUGHT EXCEPTION: ${error.message}\n${error.stack || ''}`, 'error'); // Do not exit to keep the server running }); process.on('unhandledRejection', (reason) => { const errorMessage = reason instanceof Error ? `UNHANDLED REJECTION: ${reason.message}\n${reason.stack || ''}` : `UNHANDLED REJECTION: ${String(reason)}`; utils.log(errorMessage, 'error'); // Do not exit to keep the server running });