An API is a contract for code you wrote. A developer reads the docs, picks the endpoints, and hard-codes when to call them. MCP (the Model Context Protocol) is a standard way for a server to describe its tools so that an AI model can find them and decide, mid-conversation, which one to call. MCP doesn't replace APIs. Most MCP servers call one underneath. The difference that matters is who makes the decision: your code when you write it, or a model while it runs.

Most "MCP vs API" explainers stop there. We run an MCP server that sits on top of a carrier API, so this one goes further: below is the same outbound phone call built both ways, what moved from your code into the server, and the cases where calling the API directly is still the right answer. The protocol details are checked against the current spec revision, 2026-07-28, as of October 12, 2026.

MCP vs API at a glance

Rows of telephone punch-down blocks on a plywood backboard, packed with blue and white twisted-pair wires
Telephone punch-down blocks, where a carrier API ends up. Photo: dmitrybarsky / Flickr (CC BY 2.0), cropped.

"API" here means the usual web API: REST, GraphQL or gRPC, called by an application. "MCP server" means a server speaking the 2026-07-28 revision over Streamable HTTP.

Web API (REST and friends)MCP server
Who calls itCode a developer wroteAn AI app (the host), on behalf of a model that chose the tool
When the call is decidedAt build timeAt run time, from the tool's name, description and schema
How capabilities are foundDocs or an OpenAPI file, read by a personA tools/list request, fetched by the client, which may cache it for the server's ttlMs
Shape on the wireMany URLs and HTTP methods, any payload formatOne endpoint; each client message is its own POST carrying JSON-RPC 2.0, answered with JSON or an SSE stream
ErrorsHTTP status codes your code branches onJSON-RPC errors for broken requests; isError: true results the model is meant to read and act on
CredentialsA key or token stored in your appFor remote servers that use the spec's authorization, an OAuth 2.1 token per user; the server must not forward that token upstream
StateUp to the APIStateless per request; cross-call state travels as an explicit handle passed back as an argument (DialMCP's call_id is one)

Every row follows from the first two. Once the caller is a model choosing at run time, the server has to describe itself in a form the model can read, report errors in words rather than codes, and protect itself from a caller that might pick the wrong tool or the wrong arguments.

Is MCP an API?

Strictly, yes. An MCP server exposes a defined interface over HTTP or stdio, so it is an API in the broad sense. What sets it apart is who reads that interface. A REST API's docs are read once, by a developer, and baked into code. An MCP server's tool list is fetched by a client (and refreshed when it changes) and handed to a model, which makes a fresh choice each time.

That is also why "MCP server vs API" is a slightly odd matchup. An MCP server is a particular kind of API, built for one kind of caller. The spec's own definition of tools says they let models "interact with external systems, such as querying databases, calling APIs, or performing computations." The API stays exactly where it is. MCP is the layer that lets a model ask for it.

What each looks like on the wire

Here's the first step of placing a phone call through a carrier's REST API. This is Telnyx's Call Control dial endpoint, which takes a connection ID, a caller ID and a destination:

POST https://api.telnyx.com/v2/calls
Authorization: Bearer $TELNYX_API_KEY
Content-Type: application/json

{
  "connection_id": "7267xxxxxxxxxxxxxx",
  "from": "+14155550100",
  "to": "+14155551234",
  "webhook_url": "https://your-app.example.com/telnyx/events",
  "stream_url": "wss://your-app.example.com/media",
  "stream_bidirectional_mode": "rtp"
}

That request dials. Nothing more. What happens next arrives as webhooks to your server: call.initiated, then call.answered or call.hangup, an answering-machine result if you asked for detection, and a media stream over the WebSocket if you set one up. Every decision after the dial is yours to code.

Here's the same intent as an MCP tool call to DialMCP. The model produced the arguments after reading the tool's description:

{
  "jsonrpc": "2.0",
  "id": 12,
  "method": "tools/call",
  "params": {
    "name": "place_call",
    "arguments": {
      "to": "+14155551234",
      "callee_name": "Zuni Café",
      "objective": "Book a table for 2 tonight at 19:30 under 'Iddo'. Acceptable window 19:00 to 20:30. If nothing is free, get the earliest time tomorrow."
    }
  }
}

The request is shorter because the arguments describe an outcome rather than a call leg. There's no caller ID field, because the server already knows whose number to use from the sign-in. There's no webhook URL either: the result comes back as a call_id, and the model checks on it with get_call. (The real request also carries protocol metadata in _meta, omitted here as the spec's own examples do. The JSON-RPC mechanics are covered in MCP server commands and tool calls.)

The same phone call, built both ways

Getting from "dial" to "the restaurant confirmed a table" is where the two paths split.

Built directly on the API

With a carrier API, your application owns the whole call. A realistic list of what you build:

  • A webhook handler that tracks each call through its states and survives retries and out-of-order events.
  • A real-time audio loop on the media stream: speech-to-text, a model deciding what to say, text-to-speech, and barge-in handling, all fast enough that the other person doesn't hear long pauses.
  • Phone menu navigation (sending keypresses), recognising hold music and staying quiet through it, and deciding what to do when voicemail answers.
  • The rules: telling the other person they're talking to an AI and being recorded, calling only at reasonable local hours, rate limits, a do-not-call list, and a caller ID you're allowed to use.
  • Recording storage, transcripts, and some way to turn a transcript into "did it work?"

None of that is a criticism of the API. It is a pipe, and it's meant to be one. If you're building a voice product, this is the job, and our guide to building an AI voice agent walks through it. We also compared which voice AI APIs can actually ring a cell phone.

Through an MCP server

With DialMCP, the model calls place_call, gets a call_id and a private listen link straight away, and follows up with get_call until the call reaches a final status. Calls usually take two to five minutes, longer than many MCP clients will wait on a single tool call by default. That's why the tool returns at once instead of blocking. The finished call comes back with a transcript, a recording, and a structured result. This one is the example from the tool reference:

"resolution": {
  "outcome": "achieved",
  "summary": "Booked a table for 2 at 19:30 tonight under Iddo.",
  "commitments": ["Table for 2 at 19:30 tonight, held 15 minutes"],
  "follow_ups": ["Call back if plans change; no card was taken"],
  "key_facts": ["Host: Marco", "Kitchen closes at 22:00"],
  "callee_requested_no_further_calls": false
}

Everything in the API list above still happens. It just happens inside the server, where it was written once rather than in every agent that wants to make a call. Underneath, DialMCP's server code calls Telnyx's API to dial, the same way you would. That is the normal pattern: the MCP server is an API client, and the model is the MCP server's client. The full contract for all four tools is on the tool reference.

API-shaped and task-shaped MCP servers

The comparison gets more useful once you notice that MCP servers themselves come in two shapes.

An API-shaped server maps endpoints to tools more or less one to one. Twilio's labs project describes itself as "Exposing all of Twilio's API as MCP Tools" and ships an openapi-mcp-server package that serves any OpenAPI spec as MCP tools. Telnyx's original Python server (now archived in favour of a TypeScript one) listed call-control tools for making a call, hanging up, transferring, playing audio, sending DTMF tones, and speaking text. These servers are good at administration: listing your numbers, sending a text, checking a message, buying a number if you let it.

A task-shaped server exposes outcomes. DialMCP has four tools, and the main one takes an objective in plain language, not a call leg to steer.

The shape matters for two reasons.

  • Some jobs can't be driven one tool call at a time. A phone conversation needs quick replies. A model that reasons, calls "speak text", waits for the result, and then reasons about what it heard would leave long silences on the line. The real-time loop has to live inside the server, and the model's job shrinks to stating the goal and reading the result.
  • Every tool costs context. Most clients put every tool's name, description and schema into the model's context. Twilio's README warns about this directly: "Due to LLM context limits, load specific APIs using --services or --tags." A few task-level tools are cheap to keep loaded. Hundreds of endpoint wrappers are not.

So "turn my REST API into an MCP server" is a reasonable first step, but it's rarely the finished design. Wrap the endpoints you'd want a model to touch, then ask which multi-step jobs deserve a single tool of their own.

Where the rules live

With an API, the rules are wherever your code puts them. Your app checks the hour before dialing because you wrote that check. With MCP, the caller is a model, and a model follows tool descriptions most of the time, which is not the same as always. So anything that must hold belongs in server code, not in a description. The spec says the same: servers MUST validate all tool inputs, implement access controls, and rate limit tool invocations.

DialMCP's limits sit in the server for that reason. Calls start only between 8:00 and 21:00 in the destination's local time. Each account gets three calls per rolling hour and ten per rolling day. Your account can call the same number at most twice in a rolling 24 hours. Every call opens with an AI and recording disclosure, and a number that asks not to be called again is blocked for every user, permanently. None of it depends on which client is connected or how the model was prompted. The policy is written up on Safety.

The other half is how a refusal reaches the caller. An API returns a 429 and your code decides what to do. An MCP server's refusal is read by a model, so it should say what happened and when to try again. A DialMCP rejection carries fields such as reset_at, or the destination's local hour, so the assistant can tell the user "it's 6 a.m. there and calls can start at 8, I'll try then" instead of retrying blindly. One honest detail: DialMCP returns these as ordinary tool results with an error key rather than with isError: true, so a client that only surfaces isError results should check for that key too.

Credentials: an API key vs a per-user grant

An API integration usually holds one key for the whole app, kept in a secret store. Whoever controls the app controls what the key can do.

A remote MCP server flips that around. Under the spec's authorization rules, the server is an OAuth 2.1 resource server, and each user's client signs in and gets its own token. The server must check that the token was issued for it, and it "MUST NOT pass through the token it received from the MCP client" to anything upstream. When the server calls another API, it uses its own separate credential. DialMCP works that way: you sign in by verifying your phone number by SMS, calls go out from that number, and the carrier key never leaves the server. The flow is explained step by step in MCP authorization and OAuth.

Local MCP servers are different. Twilio's and Telnyx's servers run on your machine by default and read an API key from their configuration, which puts them closer to the API model: typically one key with broad access to the account.

When a plain API is the better choice

MCP earns its overhead when a model needs to choose. When nothing in the loop is choosing, it's extra machinery.

  • No model in the loop. A nightly sync, a webhook handler, a billing job. Your code already knows what to call.
  • Fixed sequences. If every run calls the same three endpoints in the same order, letting a model pick the order adds variance and buys nothing.
  • Hot paths. A tool call means a model round trip before the request even goes out. Inside DialMCP, the real-time conversation doesn't go through MCP at all, for exactly this reason.
  • Bulk work. Thousands of identical operations belong in a script, not in a chat.
  • You are the product. If phone calls are your business, you want the carrier API and full control, not someone else's server.

The reverse case: use MCP when the next step depends on what the user said or what the last result was, when you want one integration to work across Claude, ChatGPT, Cursor and VS Code without writing a client for each, and when the server should hold credentials and rules the model must not be able to skip.

Most teams end up with both

The common setup isn't a choice between the two. Keep the API for your frontend, partners and scheduled jobs. Add an MCP server in front of it for agents, designed around the tasks those agents need rather than as a mirror of every endpoint, and give it its own auth and limits. If you're starting from scratch, how to build an MCP server covers the tooling. Remote MCP servers explained covers what changes once it's hosted, and MCP client vs server covers the roles on each side. If you already have several servers and want one front door for them, that is a gateway's job; see MCP gateway vs a hosted remote MCP.

Further reading

Try a task-shaped MCP server. DialMCP lets Claude, ChatGPT, Codex, Cursor or VS Code place real phone calls from your own verified number and hand back a transcript and a structured outcome. Add https://mcp.dialmcp.com/mcp as a connector and sign in. US and Canada, free during launch.

Connect DialMCP to your client

FAQ

What is the difference between MCP and an API?

An API is an interface that code you wrote calls in a way you decided at build time. MCP is a protocol that lets a server describe its tools so an AI model can discover them and decide at run time which to call. Most MCP servers call an ordinary API underneath, so the two are layers rather than alternatives.

Is MCP an API?

In the broad sense, yes. An MCP server exposes a defined JSON-RPC interface over HTTP or stdio. The difference is the reader: a REST API's docs are read once by a developer, while an MCP server's tool list is fetched by the client and handed to a model to choose from.

Does MCP replace REST APIs?

No. The REST API keeps serving your frontend, partners and scheduled jobs. An MCP server is usually a separate layer in front of it, built for AI clients, with its own authorization and limits.

Is calling a tool through MCP slower than calling the API directly?

Usually, yes. A tool call goes through the model and the MCP client before the server makes the underlying API request. That overhead is worth paying when a model needs to choose what to do next, and not worth it for fixed sequences, bulk jobs or real-time loops.

Can I turn my REST API into an MCP server automatically?

You can serve one from an OpenAPI spec; Twilio's proof-of-concept @twilio-alpha/openapi-mcp-server does exactly that. Treat the result as a starting point. Most clients load every tool's description and schema into the model's context, so expose only the endpoints a model should touch, and consider single task-level tools for multi-step jobs.