# AGENT_TOOLS — Capa agentica MCP para MP Manager

Este documento describe la capa MCP que permite a Claude Code (y otros clientes
MCP) operar el ecosistema MP Manager como herramienta. Es el **punto de entrada
canónico** para entender qué se expone como tool, cómo invocarla, qué garantías
de seguridad existen y dónde leer cuando algo falla.

> **Para humanos (Uriel)**: lee [GUIA_AGENTICA.md](GUIA_AGENTICA.md) — recetas, prompts copy-paste y cómo trabajar con Claude Code como manager operativo.
> **Para humanos (arquitectura)**: reglas de negocio en `CLAUDE.md` y `AGENTS.md`. Este archivo cubre solo la capa LLM.
> **Para LLM**: cuando arranques una sesión, lee `generated/agent/tools_manifest.json`
> primero — es el inventario actualizado y autogenerado.

---

## 1. Arquitectura

```
Claude Code ── stdio ──► python -m mcp_server
                              │
                              ├── adapters.py  ──► funciones Python directas (db, sync_*.run_sync, ghl_client)
                              └── run_script   ──► subprocess scripts/*.py --json
                                                         │
                                                         ▼
                                                generated/agent/runs/*.json (offload de payloads grandes)
```

- **Transport**: stdio. El SDK MCP oficial de Anthropic (`mcp[cli]>=1.0`).
- **Sin HTTP**: la mayoría de tools llaman funciones Python directas. La tool
  genérica `run_script` usa subprocess para scripts que no exportan función.
- **Reutiliza el código existente**: `db.py`, `sync_missing_*.run_sync()`,
  `script_audit`, `ghl_client`, `paths.*`. El MCP es una capa thin.

---

## 2. Arranque

`.mcp.json` en la raíz del repo:

```json
{
  "mcpServers": {
    "mp-manager": {
      "command": "python",
      "args": ["-m", "mcp_server"],
      "cwd": "."
    }
  }
}
```

Claude Code detecta el archivo automáticamente y arranca el server al abrir
sesión en el directorio. Manualmente:

```bash
python -m mcp_server                 # arranca por stdio
python -m mcp_server.manifest        # regenera tools_manifest.json
python scripts/audit_agent_readiness.py  # refresca audit_report.json (insumo del manifest)
```

---

## 3. Seguridad — defaults y confirm_token

**Toda tool mutadora arranca con `apply=False`** (dry-run). Para aplicar
cambios reales el LLM debe pasar explícitamente:

```python
apply=True
confirm_token="I-HAVE-USER-CONFIRMATION"
```

Si `apply=True` sin el token correcto, la tool devuelve error sin tocar nada.
El token es literal — no se deriva ni se genera. Su propósito es que el LLM
sea forzado a **pedir confirmación al usuario** antes de incluirlo en la
llamada. Cualquier `apply=True` queda registrado en `script_audit` con un
`run_id` y es reversible vía el dashboard del SPA.

Workflow estándar (protocolo dry-run obligatorio del proyecto):

1. LLM llama tool con `apply=False` → obtiene plan/preview con números reales.
2. LLM resume al usuario los cambios.
3. Usuario confirma explícitamente.
4. **Piloto**: LLM llama con `apply=True` + `confirm_token` aplicando SOLO a una sucursal (filtrar por `location_id` o usar `--location` / args equivalentes).
5. LLM valida el resultado del piloto contra la API/DB y reporta al usuario.
6. Usuario confirma el batch al resto.
7. LLM aplica al lote completo. Cada apply genera `run_id` registrado en `script_audit` (rollback desde el dashboard).

**Nunca saltar el piloto** aunque la lógica parezca trivial. El blast radius en Bucéfalo es alto (50 cuentas, replicación bidireccional, reglas de negocio interdependientes). Si el script no soporta filtrado por sucursal, decirle al usuario y pedir guía antes de aplicar.

---

## 4. Catálogo de tools

