Skip to main content
Glama

Interactive Brokers MCP Server

by code-rabi
headless-auth.ts11.4 kB
import { chromium, Browser, Page } from 'playwright-core'; import { Logger } from './logger.js'; import { IBClient } from './ib-client.js'; import { BrowserInstaller } from './browser-installer.js'; export interface HeadlessAuthConfig { url: string; username: string; password: string; timeout?: number; ibClient?: IBClient; paperTrading?: boolean; } export interface HeadlessAuthResult { success: boolean; message: string; waitingFor2FA?: boolean; error?: string; } export class HeadlessAuthenticator { private browser: Browser | null = null; private page: Page | null = null; async authenticate(authConfig: HeadlessAuthConfig): Promise<HeadlessAuthResult> { try { Logger.info('🔐 Starting headless authentication...'); // Log the full auth config for debugging (excluding sensitive data) const logConfig = { ...authConfig }; if (logConfig.password) logConfig.password = '[REDACTED]'; Logger.info(`🔍 Authentication config: ${JSON.stringify(logConfig, null, 2)}`); // Use local browser - let Playwright handle everything Logger.info('🔧 Using local browser (Playwright default)'); this.browser = await BrowserInstaller.launchLocalBrowser(); this.page = await this.browser.newPage(); // Set a longer timeout for navigation - several minutes for full auth process this.page.setDefaultTimeout(authConfig.timeout || 300000); // 5 minutes default // Navigate to IB Gateway login page Logger.info(`🌐 Navigating to ${authConfig.url}...`); await this.page.goto(authConfig.url, { waitUntil: 'networkidle' }); // Wait for login form to be visible Logger.info('⏳ Waiting for login form...'); await this.page.waitForSelector('input[name="user"], input[id="user"], input[type="text"]', { timeout: 30000 }); // Find and fill username field const usernameSelector = 'input[name="user"], input[id="user"], input[type="text"]'; await this.page.fill(usernameSelector, authConfig.username); Logger.info('✅ Username filled'); // Find and fill password field const passwordSelector = 'input[name="password"], input[id="password"], input[type="password"]'; await this.page.fill(passwordSelector, authConfig.password); Logger.info('✅ Password filled'); // Handle paper trading toggle if specified - BEFORE submitting the form if (authConfig.paperTrading !== undefined) { try { Logger.info(`📊 Setting paper trading to ${authConfig.paperTrading ? 'enabled' : 'disabled'}...`); // Wait a moment for any dynamic content to load await this.page.waitForTimeout(1000); // Look for the specific paper trading checkbox const paperSwitchSelector = 'label[for="toggle1"]'; const element = await this.page.$(paperSwitchSelector); if (element) { const isChecked = await element.isChecked(); const shouldBeChecked = authConfig.paperTrading; if (isChecked !== shouldBeChecked) { Logger.info(`📊 Clicking paper trading checkbox to turn it ${shouldBeChecked ? 'ON' : 'OFF'}`); await element.click(); // Wait for any page updates after toggling await this.page.waitForTimeout(500); } else { Logger.info(`📊 Paper trading checkbox already in correct state: ${shouldBeChecked ? 'ON' : 'OFF'}`); } } else { Logger.warn('⚠️ Paper trading checkbox not found - may not be available for this account type'); } } catch (error) { Logger.warn('⚠️ Error while setting paper trading configuration:', error); // Continue with authentication - this shouldn't be a fatal error } } // Look for submit button and click it const submitSelector = 'input[type="submit"], button[type="submit"], button'; Logger.info('🔄 Submitting login form...'); await this.page.click(submitSelector); // Wait for the authentication process to complete using IB client polling Logger.info('⏳ Waiting for authentication to complete...'); const maxWaitTime = authConfig.timeout || 300000; // 5 minutes default const startTime = Date.now(); while (Date.now() - startTime < maxWaitTime) { await new Promise(resolve => setTimeout(resolve, 3000)); // Check every 3 seconds // Use IB Client to check authentication status if available if (authConfig.ibClient) { try { const isAuthenticated = await authConfig.ibClient.checkAuthenticationStatus(); if (isAuthenticated) { Logger.info('🎉 Authentication completed! IB Client confirmed authentication.'); await this.cleanup(); return { success: true, message: 'Headless authentication completed successfully. IB Client confirmed authentication.' }; } } catch (error) { Logger.debug('IB Client auth check failed, continuing...', error); } } // Fallback to page content checking if no IB client or client check fails try { const currentUrl = this.page.url(); const pageContent = await this.page.content(); // Check if we successfully authenticated by looking for the specific success message const authSuccess = pageContent.includes('Client login succeeds'); if (authSuccess) { Logger.info('🎉 Authentication completed! Found "Client login succeeds" message.'); await this.cleanup(); return { success: true, message: 'Headless authentication completed successfully. Client login succeeds message detected.' }; } // Check for potential 2FA or other intermediate states const has2FAIndicators = pageContent.includes('two-factor') || pageContent.includes('2FA') || pageContent.includes('authentication') || pageContent.includes('verification') || pageContent.includes('code') || currentUrl.includes('sso'); if (has2FAIndicators) { Logger.info('🔐 Two-factor authentication detected - continuing to wait...'); } else { Logger.info(`🔍 Still waiting for authentication completion... (${Math.round((Date.now() - startTime) / 1000)}s elapsed)`); } } catch (pageError) { Logger.warn('Page content check failed, continuing with IB client checks only...', pageError); // Continue with just IB client checks if page becomes unavailable } } // Timeout reached without seeing success message Logger.warn('⏰ Authentication timeout reached without seeing "Client login succeeds"'); return { success: false, message: 'Authentication timeout. Did not detect "Client login succeeds" message within the timeout period.', error: 'Authentication timeout - success message not detected' }; } catch (error) { Logger.error('❌ Headless authentication failed:', error); Logger.error('Environment info:', { platform: process.platform, arch: process.arch, nodeVersion: process.version }); await this.cleanup(); const errorMessage = error instanceof Error ? error.message : String(error); const errorDetails = error instanceof Error ? error.stack : 'No stack trace available'; return { success: false, message: 'Headless authentication failed', error: `${errorMessage}\n\nStack trace:\n${errorDetails}\n\nEnvironment: ${process.platform}-${process.arch}, Node: ${process.version}` }; } } async waitForAuthentication(maxWaitTime: number = 300000, ibClient?: IBClient): Promise<HeadlessAuthResult> { if (!this.page) { return { success: false, message: 'No active browser session', error: 'Browser session not found' }; } try { Logger.info('⏳ Waiting for 2FA completion...'); // Poll for authentication completion const startTime = Date.now(); while (Date.now() - startTime < maxWaitTime) { // Use IB Client to check authentication status if available if (ibClient) { try { const isAuthenticated = await ibClient.checkAuthenticationStatus(); if (isAuthenticated) { Logger.info('🎉 Authentication completed! IB Client confirmed authentication.'); await this.cleanup(); return { success: true, message: 'Authentication completed successfully. IB Client confirmed authentication.' }; } } catch (error) { Logger.debug('IB Client auth check failed during 2FA wait, continuing...', error); } } // Fallback to page content checking try { const currentUrl = this.page.url(); const pageContent = await this.page.content(); // Check if authentication is complete by looking for the specific success message const authSuccess = pageContent.includes('Client login succeeds'); if (authSuccess) { Logger.info('🎉 Authentication completed! Found "Client login succeeds" message.'); await this.cleanup(); return { success: true, message: 'Authentication completed successfully. Client login succeeds message detected.' }; } } catch (pageError) { Logger.warn('Page content check failed during 2FA wait, continuing with IB client checks only...', pageError); } // Wait before next check await new Promise(resolve => setTimeout(resolve, 5000)); } // Timeout reached Logger.warn('⏰ 2FA timeout reached'); await this.cleanup(); return { success: false, message: 'Two-factor authentication timeout. Please try again.', error: 'Authentication timeout' }; } catch (error) { Logger.error('❌ Error waiting for 2FA:', error); await this.cleanup(); const errorMessage = error instanceof Error ? error.message : String(error); const errorDetails = error instanceof Error ? error.stack : 'No stack trace available'; return { success: false, message: 'Error while waiting for two-factor authentication', error: `${errorMessage}\n\nStack trace:\n${errorDetails}` }; } } private async cleanup(): Promise<void> { try { if (this.page) { await this.page.close(); this.page = null; } if (this.browser) { await this.browser.close(); this.browser = null; } } catch (error) { Logger.error('⚠️ Error during cleanup:', error); } } // Cleanup method that can be called externally async close(): Promise<void> { await this.cleanup(); } }

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/code-rabi/interactive-brokers-mcp'

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