casos practicos
Elicitación MCP en Claude Code: confirma antes de tocar Odoo
Desde la versión 2.1.76, Claude Code muestra los formularios que un servidor MCP pide a mitad de una tarea. Construimos en Python un servidor que pide tu confirmación antes de confirmar un pedido en Odoo 19.
Focuz Academy · 30 de marzo de 2026 · 7 min
Un agente que lee Odoo es útil. Uno que escribe en Odoo necesita frenos. El 14 de marzo de 2026 se publicó Claude Code 2.1.76, cuyo CHANGELOG anuncia: "Added MCP elicitation support — MCP servers can now request structured input mid-task via an interactive dialog (form fields or browser URL)". Además añadió los hooks Elicitation y ElicitationResult.
En la práctica, tu servidor MCP puede detener la tarea y preguntarte algo, y Claude Code te muestra el formulario. Lo usaremos para algo concreto: que nadie confirme un pedido de venta sin un "sí" humano.
Qué es la elicitación en MCP
La especificación de MCP (revisión 2025-11-25) define la elicitación como la forma estándar en que un servidor pide información al usuario a través del cliente. Tiene dos modos:
- Formulario (form): el servidor envía un
requestedSchemay el cliente arma el formulario. El esquema se limita a objetos planos con propiedades primitivas: texto, número, booleano y enumeraciones. - URL: el cliente abre un enlace externo para interacciones sensibles que no deben pasar por el cliente. Este modo se introdujo en la revisión 2025-11-25.
La respuesta tiene tres acciones: accept (el usuario envió datos), decline (rechazó explícitamente) y cancel (cerró sin decidir).
Una regla de seguridad importante: los servidores no deben pedir contraseñas, API keys, tokens ni datos de pago en modo formulario; para eso existe el modo URL.
La pieza de Odoo: JSON-2
Usaremos la API externa JSON-2 de Odoo 19: un POST a /json/2/<modelo>/<método> con el encabezado Authorization: bearer <API key> y, opcionalmente, X-Odoo-Database. El cuerpo lleva ids y los parámetros del método con su nombre. Ojo: según esa página, el acceso a la API externa está disponible solo en los planes Custom de Odoo Online, no en One App Free ni en Standard.
El servidor MCP en Python
Usamos el SDK oficial mcp (la versión 1.26.0 se publicó el 24 de enero de 2026). Su README documenta await ctx.elicit(message, schema), que recibe un modelo de Pydantic y devuelve un resultado con action y data.
import os
import httpx
from pydantic import BaseModel, Field
from mcp.server.fastmcp import Context, FastMCP
from mcp.server.session import ServerSession
ODOO_URL = os.environ["ODOO_URL"] # p. ej. https://erp.tuempresa.com
HEADERS = {
"Authorization": f"bearer {os.environ['ODOO_API_KEY']}",
"X-Odoo-Database": os.environ["ODOO_DB"],
"Content-Type": "application/json; charset=utf-8",
"User-Agent": "focuz-academy-mcp",
}
mcp = FastMCP(name="odoo-ventas")
async def odoo(modelo: str, metodo: str, **params):
"""Llama a /json/2/<modelo>/<metodo> de Odoo 19."""
async with httpx.AsyncClient(timeout=30) as cliente:
r = await cliente.post(f"{ODOO_URL}/json/2/{modelo}/{metodo}", headers=HEADERS, json=params)
r.raise_for_status()
return r.json()
class Confirmacion(BaseModel):
confirmar: bool = Field(description="¿Confirmar este pedido de venta?")
@mcp.tool()
async def confirmar_pedido(pedido_id: int, ctx: Context[ServerSession, None]) -> str:
"""Confirma un pedido de venta de Odoo, previa aprobación del usuario."""
[pedido] = await odoo(
"sale.order", "read",
ids=[pedido_id], fields=["name", "partner_id", "amount_total", "state"],
)
if pedido["state"] not in ("draft", "sent"):
return f"{pedido['name']} está en estado '{pedido['state']}': no se confirma."
respuesta = await ctx.elicit(
message=(
f"¿Confirmar {pedido['name']} de {pedido['partner_id'][1]} "
f"por {pedido['amount_total']}?"
),
schema=Confirmacion,
)
if respuesta.action != "accept" or not respuesta.data or not respuesta.data.confirmar:
return f"{pedido['name']} NO se confirmó (acción: {respuesta.action})."
await odoo("sale.order", "action_confirm", ids=[pedido_id])
return f"{pedido['name']} confirmado en Odoo."
if __name__ == "__main__":
mcp.run()
Por qué está escrito así:
- El servidor decide cuándo preguntar, no el modelo. Aunque el agente "quiera" confirmar, la escritura queda detrás de un
acceptexplícito. - Valida el estado antes de preguntar. En Odoo 19, los estados de
sale.ordersondraft,sent,saleycancel(lo comprobamos enaddons/sale/models/sale_order.pyde la rama 19.0). - El esquema es plano (un booleano), como exige la especificación.
- La API key va en variables de entorno, nunca en el formulario.
Probamos el flujo con un cliente MCP en memoria y Odoo simulado: con accept se llamó a action_confirm; con decline, solo se hizo la lectura.
Conectarlo a Claude Code
Según la documentación de MCP en Claude Code, un servidor local se registra así:
claude mcp add \
--env ODOO_URL=https://erp.tuempresa.com \
--env ODOO_DB=produccion \
--env ODOO_API_KEY=tu_api_key \
--transport stdio \
odoo-ventas -- python servidor_odoo.py
Ojo con el orden: si el nombre del servidor va justo después de un --env, el CLI lo lee como otro par KEY=value y lo rechaza. Por eso --transport stdio va entre los --env y el nombre.
Pídele a Claude: "Confirma el pedido 42". Cuando el servidor llame a ctx.elicit, Claude Code mostrará el diálogo. No hay que configurar nada: los diálogos aparecen solos cuando el servidor los solicita.
Ideas para ir más allá
- Hooks
ElicitationyElicitationResult: permiten interceptar o responder automáticamente. Úsalos para registrar cada aprobación, no para saltarte la confirmación en producción. - Más campos en el esquema: una fecha de entrega o un motivo, siempre planos y sin datos sensibles.
- Modo URL: si tu flujo exige autenticarse en otro sistema, la especificación indica usar URL y no formulario.
Ejercicio
- Crea una API key de prueba en Odoo 19 (Preferencias → Seguridad de la cuenta → Nueva clave API) con duración corta.
- Levanta el servidor y conéctalo a Claude Code contra una base de pruebas.
- Pide confirmar un pedido en borrador; responde una vez
accepty otradecline. Revisa el estado en Odoo. - Reto: crea
cancelar_pedidocon un esquema que incluya unmotivo(texto) y registra ese motivo en tu log.
Fuentes: CHANGELOG de Claude Code, especificación de elicitación MCP 2025-11-25, README del SDK de MCP para Python v1.26.0, API externa JSON-2 de Odoo 19, MCP en Claude Code.
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.