skool-mcp-kit
by elchamoluso
README.md
# skool-mcp-kit
Servidor **MCP** + **skill** de Claude Code para operar y analizar tu comunidad de **Skool** con tu propia sesión: ver el **calendario de reuniones/eventos**, leer el feed y los posts, consultar la comunidad, el classroom y los miembros, buscar, y **publicar/comentar** en tu nombre (con confirmación). Espejo del patrón del kit de LinkedIn del autor. **Cero dependencias npm** (Node ≥ 18, `fetch` global).
> ⚠️ Skool no tiene API pública: esto usa su API interna vía tu sesión autenticada. Va contra el ToS de Skool; úsalo solo con **tu** cuenta, a **ritmo humano**, asumiendo el riesgo. No afiliado a Skool.
## Arquitectura
- **Lectura (primaria):** rutas SSR de Next.js `www.skool.com/_next/data/{buildId}/…json` con las cookies de sesión (`self` poblado = autenticado). El `buildId` se scrapea en vivo.
- **api2.skool.com:** búsqueda de miembros, comentarios de un post, metadata pública y las **escrituras** (`POST /posts`, `POST /comments`).
- **Auth:** cookies de Skool en `~/.skool-mcp/session.json` (chmod 600), nunca en el repo. La que
autentica es **`auth_token`**, un JWT que **dura un año** (su claim `exp`; medido el 2026-09-04,
exactamente 365 días desde el login). `client_id` y `aws-waf-token` acompañan.
**Medido el 2026-09-04:** las lecturas SSR funcionan con **`auth_token` a secas** — sin
`aws-waf-token` y sin `client_id`. O sea que la sesión es tan estable como la de un token de API:
se siembra una vez y aguanta el año. El `aws-waf-token` solo importa si el WAF llega a plantar un
challenge, y entonces hace falta un navegador para acuñar uno nuevo.
## Instalación
```bash
git clone https://github.com/elchamoluso/skool-mcp-kit.git && cd skool-mcp-kit
bash install.sh # copia la skill + registra el MCP (scope user)
# Sesión (una vez por máquina):
node bin/skool-session.js seed --from <storageState.json> # desde un state de agent-browser/Playwright
# o: node bin/skool-session.js login # login interactivo en navegador
node bin/skool-session.js status # verifica token_valid
# Reinicia Claude Code para cargar el server MCP.
```
Registro manual (si no usas el installer): copia `configs/claude-code.mcp.json` a tu `.mcp.json`, o:
```bash
claude mcp add skool --scope user --transport stdio --env SKOOL_COMMUNITY=ailinkvip -- node "$PWD/skool-mcp-server.js"
```
## Herramientas (`mcp__skool__*`)
| Tool | Qué hace |
|---|---|
| `skool_status` | Valida la sesión y devuelve tu identidad + comunidad. |
| `skool_community_info` | Nombre, descripción, dueño, totales, pestañas, enlaces. |
| `skool_calendar` | **Calendario de eventos/reuniones** (por defecto próximas). `from`/`to`/`next`/`limit`. |
| `skool_event` | Detalle de un evento por `event_id` (+ `occurrence_id`). |
| `skool_feed` | Feed de posts (`page`, `sort`, `label`, `pinned_only`). |
| `skool_post` | Un post por `post_slug` + su hilo de comentarios. |
| `skool_members` | Directorio paginado (email omitido salvo `include_email:true`). |
| `skool_search` | Busca miembros por nombre. |
| `skool_classroom` | Cursos; con `course_id`, resumen del curso. |
| `skool_create_post` ⚠️ | Publica un post (`content`, `title?`, `label?`, `confirm:true`). |
| `skool_create_comment` ⚠️ | Comenta un post (`post_slug`\|`post_id`, `content`, `confirm:true`). |
Las **escrituras exigen `confirm:true`** (gate en el server) además de los guardrails de la skill (confirmación por acción, sin bulk, topes diarios, ritmo humano, stop-on-checkpoint).
## CLI de sesión (`bin/skool-session.js`)
`seed --from <state>` · `status` · `login` (navegador headed → re-siembra y refresca el `aws-waf-token`) ·
**`login --auto`** (desatendido, ver abajo) · `refresh` (re-siembra desde el perfil ya logueado, **sin
abrir navegador**) · `logout`.
> La captura de cookies prueba `cookies get --json` y, si no trae `auth_token`, cae a `state save`.
> Hace falta: con el navegador cerrado, `agent-browser 0.33.2` devuelve `success:true` con **cero
> cookies**, y sin `--json` imprime texto plano sin dominio. `refresh` sirve cuando solo caducó el
> `aws-waf-token` y el perfil sigue logueado; si tampoco eso basta, `login`.
## Login desatendido (`login --auto`)
Para que la sesión se renueve **sin que nadie teclee nada**, ni siquiera una vez al año:
```bash
# La contraseña, una vez y en un terminal aparte (no pasa por argv ni por el historial):
read -rs -p "Contraseña de Skool: " P && printf '%s' "$P" > ~/secrets/skool.password \
&& chmod 600 ~/secrets/skool.password && unset P
node bin/skool-session.js login --auto --email tu@correo.com
```
| Variable | Para qué |
|---|---|
| `SKOOL_EMAIL` | El correo de la cuenta (alternativa a `--email`) |
| `SKOOL_PASSWORD_FILE` | Dónde está la contraseña (def `~/secrets/skool.password`) |
| `SKOOL_CODE_COMMAND` | Comando cuyo **stdout es el código de verificación**, por si Skool lo pide. Se reintenta durante ~1 min mientras llega el correo |
**Cómo funciona, y por qué así.** Medido el 2026-09-04 contra la API real: `POST /auth/login` y
`/auth/login-with-code-init` devuelven **403 del WAF** desde fuera del navegador —y también desde un
`fetch` inyectado en la página—, mientras que los GET de la misma API sí pasan. El WAF guarda los POST
de auth y solo los acepta del formulario real. Así que `--auto` abre el navegador **headless**, rellena
`#email` y `#password` por **CDP** y pulsa el botón, que es exactamente lo que hace una persona.
La contraseña se lee de fichero y viaja **dentro del mensaje CDP**: no aparece en `ps`, ni en el
historial, ni en el entorno. Nunca se acepta por flag. Requiere `WebSocket` global (**Node ≥ 22**; en
la 21 va detrás de `--experimental-websocket`); el resto del kit sigue en Node ≥ 18.
`--auto` borra **solo la cookie `auth_token`** antes de entrar, nunca el tarro entero: un perfil con
sesión viva redirige `/login` al feed y no habría formulario que rellenar, y el sentido de `--auto` es
justo renovar mientras la sesión vieja aún vale. Se borra solo esa porque tirar todas las cookies se
lleva por delante el `aws-waf-token`, el `client_id` y la telemetría de visitas anteriores: cada
renovación se presentaría como un dispositivo recién nacido conducido por CDP, que es lo que dispara
el código de verificación y lo que sube la puntuación del antifraude. `session.json` no se toca hasta
que el login funciona: si falla, se conserva la sesión anterior.
**Un login nuevo no invalida el anterior** (comprobado el 2026-09-04: el token viejo seguía
autenticando después). Por eso se puede ensayar el flujo contra un perfil temporal sin tocar la
sesión que esté en uso — útil para enterarse de que el formulario cambió *antes* de necesitarlo.
Ante un rechazo de Skool (contraseña incorrecta, cuenta bloqueada, demasiados intentos) **para y lo
dice**: no reintenta, porque insistir en un login es lo que bloquea una cuenta.
## Sesión caducada / bloqueo del WAF
`status` y la tool `skool_status` dicen **hasta qué día vale la sesión** (`sesionCaduca`,
`diasRestantes`), leyendo el `exp` del JWT sin salir a la red, y avisan solos por debajo de 30 días.
No hay que adivinar cuándo toca renovar.
| Síntoma | Qué es | Qué hacer |
|---|---|---|
| `status` avisa de que quedan pocos días, o `token_valid:false` | El JWT llegó a su año | `login` (navegador headed, login manual) |
| Una lectura devuelve **403** | El WAF plantó un challenge | `login` (o `login --auto`): hace falta **navegar** para que el navegador acuñe un `aws-waf-token` nuevo |
| Redirect a `/login` en una ruta SSR | La sesión no vale para esa comunidad | `status` para confirmar, y `login` |
**Qué hace y qué NO hace `refresh`:** recopia al `session.json` las cookies que ya están en el perfil
de `~/.skool-mcp/browser-profile`. **No navega**, así que no puede acuñar un `aws-waf-token` fresco:
sirve cuando el `session.json` se quedó desincronizado del perfil, no para levantar un bloqueo del
WAF. Para eso hay que entrar: `login` o `login --auto`.
## Créditos
Código propio (molde de plumbing MCP: `nt-mcp-server`). Conocimiento de la API interna de Skool documentado a partir de [`louiewoof2026/skool-mcp`](https://github.com/louiewoof2026/skool-mcp) (`API-DISCOVERY.md`), reimplementado sin copiar código. Licencia **MIT**. Ver `README.en.md` para la versión en inglés.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues