Two threads today, and they rhyme more than I expected: both are about who gets to do what, and how you keep that boundary honest. One was exposing an app to an AI agent safely; the other was moving config out of deploy-time files and into a settings UI without losing the guardrails. Different problems, same instinct — make the boundary explicit and put it in one place.

Thread 1 — MCP tools that respect tenancy and permissions

I spent most of the day building out a set of MCP tools so an AI agent can drive an events platform: list events, check readiness, publish, run the lifecycle. The interesting part isn't the tools — it's the fencing around them.

MCP tools authenticate with a token, which means there's no ambient "current tenant" context the way a session-backed web request has. If you lean on a global tenant scope, it reads nothing and your "fenced" query quietly returns everyone's rows. So the rule became: under token auth, filter by organization explicitly, every time, in one shared trait — never a global scope you hope is active.

On top of scope, every tool declares a single ability() — and it reuses the same permission strings the web app already enforces, so the agent can never be more privileged than the human behind it. Read tools get an #[IsReadOnly] annotation so destructive calls are visible at a glance. Lookups are by UUID, never the enumerable auto-increment ID.