orchestry-mcp
Version:
Orchestry MCP Server for multi-session task management
236 lines (198 loc) ⢠6.38 kB
text/typescript
/**
* 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);
});