TL;DR: MCP vs API isn't really a rivalry: they solve different problems. Build an MCP server when you want one integration that works across Claude Desktop, Cursor, Claude Code, and other agent clients without writing separate code for each. Build a REST API when you're integrating with non-agent software, need custom caching or query patterns, or don't want a stateless JSON-RPC protocol. Many products need both.
What actually is MCP, compared to an API you already know?
The Model Context Protocol (MCP) is a specification for how an AI client (an agent, a chat app, a coding tool) discovers and calls capabilities exposed by a server, at runtime. A traditional REST API is a specification for how any client calls capabilities exposed by a server, decided ahead of time by whoever reads the docs.
That's the whole difference in one sentence, and it explains almost everything else in this post. A REST API is a contract you read once, by hand, and hard-code against: an OpenAPI file, a Postman collection, a page of curl examples. Add an endpoint to your REST API and every existing client keeps calling the old ones; nobody discovers the new one unless you tell them, update their code, and ship a new build. MCP flips that: a client sends a tools/list request and gets back whatever tools the server currently exposes, with their names, descriptions, and input schemas, in the same request-response shape it already knows how to make. Add a tool to your MCP server and every connected client can see it and call it the next time it asks.
Under the hood, both are usually JSON over HTTP. MCP is specific about the shape of that JSON: every message, whether a request, a response, or a notification, is JSON-RPC 2.0, verified directly against the current spec (revision 2026-07-28, fetched September 25, 2026). A REST API has no such requirement; its JSON shape is whatever the API designer chose, and that's exactly why a new client has to be hand-integrated every time.
What does an MCP tool call actually look like on the wire?
Discovery and invocation are two separate, standardized requests. A client asks what's available:
// → tools/list
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}
// ← response
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"resultType": "complete",
"tools": [
{
"name": "improve",
"description": "Rewrite a prompt for clarity, structure, and specificity.",
"inputSchema": {
"type": "object",
"properties": { "prompt": { "type": "string" } },
"required": ["prompt"]
}
}
]
}
}
Then it invokes one:
// → tools/call
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "improve",
"arguments": { "prompt": "write a landing page" }
}
}
// ← response
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"resultType": "complete",
"content": [{ "type": "text", "text": "..." }],
"isError": false
}
}
Compare that to a REST equivalent, where the shape of the request and the meaning of a 200 versus a 422 are unique to whoever designed this particular API:
POST /v1/prompts/improve HTTP/1.1
Host: api.example.com
Authorization: Bearer sk_live_...
Content-Type: application/json
{ "prompt": "write a landing page" }
Neither snippet is more "correct." They're solving different discovery problems. The REST call only works because a developer already read your docs and wrote that exact path and body. The MCP call works the same way against any server that speaks the protocol, because the client asked what to send first.
The error model is a real, underrated difference
MCP separates two kinds of failure, and the distinction matters more than it looks. Reading the current spec's tools page directly: protocol errors (an unknown tool, a malformed request) come back as standard JSON-RPC errors, while tool execution errors (a bad date format, a value out of range, a failed downstream API call) come back inside a normal result with isError: true. The spec's own reasoning for the split is that tool execution errors "contain actionable feedback that language models can use to self-correct and retry with adjusted parameters."
A REST API has no equivalent split baked into the protocol. A 400 and a 500 are both just HTTP status codes; whether the calling code, often written by a human rather than read by a model mid-conversation, retries, backs off, or gives up is entirely up to how that client was written. If you've ever watched an agent get stuck retrying an MCP tool call forever, that's this mechanism working as designed on a problem no retry can fix, not a bug in the protocol.
MCP vs API: which comparison actually matters for a build decision?
| Feature | MCP server | REST API |
|---|---|---|
| Capability discovery | Dynamic: client calls tools/list at runtime | Static: read the docs/OpenAPI spec ahead of time |
| Per-client integration work | One implementation; any MCP-aware client can connect | Custom integration code written for each consuming app |
| Wire format | JSON-RPC 2.0, mandated by the spec | Whatever JSON shape the API designer chose |
| Auth (when implemented) | OAuth 2.1 is the specified path for HTTP transport | Designer's choice: API keys, OAuth, mTLS, anything |
| State across calls | Stateless by design; state needs an explicit handle | Depends entirely on the API; sessions are common |
| Error handling for the caller | Two-tier: protocol errors vs. self-correctable tool errors | HTTP status codes; retry logic is the client's job |
| Best fit | An agent or chat client calls your capability autonomously | Another program calls your capability in a fixed, known way |
Read that table as a checklist, not a scorecard. If most of the left column describes your situation, build MCP. If most of the right column does, build REST. If both columns describe real needs you have, the honest answer is to build both: an MCP server is very often a thin adapter in front of the same backend logic a REST API already calls, not a replacement for it.
When should you actually build an MCP server?
Build one when the thing calling your capability is an agent, not a program someone else wrote by hand. Concretely:
- You want one integration to reach many clients without writing bespoke code per client. Claude Desktop, Claude.ai, Claude Code, Cursor, and OpenAI's Codex and Codex CLI are common MCP-aware targets today, and each connects to the same server without you writing anything client-specific. OpenAI's own Responses API guide to tools and connectors documents a first-class
mcptool type: setserver_url, aserver_label, and arequire_approvalpolicy, and the model can call your remote MCP server directly (verified from the current page, September 25, 2026):
{
"type": "mcp",
"server_label": "prompt-architects",
"server_description": "Prompt enhancement tools",
"server_url": "https://mcp.prompt-architects.com/mcp",
"require_approval": "never"
}
- You want the tool list itself to be discoverable and to grow without breaking existing integrations. Add a fifth tool to an MCP server and every connected client can see it on its next
tools/listcall. Add a fifth endpoint to a REST API and nothing downstream knows it exists until someone reads a changelog. - You're fine with a stateless request/response model. If your workflow genuinely needs a long-lived session (a shopping cart, an open transaction), the spec's own non-normative guidance for tools is to hand back an explicit identifier (a "handle") from a creation call and have the model pass it forward on every subsequent call, rather than relying on connection state.
When does a REST API still win?
Plenty of situations, and it's worth naming them rather than pretending MCP is a universal upgrade:
- The caller isn't an agent. A mobile app, a partner's backend, a dashboard, or a webhook consumer has no use for
tools/list; it needs a documented, versioned HTTP contract it can hard-code against once and trust not to move under it. - You need caching, pagination, or query flexibility that a tool call schema doesn't express well. REST's maturity around ETags, cursors, filtering, and CDN-friendly
GETsemantics didn't happen by accident; it solves real problems that a singletools/callinvocation isn't shaped for. - You need synchronous, low-latency reads at scale, where the overhead of a JSON-RPC envelope and (for HTTP transport) a token-audience-bound OAuth 2.1 flow is pure cost with no discovery benefit, because the caller already knows exactly what it wants.
- You want ecosystem-standard tooling (API gateways, rate-limiting proxies, and monitoring stacks built around REST conventions) without adopting a newer, still-consolidating protocol alongside it.
Does MCP's 'roots' primitive sandbox your server?
This matters for the build decision because it's the single most common reason a team picks MCP for the wrong reason: the belief that the protocol enforces a sandbox. It doesn't, and it never did. Whatever access control your server needs (file scoping, tenant isolation, rate limits), you have to build it yourself, at the OS, container, or application layer, exactly as you would for a REST API.
Auth: the one place MCP is more prescriptive than REST
REST APIs don't mandate an auth scheme: API keys, session cookies, mTLS, and OAuth all coexist across different APIs, and that's fine because each has one implementer choosing for one API. MCP is more opinionated when a server implements auth over HTTP: authorization itself is optional in the spec, but when a server does implement it over an HTTP-based transport, the current authorization specification states it plainly: "Authorization servers MUST implement OAuth 2.1 with appropriate security measures for both confidential and public clients." Servers using stdio transport should skip this framework entirely and pull credentials from the environment instead.
That's exactly the shape of our own MCP server: OAuth 2.1 with PKCE for browser-based sign-in, and a long-lived pa_live_… personal access token for headless or CI use, matching what's published on our MCP integration docs and confirmed against the live product source. Four tools are exposed (improve, refine, shorten, and enhance), each callable as an autonomous tool call or a slash command, with no REST endpoint standing in for them; if a workflow genuinely needs a plain HTTP endpoint to embed into another product, that specific need isn't one we serve today, and the honest answer is to say so rather than imply otherwise.
A worked decision, using our own product as the example
We didn't build both. We shipped MCP only, and the adoption data explains why that bet made sense for this product specifically, even though it wouldn't for every product. MCP usage sits at a modest 4.8% of customers overall, but 60.6% of our highest-value customer segment ("Champions") use it, against just 0.7% of the lowest-engagement segment ("Dabblers"), the single strongest predictor of a high-value customer anywhere in that dataset. The customers who connect an agent client once and let it call our prompt tools autonomously are, overwhelmingly, the customers who stick around. That's a case for MCP specifically because our capability, improving, refining, shortening, and enhancing a piece of text, is something an agent calls mid-workflow, not something a separate program needs to poll or paginate through. If our product instead needed to hand back large paginated result sets to a partner's backend on a fixed schedule, the right call would flip, and a REST API would be the one worth building first.
Use that same test on your own capability before you write a line of server code. Is the caller an agent that benefits from discovering what it can do and calling it mid-conversation? Build MCP. Is the caller a program that already knows exactly what it needs and just needs a stable, documented, cacheable contract? Build REST. If you genuinely have both callers, build both, starting with whichever one your actual first integration needs, and treat the other as an addition, not a rewrite, once real usage tells you it's needed.
Related reading: our breakdown of which ChatGPT model to use for what covers a similar "match the tool to the job" decision one level down, once you've picked MCP or REST and are deciding what the tool call itself should invoke. The same logic that separates few-shot from zero-shot prompting, giving the model just enough structure to act correctly and not more than it needs, applies directly to how you write an MCP tool's description and inputSchema; an overloaded description is the tool-calling equivalent of an overstuffed few-shot prompt. And if "compare the options, then commit to one" sounds familiar, it's the same shape of decision we walked through comparing Veo 3, Sora, and Kling for video generation: a different domain, the same discipline of naming what each option is actually for before picking one.
If you're building an MCP server today, our guide to using MCP inside Cursor and Claude Desktop covers the client side of the connection, and our MCP troubleshooting guide is worth bookmarking before your first connection attempt, not after it fails.
Stop rewriting prompts. Start shipping.
Works with ChatGPT, Claude, Gemini, Grok, Midjourney, Ideogram, Veo3 & Kling. 4.8★ on the Chrome Web Store.
Create An Account