Files
task-stream/docs/09-mcp.md
Josep Piguillem dfb5f973f4 feat: «Mi día», the few tickets I pick for today
A home panel with the tickets picked for today (10 at most), crossed out
as they get done and counted («1 de 3 terminados»), my open tickets to
add with one click, and what was left unfinished on my last day to carry
over. «☀ Mi día» on a ticket adds it or takes it out. The day is the
person's own (YYYY-MM-DD from the browser); private, no history (Mongo
`my_days`).

API: myDay(day), addToMyDay, removeFromMyDay; GET /api/me/day,
PUT/DELETE /api/me/day/{issue}. MCP: my_day.

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

21 KiB

9. Servidor MCP

apps/mcp es un servidor MCP (Model Context Protocol) para que asistentes de IA (Claude Code, Claude Desktop, Cursor…) trabajen con TaskStream: consultar tableros y tickets, crear y mover tickets, comentar, gestionar releases o leer las estadísticas. Está hecho con NestJS y @nestjs-mcp/server 2.x, y consume la API REST como cualquier otra app: con tipos generados desde apps/api/openapi.json.

flowchart LR
  C["Cliente MCP<br/>(Claude…)"] -- "POST /mcp<br/>Authorization: Bearer token" --> M["apps/mcp<br/>(NestJS)"]
  M -- "REST /api/…<br/>mismo token" --> A["apps/api"]
  A -- "valida el token" --> P["Pireon"]

9.1 Cómo funciona

  • Un endpoint: POST /mcp, transporte Streamable HTTP. Sin estado (la versión 2026-07-28 del protocolo retiró las sesiones): cualquier instancia atiende cualquier llamada.
  • Sin credenciales propias: cada llamada lleva su Authorization: Bearer <token de Pireon>, y el MCP la reenvía tal cual a la API. Cada persona ve y puede cambiar exactamente lo que la API le permite (su rol en cada proyecto); sin token, solo se leen los proyectos públicos.
  • Errores legibles: si la API rechaza algo (permiso, regla de negocio, transición no permitida…), la herramienta responde con un error que el modelo puede leer y corregir («Cannot move WEB-16 from Backlog to Hecho. Allowed: Por hacer, En curso, Cancelado»), en vez de cortar la llamada.
  • Validación: los parámetros de cada herramienta se validan con zod antes de llamar a la API.
  • Solo en su dominio: /mcp rechaza (403) las peticiones cuyo Host no esté en MCP_ALLOWED_HOSTS, y las de un navegador cuyo Origin tampoco lo esté. Es la protección contra DNS rebinding que pide la especificación de MCP; los clientes que llaman desde un servidor no envían Origin y solo necesitan el dominio correcto.
  • Respuestas compactas: los listados devuelven lo que el modelo necesita (clave, título, estado, persona…); el detalle completo, con get_issue o get_project.

9.2 Herramientas

60 herramientas, agrupadas por ámbito:

Ámbito Herramienta Qué hace
Usuario whoami El usuario del token
search_users Buscar personas de Pireon (para asignar)
my_notifications Mis notificaciones (por defecto, las no leídas)
mark_notifications_read Marcar como leídas unas o todas
Proyectos list_my_projects Mis proyectos
list_public_projects Proyectos públicos
create_project Crear un proyecto (quien lo crea es su dueño), también copiando la configuración de otro (template)
get_project Equipo y configuración: ids de estados, tipos y áreas que aceptan las demás herramientas
get_project_showcase El escaparate público: hoja de ruta, releases, sprint en curso y tickets para empezar
get_board Tablero por columnas (filtros por persona, release, área, sprint)
get_project_stats Cifras de seguimiento
get_project_analytics Estadísticas de los últimos días (creados/resueltos, flujo, tiempos, throughput, QA)
get_portfolio Todos mis proyectos activos de un vistazo: abiertos y en riesgo, sprint, previsión de releases y épicas
get_my_week Mi semana: lo mío que vence en 7 días, fin de sprints y releases; lo terminado por mí y por mis equipos, racha; sprints en curso y qué podría coger
get_project_insights El panel: sprint, riesgo, antigüedad, cuellos de botella, releases, equipo, backlog, calidad, campos, actividad por día, recorrido de un ticket, propuestas y, con since, los cambios
get_project_activity Actividad reciente, o la de un día (day)
Tickets my_issues Mi trabajo en todos los proyectos (o de cualquiera, por categoría; o los que sigo); también por prioridad, solo bugs o vencidos
search_issues Buscar por texto y filtros (épica, sprint, fechas, campos…); el backlog: noSprint y orden RANK
get_issue Detalle: épica, lista de comprobación, relaciones, commits, seguidores, transiciones, decisiones y comentarios
create_issue Crear un ticket (también colgado de una épica)
update_issue Cambiar título, descripción, tipo, área, prioridad, etiquetas, fechas, campos personalizados…
change_issue_status Mover de estado (con motivo cuando el estado lo pide)
assign_issue Asignar o desasignar
set_issue_release Meter en una release o sacar
add_comment / edit_comment / delete_comment Comentar (o responder a un comentario), corregir o borrar
link_issues / unlink_issues Relacionar tickets (DEPENDS_ON, BLOCKS, TESTS…) o quitar la relación
watch_issue Seguir un ticket o dejar de seguirlo; con tree, una épica con todo lo que cuelga de ella
vote_issue Votar un ticket o quitar el voto
duplicate_issue Crear otro ticket con lo mismo (sin estado, persona ni comentarios)
react_to_comment Reaccionar a un comentario (👍 🎉…) o quitar la reacción
mark_decision Marcar un comentario como decisión del ticket, o quitarla
resolve_thread Resolver una conversación (con conclusión opcional, que queda como decisión) o reabrirla
remind_me Recordármelo: un aviso solo para mí sobre un ticket, a una fecha y hora, con nota (o quitarlo)
my_day Mi día: los tickets elegidos para hoy (y lo que quedó del último día); añadir o quitar uno
search_decisions Qué se decidió: decisiones por su texto, de un proyecto o de todos los míos
add_checklist_item / update_checklist_item / remove_checklist_item La lista de comprobación: añadir, marcar o reescribir, quitar
Organización list_epics Épicas con el progreso de lo que cuelga de cada una
set_issue_parent Colgar un ticket de una épica u otro ticket, o soltarlo
rank_issue Mover un ticket en el backlog
bulk_update_issues El mismo cambio en hasta 100 tickets (por clave); dice cuáles no se dejaron
delete_issue / restore_issue / list_trash Papelera: borrar (30 días para recuperar), recuperar y ver
Sprints list_sprints Sprints de un proyecto
get_sprint Progreso, tickets y burndown
create_sprint / update_sprint / delete_sprint Planificar, cambiar o borrar (si no empezó) un sprint
start_sprint / close_sprint Empezarlo; cerrarlo pasando lo pendiente a otro sprint o al backlog
set_issue_sprint Meter un ticket en un sprint o devolverlo al backlog
Releases list_releases Releases de un proyecto
upcoming_releases Próximas releases de mis proyectos
get_release Progreso, notas, tickets y burnup
create_release / update_release Planificar una release o cambiar su nombre, descripción o fecha
draft_release_notes / set_release_notes Borrador de las notas desde los tickets terminados, y guardarlas
publish_release Publicar (con force si quedan tickets sin terminar)
archive_release Archivar una release que no saldrá; lo pendiente pasa a moveTo (otra release abierta) o la deja

