DEV Community

Cover image for Build MCP Servers Before Agents: The Enterprise Shift AI Teams Need
Guy for AWS Heroes

Posted on

Build MCP Servers Before Agents: The Enterprise Shift AI Teams Need

A team builds a support agent in an agent framework. Another team builds a finance agent. A third builds a sales operations agent. Each demo is impressive. Each agent has a prompt, a model, some memory, a few tools, and a nice workflow graph. Then the enterprise reality arrives.

The support agent has its own private Salesforce connector. The finance agent has a different connector to the same customer data. The sales operations agent defines "North America" differently from both of them. Each team handles authentication a little differently. Each team logs different things. Each team wraps the same APIs again. Six months later, the company does not have an agent platform. It has a convoy of bespoke machines held together by prompts, glue code, and optimism.

That is the wrong foundation.

The current market narrative tells teams to start with the agent: pick a framework, write the instructions, choose a model, add tools, and ship. That works for demos. It is backward for enterprises.

The reusable asset in enterprise AI is usually not the agent. It is the MCP server (AKA connector).

Agents should come later, as lightweight clients built from three parts:

  • instructions: what the agent should do, what it should not do, and how it should behave — much like instructions you would give a human employee. These are defined by business people and updated frequently as the business learns.
  • LLM: the reasoning and language component, usually from a major vendor such as OpenAI or Anthropic.
  • tools: capabilities exposed through MCP servers, with all the design, security, testing, and governance work we covered in the earlier articles.

That is the shift this article is about. And to make it concrete, this time we will not stop at the diagram — we will build a complete, running agent and show that the "hard part" was never the agent at all.

What Is MCP? (The 30-Second Version)

The Model Context Protocol (MCP, spec 2025-11-25) is the interface layer between AI clients and external systems through tools, prompts, and resources. In enterprise deployments, MCP servers should be thin, remote, mostly stateless interface layers over internal systems and SaaS platforms. They are the AI-facing equivalent of web servers and mobile backends.

That makes the next architectural point straightforward:

  • MCP servers are the governed capability layer
  • agents are MCP clients

An Agent Is Simpler Than Many Frameworks Make It Look

Before talking about architecture, it helps to simplify the mental model.

At its core, an enterprise agent usually needs only three things:

  1. Instructions — the job description. What this agent is for, what success looks like, what boundaries it must respect, what tone to use, when to ask for clarification, when to stop, and when to escalate.

  2. LLM — the reasoning and language engine. It interprets the request, decides what to do next, calls tools, reads results, and produces the answer.

  3. Tools — the actions and data-access surfaces. In a serious enterprise design, these come from MCP servers rather than as private helper functions bundled inside a single agent.

That leads to a simple loop:

read instructions
read user request
decide whether to answer directly or call a tool
if needed, call a tool on an MCP server
read the tool result
repeat until the request is satisfied or the task fails safely
Enter fullscreen mode Exit fullscreen mode

That is the basic ReAct loop. For many real enterprise cases, that is the right default model. The agent does not need a giant graph, a maze of private adapters, or a pile of framework-specific abstractions to be useful.

The claim that "an agent is simple" is easy to make and easy to doubt. So let us prove it in code.

The Simplicity Is Real: An Agent in Forty Lines

Here is a complete agent written in Rust with the PMCP SDK. Don't worry if you're not fluent in Rust — the comments walk through every important line, and the shape is what matters. Watch how directly the three parts above map onto three pieces of code.

// -- Dependencies --
// pmcp-agent: the deploy-anywhere agent decision loop for the PMCP SDK.
// An agent here is a loop over THREE swappable "seams":
//   CompletionSource  -> the LLM (produce the next model turn)
//   ToolInvoker       -> the tools (run a tool call, return its result)
//   ConversationStore -> the memory (persist and replay the conversation)
use pmcp_agent::{
    AgentEngine, ResolvedAgentConfig, InMemoryStore,
    CompletionSource, ToolInvoker, ToolCall, ToolCallResult, RunOutcome,
};
use async_trait::async_trait;
use serde_json::json;

// -- Part 1: INSTRUCTIONS --
// The "job description." Plain text a business person can own and edit.
// The last two numbers are guardrails, not intelligence: a token budget
// and a hard cap on how many tool-calling turns the loop may take before
// it stops safely. That cap is why a thin agent cannot run away.
fn instructions() -> ResolvedAgentConfig {
    ResolvedAgentConfig::new(
        "You are a concise support assistant. Look up orders when asked. \
         If a request is outside orders, say so and stop.",
        "claude-sonnet-4-5", // the model name; the LLM itself is the next seam
        /* max_tokens     */ 100_000,
        /* max_iterations */ 5,
    )
}

