Thirty new tools (54 in all) for what the API gained since the MCP: create a project and read its public showcase; sprints (list, progress and burndown, plan, change, start, close with carry-over, delete, move a ticket in or out); epics and the backlog (list epics with progress, set a ticket's parent, rank); bulk edit of up to 100 tickets by key, saying which refused; the trash; checklists; following a ticket, removing a relation, editing and deleting comments; editing and archiving releases, drafting and saving their notes; notifications. Searching filters by epic, sprint and dates and sorts by backlog order; get_issue shows parent, checklist, commits and followers; create and update take parent and start date. API: `unreadOnly` and `dryRun` were documented as objects in the OpenAPI; they are booleans now. Chapter 9 lists every tool; Word and PDF regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
14 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:
/mcprechaza (403) las peticiones cuyoHostno esté enMCP_ALLOWED_HOSTS, y las de un navegador cuyoOrigintampoco 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íanOriginy 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_issueoget_project.
9.2 Herramientas
54 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) | |
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_project_activity |
Actividad reciente | |
| Tickets | my_issues |
Mi trabajo en todos los proyectos (o de cualquiera, por categoría: p. ej. lo que espera QA) |
search_issues |
Buscar por texto y filtros (épica, sprint, fechas…); el backlog: noSprint y orden RANK |
|
get_issue |
Detalle: épica, lista de comprobación, relaciones, commits, seguidores, transiciones, comentarios | |
create_issue |
Crear un ticket (también colgado de una épica) | |
update_issue |
Cambiar título, descripción, tipo, área, prioridad, etiquetas, fechas… | |
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, corregir o borrar un comentario | |
link_issues / unlink_issues |
Relacionar tickets (DEPENDS_ON, BLOCKS, TESTS…) o quitar la relación |
|
watch_issue |
Seguir un ticket o dejar de seguirlo | |
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á (sus tickets la dejan) |
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.