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
});