Skip to main content
Glama
account-discovery-test.tsβ€’25.5 kB
#!/usr/bin/env node import dotenv from 'dotenv'; import { UmbrellaAuth } from './auth.js'; import { UmbrellaApiClient } from './api-client.js'; import { getTestCredentialSets } from './auth-helper.js'; import { UMBRELLA_ENDPOINTS } from './types.js'; dotenv.config(); interface AccountInfo { accountId: string; accountName?: string; cloudType: 'AWS' | 'Azure' | 'GCP'; organizationId?: string; isDefault?: boolean; region?: string; } interface OrganizationInfo { organizationId: string; organizationName?: string; accounts: AccountInfo[]; cloudType?: string; } interface DiscoveredContext { organizations: OrganizationInfo[]; allAccounts: AccountInfo[]; awsAccounts: AccountInfo[]; azureAccounts: AccountInfo[]; gcpAccounts: AccountInfo[]; defaultAccountByCloud: Record<string, AccountInfo>; } interface EnhancedTestResult { endpoint: string; method: string; description: string; category: string; success: boolean; error?: string; data?: any; responseTime: number; accountContext?: AccountInfo; organizationContext?: OrganizationInfo; providedParams?: Record<string, any>; } class AccountDiscoveryTester { private auth: UmbrellaAuth; private apiClient: UmbrellaApiClient; private baseURL: string; private credentials: { username: string; password: string } | null = null; private discoveredContext: DiscoveredContext | null = null; constructor(baseURL: string) { this.baseURL = baseURL; this.auth = new UmbrellaAuth(baseURL); this.apiClient = new UmbrellaApiClient(baseURL); } async authenticate(credentials: { username: string; password: string }): Promise<boolean> { try { const authResult = await this.auth.authenticate(credentials); const authHeaders = this.auth.getAuthHeaders(); this.apiClient.setAuthToken(authHeaders); this.credentials = credentials; return true; } catch (error: any) { console.error(`❌ Authentication failed for ${credentials.username}: ${error.message}`); return false; } } async discoverAccountContext(): Promise<DiscoveredContext> { console.log('\nπŸ” DISCOVERING ACCOUNT & ORGANIZATION CONTEXT'); console.log('='.repeat(60)); const context: DiscoveredContext = { organizations: [], allAccounts: [], awsAccounts: [], azureAccounts: [], gcpAccounts: [], defaultAccountByCloud: {} }; // Try multiple discovery endpoints based on frontend analysis const discoveryEndpoints = [ // User management endpoints { path: '/user-management/accounts', description: 'User accounts' }, { path: '/user-management/organization', description: 'Organization info' }, { path: '/user-management/customers', description: 'Customer accounts' }, // Account-specific endpoints { path: '/users/linked-accounts', description: 'Linked accounts' }, { path: '/users/cost-centers', description: 'Cost centers' }, { path: '/admin/accounts', description: 'Admin accounts view' }, // Division/customer endpoints { path: '/divisions', description: 'Account divisions' }, { path: '/divisions/customers', description: 'Division customers' }, // Public/internal endpoints that might reveal account info { path: '/invoices/internal/accounts', description: 'Internal accounts' }, { path: '/usage/accounts', description: 'Usage accounts' }, ]; for (const endpoint of discoveryEndpoints) { try { console.log(` πŸ” Trying ${endpoint.path}...`); const response = await this.apiClient.makeRequest(endpoint.path, {}); if (response.success && response.data) { console.log(` βœ… ${endpoint.description}: Found data`); // Parse account information from response const accounts = this.parseAccountsFromResponse(response.data, endpoint.path); context.allAccounts.push(...accounts); console.log(` πŸ“Š Extracted ${accounts.length} accounts`); // Show sample account if available if (accounts.length > 0) { const sample = accounts[0]; console.log(` πŸ“‹ Sample: ${sample.accountId} (${sample.cloudType})`); } } else { console.log(` ⚠️ ${endpoint.description}: ${response.error || 'No data'}`); } } catch (error: any) { console.log(` ❌ ${endpoint.description}: ${error.message}`); } // Brief pause between requests await new Promise(resolve => setTimeout(resolve, 200)); } // Try to discover from working endpoints we know have account context await this.discoverFromWorkingEndpoints(context); // Process and categorize discovered accounts this.categorizeAccounts(context); // Try to determine default accounts per cloud this.determineDefaultAccounts(context); this.discoveredContext = context; return context; } private async discoverFromWorkingEndpoints(context: DiscoveredContext): Promise<void> { console.log('\n πŸ” Discovering from known working endpoints...'); // Try recommendations report - it often contains account/org info try { const response = await this.apiClient.makeRequest('/recommendations/report', {}); if (response.success && response.data) { console.log(' πŸ“Š Analyzing recommendations report for account context...'); // Look for account/organization IDs in the response const accounts = this.extractAccountsFromRecommendations(response.data); context.allAccounts.push(...accounts); if (accounts.length > 0) { console.log(` βœ… Found ${accounts.length} accounts from recommendations`); } } } catch (error) { console.log(' ⚠️ Could not analyze recommendations for accounts'); } // Try service names - sometimes contains account-specific data try { const response = await this.apiClient.makeRequest('/invoices/service-names/distinct', { limit: 5 }); if (response.success) { console.log(' βœ… Service names endpoint confirmed working'); } } catch (error) { console.log(' ⚠️ Service names endpoint failed'); } } private parseAccountsFromResponse(data: any, endpointPath: string): AccountInfo[] { const accounts: AccountInfo[] = []; try { // Handle different response structures based on endpoint if (Array.isArray(data)) { data.forEach((item: any) => { const account = this.extractAccountFromItem(item, endpointPath); if (account) accounts.push(account); }); } else if (typeof data === 'object' && data !== null) { // Single account object const account = this.extractAccountFromItem(data, endpointPath); if (account) accounts.push(account); // Check for nested account arrays Object.keys(data).forEach(key => { if (Array.isArray(data[key]) && key.toLowerCase().includes('account')) { data[key].forEach((item: any) => { const account = this.extractAccountFromItem(item, endpointPath); if (account) accounts.push(account); }); } }); } } catch (error) { console.log(` ⚠️ Error parsing accounts from ${endpointPath}: ${error}`); } return accounts; } private extractAccountFromItem(item: any, source: string): AccountInfo | null { if (!item || typeof item !== 'object') return null; // Common account ID field patterns const accountIdFields = ['accountId', 'account_id', 'awsAccountId', 'aws_account_id', 'id']; const accountNameFields = ['accountName', 'account_name', 'name', 'displayName']; const cloudTypeFields = ['cloudType', 'cloud_type', 'provider', 'cloudProvider']; const orgIdFields = ['organizationId', 'organization_id', 'orgId', 'org_id']; let accountId = ''; let accountName = ''; let cloudType: 'AWS' | 'Azure' | 'GCP' = 'AWS'; // Default to AWS let organizationId = ''; // Extract account ID for (const field of accountIdFields) { if (item[field]) { accountId = String(item[field]); break; } } // Extract account name for (const field of accountNameFields) { if (item[field]) { accountName = String(item[field]); break; } } // Extract cloud type for (const field of cloudTypeFields) { if (item[field]) { const type = String(item[field]).toUpperCase(); if (type.includes('AWS') || type.includes('AMAZON')) cloudType = 'AWS'; else if (type.includes('AZURE') || type.includes('MICROSOFT')) cloudType = 'Azure'; else if (type.includes('GCP') || type.includes('GOOGLE')) cloudType = 'GCP'; break; } } // Extract organization ID for (const field of orgIdFields) { if (item[field]) { organizationId = String(item[field]); break; } } // Must have at least an account ID to be valid if (!accountId) return null; return { accountId, accountName: accountName || undefined, cloudType, organizationId: organizationId || undefined, isDefault: false // Will be determined later }; } private extractAccountsFromRecommendations(data: any): AccountInfo[] { const accounts: AccountInfo[] = []; try { if (Array.isArray(data)) { data.forEach(report => { if (report.accountId) { accounts.push({ accountId: report.accountId, accountName: report.accountName || undefined, cloudType: 'AWS', // Recommendations are typically AWS-focused organizationId: report.organizationId || undefined, isDefault: true // Recommendations usually show primary accounts }); } // Check for linked accounts if (report.linkedAccountIds && Array.isArray(report.linkedAccountIds)) { report.linkedAccountIds.forEach((accountId: string) => { accounts.push({ accountId, cloudType: 'AWS', organizationId: report.organizationId || undefined, isDefault: false }); }); } }); } } catch (error) { console.log(` ⚠️ Error extracting accounts from recommendations: ${error}`); } return accounts; } private categorizeAccounts(context: DiscoveredContext): void { // Remove duplicates based on accountId const uniqueAccounts = context.allAccounts.reduce((acc: AccountInfo[], current) => { const exists = acc.find(account => account.accountId === current.accountId); if (!exists) { acc.push(current); } return acc; }, []); context.allAccounts = uniqueAccounts; // Categorize by cloud type context.awsAccounts = context.allAccounts.filter(acc => acc.cloudType === 'AWS'); context.azureAccounts = context.allAccounts.filter(acc => acc.cloudType === 'Azure'); context.gcpAccounts = context.allAccounts.filter(acc => acc.cloudType === 'GCP'); // Group by organization const orgMap = new Map<string, OrganizationInfo>(); context.allAccounts.forEach(account => { if (account.organizationId) { if (!orgMap.has(account.organizationId)) { orgMap.set(account.organizationId, { organizationId: account.organizationId, accounts: [] }); } orgMap.get(account.organizationId)!.accounts.push(account); } }); context.organizations = Array.from(orgMap.values()); } private determineDefaultAccounts(context: DiscoveredContext): void { // AWS default: First account marked as default, or first account if (context.awsAccounts.length > 0) { const defaultAws = context.awsAccounts.find(acc => acc.isDefault) || context.awsAccounts[0]; context.defaultAccountByCloud['AWS'] = defaultAws; } // Azure default: Similar logic if (context.azureAccounts.length > 0) { const defaultAzure = context.azureAccounts.find(acc => acc.isDefault) || context.azureAccounts[0]; context.defaultAccountByCloud['Azure'] = defaultAzure; } // GCP default: Similar logic if (context.gcpAccounts.length > 0) { const defaultGcp = context.gcpAccounts.find(acc => acc.isDefault) || context.gcpAccounts[0]; context.defaultAccountByCloud['GCP'] = defaultGcp; } } private printDiscoveryResults(context: DiscoveredContext): void { console.log('\nπŸ“Š ACCOUNT DISCOVERY RESULTS'); console.log('='.repeat(60)); console.log(`\n🌐 TOTAL DISCOVERED ACCOUNTS: ${context.allAccounts.length}`); console.log(` ☁️ AWS: ${context.awsAccounts.length}`); console.log(` ☁️ Azure: ${context.azureAccounts.length}`); console.log(` ☁️ GCP: ${context.gcpAccounts.length}`); if (context.organizations.length > 0) { console.log(`\n🏒 ORGANIZATIONS: ${context.organizations.length}`); context.organizations.forEach(org => { console.log(` πŸ“‹ ${org.organizationId}: ${org.accounts.length} accounts`); }); } console.log('\n🎯 DEFAULT ACCOUNTS PER CLOUD:'); Object.entries(context.defaultAccountByCloud).forEach(([cloud, account]) => { console.log(` ${cloud}: ${account.accountId} ${account.accountName ? `(${account.accountName})` : ''}`); }); if (context.allAccounts.length > 0) { console.log('\nπŸ“‹ ALL DISCOVERED ACCOUNTS:'); context.allAccounts.forEach(account => { const name = account.accountName ? ` (${account.accountName})` : ''; const org = account.organizationId ? ` [Org: ${account.organizationId}]` : ''; const def = account.isDefault ? ' [DEFAULT]' : ''; console.log(` ${account.cloudType}: ${account.accountId}${name}${org}${def}`); }); } } private getEnhancedTestParameters(endpoint: any, accountContext?: AccountInfo, orgContext?: OrganizationInfo): Record<string, any> { const params: Record<string, any> = {}; // Add account context if available if (accountContext) { params.accountId = accountContext.accountId; if (accountContext.organizationId) { params.organizationId = accountContext.organizationId; } } // Add organization context if available if (orgContext) { params.organizationId = orgContext.organizationId; } // Add common date parameters for cost-related endpoints if (endpoint.path.includes('cost') || endpoint.path.includes('usage') || endpoint.path.includes('invoice')) { params.startDate = '2024-01-01'; params.endDate = '2024-01-31'; } // Add pagination for list endpoints if (endpoint.path.includes('list') || endpoint.description.toLowerCase().includes('list')) { params.limit = 10; params.offset = 0; } // Add endpoint-specific parameters based on our analysis switch (true) { case endpoint.path.includes('/caui'): Object.assign(params, { costType: ['unblended'], groupBy: ['service'], periodGranLevel: 'daily' }); break; case endpoint.path.includes('/service-names/distinct'): params.limit = 10; break; case endpoint.path.includes('/resource-explorer/distinct'): params.search = 'aws'; // Provide required search parameter break; case endpoint.path.includes('/kubernetes'): if (accountContext) { params.clusterId = 'default'; params.namespace = 'default'; } break; } return params; } async testEndpointWithContext(endpoint: any, accountContext?: AccountInfo, orgContext?: OrganizationInfo): Promise<EnhancedTestResult> { const startTime = Date.now(); const testParams = this.getEnhancedTestParameters(endpoint, accountContext, orgContext); try { const contextDesc = accountContext ? `[${accountContext.cloudType}:${accountContext.accountId}]` : '[No Context]'; console.log(` πŸ” ${endpoint.method} ${endpoint.path} ${contextDesc}`); console.log(` πŸ“ ${endpoint.description}`); if (Object.keys(testParams).length > 0) { console.log(` πŸ”§ Params: ${Object.keys(testParams).join(', ')}`); } const response = await this.apiClient.makeRequest(endpoint.path, testParams); const responseTime = Date.now() - startTime; if (response.success) { let dataInfo = 'No data'; if (response.data) { if (Array.isArray(response.data)) { dataInfo = `${response.data.length} array items`; } else if (typeof response.data === 'object') { dataInfo = `object with ${Object.keys(response.data).length} keys`; } } console.log(` βœ… SUCCESS: ${dataInfo} (${responseTime}ms)`); return { endpoint: endpoint.path, method: endpoint.method, description: endpoint.description, category: endpoint.category, success: true, data: response.data, responseTime, accountContext, organizationContext: orgContext, providedParams: testParams }; } else { console.log(` ❌ FAILED: ${response.error} (${responseTime}ms)`); return { endpoint: endpoint.path, method: endpoint.method, description: endpoint.description, category: endpoint.category, success: false, error: response.error, responseTime, accountContext, organizationContext: orgContext, providedParams: testParams }; } } catch (error: any) { const responseTime = Date.now() - startTime; console.log(` πŸ’₯ ERROR: ${error.message} (${responseTime}ms)`); return { endpoint: endpoint.path, method: endpoint.method, description: endpoint.description, category: endpoint.category, success: false, error: error.message, responseTime, accountContext, organizationContext: orgContext, providedParams: testParams }; } } async runEnhancedTest(credentials: { username: string; password: string }): Promise<EnhancedTestResult[]> { console.log(`\n${'='.repeat(100)}`); console.log(`πŸ§ͺ ENHANCED API TEST WITH ACCOUNT DISCOVERY: ${credentials.username}`); console.log(`πŸ“‘ Base URL: ${this.baseURL}`); console.log(`${'='.repeat(100)}`); // Authenticate console.log('\nπŸ” AUTHENTICATION'); const authSuccess = await this.authenticate(credentials); if (!authSuccess) { throw new Error('Authentication failed'); } console.log(`βœ… Authentication successful for ${credentials.username}`); // Discover account context const context = await this.discoverAccountContext(); this.printDiscoveryResults(context); // Test endpoints with discovered context const allResults: EnhancedTestResult[] = []; console.log(`\nπŸ§ͺ TESTING ENDPOINTS WITH ACCOUNT CONTEXT`); console.log(`πŸ“Š Total endpoints: ${UMBRELLA_ENDPOINTS.length}`); console.log('='.repeat(100)); // Test without context first (baseline) console.log(`\nπŸ“‹ Phase 1: Testing without account context`); for (let i = 0; i < Math.min(5, UMBRELLA_ENDPOINTS.length); i++) { const endpoint = UMBRELLA_ENDPOINTS[i]; console.log(`\n[${i + 1}/5] ${endpoint.category} (No Context)`); const result = await this.testEndpointWithContext(endpoint); allResults.push(result); await new Promise(resolve => setTimeout(resolve, 200)); } // Test with account context for each cloud if (Object.keys(context.defaultAccountByCloud).length > 0) { console.log(`\nπŸ“‹ Phase 2: Testing with default accounts per cloud`); for (const [cloudType, defaultAccount] of Object.entries(context.defaultAccountByCloud)) { console.log(`\n☁️ Testing with ${cloudType} context: ${defaultAccount.accountId}`); // Test key endpoints with this account context const keyEndpoints = UMBRELLA_ENDPOINTS.filter(ep => ep.path.includes('caui') || ep.path.includes('cost-and-usage') || ep.path.includes('budget') || ep.path.includes('kubernetes') || ep.description.toLowerCase().includes('account') ); for (let i = 0; i < keyEndpoints.length; i++) { const endpoint = keyEndpoints[i]; console.log(`\n[${cloudType}] ${endpoint.category}`); const result = await this.testEndpointWithContext(endpoint, defaultAccount); allResults.push(result); await new Promise(resolve => setTimeout(resolve, 200)); } } } return allResults; } printEnhancedResults(results: EnhancedTestResult[]): void { console.log(`\n${'='.repeat(100)}`); console.log(`πŸ“ˆ ENHANCED TEST RESULTS WITH ACCOUNT CONTEXT`); console.log(`${'='.repeat(100)}`); const successCount = results.filter(r => r.success).length; const failureCount = results.length - successCount; const successRate = (successCount / results.length) * 100; console.log(`\n🎯 OVERALL SUMMARY:`); console.log(` βœ… Successful: ${successCount}/${results.length} (${successRate.toFixed(1)}%)`); console.log(` ❌ Failed: ${failureCount}/${results.length}`); console.log(` ⏱️ Avg Response Time: ${Math.round(results.reduce((sum, r) => sum + r.responseTime, 0) / results.length)}ms`); // Results with account context vs without const withContext = results.filter(r => r.accountContext); const withoutContext = results.filter(r => !r.accountContext); if (withContext.length > 0 && withoutContext.length > 0) { const contextSuccessRate = (withContext.filter(r => r.success).length / withContext.length) * 100; const noContextSuccessRate = (withoutContext.filter(r => r.success).length / withoutContext.length) * 100; console.log(`\nπŸ“Š CONTEXT IMPACT:`); console.log(` 🎯 With Account Context: ${contextSuccessRate.toFixed(1)}% success`); console.log(` 🎯 Without Context: ${noContextSuccessRate.toFixed(1)}% success`); console.log(` πŸ“ˆ Improvement: ${(contextSuccessRate - noContextSuccessRate).toFixed(1)}%`); } // Working endpoints by cloud context if (withContext.length > 0) { console.log(`\n☁️ SUCCESS BY CLOUD CONTEXT:`); const byCloud = withContext.reduce((acc: any, result) => { if (result.accountContext) { const cloud = result.accountContext.cloudType; if (!acc[cloud]) acc[cloud] = { success: 0, total: 0 }; acc[cloud].total++; if (result.success) acc[cloud].success++; } return acc; }, {}); Object.entries(byCloud).forEach(([cloud, stats]: [string, any]) => { const rate = (stats.success / stats.total) * 100; console.log(` ${cloud}: ${stats.success}/${stats.total} (${rate.toFixed(1)}%)`); }); } // Show successful endpoints with context const successfulWithContext = results.filter(r => r.success && r.accountContext); if (successfulWithContext.length > 0) { console.log(`\nβœ… ENDPOINTS WORKING WITH ACCOUNT CONTEXT:`); successfulWithContext.forEach(result => { const cloud = result.accountContext!.cloudType; const accountId = result.accountContext!.accountId; console.log(` ${result.method} ${result.endpoint} [${cloud}:${accountId}]`); }); } } } async function main() { console.log('πŸ” UMBRELLA MCP API TESTING WITH ACCOUNT DISCOVERY'); console.log('=================================================='); console.log(`πŸ“… Test Time: ${new Date().toISOString()}`); const baseURL = process.env.UMBRELLA_API_BASE_URL || 'https://api.umbrellacost.io/api/v1'; const credentialSets = getTestCredentialSets(); if (!credentialSets) { console.error('❌ No test credentials available'); process.exit(1); } for (let i = 0; i < credentialSets.length; i++) { const creds = credentialSets[i]; const credName = `Account ${i + 1} (${creds.username})`; try { const tester = new AccountDiscoveryTester(baseURL); const results = await tester.runEnhancedTest(creds); tester.printEnhancedResults(results); } catch (error: any) { console.error(`❌ Enhanced test failed for ${credName}: ${error.message}`); } // Pause between accounts if (i < credentialSets.length - 1) { console.log('\n⏳ Pausing before testing next account...'); await new Promise(resolve => setTimeout(resolve, 3000)); } } console.log(`\nπŸŽ‰ ENHANCED TESTING WITH ACCOUNT DISCOVERY COMPLETED!`); } // Run main function if this file is executed directly if (import.meta.url === `file://${process.argv[1]}`) { main().catch(console.error); } export { AccountDiscoveryTester, AccountInfo, OrganizationInfo, DiscoveredContext, EnhancedTestResult };

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/daviddraiumbrella/invoice-monitoring'

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