import { UmbrellaMcpServer } from './server.js';
import { ConfigLoader } from './config/config-loader.js';
import { StdioTransport } from './transports/stdio-transport.js';
import { HttpsTransport } from './transports/https-transport.js';
import { CloudflareTunnelManager } from './tunnel/cloudflare-manager.js';
/**
* Enhanced MCP Server with dual transport support
* Supports both stdio (for backward compatibility) and HTTPS with OAuth
*/
export class DualTransportMcpServer {
private config = ConfigLoader.getInstance();
private mcpServer: UmbrellaMcpServer;
private tunnelManager?: CloudflareTunnelManager;
constructor() {
const umbrellaUrl = this.config.getConfig().umbrella.baseUrl;
const frontendUrl = this.config.getConfig().umbrella.frontendBaseUrl;
const transportMode = this.config.getTransportMode();
// Pass transport mode to server so it knows whether to expose auth tools
this.mcpServer = new UmbrellaMcpServer(umbrellaUrl, frontendUrl);
// Log transport mode for debugging
if (transportMode === 'https') {
console.log('[MAIN] HTTPS mode - authentication via OAuth Bearer tokens only');
}
}
async start(): Promise<void> {
const transportMode = this.config.getTransportMode();
const serverConfig = this.config.getConfig().server;
console.log(`[MAIN] Starting MCP server in ${transportMode.toUpperCase()} mode`);
console.log(`[MAIN] Debug mode: ${serverConfig.debug ? 'ENABLED' : 'DISABLED'}`);
console.log(`[MAIN] Production mode: ${serverConfig.production ? 'YES' : 'NO'}`);
if (transportMode === 'stdio') {
// Traditional stdio mode
const stdioTransport = new StdioTransport(this.mcpServer);
await stdioTransport.start();
} else if (transportMode === 'https') {
// HTTPS mode with OAuth
// Start Cloudflare tunnel if enabled
if (this.config.getConfig().cloudflare.enabled) {
this.tunnelManager = new CloudflareTunnelManager();
try {
console.log('[MAIN] Starting Cloudflare tunnel...');
const tunnelUrl = await this.tunnelManager.startTunnel();
console.log(`[MAIN] Tunnel started successfully: ${tunnelUrl}`);
// Give the tunnel a moment to stabilize
await new Promise(resolve => setTimeout(resolve, 2000));
} catch (error: any) {
console.error(`[MAIN] Failed to start tunnel: ${error.message}`);
console.error('[MAIN] Please ensure cloudflared is installed and accessible');
process.exit(1);
}
}
// Start HTTPS server
const httpsTransport = new HttpsTransport(this.mcpServer);
await httpsTransport.start();
// Print connection instructions
this.printConnectionInstructions();
} else {
throw new Error(`Unknown transport mode: ${transportMode}`);
}
// Handle graceful shutdown
this.setupShutdownHandlers();
}
private printConnectionInstructions(): void {
const config = this.config.getConfig();
console.log('\n' + '='.repeat(60));
console.log('MCP SERVER READY');
console.log('='.repeat(60));
if (config.cloudflare.currentTunnelUrl) {
console.log('\n📡 TUNNEL URL:', config.cloudflare.currentTunnelUrl);
console.log('🔐 MCP ENDPOINT:', `${config.cloudflare.currentTunnelUrl}/mcp`);
} else if (config.server.production) {
console.log('\n🔐 MCP ENDPOINT:', `${config.auth.issuer}mcp`);
}
console.log('\n📋 CONFIGURATION:');
console.log(` - Transport: ${config.server.transport}`);
console.log(` - Token Expiration: ${config.auth.tokenExpiration}s (${config.auth.tokenExpiration / 3600} hours)`);
console.log(` - Debug Mode: ${config.server.debug ? 'ENABLED' : 'DISABLED'}`);
console.log('\n🔧 TO CONNECT FROM CLAUDE DESKTOP:');
console.log(' 1. Open Claude Desktop settings');
console.log(' 2. Add a custom MCP server');
console.log(' 3. Use the MCP endpoint URL shown above');
console.log(' 4. Authenticate when prompted');
console.log('\n' + '='.repeat(60) + '\n');
}
private setupShutdownHandlers(): void {
const cleanup = async () => {
console.log('\n[MAIN] Shutting down...');
// Stop tunnel if running
if (this.tunnelManager?.isRunning()) {
console.log('[MAIN] Stopping Cloudflare tunnel...');
this.tunnelManager.stopTunnel();
}
console.log('[MAIN] Shutdown complete');
process.exit(0);
};
process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);
}
/**
* Get the MCP server instance (for testing)
*/
getMcpServer(): UmbrellaMcpServer {
return this.mcpServer;
}
}
// Auto-start if run directly
if (import.meta.url === `file://${process.argv[1]}`) {
const server = new DualTransportMcpServer();
server.start().catch((error) => {
console.error('[MAIN] Fatal error:', error);
process.exit(1);
});
}