UNPKG

adobe-xd-mcp

Version:

Model Context Protocol server for Adobe XD that enables AI-powered design capabilities

326 lines (295 loc) 8.11 kB
const express = require('express'); const http = require('http'); const WebSocket = require('ws'); const { v4: uuidv4 } = require('uuid'); const cors = require('cors'); const bodyParser = require('body-parser'); const dotenv = require('dotenv'); const readline = require('readline'); // Load environment variables dotenv.config(); // Import MCP handlers const toolsHandler = require('./handlers/tools'); const resourcesHandler = require('./handlers/resources'); const promptsHandler = require('./handlers/prompts'); const rootsHandler = require('./handlers/roots'); // Check if we should use stdio for MCP transport const useStdio = process.env.USE_STDIO === 'true'; // If not using stdio, set up the Express server let app; let server; let wss; const port = process.env.PORT || 3000; const connections = new Map(); if (!useStdio) { app = express(); // Middleware app.use(cors()); app.use(bodyParser.json()); // Create HTTP server server = http.createServer(app); // Initialize WebSocket server for real-time communication wss = new WebSocket.Server({ server }); // Store active connections // MCP protocol handler wss.on('connection', (ws) => handleConnection(ws)); // REST API endpoints app.get('/health', (req, res) => { res.status(200).json({ status: 'healthy', connections: connections.size }); }); app.get('/api/version', (req, res) => { res.json({ version: '0.1.0' }); }); // Start the server server.listen(port, () => { console.log(`Adobe XD MCP Server running on port ${port}`); }); } else { // Use stdio for MCP transport console.error('Starting Adobe XD MCP Server in stdio mode'); const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: false }); const stdioConnection = { send: (data) => { console.log(data); } }; // Handle connection immediately for stdio handleConnection(stdioConnection); // Process incoming messages from stdin rl.on('line', (line) => { try { const message = line.trim(); if (message) { stdioConnection.onmessage({ data: message }); } } catch (error) { console.error('Error processing stdin message:', error); } }); // Handle process termination process.on('SIGINT', () => { rl.close(); process.exit(0); }); process.on('SIGTERM', () => { rl.close(); process.exit(0); }); } /** * Handle a new MCP connection * @param {Object} connection - WebSocket or stdio connection */ function handleConnection(connection) { const connectionId = uuidv4(); if (!useStdio) { connections.set(connectionId, connection); console.log(`New connection established: ${connectionId}`); } // Set up message handler connection.onmessage = async (event) => { try { const message = event.data; const data = JSON.parse(typeof message === 'string' ? message : message.toString()); if (!useStdio) { console.log(`Received: ${JSON.stringify(data)}`); } // Handle various MCP requests if (data.method.startsWith('tools/')) { await handleToolsRequest(connection, data); } else if (data.method.startsWith('resources/')) { await handleResourcesRequest(connection, data); } else if (data.method.startsWith('prompts/')) { await handlePromptsRequest(connection, data); } else if (data.method.startsWith('roots/')) { await handleRootsRequest(connection, data); } else { // Unknown request connection.send(JSON.stringify({ id: data.id, type: 'response', error: { code: -32601, message: `Method not found: ${data.method}` } })); } } catch (error) { console.error('Error processing message:', error); try { const data = JSON.parse(event.data); connection.send(JSON.stringify({ id: data.id, type: 'response', error: { code: -32603, message: `Internal error: ${error.message}` } })); } catch (e) { // Could not parse original message connection.send(JSON.stringify({ id: 'unknown', type: 'response', error: { code: -32700, message: 'Parse error' } })); } } }; // Handle connection close connection.onclose = () => { if (!useStdio) { console.log(`Connection closed: ${connectionId}`); connections.delete(connectionId); } }; // Send initialization message connection.send(JSON.stringify({ id: uuidv4(), type: 'notification', method: 'server/initialize', params: { info: { name: 'Adobe XD MCP Server', version: '0.1.0' }, capabilities: { tools: true, resources: true, prompts: true, roots: true } } })); } // Tools request handler async function handleToolsRequest(connection, data) { switch (data.method) { case 'tools/list': const tools = await toolsHandler.listTools(); connection.send(JSON.stringify({ id: data.id, type: 'response', result: { tools: tools } })); break; case 'tools/execute': const result = await toolsHandler.executeTool(data.params.name, data.params.parameters); connection.send(JSON.stringify({ id: data.id, type: 'response', result: result })); break; default: connection.send(JSON.stringify({ id: data.id, type: 'response', error: { code: -32601, message: `Method not found: ${data.method}` } })); } } // Resources request handler async function handleResourcesRequest(connection, data) { switch (data.method) { case 'resources/list': const resources = await resourcesHandler.listResources(data.params); connection.send(JSON.stringify({ id: data.id, type: 'response', result: { resources: resources } })); break; case 'resources/read': const content = await resourcesHandler.readResource(data.params.uri); connection.send(JSON.stringify({ id: data.id, type: 'response', result: { content: content } })); break; default: connection.send(JSON.stringify({ id: data.id, type: 'response', error: { code: -32601, message: `Method not found: ${data.method}` } })); } } // Prompts request handler async function handlePromptsRequest(connection, data) { switch (data.method) { case 'prompts/list': const prompts = await promptsHandler.listPrompts(); connection.send(JSON.stringify({ id: data.id, type: 'response', result: { prompts: prompts } })); break; case 'prompts/get': const prompt = await promptsHandler.getPrompt(data.params.id); connection.send(JSON.stringify({ id: data.id, type: 'response', result: { prompt: prompt } })); break; default: connection.send(JSON.stringify({ id: data.id, type: 'response', error: { code: -32601, message: `Method not found: ${data.method}` } })); } } // Roots request handler async function handleRootsRequest(connection, data) { switch (data.method) { case 'roots/list': const roots = await rootsHandler.listRoots(); connection.send(JSON.stringify({ id: data.id, type: 'response', result: { roots: roots } })); break; default: connection.send(JSON.stringify({ id: data.id, type: 'response', error: { code: -32601, message: `Method not found: ${data.method}` } })); } }