MCP as Discovery Protocol
I spent seven posts building the case for MCP as a semantic data layer. I was directionally right and specifically wrong. MCP works best when it's narrower than a layer — when it's a discovery protocol that describes what's available and gets out of the way.
Thesis: MCP's highest-value role is as a discovery protocol — a thin shim where agents find capabilities, receive guidance, and negotiate context at runtime. The moment MCP accumulates logic, knowledge, or state, it repeats the enterprise service bus failure that this blog already warned about.
This is Post 1 of 2 in the "MCP Architecture" series. Post 2, Externalize the Knowledge, covers the implementation pattern that follows from the discovery protocol principle.
From Semantic Layer to Discovery Protocol
In MCP: The Semantic Data Layer, I framed MCP with five responsibilities: abstraction, semantics, discovery, negotiation, and governance. I argued it was the enterprise service bus done right — centralizing description while distributing reasoning.
That framing was useful. It was also too generous.
Layers accumulate responsibility. That's what layers do. Once you've said "MCP is a layer that handles abstraction, discovery, negotiation, and governance," you've created five places where logic can creep in. Abstraction becomes transformation. Negotiation becomes routing. Governance becomes enforcement. Each one pulls MCP from "thin shim" toward "smart middleware."
A protocol is different. A protocol defines how two parties communicate — it doesn't accumulate the responsibilities of either party. HTTP doesn't render web pages. DNS doesn't serve content. SMTP doesn't write emails. They describe, route, and delegate.
MCP should do the same: describe capabilities, route queries, delegate execution. Not abstract, not govern, not orchestrate.
block-beta
columns 2
block:layer["Semantic Layer (accumulates)"]
columns 1
L1["Abstraction"]
L2["Semantics"]
L3["Discovery"]
L4["Negotiation"]
L5["Governance"]
end
block:protocol["Discovery Protocol (deflects)"]
columns 1
P1["Describe capabilities"]
P2["Route queries"]
P3["Delegate execution"]
space
space
end
style layer fill:#9d4444,color:#fff
style protocol fill:#2d6a4f,color:#fff
The difference between a layer and a protocol is where complexity accumulates. Layers absorb it. Protocols deflect it.
What Discovery Actually Means
DNS is the canonical discovery protocol. You ask "where is example.com?" and DNS returns an IP address. It doesn't serve the web page. It doesn't validate your request. It doesn't decide whether you should visit that site. It resolves names to addresses and steps aside.
MCP as a discovery protocol works the same way. An agent connects and asks: "What can I do here?" MCP returns tool schemas, resource descriptions, and server instructions. The agent reasons about what to use, in what order, with what parameters. MCP describes the menu. The agent orders dinner.
The agent always starts by talking to MCP. What varies is what happens next: does the agent continue calling MCP tools that abstract the backend, or does MCP give the agent what it needs to go direct?
Path A: MCP-Mediated Execution
The agent discovers tools via MCP and continues calling them through MCP. MCP abstracts the backend — the agent doesn't need to know the underlying service details. Each tool call flows through MCP, which delegates to the backend and returns the result with guidance.
sequenceDiagram
participant Agent
participant MCP as MCP Server
participant Service as Service<br/>(API, DB, etc.)
Agent->>MCP: Connect
MCP-->>Agent: Tool schemas + resources + instructions
Note over Agent: Agent reasons about<br/>what to use and how
Agent->>MCP: Call tool (with parameters)
MCP->>Service: Delegate execution
Service-->>MCP: Result
MCP-->>Agent: Result + guidance
Agent->>MCP: Call another tool
MCP->>Service: Delegate execution
Service-->>MCP: Result
MCP-->>Agent: Result + guidance
This is how most MCP integrations work today. Claude Code's Tool Search is a production example: rather than loading all tool schemas into context upfront, Claude defers tool definitions and discovers them on demand — searching by keyword, loading only the 3–5 tools relevant to the current task. A typical multiserver setup can consume ~55K tokens in tool definitions before an agent does any work; tool search typically reduces this by over 85%. The discovery is the protocol doing its job; the tool calls that follow are MCP mediating execution.
Path B: MCP-Informed Direct Access
The agent discovers via MCP but then calls services directly. MCP's connect response — resources, instructions, or a discovery tool result — includes the service URI, API spec, CLI command, or another MCP server address. The agent has what it needs to go direct.
sequenceDiagram
participant Agent
participant MCP as MCP Server
participant Service as Service<br/>(API, CLI, other MCP)
Agent->>MCP: Connect
MCP-->>Agent: Tool schemas + resources + instructions<br/>(includes service URIs, API specs, CLI commands)
Note over Agent: Agent extracts service specs<br/>from MCP response
Agent->>Service: Direct call using specs from MCP
Service-->>Agent: Result
Agent->>Service: Another direct call
Service-->>Agent: Result
This is the discovery protocol at its thinnest. MCP's role is complete after the initial handshake — it provided the menu and the addresses, and the agent is autonomous from there. The connect response might include an OpenAPI spec URL, a CLI binary path, or another MCP server's address. The agent integrates on first contact. No additional MCP round-trips required.
Both Paths, One Protocol
Both paths start with MCP. The difference is whether MCP stays in the execution loop or steps aside after discovery. In practice, agents use both paths simultaneously — calling some services through MCP tools (Path A) while going direct to others using specs MCP provided (Path B). The protocol doesn't mandate which path to use; it enables both.
This is exactly what Claude does. Claude Code connects to MCP servers for tool discovery and mediated execution (Path A). But when MCP resources or instructions include direct service references — "for this operation, use gh pr create" or "the REST endpoint is POST /api/v1/deploy" — Claude uses them directly (Path B). MCP informed the decision without mediating the execution.
Why Discovery Changes Everything
The narrowing from "layer" to "protocol" isn't semantic — it unlocks three operational properties that a thicker layer can't provide.
Pluggability without rebuilding. When MCP only describes capabilities, adding a new backend service means adding a new tool schema — not writing routing logic, transformation code, or orchestration flows. A team ships a new microservice on Tuesday; by Wednesday, the MCP server exposes a tool description and every connected agent can discover and use it. No agent rebuild. No MCP logic change. No coordinated release across teams.
Backend evolution without client changes. The backend team can refactor, replatform, or replace a service entirely. As long as the tool schema stays stable (or evolves with a new version), every agent continues working. The MCP server delegates execution — it doesn't encode assumptions about how the backend is implemented. Swap Postgres for DynamoDB, rewrite a Python service in Go, move from on-prem to cloud. The discovery layer doesn't care; it describes the same capability pointing at a different backend.
Independent lifecycles everywhere. This is the compound effect. The agent team ships agent improvements without touching MCP. The backend team ships API changes without touching MCP. The knowledge team updates guidance without touching MCP (see Post 2). MCP itself only changes when the protocol surface changes — new tools, renamed parameters, schema updates. Everything else flows around it.
flowchart TD
subgraph independent["Independent Lifecycles"]
Agent["Agent Team<br/>(reasoning, UX)"] ---|"discovers"| MCP["MCP<br/>(thin protocol surface)"]
Backend["Backend Team<br/>(APIs, services)"] ---|"exposed via"| MCP
Knowledge["Knowledge Team<br/>(guidance, docs)"] ---|"published to"| MCP
end
MCP ---|"only changes when<br/>protocol surface changes"| Schema["Tool schemas<br/>Parameter names<br/>Resource structure"]
style MCP fill:#2d6a4f,color:#fff
style Schema fill:#5a5a8a,color:#fff
When MCP is a layer with five responsibilities, it becomes the coordination bottleneck — every team's changes flow through it. When MCP is a discovery protocol, it becomes the stable surface that everyone builds against independently. The thinner the protocol, the fewer reasons it has to change.
This is a narrower claim than "semantic data layer." Here's where the responsibility boundaries fall:
| Responsibility | Discovery Protocol | Capability Layer | Orchestration Layer |
|---|---|---|---|
| Describe capabilities | Yes | Yes | Yes |
| Validate inputs | Schema-level only | Deep validation | Deep validation |
| Route requests | No — agent decides | Sometimes | Always |
| Transform data | No | Yes | Yes |
| Enforce policy | No — delegates to auth/policy layer | Yes | Yes |
| Sequence operations | No — agent decides | No | Yes |
| Accumulate state | No | Sometimes | Always |
| Where logic lives | Agent + backend | Shared with layer | Centralized in layer |
The further right you go, the more your MCP server looks like an ESB.
Agents Own the Logic
This is the prescription that follows from the discovery framing: the agent is where reasoning, sequencing, error handling, and decision-making live. MCP provides inputs to that reasoning — it doesn't perform it.
Consider two designs for an MCP server that exposes a platform with multiple deployment environments:
flowchart TD
subgraph A["Design A: MCP Routes"]
A_Agent["Agent"] --> A_MCP["MCP Server<br/>(contains logic)"]
A_MCP -->|"if promote"| A_Val["Validate Staging"]
A_MCP -->|"if rollback"| A_Roll["Find Rollback Target"]
A_Val --> A_Deploy["Deploy to Prod"]
A_Roll --> A_Exec["Execute Rollback"]
end
subgraph B["Design B: MCP Describes"]
B_Agent["Agent<br/>(contains logic)"] -->|"1. discover"| B_MCP["MCP Server<br/>(thin shim)"]
B_Agent -->|"2. validate"| B_MCP
B_Agent -->|"3. deploy"| B_MCP
B_MCP --> B_Backend["Backend APIs"]
end
style A_MCP fill:#9d4444,color:#fff
style B_MCP fill:#2d6a4f,color:#fff
style B_Agent fill:#5a5a8a,color:#fff
Design A: MCP routes
// MCP server decides based on intent
tools: [{
name: "manage_deployment",
description: "Manages deployments across environments",
handler: async (input) => {
if (input.action === "promote") {
// MCP decides: validate staging first, then deploy to prod
await validateStaging(input.app);
await deployToProd(input.app);
} else if (input.action === "rollback") {
// MCP decides: check rollback eligibility, pick version
const version = await findRollbackTarget(input.app);
await rollback(input.app, version);
}
}
}]
Design B: MCP describes
// MCP describes — agent decides
tools: [
{
name: "list_environments",
description: "Returns all deployment environments with current state"
},
{
name: "get_deployment_status",
description: "Returns deployment health, version, and last deploy time"
},
{
name: "deploy",
description: "Deploys a specific version to a specific environment"
},
{
name: "validate_environment",
description: "Runs health checks against an environment"
}
]
Design A is fewer tools. Design A is also an ESB. The MCP server has internalized the deployment workflow — when to validate, what to roll back to, what sequence to follow. Change the deployment process and you change the MCP server.
Design B gives the agent primitives. The agent calls list_environments to understand the landscape, calls validate_environment to check staging, decides whether to proceed, then calls deploy. The deployment logic lives in the agent's reasoning, not in MCP's handlers.
The agent might get it wrong. That's what guardrails, confirmations, and backend validation are for — not MCP-side orchestration.
Resources as Guidance, Not Rules
MCP's resource and prompt primitives are where guidance enters the system. Server instructions describe how to use the tools. Resources provide reference data. Prompts offer templates for common workflows.
The key distinction: these are guidance the agent interprets, not directives the server enforces.
An ESB enforces routing rules centrally — message X always goes to service Y through transformation Z. The service has no say. MCP publishes guidance that the agent reasons about contextually. The agent might follow the guidance exactly. It might adapt it. It might ignore it because the user's request doesn't fit the suggested pattern.
This is the design that scales. When guidance is advisory rather than enforced, you can update it without breaking consumers. You can publish different guidance for different contexts. You can let agents with different capabilities interpret the same guidance differently. The MCP server doesn't need to anticipate every consumer's needs — it publishes what it knows and trusts the consumer to reason.
The moment you move from "here's how this typically works" to "you must do it this way," you've moved enforcement into the protocol layer. That's the ESB creep.
CLI Doesn't Negate Discovery
Perplexity's CTO said they're dropping MCP for APIs and CLIs. I addressed this in MCP Isn't the Problem, but the discovery protocol framing sharpens the analysis.
CLIs work when the consumer is known and the interface is stable. gh pr create works because GitHub's CLI team designed it for human developers who know what a pull request is. The consumer is known. The vocabulary is shared. Discovery isn't needed — the --help flag is sufficient.
MCP as a discovery protocol serves a different scenario: the consumer is unknown, the vocabulary may not be shared, and the task is open-ended. An agent connecting to your MCP server might be Claude, Cursor, a custom pipeline, or something that doesn't exist yet. It needs to discover capabilities at runtime, not read a man page at build time.
Here's what matters: the discovery protocol is agnostic about what it describes. MCP can return tool schemas for API calls. It can also return resource descriptions that point to CLI commands, SDK methods, or REST endpoints. The protocol describes capabilities — it doesn't mandate how they're consumed.
| Scenario | Best Interface | Why |
|---|---|---|
| Known consumer, stable task | CLI / SDK | Lower overhead, training data advantage |
| Known consumer, dynamic capabilities | MCP discovery + direct execution | Consumer adapts to new tools at runtime |
| Unknown consumer, any task | MCP discovery | Only option — consumer must learn at connection time |
| Multiple consumer types | MCP discovery + format negotiation | Describe once, consume many ways |
The CLI trend and the discovery protocol aren't competing. They're solving different problems. When Perplexity builds a CLI, they're optimizing for a known consumer (their own agent) against a stable API. When an enterprise platform exposes MCP, they're optimizing for consumers they can't predict against capabilities that evolve.
MCP can even serve the CLI scenario: return a resource that says "for this operation, the most efficient path is perplexity search --query '...'" and let the agent execute the CLI command directly. Discovery doesn't require MCP-mediated execution.
The ESB Test
A simple diagnostic for whether your MCP server is a discovery protocol or an emerging ESB:
Ask: "Is your MCP server getting smarter with each integration?"
If the answer is yes — if each new backend or use case adds logic, routing rules, or conditional behavior to the MCP server — you're building an ESB. The server is accumulating intelligence that belongs elsewhere.
If the answer is no — if new integrations add descriptions (tool schemas, resource listings, guidance) but not logic — you're building a discovery protocol. The server is getting more descriptive without getting more complex.
flowchart TD
Q["Is your MCP server getting<br/>smarter with each integration?"] -->|"Yes — adding logic,<br/>routing, conditionals"| ESB["You're building<br/>an ESB"]
Q -->|"No — adding descriptions,<br/>schemas, guidance"| DP["You're building a<br/>discovery protocol"]
ESB --> Fix["Refactor: move logic<br/>to agent or backend"]
DP --> Keep["Keep going"]
style ESB fill:#9d4444,color:#fff
style DP fill:#2d6a4f,color:#fff
style Fix fill:#5a5a8a,color:#fff
Three warning signs that the ESB pattern has crept in:
-
Your MCP handler has
if/elsebranches based on input content. This is routing logic. The agent should decide which tool to call; the tool should execute one operation. -
Your MCP server calls multiple backend services in sequence. This is orchestration. Expose each backend as a separate tool and let the agent compose the sequence.
-
Your MCP server maintains state between tool calls. This is session management. If state is needed, let the agent hold it (in context) or persist it in the backend — not in the protocol layer.
None of these are absolute prohibitions. A small MCP server for a single team might reasonably combine a few operations. But if you see these patterns growing with each integration, you're on the ESB trajectory.
What to Do Next
- Audit your MCP servers. For each tool handler, ask: does this describe or decide? Does this route or delegate? Mark any handler that contains conditional logic based on input content.
- Apply the ESB test monthly. Track whether new integrations add logic or descriptions. Plot the trend.
- Move routing to the agent. If your MCP server decides which backend to call based on user intent, extract those as separate tools and let the agent choose.
- Move enforcement to the backend. Validation, authorization, rate limiting, and policy enforcement belong in the API layer, not the MCP layer. MCP should describe the constraints; the backend should enforce them.
- Treat server instructions as guidance. Write them to inform the agent's reasoning, not to mandate behavior. If you find yourself writing "you MUST always" in server instructions, consider whether that rule should be a backend validation instead.
Next: Externalize the Knowledge — what happens when you take the discovery protocol principle seriously and stop embedding domain knowledge in MCP servers.
Related reading: MCP: The Semantic Data Layer for the original framing this post evolves, MCP Isn't the Problem for the CLI debate, and The MCP Mental Model for core primitives.