Las de lectura van marcadas como readOnlyHint, para que el cliente no pida confirmación al usarlas, y las que borran o archivan, como destructiveHint, para que la pida. Lo que es de administración (miembros, configuración, visibilidad, borrar proyectos, webhooks, integración con git, importar y exportar CSV) no se expone: se hace desde la web.

9.3 Conectar un cliente

Hace falta un token de Pireon. Con email y contraseña (si la API tiene configurado el cliente password):

curl -s https://api.taskstream.pireon.dev/api/auth/token \
  -H 'content-type: application/json' \
  -d '{"grant_type":"password","username":"tu@email.com","password":"…"}'
# → { "access_token": "…", "refresh_token": "…", "expires_in": … }

Claude Code:

claude mcp add --transport http taskstream https://mcp.taskstream.pireon.dev/mcp \
  --header "Authorization: Bearer $TASKSTREAM_TOKEN"
# en local: http://localhost:3010/mcp

Otros clientes (JSON de configuración):

{
  "mcpServers": {
    "taskstream": {
      "type": "http",
      "url": "https://mcp.taskstream.pireon.dev/mcp",
      "headers": { "Authorization": "Bearer <token de Pireon>" }
    }
  }
}

El token caduca como cualquier sesión de Pireon: cuando la herramienta responda «Error 401», hay que pedir otro (o renovarlo con grant_type: refresh_token). El MCP todavía no implementa el flujo de autorización OAuth del propio protocolo MCP, así que el cliente no lo obtiene solo.

9.4 Desarrollo

npx nx serve api                     # la API en :3000
npx nx serve mcp                     # el MCP en http://localhost:3010/mcp (TASKSTREAM_API_URL=http://localhost:3000)
npx @modelcontextprotocol/inspector  # probar las herramientas en el navegador
npx nx test mcp                      # MCP real contra una API simulada: token reenviado, errores, validación
npx nx run mcp:openapi-types         # regenerar los tipos tras cambiar la API (apps/api/openapi.json)
Variable Por defecto Qué es
MCP_PORT 3010 Puerto del MCP (no PORT: Nx carga el .env del workspace, donde PORT es el de la API)
TASKSTREAM_API_URL http://localhost:3000 Origen de la API (sus rutas ya llevan /api)
MCP_ALLOWED_HOSTS (solo localhost) Dominios en los que responde, separados por comas
MCP_LOG_LEVEL warn error, warn, log, debug o verbose. Por encima de warn, la librería registra cada herramienta en cada llamada

Añadir una herramienta: un método con @Tool({ name, description, paramsSchema: z.object(…), annotations }) en el resolver de su ámbito (apps/mcp/src/app/tools), que llame a la API con this.api.as(ctx) (cliente tipado con el token de quien llama) y devuelva toolResult(…).

La imagen solo instala lo que lista apps/mcp/package.json: si el MCP empieza a usar un paquete nuevo en tiempo de ejecución, hay que añadirlo ahí (igual que en la API).

9.5 Despliegue

El servicio mcp de docker-compose.yml va en el perfil mcp, así que no arranca salvo que se pida:

docker compose --profile mcp up -d --build   # local: http://localhost:3010/mcp

En Dokploy: COMPOSE_PROFILES=mcp en las variables del proyecto y un dominio (p. ej. mcp.taskstream.pireon.dev) → servicio mcp, puerto 3010. Si el dominio es otro, ponlo en MCP_ALLOWED_HOSTS (por defecto mcp.taskstream.pireon.dev,localhost); si no, el MCP responderá 403. Habla con la API por la red interna (http://task-stream-api:3000). La imagen usa Node 24, que es lo que pide @nestjs-mcp/server 2.