# Compraventa360 · Servidor MCP para agentes — estado verificado

Última actualización: 2026-09-05

Este documento describe **solo lo verificado**. Cada afirmación indica cómo se
comprobó. Lo construido pero no probado en ejecución real se marca como
APAGADO, no como disponible.

## Endpoint y autenticación

- Endpoint público sin OAuth: `https://tqyhjvylclmqvtjtavge.supabase.co/functions/v1/mcp-public`.
- En el endpoint público (`/functions/v1/mcp-public`, sin OAuth), `initialize`,
  `tools/list`, `search_businesses` y `get_business` funcionan sin
  `Authorization`; no se registran otras herramientas.
- Límite anónimo: 30 llamadas/minuto y 300/hora por IP. Cada llamada se audita
  con `user_id = NULL` y `client_id = 'anonymous'`.

- Endpoint MCP (Streamable HTTP):
  `https://tqyhjvylclmqvtjtavge.supabase.co/functions/v1/mcp`
- Autenticación **configurada**: OAuth 2.1 contra el servidor de autorización
  del proyecto (`https://tqyhjvylclmqvtjtavge.supabase.co/auth/v1`), con
  registro dinámico de clientes y pantalla de consentimiento en
  `/.lovable/oauth/consent`.
- Verificado (2026-09-05, con `curl`):
  - `GET /functions/v1/mcp/.well-known/oauth-protected-resource` → `200`, con
    `authorization_servers` apuntando al emisor de arriba.
  - `POST /functions/v1/mcp` sin token, tanto `tools/list` como una llamada de
    escritura (`create_listing_draft`) → `401 unauthorized`. Ninguna escritura
    se ejecutó y no se creó ningún dato de prueba.
- **No verificado todavía:** el ciclo completo de OAuth con un cliente concreto
  (ChatGPT, Claude, Cursor). Ese paso exige que una persona inicie sesión y
  apruebe el consentimiento en el navegador; no se puede automatizar desde el
  servidor. Hasta que ocurra, no afirmamos compatibilidad "universal" con
  clientes MCP.
- El dueño de cada dato se deriva **del token verificado** (`sub`). Ningún
  parámetro de entrada puede fijar `user_id`.
- Un token de sesión de la app pegado a mano no sirve: el servidor exige el
  claim `client_id` de un cliente OAuth registrado.

## Herramientas

### Lectura pública
| Herramienta | Qué hace |
| --- | --- |
| `search_businesses` | Busca anuncios activos por texto, categoría, ciudad y rango de precio (`min_price`/`max_price` en USD). Devuelve `asking_price_usd`. |
| `get_business` | Detalle público de un anuncio por UUID, incluido `asking_price_usd`. |

El precio solicitado (`asking_price_usd`) es público. Las cifras financieras
(ingresos, ganancia operativa/EBITDA, empleados) nunca se devuelven por MCP:
requieren firmar un NDA en `https://compraventa360.com/negocio/<id>/nda`.

Ambas aplican los mismos filtros que la web: `status = 'active'`,
`is_demo = false`, país habilitado (EC, incluyendo filas antiguas sin país) y
una lista explícita de columnas públicas (sin contacto, sin `lead_*`, sin
`utm_*`).

Las cifras financieras no se exponen en el feed ni en el MCP público: requieren
NDA digital, que un agente puede iniciar en /negocio/<id>/nda.


### Datos del usuario autenticado
| Herramienta | Qué hace |
| --- | --- |
| `list_my_businesses` | Anuncios del usuario (todos los estados). |
| `list_my_favorites` | Favoritos del usuario. |
| `get_listing_status` | Borradores propios y estado de revisión del anuncio resultante. |
| `get_service_order_status` | Pedidos de servicios pagos propios y pagos confirmados. |
| `list_my_deal_requests` | Solicitudes propias de interés / NDA / LOI / diligencia. |

### Etapa 1 — publicación asistida · APAGADA (bandera `MCP_STAGE_LISTINGS_ENABLED`)
| Herramienta | Qué hace |
| --- | --- |
| `create_listing_draft` | Crea un borrador **privado**; no aparece en el marketplace. |
| `update_listing_draft` | Edita campos permitidos del borrador propio. |
| `submit_listing_draft` | Crea el anuncio en estado `pending` para **revisión humana**. |

