Skip to main content
Glama
tracking.ts10.7 kB
import { AxiosInstance } from 'axios'; import { Tool } from '@modelcontextprotocol/sdk/types.js'; import { TrackingOptions } from '../types/index.js'; export class TrackingTools { constructor(private apiClient: AxiosInstance) {} // Definir as ferramentas de tracking getTools(): Tool[] { return [ { name: 'get_contact_tracking_logs', description: 'Busca os logs de eventos/tracking de um contato específico', inputSchema: { type: 'object', properties: { contactId: { type: 'string', description: 'ID do contato para buscar os logs de eventos', }, limit: { type: 'number', description: 'Limite de resultados (padrão: 100, máximo: 100)', default: 100, maximum: 100, }, offset: { type: 'number', description: 'Offset para paginação (padrão: 0)', default: 0, }, eventType: { type: 'string', description: 'Filtrar por tipo de evento específico (opcional)', enum: [ 'open', 'click', 'sent', 'bounce', 'unsubscribe', 'subscribe', 'reply', 'forward', 'update', 'deal_add', 'deal_update', 'deal_delete', 'note_add', 'task_add', 'automation_start', 'automation_complete' ] }, dateRange: { type: 'object', description: 'Filtrar por intervalo de datas', properties: { start: { type: 'string', description: 'Data de início (formato: YYYY-MM-DD)', }, end: { type: 'string', description: 'Data de fim (formato: YYYY-MM-DD)', }, }, }, }, required: ['contactId'], }, }, { name: 'get_contact_tracking_logs_by_email', description: 'Busca os logs de eventos/tracking de um contato pelo email', inputSchema: { type: 'object', properties: { email: { type: 'string', description: 'Email do contato para buscar os logs de eventos', }, limit: { type: 'number', description: 'Limite de resultados (padrão: 100)', default: 100, }, offset: { type: 'number', description: 'Offset para paginação (padrão: 0)', default: 0, }, eventType: { type: 'string', description: 'Filtrar por tipo de evento específico (opcional)', }, }, required: ['email'], }, }, ]; } // Executar ferramenta de tracking async executeTool(name: string, args: any) { switch (name) { case 'get_contact_tracking_logs': return await this.getContactTrackingLogs( args?.contactId as string, { limit: args?.limit as number, offset: args?.offset as number, eventType: args?.eventType as string, dateRange: args?.dateRange as { start?: string; end?: string }, } ); case 'get_contact_tracking_logs_by_email': return await this.getContactTrackingLogsByEmail( args?.email as string, { limit: args?.limit as number, offset: args?.offset as number, eventType: args?.eventType as string, } ); default: throw new Error(`Ferramenta de tracking desconhecida: ${name}`); } } private async getContactTrackingLogs( contactId: string, options: TrackingOptions = {} ) { try { console.error(`[DEBUG] Buscando tracking logs para contato ID: ${contactId}`); const params: any = { limit: options.limit || 100, offset: options.offset || 0, }; // Adicionar filtros opcionais if (options.eventType) { params.type = options.eventType; } if (options.dateRange?.start) { params.after = options.dateRange.start; } if (options.dateRange?.end) { params.before = options.dateRange.end; } const response = await this.apiClient.get( `/api/3/contacts/${contactId}/trackingLogs`, { params } ); const trackingData = await this.formatTrackingLogs(response.data); return { content: [ { type: 'text', text: JSON.stringify(trackingData, null, 2), }, ], }; } catch (error) { console.error(`[ERROR] Erro ao buscar tracking logs:`, error); throw new Error( `Erro ao buscar logs de tracking: ${error instanceof Error ? error.message : 'Erro desconhecido'}` ); } } private async getContactTrackingLogsByEmail( email: string, options: Omit<TrackingOptions, 'dateRange'> = {} ) { try { // Primeiro, buscar o contato pelo email const contactResponse = await this.apiClient.get('/api/3/contacts', { params: { email: email }, }); const contacts = contactResponse.data.contacts; if (!contacts || contacts.length === 0) { return { content: [ { type: 'text', text: `Nenhum contato encontrado com o email: ${email}`, }, ], }; } const contactId = contacts[0].id; // Agora buscar os tracking logs return await this.getContactTrackingLogs(contactId, options); } catch (error) { throw new Error( `Erro ao buscar logs por email: ${error instanceof Error ? error.message : 'Erro desconhecido'}` ); } } private async formatTrackingLogs(rawData: any): Promise<any> { const trackingLogs = rawData.trackingLogs || []; const meta = rawData.meta || {}; // Buscar informações adicionais para enriquecer os dados const enrichedLogs = await Promise.all( trackingLogs.map(async (log: any) => { const enrichedLog: any = { id: log.id, type: log.type, timestamp: log.tstamp, date: this.formatTimestamp(log.tstamp), contact: log.contact, subscriberId: log.subscriberid, hash: log.hash, description: this.getEventDescription(log.type), }; // Adicionar valor se existir if (log.value) { enrichedLog.value = log.value; } // Adicionar links se existirem if (log.links) { enrichedLog.links = log.links; } // Adicionar dados específicos baseados no tipo de evento (apenas se existirem) if (log.campaign) { try { const campaignResponse = await this.apiClient.get(`/api/3/campaigns/${log.campaign}`); enrichedLog.campaign = { id: log.campaign, name: campaignResponse.data.campaign?.name || 'N/A', }; } catch (error) { enrichedLog.campaign = { id: log.campaign, name: 'N/A' }; } } if (log.automation) { enrichedLog.automation = { id: log.automation }; } if (log.email) { enrichedLog.email = { id: log.email }; } if (log.link) { enrichedLog.link = { id: log.link }; } if (log.eventdata) { enrichedLog.eventData = log.eventdata; } return enrichedLog; }) ); return { summary: { total: meta.total || trackingLogs.length, count: meta.count || trackingLogs.length, limit: meta.limit || 100, offset: meta.offset || 0, eventTypes: this.getEventTypeSummary(trackingLogs), }, events: enrichedLogs, }; } private getEventDescription(eventType: string): string { const descriptions: { [key: string]: string } = { open: 'Email foi aberto', click: 'Link no email foi clicado', sent: 'Email foi enviado', bounce: 'Email retornou (bounce)', unsubscribe: 'Contato se descadastrou', subscribe: 'Contato se inscreveu', reply: 'Contato respondeu o email', forward: 'Email foi encaminhado', update: 'Dados do contato foram atualizados', deal_add: 'Deal foi adicionado', deal_update: 'Deal foi atualizado', deal_delete: 'Deal foi deletado', note_add: 'Nota foi adicionada', task_add: 'Tarefa foi adicionada', automation_start: 'Automação foi iniciada', automation_complete: 'Automação foi completada', }; // Verificar se há uma tradução específica, senão retornar o tipo original const lowerEventType = eventType.toLowerCase(); return descriptions[lowerEventType] || eventType; } private getEventTypeSummary(logs: any[]): { [key: string]: number } { const summary: { [key: string]: number } = {}; logs.forEach(log => { const type = log.type; summary[type] = (summary[type] || 0) + 1; }); return summary; } private formatTimestamp(timestamp: any): string { try { // Verificar se o timestamp é válido if (!timestamp) { return 'N/A'; } // Se já for uma string ISO, tentar criar a data diretamente if (typeof timestamp === 'string') { const date = new Date(timestamp); // Verificar se a data é válida if (isNaN(date.getTime())) { return 'N/A'; } return date.toISOString(); } // Se for número, assumir que é timestamp Unix const numericTimestamp = Number(timestamp); // Verificar se é um número válido if (isNaN(numericTimestamp) || numericTimestamp <= 0) { return 'N/A'; } // Criar a data (timestamp em segundos, então multiplicar por 1000) const date = new Date(numericTimestamp * 1000); // Verificar se a data é válida if (isNaN(date.getTime())) { return 'N/A'; } return date.toISOString(); } catch (error) { console.error('Erro ao formatar timestamp:', timestamp, error); return 'N/A'; } } }

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/mmarqueti/activecampaign-mcp-server'

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