Skip to content
Model Context Protocol (MCP)What MCP is and why it exists

Senior · Core path · 8 chapters

What MCP is and why it exists

Decoupling tool implementations from the applications that use them, so an integration is written once rather than per-client

MCP pays off when many clients need the same capability, but adds protocol and operational overhead that is wasteful for a single tightly coupled caller.

Read these first

What you will know

  • Build a minimal MCP server exposing one tool, then invoke it from a compliant client without client-specific adapter code.

    The working path should cover connection, capability discovery, and a tool call while keeping the tool implementation independent of the client.

  • Run the same MCP server from two different compliant clients and verify that its implementation and configuration do not change between runs.

    This exposes whether the claimed portability is real or hidden behind per-client wrappers and assumptions.

  • Compare a direct function-call integration with an MCP integration for the same tool by measuring setup time, request latency, and implementation surface.

    The protocol introduces serialization, discovery, and process or transport boundaries that may outweigh reuse for a single caller.

  • Trace one MCP request from client initialization through tool discovery and invocation, recording each boundary where errors or compatibility issues can occur.

    Failures often arise before the tool runs, such as unsupported capabilities, transport mismatches, or schema disagreement.

  • Decide whether a given integration is worth the protocol

    If only your app will ever use it, a function call is simpler and has no protocol overhead.


Chapters

  1. 1

    What an MCP server decouples

    The unit of reuse is the server, not the function behind it. A tool wired straight into one agent through the provider's tool-calling API works for exactly one caller; behind an MCP server, a second app, a desktop agent or a teammate's editor connects, discovers it and calls it with no per-client adapter shipped by you. This chapter prices the four transcriptions that collapse into one declaration, and is precise about the three things the protocol is constantly confused with and the things it plainly does not give you.

  2. 2

    Building a minimal MCP server

    The artefact the rest of the topic argues about, built rather than described: a server that declares a single tool with a name, a description and an input schema, a handler behind it, and a transport it is served over — then a compliant MCP client pointed at it by configuration alone. Four things in that file are load-bearing and the rest is ordinary Python. Running the path end to end is connection, discovery, one invocation and one result, with no client-specific code anywhere in it.

  3. 3

    Translating MCP tools for the model

    Correcting the most common sentence said about MCP: the model does not talk to servers and does not speak the protocol. The client discovers names, descriptions and schemas over MCP, then writes them into the ordinary tools array of the provider request, leaving the tool-calling loop untouched. Only the source of the definitions moved — which is also why forty tools from three servers are serialised, tokenised and charged for in every request you send, at roughly 5,200 tokens before your system prompt.

  4. 4

    The JSON-RPC handshake

    Underneath the SDK's convenience is a fixed sequence: open a transport, send an `initialize` request that negotiates a protocol version and the capabilities each side supports, get the response, finish the handshake with a notification that carries no id, ask for the tool list, and only then call a tool. The wire format is JSON-RPC — request objects carrying ids, matching responses, errors returned in the response rather than by the transport. Knowing these are four distinct steps with four distinct messages is what makes a trace readable, and the 234 ms of session setup is what a long-lived client stops paying.

  5. 5

    Failure boundaries before your handler

    One request travels from client initialization through capability discovery to invocation, and six boundaries can reject it on the way: a transport that never carried a byte, a version with no overlap, a handshake the client abandoned, an undeclared capability, a discovery step that returned nothing, and a schema both sides read differently. A log line at the top of your handler is a hard fact — if it never appeared, nothing inside the tool ran. Each boundary leaves its own message, or its own silence, in the trace.

  6. 6

    Testing a server under two clients

    Running the same server from two compliant clients is a test with a real failure condition, and it only means something if you decide in advance what would count as a negative result. The server source, the launch command, the environment and the tools served have to be identical; the config file, its format and the key name wrapping it may differ freely. If either client needed a forked config, a shell wrapper or a client-specific branch, the decoupling was never in the server — it was in the adapter you wrote twice.

  7. 7

    Per-call overhead of a protocol boundary

    Take the model out of the loop and measure the same tool called as a Python function and called through a local stdio server: 4 ms becomes 31 ms. The extra 27 ms is almost none of it the pipe — 0.2 ms — and almost all of it serialisation, schema validation and event-loop scheduling on both sides. It is judged against the model call it sits inside rather than against the 4 ms, which makes it a one percent regression in an agent turn and 87% of elapsed time in a loop of forty calls.

  8. 8

    Deciding between a server and a function

    MCP amortises against consumers, so a capability with exactly one caller buys serialization, a process boundary and a version-skew surface for nothing. The counting argument makes the answer estimable rather than a preference: converting later costs about one day more than converting now, while building now costs two days plus three days a year of operating attention per server, forever. And keeping the function alongside a server for other teams leaves two implementations of one capability unless both entry points call one core.

External resources