
In brief
An MCP server is a JSON-RPC wrapper that turns API calls into tools an AI host can discover and invoke. The wrapper buys discovery, delegation and one protocol for every integration; it costs a second surface, a new security model and churn between dated spec revisions.
An MCP server and an API are easy to confuse because the same backend usually sits behind both. The API is the contract your program calls. The MCP server is a second contract, written in JSON-RPC, that exists so an AI host can discover and invoke that backend without you shipping a custom integration for every model.
The Model Context Protocol still matters in 2026, but most blog posts describe a protocol that no longer exists. Revision 2026-07-28 removed the connection-wide handshake, deprecated sampling and roots, and made every request carry its own version and capabilities. This guide compares an MCP server with a plain API against the current specification, then shows where the wrapper earns its cost.
01What each side actually is
A REST API is a contract between two programs. You know the caller, you write the client, and you get compile-time or test-time feedback when the contract moves. The shape is stable: an HTTP method, a path, headers, a body and a status code.
An MCP server is a program that exposes context and capabilities to an AI host over the Model Context Protocol. The specification defines three roles: the host is the LLM application, a client is a connector inside that host, and a server is the program that provides context. The host opens one client per server, so a single coding assistant can talk to a filesystem server, a database server and a third-party SaaS server at the same time.
The part that changes everything is who chooses the call. In an API integration, the call site is code you reviewed. In MCP, a model selects a tool from a list and fills in the arguments, so the server has to be safe when the caller is wrong, curious or actively adversarial.
API call : your code -> fixed endpoint -> typed result
MCP tool call: model picks it -> tools/call -> content[] + structuredContent
02What MCP adds on top of an API
MCP standardises four things that a plain API leaves to each integrator:
- Discovery. A server that declares the tools capability must answer tools/list with the tools currently available. Each entry carries a name, a description and an inputSchema, so the host can hand a fresh tool list to the model without a new deployment.
- Delegation. The model can pick the tool and the arguments at run time, which is exactly what you want when the user request is open-ended and you cannot enumerate every path in advance.
- One client for every server. The host implements the protocol once. After that, a new integration is a new server, not a new SDK and auth flow per vendor.
- A consent surface. The specification says there should always be a human in the loop with the ability to deny a tool invocation, and it asks hosts to show which tools are exposed and when they run.
A tool listing is a small JSON-RPC request and response:
{ "jsonrpc": "2.0", "id": 1, "method": "tools/list", "params": {} }
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "search_orders",
"description": "Search orders by status for the signed-in customer",
"inputSchema": {
"type": "object",
"properties": { "status": { "type": "string", "enum": ["open", "shipped", "cancelled"] } },
"required": ["status"]
}
}
]
}
}
Everything in that block is prompt text. The name, the description and the schema are read by a model, not by a compiler, which is why tool design is closer to writing documentation than to writing an SDK.
03What an API still does better
MCP is a facade, so the API keeps every advantage that comes from having a deterministic caller:
| Concern | Plain API | MCP server |
|---|---|---|
| Caller | Code you wrote | A model that may pick the wrong tool |
| Contract check | Compiler, types, contract tests | JSON Schema, checked at run time |
| Bulk work | Pagination, batch endpoints, streaming | One tool call per decision, or a custom tool |
| Caching | Standard HTTP semantics | Per-tool TTL metadata, no general cache layer |
| Auth | Whatever your HTTP stack already does | MCP authorization spec, added on top |
| Debugging | Request logs and traces | Logs plus the prompt and tool-choice context |
Two costs deserve emphasis. The first is context. Every tool you expose is schema and description text that competes for the same context window as the user's actual question, which is why teams publish MCP servers that reduce context consumption by filtering what the model sees. The second is failure mode. An API error is a bug; a tool error the model chose wrongly is a behaviour you have to design for.
04When an MCP server is the right call
| Situation | Better choice |
|---|---|
| Your own service calls your own service | Plain API, typed client |
| Several AI hosts need the same backend | MCP server, one per backend |
| A model must choose among many operations | MCP tools with narrow schemas |
| High-volume batch transfer | Plain API, keep it out of the tool surface |
| Local files, git, CLI tools | Local MCP server over stdio |
| A shared third-party service | Remote MCP server with the authorization flow |
The rule of thumb: add MCP where a model has to make a decision, and keep the plain API everywhere a program already knows what to do. Wrapping an entire API one-to-one is the most common way to make an agent slower, more expensive and easier to confuse.
05Expose an existing API without rewriting it
You rarely need a new backend. A thin MCP server translates one tool call into one API request:
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": { "name": "search_orders", "arguments": { "status": "shipped" } }
}
That call maps to a single REST request:
GET /api/orders?status=shipped
Authorization: Bearer <token for the signed-in user>
The response returns content blocks, and any machine-readable value goes in structuredContent:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [{ "type": "text", "text": "3 shipped orders found." }],
"structuredContent": { "orders": [ { "id": "A-1043", "status": "shipped" } ] },
"isError": false
}
}
Three implementation rules keep the wrapper thin:
Before you ship the wrapper, run the request and the response through the JSON validator to confirm both shapes parse.
- Keep tool names and schemas stable. They are the contract the model reads, so renaming a field is a breaking change even when the API did not move.
- Pass the caller's credentials through, scoped. The tools a server returns may vary by the authorization presented on the request, so a token with fewer scopes can see a smaller tool list instead of a list it is not allowed to use.
- Return an error the model can act on. An invalid argument should be an isError result with a clear message, not a silent empty list.
06The transport decides the deployment model
MCP separates what a message means from how it travels. The specification defines two standard bindings:
- stdio frames newline-delimited JSON-RPC over the standard streams of a subprocess that the client launches. It typically serves a single client and fits local tools such as a filesystem server.
- Streamable HTTP sends each message as an HTTP POST to one MCP endpoint, and the reply arrives as a JSON object or a request-scoped server-sent events stream. It serves many clients, which is what a shared remote server needs.
The older HTTP+SSE transport is deprecated in favour of Streamable HTTP. Remote servers also inherit the MCP authorization specification, which is built on OAuth 2.1, so the auth design is not something you can skip by choosing HTTP.
One last transport detail: metadata now travels in the message body. Every request carries its protocol version and client capabilities in _meta fields, and the Streamable HTTP binding may mirror selected fields into headers for intermediaries. The body stays the source of truth.
07The security model changes
An API is attacked by whoever can reach its network. An MCP server is attacked by whoever can reach its network plus whoever can influence the text a model reads before it calls a tool. The MCP security guidance documents the failure modes, and they are worth reading before you ship a remote server:
| Risk | What it exploits | Mitigation |
|---|---|---|
| Confused deputy | A proxy server with a static client ID, dynamic client registration and a consent cookie lets a malicious client obtain an authorization code | The proxy server must implement per-client consent |
| Token passthrough | A server accepts a token that was not issued for it | Validate that the token audience is your server |
| SSRF | A server fetches a URL supplied by a client | Allowlist destinations and block internal addresses |
| State handle hijacking | Resource and tool handles are reused or guessed across sessions | Bind handles to the client that created them |
| Local server compromise | A stdio server runs with your user's privileges and can read anything you can | Run it with the least privilege it needs and pin its version |
The specification's own principles are blunt: users must explicitly consent to data access and operations, host applications must not forward user data to a server without consent, and tool descriptions should be treated as untrusted unless they come from a trusted server. Those are not slogans. They are the difference between a demo and a service that survives contact with a hostile document.
A tool description is prompt text written by someone else.
MCP tells implementers to treat tool descriptions as untrusted input, because a compromised or malicious server can use them to steer the model. Review a third-party server the way you review a dependency, not the way you review a REST endpoint.
08Version churn is part of the deal
MCP revisions are dated, not numbered. The latest as of this writing is 2026-07-28; the previous widely deployed revision is 2025-06-18. The 2026-07-28 revision is a breaking change for anyone who implemented to a blog post:
- The initialize handshake is gone. Every request declares its protocol version and client capabilities, and the server accepts or rejects it independently. A request for a version the server does not implement returns UnsupportedProtocolVersionError with the supported list.
- Roots, sampling, logging and dynamic client registration are deprecated as of 2026-07-28, with removal eligible from the next revision onward.
- Servers no longer initiate JSON-RPC requests, and clients no longer send JSON-RPC responses. That single rule removes sampling-style recursion from the core protocol.
If an integration guide tells you to open a stateful session and then negotiate capabilities once, it was written for a legacy revision. Pin the revision your server implements, and surface the supported list instead of failing opaquely.
09Common mistakes
| Mistake | Why it hurts | Fix |
|---|---|---|
| Mirroring every REST endpoint as a tool | Context bloat and near-identical tools confuse the model | Expose a few decision-shaped tools |
| Treating the tool description as documentation | The model reads the description as instructions | Write it as a contract, and treat it as untrusted |
| Trusting the model's arguments | A hijacked model can send any value | Validate against inputSchema before acting |
| Building on the legacy handshake | The 2026-07-28 client will not negotiate the way you expect | Declare version and capabilities per request |
| Skipping per-client consent on a proxy | Confused deputy hands tokens to an attacker | Store consent per client ID and user |
| Running a local server as your user | The server can read every file you can | Scope its filesystem and network access |
When a tool schema changes, review the old and new versions side by side with the diff checker before the model ever sees the new contract, and keep the REST status codes behind it honest with the HTTP status code reference.
10Sources
- Model Context Protocol — Specification, revision 2026-07-28 (accessed 2026-10-04)
- Model Context Protocol — Architecture overview (accessed 2026-10-04)
- Model Context Protocol — Transports (accessed 2026-10-04)
- Model Context Protocol — Versioning and Compatibility (accessed 2026-10-04)
- Model Context Protocol — Server Features: Tools (accessed 2026-10-04)
- Model Context Protocol — Deprecated Features (accessed 2026-10-04)
- Model Context Protocol — Security Best Practices (accessed 2026-10-04)
- JSON-RPC 2.0 Specification (accessed 2026-10-04)
Validate an MCP configuration file and its JSON-RPC payloads before a server ever sees them.
JSON ValidatorFrequently asked questions
Is an MCP server a replacement for my API?
No. It is a facade in front of the API. The MCP server still calls your HTTP endpoints, but it exposes them as tools with a JSON Schema so an AI host can discover and invoke them.
What protocol does MCP use?
MCP uses JSON-RPC 2.0 messages. Requests, responses and notifications follow the JSON-RPC shape, and transport bindings such as stdio and Streamable HTTP define how those messages are framed and delivered.
Does MCP still use an initialize handshake?
Not in revision 2026-07-28. Every request now carries its protocol version and client capabilities in _meta fields, and the server accepts or rejects each request independently. The initialize handshake belongs to the legacy revisions that predate it.
What is the difference between an MCP tool and a REST endpoint?
A REST endpoint is a fixed contract that your code calls. An MCP tool is a JSON Schema plus a description that a model may choose to call, so the tool name and description become part of the prompt, and the model can pick the wrong one.
Should an MCP server run locally or remotely?
Local servers use the stdio transport and are launched as a subprocess by the host, which suits filesystem and CLI access. Remote servers use Streamable HTTP and serve many clients, which suits shared services but requires the MCP authorization flow.
Is an MCP server less secure than an API?
It is a different security model, not automatically a weaker one. The MCP specification requires explicit user consent, warns that tool descriptions are untrusted input, and lists confused deputy, token passthrough, SSRF and handle hijacking as risks that implementations must mitigate.
When is an MCP server overkill?
When you control both ends and the caller is your own code. A typed REST client is cheaper, faster and easier to test than a model-driven tool call, so keep the API and add MCP only where a model needs to choose the call.
Related articles
- What is a SKILL.md file?
Skills and MCP servers are the two ways an agent gains new behaviour, and they are not the same trade.
- How to choose a coding agent harness
The host decides how much of the MCP tool surface an agent actually sees.
Related tools
- JSON Validator
Check the JSON-RPC request or the MCP config before it reaches the server.
- JSON Formatter & Validator
Read the payload a tool call returns, structured content included.
- HTTP Status Codes
Pick the right REST status code for the API you are wrapping.
- Diff Checker
Compare two versions of a tool schema before you ship the change.