MechCrate

Meta-framework for service ecosystems

Stand up and operate entire service ecosystems. Consistently, locally.

An AI-native meta-framework with agents as first-class developers. Every repo has its own layout, its own env-var loading order, its own half-documented way to start the stack. Humans burn onboarding time relearning it, agents burn tokens re-exploring it. mx makes the shape of a project the one thing nobody has to relearn.

Install
curl -fsSL https://mechcrate.dev/install.sh | sh # or with Homebrew: brew install unyform-ai/tap/mechcrate

Downloads the release for your platform, verifies its checksum, and installs the mx binary to ~/.local/bin. No sudo. Then mx doctor tells you what else your machine needs.

Runs on macOS 11 or later (Apple silicon and Intel), Linux x86_64 and aarch64, and Windows through WSL 2. Platform matrix

View on GitHub Read the docs

The MechCrate raccoon: a goggled mechanic shouldering a wrench and carrying a wooden crate.

The subset problem

You can’t run all twelve services locally

Real ecosystems have ten or more services, and most of them are irrelevant to the feature you are building today. Running the lot on a laptop is not feasible, and pretending otherwise is how dev environments rot.

mx makes the subset the unit of work. Every service gets its own atomic compose file: a baseline that describes its production shape, plus a dev override that adds hot-reload mounts, debug ports and relaxed health checks. Nothing forces all-or-nothing, and nothing about “what runs where” lives in tribal knowledge.

Pick your slice
make dev # the whole stack make dev s=api # just the one you're on
  • docker/compose/<service>.yml: the baseline, the same shape you deploy.
  • <service>.dev.yml: the override, merged only by make dev.
Compose layering, baseline plus dev overrideThe baseline compose file for a service defines the release build target, env files, healthcheck, Traefik routing labels and networks. The dev override file re-targets the build at the development stage, bind-mounts the source for hot reload, masks node_modules with anonymous volumes, publishes the Vite HMR port and relaxes the healthcheck start period. Docker Compose merges the two files, and make dev brings up the merged result, reachable through the router.

docker/compose/site.yml
baseline — same everywhere
build target: runner
env_file: .env.secrets · .env.shared · .env.site
healthcheck: curl /api/health
labels: Host(mechcrate.localhost) → port 4321
networks: default + devmesh-traefik

docker/compose/site.dev.yml
dev override — merged on top
build target: development
volumes: ../../apps/site:/app
masks /app/node_modules and /app/.astro
ports: 24678 (Vite HMR)
NODE_ENV=development
healthcheck start_period: 30s

docker compose
-f site.yml -f site.dev.yml

effective dev stack
container site-dev
source mounted · hot reload on
db and redis ports published locally

http://mechcrate.localhost
via the mx router — no port juggling

make up / release
baseline only — no override

Compose layering, baseline plus dev overrideThe baseline compose file for a service defines the release build target, env files, healthcheck, Traefik routing labels and networks. The dev override file re-targets the build at the development stage, bind-mounts the source for hot reload, masks node_modules with anonymous volumes, publishes the Vite HMR port and relaxes the healthcheck start period. Docker Compose merges the two files, and make dev brings up the merged result, reachable through the router.

docker/compose/site.yml
baseline — same everywhere
build target: runner
env_file: .env.secrets · .env.shared · .env.site
healthcheck: curl /api/health
labels: Host(mechcrate.localhost) → port 4321
networks: default + devmesh-traefik

docker/compose/site.dev.yml
dev override — merged on top
build target: development
volumes: ../../apps/site:/app
masks /app/node_modules and /app/.astro
ports: 24678 (Vite HMR)
NODE_ENV=development
healthcheck start_period: 30s

docker compose
-f site.yml -f site.dev.yml

effective dev stack
container site-dev
source mounted · hot reload on
db and redis ports published locally

http://mechcrate.localhost
via the mx router — no port juggling

make up / release
baseline only — no override

A baseline compose file describes the service everywhere; a dev override adds the source mount, the HMR port and a relaxed healthcheck. make dev merges the two, make up uses the baseline alone.

The context-switch problem

Switching organizations shouldn’t mean tearing down your stack

Work across two organizations (or just two products) and the usual routine is misery: spin down ecosystem A’s containers so ecosystem B’s ports don’t collide, lose your state, repeat tomorrow.

