Development
Prerequisites
- Node.js 22+
- Corepack with pnpm 9
- Docker and Docker Compose v2
On Windows, Docker Desktop must be running with the Linux container engine before Compose-backed tests can run.
Workspace setup
corepack enable
pnpm install
pnpm db:generateIf Corepack cannot install global shims, invoke pnpm through Corepack:
corepack pnpm install
corepack pnpm -r buildFull-stack development
Watched development stack
Docker Compose 2.22 or later is the recommended development path. It starts every dependency and application, then watches the workspace:
cp .env.example .env
pnpm devThe command combines infra/docker-compose.yml with infra/docker-compose.dev.yml and runs Compose Watch in the foreground.
.env.example explicitly selects the demo Compose profile. The profile starts the development-only mock CRM and is appropriate only for the seeded Acme scenario. Remove COMPOSE_PROFILES=demo, disable MCP_OPS_DEMO_MODE, and omit MOCK_CRM_URL when testing against a real allowlisted integration.
Watch behavior:
- Changes in
apps/webare synchronized and handled by Next.js Fast Refresh. - Changes in
apps/api,apps/runtimeandapps/workerare synchronized and restarted by their role's watch processes. - Shared package source is synchronized into the affected application containers and compiled by TypeScript watch processes without rebuilding images.
- The mock CRM uses Node's native watch mode.
- Prisma files, application package manifests,
pnpm-lock.yamlandtsconfig.base.jsonrebuild affected development images. - PostgreSQL and Redis volumes are preserved across normal stops.
Stop the watched stack with Ctrl+C, or from another terminal:
pnpm dev:downPrisma changes regenerate the client when affected images rebuild, but migrations are not applied by source synchronization. After creating a migration, run:
docker compose -f infra/docker-compose.yml -f infra/docker-compose.dev.yml run --rm migrateCompose Watch rebuilds the affected application containers after the Prisma files change.
Production-like Compose stack
cp .env.example .env
docker compose -f infra/docker-compose.yml up --buildWith the example development environment, Compose starts PostgreSQL, Redis, a one-shot migration and seed container, the public control-plane role, the private scalable worker role, and the demo-profile mock CRM. The control plane packages Caddy, Next.js and Fastify. Each worker replica packages the private runtime listener and deployment job consumer. Without the demo profile the mock CRM is not created.
Scale identical worker replicas without exposing worker ports:
docker compose -f infra/docker-compose.yml up --build --scale worker=3All production application containers run pnpm config:validate before startup, and the migration container runs it before database changes. NODE_ENV=production requires explicit non-development credentials and non-local HTTPS public URLs and refuses demo mode or MOCK_CRM_URL.
The PostgreSQL container initializes with two roles:
POSTGRES_ADMIN_USERis the bootstrap administrator required by the official image.POSTGRES_USERis a separate non-superuser application role used byDATABASE_URL.
These names must differ. The application role owns the public schema so Prisma can apply migrations without superuser privileges.
The application role also receives database-level CREATE permission because Prisma's baseline migration executes CREATE SCHEMA IF NOT EXISTS public. It remains NOSUPERUSER, NOCREATEDB, NOCREATEROLE and NOINHERIT.
http://localhost:8080 unified public gateway (UI, API, MCP and HTTP)
localhost:5432 PostgreSQL
localhost:6379 RedisWorkers are private Compose services. Caddy load-balances /mcp and /http requests to healthy worker replicas and authenticates the hop with INTERNAL_API_TOKEN.
RUNTIME_CONCURRENCY limits concurrent proxied invocations in each worker replica. DEPLOYMENT_CONCURRENCY separately limits BullMQ build jobs so builds cannot consume the runtime request budget.
The seed is idempotent. Re-running the migration container refreshes development credentials and preserves the stable Acme demo identifiers where possible.
Recovering from an interrupted database initialization
PostgreSQL runs initialization hooks only for an empty data directory. If an init hook fails during the first startup, recreate the incomplete local volume before retrying:
docker compose -f infra/docker-compose.yml down --volumes
docker compose -f infra/docker-compose.yml up --buildThis deletes local development PostgreSQL and Redis data. Do not use it for retained environments.
Native application development
PostgreSQL and Redis may run in Docker while individual applications run on the host. When doing so, change infrastructure and internal application hostnames to localhost and their development ports. Native development keeps the source applications separate even though production-like Compose packages them into two deployable roles.
To generate Prisma, build workspace packages once and start all package and application watchers:
pnpm dev:localPackage TypeScript watchers keep exported dist files current while application watchers reload their processes.
Useful commands:
pnpm --filter @mcpops/web dev
pnpm --filter @mcpops/api dev
pnpm --filter @mcpops/runtime dev
pnpm --filter @mcpops/worker devThe web application calls relative /api URLs. During native host development, where Next.js listens directly on port 3000, it uses API_INTERNAL_URL for its rewrite. Compose exposes only the unified port 8080 origin.
Database workflow
The schema is prisma/schema.prisma; the seed is prisma/seed.ts.
pnpm db:generate
pnpm db:migrate
pnpm db:seedCreate a migration for every schema change and inspect the generated SQL before committing it. Keep project-scoping indexes and foreign-key deletion behavior explicit.
The Project and reusable project-Function models are a clean, unreleased baseline with no compatibility migration. After pulling these schema changes, remove the old local Compose volume with docker compose -f infra/docker-compose.yml down -v, then start normally so the baseline and development seed run against a new database.
Testing
Run all unit tests:
pnpm testRun focused packages during development:
pnpm --filter @mcpops/runtime test
pnpm --filter @mcpops/sandbox test
pnpm --filter @mcpops/shared test
pnpm --filter @mcpops/db testRun the integration test against a healthy Compose stack:
pnpm test:e2eThe integration script signs in, queues a deployment, waits for activation, calls MCP tools/list and tools/call, invokes the equivalent HTTP route and verifies persisted execution records through the unified public gateway.
Debugging
Every API and runtime response carries x-request-id. Use it to correlate application logs with FunctionExecution.requestId and audit metadata.
Useful endpoints:
GET /health
GET /ready
GET /metrics
GET /internal/runtime-endpoints/{endpointId}/manifestThe internal manifest endpoint is not routed by Caddy. Use it only from the internal network and configure INTERNAL_API_TOKEN.
Deployment failures are recorded in DeploymentLog; do not rely only on worker stdout. Function execution errors exposed to callers are intentionally sanitized.
Final verification
pnpm build
pnpm test
docker compose -f infra/docker-compose.yml config --quiet
git diff --checkIf a live Compose run is unavailable, record that limitation in the handoff or pull request.