mech.app

The mech.app newsletter

Agentic AI, minus the noise.

Get practical field notes on AI agents, automation, developer tools and security delivered to your inbox.

No spam. Unsubscribe anytime.

Automation

Why Building an MCP Compiler Beats Writing Another Server: What Tool Proliferation Reveals About Agent Infrastructure

A compiler for MCP servers solves the fifth-server problem. Examines the IR layer, emitter architecture, and when code generation beats hand-coding.

Source: dev.to
Why Building an MCP Compiler Beats Writing Another Server: What Tool Proliferation Reveals About Agent Infrastructure

The first MCP server takes an afternoon. The fifth one requires a team. That gap is not about skill. It is about the shape of the problem changing once you cross from “we need an integration” to “we need to maintain integrations.”

Adrian Bratulescu built a compiler instead of writing another server. The decision exposes a pattern that applies beyond MCP: when tool proliferation becomes the bottleneck, code generation starts to beat hand-coding.

The Fifth-Server Problem

Here is what happens when a company decides its capabilities should be reachable by AI agents:

  1. You write an MCP server. It works.
  2. ChatGPT wants the same capability shaped differently.
  3. Gemini wants its own version.
  4. Whatever ships next quarter needs another adapter.

Each integration is a separate project with its own SDK, tool definition format, and release cadence. Underneath, your API keeps changing. A field gets renamed, a response becomes nullable, an endpoint moves. Now you have four hand-written integrations that are each independently wrong, and no build step that will tell you.

The first server is an afternoon. The fifth is a maintenance surface that grows faster than the team.

What a Compiler for MCP Servers Actually Does

The architecture is a classic IR pipeline:

capabilities.yaml → semantic model → IR → emitter → MCP tools
                                        ↘ emitter → SDK / REST / GraphQL

You describe a capability once in business terms. What it does, what it needs, what it returns, whether calling it is safe to retry. No HTTP, no JSON Schema, no SDK boilerplate.

That lowers to a target-agnostic intermediate representation. Emitters consume the IR and generate output for each target: MCP servers, REST clients, GraphQL resolvers, whatever the next agent runtime requires.

Here is the entire tourism.search capability definition:

capability:
  id: tourism.search
  description: Find places to stay matching a traveler's intent.
  effect: read
  input:
    destination: { type: location }
    dates: { type: date-range }
    travelers: { type: integer }
  output:
    results: { type: array, items: accommodation }

Twelve lines. The emitter generates the MCP server, the tool schema, the input validation, and the response serialization. When the MCP spec evolves, you fix the emitter once. When your API changes, you update the YAML and regenerate.

The IR Layer: What It Guarantees and What It Does Not

The intermediate representation is where type safety and protocol independence meet. The IR knows about:

  • Capability semantics: read vs. write, idempotent vs. side-effecting
  • Type structure: primitives, enums, arrays, nested objects
  • Validation rules: required fields, ranges, formats
  • Error boundaries: what failures are retryable, what are permanent

The IR does not know about:

  • HTTP status codes
  • JSON Schema quirks
  • MCP transport details
  • SDK method signatures

This separation is what makes the compiler useful. When MCP adds a new feature (say, streaming responses or capability negotiation), you extend the IR and update the emitter. The capability definitions stay unchanged.

Testing Surface: Compiler Once or Every Generated Server?

The testing strategy splits into two layers:

Compiler tests verify that the IR correctly represents the input and that emitters produce valid output. You write property-based tests that generate random capability definitions and check that:

  • The IR preserves all semantic information
  • Emitted MCP servers pass the MCP test suite
  • Round-tripping (YAML → IR → YAML) is lossless

Generated server tests verify that the capability actually works. These are integration tests against your real API. You generate the server, deploy it, and run the same test suite you would run for a hand-written server.

The key insight: you test the compiler exhaustively once. You test each generated server the same way you test any integration: does it call the right endpoint, does it handle errors, does it serialize responses correctly.

When the MCP spec evolves, the compiler tests catch breakage before you regenerate anything.

Failure Modes and Observability Gaps

Code generation introduces new failure modes:

Failure TypeHand-Written ServerGenerated Server
Type mismatchCaught at compile timeCaught at generation time
Protocol violationRuntime error in productionCaught by emitter tests
API schema driftSilent breakageBuild fails if IR is stale
DebuggingStep through your codeStep through generated code

The debugging experience is worse. When a generated server fails, you need to understand both the capability definition and the emitter logic. Stack traces point to generated files, not your YAML.

Observability becomes critical. You need:

  • Generation metadata: which emitter version, which IR version, which capability definition hash
  • Diff tracking: what changed between the last working build and this one
  • Emitter logs: what decisions the code generator made and why

Without these, debugging a generated server feels like reverse-engineering a black box.

When to Generate vs. When to Hand-Code

The compiler approach makes sense when:

  • You have more than three target platforms
  • Your API changes frequently
  • You need to support multiple agent runtimes
  • The cost of maintaining N servers exceeds the cost of maintaining one compiler

Hand-coding still wins when:

  • You have one or two integrations
  • The protocol is stable
  • You need fine-grained control over error handling
  • The capability is too weird to fit a schema (stateful sessions, streaming, callbacks)

The crossover point is not about lines of code. It is about the rate of change. If your API evolves weekly and you support four agent platforms, the compiler pays for itself in a month. If your API is stable and you only need MCP, hand-coding is simpler.

What This Pattern Reveals About Agent Tooling Maturity

The shift from “write another server” to “generate all servers from a schema” is a maturity signal. It means:

  1. The protocol is stable enough to codify. Early-stage protocols change too fast for code generation to help.
  2. The ecosystem is fragmented enough to justify meta-tooling. If everyone used the same runtime, you would just write one adapter.
  3. The maintenance burden is real. Teams are hitting the limits of manual integration work.

MCP is entering this phase now. Enough servers exist that the pattern is clear. Enough variation exists (ChatGPT, Claude, Gemini, local runtimes) that multi-target support is not optional. The next wave of tooling will be compilers, schema validators, and cross-platform test harnesses.

Deployment Shape and State Management

Generated servers are stateless by design. The compiler assumes:

  • Each capability is a pure function from input to output
  • State lives in your API, not the MCP server
  • Retries are safe for read operations, unsafe for writes

This works for most agent use cases. It breaks down when:

  • You need session affinity (multi-turn conversations that share context)
  • You need streaming (long-running operations that emit partial results)
  • You need callbacks (the agent calls you, you call back later)

For these, you either extend the IR to support stateful capabilities or drop back to hand-coding. The compiler is a tool for the common case, not the edge case.

Technical Verdict

Use a compiler when:

  • You maintain integrations for three or more agent platforms
  • Your API changes more than once a month
  • You can express your capabilities as stateless request/response pairs
  • You have the infrastructure to test and deploy generated code

Hand-code when:

  • You have one or two integrations
  • The protocol is experimental or rapidly changing
  • You need fine-grained control over error handling, retries, or state
  • Your capabilities do not fit a schema (streaming, callbacks, sessions)

The compiler approach is not about avoiding code. It is about moving the maintenance burden from N servers to one IR and M emitters. When N × API_change_rate exceeds M × protocol_change_rate, generation wins.

The fifth server is where you find out if you are building integrations or building infrastructure.


Tags

agentic-ai orchestration infrastructure

Primary Source

dev.to