Every explanation of MCP I've read starts by making it sound like a new kind of intelligence, and that's exactly what trips people up. You read "Model Context Protocol" and your brain fills in something clever happening under the hood: some extra reasoning layer, some new capability the model didn't have before. It isn't that. MCP doesn't make an LLM smarter or an agent more capable. It just standardizes how an agent finds out what tools it has and how to call them. That's the whole thing. Once you see it that way, it stops being a mystery and starts looking like plumbing you've built a version of before.
What the agent actually needs
An LLM on its own only produces text. Ask it to book a flight and the best it can do is describe how you'd book one yourself. It can't reach out and click anything. An agent is what you get when you wrap that LLM with tools, memory, and a loop: it can call something, look at the result, decide what to do next, and call something else. That loop is what turns "answer a question" into "go do a thing."
To go do a thing, the agent needs a way to reach the outside world. That's what a tool is: searchFlights(), bookFlight(), whatever. Underneath, those tools usually just call an API:
Agent → Tool → API → External service
None of this is new. It's the same shape as any integration you've written. The interesting part is what happens when there isn't just one API.
The problem: every service describes itself differently
Say there are a few hundred airlines, each with its own API. One returns:
{ "flightNumber": "123", "origin": "SFO", "destination": "JFK" }
Another returns the same information as:
{ "number": "123", "from": "SFO", "to": "JFK" }
Different field names, probably different endpoint names too. If you want one agent that can work with all of them, you end up writing a separate adapter per airline:
Agent
├── Airline A Adapter
├── Airline B Adapter
├── Airline C Adapter
└── ...
Add a service, write another adapter. That's not a hard problem conceptually, it's just tedious in a way that scales badly. This is the exact problem MCP is aimed at, not "agents can't think," but "agents can't discover and use arbitrary services without someone hand-wiring each one."
MCP is a way to describe capabilities, not a way to gain them
MCP (Model Context Protocol) gives a service a standard way to say: here's what I can do, here's what input each thing needs, here's what it returns. An MCP server for an airline might expose something like:
Capabilities:
searchFlights
bookFlight
with a schema attached to each one, roughly:
{
"name": "searchFlights",
"description": "Search available flights",
"inputSchema": {
"type": "object",
"properties": {
"from": { "type": "string" },
"to": { "type": "string" }
},
"required": ["from", "to"]
}
}
An MCP client, the thing your agent runs inside, connects to that server and discovers this. The agent no longer needs Airline A: /api/flights, Airline B: /flights-list, Airline C: /list-flights hardcoded anywhere. It asks the server what's available and gets a consistent shape back, regardless of what the underlying API actually looks like.
The MCP server sits between the agent and your API, not in place of it
This is the part that's easy to miss: the MCP server isn't the business logic. It's a thin layer in front of logic you already have.
Without MCP, if you wrote the agent yourself, you'd hardcode the API calls directly into it:
var flights = await httpClient.GetFromJsonAsync<Flight[]>(
"/api/flights?from=SFO&to=JFK"
);
await httpClient.PostAsJsonAsync(
"/api/flights/book",
booking
);
The agent has to know the API's shape. With an MCP server in between, you wrap the same calls as tools:
[McpTool("searchFlights")]
public async Task<List<Flight>> SearchFlights(string from, string to)
{
return await flightApi.Search(from, to);
}
[McpTool("bookFlight")]
public async Task<BookingResult> BookFlight(
string flightId,
string firstName,
string lastName,
string email)
{
return await flightApi.Book(flightId, firstName, lastName, email);
}
Look at what's inside those methods: flightApi.Search(...), flightApi.Book(...). That's still your existing client hitting your existing API. The MCP layer's job is only to describe those two methods to the agent in a shape it can discover and call. The chain is:
MCP Tool → Flight API Client → Flight Service
You're not rebuilding the Flight Service. You're exposing it.
What the round trip looks like
Say the user asks: "Find me the cheapest flight from SFO to JFK." The LLM looks at the tools it's been given, decides searchFlights fits, and produces a tool call:
{ "name": "searchFlights", "arguments": { "from": "SFO", "to": "JFK" } }
That goes to the MCP client, which sends it to the MCP server, which turns it into the actual HTTP call:
LLM → tool call → MCP Client → MCP Server → HTTP request → Flight API
The API responds with something like:
[
{ "id": "FL123", "price": 280, "airline": "Air A" },
{ "id": "FL456", "price": 220, "airline": "Air B" }
]
That comes back up through the same chain, the agent hands it to the LLM, and the LLM picks FL456 as the cheaper option. If the user then says "book it," the same pattern repeats with bookFlight, and the API eventually returns a booking reference. Nothing in this sequence required the model to know anything about the Flight API's actual shape. It only needed the tool's name and schema.
Why this matters if you're a backend developer
This is the part that made MCP click for me, because I already have a service with exactly this shape. ProcessHub, the workflow platform I've been building, has modules like:
ProcessHub
├── Workflow
├── Employee
├── Loan
├── Task
└── Notification
I could expose an MCP server on top of it with tools like:
getEmployee
getPendingTasks
getLoanStatus
getWorkflowStatus
approveTask
rejectTask
None of that is new backend work. It's the same application services that already back the UI. The MCP server just makes them discoverable and callable by an agent instead of only reachable through a browser click.
Then a request like "check the status of Ali Rezaei's loan request" becomes a sequence the LLM can work out on its own: call getEmployee, then getLoanStatus, then getWorkflowStatus. Or "find the loan requests waiting on my approval" maps straight to getPendingTasks. The agent isn't reasoning about loans or workflows in some deeper sense, it's matching a request to the tools it was told exist, the same way it matched "find the cheapest flight" to searchFlights.
That's the actual value for someone in my position: you don't rebuild anything to make your system agent-usable. You write a thin MCP layer over the application services you already have, and the discovery and calling problem, the part that used to mean one bespoke adapter per integration, gets handled by the protocol instead.
One thing worth keeping in mind: permission
None of this means the agent should just go do things unsupervised. For anything sensitive, booking something, approving a task, changing data, it makes sense for the agent to ask first:
Agent: I want to book this flight. Approve?
User: Approve
Only then does the actual call happen. For something like approveTask on a loan workflow, that confirmation step isn't optional. It's the difference between a useful assistant and one you can't trust near real data.
Where this leaves things
The LLM still does the thinking. The agent still does the looping between the LLM and its tools. What MCP adds is a way for the agent to find out what's callable, without someone hand-coding that discovery for every single service. If you've already got working APIs behind your product, most of the work isn't teaching an agent to be smart about them. It's writing the thin layer that tells the agent they exist.
Top comments (0)