DEV Community

Cover image for MCP Is the WSDL Moment for Agents
Mark Fussell
Mark Fussell

Posted on Edited on Originally published at Medium

MCP Is the WSDL Moment for Agents

Web services standardised how to describe a service, then spent a decade learning that identity, reliability and recovery don't belong in the message format. MCP's maintainers have learned that lesson. Every team running an MCP server still has to.

In 2001 the W3C published WSDL, the Web Services Description Language. A WSDL file described what a service could do: its operations, its message formats and where to send them. Feed it to a tool and you got a working client. Two companies that had never met could integrate in an afternoon. I was at Microsoft through those years and was fully engaged in the WS-* specification standards bodies, and the excitement was real.

The Model Context Protocol does the same job for agents. An MCP server wraps a database, an API or a file system and tells any MCP client which tools it has, what arguments each takes and what it returns. Messages are JSON-RPC 2.0, over STDIO on the same machine or Streamable HTTP across a network. Write the server once and every MCP-compatible agent can use it. That removes the M-by-N problem function calling left us with, where M AI applications and N tools meant wiring each pair by hand.

So far, so WSDL. Here is where I think the story diverges: MCP will win where WSDL didn't, because it is choosing to stay thin. That puts the hard problems somewhere else, and anyone shipping an MCP server has to decide where.

What description didn't cover

Describing a service turned out to be the easy part. Once WSDL made integration cheap, everyone asked the next questions. Who is allowed to call this operation? What happens if the call is delivered twice? What if the caller crashes halfway through a five-step exchange? How do I find out what happened?

The industry answered by writing more specifications: WS-Security, WS-ReliableMessaging, WS-AtomicTransaction, WS-Coordination and a long tail after them. I was in the standards bodies for all of these, implementing them in Microsoft products. Each was reasonable on its own. Together they made a stack few teams could implement and fewer could debug. It was a specification nightmare that only Microsoft and IBM could afford to do. The public UDDI registries, meant to be the directory of every web service on earth, were switched off in 2006.

REST and JSON won the next decade by doing less and not having the insane complexity that WS-* involved. The hard problems moved out of the message format and into the runtime, which is where they belonged from the start: API gateways, service meshes, retry policies, mutual TLS and distributed tracing. And of course the Dapr project.

MCP chose the REST path

The clearest sign is the 2026-07-28 revision of the Model Context Protocol, the largest since launch. Its direction is the opposite of WS-*: it takes things out.

  • No more sessions. The initialize handshake and the Mcp-Session-Id header are gone. Every request carries its own protocol version and client capabilities, so any server instance can handle any call behind a plain load balancer.
  • State moves to the application. A server that needs continuity hands the model an explicit handle, a basket ID or a browser ID, and gets it back as an ordinary argument on the next call.
  • Less in the core. Tasks, the long-running work feature, moved out of the core spec into an opt-in extension. Roots, Sampling and Logging are deprecated, with OpenTelemetry named as the home for observability.
  • Built for infrastructure. New Mcp-Method and Mcp-Name headers let gateways route and rate-limit without reading the body, and W3C Trace Context in the request metadata lets traces follow a tool call end to end. We applied exactly the same approach in Dapr.

That is the REST lesson applied on purpose: the protocol defines the contract, and the infrastructure around it does the rest. New capabilities now arrive as extensions that are negotiated and versioned separately, rather than as specs every implementation must carry. WS-* never had that release valve. Whether extensions stay optional is the thing to watch.

What every MCP server in production still has to answer

A thinner protocol doesn't make the WSDL-era questions go away. It hands them to you.

Who may call which tool. The spec now treats an MCP server as an OAuth 2.1 resource server, which settles how a client proves who it is. It doesn't decide which of a server's forty tools that client may invoke, or where the upstream API key for your CRM lives. Too often that key still sits in agent code or an environment variable.

