I stopped starting projects from the UX. I start from the MCP.
Starting from the UX makes side effects propagate. Starting from the MCP contract makes them disappear.
I stopped starting projects from the UX. I start from the MCP.
Subtitle (for Substack): Starting from the UX makes side effects propagate. Starting from the MCP contract makes them disappear.
When vibecoding first came around, I used it as a pure goal-achieving tool: “I want this outcome. I don’t care how you do it. Do it.”
Quick context on where this comes from: I’m a senior engineer at Google, and I’ve been building with MCP for a while now, both at work and on my own projects. Everything below comes from shipping one of them.
LLMs turned out to be surprisingly good at reaching my goals. The problem was that my goals were surprisingly vague. With every vibecoding session, the distance between how I had imagined the system and how it actually turned out grew wider. The model would optimize so hard for the stated outcome that it filled the project with unintuitive, inefficient, wrong, ugly code, whatever it took to technically satisfy the goal.
Ask for a smelly horse, get a horse built out of trash cans. Technically smelly. Technically a horse.
So I started adding constraints, more of them, and more specific. That unlocked a different way of working, spec-driven development, and it substantially improved how closely the result matched what I had in my head.
It is the difference between sharpening the axe for six hours and then cutting once, versus swinging twenty hours with a dull one. Six constraints instead of one. One careful shot instead of endless rounds of “no, not like that” until I am finally satisfied.
This was better. But on increasingly complex projects it was still exhausting. Detailing every constraint and every corner case up front, before the model writes a line, turned out to be its own kind of grind. I was pleased it beat the previous iteration. I was not pleased that it scaled this badly.
Then the shift.
The instinct from spec-driven development is that the constraints are the bottleneck, so you should write better constraints, faster. I went a different way. The realization was not “write the constraints faster.” It was that I had been starting from the wrong place entirely.
I had been starting from the UX.
Here is what that looked like in practice. On a project I am building right now (I will keep it unnamed for now), I built the user-facing experience first, spec-driven. It worked, at first. Then it became a losing battle. Every new feature I added rippled across the views. The interface had something like 53 separate view files, and they were coupled: touch one capability and you were chasing side effects through a dozen screens. Couple of weeks in, I was spending more time containing the blast radius of each change than building.
So I stopped, and I refactored from the other end. I took every function the backend served and cleaned them into MCP tools, not as an afterthought, but as the foundation. In a single day, the backend became 22 MCP tools with a clear, named contract.
Once the contract existed, everything downstream consumed the same surface. The UX consumed it. An agent demo I built consumed it. And because the MCP server was public, Claude Code and Gemini CLI could connect to it directly.
The side-effect problem did not get managed. It disappeared. Not because an agent now derives the constraints for me, but because there is one contract instead of N coupled views, each demanding its own hand-written constraints. You do not constrain twelve screens into agreement. You define the capability once, and the screens are just consumers.
So now I start some projects from the MCP. I think about what the system needs to serve, and how granular each tool should be, and that is the spec. The UX, the agents, the external clients all come after, and they all come cheaply, because they all speak to the same contract.
There is a catch in this, and I will come to it.
The obvious objection: is this not just a well-designed API? A service layer? Engineers have been drawing clean contracts between backend and frontend for twenty years.
Two things make it different.
The first is who is on the other end of the contract. A REST API serves fixed endpoints that a developer wires, by hand, into a fixed UI. An MCP serves capabilities to an agent, and the agent can combine those capabilities, on the fly, into an experience personalized to the person in front of it. The human comes out of the wiring loop. The contract stops being something one developer assembles into one interface and becomes a surface that anything intelligent can recombine.
The second is timing. People have gotten used to conversational interfaces. For a growing set of tasks, talking to a system is simply the faster way to get your work done than clicking through screens someone arranged in advance. A fixed UI assumes you can predict every path a user will want. A conversational, agent-driven experience does not need that prediction. But it does need an agent that can reach every capability. That is what the MCP contract gives it. A clean service layer in 2015 was good engineering. The same clean surface today is the thing that makes an agent-native product possible at all.
This is not free. I said it comes with a catch, so here it is: two of them.
First: thinking granularly, up front, about everything the system serves is hard. Spec-driven development let me defer that. I could discover granularity as I went. Starting from the MCP, I front-load the entire decision. Get the granularity of a tool wrong and you have poured a bad foundation.
Second, and more serious: once an agent is consuming your tools, the agent can still hallucinate. It can call a tool wrong, assume a behavior that is not there, or invent an answer. To catch that, you need a source of truth for every tool, something you can verify the agent’s output against, so you can actually prove it did not hallucinate. That verification layer is real work, and it is work the UX-first approach never made you do.
Three steps, then. Outcome-only, and the model fills the gaps with garbage. Spec-driven, and you win accuracy but pay for it in up-front constraint-writing that does not scale. And then, start from the MCP: define the contract once, let every consumer speak to it, and accept the cost of designing that contract well and proving it stays honest
Honest pushback welcome. Where does this break?