The mx router is a single global Traefik instance that every mx project registers with. Application services publish no HTTP host ports; Traefik routes to them by hostname, so two ecosystems’ web apps never collide. Multiple ecosystems coexist on one machine, and switching context is cd, not teardown.

Stable hostnames
http://api.localhost http://admin.localhost http://docs.localhost
  • One router per machine. Installed once with mx router install && mx router up, it serves every project you ever scaffold.
  • Backing services still get ports. Dev overrides expose Postgres and Redis on their conventional ports for local GUI tools (per compose file, easy to change).
MechCrate ecosystem topologyA browser reaches hostnames such as api.localhost and mechcrate.localhost. One workstation-wide Traefik router, installed by mx router install, reads Host rules from container labels on the shared devmesh-traefik Docker network and forwards each hostname to the running container that claims it. Several mx projects attach to that same network, and only the subset of services you started is up.

devmesh-traefik — shared external Docker network

mx router — one per workstation

project: mech-crate/site

project: acme

Host match

Host match

Host match

project: legacy-shop

every service
not started

Browser
api.localhost · admin.localhost · mechcrate.localhost

Traefik
:80 · Host() rules read from container labels

api
Host: api.localhost

admin
Host: admin.localhost

worker
not started

site
Host: mechcrate.localhost

db · redis
not started

MechCrate ecosystem topologyA browser reaches hostnames such as api.localhost and mechcrate.localhost. One workstation-wide Traefik router, installed by mx router install, reads Host rules from container labels on the shared devmesh-traefik Docker network and forwards each hostname to the running container that claims it. Several mx projects attach to that same network, and only the subset of services you started is up.

devmesh-traefik — shared external Docker network

mx router — one per workstation

project: mech-crate/site

project: acme

Host match

Host match

Host match

project: legacy-shop

every service
not started

Browser
api.localhost · admin.localhost · mechcrate.localhost

Traefik
:80 · Host() rules read from container labels

api
Host: api.localhost

admin
Host: admin.localhost

worker
not started

site
Host: mechcrate.localhost

db · redis
not started

One router per workstation. Every project attaches to the same devmesh-traefik network, Traefik reads Host() rules off container labels, and only the services you started are up.

The scaffolding problem

Scaffolding should carry wisdom, not boilerplate

Starting a new service usually means cobbling: copy a Dockerfile from the last project, hand-port the test harness, rediscover the admin-panel setup, re-solve deployment. Piecemeal, every time.

mx recipes are production-shaped service definitions that carry the accumulated decisions of the stacks they came from: dependency choices, testing setup, admin tooling, dev overrides, deploy configuration, all applied in one motion. And projects don’t fork away from the framework: mx upgrade is how an existing project adopts newer scaffolding as the templates improve.

From nothing to a running service
mx new my-platform mx add api --recipe rust-api \ --domain api.localhost make dev

Being straight with you: mx upgrade used to fail before it did anything, tracked as mech-crate-z5i with a red test asserting the behaviour we owed you. That test is green and in the gate now, and the upgrade page shows a real run along with the one migration it asks of you. What is still open gets named the same way: thirteen defects sit in the known-broken lane with a red test each, not in a backlog nobody reads.

Recipe install flowRunning mx add site with the astro recipe loads that recipe's manifest, resolves its options and expands its placeholders, optionally runs the recipe's init command to scaffold the framework app and create the declared directories, renders the template files into the project as the app source, the compose baseline and dev override, the dockerfiles and the service env file, writes Traefik routing labels for the chosen domain, and prints next steps ending in make dev, which brings the service up behind the router.

files landed

