Files
task-stream/docs/06-desarrollo.md
Josep Piguillem 9236cb73c6 feat(mcp): MCP server for AI clients, consuming the REST API
New app apps/mcp (NestJS + @nestjs-mcp/server 2): Streamable HTTP at
POST /mcp, stateless.

- 24 tools: projects (mine, public, details with settings, board, stats,
  analytics, activity), tickets (my work, search, details with comments and
  history, create, update, move, assign, release, comment, link), releases
  (list, upcoming, details with progress and burnup, create, publish) and
  users (whoami, search). Admin operations stay in the web.
- No credentials of its own: each call's Authorization header (a Pireon
  token) is forwarded to the API; without it only public projects are read.
- Typed REST client generated from apps/api/openapi.json (openapi-fetch +
  openapi-typescript via `nx run mcp:openapi-types`); API refusals become
  readable tool errors; parameters validated with zod.
- Tests: the real MCP against a simulated API (token forwarding, errors,
  validation, writes).
- Docker target `mcp` (Node 24) and an optional compose service under the
  `mcp` profile; zod declared as a root dependency.
- Docs: chapter 9 «Servidor MCP», references in 01/06/07 and the index;
  Word/PDF regenerated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 18:59:38 +02:00

7.4 KiB

6. Desarrollo

6.1 Requisitos

  • Node 22 y npm 10.

  • Docker (PostgreSQL, MongoDB y Redis).

  • Acceso al Nexus de Pireon para @pi/ui. El scope está en .npmrc (sin token); el token va en tu ~/.npmrc:

    npm config set //nexus.pireon.dev/repository/npm-releases/:_authToken "<token>"
    

6.2 Puesta en marcha

npm install
docker compose up -d postgres mongo redis
cp .env.example .env            # ajusta PIREON_*
npx nx serve api                # http://localhost:3000 (GraphiQL en /graphql)
npx nx serve web                # http://localhost:4200

nx serve web usa apps/web/proxy.conf.json para enviar /api y /graphql (también el WebSocket) a la API, así que la web y la API comparten origen igual que en Docker.

6.3 Comandos

Comando Qué hace
npx nx run-many -t lint Lint de todo, incluidas las reglas de capas y contextos
npx nx run-many -t test Tests unitarios (dominio y aplicación, API y web)
npx nx e2e api Flujo completo de la API contra Docker, con Pireon simulado
npx nx run-many -t build Builds de la API y la web
npx nx run web:codegen Regenera los tipos GraphQL del cliente desde apps/api/schema.gql
npx nx serve mcp Servidor MCP en http://localhost:3010/mcp, contra la API local (MCP)
npx nx run mcp:openapi-types Regenera los tipos REST del MCP desde apps/api/openapi.json
npx nx run api:prune Build de la API con package.json y lockfile podados (lo usa Docker)
npx nx graph Grafo de dependencias entre proyectos
npx nx test api-tracker-domain Tests de una librería concreta (api-<contexto>-<capa>, web-<contexto>-<capa>)

Tests

Dónde Qué se prueba Herramienta
api/*/domain Reglas de los agregados y reconstrucción desde el historial de eventos Jest
api/*/application Commands contra un event store en memoria (incluida la concurrencia) Jest + @nestjs/testing
apps/api/e2e Todo el flujo: GraphQL → Postgres → Redis → Mongo → subscriptions Jest contra Docker
web/*/domain, web/*/application Sesión y reducers del tablero Jest / Vitest
apps/web Rutas y guards Vitest

El e2e sustituye a Pireon por usuarios fijos (token-ana, token-bob…) con overrideProvider(IdentityProvider); el resto de la pila es real.

6.4 Cómo añadir una funcionalidad

Ejemplo: marcar un ticket como bloqueado con un motivo. El recorrido es siempre el mismo, de dentro hacia fuera.

API

  1. Dominio (libs/api/tracker/domain)
    • Evento issue-blocked.event.ts con @AutowiredEvent, que extiende TrackerEvent.
    • En Issue, el método block(actorId, reason) valida y hace this.apply(new IssueBlockedEvent(…)); el handler onIssueBlockedEvent(event) cambia el estado.
    • Test en issue.spec.ts, incluida la reconstrucción desde el historial (replay).
  2. Aplicación (libs/api/tracker/application)
    • commands/block-issue.command.ts y block-issue.command-handler.ts: rehidratar (WriteAccess), llamar al dominio y commitAndWait(). Registrar el handler en command-handlers.ts.
    • Método en IssuesService.
    • Proyección event-handlers/issue-blocked.event-handler.ts (@EventsHandler) que actualiza el read model y avisa por ReadModelNotifier. Añadir el evento al ActivityEventHandler y una frase en activity-summary.ts.
    • Si el read model cambia, añadir el campo a IssueReadModel (dominio) y al esquema Mongo (infraestructura).
  3. Presenters (libs/api/tracker/presenters): input y mutation blockIssue en IssueResolver, que llama al service.
  4. Arrancar la API para regenerar apps/api/schema.gql.

Web

  1. npx nx run web:codegen tras añadir la mutation a issues.graphql.
  2. Dominio: método en el puerto IssuesGateway. Infraestructura: implementarlo en GraphqlIssuesGateway.
  3. Aplicación: evento blockRequested en issuePageEvents, su efecto en IssueStore (runCommand) y el método block(reason) en IssueFacade.
  4. Presenters: botón y formulario en la página del ticket que llaman a facade.block(reason).

El lint impide atajos: si un resolver intenta usar un repositorio o una página intenta usar Apollo, falla. Y como application solo exporta facades, una página tampoco puede tocar un store ni despachar eventos por su cuenta.

6.5 Problemas conocidos

Síntoma Causa y solución
npm install falla con Cannot read properties of null (reading 'edgesOut') Bug de npm con un peer opcional de Vite que arrastra vitest 5. El overrides de package.json fija vitest; no lo quites
401 al instalar @pi/ui Falta el token del Nexus en ~/.npmrc (o NEXUS_TOKEN al construir con Docker)
failed to stat /root/.npmrc al desplegar Versión antigua del docker-compose.yml: ahora el token llega por NEXUS_TOKEN (ver Despliegue)
nx serve web arranca en el puerto 3000 Nx carga el .env raíz (PORT=3000) en todas las tareas; apps/web/.env lo fija en 4200
La API no ve un evento nuevo al deserializar La clase del evento no tiene @AutowiredEvent o no se importa
Un componente de @pi/ui lanza Unable to find icon Falta registrar ese icono en libs/web/shared/ui/src/lib/icons.ts
Tablas antiguas (projects, issues, outbox) en tu Postgres local Restos de una versión anterior sin event sourcing; docker compose down -v empieza de cero
El login con Pireon falla (unauthorized_client, client_secret…) Configuración de los clientes OAuth: ver Si el login falla