Files
task-stream/docker-compose.yml
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

132 lines
4.4 KiB
YAML

# TaskStream: web + API + infrastructure — production definition (Dokploy deploys this file).
# No ports are published here: services only talk over the compose network, and the web is
# exposed through the platform's proxy (Dokploy domain → service `web`, port 80).
#
# Services talk to each other by unique aliases (task-stream-*), never by the bare service
# name: on a shared network (Dokploy's dokploy-network) other projects also have an `api`,
# `postgres` or `redis`, and Docker DNS would hand out theirs too.
#
# Locally, `docker compose` also loads docker-compose.override.yml, which publishes the ports:
# docker compose up -d --build → everything: web on http://localhost:8080
# docker compose up -d postgres mongo redis → only infrastructure (then `npx nx serve api` / `npx nx serve web`)
#
# Building needs access to Pireon's Nexus (@pi/ui): set NEXUS_TOKEN in the environment or in .env
# (Dokploy: Environment tab). It reaches the build as a secret and never lands in an image layer.
services:
web:
build:
context: .
target: web
secrets: [nexus_token]
image: task-stream-web
expose: ['80']
networks: [default]
depends_on:
api: { condition: service_healthy }
restart: unless-stopped
api:
build:
context: .
target: api
secrets: [nexus_token]
image: task-stream-api
expose: ['3000']
networks:
default:
aliases: [task-stream-api]
environment:
POSTGRES_URL: postgres://taskstream:taskstream@task-stream-postgres:5432/taskstream
# No migrations yet: TypeORM creates the event store and lookup tables.
POSTGRES_SYNCHRONIZE: 'true'
MONGO_URL: mongodb://task-stream-mongo:27017/taskstream_read
REDIS_URL: redis://task-stream-redis:6379
EVENT_STREAM_KEY: task-stream:events
EVENTS_BRIDGE_ENABLED: 'true'
PIREON_BASE_URL: https://oauth.pireon.dev
PIREON_WEBHOOK_SECRET: ${PIREON_WEBHOOK_SECRET:-}
PIREON_CLIENT_ID: ${PIREON_CLIENT_ID:-}
PIREON_CLIENT_SECRET: ${PIREON_CLIENT_SECRET:-}
# Pireon clients allow one grant type: the one above is authorization_code,
# this one password (email + password sign-in). Optional.
PIREON_PASSWORD_CLIENT_ID: ${PIREON_PASSWORD_CLIENT_ID:-}
PIREON_PASSWORD_CLIENT_SECRET: ${PIREON_PASSWORD_CLIENT_SECRET:-}
GRAPHQL_PLAYGROUND: 'true'
# The SDL / OpenAPI files are only useful in development (codegen); the
# document is still served at /api/docs-json.
GRAPHQL_SCHEMA_FILE: ''
OPENAPI_FILE: ''
depends_on:
postgres: { condition: service_healthy }
mongo: { condition: service_healthy }
redis: { condition: service_healthy }
restart: unless-stopped
# MCP server for AI clients (Claude, Cursor…): Streamable HTTP at /mcp. It consumes the API
# with each caller's own Pireon token (Authorization header), so it holds no credentials.
# Optional: it only starts with the `mcp` profile (`docker compose --profile mcp up -d`; in
# Dokploy, COMPOSE_PROFILES=mcp) and needs a domain → service `mcp`, port 3010.
mcp:
profiles: [mcp]
build:
context: .
target: mcp
secrets: [nexus_token]
image: task-stream-mcp
expose: ['3010']
networks:
default:
aliases: [task-stream-mcp]
environment:
TASKSTREAM_API_URL: http://task-stream-api:3000
depends_on:
api: { condition: service_healthy }
restart: unless-stopped
postgres:
image: postgres:17-alpine
networks:
default:
aliases: [task-stream-postgres]
environment:
POSTGRES_USER: taskstream
POSTGRES_PASSWORD: taskstream
POSTGRES_DB: taskstream
volumes: [postgres-data:/var/lib/postgresql/data]
healthcheck:
test: ['CMD-SHELL', 'pg_isready -U taskstream']
interval: 5s
retries: 10
mongo:
image: mongo:8
networks:
default:
aliases: [task-stream-mongo]
volumes: [mongo-data:/data/db]
healthcheck:
test: ['CMD', 'mongosh', '--quiet', '--eval', 'db.adminCommand("ping")']
interval: 5s
retries: 10
redis:
image: redis:7-alpine
networks:
default:
aliases: [task-stream-redis]
command: ['redis-server', '--appendonly', 'yes']
volumes: [redis-data:/data]
healthcheck:
test: ['CMD', 'redis-cli', 'ping']
interval: 5s
retries: 10
volumes:
postgres-data:
mongo-data:
redis-data:
secrets:
nexus_token:
environment: NEXUS_TOKEN