Skip to main content
Glama

Asterisk S2S MCP Server

by gcorroto
realtime-client.ts9.15 kB
// 📞 Cliente HTTP para Asistente Telefónico Conversacional import axios, { AxiosInstance, AxiosError, AxiosResponse } from 'axios'; import { PhoneClientConfig, PhoneCallRequest, PhoneCallResponse, CallStatus, PhoneAssistantError, HttpTool } from '../types/realtime-assistant.js'; /** * Cliente para interactuar con la API del Asistente Telefónico */ export class PhoneClient { private client: AxiosInstance; private config: PhoneClientConfig; constructor(config: PhoneClientConfig) { this.config = config; this.client = axios.create({ baseURL: config.baseUrl, timeout: config.timeout, headers: { 'Content-Type': 'application/json', 'X-API-Key': config.apiKey, 'User-Agent': 'MCP-PhoneAssistant/1.0' } }); // Interceptor para manejo de errores this.client.interceptors.response.use( (response: AxiosResponse) => response, (error: AxiosError) => this.handleAxiosError(error) ); } /** * Invocar al asistente telefónico para realizar una llamada conversacional */ async makePhoneCall(request: PhoneCallRequest): Promise<PhoneCallResponse> { try { console.log(`🔄 Iniciando llamada telefónica a: ${request.usuario} (${request.telefono})`); console.log(`📋 Propósito: ${request.proposito}`); // Construir las instrucciones completas combinando propósito y contexto let instrucciones = `${request.proposito}.`; if (request.contexto) { instrucciones += ` ${request.contexto}`; } // Construir la herramienta HTTP para el callback del MCP const mcpCallbackTool = this.buildMcpCallbackTool(); // Convertir herramientas al formato esperado por la API const tools = [...request.herramientas, mcpCallbackTool].map(tool => ({ name: tool.name, description: tool.description, type: "http", url: tool.endpoint, method: tool.method || "POST", headers: tool.authentication ? { [tool.authentication.header || 'Authorization']: tool.authentication.key || '' } : {}, parameters: tool.parameters || [] })); // Estructura de datos que espera la API real const apiRequest = { usuario: request.usuario, telefono: request.telefono, timeout: request.timeout, instrucciones: instrucciones, tools: tools }; // Configurar headers incluyendo X-API-Key const headers = { 'Content-Type': 'application/json', 'X-API-Key': this.config.apiKey, 'User-Agent': 'MCP-PhoneAssistant/1.0' }; const response = await this.client.post('/instrucciones', apiRequest, { headers }); console.log(`✅ Llamada telefónica iniciada. Respuesta:`, response.data); return { success: true, callId: response.data.call_id || this.generateCallId(), message: response.data.mensaje || 'Llamada telefónica iniciada correctamente', estimatedDuration: request.timeout }; } catch (error) { console.error('❌ Error al iniciar llamada telefónica:', error); throw this.createPhoneError('CALL_FAILED', 'Error al iniciar la llamada telefónica', error); } } /** * Obtener el estado de una llamada */ async getCallStatus(callId: string): Promise<CallStatus> { try { const response = await this.client.get(`/call/${callId}/status`); return response.data; } catch (error) { console.error(`❌ Error al obtener estado de llamada ${callId}:`, error); throw this.createPhoneError('STATUS_FAILED', 'Error al obtener estado de llamada', error); } } /** * Cancelar una llamada en curso */ async cancelCall(callId: string): Promise<boolean> { try { await this.client.post(`/call/${callId}/cancel`); console.log(`🚫 Llamada ${callId} cancelada`); return true; } catch (error) { console.error(`❌ Error al cancelar llamada ${callId}:`, error); throw this.createPhoneError('CANCEL_FAILED', 'Error al cancelar llamada', error); } } /** * Construir la herramienta HTTP para el callback del MCP */ private buildMcpCallbackTool(): HttpTool { return { name: 'responder_al_mcp', description: 'Envía el resultado de la conversación telefónica de vuelta al MCP', endpoint: `${this.config.mcpCallbackUrl}/api/phone/conversation-result`, method: 'POST', parameters: [ { name: 'callId', type: 'string', description: 'ID único de la llamada', required: true }, { name: 'usuario', type: 'string', description: 'Nombre del usuario que recibió la llamada', required: true }, { name: 'telefono', type: 'string', description: 'Número de teléfono llamado', required: true }, { name: 'status', type: 'string', description: 'Estado final de la llamada (completed, failed, timeout, cancelled)', required: true }, { name: 'duration', type: 'number', description: 'Duración de la llamada en segundos', required: true }, { name: 'resumen_conversacion', type: 'string', description: 'Resumen natural y conversacional de lo que se habló', required: true }, { name: 'resultado_accion', type: 'string', description: 'Qué se logró o decidió en la llamada', required: false }, { name: 'informacion_obtenida', type: 'string', description: 'Información estructurada extraída (JSON string)', required: false }, { name: 'transcripcion', type: 'string', description: 'Transcripción completa en JSON si está disponible', required: false }, { name: 'metadata', type: 'string', description: 'Metadatos adicionales en JSON', required: false } ], authentication: { type: 'api_key', header: 'X-MCP-API-Key', key: process.env.MCP_CALLBACK_API_KEY || 'mcp-default-key' } }; } /** * Generar un ID único para la llamada */ private generateCallId(): string { const timestamp = Date.now(); const random = Math.random().toString(36).substring(2, 15); return `call_${timestamp}_${random}`; } /** * Manejar errores de Axios */ private handleAxiosError(error: AxiosError): never { if (error.response) { // El servidor respondió con un código de error throw this.createPhoneError( `HTTP_${error.response.status}`, `Error HTTP ${error.response.status}: ${error.response.statusText}`, error.response.data ); } else if (error.request) { // La petición fue hecha pero no se recibió respuesta throw this.createPhoneError( 'NO_RESPONSE', 'No se pudo conectar con el asistente telefónico', error.message ); } else { // Error en la configuración de la petición throw this.createPhoneError( 'REQUEST_ERROR', 'Error en la configuración de la petición', error.message ); } } /** * Crear un error personalizado del asistente telefónico */ private createPhoneError(code: string, message: string, details?: any): PhoneAssistantError { return new PhoneAssistantError(message, code, details); } /** * Realizar reintentos con backoff exponencial */ async withRetry<T>(operation: () => Promise<T>, maxRetries: number = this.config.retries): Promise<T> { let lastError: Error; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await operation(); } catch (error) { lastError = error as Error; if (attempt === maxRetries) { break; } const delay = Math.pow(2, attempt) * 1000; // Backoff exponencial console.log(`⏱️ Reintento ${attempt}/${maxRetries} en ${delay}ms`); await new Promise(resolve => setTimeout(resolve, delay)); } } throw lastError!; } /** * Validar la conectividad con el asistente telefónico */ async healthCheck(): Promise<boolean> { try { const response = await this.client.get('/health'); return response.status === 200; } catch (error) { console.error('❌ Health check falló:', error); return false; } } /** * Obtener métricas del asistente telefónico */ async getMetrics(): Promise<any> { try { const response = await this.client.get('/metrics'); return response.data; } catch (error) { console.error('❌ Error al obtener métricas:', error); throw this.createPhoneError('METRICS_FAILED', 'Error al obtener métricas', error); } } }

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/gcorroto/mcp-s2s-asterisk'

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