| Tool | Categoría | Mutadora | Propósito |
|---|---|---|---|
| `list_accounts` | accounts | no | Lista cuentas (Marca + 49 sucursales). |
| `get_account` | accounts | no | Detalle de una cuenta por `location_id`. |
| `get_global_metrics` | metrics | no | Totales globales (contactos, opps, etc.). |
| `get_account_metrics` | metrics | no | Métricas de una sucursal. |
| `search_contacts` | contacts | no | Busca por nombre/email/teléfono en cache SQLite. |
| `get_contact` | contacts | no | Detalle de contacto por id. |
| `get_opportunities` | opps | no | Opps de una location (opcional `pipeline_id`). |
| `get_pipelines` | opps | no | Pipelines/etapas de una location. |
| `get_workflows` | workflows | no | Workflows de una location o de todas. |
| `sync_missing_contacts` | sync | **sí** | Sucursal→Marca contactos faltantes. |
| `sync_missing_opps` | sync | **sí** | Sucursal→Marca opps faltantes. |
| `sync_logs` | ops | no | Logs recientes de sync. |
| `error_logs` | ops | no | Errores recientes. |
| `agent_audit_report` | ops | no | Reporte de salud agentica. |
| `script_catalog` | ops | no | Inventario completo de scripts y tools. |
| `run_script` | advanced | **sí** | Ejecuta cualquier script de `scripts/` (subprocess). |

### 4.1 Cómo invocar `run_script`

Es la salida para los ~60 scripts no expuestos como tool dedicada. Pasa el
nombre (con o sin `.py`), los args como lista de strings, y opcionalmente
`expect_json=True` si el script soporta `--json` (vuelca a archivo si es grande).
Si el script muta GHL debes pasar `apply=True` + `confirm_token`, además de
los flags propios del script (típicamente `--apply --run-id <uuid>`).

Ejemplos:

```jsonc
// Auditoría read-only con JSON
{ "name": "audit_brand_vs_branches_totals", "args": ["--json"], "expect_json": true }

// Mutador: requiere confirm_token MCP + flags del script
{
  "name": "cleanup_cross_branch_duplicates",
  "args": ["--apply", "--yes", "--run-id", "<uuid>"],
  "apply": true,
  "confirm_token": "I-HAVE-USER-CONFIRMATION"
}
```

---

## 5. Manejo de payloads grandes

Cualquier tool puede devolver una de estas dos formas:

```jsonc
// Payload chico: inline
{ "ok": true, "summary": {...}, "details": [...] }

// Payload grande (>8KB serializado): offload a disco
{ "ok": true, "summary": {...}, "report_path": "generated/agent/runs/<tool>_<ts>.json" }
```

Cuando recibas `report_path`, lee el archivo solo si necesitas detalles
específicos. El `summary` está pensado para que decidas si vale la pena.

---

## 6. Recetas frecuentes

### 6.1 Investigar discrepancia de totales Marca vs sucursales

1. `get_global_metrics` para ver el descuadre.
2. `run_script("audit_brand_vs_branches_totals", ["--json", "--show-missing"], expect_json=true)`.
3. Si el reporte sugiere contactos faltantes, `sync_missing_contacts(apply=false)` para preview.
4. Pedir confirmación al usuario → `sync_missing_contacts(apply=true, confirm_token=...)`.

### 6.2 Buscar un contacto y revisar sus oportunidades

1. `list_accounts` para tener la lista de location_ids.
2. `search_contacts(location_id="GbKkBpCmKu2QmloKFHy3", query="<nombre/teléfono>")`.
3. `get_contact(location_id, contact_id)` para detalle.
4. `get_opportunities(location_id)` filtrando por contacto en cliente.

### 6.3 Revisar duplicados cross-branch antes de cleanup

1. `run_script("find_cross_branch_duplicates", ["--json"], expect_json=true)`.
2. Revisar la jerarquía de resolución (ver CLAUDE.md sección "Duplicate resolution rules" en memory).
3. `run_script("cleanup_cross_branch_duplicates", ["--apply", "--yes", "--run-id", "<uuid>"], apply=true, confirm_token="I-HAVE-USER-CONFIRMATION")`.

