Retirar el servidor MCP y fijar todas las dependencias del API

MCP: se retira por completo (endpoint, paquete api/app/mcp/ y dependencia).
Tres razones, en orden de peso:

- Nadie lo usa.
- Llevaba roto desde antes de este trabajo. Con credencial valida devolvia 500:
  el handler construia un StreamableHTTPServerTransport nuevo por peticion, sin
  manejo de sesion. Lo que estaba expuesto a internet era la puerta abierta de un
  cuarto averiado.
- Su SDK sin fijar tumbo el API entero en produccion al saltar a 2.0.0.

Retirarlo es una mitigacion mas fuerte que autenticarlo, que fue lo que hizo el
commit anterior. Recuperable con `git show edb500f5:api/app/mcp/server.py`.
Se limpia tambien la ruta "/mcp" que el endpoint raiz seguia anunciando, y las
menciones a MCP del docstring y la descripcion de Swagger.

Dependencias: once de las doce eran rangos `>=` sin techo, o sea que cada
reconstruccion era una tirada de dados contra PyPI. `pydantic>=2.0` habria
aceptado pydantic 3 con la misma alegria con la que `mcp>=1.0.0` acepto 2.0.0.
Ahora todas van fijadas a la version exacta que corre sana en produccion,
capturada con pip freeze del contenedor healthy.

El lado Next.js ya era reproducible via package-lock.json; por eso el web nunca
se cayo durante el incidente y el api si.

Docs actualizados: AGENTS.md, README.md, api/COTIZADOR_API_SKILL.md y el spec,
que ademas registra en su seccion 0 las tres desviaciones de Fase 0 respecto a
lo disenado (el Despliegue B cancelado, la retirada del MCP y la deriva de
dependencia).

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
urieljareth
2026-07-28 17:51:21 -06:00
co-authored by Claude Opus 5
parent edb500f517
commit 20f535d485
9 changed files with 118 additions and 862 deletions
@@ -8,11 +8,60 @@
> el análisis destapó) y Fase 1 (script local que valida si la IA escribe documentos utilizables).
> Fases 2-4 quedan fuera y tendrán su propio ciclo.
>
> **Fecha:** 2026-07-28 · **Estado:** aprobado para planeación
> **Fecha:** 2026-07-28 · **Estado:** Fase 0 desplegada en producción · Fase 1 pendiente
> **Base verificada:** commit `26e6d3a9`
---
## 0. Registro de ejecución — Fase 0 (2026-07-28)
Fase 0 está en producción. Tres cosas salieron distinto de lo diseñado y este documento
quedaría mintiendo si no se dijeran.
### 0.1 El movimiento de datos (Despliegue B) se canceló, no se pospuso
El §4.3 describe un `UPDATE` que mueve `observaciones``observacionesInternas`. **No se
va a ejecutar nunca.** Al inspeccionar producción antes de migrar, la única fila con
`observaciones` no vacías resultó ser `UJ2606AG777` (estado `aprobada`), y su texto son
condiciones de pago dirigidas al cliente en segunda persona: *"llevamos una bitácora de
horas con acceso para ti; a fin de mes te enviamos el reporte… solo pagas las horas
efectivamente trabajadas."*
Aplicar el `UPDATE` conservador habría ocultado condiciones ya acordadas de un documento
emitido. Esa fila ya estaba clasificada correctamente. La migración quedó reducida a
`ADD COLUMN IF NOT EXISTS`: cero filas modificadas. Verificado tras el despliegue —
conteos `2/3/59/5/2` idénticos al respaldo y cero filas en `observacionesInternas`.
**Lección para el futuro: mirar el dato real antes de diseñar su migración.** El diseño
"conservador" era el equivocado para estos datos.
### 0.2 El servidor MCP se retiró en vez de asegurarse
El §4.1 diseñaba Fase 0-A como *"aplicar `require_auth` + lista blanca de columnas"*. Eso
se implementó y se verificó en producción (401 sin credencial). Después se descubrió que,
**con credencial válida, el endpoint devolvía 500**: el código construía un
`StreamableHTTPServerTransport` por petición sin manejo de sesión. Llevaba roto desde antes
de este trabajo — lo que estaba expuesto era la puerta abierta de un cuarto averiado.
Dado que nadie lo usaba, se retiró por completo (endpoint, paquete `api/app/mcp/` y la
dependencia). Es una mitigación más fuerte que autenticarlo. Recuperable con
`git show edb500f5:api/app/mcp/server.py`.
### 0.3 Una dependencia sin fijar tumbó el API 50 minutos
`api/requirements.txt` tenía `mcp>=1.0.0`. La primera reconstrucción en dos semanas tomó
`mcp` 2.0.0, que eliminó `Server.list_tools()`. El import lanzó `AttributeError` al
arrancar y el contenedor quedó en crash-loop.
Lo agravó que `main.py` capturara solo `ImportError` alrededor del montaje del MCP: un
`AttributeError` se escapó y tumbó también el REST, por un componente opcional.
Once de las doce dependencias eran rangos `>=` sin techo. **Todas se fijaron a versión
exacta** — las que corrían sanas en producción. El `web` (Next.js) nunca estuvo caído:
tiene `package-lock.json` y es reproducible.
---
## 1. Por qué el alcance es este
El documento de negocio describe ocho subsistemas. Construirlos de una vez significa un mes de
@@ -122,6 +171,10 @@ Cuatro bloques. A y B son prerrequisitos de C.
### 4.1 Fase 0-A · Cerrar el MCP
> **Superado por §0.2.** Esto se implementó tal cual y se verificó (401 sin credencial),
> pero después el servidor MCP se retiró por completo. Se conserva el diseño original
> porque documenta el agujero que existía y por qué importaba.
1. Aplicar `require_auth` (`api/app/auth.py:45`) al endpoint `POST /mcp` en `api/main.py:107`.
2. Sustituir el `SELECT c.*` de `_obtener_cotizacion` (`api/app/mcp/server.py:250`) por una lista
blanca de columnas explícita, con el mismo criterio que el REST ya aplica vía
@@ -168,7 +221,10 @@ ninguno.
declara `observacionesInternas` — la garantía es estructural, no disciplinaria.
- Ningún exportador recibe el campo interno.
**Despliegue B — movimiento de datos.** Solo cuando A lleve días estable:
**Despliegue B — movimiento de datos. CANCELADO, ver §0.1.** El `UPDATE` de abajo no se
ejecutó ni se va a ejecutar: la única fila afectada contenía texto dirigido al cliente y ya
estaba donde debía. Se conserva por si algún día aparece una instalación con datos mal
clasificados.
```sql
UPDATE "Cotizacion"
@@ -425,7 +481,8 @@ exige normalizar saltos de línea.
**Fase 0** — verificable de inmediato:
- `POST /mcp` sin credencial devuelve 401; con credencial, `obtener_cotizacion` no expone el campo interno.
- El endpoint `/mcp` ya no existe (§0.2). Se cumplió antes con 401 verificado; la retirada lo supera.
- `api/requirements.txt` fija versiones exactas y el API arranca sin el paquete `mcp` (§0.3).
- El total del PDF y el del Excel coinciden dígito por dígito en cotizaciones reales.
- Una cotización con `incluirIva: false` no muestra IVA en ningún exportador.
- Las notas internas no aparecen en ningún archivo exportable.