How is the techhub repo's code organized into packages/services, and why is @mcp/core a genuinely published npm package instead of a workspace-local dependency copied into each service's Docker image?
Fix: Top level: packages/core (shared internal library — db access via Sequelize + raw pg for pgvector tables, OAuth, Gemini client, queue, cache, config, logger, Sentry), services/mcp-server (MCP tool server + OAuth authorization/resource server — the only internet-facing service), services/reviewer-worker (pg-boss consumer + Gemini review pipeline — zero inbound HTTP surface), infrastructure/ (Dockerfiles, compose, nginx, env templates). Within packages/core/src, folders are organized by concern (auth/, db/, logging/, error/, queue/, cache/, ai/, vector/), not by type; a new concern gets a new folder rather than files piling up flat. File naming inside those folders follows <domain>.<kind>.js (auth.middleware.js, gemini.provider.js, submissions.repo.js). @mcp/core is one shared package rather than fragmented micro-packages, and is genuinely published (via changesets, to techhub's own GitLab npm registry) rather than kept as a workspace-local "*" dependency — because prod Dockerfiles need to npm install it like any other registry dependency (COPY services/X/package*.json → npm install → COPY services/X .) without separately COPY-ing package source into every service image. Declaring a real semver range (^0.1.0) doesn't break local dev: npm workspaces still resolve it to the local packages/core folder for dev and docker-compose.dev, since a workspace-local package is symlinked in preference to the registry whenever it satisfies the declared semver range. Service separation follows the same inbound/outbound risk logic: mcp-server is the only inbound-facing service (OAuth auth server + resource server + MCP tool server in one process, sharing one request path); reviewer-worker has zero inbound HTTP surface since it holds the Gemini key and makes outbound fetches to arbitrary agent-submitted URLs (an SSRF-shaped risk) — a service with that outbound risk profile should never also be reachable inbound. There's no service-to-service API between them; they communicate only by reading/writing the same Postgres rows (submissions → verified_suggestions).
techhubrepo-structuremonoreponpm-workspacesservice-separation
References
- https://docs.npmjs.com/cli/v11/using-npm/workspaces/ — Workspaces favor symlinking the local package over downloading from the registry whenever the local version satisfies the declared semver range.
- https://github.com/goldbergyoni/nodebestpractices — Structure your app by business components/bounded contexts (concern), not technical layers.