// -- Part 3: TOOLS --
// The tools seam. This is the ENTIRE surface where an agent touches the
// outside world. In production you would hand this seam a `ClientToolInvoker`
// that forwards each call to a governed MCP server (see the next section).
// Here we stub one tool inline so the example runs with no network:
struct OrdersTools;

#[async_trait]
impl ToolInvoker for OrdersTools {
    async fn invoke(&self, call: ToolCall) -> ToolCallResult {
        // `call.name` is the tool the LLM chose; `call.arguments` is its JSON input.
        // A real invoker dispatches to an MCP server and returns its result verbatim.
        ToolCallResult::ok(call.id, json!({ "status": "shipped", "eta": "2 days" }))
    }
}

#[tokio::main]
async fn main() {
    // -- Assemble the agent from its three parts + memory --
    // Part 2 (the LLM / CompletionSource) is passed in as `llm`. In a test, it is
    // a mock; in production it is one line — see "Swapping the Brain" below.
    let llm = /* a CompletionSource: mock, Anthropic, or any OpenAI-compatible endpoint */;

    let engine = AgentEngine::new(
        llm,                  // Part 2: the brain
        OrdersTools,          // Part 3: the tools (the MCP boundary)
        InMemoryStore::new(), // memory
        instructions(),       // Part 1: the job description
    );

    // -- Run it. This IS the ReAct loop. --
    // A "run" is a conversation identified by `run_id`; its first turn is the
    // user's request, seeded into the store. (In production the AgentServer
    // adapter seeds it for you from the incoming MCP tool call — see below.)
    // `run` then interprets that request, lets the model choose a tool, dispatches
    // it through the invoker, feeds the result back, and repeats until the model
    // ends the turn or a guardrail trips. You did not write that loop.
    let run_id = "order-status-4815";
    match engine.run(run_id).await {
        RunOutcome::Completed { result } => println!("done: {result:?}"),
        RunOutcome::LimitReached        => println!("hit the iteration guardrail — stopped safely"),
        other                           => println!("stopped: {other:?}"),
    }
}
Enter fullscreen mode Exit fullscreen mode

Now look at what you did not write:

  • No ReAct loop. AgentEngine::run is the loop.
  • No tool-dispatch plumbing, no argument parsing, no result threading.
  • No retry, budget, or turn-limit bookkeeping — the config and the engine own that.
  • No framework-specific graph, node, or edge abstractions.

What remains is exactly the three-part model: a paragraph of instructions, a model name, and a tools seam. The agent is thin on purpose. All of its leverage lives on the other side of that ToolInvoker.

If you want to scaffold this rather than type it, the CLI generates the whole thing and runs it offline against a scripted model, so you can see the loop before wiring a real LLM or any MCP servers:

cargo pmcp agent new support-agent   # scaffold instructions + the three seams
cargo pmcp agent dev --source fixed   # run the loop offline, no API keys, no network
Enter fullscreen mode Exit fullscreen mode

Where The Tools Come From

The single most important line in that example is the one that looks least important:

OrdersTools,          // Part 3: the tools (the MCP boundary)
Enter fullscreen mode Exit fullscreen mode

The ToolInvoker seam is the entire boundary between the agent and the outside world. Whatever you plug in there decides where the agent's capabilities live. That is the whole architectural argument of this series, expressed as one type parameter. Once you understand that an AI agent is simply an MCP client, everything else falls into place, and all the investment in building MCP server connectors returns with huge dividends of security, simplicity, and scale.

Plug in a struct full of private helper functions, and you have rebuilt the Mad Max convoy: this agent's Salesforce logic, this agent's "North America," this agent's auth. Plug in an invoker that forwards each call to a governed MCP server, and every capability comes from the shared platform layer — designed, secured, tested, and observed once.

The PMCP SDK ships exactly that production invoker. Instead of OrdersTools, a real agent uses ClientToolInvoker, which dispatches each tool call to an MCP server and returns its result — including long-running work modeled as tasks:

// Production: the tools seam forwards to a governed MCP server.
// `connector` is an MCP client pointed at your orders/CRM/warehouse server.
// The agent gains no private connector code; it borrows the platform's.
let tools = ClientToolInvoker::new(connector, /* max_poll_secs */ 30);

let engine = AgentEngine::new(llm, tools, InMemoryStore::new(), instructions());
Enter fullscreen mode Exit fullscreen mode

Nothing else in the agent changes. The instructions are the same, the loop is the same, the model is the same. The only thing that changed is where the capabilities come from — and that is the only thing that should decide whether your fleet of agents is governable.

This is the capability-first model as a code diff: swapping a private-tools invoker for an MCP-backed one is a one-line change, and it is the difference between a rogue demo and a platform citizen.