apps/site/**
app source
+ health route

docker/compose/
site.yml
site.dev.yml

docker/dockerfiles/site/
app
app.prod

docker/.config/
.env.site

mx add site --recipe astro --domain mechcrate.localhost

templates/recipes/astro/recipe.json
options · placeholders · init_app · directories · templates

resolve + expand
flags beat manifest defaults
SERVICE_NAME=site · DOMAIN=mechcrate.localhost

scaffold
init_app (skip_if_exists) · create declared directories

router labels written
traefik.enable · Host(mechcrate.localhost) → 4321 · devmesh-traefik

make dev s=site
baseline + dev override, up

http://mechcrate.localhost

Recipe install flowRunning mx add site with the astro recipe loads that recipe's manifest, resolves its options and expands its placeholders, optionally runs the recipe's init command to scaffold the framework app and create the declared directories, renders the template files into the project as the app source, the compose baseline and dev override, the dockerfiles and the service env file, writes Traefik routing labels for the chosen domain, and prints next steps ending in make dev, which brings the service up behind the router.

files landed

apps/site/**
app source
+ health route

docker/compose/
site.yml
site.dev.yml

docker/dockerfiles/site/
app
app.prod

docker/.config/
.env.site

mx add site --recipe astro --domain mechcrate.localhost

templates/recipes/astro/recipe.json
options · placeholders · init_app · directories · templates

resolve + expand
flags beat manifest defaults
SERVICE_NAME=site · DOMAIN=mechcrate.localhost

scaffold
init_app (skip_if_exists) · create declared directories

router labels written
traefik.enable · Host(mechcrate.localhost) → 4321 · devmesh-traefik

make dev s=site
baseline + dev override, up

http://mechcrate.localhost

mx add reads a recipe manifest, expands its placeholders, scaffolds the framework app, lands the compose, dockerfile and env files, and writes the router labels. Then make dev.

The everything-else problem

The cobbled-together everything else

Environment loading, the local CLI, where files live, how you check the machine is healthy. These are the concerns every team solves badly, differently, once per project. mx standardises them once, in a folder contract that is deliberately non-negotiable.

That contract is the point. It is what lets one make dev work everywhere, one router serve everything, and one MCP server understand any project without being told about it first.

  • Environment config. One documented loading order: .env.shared → .env.secrets (gitignored) → .env.<service>, all under docker/.config/.
  • A consistent CLI. make dev / up / down / logs / sh / ps / build / doctor behave identically in every project, and mx mirrors the same verbs globally.
  • Filesystem discipline. 1:1 host-to-container mounts under docker/system/. What you see is what the container sees.
  • Documentation and health. mx docs compiles project Markdown to PDF and HTML; mx doctor and make doctor check dependencies before they ruin your afternoon.
The mx folder contractAn mx project root contains a thin Makefile dispatcher, a make directory with one include per verb, a scripts directory holding the actual bash, an apps directory with one directory per service that mx never edits, and a docker directory split into dot-config for env files, compose for the baseline and dev override, system for infrastructure you own, and dockerfiles for per-service build files.

myproject/
an mx project root

Makefile
thin dispatcher — make dev · up · build · logs

make/
one .mk include per verb
dev.mk · build.mk · logs.mk · …

scripts/
the actual bash
dev.sh · build.sh · doctor.sh · …

apps/

docker/

{service}/
your application source
mx scaffolds it, then never edits it

.config/
.env.shared · .env.{service}
.env.secrets (gitignored)

compose/
{service}.yml — baseline
{service}.dev.yml — dev override

system/
infrastructure you own
proxies, certs, seed data

dockerfiles/
{service}/app — dev target
{service}/app.prod — release target

The mx folder contractAn mx project root contains a thin Makefile dispatcher, a make directory with one include per verb, a scripts directory holding the actual bash, an apps directory with one directory per service that mx never edits, and a docker directory split into dot-config for env files, compose for the baseline and dev override, system for infrastructure you own, and dockerfiles for per-service build files.

myproject/
an mx project root

Makefile
thin dispatcher — make dev · up · build · logs

make/
one .mk include per verb
dev.mk · build.mk · logs.mk · …

scripts/
the actual bash
dev.sh · build.sh · doctor.sh · …

apps/

docker/

{service}/
your application source
mx scaffolds it, then never edits it

.config/
.env.shared · .env.{service}
.env.secrets (gitignored)

compose/
{service}.yml — baseline
{service}.dev.yml — dev override

system/
infrastructure you own
proxies, certs, seed data

dockerfiles/
{service}/app — dev target
{service}/app.prod — release target

Every mx project has the same shape, so every project answers to the same verbs. mx owns the contract; you own apps/{service} and docker/system.

The AI layer

AI that helps without leaking or wasting

Most “AI-enabled” development bolts a chatbot onto chaos. mx inverts it: make the terrain legible first, then arm the agent. Identical layouts and stable conventions mean an agent lands in familiar ground every session instead of re-deriving project shape before its first useful edit.

  • One MCP server, forty-plus tools

    mx-mcp-server groups its tools into four families: scaffold, operate, understand, retrieve. An agent can create a service, start the right subset of the stack, tail its logs and consult the corpus before writing a line. How the AI layer works →

  • A techniques corpus you host yourself

    RAG over curated engineering docs on pgvector, stored in a Postgres you control: a Neon project or a local container, never a third-party AI vendor’s store. Embeddings go through any OpenAI-compatible /embeddings endpoint: OpenAI by default, or point embedding_base_url at Ollama or LM Studio to keep ingestion and queries local too. Browse the published corpus →

  • A corpus that grows on schedule

    A research skill runs weekly: it picks a topic, corroborates claims against primary sources, and opens a pull request carrying provenance frontmatter and a full source list. A human merges. The corpus grows on purpose, with an audit trail. Research log and backlog →

The MechCrate AI loopA coding agent calls the mx MCP server, whose rag_context tool embeds the query and searches a pgvector store hosted on Neon or a local Postgres. The store is filled by mx rag ingest from the sixty-seven markdown docs under docs/development. Those docs are written and revised by a weekly research pipeline that opens a pull request; a human reviews and merges it, and only merged docs are ingested. The same docs are published to this site.

growth — weekly, and PR-gated

retrieval — every agent turn

tool call

rejected

vector search

ranked passages

merged

mx rag ingest

corpus loader

Coding agent
any MCP client

mx MCP server
mx_· make_· project_· rag_

rag_context
embed query → top-k

techniques corpus
Postgres + pgvector · Neon or local

docs/development/*.md
67 frontmatter-typed docs

Weekly run
or /technique-research

Research pass
coverage → sources → draft

Pull request
branch research/{slug}

Human review

mechcrate.dev/docs/corpus
the same docs, for humans

The MechCrate AI loopA coding agent calls the mx MCP server, whose rag_context tool embeds the query and searches a pgvector store hosted on Neon or a local Postgres. The store is filled by mx rag ingest from the sixty-seven markdown docs under docs/development. Those docs are written and revised by a weekly research pipeline that opens a pull request; a human reviews and merges it, and only merged docs are ingested. The same docs are published to this site.

growth — weekly, and PR-gated

retrieval — every agent turn

tool call

rejected

vector search

ranked passages

merged

mx rag ingest

corpus loader

Coding agent
any MCP client

mx MCP server
mx_· make_· project_· rag_

rag_context
embed query → top-k

techniques corpus
Postgres + pgvector · Neon or local

docs/development/*.md
67 frontmatter-typed docs

Weekly run
or /technique-research

Research pass
coverage → sources → draft

Pull request
branch research/{slug}

Human review

mechcrate.dev/docs/corpus
the same docs, for humans

Agents read the corpus through the MCP rag_context tool. The research pipeline writes to it, but only through a pull request a human merges, so the corpus grows on purpose.

Quality

Proven gates, and a public list of what is broken

The three blocking gates (lint, test, coverage) are proven, not assumed. Each was demonstrated failing on a deliberately broken branch before the baseline landed, so we know they catch what they claim to catch. A release tag cannot ship unless its commit passes all three.

  • Blocking gate make test

    Unit and integration suite plus doc-tests, on every PR and push to main.

  • Blocking gate make lint

    Clippy with warnings denied. One useless format string fails the build.

  • Blocking gate make coverage

    Line coverage against a ratcheting floor. Drops fail CI; raises are opt-in.

  • Report make test-known-broken

    A TDD lane where every open, testable defect has a red test asserting its fixed behaviour.

  • Report make test-e2e

    Scaffold → router → live URL against real Docker, on a dispatched workflow.

  • Report make test-mutants

    Scheduled mutation testing over the core library, as a weekly report.

The known-broken lane

We would rather publicly index our own defects than pretend there aren’t any. Thirteen are open and named, the corpus’s measurably weak lexical retrieval arm (mech-crate-4jw) among them, each with a red test that asserts the behaviour we owe you.

Un-ignoring that test is the fix’s definition of done, and mx upgrade (mech-crate-z5i) is the first row to leave the lane that way. The lane is a report, not a gate, which is exactly why it can afford to be honest. See the lane and the research log →

A developer who knows one mx project knows all of them

Install the CLI, bring up the router once, and scaffold your first project. The second one will look exactly like it. That is the whole idea.

Get started
curl -fsSL https://mechcrate.dev/install.sh | sh # or: brew install unyform-ai/tap/mechcrate mx router install && mx router up mx new my-app # and you're building

Start here View on GitHub