- Logs default to warnings and errors (MCP_LOG_LEVEL): the library logged every tool on every request (~48 lines per call). - DNS rebinding protection on /mcp, as the MCP spec asks of HTTP servers: the Host header must be in MCP_ALLOWED_HOSTS (default: localhost; compose default mcp.taskstream.pireon.dev,localhost) and a browser Origin, when present, too; otherwise 403 with a JSON-RPC error. Uses the official validators from @modelcontextprotocol/server. - Test for allowed/foreign Host and Origin; .env.example, compose and docs/09-mcp.md document the variables; Word/PDF regenerated. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
9.5 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
| Ámbito | Herramienta | Qué hace |
|---|---|---|
| Usuario | whoami |
El usuario del token |
search_users |
Buscar personas de Pireon (para asignar) | |
| Proyectos | list_my_projects |
Mis proyectos |
list_public_projects |
Proyectos públicos | |
get_project |
Equipo y configuración: ids de estados, tipos y áreas que aceptan las demás herramientas | |
get_board |
Tablero por columnas (filtros por persona, release, área) | |
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 | |
get_issue |
Detalle, transiciones permitidas, enlaces, comentarios e historial | |
create_issue |
Crear un ticket | |
update_issue |
Cambiar título, descripción, tipo, área, prioridad, etiquetas… | |
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 |
Comentar | |
link_issues |
Relacionar tickets (DEPENDS_ON, BLOCKS, TESTS…) |
|
| Releases | list_releases |
Releases de un proyecto |
upcoming_releases |
Próximas releases de mis proyectos | |
get_release |
Progreso, tickets y burnup | |
create_release |
Planificar una release | |
publish_release |
Publicar (con force si quedan tickets sin terminar) |
Las de lectura van marcadas como readOnlyHint, para que el cliente no pida confirmación al usarlas. Lo que es de
administración (miembros, configuración, visibilidad, borrar proyectos) 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.