import asyncio import os from datetime import datetime, timedelta, timezone from zoneinfo import ZoneInfo from fastmcp import Client from langchain.tools import tool from ..logger import logger from .schemes import ScheduleSchema TIMEZONE = "America/Bogota" MCP_SERVER_HOST = os.getenv("MCP_SERVER_HOST", "localhost") MCP_SERVER_PORT = os.getenv("MCP_SERVER_PORT", "3001") MCP_SERVER_URL = f"http://{MCP_SERVER_HOST}:{MCP_SERVER_PORT}/mcp" _MCP_CLIENT = Client(MCP_SERVER_URL) def _extract_mcp_content_text(result, fallback: str = "") -> str: if not result.content: logger.warning("MCP result content is empty") return fallback if not hasattr(result.content[0], "text"): logger.warning("MCP result content[0] has no 'text' attribute") return fallback return result.content[0].text def check_mcp_connection(self) -> str: """ Verifica la conexión con el servidor MCP. Returns: str: "connected" si el servidor está disponible, "disconnected" si no hay conexión. """ async def call_tool(): async with _MCP_CLIENT: result = await _MCP_CLIENT.ping() logger.info(f"MCP connection status: {result}") return True is_connected = asyncio.run(call_tool()) return "connected" if is_connected else "disconnected" class NaliiaTools: def __init__(self): self._session_active = False def get_tools(self): return [ self.get_current_datetime, self.get_tomorrow_date, self.register_customer, self.schedule_appointment, self.find_service_centers, self.find_products_and_services, self.find_customer_by_identifier, ] @tool def get_tomorrow_date() -> str: """ Consulta la fecha actual del sistema y calcula la fecha del día de mañana. Returns: str: Fecha del día siguiente en formato ISO 8601 (YYYY-MM-DD). Ejemplo: "2026-03-10" Notes: - No se aceptan fechas en el pasado con respecto a esta fecha. - Utiliza la zona horaria de Colombia (America/Bogota, UTC-5). """ td = timedelta(days=1) tomorrow = datetime.now(tz=ZoneInfo(TIMEZONE)).date() + td tomorrow_date_formated = tomorrow.isoformat() logger.info(tomorrow_date_formated) return tomorrow_date_formated @tool def get_current_datetime() -> str: """ Consulta la fecha y hora actual del sistema. Returns: str: Fecha y hora actual en formato ISO 8601 con zona horaria. Ejemplo: "2026-03-09T17:30:00-05:00" Notes: - No se aceptan fechas en el pasado con respecto a esta fecha. - Utiliza la zona horaria de Colombia (America/Bogota, UTC-5). """ logger.info("Consultando fecha actual.") today_date_formated = datetime.now(tz=ZoneInfo(TIMEZONE)).isoformat() logger.info(today_date_formated) return today_date_formated @tool def register_customer(name: str, cellphone: str, email: str = None) -> bool: """ Registra un nuevo cliente Args: name: Nombre Completo del Cliente. cellphone: Numero de Celular o Contacto. Returns: list: En caso del cliente fuese creado exitosamente se obtendra una lista con el id del cliente en la base de datos. Ejemplo: [1] """ identifiers = {"type": "mobile", "code": cellphone} async def call_tool(): async with _MCP_CLIENT: result = await _MCP_CLIENT.call_tool( "create_customer", {"name": name, "identifiers": identifiers} ) logger.info(_extract_mcp_content_text(result)) return result result = asyncio.run(call_tool()) return _extract_mcp_content_text(result) @tool def find_customer_by_identifier(identifier: str) -> str: """ Busca un cliente por su identificador (teléfono o número de documento). Args: identifier: Identificador del cliente (teléfono o número de documento). Returns: str: JSON con la información del cliente encontrado. Estructura: {"id": int, "name": str, "phone": str, "email": str} Ejemplo: '{"id": 1, "name": "Juan Perez", "phone": "573001234567", "email": "juan@example.com"}' Retorna "{}" si no se encuentra el cliente. """ async def call_tool(): async with _MCP_CLIENT: result = await _MCP_CLIENT.call_tool( "find_customer_by_identifier", {"identifier": identifier} ) logger.info(_extract_mcp_content_text(result)) return result result = asyncio.run(call_tool()) return _extract_mcp_content_text(result) @tool def find_service_centers(): """ Obtiene la lista de centros de servicio disponibles para agendar citas. Returns: str: JSON array con los centros de servicio disponibles. Cada elemento contiene: - id (int): Identificador único del centro de servicio - name (str): Nombre del centro de servicio Example: '[{"id": 1, "name": "Campo Amor"}, {"id": 2, "name": "Belen"}]' """ async def call_tool(): async with _MCP_CLIENT: result = await _MCP_CLIENT.call_tool("find_service_centers", None) logger.info(_extract_mcp_content_text(result)) return result result = asyncio.run(call_tool()) return _extract_mcp_content_text(result) @tool def find_products_and_services(): """ Obtiene la lista de productos y servicios disponibles para agendar. Returns: str: JSON array con los productos y servicios disponibles. Cada elemento contiene: - id (int): Identificador único del producto/servicio - name (str): Nombre del producto o servicio - price (float): Precio del servicio - description (str | None): Descripción adicional del servicio Example: '[{"id": 1, "name": "Manicure", "price": 20000.0, "description": null}, {"id": 2, "name": "Corte de Cabello", "price": 30000.0, "description": "Corte frances, aleman"}]' """ async def call_tool(): async with _MCP_CLIENT: result = await _MCP_CLIENT.call_tool("find_products_and_services", None) logger.info(_extract_mcp_content_text(result)) return result result = asyncio.run(call_tool()) return _extract_mcp_content_text(result) @tool(args_schema=ScheduleSchema) def schedule_appointment( schedule_date, schedule_time, description, service_center=11, customer=None, professional=6, customer_id=None, ) -> bool: """ Permite agendar una cita para un cliente en un centro de servicio específico. Args: schedule_date: Fecha de la cita en formato YYYY-MM-DD. schedule_time: Hora de la cita en formato HH:MM:SS. description (str): Descripción o motivo de la cita. service_center (int, optional): ID del centro de servicio. Por defecto es 11. customer (int, optional): ID del cliente. Si no se proporciona, se usa customer_id. professional (int, optional): ID del profesional. Por defecto es 6. customer_id (int, optional): ID del cliente obtenido del estado. Returns: bool: True si la cita fue agendada exitosamente, False en caso contrario. Notes: - La fecha y hora se almacenan en zona horaria America/Bogota (UTC-5). - La fecha no puede ser en el pasado. """ logger.info("Agendando cita...") logger.info([schedule_date, schedule_time]) if not schedule_date and schedule_time: return False if customer is None and customer_id is not None: customer = customer_id logger.info(f"Customer ID obtained from agent state: {customer}") if customer is None or customer == 0: logger.warning("No se pudo obtener el ID del cliente") return False schedule = ( datetime.combine(schedule_date, schedule_time) .replace(tzinfo=ZoneInfo(TIMEZONE)) .isoformat() ) async def call_tool(): async with _MCP_CLIENT: result = await _MCP_CLIENT.call_tool( "create_schedule", { "professional": professional, "description": description, "customer": customer, "date": schedule, "service_center": service_center, }, ) logger.info(result) return result result = asyncio.run(call_tool()) return True