Building an agent MCP-first — server for tools, client on the host, wire the loop — is the shape that most 2026 production agents converge on, and the field guide chapter for that shape is the natural capstone for the MCP deep-dive group.
By mid-2026 the median production agent doesn't build its own tool layer — it consumes MCP servers. Anthropic's own docs frame it that way; Bloomberry's survey of 1,412 production servers shows why. This chapter walks a real MCP-native agent end-to-end: pick tools by exposing them as an MCP server, wire the client on the host side, handle the loop including sampling and elicitation, test it in-process, and ship it with the operational disciplines the MCP deep-dive group covers. By the end you'll have built a small agent that reads and writes to a filesystem via an MCP server with proper auth and audit, and you'll know exactly which MCP essays to reach for when your production build hits each surface.
Why "MCP-native" is a real category in 2026.
The tool layer used to be something every agent team wrote from scratch. Each project reinvented the function-schema format, the argument validator, the retry policy, the audit log. That work absorbed weeks per project and produced tool layers that didn't compose across teams, so the moment you wanted to share a "read from S3" or "query Snowflake" tool between two agents you were rewriting the same wiring twice.
MCP finished that phase. The Model Context Protocol gave the field a common shape for how a model addresses a tool — a JSON-RPC handshake, a tools/list manifest, a tools/call invocation, and a standard place to hang auth. By 2026 that shape had been widely enough adopted that the median production agent no longer builds a tool layer at all. It consumes MCP servers written by someone else — sometimes an official vendor server, sometimes an internal server another team on the same company already stood up.
The data is concrete. Bloomberry's February 2026 survey of 1,412 public MCP servers found that the median server ships five tools, zero resources, zero prompts. Those numbers aren't a curiosity — they're the shape of the ecosystem. Every host with MCP support (Claude Desktop, Cursor, the Anthropic and OpenAI APIs' native MCP client surfaces, Windsurf, Continue) can wire the same server without adaptation, and the servers you consume are small and single-purpose. The eight-MCP production stack — a filesystem server, a database server, a monitoring server, a project-management server, and four more like them — is now a common shape. That is what "MCP-native" means: your agent's tool layer isn't code you wrote, it's a set of MCP endpoints your host connects to.
This chapter is the field-guide capstone for the MCP deep-dive group. If tool use was the introductory chapter for how models call functions, this is the operational chapter for how a 2026 agent actually wires those calls: expose the tools you own as an MCP server, run an MCP client in your host, and let the deep-dive essays supply the details as each surface bites.
Choosing which capabilities to expose as tools vs resources vs prompts.
MCP gives a server three primitives to expose a capability with. Tools are model-controlled actions — the model decides at runtime whether to invoke one, and the call may perform a side effect or a query on the model's behalf. Resources are host-controlled context — URI-addressable, read-only data the host reads and places into the model's context on its own schedule. Prompts are user-controlled templates — workflow scaffolding the server exposes and the host surfaces as slash-commands.
The mnemonic that sticks: tool = model chooses, resource = host chooses, prompt = user chooses. The MCP tool design deep-dive walks the choice in depth; the field-guide version applied to our worked example — a filesystem server that lets an agent read, write, and enumerate files under a sandboxed root — comes out like this.
read_file(path) and write_file(path, content) are tools. The model has to decide, mid-turn, that it needs to open a specific file or persist a specific edit. Neither call fits a "load at session start" pattern; both perform an operation the model requested at a specific point in the loop.
list_directory(path) is the ambiguous one, and the answer changes with the use case. If the agent needs a live directory listing at arbitrary times — say, an editor agent walking a codebase — it's a tool. If your host wants to pre-load a fixed tree at session start so the model always knows the project layout, it's a resource keyed off a URI like fs://project/tree. Most 2026 agents ship the tool version and add the resource only if the host actually surfaces resources; the median server ships zero resources for exactly this reason.
A prompt on this server might be /refactor {file} — a template that expands to a specific instruction the user can invoke as a slash-command. Prompts are the least-used surface; most filesystem servers don't ship any. Ship one only when it earns its keep in the host's slash-command UX.
Wiring the MCP client into your host loop.
The host side is where an "MCP-native" agent gets stitched together. The client establishes a session with each server it's configured to reach, calls tools/list to learn what's available, folds those tools into the same tool array the model already sees, and dispatches tools/call when the model picks one. The MCP architecture deep-dive walks the JSON-RPC lifecycle and the participant model; the building servers in practice essay covers the FastMCP server side. Here's the minimal client wired into a plain agent loop.
# host.py — minimal MCP-native agent loop (Python + fastmcp Client + Anthropic SDK) import anthropic from fastmcp import Client async def run_agent(user_msg: str, servers: list[str]) -> str: llm = anthropic.AsyncAnthropic() async with Client(servers[0]) as mcp: tools = [{"name": t.name, "description": t.description, "input_schema": t.inputSchema} for t in await mcp.list_tools()] messages = [{"role": "user", "content": user_msg}] while True: resp = await llm.messages.create( model="claude-mythos-preview", tools=tools, messages=messages, max_tokens=2048) messages.append({"role": "assistant", "content": resp.content}) if resp.stop_reason != "tool_use": return resp.content[0].text for block in resp.content: if block.type == "tool_use": result = await mcp.call_tool(block.name, block.input) messages.append({"role": "user", "content": [ {"type": "tool_result", "tool_use_id": block.id, "content": result.structured_content or result.text}]})
Three things are doing real work in that snippet. The Client(servers[0]) context manager runs the full initialize handshake — the client and server negotiate protocol version and capabilities before anything else happens. The tool-manifest translation adapts MCP's inputSchema shape into the vendor SDK's input_schema field; the two are the same JSON Schema but live under different property names, and MCP-native hosts learn to normalize once at the boundary. And the loop terminates on stop_reason != "tool_use", meaning any turn that doesn't request a tool is the final answer. Everything else — session management, framing, error envelopes — the SDK handles.
On the wire, the first exchange the host sees back from the server is a tools/list response like this one.
{"jsonrpc":"2.0","id":1,"result":{"tools":[
{"name":"read_file",
"description":"Read a file under the sandbox root. Returns text or a binary marker.",
"inputSchema":{"type":"object","required":["path"],
"properties":{"path":{"type":"string"}}}},
{"name":"write_file",
"description":"Write text to a file under the sandbox root. Creates parents.",
"inputSchema":{"type":"object","required":["path","content"],
"properties":{"path":{"type":"string"},"content":{"type":"string"}}}},
{"name":"list_directory",
"description":"List names under a directory. Non-recursive.",
"inputSchema":{"type":"object","required":["path"],
"properties":{"path":{"type":"string"}}}}]}}
Three tools, three descriptions written for the model to read, three schemas the host can validate. That is the entire agent-facing contract of the filesystem server, and it flows into the loop above without another line of glue.
The full loop, including sampling and elicitation.
The loop above is the ninety-percent case: model asks for a tool, server runs it, result feeds back. The other ten percent is where MCP earned its reputation for being subtle, and it's what the sampling and elicitation deep-dive covers in full. Both mechanisms invert the direction — the server initiates a call back into the host — and both matter more in production than most tutorials admit.
Sampling. The server needs an LLM to help it do its job — summarize a file, classify a piece of content, decide between two schemas — but the server doesn't hold an API key. In the pre-MCP world every server carried its own model credentials and every credential rotation was another operational surface. Sampling flips it: the server issues a sampling/createMessage request back through the client, the client uses the host's model with the host's key, and returns the completion. The host controls what model runs, what cost budget applies, and whether the user gets to approve the call. The server borrows intelligence without borrowing a key.
Elicitation. Mid-call, the server discovers it needs a value from the user — a confirmation, a piece of PII, a choice between options — that wasn't in the original tool arguments. Instead of failing or guessing, the server issues an elicitation/create request and the host renders a form to the user, then returns the response. This is the surface that lets a "delete these five files" tool ask "are you sure?" without the model having to fake a confirmation flow in its own reasoning.
Both features are declared as client capabilities during the initialize handshake, and both are optional. A server that requires sampling will refuse to start against a client that didn't advertise it; a server that offers elicitation gracefully degrades if the host can't render a form. In our filesystem example, elicitation is the correct primitive for the destructive path: delete_file issues an elicitation before it acts, and the user's answer becomes the ground truth, not the model's inference of what the user probably wants.
Testing the whole thing in-process.
The default instinct — spawn the MCP server as a subprocess, talk to it over stdio, assert on the JSON-RPC frames — is the wrong one, and the MCP testing deep-dive is the essay you reach for the moment your CI starts flaking. Both official SDKs ship an in-memory transport that runs a full protocol handshake without a process boundary. FastMCP's Python Client takes a server object directly; the TypeScript SDK exposes InMemoryTransport.createLinkedPair(). Either way, tests that used to take 200-500ms of subprocess boot collapse to single-digit milliseconds.
# tests/test_agent_loop.py — in-memory end-to-end for the loop above import pytest from fastmcp import Client from fs_server import mcp # the FastMCP filesystem server object @pytest.mark.asyncio async def test_read_after_write_round_trip(tmp_path): async with Client(mcp) as client: await client.call_tool("write_file", {"path": str(tmp_path / "note.md"), "content": "hello"}) result = await client.call_tool("read_file", {"path": str(tmp_path / "note.md")}) assert result.structured_content["text"] == "hello"
What matters about the shape: no config file, no CLI invocation, no port. The mcp server object is imported from the same package the production entrypoint imports, so a test that passes against the in-memory transport passes against a real client for the same reasons. Schema tests — assertions that tools/list still returns the tools with the descriptions and required fields your clients cached — live in a separate file and run first, because a schema regression breaks every downstream client at once. Behavior tests like the one above cover the specific cases; the deep-dive walks the split and shows what belongs in each suite.
Ship checklist.
The rest of a production MCP-native agent is operational discipline. Four surfaces each deserve one bite of attention before you flip the switch, and each has a deep-dive that goes deeper than this section will.
Auth. Anything reachable over a network needs the MCP OAuth 2.1 profile: mandatory PKCE, resource indicators per RFC 8707, Protected Resource Metadata for authorization-server discovery. The Bloomberry survey found 39% of production servers ship none of that. Don't be in that 39%. The MCP auth deep-dive is the reference.
Audit logging. Log the argument shape — the tool name, the schema-shaped arguments minus their values — not the argument values. That's what lets you answer "which tools did this session touch?" without dumping user PII into the log store. Per-tool kill switches, tenant isolation from verified token claims, and rate-limits sized for agent traffic all live under the same operational umbrella; MCP ops in production walks the pattern.
Transport. Local stdio for a server that ships with the host (Claude Desktop bundling a filesystem server, an IDE bundling a code-search server). Streamable HTTP for a server that lives on a network — the single-endpoint replacement for the deprecated HTTP+SSE transport, with MCP-Session-Id and Last-Event-ID resumability. Pick per server based on where it needs to run, not by whether you consider the setup dev or prod.
Failure envelope. The client sees three failure modes: the server returns a tool error (structured, actionable), the server returns a protocol error (initialize failed, transport dropped), or the server is unreachable. The host loop needs a policy for each. Tool errors flow back to the model as tool results with an isError: true marker so the model can decide whether to retry or reroute. Protocol errors surface to the operator. Unreachable servers should not silently disappear from the tool manifest mid-run — either the loop pauses and reconnects, or it fails loudly. Ambiguity here is where MCP-native agents get flaky in production.
The through-line: MCP-native isn't a rewrite, it's a refactor of where responsibility lives. Tools move out of your agent's codebase into servers your host connects to. Auth moves into a profile of a protocol the field already agreed on. Testing moves into an in-process transport that runs in milliseconds. What's left in your agent code is the loop, the host, and the product logic — smaller than it used to be, and easier to reason about, because every reusable piece has a place it lives.