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>
132 lines
4.4 KiB
YAML
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
|