UNPKG

orchestry-mcp

Version:

Orchestry MCP Server for multi-session task management

236 lines (198 loc) • 6.38 kB
#!/usr/bin/env node /** * Orchestry MCP Server with integrated Web UI * This is the main entry point for npx orchestry-mcp */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import express from 'express'; import { createServer } from 'http'; import { Server as SocketIOServer } from 'socket.io'; import cors from 'cors'; import { spawn, exec } from 'child_process'; import path from 'path'; import { fileURLToPath } from 'url'; import net from 'net'; import { Database } from './database.js'; import { DatabaseManager } from './database-manager.js'; import { OrchestryTools } from './tools/index.js'; import { setupAPI } from './api/index.js'; const __dirname = path.dirname(fileURLToPath(import.meta.url)); // Check if port is available function isPortAvailable(port: number): Promise<boolean> { return new Promise((resolve) => { const server = net.createServer(); server.once('error', () => resolve(false)); server.once('listening', () => { server.close(); resolve(true); }); server.listen(port); }); } // Open browser function openBrowser(url: string) { const platform = process.platform; let command: string; if (platform === 'darwin') { command = `open ${url}`; } else if (platform === 'win32') { command = `start ${url}`; } else { command = `xdg-open ${url}`; } exec(command, (error) => { if (error) { console.error('Please open your browser and navigate to:', url); } }); } async function startOrchestry() { console.error('šŸš€ Starting Orchestry with Web UI...'); const API_PORT = parseInt(process.env.API_PORT || '7531'); const WEB_PORT = parseInt(process.env.WEB_PORT || '7530'); // Check ports const apiAvailable = await isPortAvailable(API_PORT); const webAvailable = await isPortAvailable(WEB_PORT); if (!apiAvailable) { console.error(`āŒ Port ${API_PORT} is already in use. Please stop the existing process.`); process.exit(1); } if (!webAvailable) { console.error(`āŒ Port ${WEB_PORT} is already in use. Please stop the existing process.`); process.exit(1); } // Initialize database for API (temporary - will use DatabaseManager later) const db = new Database(); await db.initialize(); // Initialize database manager for MCP tools const dbManager = new DatabaseManager(); // Create Express app const app = express(); app.use(cors()); app.use(express.json()); // Create HTTP server and Socket.IO const httpServer = createServer(app); const io = new SocketIOServer(httpServer, { cors: { origin: '*', methods: ['GET', 'POST'], }, }); // Initialize tools with database manager const tools = new OrchestryTools(dbManager, io); // Setup REST API (using regular database for now) setupAPI(app, db, io); // Setup WebSocket io.on('connection', (socket) => { console.error('Client connected:', socket.id); socket.on('subscribe', (projectId: string) => { socket.join(`project:${projectId}`); }); socket.on('unsubscribe', (projectId: string) => { socket.leave(`project:${projectId}`); }); socket.on('disconnect', () => { console.error('Client disconnected:', socket.id); }); }); // Start API server await new Promise<void>((resolve) => { httpServer.listen(API_PORT, () => { console.error(`āœ… API Server started at http://localhost:${API_PORT}`); resolve(); }); }); // Start Vite dev server for the web UI console.error('šŸ“¦ Starting Web UI...'); const viteProcess = spawn('npm', ['run', 'dev'], { cwd: path.join(__dirname, '../web'), stdio: ['ignore', 'pipe', 'pipe'], shell: true }); let viteStarted = false; viteProcess.stdout?.on('data', (data) => { const output = data.toString(); if (!viteStarted && output.includes('Local:')) { viteStarted = true; console.error(`āœ… Web UI started at http://localhost:${WEB_PORT}`); // Open browser after a short delay setTimeout(() => { openBrowser(`http://localhost:${WEB_PORT}`); }, 1000); } }); viteProcess.stderr?.on('data', (data) => { // Suppress Vite warnings/errors unless debugging if (process.env.DEBUG) { console.error(`[Vite] ${data}`); } }); // Wait a bit for Vite to start await new Promise(resolve => setTimeout(resolve, 3000)); // Initialize MCP Server const mcpServer = new Server( { name: 'orchestry', version: '1.0.0', }, { capabilities: { tools: {}, }, } ); // Register MCP tool handlers mcpServer.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: tools.getToolDefinitions(), })); mcpServer.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { const result = await tools.executeTool(name, args); return { content: [ { type: 'text', text: JSON.stringify(result, null, 2), }, ], }; } catch (error) { return { content: [ { type: 'text', text: `Error: ${error instanceof Error ? error.message : 'Unknown error'}`, }, ], }; } }); // Connect MCP server via stdio const transport = new StdioServerTransport(); await mcpServer.connect(transport); console.error('\nāœ… Orchestry is running:'); console.error(` - MCP Server: Active (stdio)`); console.error(` - Web UI: http://localhost:${WEB_PORT}`); console.error(` - API: http://localhost:${API_PORT}`); console.error('\nUse Ctrl+C to stop\n'); // Handle shutdown process.on('SIGINT', () => { console.error('\nšŸ›‘ Shutting down Orchestry...'); viteProcess.kill(); httpServer.close(); process.exit(0); }); process.on('SIGTERM', () => { viteProcess.kill(); httpServer.close(); process.exit(0); }); } // Start everything startOrchestry().catch((error) => { console.error('Failed to start Orchestry:', error); process.exit(1); });