Lección 3 de 7 · fundamentos · 15 min
Tu primer agente en Python conectado a Odoo
Instala el SDK, crea un helper para la API JSON-2 de Odoo y dale al agente una herramienta de solo lectura sobre el CRM.
Requisitos
- Python 3.10 o superior y
pip install claude-agent-sdk httpx. - La variable
ANTHROPIC_API_KEYen tu entorno (el SDK no lee archivos.envpor sí solo). - Un Odoo 19 o 20 con un API key de un usuario con permisos mínimos (Preferencias → Seguridad de la cuenta → Nueva clave API).
1. Helper para la API JSON-2 de Odoo
Desde Odoo 19 la API recomendada es JSON-2: POST /json/2/<modelo>/<método> con el API key en la cabecera Authorization: bearer. XML-RPC y JSON-RPC están programados para eliminarse en Odoo 22.
import os, httpx
BASE = f"{os.environ['ODOO_URL']}/json/2"
HDRS = {
"Authorization": f"bearer {os.environ['ODOO_API_KEY']}",
"X-Odoo-Database": os.environ["ODOO_DB"], # necesario si el servidor tiene varias bases
}
async def odoo(model: str, method: str, **kwargs):
"""Llama a /json/2/<model>/<method>. En JSON-2 todos los argumentos van con nombre."""
async with httpx.AsyncClient(timeout=30) as c:
r = await c.post(f"{BASE}/{model}/{method}", headers=HDRS, json=kwargs)
r.raise_for_status()
return r.json()
2. Una herramienta para el agente
import json
from typing import Any
from claude_agent_sdk import (tool, create_sdk_mcp_server, ToolAnnotations,
query, ClaudeAgentOptions, ResultMessage)
@tool("search_leads", "Busca oportunidades en crm.lead por texto", {"text": str},
annotations=ToolAnnotations(readOnlyHint=True))
async def search_leads(args: dict[str, Any]) -> dict[str, Any]:
try:
rows = await odoo("crm.lead", "search_read",
domain=[["name", "ilike", args["text"]]],
fields=["name", "expected_revenue", "probability", "stage_id"], limit=20)
return {"content": [{"type": "text", "text": json.dumps(rows, ensure_ascii=False)}]}
except Exception as e:
return {"content": [{"type": "text", "text": f"Error de Odoo: {e}"}], "is_error": True}
server = create_sdk_mcp_server(name="odoo", version="1.0.0", tools=[search_leads])
3. El loop
import asyncio
opts = ClaudeAgentOptions(
mcp_servers={"odoo": server},
allowed_tools=["mcp__odoo__search_leads"], # nombre: mcp__<servidor>__<herramienta>
permission_mode="dontAsk", # todo lo no permitido se rechaza
max_turns=5,
)
async def main():
async for msg in query(prompt="¿Qué oportunidades de 'ERP' hay y cuál tiene más ingreso esperado?", options=opts):
if isinstance(msg, ResultMessage) and msg.subtype == "success":
print(msg.result)
asyncio.run(main())
Claude decidirá llamar a search_leads, leerá el resultado y redactará la respuesta. Si le preguntas algo que necesita dos búsquedas, hará dos turnos.
Verificado por Focuz con
claude-agent-sdk0.2.163 y Odoo 20. Fuentes: herramientas personalizadas, API externa de Odoo.
← Anterior
Turnos, mensajes y resultados: anatomía de una sesión
Siguiente →
Controla el loop: turnos, presupuesto, effort y permisos
Practica con 5 casos reales
Descarga la guía gratuita con agentes Python para Odoo, de Junior a Senior.
Quiero la guía →