Erik Engelen

Business Automation

Tenant Manager

MCP-native SaaS for landlord operations. Agents can run the business; humans confirm.

  • 48 MCP tools exposed
  • 74+ PostgreSQL migrations
  • 80 Database tables
  • 100% Property scoping enforced on every query
  • Vlaams WHD, nieuw BW, Ger.W. Belgian rental law citations live
Tenant Manager — MCP tools, rent_svc source of truth, Belgian rental law domain, agent-confirm-human flow

Tenant Manager is the operations system for a small real-estate portfolio I run as landlord. Every business operation — rent reconciliation, formal notices, maintenance requests, lease renewals — is exposed as an MCP tool, so an LLM agent can take action on my behalf and a human signs off. It's a real product with real users (me) and real money flowing through it.

Decisions

  1. 01

    MCP-native, not chat-bolted

    Every business operation is exposed as an MCP tool with a typed schema, including the ones humans use in the UI. Agents and humans hit the same code paths through the same contracts. That's the difference between a SaaS with a chat widget glued on and a SaaS where the LLM is a first-class user. The 48 tools cover rent status, arrears, tenant lookup, communications, notices, maintenance, observations, dossier export — anything an operator would do.

  2. 02

    rent_svc is the single source of truth for money

    All arrears and payment data goes through `rent_svc.get_full_payment_history()`. No raw SQL on `rent_payments` anywhere — not in views, not in templates, not in agent tools. Past me learned the hard way that "just join the table" produces three different arrears numbers depending on which feature is asking. One source-of-truth function, called everywhere, ended that whole class of bug.

  3. 03

    Belgian rental law as a first-class domain

    The dual citation Art. 1139 oud BW / 5.231 nieuw BW (Boek 5 since 2023) is in the notice templates because real notices need both. The escalation path (friendly → ingebrekestelling → verzoekschrift) is encoded in code, not in someone's head. EPC dates, indexation rules, waarborg, lease termination — all live in the domain layer rather than scattered through templates. The cost is keeping it current as the law updates; the benefit is that the next feature that touches rent never has to re-learn the law.

  4. 04

    Property scoping on every query, no exceptions

    Every units query must filter by `property_id`. Not "should" — must. There is exactly one valid exception (tenant creation, where the new tenant has no property yet), and it's documented in `feedback_property_scoping.md` with the precise pattern. Cron jobs that don't scope by property are flagged as bugs to be fixed. The cost is a few extra lines per query; the benefit is no more cross-property data leaks when a new feature is added in a hurry.

  5. 05

    Per-unit subscription model

    Pricing is per-unit (€2/unit starter, €1.75 pro, €1.50 enterprise) because that's the unit of value for a landlord. The `subscriptions` table has Stripe customer + subscription ID columns ready; the checkout/webhook wiring is the last big remaining piece. The pricing decision is locked because it determined how the SaaS foundation (organizations, roles, owner portal, impersonation) was modeled.

Tenant Manager is the operations system for a small real-estate portfolio I run as a landlord in Leuven. In production with 48 MCP tools, 80 database tables, 74+ migrations, and a full SaaS readiness sprint behind it (CSRF, GDPR account deletion, MFA, per-org SMTP, i18n nl/fr/en, gamification, maintenance SLA engine, lawyer portal with dossier export). It’s the most-shipped of my three platforms because it has real users — me — and real money flowing through it.

The thing I want to emphasize about this case study is that it has real users — me — and real money flowing through it. Every decision was made under the constraint that a wrong arrears calculation, a missed formal notice deadline, or a property-scope leak has real consequences. That’s a different discipline than building something for a demo. The decisions above (one source of truth, property scoping enforced, the law as a first-class domain) all came from a specific bug I shipped that I don’t want to ship again.

It also showed me what MCP-native looks like in practice. The 48 tools aren’t a chatbot afterthought — they’re the actual interface to the business. A human uses them through a web UI, an LLM agent uses them through the MCP protocol, and the code paths are identical. That’s the part I’d build the same way again.

Related work

Open to permanent AI Architect roles, EU remote.

Email, LinkedIn, or grab 30 minutes on the calendar.