Saltar al contenido
NUEVOGuía práctica con 5 agentes Python para Odoo, verificada contra Odoo 20 →
Focuz/academy

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 requestedSchema y 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 accept explícito.
  • Valida el estado antes de preguntar. En Odoo 19, los estados de sale.order son draft, sent, sale y cancel (lo comprobamos en addons/sale/models/sale_order.py de 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 Elicitation y ElicitationResult: 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

  1. Crea una API key de prueba en Odoo 19 (Preferencias → Seguridad de la cuenta → Nueva clave API) con duración corta.
  2. Levanta el servidor y conéctalo a Claude Code contra una base de pruebas.
  3. Pide confirmar un pedido en borrador; responde una vez accept y otra decline. Revisa el estado en Odoo.
  4. Reto: crea cancelar_pedido con un esquema que incluya un motivo (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.

Sigue leyendo