Skip to main content
Glama
LeadFunnel MCP

LeadFunnel MCP

by UcemaRepo
README.md
# LeadFunnel MCP — Render

Servidor MCP con la sesión en **RAM del proceso**. Sin base, sin disco, sin cache.

```
POST /cargar   →  Salesforce → depura → scorea → RAM
POST /mcp      →  Claude consulta (lee de RAM)
POST /purgar   →  RAM = null
GET  /health   →  ¿está vivo? ¿hay sesión?
```

Cuando Render apaga el servicio por inactividad, el proceso muere y los datos
se van con él. Esa es la garantía de efimeridad — no depende de un TTL.

---

## Deploy

1. Subí este repo a GitHub (privado).
2. Render → **New → Web Service** → conectá el repo.
3. Configuración:
   - Runtime: **Node**
   - Build command: `npm install`
   - Start command: `npm start`
   - Health check path: `/health`

### Variables de entorno

| Variable | Valor |
|---|---|
| `SF_DOMAIN` | `ucema2.my.salesforce.com` |
| `SF_CLIENT_ID` | consumer key de la Connected App |
| `SF_CLIENT_SECRET` | consumer secret |
| `MCP_TOKEN` | `openssl rand -hex 32` |
| `ENMASCARAR_PII` | `true` (poné `false` solo si necesitás el dato completo) |
| `KEEPALIVE` | opcional: `false` desactiva el auto-ping (ver Limitaciones) |
| `KEEPALIVE_MINUTOS` | opcional: intervalo del auto-ping, default `4` |

---

## Prueba

```bash
# Salud
curl https://TU-APP.onrender.com/health

# Cargar (tarda: hace todo el pull de Salesforce)
curl -X POST https://TU-APP.onrender.com/cargar \
  -H "Authorization: Bearer $MCP_TOKEN"

# Handshake MCP
curl -X POST "https://TU-APP.onrender.com/mcp?k=$MCP_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}'

# Purgar
curl -X POST https://TU-APP.onrender.com/purgar \
  -H "Authorization: Bearer $MCP_TOKEN"
```

El header `Accept` con `text/event-stream` es **obligatorio** en Streamable HTTP.
Sin él, el SDK rechaza el request.

Inspector oficial antes de conectar a Claude:
```bash
npx @modelcontextprotocol/inspector
```

---

## Conciliación (Salesforce vs. sesión)

`GET /conciliacion?desde=2026-01-01` compara los leads que hay ahora en
Salesforce contra los cargados en la sesión (requiere `/cargar` antes).
En Windows: `scripts\conciliacion.bat [AAAA-MM-DD] [resumen]`. Pide el token
o lo toma de la variable `MCP_TOKEN`; guarda la respuesta en un `.json`.

---

## Conectar a Claude

`claude.ai → Customize → Connectors → + → Add custom connector`

URL: `https://TU-APP.onrender.com/mcp?k=TU_TOKEN`

---

## Flujo de trabajo diario

1. `POST /cargar` cuando arrancás
2. Consultás desde Claude todo lo que quieras
3. Si te fuiste un rato largo, Render apagó el servicio: volvé a `/cargar`

---

## Limitaciones conocidas

**Cold start.** En el free tier, tras ~15 min sin tráfico Render apaga el
servicio. La siguiente request lo despierta (~50 s) **y la sesión se perdió**.
Para evitarlo, el servidor se pingea solo cada 4 min a `/` usando la URL
pública (`RENDER_EXTERNAL_URL`, que define Render; ver `src/keepalive.js`).
Ojo: eso mantiene los datos vivos en RAM indefinidamente; usá `/purgar` cuando
no los necesites, o `KEEPALIVE=false` para volver al comportamiento anterior.
Un servicio prendido 24/7 consume ~744 de las 750 h/mes gratis del workspace.

**Token en la URL.** La UI de conectores de Claude no permite headers
arbitrarios. El token en query param es más débil que un header: queda en la
config del conector y potencialmente en logs. El paso siguiente es OAuth 2.0
real, que con el SDK es factible pero es otro proyecto.

**RAM.** 12k leads ≈ 25 MB en objetos JS. El free tier de Render da 512 MB.
Holgado, pero si el dataset creciera mucho habría que paginar.

---

## Qué se guarda

| Campo | Se guarda |
|---|---|
| Nombre y apellido | sí |
| `sfId` | sí (para saltar al registro) |
| DNI | **nunca** |
| Teléfono | enmascarado (`***4821`) |
| Email | enmascarado (`ni***@gmail.com`) |
| Motivación | solo el largo, no el texto |

Con `ENMASCARAR_PII=false` se guardan teléfono y mail completos. Es una decisión
de riesgo consciente: con esa variable en `false`, los datos de contacto de
candidatos reales entran al contexto del modelo en cada consulta.