Swapping The Brain Is One Line

The other seam that proves how thin an agent should be is the LLM. Because the model sits behind CompletionSource, changing it never touches the loop, the tools, or the instructions:

use pmcp_agent::sources::{AnthropicSource, OpenAiCompatSource, SecretString};

let key = SecretString::new(std::env::var("LLM_API_KEY")?);

// A local model for development (any OpenAI-compatible endpoint, e.g. Ollama):
let llm = OpenAiCompatSource::new("http://localhost:11434/v1", "llama3.2", key)?;

// A frontier model for production — same engine, same tools, same instructions:
let llm = AnthropicSource::new("https://api.anthropic.com", "claude-sonnet-4-5", key)?;
Enter fullscreen mode Exit fullscreen mode

Both satisfy the same seam, so both drop straight into AgentEngine::new. You can develop offline against a scripted or local model and promote to a hosted one for production without rewriting the agent. When the model is this easy to swap, you stop entangling business logic with a particular vendor — and you keep the reusable work where it belongs, on the MCP servers behind the tools seam.

The Capability-First Agent Model

Step back from the code and the architecture reads as one principle: capabilities are shared infrastructure; agents are thin clients.

  1. Capabilities live on MCP servers. Tools, prompts, resources, and governed code-mode surfaces belong in the shared platform layer.

  2. Agents stay thin. An agent is mostly instructions, an LLM, and a selected set of those capabilities — the forty lines above.

  3. Specialized agents can themselves become capabilities. If a later design exposes a specialist agent through MCP, another agent can call it like any other tool. (The PMCP AgentServer adapter does exactly this — it exposes the same loop as a normal MCP server. That recursive step is the subject of the next article.)

  4. Shared state stays explicit. Tasks, artifacts, and systems of record are better homes for shared state than hidden prompt state or private connector logic.

This article is mainly about the first two points. The next article extends the same model to teams.

The Mistake: Treating Tools As A By-Product Of The Agent

The common development pattern today looks like this:

  1. choose an agent framework
  2. define the agent's instructions
  3. pick a model
  4. write some tools for that agent
  5. wire them together

That feels natural because the agent is the visible thing. But architecturally it is upside down. When tools are created as part of one agent, the problems appear quickly:

  • The same backend gets wrapped multiple times by different teams.
  • Business semantics drift between agents.
  • Authentication and authorization logic get duplicated.
  • Observability is fragmented across agent codebases.
  • Testing quality varies by team.
  • Security posture depends on whoever wrote the last connector.
  • No one knows which tool implementation is the canonical one.

This is how organizations end up with rogue agents: individually clever, collectively ungovernable. The deeper issue is that the tool layer is being treated as disposable application code when it should be treated as shared platform infrastructure. In the code, that mistake is a single choice — what you pass to the ToolInvoker seam.

Build The Roads Before The Cars

The cleanest analogy I know is transportation.

Rogue agents vs. ordered ones

The agent-first model looks like a Mad Max convoy. Every team builds its own vehicle from scratch. The wheels are different. The fuel systems are different. The controls are different. Each vehicle may be brilliant in isolation, but the system as a whole is chaotic and expensive.

The MCP-first model looks like a city that built the roads first. The roads are paved, the lanes are marked, the traffic lights are standardized, the signs are shared, maintenance is organized, and safety rules are explicit. Once that infrastructure exists, many kinds of efficient vehicles that everybody can drive can use it.

That is what MCP servers are for enterprise AI. They are the roads. They give the organization shared interfaces to data systems, shared business definitions, shared authentication and authorization boundaries, shared observability, shared testing discipline, and shared governance. Agents are then the vehicles that move on top of that infrastructure — some simple, some specialized, some aggressively optimized, but all benefiting from the same road system.

The Enterprise Shift

The shift I want enterprise teams to make is this:

Stop asking "what tools should this agent have?" before you ask "what MCP servers should this organization have?"

Those are not the same question. The first is local and short-term. The second is architectural. An enterprise-first sequence looks more like this:

  1. Identify the core data systems and business domains.
  2. Build MCP servers for those systems using the best practices from the earlier articles.
  3. Expose outcome-oriented tools, prompts, resources, and, where appropriate, code mode.
  4. Apply security, testing, and governance at the server layer.
  5. Only then define agents as instruction sets plus models plus selected MCP capabilities.

This order matters because the MCP server layer is where reuse happens. It is not a call for a big-bang platform rewrite — start with one business domain, publish one or two well-governed MCP servers, and build a small number of agents on top. The important shift is not "wait until the platform is perfect." It is "put reusable capability work in the platform layer from the beginning."

