diff --git a/src/config.ts b/src/config.ts index 50ee6ba..ea74c90 100644 --- a/src/config.ts +++ b/src/config.ts @@ -1,21 +1,71 @@ import { config } from 'dotenv'; -config(); +import { join } from 'path'; +import { existsSync } from 'fs'; + +const envPath = join(process.cwd(), '.env'); +if (!existsSync(envPath)) { + throw new Error( + 'No .env file found. Please create one based on .env.example' + ); +} + +const result = config(); + +if (result.error) { + throw new Error(`Error loading .env file: ${result.error.message}`); +} + +const HEX_KEYS_REGEX = /^(?:[0-9a-fA-F]{64})$/; + +function requireEnvVar(name: string): string { + const value = process.env[name]; + if (!value) { + throw new Error( + `Missing required environment variable: ${name}. Check your .env file` + ); + } + return value; +} + +function getEnvVar(name: string, defaultValue: string): string { + return process.env[name] || defaultValue; +} export const CONFIG = { nostr: { - privateKey: process.env.PRIVATE_KEY!, - relayUrls: process.env.RELAY_URLS!.split(',').map((url) => url.trim()), + privateKey: requireEnvVar('PRIVATE_KEY'), + relayUrls: requireEnvVar('RELAY_URLS') + .split(',') + .map((url) => url.trim()), }, mcp: { - // Service info - name: process.env.MCP_SERVICE_NAME || 'DVM MCP Bridge', - about: - process.env.MCP_SERVICE_ABOUT || - 'MCP-enabled DVM providing AI and computational tools', - // Client connection info - clientName: process.env.MCP_CLIENT_NAME!, - clientVersion: process.env.MCP_CLIENT_VERSION!, - serverCommand: process.env.MCP_SERVER_COMMAND!, - serverArgs: process.env.MCP_SERVER_ARGS!.split(','), + name: getEnvVar('MCP_SERVICE_NAME', 'DVM MCP Bridge'), + about: getEnvVar( + 'MCP_SERVICE_ABOUT', + 'MCP-enabled DVM providing AI and computational tools' + ), + clientName: requireEnvVar('MCP_CLIENT_NAME'), + clientVersion: requireEnvVar('MCP_CLIENT_VERSION'), + serverCommand: requireEnvVar('MCP_SERVER_COMMAND'), + serverArgs: requireEnvVar('MCP_SERVER_ARGS').split(','), }, }; + +if (!HEX_KEYS_REGEX.test(CONFIG.nostr.privateKey)) { + throw new Error('PRIVATE_KEY must be a 32-byte hex string'); +} + +CONFIG.nostr.relayUrls.forEach((url) => { + try { + new URL(url); + if (!url.startsWith('ws://') && !url.startsWith('wss://')) { + throw new Error(`Relay URL must start with ws:// or wss://: ${url}`); + } + } catch (error) { + throw new Error(`Invalid relay URL: ${url}`); + } +}); + +if (CONFIG.nostr.relayUrls.length === 0) { + throw new Error('At least one relay URL must be provided in RELAY_URLS'); +} diff --git a/src/dvm-bridge.ts b/src/dvm-bridge.ts index 37125cb..6504e1e 100644 --- a/src/dvm-bridge.ts +++ b/src/dvm-bridge.ts @@ -9,22 +9,58 @@ export class DVMBridge { private mcpClient: MCPClientHandler; private nostrAnnouncer: NostrAnnouncer; private relayHandler: RelayHandler; + private isRunning: boolean = false; constructor() { + console.log('Initializing DVM Bridge...'); this.mcpClient = new MCPClientHandler(); this.nostrAnnouncer = new NostrAnnouncer(); this.relayHandler = new RelayHandler(CONFIG.nostr.relayUrls); } async start() { - await this.mcpClient.connect(); + if (this.isRunning) { + console.log('Bridge is already running'); + return; + } - const tools = await this.mcpClient.listTools(); - console.log('Available MCP tools:', tools); + console.log('Starting DVM Bridge...'); + try { + console.log('Connecting to MCP server...'); + await this.mcpClient.connect(); - await this.nostrAnnouncer.announceService(); + const tools = await this.mcpClient.listTools(); + console.log('Available MCP tools:', tools); - this.relayHandler.subscribeToRequests(this.handleRequest.bind(this)); + console.log('Announcing service to Nostr network...'); + await this.nostrAnnouncer.announceService(); + + console.log('Setting up request handlers...'); + this.relayHandler.subscribeToRequests(this.handleRequest.bind(this)); + + this.isRunning = true; + console.log('DVM Bridge is now running and ready to handle requests'); + } catch (error) { + console.error('Failed to start DVM Bridge:', error); + throw error; + } + } + + async stop() { + if (!this.isRunning) { + return; + } + + console.log('Stopping DVM Bridge...'); + try { + await this.mcpClient.disconnect(); + this.relayHandler.cleanup(); + this.isRunning = false; + console.log('DVM Bridge stopped successfully'); + } catch (error) { + console.error('Error stopping DVM Bridge:', error); + throw error; + } } private async handleRequest(event: Event) { @@ -100,9 +136,32 @@ export class DVMBridge { console.error('Error handling request:', error); } } +} - async stop() { - await this.mcpClient.disconnect(); - this.relayHandler.cleanup(); +if (import.meta.main) { + console.log('Starting DVM-MCP Bridge service...'); + const bridge = new DVMBridge(); + + const shutdown = async () => { + console.log('Shutting down...'); + try { + await bridge.stop(); + process.exit(0); + } catch (error) { + console.error('Error during shutdown:', error); + process.exit(1); + } + }; + + // Handle termination signals + process.on('SIGINT', shutdown); + process.on('SIGTERM', shutdown); + + // Start the bridge + try { + await bridge.start(); + } catch (error) { + console.error('Failed to start service:', error); + process.exit(1); } }