← All posts

MCP servers are reminding us why DDD still matters

I keep seeing engineers treat MCP servers as a quick way to expose existing APIs to AI agents, and it worries me. When I read the Dev.to article that connects those servers to Domain‑Driven Design, I realized we’re about to repeat the microservices mistake we spent a decade fixing. The piece isn’t just a history lesson; it’s a practical reminder that the patterns we already know apply directly to the new AI‑tooling layer.

The MCP wrapper trap

The article opens with a frustrated devops engineer asking how to secure MCP servers that suddenly appeared in their infrastructure. The comments reveal a pattern: engineers are wrapping REST APIs one‑to‑one and calling it AI integration. That’s exactly how many teams approached microservices in the early 2010s — split the monolith, label the pieces “services,” and end up with distributed monoliths. The operational complexity went up, but the architectural benefits never materialized because the boundaries weren’t rooted in a shared mental model.

The author points out that the same thing is happening with MCP. We’re creating servers that expose filesystem access, shell execution, and database connectors all in one place. As the Reddit comment quoted in the piece puts it:

“don't mix list_files and execute_shell in one server”

That line stuck with me because it’s a concise statement of bounded‑context thinking, even if the commenter didn’t know the term. It’s a sign that the community is already groping toward the right idea, but we need the vocabulary to make it explicit.

Bounded contexts already in the protocol

What I found most useful in the article is the observation that MCP’s design already enforces a form of bounded context at the topology level. Because each host spawns a separate client per server, and a client can talk to only one server, there’s no protocol‑level way for an MCP server to accidentally call another server’s tools. You have to deliberately build a bridge. That makes cross‑boundary coupling visible and intentional — exactly what DDD aims for.

If we name our MCP servers after the domain they own (e.g., “payment‑mcp” or “file‑store‑mcp”) rather than the API they wrap, we turn that topological guarantee into a conceptual one. The article’s suggestion to let a teammate know there are established patterns when they say “separate by blast radius” is a small but powerful nudge toward a shared language.

Where the anti‑corruption layer belongs

The second half of the piece dives into the Anti‑Corruption Layer (ACL) and shows two versions of a transfer_funds tool. The first version mixes LLM‑friendly string handling with domain logic directly in the tool function. The second version splits concerns: a thin tool layer that translates between the LLM’s string world and rich domain objects, and a service layer that holds the business rules, validation, and side effects.

I’ve seen this split work well in traditional systems, and the article makes a convincing case that it’s equally valuable here. The ACL protects the domain from leaking LLM‑specific concerns (like token limits or string coercion) and shields the LLM from having to understand complex domain invariants. It also gives us testability: the service can be exercised from unit tests, CI pipelines, or scripts without invoking an LLM at all.

Pushing back on the “abstraction tax” narrative

The article acknowledges a fair criticism: adding an MCP layer introduces an abstraction tax, losing some fidelity compared to calling a CLI tool directly. The Thoughtworks Tech Radar labels this a caution, and Simon Willison notes that many MCP use cases could be handled by a CLI instead.

I agree that the tax exists, but I think the article undersells why it’s worth paying. The tax isn’t just a cost; it’s the ACL doing its job. By forcing a translation step, we gain isolation, replaceability, and the ability to evolve the LLM interface or the domain independently. The same argument was made against API gateways and service meshes years ago, and those layers proved essential for operating at scale. If we pay the tax without getting the boundary benefit — i.e., we wrap APIs 1:1 and call it MCP — then we’re indeed getting all cost and no upside. But when we use the layer to enforce a true bounded context, the trade‑off pays off.

What I’d do differently

If I were advising a team starting with MCP, I’d suggest a quick exercise before writing any tool: sketch a context map. Identify the distinct subdomains your AI agents will interact with (payments, file storage, user profiles, etc.) and assign each its own MCP server. Name the servers after those subdomains, not after the underlying APIs. Then, define a thin ACL layer that translates between the LLM’s string‑based prompts and your domain objects. Only after those boundaries are clear should you start implementing the actual tool logic.

This approach mirrors how we eventually got microservices right: we stopped splitting code and started splitting models. The article shows we already have the map; we just need to use it.

If you want to read more deep dives like this without the noise of endless feeds, give DigestPress a try — it curates articles from the sources you choose and delivers a clean EPUB to your e‑ink device on your schedule. You can also see how we shape each issue here and why we built the product for e‑ink reading here. The original piece that sparked this reflection is available here.

See how DigestPress works →