Files
task-stream/Dockerfile
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

67 lines
2.8 KiB
Docker

# syntax=docker/dockerfile:1.7
# TaskStream images (one Dockerfile, three targets):
# docker compose up -d --build → api + web + infrastructure
# docker compose --profile mcp up -d --build → plus the MCP server
#
# @pi/ui comes from Pireon's private Nexus. Its token is passed as a build
# secret (compose: `secrets.nexus_token` ← $NEXUS_TOKEN) and never lands in a
# layer: it is written to a temporary npmrc that the same RUN deletes.
# ---- Workspace dependencies (shared by both builds) --------------------
FROM node:22-alpine AS deps
WORKDIR /workspace
ENV NX_DAEMON=false NX_NO_CLOUD=true NX_SKIP_NX_CACHE=true
COPY package.json package-lock.json .npmrc ./
RUN --mount=type=secret,id=nexus_token,required=true \
printf '//nexus.pireon.dev/repository/npm-releases/:_authToken=%s\n' "$(cat /run/secrets/nexus_token)" > /tmp/auth.npmrc \
&& npm ci --no-audit --no-fund --userconfig /tmp/auth.npmrc \
&& rm -f /tmp/auth.npmrc
# ---- API: Nest bundle + pruned package.json / lockfile -----------------
FROM deps AS build-api
COPY . .
RUN npx nx run api:prune
FROM node:22-alpine AS api
WORKDIR /app
ENV NODE_ENV=production PORT=3000
# Only the API's runtime dependencies (apps/api/package.json): no frontend
# packages, nothing from the private registry.
COPY --from=build-api /workspace/dist/apps/api/package.json /workspace/dist/apps/api/package-lock.json ./
RUN npm ci --omit=dev --ignore-scripts --no-audit --no-fund && npm cache clean --force
COPY --from=build-api /workspace/dist/apps/api/main.js ./
USER node
EXPOSE 3000
HEALTHCHECK --interval=10s --timeout=3s --start-period=20s --retries=5 \
CMD wget -qO- http://127.0.0.1:3000/api/health > /dev/null || exit 1
CMD ["node", "main.js"]
# ---- MCP: server for AI clients, consumes the API over REST ------------
FROM deps AS build-mcp
COPY . .
RUN npx nx run mcp:prune
# @nestjs-mcp/server 2 targets Node 24.
FROM node:24-alpine AS mcp
WORKDIR /app
ENV NODE_ENV=production MCP_PORT=3010
COPY --from=build-mcp /workspace/dist/apps/mcp/package.json /workspace/dist/apps/mcp/package-lock.json ./
RUN npm ci --omit=dev --ignore-scripts --no-audit --no-fund && npm cache clean --force
COPY --from=build-mcp /workspace/dist/apps/mcp/main.js ./
USER node
EXPOSE 3010
HEALTHCHECK --interval=10s --timeout=3s --start-period=10s --retries=5 \
CMD wget -qO- http://127.0.0.1:3010/health > /dev/null || exit 1
CMD ["node", "main.js"]
# ---- Web: Angular SPA behind nginx (proxies /api and /graphql) ---------
FROM deps AS build-web
COPY . .
RUN npx nx build web --configuration=production
FROM nginx:1.27-alpine AS web
COPY apps/web/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=build-web /workspace/dist/apps/web/browser /usr/share/nginx/html
EXPOSE 80
HEALTHCHECK --interval=10s --timeout=3s --retries=5 CMD wget -qO- http://127.0.0.1/ > /dev/null || exit 1