UNPKG

facebook-ads-mcp-server

Version:

Universal Facebook Ads Server - Supports MCP, OpenAI Function Calling, and Gemini Function Calling

310 lines (260 loc) 9.16 kB
import axios from 'axios'; import dotenv from 'dotenv'; dotenv.config(); /** * AppsFlyer API Client for multi-client agency setup * Handles authentication and API requests for multiple client accounts */ export class AppsFlyerAPIClient { constructor() { this.baseURL = 'https://hq1.appsflyer.com/api'; this.clients = this.loadClientConfigs(); this.currentClientId = null; } /** * Load client configurations from environment variables * Expected format: APPSFLYER_CLIENT_A_TOKEN, APPSFLYER_CLIENT_A_NAME, etc. */ loadClientConfigs() { const clients = {}; const envVars = process.env; // Find all client configurations const clientTokens = Object.keys(envVars).filter(key => key.startsWith('APPSFLYER_') && key.endsWith('_TOKEN') ); for (const tokenKey of clientTokens) { // Extract client ID from env var name (e.g., APPSFLYER_CLIENT_A_TOKEN -> CLIENT_A) const clientId = tokenKey.replace('APPSFLYER_', '').replace('_TOKEN', ''); const nameKey = `APPSFLYER_${clientId}_NAME`; const appsKey = `APPSFLYER_${clientId}_APPS`; clients[clientId.toLowerCase()] = { id: clientId.toLowerCase(), name: envVars[nameKey] || `Client ${clientId}`, token: envVars[tokenKey], apps: envVars[appsKey] ? envVars[appsKey].split(',').map(app => app.trim()) : [] }; } return clients; } /** * List all configured client accounts */ getClients() { return Object.keys(this.clients).map(clientId => ({ id: clientId, name: this.clients[clientId].name, appCount: this.clients[clientId].apps.length, hasToken: !!this.clients[clientId].token })); } /** * Set the current client for subsequent API calls */ setCurrentClient(clientId) { if (!this.clients[clientId]) { throw new Error(`Client '${clientId}' not found. Available clients: ${Object.keys(this.clients).join(', ')}`); } if (!this.clients[clientId].token) { throw new Error(`No API token configured for client '${clientId}'. Please set APPSFLYER_${clientId.toUpperCase()}_TOKEN`); } this.currentClientId = clientId; return this.clients[clientId]; } /** * Get current client configuration */ getCurrentClient() { if (!this.currentClientId) { throw new Error('No client selected. Call setCurrentClient() first.'); } return this.clients[this.currentClientId]; } /** * Make authenticated API request */ async makeRequest(endpoint, method = 'GET', params = {}, data = null) { const client = this.getCurrentClient(); const config = { method, url: `${this.baseURL}${endpoint}`, headers: { 'Authorization': `Bearer ${client.token}`, 'Accept': 'application/json', 'Content-Type': 'application/json' }, timeout: 30000 // 30 second timeout }; // Add query parameters for GET requests if (method === 'GET' && Object.keys(params).length > 0) { config.params = params; } // Add request body for POST/PUT requests if (data) { config.data = data; } try { console.error(`🔗 AppsFlyer API: ${method} ${endpoint} (Client: ${client.name})`); const response = await axios(config); return response.data; } catch (error) { console.error(`❌ AppsFlyer API Error:`, error.response?.data || error.message); if (error.response?.status === 401) { throw new Error(`Authentication failed for client '${this.currentClientId}'. Please check the API token.`); } if (error.response?.status === 403) { throw new Error(`Access denied. Client '${this.currentClientId}' may not have permission for this resource.`); } if (error.response?.status === 429) { throw new Error(`Rate limit exceeded for client '${this.currentClientId}'. Please try again later.`); } throw new Error(`AppsFlyer API request failed: ${error.response?.data?.message || error.message}`); } } /** * Test authentication for a client */ async testAuthentication(clientId) { this.setCurrentClient(clientId); try { // Use the app list endpoint to test authentication const response = await this.makeRequest('/mng/apps', 'GET', { limit: 1 }); return { success: true, client: this.getCurrentClient(), appCount: response.apps?.length || 0, message: `Authentication successful for ${this.getCurrentClient().name}` }; } catch (error) { return { success: false, client: this.getCurrentClient(), error: error.message }; } } /** * Get apps list for current client */ async getApps(limit = 100, offset = 0) { const params = { limit, offset }; const response = await this.makeRequest('/mng/apps', 'GET', params); return response.apps || []; } /** * Get installs report for an app */ async getInstallsReport(appId, dateRange, options = {}) { const client = this.getCurrentClient(); // Validate app belongs to current client if (client.apps.length > 0 && !client.apps.includes(appId)) { console.warn(`⚠️ App ${appId} not in configured apps list for client ${client.name}`); } const params = { from: dateRange.from, to: dateRange.to, timezone: options.timezone || 'UTC', ...options }; const endpoint = `/raw-data/export/app/${appId}/installs_report/v5`; return await this.makeRequest(endpoint, 'GET', params); } /** * Get in-app events report for an app */ async getEventsReport(appId, dateRange, options = {}) { const client = this.getCurrentClient(); // Validate app belongs to current client if (client.apps.length > 0 && !client.apps.includes(appId)) { console.warn(`⚠️ App ${appId} not in configured apps list for client ${client.name}`); } const params = { from: dateRange.from, to: dateRange.to, timezone: options.timezone || 'UTC', ...options }; const endpoint = `/raw-data/export/app/${appId}/in_app_events_report/v5`; return await this.makeRequest(endpoint, 'GET', params); } /** * Get aggregate overview report for an app */ async getOverviewReport(appId, dateRange, options = {}) { const client = this.getCurrentClient(); // Validate app belongs to current client if (client.apps.length > 0 && !client.apps.includes(appId)) { console.warn(`⚠️ App ${appId} not in configured apps list for client ${client.name}`); } const params = { from: dateRange.from, to: dateRange.to, timezone: options.timezone || 'UTC', grouping: options.grouping || 'day', ...options }; const endpoint = `/agg-data/export/app/${appId}/overview_report/v5`; return await this.makeRequest(endpoint, 'GET', params); } /** * Get partners report (media sources performance) */ async getPartnersReport(appId, dateRange, options = {}) { const client = this.getCurrentClient(); // Validate app belongs to current client if (client.apps.length > 0 && !client.apps.includes(appId)) { console.warn(`⚠️ App ${appId} not in configured apps list for client ${client.name}`); } const params = { from: dateRange.from, to: dateRange.to, timezone: options.timezone || 'UTC', grouping: options.grouping || 'day', ...options }; const endpoint = `/agg-data/export/app/${appId}/partners_report/v5`; return await this.makeRequest(endpoint, 'GET', params); } /** * Get cohort report for LTV analysis */ async getCohortReport(appId, dateRange, options = {}) { const client = this.getCurrentClient(); // Validate app belongs to current client if (client.apps.length > 0 && !client.apps.includes(appId)) { console.warn(`⚠️ App ${appId} not in configured apps list for client ${client.name}`); } const params = { from: dateRange.from, to: dateRange.to, timezone: options.timezone || 'UTC', period: options.period || 'day', ...options }; const endpoint = `/agg-data/export/app/${appId}/cohort_report/v5`; return await this.makeRequest(endpoint, 'GET', params); } /** * Validate date range format */ static validateDateRange(dateRange) { if (!dateRange.from || !dateRange.to) { throw new Error('Date range must include both from and to dates'); } const dateRegex = /^\d{4}-\d{2}-\d{2}$/; if (!dateRegex.test(dateRange.from) || !dateRegex.test(dateRange.to)) { throw new Error('Dates must be in YYYY-MM-DD format'); } const fromDate = new Date(dateRange.from); const toDate = new Date(dateRange.to); if (fromDate > toDate) { throw new Error('From date cannot be later than to date'); } return true; } /** * Helper to format date range for display */ static formatDateRange(dateRange) { return `${dateRange.from} to ${dateRange.to}`; } }