What Is MCP? The Model Context Protocol Explained for Developers
MCP is the USB-C port for AI tools — one protocol instead of one integration per app. How it works, what a server exposes, and where it still hurts.
If you built LLM tool integrations before late 2024, you wrote the same adapter over and over. Your Slack integration for one app could not be reused by another, because every framework had its own idea of what a tool was.
That is the problem MCP solves, and it is why it went from an Anthropic proposal in November 2024 to something OpenAI, Google DeepMind and Microsoft all adopted.
What is MCP?
The Model Context Protocol is an open standard for connecting AI applications to external systems — tools they can call, data they can read, prompts they can reuse.
The usual analogy is a USB-C port for AI. Before USB-C, every device had its own connector. MCP is the connector: write one server for your system, and any MCP-compatible host can use it.
The M×N problem
The reason a protocol wins here is arithmetic.
With bespoke integrations, connecting M applications to N tools costs M × N pieces of code. Five apps and ten tools is fifty integrations, each written and maintained separately.
With a protocol it is M + N. Ten servers, five clients, fifteen things to maintain. Write a server for your internal API once and Claude Code, your own agent and anything else that speaks MCP can all call it.
That is the entire pitch. Everything else is detail.
The three roles
Host — the AI application. Claude Code, an IDE, your own agent.
Client — lives inside the host, one per server, and manages that connection.
Server — a program exposing capabilities over the protocol. It fronts your database, your API, your filesystem. Crucially, it knows nothing about which model is calling it, which is what makes it reusable.
What a server actually exposes
Three things, and the distinction matters more than most introductions suggest:
Tools — functions the model can call. Model-controlled: the model decides when to invoke them. create_issue, run_query, send_email.
Resources — data the host can read. Application-controlled: the host decides what to load, not the model. A file, a schema, a document.
Prompts — reusable templates a user can invoke deliberately, usually surfaced as slash commands.
The common mistake is making everything a tool. If the model does not need to decide whether to fetch something, it is probably a resource — and modelling it as a tool spends context describing a decision that was never the model’s to make.
A minimal server
The Python SDK makes this genuinely small. A working server, start to finish:
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("orders")
@mcp.tool()
def find_order(order_id: str) -> dict:
"""Look up an order by its ID. Returns status, items and totals."""
# The docstring IS the description the model reads. Write it for the model,
# not for your teammates — vague docstrings cause wrong tool calls.
return db.orders.get(order_id)
@mcp.tool()
def refund_order(order_id: str, reason: str) -> dict:
"""Refund an order. Irreversible — the caller should confirm with the user first."""
return payments.refund(order_id, reason=reason)
@mcp.resource("schema://orders")
def orders_schema() -> str:
"""The orders table schema. Host loads this; the model does not choose to."""
return db.orders.schema_sql()
if __name__ == "__main__":
mcp.run()
Two things worth copying from that.
The docstring is the interface. It is what the model reads to decide whether to call the tool. “Look up an order by its ID” gets called correctly; “order lookup” gets called at the wrong times.
Irreversible operations say so. MCP does not enforce approval — that is the host’s job — but flagging it in the description is what lets a well-built host gate it.
Where MCP actually hurts
It is not free, and the problems are real enough that they are worth knowing before you commit.
Tool schemas eat your context. Every connected server’s tool definitions sit in the window. Connect eight servers with a dozen tools each and you have spent tens of thousands of tokens describing capabilities before the user has said anything — and that is a direct route to context rot. The emerging fix is dynamic tool loading: a lightweight router that injects only the schemas relevant to the current request instead of all of them.
Authorisation is your problem. The protocol standardises transport, not permissions. A server that exposes refund_order will happily refund orders. Scoping, auditing and approval gates are on you.
Local servers run with your privileges. A stdio server is a process on your machine with your access. Treat installing a third-party MCP server the way you would treat installing any dependency that can read your filesystem — because that is what it is.
Should you build one?
A reasonable test: would more than one application want to call this?
If yes — an internal API several agents need, a database your team queries from different tools — a server is the right shape, and the M+N maths pays off.
If it is one function for one agent, a plain function call is simpler and you should just write that. MCP earns its overhead through reuse. Without reuse, it is ceremony.
I'm deciding whether to build an MCP server for this system:
[DESCRIBE THE SYSTEM AND WHO WOULD CALL IT]
Answer these directly, and push back if the honest answer is "don't build it":
1. How many distinct applications would realistically call this in the next year?
If the answer is one, say a plain function call is enough and explain why.
2. Split the capabilities into TOOLS (model decides when to call) and RESOURCES
(host decides what to load). Justify each — I over-use tools by default.
3. For each tool, write the docstring the model will actually read. Be specific
enough that a wrong call is unlikely.
4. Which operations are irreversible, and what should the host gate?
5. Estimate the total token cost of this server's schemas sitting in context.
If it is over ~3,000 tokens, tell me which tools to merge or drop.
The bottom line
MCP replaces M×N bespoke integrations with M+N reusable ones, and that is a genuinely good trade once more than one thing needs the same capability. The three roles are simple, a working server is a few dozen lines, and the SDKs are pleasant.
The costs are equally real: schemas consume context, permissions are entirely yours, and local servers run with your privileges. Build one when reuse justifies it — and keep the tool surface small, because every tool you expose is context you spend on every single request.
