05. One protocol, many servers — MCP as the standard memo between agent and world¶
~16 min read. Tools compose beautifully inside one codebase. But real agents reach across organizational boundaries — three vendors, two internal teams, yesterday's prototype. Without a shared wire format, every client rebuilds every wrapper. With one, the wrappers collapse from N×M to N+M.
Builds on 04-tool-composition.md. Composition patterns assumed every tool lived in the same repo. The memo format — the standard protocol every tool can speak — is what makes many tools callable from many agents without bespoke glue per pair.
The team that rebuilt the same wrapper six times¶
A mid-sized fintech runs three agents — support, developer assistant, analytics co-pilot. Each needs Zendesk, Confluence, Snowflake, and Slack. The team chose bespoke wrappers: each agent gets its own wrapper per tool. Three agents, four tools, twelve integration surfaces.
Three months in, roughly forty percent of agent engineering time has gone to integration and re-integration, not to actual agent design. When Zendesk changed its OAuth flow, three wrappers needed patching. When the support team added a fifth agent, they wrote four new wrappers from scratch — same APIs, but the conventions from the analytics co-pilot didn't fit. One uses snake_case argument names, another camelCase, the third inherited an older convention. The same Zendesk API, three subtly different shapes presented to three different models.
A second team at the same company took a different path. One Zendesk MCP server exposes list_tickets, get_ticket, add_comment. One Confluence server, one Snowflake server, one Slack server. Four servers, three agents — seven integration surfaces. When Zendesk changed OAuth, one server updated. When the fifth agent arrived, it wired up in an afternoon. The argument-name drift disappeared because every agent sees the same schema — the schema the server declares.
The model didn't change. The tools didn't change. The protocol layer decided the engineering cost surface.
Teacher voice. A protocol is not a feature; it is a contract that survives the framework, the model, and the team that wrote the original wrapper. Frameworks come and go on quarterly cycles. The contract you can read in 2030 is the protocol you adopted in 2026.
Why a protocol: the N×M arithmetic¶
Without a shared protocol, N agent clients and M tool backends give N × M integrations. Add a client and you owe M new wrappers. Add a backend and every client owes one more.
without a shared protocol
clients ────────────────────────────────── tools
Support agent ──┬── wrapper #1 ───→ Zendesk
├── wrapper #2 ───→ Confluence
├── wrapper #3 ───→ Snowflake
└── wrapper #4 ───→ Slack
Dev assistant ──┬── wrapper #5 ───→ Zendesk
├── wrapper #6 ───→ Confluence
...
3 clients × 4 tools = 12 integrations
with MCP
clients ── speak MCP ──→ MCP servers ──→ tools
Support agent ──────→ Zendesk server ─→ Zendesk
Dev assistant ──────→ Confluence sv ─→ Confluence
Analytics copilot──────→ Snowflake sv ─→ Snowflake
┌────→ Slack server ─→ Slack
3 clients + 4 servers = 7 integrations
At three and four, the gap is five integrations — interesting but not life-changing. Push the numbers: ten clients, twenty-five tools. Without a protocol — 250 integrations to write, test, and upgrade. With one — 35. The cost surface shifts from quadratic to linear, and a quadratic curve at scale swallows engineering teams.
What forces a protocol to exist is not that any single integration is hard. Each Zendesk wrapper is a weekend of work. What forces it is the compounded maintenance — upgrades, auth-flow changes, schema migrations, per-client drift that makes the same backend behave subtly differently for different agents. The integration cost is paid in maintenance, not in build.
The tension: integration cost vs flexibility. Without a standard, every new tool is bespoke glue. With a standard, you gain composability but accept the protocol's constraints. The protocol wins when the edge count crosses roughly 3 agents × 3 backends.
What MCP actually standardises¶
MCP — Model Context Protocol — is the Anthropic-led spec that gives the agent-tool graph a single edge type. Treat the name structurally: the protocol by which models obtain context and capabilities. Same family as HTTP standardising web requests. Four layers of standardisation:
Discovery. Client connects, asks "what can you do?" Server answers with a structured list of tools, resources, and prompts. Discovery is a method call, not a README. A new server is discoverable the moment it boots.
Description. Every tool carries a name, description, and JSON-Schema input contract — exactly the schema-and-description discipline from earlier chapters. Resources carry a URI and content type. Prompts carry a name, description, and argument list.
Invocation. Client sends tool name + arguments matching declared schema. Server replies with structured response or structured error. Network failures and protocol errors are distinguishable from business-logic errors because the protocol distinguishes them at the message level.
Resource and prompt fetch. Resources are read-only context fetched by URI. Prompts are reusable instruction templates fetched by name. The agent doesn't call a tool to read a document; it asks for a resource at a known URI.
The point: schemas, RPC, and URI-addressed resources all existed before MCP — but separately for each integration. The cost of agreement was paid client by client. MCP is the agreement.
What the per-agent code looks like before and after:
agent codebase, pre-MCP agent codebase, post-MCP
zendesk_oauth.py (200 lines) # nothing — server handles auth
zendesk_client.py (250 lines) mcp_client.connect("zendesk")
zendesk_wrapper.py (120 lines) # nothing — protocol handles dispatch
zendesk_tool_defs.py (80 lines) # discovered via list_tools()
zendesk_errors.py (60 lines) # protocol surfaces errors uniformly
total: ~710 lines per agent total: ~5 lines per agent
The seven hundred lines are not deleted globally — they live in the server. They are deleted from every agent that adopts the server. Three agents save roughly two thousand lines and, more importantly, the upgrade tax on those two thousand lines.
The three primitives: tools, resources, prompts¶
A well-designed MCP server exposes three structurally different capability shapes. The categorisation rule:
- Tool — changes state in an external system. The model selects it, supplies arguments, the server runs business logic. Database rows written, messages sent, files created.
- Resource — read-only context at a stable URI. No side effects, no business logic beyond returning content. Policy documents, schema files, runbooks.
- Prompt — reusable instruction template with named arguments. Published so any client can start the pattern consistently. "Summarise findings," "review brief."
┌────────────────┐ ┌────────────────┐ ┌────────────────┐
│ TOOL │ │ RESOURCE │ │ PROMPT │
│ action │ │ data │ │ template │
│ model calls │ │ client reads │ │ user picks │
│ side effect │ │ no side │ │ reusable │
│ on the world │ │ effect │ │ framing │
│ │ │ │ │ │
│ search_reports│ │ policy://... │ │ summarise_ │
│ save_note │ │ style://... │ │ findings │
│ publish_brief │ │ research://...│ │ review_brief │
└────────────────┘ └────────────────┘ └────────────────┘
Why the separation matters — three concrete dividends:
Cost. A tool call costs a model round trip. A resource fetch is a direct client read. A team that exposes its citation policy as get_citation_policy() — a tool — burns a round trip every time the agent needs grounding. As a resource at policy://editorial/citation-rules, it's a zero-round-trip read. Across thousands of calls, the cost difference is dramatic.
Discoverability. list_tools() returns the action surface. list_resources() returns the read surface. list_prompts() returns the framing surface. Security can audit each independently — "what can this agent do?" has a precise answer separated from "what can it read?"
Consistency. Server-side prompts collapse client-side template drift into one canonical framing. Without them, three teams write three diverging versions of "summarise findings" over six months, each with subtly different definitions of what "findings" means.
The common category mistakes¶
Four patterns that ship in production and cost money:
- Static context exposed as a tool.
get_citation_policy()returns a string. The agent spends a tool call to read a static document on every brief. Fix: resource with a stable URI. - Reusable prompts hidden in client code. Each client team writes their own "summarise findings" with variations. After six months, three versions exist. Fix: server-side prompt primitive.
- Side-effecting capability exposed as a resource. A "reset cache" at a clean URI. Any client that crawls resources during discovery accidentally clears the cache. Fix: anything that changes state is a tool, period.
- Tool descriptions that read like docstrings. "Calls the /v3/publish endpoint" instead of "publishes the brief after editorial approval; do not call before review_brief has run." The model routes based on descriptions at selection time, not at implementation time.
Server and client: who exposes, who consumes, who wires¶
user
│
▼
┌─────────────────────────────┐
│ MCP CLIENT │
│ - model lives here │
│ - user interaction │
│ - prompt assembly │
│ - tool selection / loop │
│ - state management │
└──────────────┬──────────────┘
│ protocol messages
▼
┌─────────────────────────────┐
│ MCP SERVER │
│ - capability registration │
│ - tool / resource / prompt │
│ implementation │
│ - business logic │
│ - auth against backends │
└─────────────────────────────┘
The model is the client's, not the server's. This is the detail most teams get wrong on a first read. The server is a capability provider; the model is the capability consumer. The same MCP server works for a Claude-based agent, a GPT-based agent, and an internal fine-tune — without changing.
The reason the split is structural rather than stylistic: swapping either side becomes cheap only when the line stays clean. Move agent-loop logic into the server (say, retry logic that belongs to the client's stopping rule) and every client now inherits that behaviour whether they wanted it or not. Move capability logic into the client (say, embed the citation-policy text into the client's system prompt) and the server stops being authoritative — every client now has its own copy of policy that drifts.
The host pattern. A single client often connects to many servers simultaneously. Claude Desktop on an analyst's laptop might merge a filesystem server, web-search server, database server, and internal report-search server into one unified toolbelt. Each server is independent; failure of one degrades but does not break the client.
Claude Desktop (client / host)
├── filesystem server (4 tools, 0 resources)
├── web-search server (2 tools, 0 resources)
├── postgres server (3 tools, 1 resource)
├── report-search server (1 tool, many resources)
└── jira server (5 tools, 2 resources)
The host resolves namespace collisions (filesystem.search vs web.search), isolates per-server failures, and filters capabilities per turn. New capabilities arrive as new servers, not as new client code. Composability emerges from the protocol's contract, not from per-pair engineering.
The same property scales the other direction: one server, many clients. Build the search server once; any MCP-compatible client reuses it. A Zendesk server written in 2025 still works for a client built in 2027 because the protocol is the contract, not the framework.
Wire format essentials¶
MCP uses JSON-RPC 2.0 as its message format. What you need to know without reading the full spec:
Lifecycle. Client opens a connection → sends initialize with its capabilities → server responds with its capabilities → client calls list_tools(), list_resources(), list_prompts() → normal operation begins.
client server
│ │
│──── initialize(clientInfo, caps) ───────→│
│←─── result(serverInfo, caps) ───────────│
│ │
│──── tools/list ─────────────────────────→│
│←─── [{name, description, schema}, ...] ──│
│ │
│──── resources/list ─────────────────────→│
│←─── [{uri, name, mimeType}, ...] ────────│
│ │
│──── tools/call(name, args) ─────────────→│
│←─── {content: [...]} ────────────────────│
Transport. Two common shapes: - stdio — server is a child process. Client launches it, communicates over stdin/stdout. Tight lifecycle control. Used for desktop apps and local-only tools (filesystem, git). Kill the parent, kill the server. - HTTP/SSE (Streamable HTTP) — server is a long-running process on a network address. Many clients connect. Used for shared infrastructure, vendor-hosted servers, multi-tenant deployments. Needs auth, routing, ops care.
┌──────────────┬─────────────────────┬────────────────────┐
│ Transport │ Best fit │ Watch-out │
├──────────────┼─────────────────────┼────────────────────┤
│ stdio │ local tools, │ tied to parent │
│ │ desktop apps │ process; single │
│ │ │ consumer │
├──────────────┼─────────────────────┼────────────────────┤
│ HTTP / SSE / │ remote servers, │ needs auth, │
│ Streamable │ shared infra, │ routing, ops │
│ HTTP │ vendor-hosted │ │
└──────────────┴─────────────────────┴────────────────────┘
The discipline: do not couple transport to capability code. A well-designed server has tool implementations in functions that know nothing about how they got called. The framework wraps them with transport machinery. If switching from stdio to HTTP would require changing tool functions, transport has leaked into the business logic.
Capabilities negotiation. Both sides declare what they support during initialize. A server that supports resource subscriptions advertises it; a client that doesn't need prompts can skip list_prompts(). The negotiation prevents assumption drift between versions.
Discovery as permission surface. The capability list — tools, resources, prompts — is what the server can do, and it is enumerable before any agent connects. Security review becomes precise: "Tool X is safe to expose; Tool Y requires per-call approval; Resource Z is sensitive and must be tenant-scoped." Compared to hand-rolled wrappers where the only review path is reading agent source code, this is dramatically cheaper for compliance teams. The protocol doesn't enforce authorisation; it makes the surface that authorisation operates over visible.
A concrete server in 30 lines¶
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("market-research")
@mcp.tool()
def search_reports(query: str, year: int, region: str = "india") -> list[dict]:
"""Search the indexed report catalogue. Returns title and URI per hit."""
return [{"title": "UPI Growth 2024", "uri": "research://topic/upi-growth-2024"}]
@mcp.tool()
def save_note(topic: str, note_markdown: str, source_uri: str) -> dict:
"""Persist an analyst note linked to its source. Returns status."""
return {"status": "saved", "topic": topic}
@mcp.tool()
def publish_brief(title: str, audience: str, body_markdown: str) -> dict:
"""Publish a final brief after editorial review. Only call when citations attached."""
return {"status": "published", "uri": f"brief://{title.lower().replace(' ', '-')}"}
@mcp.resource("policy://editorial/citation-rules")
def citation_rules() -> str:
return "Every market claim needs a cited source. Prefer primary sources."
@mcp.resource("style://brief/market-analysis")
def brief_style() -> str:
return "Write in short sections. Lead with the finding. Keep tables small."
@mcp.prompt()
def summarise_findings(topic: str, notes_markdown: str) -> str:
return f"Summarise findings for {topic}. Keep claims tied to evidence.\n\n{notes_markdown}"
@mcp.prompt()
def review_brief(draft_markdown: str, citations_json: str) -> str:
return f"Review this brief for factual support and publish readiness.\n\n{draft_markdown}\n\nCitations: {citations_json}"
The framework handles discovery (list_tools() enumerates @mcp.tool() functions), transport wiring, schema generation from type hints, and argument validation. The author writes only capability logic.
What the agent actually does on a typical brief¶
1. client connects → list_tools(), list_resources(), list_prompts()
2. agent calls search_reports(query="UPI growth", year=2024)
→ result: [{"title": "...", "uri": "research://topic/upi-growth-2024"}]
3. client fetches resource policy://editorial/citation-rules
→ grounds the agent in citation requirements
4. client fetches resource style://brief/market-analysis
→ grounds the agent in house style
5. user selects "summarise_findings" prompt from menu
→ prompt renders into the agent's instruction shell
6. agent drafts brief grounded in policy + style
7. agent calls save_note(...) to persist results
8. editor reviews; only then publish_brief(...) fires
Notice: search and save are actions (tools). Policy and style are read context (resources). Summarisation framing is a published pattern (prompt). If citation rules had been a tool, step 3 would have been a wasted model round trip. If the summarisation pattern had been hidden in client code, three different agents would have written three diverging versions.
When to build your own MCP server¶
Build when: - Multiple agents (or future agents) need the same capability - The capability crosses team or organisational boundaries - Compliance needs an inspectable, listable permission surface - The backend changes faster than consuming agents
Don't build when: - One agent, one codebase, one team, fixed lifetime — in-process function calls are simpler - Latency-critical edge deployment where process-boundary cost is unacceptable - A community or vendor server already does what you need - Highly bespoke pipelines where each integration genuinely needs custom logic that doesn't fit JSON-Schema
The honest test: project six and twelve months. Count the edges in the agent-backend graph. If (agent × backend) pairs stay below five and one team owns both sides, hand-rolled is fine. Above ten or growing by more than two pairs per quarter, MCP pays back in roughly two quarters. Above twenty, the question stops being "should we" and starts being "how fast."
Adoption cost (typical ranges):
one-time
learning the spec + picking a client library 1–2 engineer-weeks
first MCP server (simple, e.g. Slack post) 2–4 engineer-days
retrofitting one agent to use MCP clients 3–10 engineer-days
steady-state savings (per backend, per additional agent)
hand-rolled wrapper avoided 3–10 engineer-days
per-backend upgrade tax avoided 1–3 engineer-days / quarter
schema-drift incidents avoided 1–5 per year per pair
Below the breakeven, hand-rolled is cheaper. Above it, the curves cross and MCP is dramatically cheaper. Most teams that hesitate are looking at the first server cost in isolation and missing the steady-state savings.
Tradeoffs and limitations¶
What MCP does NOT solve:
| Problem | Why it survives transport |
|---|---|
| Bad schemas | MCP transports schemas; it doesn't write them. Every lesson from earlier — typed params, enums, bounds, "do not use when" boundaries — applies inside the server |
| Authority / blast radius | MCP delivers calls but doesn't decide whether they should fire. Scoping and approval gates live above the protocol |
| Content trust | Fetched resources may contain instructions the model treats as commands (indirect prompt injection). The standardisation is on the envelope, not the payload |
| Routing at scale | 100+ tools still degrade the model's selection accuracy. At that scale, use hierarchical routing — a router server with category tools, each delegating to specialists |
Boundary leaks to watch for:
- Server holds per-client state → breaks composability when a second client connects. Fix: keep state in the client or in a backing store keyed by per-request identifiers.
- Client embeds retry logic specific to one server → stale coupling when the server fixes its issue. Fix: expose retry guidance as part of the server's response (retry_after field), let clients implement the generic retry pattern.
- Transport assumptions inside tool functions → migration from stdio to HTTP rewrites everything. Fix: tool functions take typed args and return typed responses; they never inspect headers or write to stdout.
Each costs nothing on day one and dramatic refactoring on day three hundred. The discipline is to notice the violation in code review.
The wrong mental model: "MCP replaces the framework"¶
The seductive wrong intuition is that MCP, once adopted, makes the framework irrelevant. It does not. MCP is a horizontal layer between agent and world. The framework is a vertical layer that runs the agent loop. They are orthogonal — you choose both, separately, for different reasons. MCP reduces the cost of being wrong about the framework (because the tool layer survives a framework migration), but doesn't eliminate the cost of choosing one badly.
Teams that conflate the two end up either over-investing in MCP servers expecting them to handle agent state (they don't) or under-investing in their framework choice expecting MCP to substitute (it can't).
A second wrong model: "MCP makes the system more secure." It makes capabilities inspectable, which helps security review. It does not enforce authorisation by itself. A wire_transfer tool exposed via MCP is exactly as dangerous as the same tool wired hand-rolled — unless the server's backend implements scoping.
Real-world recognition¶
MCP adoption accelerated through 2025–2026 across IDEs, desktop AI, internal platforms, and tool vendors:
- Claude Desktop — reference MCP client; ships with filesystem, web-search, and database servers
- Cursor, VS Code + Copilot, Zed — IDE MCP clients for code-search, file-edit, terminal tools
- Goose by Block — desktop agent built natively on MCP with community server directory
- Anthropic reference servers — Filesystem, GitHub, Slack, Postgres, Brave Search, Puppeteer, Sentry, Linear
- Enterprise SaaS — Stripe, Snowflake, HubSpot, Salesforce, ServiceNow, Atlassian exposing capabilities via MCP
- Framework adapters — LangChain, LlamaIndex, OpenAI Agents SDK all ship first-class MCP client support
- Cloud platforms — AWS Bedrock AgentCore Gateway, Cloudflare Workers AI, Google Vertex AI
The pattern is uniform: capabilities are discovered via method calls, schemas are typed, one server serves many clients.
Interview Q&A¶
Q1. Why use a protocol like MCP instead of letting each framework define its own tool interface? A. Framework wrappers optimise local development speed; protocols optimise across-stack interoperability and long-term stability. Framework abstractions are tied to the runtime — switching frameworks rewrites the tool layer. A protocol survives framework choice: the same Zendesk MCP server serves a LangGraph agent today and a raw-API agent next year. The compound investment is in the protocol; the framework is rented. Avoid: "Protocols are more modern." Modernity is irrelevant; durability of the contract is the win.
Q2. Why expose static policy as a resource rather than a tool that returns it? A. Two reasons. Cost — a tool call requires a model round trip; a resource fetch is a direct client read. Semantics — listing a static-read among actions confuses the model's tool selection. Resources make the read explicit and narrow the tool surface, improving routing accuracy. Avoid: "Both work." Both run, but the cost and routing-accuracy difference accumulates across thousands of calls.
Q3. Walk me through how a host merges five servers into one toolbelt.
A. Client connects to each server (stdio for local, HTTP for remote). Runs discovery on each — list_tools(), list_resources(), list_prompts(). Resolves name collisions via namespacing (filesystem.search vs web.search). Merges into one surface advertised to the model. Isolates per-server failures. Filters capabilities per turn based on policy. Composition is more than concatenation — it requires namespacing, isolation, and filtering.
Q4. A teammate says "we only have one agent, MCP is overhead." How do you respond? A. Two structural benefits hold at N=1. First, capability inspectability — the server is a declared permission surface, easier for security review than "read the agent's source." Second, future migration — the MCP boundary is exactly what survives when a second agent or model-provider change arrives. Adopting at N=1 is cheaper than retrofitting at N=3. Avoid: "You're right, skip it." The framing should be "buy the option to grow," not "skip the protocol on principle."
Q5. What goes wrong when server state leaks across clients? A. The server becomes coupled to one client's lifecycle. Two clients sharing the server interfere — one's state overwrites the other's. Composability depends on the server being stateless across calls, or state living in a backing store keyed by per-request identifiers the client supplies. The clean rule: per-request state in the request, longer-lived state in a backing store with explicit keys, never implicit per-client state in the server process.
Q6. When is MCP overkill? A. When all four hold: capability consumed by exactly one agent, same codebase and team, no inspectable permission surface needed, and backend evolves on the same schedule as the agent. The trap is shipping in-process when a second agent is six months away — retrofitting costs two-to-three times greenfield adoption. The honest test is to project six and twelve months and see whether all four conditions still hold. Avoid: "Always use MCP, it's the future." Sometimes it is overhead. The discipline is applying the conditions.
Q7. Why keep the model in the client rather than inside the server? A. The client owns the agent loop — user interaction, prompt assembly, tool selection, state, observability — and the model is the part of that loop making routing and reasoning decisions. Putting the model inside the server would force each server to also be an agent, destroying the composability that makes one server consumable by many clients. The same server serves Claude today and GPT tomorrow, without caring which model is on the other side. Avoid: "Servers cannot run models." They can; the reason is composability, not technical limitation.
Q8. How would you handle fifty-plus tools across many servers without degrading routing accuracy?
A. Hierarchical MCP routing. Expose a router server with category-level tools (code_search, crm_lookup, data_query) — maybe five or ten. Each category delegates to specialist MCP servers. The model picks among categories reliably; the specialist inside serves the narrow toolbelt. Same pattern as nested menus in UI: shallow hierarchies scale where flat lists do not.
Avoid: "Just add more servers." Flat advertising degrades routing past the threshold regardless of server count.
Apply-now exercise (10 min)¶
Step 1 — model the audit. Here is what an MCP adoption assessment looks like for one row of the agent-backend matrix:
| Question | What I would look for | Red flag answer |
|---|---|---|
| How many agents will read from this backend in 12 months? | A specific number with planning horizon | "I'm not sure, we'll see" |
| How many other backends do those agents also consume? | A count from the architecture doc | "Whatever each team decides" |
| What's the wrapper line-count per agent for one shared backend? | A measured number | "I've never counted" |
| Who owns the upgrade when the backend API changes? | A named team | "Each agent team patches their own" |
| Is the capability surface audit-reviewable today? | A registry or doc | "It's spread across three repos" |
Step 2 — your turn. Pick an agent-and-backend pair from your stack (or use: internal support agent + CRM). Fill the same five rows. Mark each green (MCP obviously helps), yellow (maybe), or red (protocol won't pay back). If you have three or more greens, MCP is likely worth the adoption cost.
Step 3 — primitive audit. List five capabilities you would expose from that backend as an MCP server. For each, write one sentence naming the primitive (tool / resource / prompt) and justifying with the categorisation rule. Mark any capability where you want "both tool and resource" — those are usually two capabilities pretending to be one.
Step 4 — sketch from memory. Draw the N×M-vs-N+M diagram with four clients and five backends. Mark every edge in both versions. Compute totals. Then draw a single MCP server serving three clients of different types (IDE, Slack bot, batch agent). If you can produce both cold, the protocol's economic argument and architectural shape are yours.
Operational memory¶
This file explained why a standard protocol between agents and tools turns a quadratic integration cost into a linear one, how MCP structures that protocol around three capability shapes (tools, resources, prompts), and how the server-client split makes one server consumable by many agents without per-pair engineering.
The core tension — integration cost vs flexibility — resolves in MCP's favour once the agent-backend graph crosses roughly nine edges (3×3). Below that threshold, hand-rolled is cheaper. Above it, the protocol pays for itself in two quarters.
Carry this diagnostic forward: when a team complains that "agent engineering is mostly integration work," ask for the edge count. If the graph is quadratic and the wrappers are bespoke, MCP is the architectural fix, not better hiring. If the graph is small and stable, MCP is overhead and hand-rolled is correct.
Remember:
- The memo format is a contract that survives framework churn. Frameworks are rented; protocols are owned.
- Integration cost without a protocol is N × M; with one it is N + M.
- Three primitives, three jobs: tools change state, resources are read-only context, prompts are reusable instruction shells. Conflating them is the most common server-design mistake.
- Server owns capabilities; client owns the loop; the model is the client's, not the server's.
- Transport is the road, not the business — tool functions should not know how they were dispatched.
- Capability lists are the inspectable permission surface security teams prefer over "read the source."
- Bad schemas, missing authorisation, and untrusted content survive transport unchanged. MCP is wire format, not security.
- Adopt at N=1 to buy the option to grow; retrofit at N=3 to pay two-to-three times the cost.
Bridge. The agent now has a loop, tools, composition patterns, and a standard protocol. But after each request ends, it forgets everything. The conversation context is gone, the tool results are gone, the reasoning is gone. Next: what the agent remembers — and what that costs. → 06-memory-architecture.md