Every AI tool integration used to be bespoke glue code. MCP is an attempt to make that plumbing standard, and it is worth understanding before you build another connector.
Connecting a language model to real data has always been custom work. If you wanted your assistant to read from your issue tracker, someone wrote a function that called the issue tracker API, described that function in a schema the model could read, parsed the model's request, and shipped it. Then the next team wanted the same issue tracker in their own product, and they wrote the same thing again, slightly differently.
This is the problem the Model Context Protocol, usually shortened to MCP, sets out to address. MCP is an open protocol that standardises how an AI application talks to external tools and data. Instead of every application writing its own connector for every data source, a data source publishes one server, and any compliant application can use it.
That is the pitch. Whether it is worth adopting depends entirely on how many of those connections you actually have.
Suppose you have N AI applications: a chat product, an internal support bot, a coding assistant. And M systems you want them to reach: a database, a ticketing system, a document store, a payments API.
Without a shared protocol, the integration work is roughly N times M. Each application writes its own glue for each system. The support bot's database connector and the coding assistant's database connector solve the same problem twice, in two codebases, with two sets of bugs and two schedules for keeping up with API changes.
With a shared protocol, the work becomes roughly N plus M. Each application implements the protocol once. Each system exposes a server once. Any application can then reach any system without either side knowing the other exists.
This is the same shape as most successful plumbing standards. Before a common interface exists, every pairing is bespoke. After it exists, both sides build to the interface and stop caring who is on the other end.
The comparison people reach for is a hardware port, or a device driver. A driver is a good analogy because it captures something important: the standard does not make the device do more. It makes the device reachable without the operating system knowing anything specific about it. MCP does not make a model smarter. It makes capabilities reachable without the application knowing anything specific about them.
MCP splits the world into three roles.
The important structural point is that the model never talks to the server. The model produces a request. The host decides whether to honour it. The client relays it. The server executes it. The result travels back the same way and is added to the conversation.
That indirection is not incidental. It is where every policy decision lives: permissions, approval prompts, rate limits, audit logs. A protocol that let the model call servers directly would have nowhere to put any of that.
MCP servers offer capabilities in a few distinct categories. The distinctions matter more than they first appear.
create_ticket, run_query, send_message. These are model controlled, meaning the model decides when to call them, subject to whatever approval the host requires. Tools are the category with side effects, and therefore the category that deserves the most scrutiny.The split between model controlled and user or application controlled is the part worth internalising. It is a design statement, not a technical detail. Things that change the world sit behind a decision point. Things that only read data can flow more freely.
Here is the piece that genuinely differs from writing tools inline.
When you define function calling tools in your own application, the tool list is fixed at build time. It lives in your source. Adding a tool means a code change and a deploy.
With MCP, the client connects to a server and asks what it offers. The server responds with its list of tools, their descriptions, and their input schemas. The host takes that list, formats it for whichever model it is using, and includes it in the request. If the server adds a tool tomorrow, the host picks it up on the next connection without a code change.
That is the real structural shift: the capability set becomes a runtime property rather than a compile time one.
It also introduces a real cost. Every discovered tool consumes context. Connect five servers with fifteen tools each and a meaningful portion of your context window is now a tool catalogue, before the user has said anything. Models also get less reliable at choosing correctly when the menu is long. Connecting many servers because you can is a common and avoidable mistake.
The specifics vary by SDK and change over time, so treat this as conceptual rather than copy ready. The shape is what matters.
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("issue-tracker")
@mcp.tool()
def search_issues(query: str, limit: int = 10) -> str:
"""Search open issues by keyword.
Use when the user asks about existing bugs or feature requests.
Returns a compact list of issue IDs, titles, and status.
"""
results = tracker_api.search(query, limit=limit, state="open")
return format_compact(results)
if __name__ == "__main__":
mcp.run()Three things are doing the work here, and none of them is the protocol.
The docstring is not documentation for humans. It is the instruction the model reads when deciding whether to call this. A vague description produces a badly used tool. The type hints become the input schema, which is what constrains the model's arguments. And format_compact matters more than it looks: returning a raw API payload floods the context with fields nobody needs. Tool output should be shaped for a reader with a token budget.
I would reach for MCP when at least one of these is true:
I would not reach for it when you have one application, a handful of tools, and no reuse in sight. A plain function in your own codebase is simpler, easier to debug, has no process boundary, and adds no dependency. The protocol earns its cost through reuse. Without reuse there is nothing for it to amortise.
| Situation | Reasonable choice |
|---|---|
| One app, one tool, no reuse | Inline function calling |
| Several internal apps, same data source | MCP server |
| Capability intended for third party hosts | MCP server |
| Prototype, still deciding the shape | Inline, extract later |
Starting inline and extracting to a server later is a legitimate path. The tool logic barely changes. What changes is the wrapper around it.
An MCP server is a capability you are handing to a model. Treat it with the seriousness that phrase deserves.
None of this is unique to MCP. All of it becomes more pressing when connecting a capability is a config change rather than an engineering project.
MCP is plumbing, and plumbing is worth understanding precisely because nobody notices it when it works.
The honest summary is this. The protocol solves a genuine problem: integration work that scales multiplicatively instead of additively. It solves it with a design that is mostly sensible, particularly in keeping the model away from direct execution and in separating things that read from things that act. It is not magic, it does not improve model behaviour, and it adds a process boundary, a dependency, and a context cost you pay on every request.
The decision is not architectural taste. It is arithmetic. Count your clients and count your data sources. If the product is meaningfully larger than the sum, a protocol pays for itself. If it is not, write the function.
Written for engineers deciding how their AI products should reach the systems around them.
Coming soon.