Skip to main content
Glama
liratanak

Tonle OpenProject MCP Server

by liratanak
index.ts5.45 kB
#!/usr/bin/env bun /** * OpenProject MCP Server (STDIO Transport) * A Model Context Protocol server for OpenProject integration * * LOGS LOCATION: ~/Tonle/logs/ * View logs with: tail -f ~/Tonle/logs/$(date +%Y-%m-%d)-stdio-server.log */ // Import logger first to ensure logging is available immediately import logger, { DEFAULT_LOGS_DIR } from './src/logger.ts'; const caller = 'stdio-server'; // Setup global error handlers IMMEDIATELY before any async operations // This ensures we capture errors even if imports or initialization fails process.on('uncaughtException', (error) => { const message = `Uncaught exception: ${error.message}`; console.error(`[Tonle MCP] ${message}`); console.error(error.stack); try { logger.error(caller, 'UNCAUGHT_EXCEPTION', message, { error: error.message, stack: error.stack, name: error.name, }); } catch { // Logger might not be available } process.exit(1); }); process.on('unhandledRejection', (reason, promise) => { const message = `Unhandled rejection: ${reason}`; console.error(`[Tonle MCP] ${message}`); try { logger.error(caller, 'UNHANDLED_REJECTION', message, { reason: String(reason), stack: reason instanceof Error ? reason.stack : undefined, }); } catch { // Logger might not be available } process.exit(1); }); // Log process start immediately console.error(`[Tonle MCP] Process starting. PID: ${process.pid}`); console.error(`[Tonle MCP] Logs directory: ${DEFAULT_LOGS_DIR}`); console.error(`[Tonle MCP] View logs: tail -f ${DEFAULT_LOGS_DIR}/$(date +%Y-%m-%d)-stdio-server.log`); logger.logServerEvent(caller, 'Process starting', { pid: process.pid, nodeVersion: process.version, platform: process.platform, cwd: process.cwd(), logsDir: DEFAULT_LOGS_DIR, env: { OPENPROJECT_URL: process.env.OPENPROJECT_URL ? '[SET]' : '[NOT SET]', OPENPROJECT_API_KEY: process.env.OPENPROJECT_API_KEY ? '[SET]' : '[NOT SET]', }, }); // Now import other dependencies import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { setupMcpServer } from './src/server-setup.ts'; async function main() { try { logger.logServerEvent(caller, 'Server starting', { transport: 'stdio', version: '1.0.0', }); // Validate required environment variables if (!process.env.OPENPROJECT_URL) { throw new Error('OPENPROJECT_URL environment variable is not set'); } if (!process.env.OPENPROJECT_API_KEY) { throw new Error('OPENPROJECT_API_KEY environment variable is not set'); } logger.logServerEvent(caller, 'Environment validated', { openProjectUrl: process.env.OPENPROJECT_URL, }); // Setup the MCP server with all tools const { server, initClient } = setupMcpServer({ name: 'openproject-mcp', version: '1.0.0', }); // Connect to MCP transport FIRST - this is critical! // Claude's MCP client expects the server to be ready immediately. // If we delay (e.g., by testing OpenProject first), the client may timeout. const transport = new StdioServerTransport(); // Create a promise that resolves when the transport closes // IMPORTANT: Set up event handlers BEFORE connecting to avoid race conditions const transportClosed = new Promise<void>((resolve) => { transport.onclose = () => { console.error('[Tonle MCP] Transport closed'); logger.logServerEvent(caller, 'Transport closed', { transport: 'stdio', }); resolve(); }; transport.onerror = (error) => { console.error(`[Tonle MCP] Transport error: ${error.message}`); logger.logServerEvent(caller, 'Transport error', { error: error.message, }); }; }); // Connect the server to the transport immediately await server.connect(transport); console.error('[Tonle MCP] OpenProject MCP Server running on stdio'); logger.logServerEvent(caller, 'Server running', { transport: 'stdio', status: 'ready', }); // Initialize the OpenProject client (lazy - will be used by tools) // Don't test connection on startup to avoid interfering with MCP handshake initClient().catch(() => { // Silently ignore - tools will handle connection errors individually }); // Keep the process alive by waiting for the transport to close // This is critical - without this, the process exits immediately after connect() await transportClosed; // Graceful shutdown console.error('[Tonle MCP] Server shutting down'); logger.logServerEvent(caller, 'Server shutdown', { transport: 'stdio', }); } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); console.error(`[Tonle MCP] Failed to start MCP server: ${errorMessage}`); logger.logServerEvent(caller, 'Server startup failed', { error: errorMessage, stack: error instanceof Error ? error.stack : undefined, }); process.exit(1); } } // Handle graceful shutdown process.on('SIGINT', () => { console.error('[Tonle MCP] Received SIGINT, shutting down...'); logger.logServerEvent(caller, 'Received SIGINT'); process.exit(0); }); process.on('SIGTERM', () => { console.error('[Tonle MCP] Received SIGTERM, shutting down...'); logger.logServerEvent(caller, 'Received SIGTERM'); process.exit(0); }); main();

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/liratanak/openproject-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server