The agent-first model can look fine when there are three demos in one lab. It breaks when the organization starts talking about dozens, then hundreds, then thousands of agents. At that scale you are no longer solving a prompt problem; you are solving a platform problem:

Question Agent-First Model MCP-First Model
Where do tools live? Inside each agent codebase In shared MCP servers
Who defines business semantics? Each team, repeatedly Once per governed server surface
How is auth handled? Per agent, inconsistently At the server layer, consistently
How do agents share capabilities? Copy code or rebuild connectors Reuse the same server
How do you test the interface? Ad hoc by team Server-level test gates
How do you observe usage? Fragmented logs across agents Centralized capability-layer telemetry
How do you swap models or instructions? Often entangled with tools Independent from tools (one line)
How do you span systems? Custom orchestration in every agent Compose across MCP servers

This is why the current market emphasis on "building agents" can be misleading. It draws attention to the system's visible tip — the forty lines — and away from the part that determines whether the whole thing can scale.

Internal MCP Servers First, Vendor MCP Servers Second, Agents Third

Enterprise AI rarely lives in one system. A real company has internal databases, internal APIs, data warehouses, identity systems, document stores, ticketing systems, and SaaS platforms. At the same time, major vendors are all pushing their own agent stories — Salesforce, Azure, AWS, ServiceNow, Google — and each wants you to build agents inside its orbit.

That is understandable from the vendor's perspective, but it is not the right control point for the enterprise. The better model is:

  • Build MCP servers around your internal systems.
  • Adopt MCP servers from external SaaS providers when they exist and are good enough.
  • Build your agents as MCP clients that can call both.

A customer-success agent might need internal customer master data, a SaaS CRM, a support platform, and internal revenue and usage data. No single vendor should own that whole orchestration surface by default. The agent should sit above those systems — behind that one ToolInvoker seam — not be trapped inside one of them.

The Business Analyst Matters More, Not Less

This shift does not remove the role of the business analyst. It makes that role more important. If agents are lightweight clients on top of MCP servers, then the quality of those servers matters even more. Someone still has to decide:

  • Which business outcomes deserve first-class tools
  • Which workflows should be packaged as prompts
  • Which resources should be injected as governed context
  • Which definitions of quarter, region, customer status, and policy are canonical
  • Which actions should be impossible rather than merely discouraged

If every team embeds those choices inside its own agent prompt, the organization gets semantic drift. If those choices are encoded once in the MCP layer, the organization gets consistency. The security article made exactly this point for access control and aggregation semantics; the same idea applies here at the full agent-platform level.

The ReAct Loop Is Simple. The Platform Under It Is Not.

It is important not to confuse two statements:

  • The runtime loop of a good agent can be simple — you saw it fit in forty lines.
  • The infrastructure that makes simple agents safe and scalable is not simple.

That is why the earlier articles matter. The loop can stay small precisely because the MCP layer carries the real engineering burden:

  • tool design makes the actions understandable
  • prompts and resources package repeatable workflows and governed context
  • testing makes the capability surface stable
  • code mode covers the long tail without exploding tool count
  • security makes the whole thing enterprise-safe

If that foundation is missing, teams compensate by stuffing more policy, more special cases, and more connector logic into the agent itself — until the forty lines become four thousand. The result is the illusion of a smart agent built atop a weak platform.

The Core Message

The market talks constantly about building agents. Agents are visible. They demo well. Vendors can package them. Frameworks can make them feel like the main event. But for enterprise AI, the real leverage is lower in the stack, and the agent itself — as the code showed — is genuinely small.

Carry this model forward:

  1. An agent is not magic. It is instructions, an LLM, and tools — three seams and a loop you don't have to write.

  2. The right default loop is simple. Read instructions, read the request, call the right tool, inspect the result, repeat until done or failed safely.

  3. Tools should not be private by-products of each agent. They belong on MCP servers, reached through the one seam that defines the agent's whole outside world.

  4. MCP servers are the reusable foundation. They carry semantics, security, testing, observability, and governance.

  5. The scalable model is cross-system. Internal MCP servers plus external SaaS MCP servers give agents a governed way to work across the real enterprise data estate.

  6. The enterprise shift is architectural. Stop starting with "What tools should this agent have?" Start with "What MCP servers should this organization have?"

If an organization wants to run a handful of demos, it can build agent-first and survive the mess. If it wants to run AI seriously across the company, it should build MCP servers first, treat them as shared platform infrastructure, encode business semantics and governance there, and compose thin agents on top — incrementally. One good server in one domain plus one or two thin agents on top of it is already a better enterprise foundation than five siloed agents with five private connectors.

Build the roads before the cars.

Top comments (0)