Reglas que aplica el servidor:

- Sesión OAuth obligatoria y **correo confirmado según Auth**
  (`email_confirmed_at`). No se usa `user_metadata`, que el propio usuario
  puede editar.
- Solo países configurados, activos y con revisión legal: hoy **Ecuador**.
- Cifras validadas con las mismas reglas del formulario web (precio mínimo
  $1.000, ganancia operativa (EBITDA) ≤ ingresos anuales).
- Campos administrativos (`status`, `is_featured`, `owner_verified`,
  `needs_review`, `verified_by_firm`, `user_id`) se descartan.
- El envío ocurre en **una sola transacción de base de datos**
  (`submit_agent_listing_draft`): bloquea el borrador, verifica dueño y correo,
  valida país y cifras, crea el anuncio `pending` y marca el borrador. Si algo
  falla, no queda nada a medias. Un índice único impide que un borrador quede
  ligado a dos anuncios, incluso con dos envíos simultáneos y claves distintas.
- Ningún agente publica: un trigger impide que un vendedor pase su anuncio a
  `active`; solo el equipo aprueba. La cuota de anuncios del plan se aplica en
  base de datos.
- `idempotency_key` **obligatoria** en toda escritura. La clave se liga a la
  huella de los datos: la misma clave con otros datos es un error explícito, un
  intento en curso responde "reintenta", un intento fallido se puede repetir, y
  si el registro de reintentos no está disponible la operación **se rechaza**
  en lugar de arriesgar duplicados.

### Etapa 2 — servicios pagos · APAGADA (`MCP_STAGE_PAID_SERVICES_ENABLED`)
| Herramienta | Qué hace |
| --- | --- |
| `list_paid_services` | Catálogo de solo lectura con precios. |
| `create_service_checkout` | Registra el pedido y devuelve un enlace de pago de Stripe. |

- Flujo "pedido primero": el pedido se registra antes de crear la sesión de
  pago; si esa escritura falla, no se llama a Stripe.
- El precio lo fija el servidor (edge function), nunca el agente.
- La clave de idempotencia de Stripe la deriva el servidor del ID del pedido
  (`agent_order_<uuid>`); una clave enviada por el cliente se ignora.
- La sesión de pago queda ligada al pedido (`checkout_session_id`, único). Si
  el enlace no queda ligado, la herramienta falla y **no entrega el enlace**.
- El pago solo se confirma por el webhook firmado de Stripe, contra el pedido
  que coincide en id, usuario, servicio e importe, de forma idempotente.
- **Bloqueo para encenderla:** falta una prueba de extremo a extremo en modo
  sandbox de Stripe. No se hará ninguna prueba con un cobro real.

### Etapa 3 — interés / NDA / LOI · APAGADA (`MCP_STAGE_DEAL_PREP_ENABLED`)
| Herramienta | Qué hace |
| --- | --- |
| `prepare_deal_request` | Prepara un **borrador interno** de interés, NDA, LOI o diligencia. |

- No envía mensajes, no crea conversaciones, no firma nada y no toca el Deal
  Log de administración.
- Cada solicitud nace en `pending_review` y es visible solo para quien la creó
  y para el equipo administrador.
- La revisión humana ocurre en el panel de administración (sección "MCP
  (agentes IA)"): un administrador aprueba o rechaza con nota, y queda
  registrado quién revisó y cuándo. Aprobar autoriza al equipo a continuar el
  contacto manualmente; no envía ni firma documentos.

## Límites y auditoría

- 60 llamadas por minuto y 1.000 por hora por usuario.
- Toda llamada queda auditada (herramienta, usuario, cliente, duración,
  resultado) sin datos sensibles.
- Embudo de agentes visible para administradores: borradores → enviados →
  publicados → pagos → solicitudes.

## Cómo se enciende una etapa

Cada etapa se activa con su variable de entorno en la función `mcp`
(`MCP_STAGE_LISTINGS_ENABLED`, `MCP_STAGE_PAID_SERVICES_ENABLED`,
`MCP_STAGE_DEAL_PREP_ENABLED`). Sin la variable, la etapa responde con un
mensaje explicando que la capacidad no está habilitada.

## Contacto

info@compraventa360.com
