adobe-xd-mcp
Version:
Model Context Protocol server for Adobe XD that enables AI-powered design capabilities
326 lines (295 loc) • 8.11 kB
JavaScript
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}`
}
}));
}
}