Why I Built mf2: A SaaS Monorepo for the Agent Era
February 24, 2026
TL;DR
I built mf2 because every SaaS project I started burned two weeks wiring auth, payments, database, email, and analytics before I wrote a single feature. npx create-mf2-app@latest scaffolds a Turborepo monorepo with 6 apps, 20+ typed packages, and Convex, Clerk, Stripe, PostHog, Sentry, Resend, Vercel AI SDK, and Arcjet pre-wired. Everything lives in typed TypeScript files, not dashboards. Your coding agent reads the schema, follows the imports, and writes correct code on the first try. We're building Cleve.ai on the same stack.
The problem
Every SaaS I've built started the same way. Pick a database. Pick an ORM. Pick auth. Pick payments. Pick email. Pick analytics. Wire them together. Pray the types align across all of them.
Two weeks pass. I haven't written a single line of business logic.
And now there's a second problem: AI agents. I use Claude Code and Cursor for most of my coding. They write good application code when they can see the full picture. They break when half your configuration lives in a Supabase dashboard, an Auth0 admin panel, or a Stripe webhook config page they can't access.
Theo Browne said it well in his stack breakdown: "when everything in your codebase is a file, not some weird config in some dashboard, AI is way better at it." Simon Willison's agentic engineering patterns series argues the same point: writing code costs almost nothing now, so the bottleneck moved to architecture and integration.
mf2 is my answer to both problems at once.
What you get
npx create-mf2-app@latest
One command. Full monorepo.
6 deployable apps
| App | Port | What it does |
|---|---|---|
app | 3000 | Main SaaS dashboard (Next.js 15 + App Router) |
web | 3001 | Marketing / landing pages |
api | 3002 | Webhooks, cron jobs, integrations |
docs | 3004 | User documentation (Mintlify) |
email | 3003 | Email templates (React Email) |
storybook | 6006 | Component development |
Each app deploys independently. One bun run dev starts them all in parallel through Turborepo's dev UI.
20+ typed packages
Every integration is a scoped package under @repo/:
@repo/backend— Convex database, queries, mutations, real-time sync@repo/auth— Clerk with OAuth, organizations, and webhook sync to Convex@repo/payments— Stripe via@convex-dev/stripecomponent@repo/design-system— 50+ shadcn/ui components with dark mode@repo/ai— Vercel AI SDK with multi-model routing@repo/analytics— PostHog event tracking, sessions, experiments@repo/observability— Sentry error tracking + BetterStack logging@repo/email— Resend transactional email@repo/security— Arcjet bot detection and secure headers@repo/rate-limit— Upstash Redis rate limiting@repo/feature-flags— Vercel feature flags with overrides@repo/notifications— Knock in-app notification feeds@repo/collaboration— Liveblocks cursors and presence@repo/cms— BaseHub headless CMS@repo/storage— Convex file storage + Vercel Blob@repo/webhooks— Svix outbound webhook delivery@repo/seo— Metadata, JSON-LD, Open Graph@repo/internationalization— next-intl translations
Each package wraps one concern. Import what you need, ignore what you don't.
Why these choices
Convex over SQL + ORM
This is the biggest departure from the typical stack. No MySQL, no Postgres, no Prisma, no Drizzle, no migrations.
Convex is a TypeScript-native database. You define your schema in a .ts file, Convex generates the types, queries are reactive by default, and there are no migrations. The entire data layer is source code your agent can read.
In Theo's stack video, he walks through his old setup: MySQL on PlanetScale, hit through Prisma/Drizzle, endpoints defined in tRPC, live updates through Pusher, React Query on the client. Convex replaced all five. One system, typed end-to-end.
The tradeoff: no raw SQL. If your project needs complex joins or aggregates, or your team thinks in SQL, Convex is wrong. For a SaaS product where you want real-time data, typed queries, and zero migrations, nothing else I've tried comes close.
Convex ships pre-built components. mf2 includes five out of the box: Stripe integration, Resend email, workflow orchestration, action retries, and migrations.
Clerk over rolling your own
Auth is what I most want out of my codebase. Session management, OAuth flows, organization support, webhook sync to the database — configuration I refuse to maintain. Clerk handles it and syncs user data to Convex via webhooks.
Turborepo + Bun
Turborepo gives you caching, filtered builds, parallel dev servers, and the dev UI. Bun gives you fast package management and the runtime. bun run dev spins up every app through Turborepo's --ui flag with a live terminal dashboard.
Filtered builds matter for agents. When Claude Code makes a change in @repo/auth, I run bun check auth — just that package and its dependents, not the entire monorepo. Fast feedback loops for both humans and agents.
The agent-first design
The core philosophy: if it's not in a .ts file, your agent can't use it.
Every package exports typed interfaces. When an agent sees import { auth } from '@repo/auth', it follows that import to the source, reads the types, and writes correct code. No guessing. No MCP servers bridging dashboard state into the codebase.
The monorepo structure helps agents navigate. Colocation keeps related files together. No barrel files — explicit imports only. A CLAUDE.md at the project root describes the packages, commands, and conventions.
This matters more than people think. I watched Claude Code work with a Supabase project where half the auth config lived in the dashboard. It hallucinated table schemas. It guessed at RLS policies it couldn't see. In mf2, those errors vanish. The schema is convex/schema.ts — a file the agent reads directly.
What's next
mf2 is a web monorepo today. Next: multi-platform.
- Mobile — React Native + Expo, sharing
@repo/*packages - Desktop — Electron, same shared packages
- Browser extension — Plasmo, same shared packages
One backend, multiple platforms, all typed, one repo. @repo/auth, @repo/backend, @repo/ai work the same whether you import them from a Next.js app or a React Native screen.
We're building Cleve.ai on this stack. If the architecture works for a template, it works for production.
Try it
npx create-mf2-app@latest my-app
cd my-app
bun run dev
- Docs: mf2.dev/docs
- GitHub: github.com/ocarinalabs/create-mf2-app
- Site: mf2.dev
Open source. Feedback welcome. If something's broken or missing, open an issue.
February 24, 2026