What the tool says it does. A tool description is text the model reads as instruction. Security teams spent 2026 warning about tool poisoning, where a description is altered after approval to steer an agent into leaking data. Nothing in the protocol stops a server from changing what it tells the model.

What happens on failure. Picture an agent that calls a refund tool, then crashes before recording the result. It restarts and sees an unfinished task. Does it refund again? A stateless protocol is the right design, but it means the answer lives entirely in your application. Retries, timeouts and circuit breakers are yours too.

Which servers to trust. The official MCP Registry is thriving where UDDI died, with roughly 39,000 servers listed by early October. But a registry is a namespace, not a trust decision. Choosing which MCP server your agents may reach is still a runtime question, and it is critical to security.

Where the answers belong

The runtime around the MCP server is the right home for each of those questions:

  • Identity comes from the platform, with deny-by-default rules per tool.
  • Credentials are injected at request time, if they're needed at all once identity is federated, and never sit in the agent.
  • Retries and timeouts are policy, written once, not code in every application.
  • Each tool call is recorded as a step in a durable workflow, so an agent that crashes after the refund resumes past it instead of making it twice.

A wave of MCP gateways now handles the first three, and that instinct is right. But a gateway sees a request, not the workflow it belongs to. Recovering from a crash means the call has to be recorded somewhere that outlives the agent process.

That is the approach we took with Diagrid Catalyst. It fronts MCP servers with proxy endpoints that speak Streamable HTTP and the stateless 2026-07-28 revision, so off-the-shelf clients and agent frameworks work unchanged. Authentication, credential injection and per-tool access rules happen outside the agent, and every tool call runs as a durable workflow step. Dapr does the same for everything else the agent touches: state, messaging and service calls.

The thin protocol wins

I expect the model context protocol to become as universal as WSDL wanted to be, because it is staying as thin as WSDL should have. MCP servers will number in the hundreds of thousands, and the engineering that decides whether they are safe to run will happen around them.

If you're building an MCP server this quarter, decide now where its caller identity, credentials, retries and crash recovery come from. The spec won't decide it for you, and its maintainers have made clear it shouldn't.

A longer explainer on how MCP works, with the architecture: Model Context Protocol (MCP) Explained.

Sources

Top comments (10)

Collapse
 
alexshev profile image
Alex Shev •

The WSDL comparison is useful because the hard lesson was never only description syntax. For agents, versioning, identity, least privilege, and reliable error semantics need to be first-class in the contract, otherwise tool discovery scales faster than safe interoperability.

Collapse
 
mfussell profile image
Mark Fussell •

Agree on the list. Where I’d split it is between what the contract describes and what the runtime enforces.

Versioning and error semantics belong in the contract. A caller needs to know which version of a tool it’s calling and whether a failure is safe to retry, and MCP already moves that way with schemas, structured errors and tool annotations like idempotent and destructive hints.

Identity and least privilege are different. A contract can declare that a tool is destructive, but it can’t stop someone calling it. Enforcement has to come from something the caller doesn’t control: platform identity, deny-by-default policy per tool, and credentials that never reach the agent. WS-Security tried to carry that in the message, and that’s where the stack got heavy.

So: the contract declares, the runtime enforces. You’re right that discovery is scaling faster than safe interoperability. The registry is past approx 39,000 servers, and nearly all were published with no enforcement.

Collapse
 
izgorodin profile image
Edward Izgorodin •

The refund case is where the two halves of the post meet. If the process running the step dies after the refund lands but before its result is written, the runtime retries the step, which is the at-least-once guarantee Dapr documents for activities. So what prevents a second refund is a key the refund tool itself recognizes, and when it is the agent that crashed, the harder question is where that key can come from once its context is gone.

The 2026-07-28 revision narrows where it can live. With the session gone, a server that needs continuity gets it back, in your words, as an ordinary argument on the next call, and even idempotentHint is worded as calling the tool repeatedly with the same arguments. So with an unchanged client, the only thing that can say which refund a call is, across a restart, is what the model puts in the arguments.

