MCP vs API: Why Traditional REST Endpoints Are Failing AI Agents

Before 2023, giving software access to a third-party service was a solved problem. You read the REST docs, pulled an API key, wrote a wrapper class, and called it a day. AI agents have broken that model. When an LLM needs to reach GitHub, Slack, Postgres, Google Calendar, the local filesystem, and a half-dozen internal microservices in the span of a single reasoning trace, the cost of hand-written glue stops being linear and starts being architectural. Each new app reinvents the same connector, each connector has its own auth story, and every change to a downstream API means patching every client that ever integrated it.

The Model Context Protocol (MCP), released by Anthropic in November 2024 and donated to the Linux Foundation, is the most serious attempt yet to fix this. It is an open protocol, not a vendor feature, and it reframes the problem in the same way USB-C reframed peripheral cables: instead of every device shipping its own plug, everyone agrees on a socket. This post explains why traditional APIs and raw OpenAI-style function calling fail agentic workloads, how MCP is structured, what primitives it adds beyond plain endpoints, and how to decide when to use which.

This post stands on its own, but it sits naturally alongside our earlier posts on how LLMs call tools via sampling and what it takes to build one from scratch.

The Architectural Gap at a Glance

MCP vs Traditional APIs Comparison Diagram

Understanding the Diagram

The diagram breaks the argument into six panels. Let us walk through them.

Panel 1: Traditional API Integration (Status Quo)

The top-left panel shows how tool access works today without a protocol. The LLM sits in the center, and every external service it might need – GitHub via REST and OAuth, Slack via webhooks and a bot token, Postgres through a SQL driver, Google Calendar through another REST API, the local filesystem through OS-specific calls – connects to it through a separate piece of hand-written glue. Each connector uses a different color and dashed style to make the point visually: the red dashed line for GitHub carries its own parser for GitHub-flavored JSON, the orange path for Slack speaks a vendor-specific schema, the purple path into Postgres has no standard auth story at all, and the blue line to the filesystem is just whatever the OS gives you. There is no protocol, no discovery, no standard shape for errors or streaming. The list of pain points at the bottom is the structural consequence: every app multiplies every service into a custom connector, tool schemas are hard-coded into prompt payloads, and there is no shared notion of resources, prompt templates, or state.

Panel 2: The MCP Architecture

The top-middle panel shows the same connectivity under MCP. A single host – which could be Claude Desktop, Cursor, VS Code, or your own agent code – sits at the top. Below it is the MCP protocol bus itself: JSON-RPC 2.0 over stdio for local servers, Streamable HTTP or SSE for remote ones. Below the bus, every external system is wrapped in an MCP server: Filesystem, Database, GitHub, Slack, Calendar. Each server exposes tools, resources, and prompts through the same wire protocol. All five connector lines are identical in color and weight because they are all the same protocol – the only thing that changes is the server implementation. The properties at the bottom summarize the economic argument: write a server once and use it in any MCP client, discover tools and resources dynamically at connection time, authenticate through a standardized OAuth 2.1 flow (and its Enterprise-Managed Authorization extension for orgs), and rely on an open standard rather than a vendor lock-in.

Panel 3: Capability Primitives

