mcp-asdeporte
README.md
# mcp-asdeporte
MCP server NestJS (`@rekog/mcp-nest`) que expone tools de **solo lectura** sobre `ms-event-v2` y `ms-inscription-v2`.
## Stack
- NestJS 10 + `@rekog/mcp-nest` (Streamable HTTP en `/mcp`)
- Hexagonal: `src/<module>/{domain,application,infrastructure,controllers}`
- Auth chatbot → MCP: header `x-api-key` = `MCP_INBOUND_APIKEY`
- Auth MCP → micros: header `x-api-key` = `MCP_APIKEY`
- Observabilidad: `nestjs-pino`, `x-correlation-id`
- Rate limit: `@nestjs/throttler` (default 60 req / 60s)
## Setup
```bash
cp .env.example .env
# Completar EVENT_BASE_URL, INSCRIPTION_BASE_URL, MCP_APIKEY, MCP_INBOUND_APIKEY, CURSOR_API_KEY
npm install
npm run start:dev
```
Endpoint MCP: `POST http://localhost:3100/mcp`
## Chat (Olimpia)
Asistente HTTP que usa **Cursor SDK** + tools MCP (solo lectura). Mismo proceso que `/mcp`.
| Endpoint | Respuesta |
|----------|-----------|
| `POST /chat/ask` | JSON `{ answer, runId, agentId }` |
| `POST /chat/ask/stream` | SSE: `token` \| `done` \| `error` |
**Auth:** header `x-api-key` = `MCP_INBOUND_APIKEY`
**Body:** `{ "question": string, "userid": uuid }`
**Env:** `CURSOR_API_KEY` (obligatorio), `CURSOR_MODEL`, `MCP_PUBLIC_URL`, `CHAT_TIMEOUT_MS` (default 120s)
**Node:** ≥ 22.13 (`@cursor/sdk`)
**Rate limit:** mismo throttler global (60 / 60s)
```bash
# Sync
curl -s -X POST http://localhost:3100/chat/ask \
-H "x-api-key: $MCP_INBOUND_APIKEY" \
-H 'Content-Type: application/json' \
-H 'x-correlation-id: demo-1' \
-d '{"question":"¿Cuántos inscritos hay en Minions 2026?","userid":"62167a2d-d0eb-4f3e-a3ca-42cc4144465b"}'
# Stream (SSE)
curl -N -X POST http://localhost:3100/chat/ask/stream \
-H "x-api-key: $MCP_INBOUND_APIKEY" \
-H 'Content-Type: application/json' \
-d '{"question":"¿Cuántos inscritos hay en Minions 2026?","userid":"62167a2d-d0eb-4f3e-a3ca-42cc4144465b"}'
```
SSE `done.data` es JSON stringificado con `{ answer, runId, agentId }`. Si el client corta la conexión, el run se cancela.
## Probar con MCP Inspector
Requiere **Node.js ≥ 22.7.5** (requisito del Inspector). El MCP debe estar corriendo (`npm run start:dev`).
### UI (recomendado)
```bash
npm run inspector
```
Abre `http://localhost:6274`. En la UI:
1. Transport: **Streamable HTTP**
2. URL: `http://localhost:3100/mcp` (preconfigurado vía `inspector/mcp.json`)
3. Auth: header name `x-api-key`, value = tu `MCP_INBOUND_APIKEY` del `.env`
4. Connect → Tools → List Tools / Call Tool
### CLI (smoke)
```bash
# Lista tools (usa MCP_INBOUND_APIKEY del .env)
npm run inspector:list
# Health check
npm run inspector:ping
# Cualquier tool
node scripts/inspector-cli.mjs tools/call event_get --tool-arg id=<uuid>
```
## Tools
Catálogo de **solo lectura**. El chatbot (o Inspector) elige la tool y manda los args; no hay mutaciones.
| Tool | Para qué |
|------|----------|
| `mcp_ping` | ¿El MCP está vivo? |
| `event_search` | Buscar eventos por texto / filtros (Typesense) |
| `event_get` | Detalle de un evento (UUID o uri) |
| `event_list` | Listado paginado de eventos (GraphQL) |
| `event_distances` | Distancias / modalidades de un evento |
| `event_convocatories` | Secciones de convocatoria |
| `inscription_get` | Detalle de una inscripción (UUID **o** slug) |
| `inscriptions_by_event` | Inscritos de un evento (filtros + paginación) |
| `inscriptions_by_user` | Inscripciones de un usuario |
| `inscription_search` | Buscar inscritos en un evento por texto libre |
| `is_inscribed` | ¿Usuario X está inscrito en evento Y? |
| `inscription_totals` | Conteo de inscritos por eventid(s) |
| `user_next_competitions` | Próximas competencias de un usuario |
| `user_sport_types` | Deportes / tipos de evento del usuario |
| `inscription_recovery` | Recuperar inscripción por nombre + fecha + evento |
| `cart_get` | Detalle de un carrito (lean, sin secretos de pago) |
| `carts_by_user` | Historial de carritos de un usuario |
### Flujo típico (chatbot)
1. Buscar evento → `event_search` / `event_list`
2. Detalle / distancias / convocatoria → `event_get`, `event_distances`, `event_convocatories`
3. Totales o listado de inscritos → `inscription_totals`, `inscriptions_by_event` / `inscription_search`
4. Persona concreta → `inscription_get` (slug o UUID) o `is_inscribed` / `inscriptions_by_user`
5. Usuario autenticado → `user_next_competitions`, `user_sport_types`; recuperación → `inscription_recovery`
6. Carrito / compra → `cart_get`, `carts_by_user`
---
### `mcp_ping`
Sin parámetros. Responde `{ status: "ok", service: "mcp-asdeporte" }`.
**Preguntas:** ¿Está arriba el MCP? / health check.
```bash
npm run inspector:ping
```
---
### `event_search`
Búsqueda full-text Typesense. Respuesta lean: `found`, `page`, `hits[]` (nombre, uri, fechas, precios flags, etc.).
| Arg | Req | Notas |
|-----|-----|--------|
| `q` | sí | Texto. Usa `*` si solo filtras |
| `filter_by` | no | Ej. `inscription_allow:true && city:CDMX` |
| `sort_by` | no | Ej. `epoch_date:desc` |
| `query_by` | no | Campos a buscar |
| `page` / `per_page` | no | Paginación (default 20, max 250) |
**Preguntas ejemplo:**
- ¿Hay eventos de Minions / maratón / ciclismo?
- Eventos en CDMX con inscripción abierta
- Próximas carreras ordenadas por fecha
```bash
node scripts/inspector-cli.mjs tools/call event_search --tool-arg 'q=minions' --tool-arg per_page=5
node scripts/inspector-cli.mjs tools/call event_search --tool-arg 'q=*' --tool-arg 'filter_by=inscription_allow:true'
```
---
### `event_get`
Detalle de un evento por UUID o `uri`.
| Arg | Req |
|-----|-----|
| `id` | sí — UUID o uri |
**Devuelve (lean):** nombre, uri, dirección, fechas, precios, currency, sports, inscription_allow, published, short_description, organizationid…
**Preguntas ejemplo:**
- Dame el detalle del evento Minions
- ¿Cuánto cuesta / está publicada la inscripción?
- ¿Cuál es la uri / fechas del evento X?
```bash
node scripts/inspector-cli.mjs tools/call event_get --tool-arg id=minions-run-cdmx-2026-2rv
node scripts/inspector-cli.mjs tools/call event_get --tool-arg id=<uuid>
```
---
### `event_list`
Listado GraphQL paginado (alternativa más “admin” a Typesense).
| Arg | Req |
|-----|-----|
| `keyword` | no |
| `organizationid` | no — UUID org |
| `page` / `perpage` | no |
**Preguntas ejemplo:**
- Lista eventos de la organización X
- Busca por keyword “étape”
---
### `event_distances`
Modalidades / distancias de un evento (stock, unidad, metros, disabled).
| Arg | Req |
|-----|-----|
| `event` | sí — UUID o uri |
**Preguntas ejemplo:**
- ¿Qué distancias tiene Minions? (3k / 5k / 10k…)
- ¿Queda stock en la de 10 km?
```bash
node scripts/inspector-cli.mjs tools/call event_distances --tool-arg event=<uuid-o-uri>
```
---
### `event_convocatories`
Secciones e items de convocatoria (HTML/contenido editorial).
| Arg | Req |
|-----|-----|
| `eventOrUri` | sí — UUID o uri |
| `lang` | no — ej. `es`, `en` |
**Preguntas ejemplo:**
- ¿Cuál es la ruta / entrega de kit / premiación?
- Muéstrame la convocatoria en inglés
- ¿Hay EXPO o beneficios plus?
```bash
node scripts/inspector-cli.mjs tools/call event_convocatories --tool-arg eventOrUri=<uri> --tool-arg lang=es
```
---
### `inscription_get`
Una inscripción. **Exactamente un filtro:** `inscriptionid` (UUID) **o** `slug` (nº de compra).
| Arg | Req |
|-----|-----|
| `inscriptionid` | uno de los dos |
| `slug` | uno de los dos — ej. `ASDLNN2JFGZ9Q` |
**Devuelve (lean):** status, participante, email, número, montos, distanceid, categoryid, eventid, created_at…
**Preguntas ejemplo:**
- Dame la inscripción con slug ASD…
- Detalle de la inscripción `<uuid>`
- ¿Está paid o pending_payment?
```bash
node scripts/inspector-cli.mjs tools/call inscription_get --tool-arg slug=ASDLNN2JFGZ9Q
node scripts/inspector-cli.mjs tools/call inscription_get --tool-arg inscriptionid=<uuid>
```
---
### `inscriptions_by_event`
Listado paginado de inscritos de un evento.
| Arg | Req | Notas |
|-----|-----|--------|
| `eventid` | sí | UUID del evento |
| `status` | no | `paid`, `cancelled`, `pending_payment`, `created`, `payment_declined` |
| `userid` | no | Filtrar un usuario |
| `filter` | no | Texto: nombre, email, folio, número |
| `page` / `perpage` | no | Default 10 |
**Preguntas ejemplo:**
- Lista 10 inscritos paid del Minions
- Busca al inscrito “Servando” en el evento X
- ¿Cuántos pending_payment hay? (ver `total` de la respuesta)
```bash
node scripts/inspector-cli.mjs tools/call inscriptions_by_event \
--tool-arg eventid=<uuid> --tool-arg status=paid --tool-arg perpage=5
```
---
### `inscriptions_by_user`
Historial de inscripciones de un usuario (el chatbot debe tener el `userid`).
| Arg | Req |
|-----|-----|
| `userid` | sí |
| `status` | no |
| `from` / `to` | no — fechas ISO |
| `eventname` | no |
| `page` / `perpage` | no |
**Preguntas ejemplo:**
- ¿En qué eventos está inscrito el user Y?
- Inscripciones pagadas de Y en 2026
- Filtra por nombre de evento “Étape”
```bash
node scripts/inspector-cli.mjs tools/call inscriptions_by_user --tool-arg userid=<uuid> --tool-arg perpage=10
```
---
### `is_inscribed`
¿El usuario tiene inscripción(es) en ese evento? Devuelve array (vacío = no).
| Arg | Req |
|-----|-----|
| `userid` | sí |
| `eventid` | sí |
**Preguntas ejemplo:**
- ¿El user Y está inscrito en Minions?
- Confirma inscripción user+evento antes de otra acción
```bash
node scripts/inspector-cli.mjs tools/call is_inscribed \
--tool-arg userid=<uuid> --tool-arg eventid=<uuid>
```
---
### `inscription_totals`
Totales de inscritos por uno o más eventos.
| Arg | Req |
|-----|-----|
| `eventids` | sí — array de UUIDs |
**Preguntas ejemplo:**
- ¿Cuántos inscritos tiene Minions?
- Compara totales de estos 3 eventos
```bash
node scripts/inspector-cli.mjs tools/call inscription_totals \
--tool-arg 'eventids=["d09a20b1-90ee-474b-85bd-d5fac1772525"]'
```
---
### `inscription_search`
Búsqueda de inscritos **dentro de un evento** por texto libre (nombre, email, folio, número). Preferir frente a `inscriptions_by_event` cuando se busca a una persona.
| Arg | Req | Notas |
|-----|-----|--------|
| `filter` | sí | Texto de búsqueda |
| `eventid` | sí | UUID del evento |
| `page` / `perpage` | no | Default 10 |
**Preguntas ejemplo:**
- Busca “Servando” en el Minions
- ¿Hay alguien con folio … en el evento X?
```bash
node scripts/inspector-cli.mjs tools/call inscription_search \
--tool-arg filter=Servando --tool-arg eventid=<uuid>
```
---
### `user_next_competitions`
Próximas competencias del usuario (eventos futuros con inscripción). El chatbot debe tener el `userid`.
| Arg | Req |
|-----|-----|
| `userid` | sí |
| `page` / `perpage` | no |
**Devuelve (lean):** inscriptionid, status, eventid, fecha, nombre oficial, uri, address…
**Preguntas ejemplo:**
- ¿Cuáles son mis próximas carreras?
- Próximos eventos del user Y
```bash
node scripts/inspector-cli.mjs tools/call user_next_competitions \
--tool-arg userid=<uuid>
```
---
### `user_sport_types`
Deportes / tipos de evento en los que el usuario se ha inscrito (con conteos).
| Arg | Req |
|-----|-----|
| `userid` | sí |
**Devuelve:** `events_total` + lista `sports` (`sportid`, `name`, `total`).
**Preguntas ejemplo:**
- ¿En qué deportes corre el user Y?
- Resumen de tipos de evento del usuario
```bash
node scripts/inspector-cli.mjs tools/call user_sport_types --tool-arg userid=<uuid>
```
---
### `inscription_recovery`
Recupera una inscripción por **nombre completo + fecha de nacimiento + evento**. Sensible: el chatbot debe verificar identidad antes de llamar.
| Arg | Req | Notas |
|-----|-----|--------|
| `full_name` | sí | Como está registrado |
| `birthdate` | sí | ISO, ej. `1978-06-13` o datetime completo |
| `eventid` | sí | UUID del evento |
**Preguntas ejemplo:**
- Recupera mi inscripción al Minions: Juan Pérez, 1978-06-13
```bash
node scripts/inspector-cli.mjs tools/call inscription_recovery \
--tool-arg full_name="Juan Perez" \
--tool-arg birthdate=1978-06-13T12:00:00.000Z \
--tool-arg eventid=<uuid>
```
---
### `cart_get`
Detalle lean de un carrito: status, montos, inscripciones, federaciones, store. **Sin** tarjetas, SPEI, URLs de gateway ni customer ids.
| Arg | Req |
|-----|-----|
| `cartid` | sí — UUID |
**Preguntas ejemplo:**
- ¿En qué status está el carrito X?
- ¿Qué inscripciones trae este carrito?
```bash
node scripts/inspector-cli.mjs tools/call cart_get --tool-arg cartid=<uuid>
```
---
### `carts_by_user`
Historial paginado de carritos de un usuario.
| Arg | Req |
|-----|-----|
| `userid` | sí |
| `page` / `perpage` | no |
**Preguntas ejemplo:**
- ¿Qué carritos tiene el user Y?
- Últimos carritos pending_payment de Y
```bash
node scripts/inspector-cli.mjs tools/call carts_by_user --tool-arg userid=<uuid>
```
---
### Fuera de alcance (hoy)
No hay tools de escritura ni de: facturas, ratings, goals, transacciones, import CSV, shipping, dependientes, etc. Solo lectura event + inscription (+ cart lean) según la tabla de arriba.
## Hardening
- Upstream timeout (`UPSTREAM_TIMEOUT_MS`) → `504`
- Chat timeout (`CHAT_TIMEOUT_MS`, default 120s) → `504` / SSE `error`; cancela el run Cursor
- Upstream 401/403 → `401`; 5xx → `502`
- Correlation: header `x-correlation-id` (generado si falta; logueado en chat con `agentId` / `runId`)
- Rate limit: `@nestjs/throttler` en `/mcp` y `/chat` (mismo window)
- Chat stream: abort al `req.close` → `run.cancel()` + dispose del agent
## Estructura
```
src/
shared/ # config, http clients, guards, interceptors, ping
event/ # tools event_* (hexagonal)
inscription/ # tools inscription_* (hexagonal)
chat/ # Olimpia: /chat/ask + /chat/ask/stream (Cursor + MCP)
inspector/ # config MCP Inspector (Streamable HTTP)
scripts/ # wrapper CLI Inspector + x-api-key
```
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessNo issues