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