The top-right panel makes the abstraction difference concrete. A REST API exposes one primitive: endpoints addressed by URL, responding to GET/POST/PUT/DELETE with JSON that your code has to interpret. That is enough for a human programmer writing a client. It is not enough for an LLM that has to discover what is available, browse data without copying it all into context, and reuse prompt templates that were authored alongside the tool. MCP layers three explicit primitives on top of the transport: Tools are callable functions with JSON Schema inputs, chosen autonomously by the model; Resources are browsable data identified by URIs (for example file:///project/main.py or db://orders/recent) that the model can read into context without inventing endpoints; Prompts are reusable, server-defined prompt templates with typed arguments that any client can list via a /prompts/list call. REST has no standard equivalent for resources or prompts – each integration invents its own. Additional MCP primitives listed at the bottom include Sampling (servers can ask the client’s LLM to generate), Roots (clients declare which resources are in scope), Elicitation (servers can ask the user a question mid-call), and transport choices that cover both local and remote deployments.

Panel 4: Discovery and Invocation

The bottom-left panel contrasts the lifecycle of a tool call on each stack. Under REST and raw function calling, a developer writes the JSON Schema by hand, the schema is sent inline with every single API request, your own code executes the call when the model asks for it, and any change to the upstream API forces a schema update in every client. Under MCP, the client connects to the server once (over stdio or HTTP), issues a tools/list request that returns schemas dynamically, the server executes the call and returns a structured result, and adding a new tool to a server requires zero client code changes – existing clients discover it on their next connection. The yellow callout at the bottom states the critical difference: REST schemas are static payloads you maintain per client; MCP schemas are discovered live at connection time, travel separately from the prompt (so they do not burn token budget), and update automatically when the server changes. The latest 2026-07-28 specification even makes list responses cacheable with deterministic ordering, so tool catalogs stay stable across reconnects and upstream prompt caches remain hit.

Panel 5: The N x M Integration Problem

The bottom-middle panel is the network-effect picture every architect eventually draws. Before MCP, four clients (Claude, Cursor, VS Code, your own app) connecting to four services (GitHub, Slack, Postgres, Calendar) requires 16 custom connectors, shown as a dense red mesh. Each connector is maintained independently. After MCP, the same picture collapses into a star: four clients and four servers each connect once to the protocol bus, producing N + M total integrations instead of N x M. Every MCP-compatible client – Claude Code, Cursor, Windsurf, Zed, Continue, the official Anthropic MCP connector in the Messages API – speaks to every MCP-compatible server, and vice versa. This is the same dynamic that made USB and HTTP successful, and it is the reason MCP adoption has outpaced every earlier tool-use standard: once the protocol exists, the economics of adding a new server flip from “write N wrappers” to “write one.”

Panel 6: When to Use Which

The bottom-right panel is a pragmatic decision guide. Plain function calling is still the right answer when tools are private to one application, when you want zero infrastructure overhead, when business logic is tightly coupled to the tool, when a single model provider is acceptable, when you are running tight low-latency loops, or when you are prototyping. MCP is the right answer when the same tool needs to work across multiple clients, when you want vendor and model portability, when tools expose browsable data, when reusable prompt templates matter, when centralized governance and audit matter, when an existing third-party server already exists, or when standardized OAuth and enterprise authorization are requirements. The yellow footer states the single most important sentence in this debate: MCP does not replace function calling – it sits above it. Internally every MCP tool call still flows through the model’s native tool-use mechanism; MCP standardizes how tools are discovered, packaged, transported, and authenticated so that the same implementation works with any host.

Why REST Breaks for Agents

REST was designed for deterministic programs making deterministic calls against stable URLs. Agents violate every assumption in that sentence.

Tools are selected, not hard-coded. In a normal program you know at compile time whether you will call POST /issues. In an agent, the model decides at inference time whether it needs GitHub, Slack, or neither, and it can chain several calls in a single turn. That means the agent needs a live catalog of what is available, not a list baked into client code.

Schemas are part of the prompt budget. OpenAI-style function calling sends every tool schema inline with every request. With a dozen tools the schema payload can be thousands of tokens before the user says a word. Those tokens come out of the context window and the billing meter. MCP separates tool discovery from the request path: catalogs are fetched once per connection, cached, and only the selected tool’s definition is material to a given call.

Auth is per-vendor and per-user. GitHub wants a PAT or OAuth app, Slack wants a bot token with scopes, Postgres wants a connection string, Google wants OAuth with its own consent screen. An agent that connects to six services is an OAuth coordination nightmare for end users and a compliance nightmare for security teams. MCP’s authorization framework standardizes OAuth 2.1 with RFC 9207 issuer validation, and the Enterprise-Managed Authorization (EMA) extension lets organizations bind MCP auth to their existing IdP so users are connected to every approved server on first login with zero per-app consent.

Context is not just responses. Agents need to browse data, not just hit endpoints. “What files are in this directory?” is a natural question that REST does not answer in any standardized way – every filesystem wrapper invents its own listing endpoint. MCP resources model this directly as URI-addressed data that clients can enumerate, read, and watch for changes.

State persists across calls. A REST call is stateless by design; a database MCP server typically holds a connection pool, a working directory, or an authenticated session across many tool invocations. Modeling those long-lived interactions with raw function calling forces every app to reimplement session management.

Ecosystem economics are wrong. The marginal cost of adding a new AI client (say, a new IDE) under REST is re-implementing every existing connector. Under MCP the marginal cost is near zero: the IDE speaks the protocol, and every existing server just works. That is why the server ecosystem grew to thousands of implementations within a year of launch.

What MCP Actually Looks Like

An MCP server is a small process that speaks JSON-RPC. The Python SDK makes a minimal server trivial:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("docs")

@mcp.tool()
def search_docs(query: str, limit: int = 5) -> list[dict]:
    """Search the internal documentation index."""
    return index.search(query, limit=limit)

@mcp.resource("file://docs/{path}")
def get_doc(path: str) -> str:
    return docs_store.read(path)

@mcp.prompt()
def summarize_doc(path: str) -> str:
    return f"Read the following document and produce a 3-bullet summary:\n\n{get_doc(path)}"

mcp.run()

Three decorators expose the three primitives: a search tool the model can call, a resource the model can read by URI, and a prompt template the client can invoke. The same server can run over stdio (for Claude Desktop or a local IDE) or over HTTP (for remote deployment). On the client side, an Anthropic API call against a remote MCP server uses the MCP connector, introduced as a beta feature in the Messages API:

{
  "model": "claude-opus-4-6",
  "max_tokens": 1000,
  "messages": [{"role": "user", "content": "Find docs about MCP auth"}],
  "mcp_servers": [
    { "type": "url", "url": "https://mcp.example.com/sse", "name": "docs" }
  ],
  "tools": [
    { "type": "mcp_toolset", "mcp_server_name": "docs" }
  ]
}

The client no longer needs to implement the protocol itself – Anthropic’s API handles tool discovery, invocation, and result marshaling against the remote server.

The 2026-07-28 Specification

The most recent MCP specification (released July 28, 2026) addresses the most common engineering objections to the early versions. The headline change is a stateless protocol core: the original MCP required an initialize/initialized handshake and a session ID carried in an Mcp-Session-Id header, which made load balancing awkward because any request had to land on the server instance that held the session. Every request is now self-describing, carrying protocol version, client identity, and client capabilities in _meta, which means requests can land on any instance behind a plain round-robin load balancer without shared storage. Method and tool names travel in Mcp-Method and Mcp-Name HTTP headers so gateways can route and authorize on headers alone. Server-to-client requests (sampling and elicitation) are redesigned as Multi Round-Trip Requests (MRTR) to remove the need for permanently open bidirectional streams, and list responses now carry cache hints with deterministic ordering so tool catalogs can be cached across reconnects without invalidating upstream prompt caches. The SDKs for TypeScript, Python, Go, and C# were updated in lockstep.

This matters because the stateless rewrite converts MCP from a promising local-only protocol into something that can be deployed like any other HTTP service behind existing API gateways, CDNs, and identity providers – which is the precondition for serious enterprise adoption.

When Not to Use MCP

It is easy to reach for MCP for every tool call and end up with unnecessary infrastructure. Three situations in which plain function calling is a better answer:

Single-app tools. If a tool only exists inside your own application and you have no intent to share it across editors, agents, or services, wrapping it in an MCP server adds a process boundary, a transport layer, and an SDK dependency for zero reuse benefit. Keep it as a function.

Hot loops. A tool that gets called thousands of times per reasoning trace – a calculator, a string transformer, a tiny lookup – pays measurable latency for the JSON-RPC round trip, even over stdio. Inline those as native functions.

Rapid prototypes. MCP is a protocol, which means you have a build step, a process to supervise, and a versioning story. For a weekend hack or an early experiment, define a few tools directly in the API call and ship.

The correct architecture for most production agents in 2026 is a mix: MCP servers for reusable third-party integrations (GitHub, Slack, databases, internal knowledge bases, shared file systems), plain function calls for tightly coupled application logic, and the MCP connector or an embedded client library to bridge the two worlds into a single tool list for the model.

Lessons from Building With MCP

Treat tool schemas as UX. A tool with a vague description or a permissive schema will be mis-called by even frontier models. A good schema is precise about required fields, lists enums explicitly, and gives concrete examples in the description. MCP makes this easier because the schema lives next to the code that implements the tool, so you can iterate on both together.

Resources scale context. Pulling entire files or tables into the prompt destroys the context window. Modeling data as MCP resources lets the client decide what to pull and when, and keeps large corpora on the server until the model actually needs them.

Auth is the hardest part. Plan OAuth from day one. For local single-user servers a personal token is fine; for anything shared across a team, implement the MCP authorization flow against your IdP rather than rolling your own.

Version your tools. The new deprecation policy in the 2026-07-28 spec guarantees a twelve-month minimum window for deprecated methods. Use it. Clients in the wild will be older than you expect.

The star topology wins. Once you have three or more clients connecting to three or more services, the N x M math becomes unanswerable without a protocol. The moment you find yourself writing the third wrapper for the same upstream API, stop and write an MCP server instead.

Further Reading

Conclusion

Traditional REST endpoints are not failing because REST is bad. They are failing because REST was designed for programs that know exactly what call they want to make, and AI agents do not. Agents discover tools at runtime, chain them across reasoning traces, share them across applications, and need more than endpoints – they need browsable data, reusable prompts, standardized auth, and a transport that works both locally and across the network. MCP packages those requirements into a single open protocol that replaces an N x M grid of custom connectors with an N + M star. It does not replace function calling; it sits above it, the same way USB sits above the electronics inside your laptop. If you are building agents that touch more than a couple of services, or publishing integrations that you want to work across Claude, Cursor, VS Code, and the dozens of MCP-compatible clients shipping this year, MCP is no longer early-adopter technology – it is the integration layer the ecosystem has already converged on.

Watch PyShine on YouTube

Contents