That sorts the candidates. A refund handle from the first call, or an instance id minted inside the agent process, lived at most in the context the crash erased. What survives is what the task carries: the order id and the amount the restarted agent reads again from its task. Keyed on those, the second call lands on the first refund. Keyed on anything the run produced, it cannot.

Your Spring AI crash recovery quickstart makes the same move with a caller-owned instance id. On the proxy path, where the client is unchanged and sends plain tools/call requests, what does the proxy match the second refund against?

Collapse
 
mfussell profile image
Mark Fussell •

Umm, yes and I agree with this.

There are two different cases.

The agent loop itself runs as a durable workflow. This is the quickstart path. The model’s decision and the arguments it chose are recorded as history. On restart the agent doesn’t ask the model again. It replays the recorded call with the same arguments and the same step identity. The key doesn’t need to survive in the context, because it lives in the runtime’s history. The at-least-once window you describe still exists, so yes, the step ID has to go to the refund tool as an idempotency key, or the tool has to dedupe on its own business key.

An unchanged client, a non-durable agent and plain tools/call through a proxy. Here the answer is that the proxy has nothing reliable to match against. Matching on arguments would block a legitimate second refund of the same amount, and the proxy can’t invent an identity the client never sent. The only safe key is the one you name: business identity the task carries, like the order ID, enforced by the refund system itself. The proxy can enforce policy and record the call, but cannot make the call idempotent.

So “unchanged client” holds for identity and policy, but not for crash recovery. Recovery needs the agent to be durable or the tool to own its idempotency.

Collapse
 
kevinhall profile image
Kevin Hall •

The refund question has a cheap answer on metered tools if you key the result instead of the call. Ours skips anyone already delivered in an earlier run and never charges for them again, so an agent that crashes after the pull and restarts can run the same call safely. Statelessness is fine when the idempotency key is in the arguments.

Collapse
 
mfussell profile image
Mark Fussell •

This is similar to the point made by Edward above ^^.

Keying the result rather than the call is the cleaner way to say it. If the tool can recognize “this outcome already happened,” a crash and restart becomes harmless, and the protocol never needs to know.

Its harder when the same arguments can legitimately mean two different actions. Two partial refunds of the same amount on the same order. The outcome alone can’t tell a retry from a second intent, so something has to carry which attempt this is. If the agent’s run is durable, the recorded step can carry that identity across the crash. If not, it has to be in the arguments the task supplies.

So yes, stateless works when the idempotency key is in the arguments. The work is making sure the key captures intent, not just the outcome.

Collapse
 
raju_dandigam profile image
Raju Dandigam •

Your split between a thin MCP contract and a durable runtime is especially clear in the refund example. A separate failure mode is a tool whose schema or description changes between discovery and replay: the recorded arguments are identical, but their meaning may have changed. Would you pin the tool definition version alongside the durable step and refuse replay when it drifts? That seems useful for both debugging and tool-poisoning audits.

Collapse
 
paul_irolla_6b5ae261d0224 profile image
Paul Irolla •

@mfussell Filtering the list before the model sees it fixes the context cost and the permissions in one move.

Collapse
 
paul_irolla_6b5ae261d0224 profile image
Paul Irolla •

WSDL was read once, when a developer wired the service. An MCP server's tool list goes back into the model's context on every request, so each tool you expose is paid on every call.

Collapse
 
mfussell profile image
Mark Fussell •

True, that’s a real difference, and a one to add. WSDL was paid for at build time. An MCP tool list is paid for at runtime, in tokens and in the model on every call.

The protocol should stay small, and so should each server. Don’t turn every API endpoint into its own tool. Design tools around the tasks an agent actually does. It’s also another reason per-tool policy belongs in the runtime. If a caller can only use 5 of a server’s 40 tools, it should only see those 5. Least privilege and a small context turn are the same IMO.

⌂ 换网址