fix: add MCP result validation, improve agent error handling and state management

- Add `_extract_mcp_content_text()` helper in naliia_tools.py to safely
  extract text from MCP results, preventing IndexError on empty content
- Replace all direct `result.content[0].text` accesses with safe helper
- Improve customer identification with preservation of existing state
- Add proper JSON parsing with fallback and error handling
- Simplify webhook state management (use agent's checkpointer internally)
- Update system prompt to remove check_mcp_connection tool reference
- Fix tests to use async mocks and correct expected values
This commit is contained in:
2026-03-19 21:11:28 -05:00
parent 12822784b0
commit 30345538f7
7 changed files with 231 additions and 91 deletions

View File

@@ -15,6 +15,35 @@ MCP_SERVER_URL = "http://192.168.58.109:3001/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
@@ -29,27 +58,8 @@ class NaliiaTools:
self.find_service_centers,
self.find_products_and_services,
self.find_customer_by_identifier,
self.check_mcp_connection,
]
@tool
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"
@tool
def get_tomorrow_date() -> str:
"""
@@ -64,7 +74,7 @@ class NaliiaTools:
- Utiliza la zona horaria de Colombia (America/Bogota, UTC-5).
"""
td = timedelta(days=1)
tomorrow = datetime.now().date() + td
tomorrow = datetime.now(tz=ZoneInfo(TIMEZONE)).date() + td
tomorrow_date_formated = tomorrow.isoformat()
logger.info(tomorrow_date_formated)
@@ -106,26 +116,21 @@ class NaliiaTools:
Ejemplo: [1]
"""
identifiers = {
"type": "mobile",
"code": cellphone
}
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
})
"create_customer", {"name": name, "identifiers": identifiers}
)
logger.info(result.content[0].text)
logger.info(_extract_mcp_content_text(result))
return result
result = asyncio.run(call_tool())
return result.content[0].text
return _extract_mcp_content_text(result)
@tool
def find_customer_by_identifier(identifier: str) -> str:
@@ -147,12 +152,12 @@ class NaliiaTools:
result = await _MCP_CLIENT.call_tool(
"find_customer_by_identifier", {"identifier": identifier}
)
logger.info(result.content[0].text)
logger.info(_extract_mcp_content_text(result))
return result
result = asyncio.run(call_tool())
return result.content[0].text
return _extract_mcp_content_text(result)
@tool
def find_service_centers():
@@ -172,13 +177,13 @@ class NaliiaTools:
async def call_tool():
async with _MCP_CLIENT:
result = await _MCP_CLIENT.call_tool("find_service_centers", None)
logger.info(result.content[0].text)
logger.info(_extract_mcp_content_text(result))
return result
result = asyncio.run(call_tool())
return result.content[0].text
return _extract_mcp_content_text(result)
@tool
def find_products_and_services():
@@ -201,13 +206,13 @@ class NaliiaTools:
async def call_tool():
async with _MCP_CLIENT:
result = await _MCP_CLIENT.call_tool("find_products_and_services", None)
logger.info(result.content[0].text)
logger.info(_extract_mcp_content_text(result))
return result
result = asyncio.run(call_tool())
return result.content[0].text
return _extract_mcp_content_text(result)
@tool(args_schema=ScheduleSchema)
def schedule_appointment(