agentes ia
Odoo 20 trae su propio servidor MCP: conecta un agente en Python
Conectamos un agente en Python al servidor MCP nativo de Odoo 20 y lo probamos de verdad: endpoint, API key, las 5 herramientas por defecto, el requisito de pgvector y la transcripción real del agente consultando el CRM.
Focuz Academy · 7 de octubre de 2026 · 10 min
Las notas de versión de Odoo 20 lo resumen en una línea: "Connect to your database via MCP". Para un desarrollador eso cambia mucho: cualquier agente compatible con MCP puede consultar Odoo sin que escribas una sola herramienta a mano. No nos quedamos con la teoría: lo instalamos en nuestra instancia Odoo 20 de demostración y conectamos un agente real.
Qué verificamos y cómo
| Afirmación | Cómo lo verificamos |
|---|---|
| Módulo, endpoint y autenticación | Código fuente del módulo ai_mcp de Odoo 20 Enterprise |
| Herramientas por defecto | tools/list contra nuestro Odoo 20 y el archivo ai_mcp/data/ir_actions_server_data.xml |
| Requisito de pgvector | Error real al instalar ai_mcp y lectura de ai/__init__.py |
| Agente → Odoo funcionando | Script Python con claude-agent-sdk 0.2.164 ejecutado contra Odoo 20 |
| Respuesta del agente correcta | Comparada con una consulta SQL directa a la base de datos |
1. El módulo: ai_mcp
- Se llama "AI MCP Server", es de licencia Enterprise (OEEL-1) y depende del módulo
ai. - El servidor queda en
<url de tu Odoo>/mcp. Por ejemplo,https://erp.tuempresa.com/mcp. - La autenticación es con un API key de alcance "MCP", enviado como
Authorization: Bearer <API_KEY>. Se genera en Mi perfil → Seguridad de la cuenta → Nueva clave API, eligiendo el alcance MCP. - El módulo trae además un servidor OAuth propio, para clientes que prefieren iniciar sesión en lugar de usar una clave.
- Funciona por HTTP directo (MCP streamable HTTP): lo probamos sin necesidad del puente
mcp-remoteque muestra la documentación.
La descripción del propio módulo insiste en dos reglas: nunca compartas el token y guárdalo siempre en campos de tipo credencial o contraseña.
2. El requisito que casi nadie menciona: pgvector
Nuestro primer intento de instalar ai_mcp se detuvo con este error:
PostgreSQL extension 'vector' is required to enable RAG for AI agents.
El módulo ai comprueba que exista la extensión vector (pgvector) en PostgreSQL e intenta crearla. Si tu servidor no la tiene, ni ai ni el servidor MCP se instalan.
Cómo lo resolvimos sin riesgo: construimos una imagen propia basada en la misma imagen postgres:16-alpine que ya usábamos, con pgvector compilado. Misma versión de PostgreSQL y misma librería del sistema, así que los datos quedan intactos:
FROM postgres:16-alpine
RUN apk add --no-cache --virtual .build-deps git build-base \
&& git clone --depth 1 --branch v0.8.7 https://github.com/pgvector/pgvector.git /tmp/pgvector \
&& cd /tmp/pgvector && make with_llvm=no && make with_llvm=no install \
&& cd / && rm -rf /tmp/pgvector && apk del .build-deps
Evita cambiar de una imagen Alpine a una Debian (o viceversa) sobre los mismos datos: la librería de C cambia el orden de los textos y puede dañar índices. Y haz siempre un respaldo antes.
Otro aprendizaje: al instalar los módulos de IA, Odoo intentó instalar automáticamente ai_website, que falló porque nuestros addons Enterprise eran más nuevos que la imagen Community de Docker. El servidor MCP no lo necesita: mantén ambas partes en versiones alineadas para evitar sorpresas.
3. Las 5 herramientas que Odoo expone por defecto
Esto devolvió tools/list en nuestro Odoo 20. Todas vienen marcadas como solo lectura (readOnlyHint: true):
| Herramienta MCP | Para qué sirve |
|---|---|
ai_tool_mcp_retrieve_initial_context |
Usuario actual, zona horaria y empresa |
ai_tool_get_models |
Listar los modelos accesibles |
ai_tool_get_fields |
Ver los campos de un modelo |
ai_tool_search |
Buscar registros y sus valores |
ai_tool_read_group |
Agrupar y totalizar |
¿Quieres exponer más? Toda acción de servidor tiene ahora la casilla "Available in MCP" (campo use_in_mcp). Según la documentación oficial, la opción "Readonly Tool" solo le indica al cliente que la herramienta es segura de invocar sin aprobación: no reemplaza a los permisos del usuario dueño del API key.
4. El agente en Python
Con claude-agent-sdk 0.2.164, el agente se conecta a Odoo como un servidor MCP remoto. Dentro del agente, las herramientas se llaman mcp__<servidor>__<herramienta>:
import asyncio, os
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage
ODOO_MCP = {
"type": "http",
"url": f"{os.environ['ODOO_URL']}/mcp",
"headers": {"Authorization": f"Bearer {os.environ['ODOO_MCP_API_KEY']}"},
}
SOLO_LECTURA = [
"mcp__odoo__ai_tool_mcp_retrieve_initial_context",
"mcp__odoo__ai_tool_get_models",
"mcp__odoo__ai_tool_get_fields",
"mcp__odoo__ai_tool_search",
"mcp__odoo__ai_tool_read_group",
]
async def main():
opts = ClaudeAgentOptions(
model="claude-sonnet-5-5",
mcp_servers={"odoo": ODOO_MCP},
tools=[], # sin herramientas integradas: solo Odoo
allowed_tools=SOLO_LECTURA,
permission_mode="dontAsk", # todo lo no permitido se rechaza
verbatim_prompts=True, # el texto del usuario no dispara @archivos ni /comandos
max_turns=10,
max_budget_usd=1.50,
system_prompt=("Eres analista de datos de Odoo 20. Las oportunidades del CRM están en "
"crm.lead (campos stage_id y expected_revenue). Usa pocas llamadas."),
)
prompt = ("¿Cuántas oportunidades del CRM hay por etapa y cuál etapa suma más ingreso esperado? "
"Responde con una tabla breve.")
async for msg in query(prompt=prompt, options=opts):
if isinstance(msg, ResultMessage) and msg.subtype == "success":
print(msg.result)
asyncio.run(main())
5. La prueba real: qué hizo el agente
Ejecutamos el script contra nuestro Odoo 20 de demostración (datos de ejemplo de Odoo). Esta es la transcripción resumida:
HERRAMIENTAS: mcp__odoo__ai_tool_get_fields, mcp__odoo__ai_tool_get_models,
mcp__odoo__ai_tool_mcp_retrieve_initial_context,
mcp__odoo__ai_tool_read_group, mcp__odoo__ai_tool_search
LLAMADA: ai_tool_mcp_retrieve_initial_context {}
LLAMADA: ai_tool_read_group {"model_name": "crm.lead", "domain": "[]",
"groupby": ["stage_id"], "aggregates": ["__count", "expected_revenue:sum"],
"order": "expected_revenue:sum DESC"}
LLAMADA: ai_tool_search {"model_name": "crm.stage", "domain": "[[\"id\",\"in\",[1,2,3,4]]]"}
RESULTADO: success | turnos: 4 | costo USD: 0.23
Y su respuesta:
| Etapa | Oportunidades | Ingreso esperado |
|---|---|---|
| New | 25 | 514 844 |
| Proposition | 6 | 105 100 |
| Qualified | 5 | 87 300 |
| Won | 3 | 23 800 |
| Total | 39 | 731 044 |
¿Es correcto? Lo comparamos con una consulta SQL directa a la base de datos: las cuatro etapas, los conteos y los montos coinciden exactamente. El agente no inventó nada: leyó Odoo.
Tres cosas para notar en la transcripción:
- Eligió la herramienta correcta: usó
read_group(una sola llamada agregada) en lugar de traer los 39 registros. - Respetó los límites: 4 turnos y USD 0,23, muy por debajo de
max_turnsymax_budget_usd. En una primera prueba con un presupuesto de USD 0,50 y sin contexto, el agente exploró más y el SDK lo detuvo al llegar al límite: exactamente el control que queremos en producción. - El contexto ayuda: decirle en el
system_prompten qué modelo están los datos ahorra llamadas y costo.
6. Buenas prácticas antes de llevarlo a producción
- Usuario dedicado para el API key, con los permisos mínimos: el agente puede todo lo que puede ese usuario. En nuestra prueba usamos un usuario de solo lectura del CRM.
allowed_toolsexplícito ypermission_mode="dontAsk". Evita comodines comomcp__odoo__*si expones acciones de escritura.- Límites siempre:
max_turnsymax_budget_usd. - Presupuesta los créditos: las notas de Odoo 20 indican que todas las funciones de IA de Odoo requieren créditos IAP.
- Prueba primero en una base neutralizada, nunca directo en producción.
¿Quieres entender el ciclo completo antes de conectar Odoo? Empieza por qué es el agent loop o descarga nuestra guía práctica con 5 agentes para Odoo.
Guía gratuita: 5 agentes Python para Odoo
Casos de Junior a Senior, con código verificado contra Odoo 20.
Descargar →¿Empiezas desde cero? Lee qué es el agent loop y sigue las lecciones gratuitas.