---

## 7. Reglas críticas heredadas (resumen)

Detalle completo en `CLAUDE.md`, `AGENTS.md` y entries de memory. Lo
indispensable para no romper nada:

- **Cuenta Marca**: `GbKkBpCmKu2QmloKFHy3` (Monte Providencia). Hardcoded en
  `sync_engine.py` y `scripts/common.py` como `BRAND_LOCATION_ID`.
- **Sucursales**: 49 locations adicionales del CSV de tokens.
- **Dirección del sync**: contactos bidireccional Marca↔Sucursal, opps
  unidireccional Sucursal→Marca (la sucursal manda).
- **Custom fields dinámicos**: nunca hardcodees IDs; usa `common.SchemaResolver`.
- **Marca de producto**: nuestro CRM se llama **Bucéfalo**. No mencionar "Go
  High Level" en interfaces de usuario.
- **Servicios E3**: solo digital. No marketing tradicional, no diseño para imprenta.
- **Multi-opp gap**: la replicación Sucursal→Marca (via n8n) solo replica la
  primera opp por contacto. Las opps adicionales (multi-empeño) no llegan a
  Marca automáticamente — para eso existe `sync_missing_opps`.
- **n8n realtime**: la replicación Marca→Sucursal en tiempo real la hace el
  workflow n8n `[1604]`, NO el sync batch.
- **sincorreo@gmail.com**: placeholder de contacto sin correo, causa falsos
  matches por email en audits.

---

## 8. Estado del ecosistema (auto-actualizado)

El comando `python scripts/audit_agent_readiness.py` genera
`generated/agent/audit_report.json` con:

- Inventario completo de scripts (77) y su compliance con las convenciones.
- Endpoints FastAPI con clasificación tool-safe.
- Decisión sugerida para huérfanos (no registrados en `SCRIPTS_METADATA`).
- Issues detectados (mutadores sin `--apply`, sin `--run-id`, sin `--json`,
  docstrings sin header).

**Snapshot inicial** (2026-05-27): 77 scripts, 49 registrados, 26 huérfanos,
59 endpoints (57 tool-safe), 157 issues totales. La normalización masiva de
issues está fuera de alcance de esta capa MCP — el reporte sirve como gate
para futuras contribuciones.

---

## 9. Convención para scripts nuevos

Para que un script nuevo sea tool-safe automáticamente:

```python
"""<oneliner del script>.

Category: audit | sync | cleanup | fix | migrate | search | browser
Mutates: yes | no
Tool-safe: yes | no
"""

# Args estándar (mutadores):
#   --dry-run / --apply
#   --run-id <uuid>
#   --json
# Exit codes:
#   0 éxito
#   1 error
#   2 dry-run con hallazgos accionables
```

Si es read-only soportar `--json` lo hace consumible por el LLM con
`run_script(..., expect_json=True)`. Si es mutador, además registrar cambios
en `script_audit` para que el rollback del dashboard funcione.

---

## 10. Troubleshooting

| Síntoma | Diagnóstico |
|---|---|
| El server no aparece en Claude Code | Verifica `.mcp.json` en la raíz y que `python -m mcp_server` corra manualmente sin error. |
| `mcp` no instalado | `python -m pip install -r requirements.txt`. |
| Tool devuelve `audit_report.json no existe` | Corre `python scripts/audit_agent_readiness.py`. |
| `apply=True` rechazado | Te falta `confirm_token="I-HAVE-USER-CONFIRMATION"`. Es a propósito. |
| Sync timeout | Subir `timeout_sec` en `run_script`, o lanzar el script directo y monitorear `script_runs`. |
| Resultados inconsistentes con dashboard | El cache SQLite puede estar stale; correr `sync_all_accounts` (via endpoint o script) antes de auditar. |
