FigmaMind MCP Server

by joao-loker
Verified
#!/usr/bin/env node // Minimal MCP client for Cursor that uses Smithery CLI to run tools const { spawn } = require('child_process'); const readline = require('readline'); // Debug logging function function debugLog(message) { if (process.env.MCP_DEBUG === 'true') { console.error(`[debug] ${message}`); } } // Function to run the Smithery CLI command with timeout function runSmitheryTool(toolName, args = {}) { return new Promise((resolve, reject) => { const command = 'npx'; const commandArgs = [ '-y', '@smithery/cli@latest', 'run', '@joao-loker/figmamind', '--key', 'd1cf009d-9fbe-4cf6-bfe2-d7cd270f295d', '--tool', toolName, '--args', JSON.stringify(args), '--json' ]; debugLog(`Running command: ${command} ${commandArgs.join(' ')}`); const childProcess = spawn(command, commandArgs); let output = ''; let errorOutput = ''; childProcess.stdout.on('data', (data) => { const dataStr = data.toString(); output += dataStr; debugLog(`stdout data: ${dataStr.trim()}`); }); childProcess.stderr.on('data', (data) => { const dataStr = data.toString(); errorOutput += dataStr; // Only log errors if MCP_DEBUG is enabled if (process.env.MCP_DEBUG === 'true') { console.error(`[stderr]: ${dataStr.trim()}`); } }); // Set a timeout for the command const timeout = setTimeout(() => { debugLog(`Command timed out after 15 seconds`); childProcess.kill(); // For figmamind.info, we can return a mock response if timed out if (toolName === 'figmamind.info') { debugLog('Returning mock info response due to timeout'); resolve({ success: true, message: 'Service information (mock due to timeout)', 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 } } }); } else { reject(new Error('Command timed out after 15 seconds')); } }, 15000); childProcess.on('close', (code) => { clearTimeout(timeout); if (code === 0) { try { // Try to extract and parse the JSON result from the output const jsonMatch = output.match(/\{.*\}/s); if (jsonMatch) { resolve(JSON.parse(jsonMatch[0])); } else { debugLog(`No JSON found in output: ${output}`); // For figmamind.info, return a mock response if no JSON found if (toolName === 'figmamind.info') { debugLog('Returning mock info response due to no JSON found'); resolve({ success: true, message: 'Service information (mock)', 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 } } }); } else { resolve({ success: false, message: 'No JSON result found in output' }); } } } catch (error) { debugLog(`Error parsing result: ${error.message}`); reject(new Error(`Failed to parse tool result: ${error.message}`)); } } else { reject(new Error(`Command failed with code ${code}. ${errorOutput}`)); } }); childProcess.on('error', (error) => { clearTimeout(timeout); reject(error); }); }); } // Create readline interface for stdin/stdout const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false }); // Send MCP initialization message console.log(JSON.stringify({ jsonrpc: "2.0", method: "startup.complete", params: { status: "ok", timestamp: new Date().toISOString() }, id: null })); // Handle MCP requests rl.on('line', async (line) => { debugLog(`Received: ${line}`); try { const request = JSON.parse(line); // Handle different MCP method requests switch (request.method) { case 'initialize': // Send initialization response console.log(JSON.stringify({ jsonrpc: '2.0', id: request.id, result: { protocolVersion: "1.0", name: "FigmaMind", version: "1.0.0", capabilities: { tools: { listChanged: false } } } })); break; case 'tools.list': // Send tools list console.log(JSON.stringify({ jsonrpc: '2.0', id: request.id, result: { 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: [] } } ] } })); break; case 'tools.get': // Handle tool info request if (request.params && request.params.name) { const toolName = request.params.name; let tool = null; if (toolName === 'figmamind.transform') { tool = { 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'] } }; } else if (toolName === 'figmamind.info') { tool = { name: 'figmamind.info', description: 'Retorna informações sobre o serviço FigmaMind', parameters: { type: 'object', properties: {}, required: [] } }; } if (tool) { console.log(JSON.stringify({ jsonrpc: '2.0', id: request.id, result: { tool } })); } else { console.log(JSON.stringify({ jsonrpc: '2.0', id: request.id, error: { code: 404, message: `Tool '${toolName}' not found` } })); } } break; case 'tools.run': // Handle tool execution request if (request.params && request.params.name) { const toolName = request.params.name; const toolArgs = request.params.arguments || {}; try { debugLog(`Running tool: ${toolName} with args: ${JSON.stringify(toolArgs)}`); // Run the tool using Smithery CLI const result = await runSmitheryTool(toolName, toolArgs); // Send the result back console.log(JSON.stringify({ jsonrpc: '2.0', id: request.id, result })); } catch (error) { debugLog(`Error running tool: ${error.message}`); console.log(JSON.stringify({ jsonrpc: '2.0', id: request.id, error: { code: 500, message: `Error running tool: ${error.message}` } })); } } break; default: // Handle unknown methods console.log(JSON.stringify({ jsonrpc: '2.0', id: request.id, error: { code: -32601, message: `Method '${request.method}' not found` } })); } } catch (error) { // Handle JSON parsing errors console.log(JSON.stringify({ jsonrpc: '2.0', id: null, error: { code: -32700, message: 'Parse error' } })); } }); // Handle process errors process.on('uncaughtException', (error) => { console.error(`[error] Uncaught exception: ${error.message}\n${error.stack || ''}`); }); process.on('unhandledRejection', (reason) => { const errorMessage = reason instanceof Error ? `Unhandled rejection: ${reason.message}\n${reason.stack || ''}` : `Unhandled rejection: ${String(reason)}`; console.error(`[error] ${errorMessage}`); });