TL;DR: If you've ever wondered how an AI agent "magically" discovers tools exposed by an MCP server, forget the marketing diagrams for a moment. Grab
curl, inspect the network traffic, and it all starts making sense.
When I first started reading about the Model Context Protocol (MCP), almost every explanation looked something like this:
User
↓
LLM
↓
MCP
↓
Tools
The explanation usually stopped at:
"The LLM discovers tools and calls them."
That wasn't satisfying.
I wanted to know:
- Where are the tools actually exposed?
- How does the agent know what tools exist?
- Can I call an MCP server without an LLM?
- Is an MCP server just another REST API?
- What's actually happening on the wire?
So instead of reading more tutorials, I decided to reverse engineer a real MCP server.
I used LangChain's public MCP endpoint:
https://docs.langchain.com/mcp
Step 1: Ignore the LLM
The first thing I realized is that you don't need an LLM at all.
Using the Python MCP SDK:
tools = await session.list_tools()
The server replied with a list of tools.
This was my first "aha!" moment.
Unlike REST APIs, where developers typically read documentation to learn which endpoints exist, an MCP client simply asks the server:
tools/list
and the server replies with every available tool and its input schema.
Conceptually:
Client
│
│ tools/list
▼
MCP Server
│
▼
Available tools
The MCP server itself becomes the source of truth.
Step 2: No LLM Is Required
Suppose the server advertises:
search_docs_by_lang_chain(query: string)
I can simply call it.
result = await session.call_tool(
"search_docs_by_lang_chain",
{
"query": "How do I create a LangGraph agent?"
}
)
No LLM.
No reasoning.
No AI.
Just:
Client
│
tools/call
│
▼
MCP Server
│
Runs tool
│
Returns result
This was probably the biggest realization:
MCP is fundamentally a protocol for discovering and invoking tools.
The LLM is just another client.
Step 3: Is the SDK Doing Something Magical?
The obvious next question was:
Is the Python SDK hiding some secret sauce?
Turns out...
Not really.
I replaced it with curl.
First, I listed the available tools.
curl -X POST https://docs.langchain.com/mcp \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{
"jsonrpc":"2.0",
"id":1,
"method":"tools/list"
}'
The server immediately returned the list of tools.
Then I called one.
{
"jsonrpc":"2.0",
"id":2,
"method":"tools/call",
"params":{
"name":"search_docs_by_lang_chain",
"arguments":{
"query":"How do I create a LangGraph agent?"
}
}
}
It worked.
The SDK wasn't performing magic.
It was simply wrapping the protocol.
Step 4: curl -v Was More Educational Than the Docs
Running with verbose output:
curl -v ...
revealed far more than I expected.
MCP Runs Over Plain HTTPS
The first surprise:
Connected to docs.langchain.com
SSL connection using TLSv1.3
HTTP/2
Nothing exotic.
Just HTTPS.
MCP Uses JSON-RPC
Instead of REST endpoints like:
GET /tools
POST /search
everything goes through a single endpoint:
POST /mcp
The operation is encoded inside the request body.
{
"jsonrpc":"2.0",
"method":"tools/list"
}
or
{
"jsonrpc":"2.0",
"method":"tools/call"
}
This is JSON-RPC.
JSON vs JSON-RPC
This confused me initially.
JSON is just structured data.
{
"query":"LangGraph"
}
JSON-RPC is a way of calling remote functions.
{
"jsonrpc":"2.0",
"method":"tools/list",
"params":{}
}
Think of it this way:
JSON
↓
A document
JSON-RPC
↓
A function call encoded as JSON
The Response Wasn't JSON
The response headers included:
content-type: text/event-stream
Instead of returning:
{
"result": ...
}
the server returned:
event: message
data:
{
"result": ...
}
This is Server-Sent Events (SSE).
What Is Server-Sent Events?
Most REST APIs work like this:
Request
↓
Response
↓
Connection closes
SSE keeps the connection alive.
Instead of one response, the server can stream many events.
event: message
data: {"progress":10}
event: message
data: {"progress":20}
event: message
data: {"progress":30}
This is why AI APIs often stream tokens, and why MCP uses SSE as its transport.
Even though tools/list only returned a single event, the protocol supports long-running tools that stream progress back to the client.
Step 5: So Where Does the LLM Come In?
Suppose the MCP server advertises:
search_docs(query)
fetch_page(url)
list_examples(topic)
A user asks:
"How do I create a LangGraph agent?"
Who decides to call search_docs?
Not the server.
Instead:
- The client asks for the available tools.
- The tool definitions are shown to the LLM.
- The LLM decides which tool best answers the user's question.
- The client sends
tools/call. - The MCP server executes the tool.
The MCP server never interprets natural language.
It simply executes requested tools.
The Mental Model That Finally Clicked
Initially I thought of MCP as:
AI magic
After spending an hour with Python, curl and the network inspector, I now think of it as:
HTTPS
+
JSON-RPC
+
Runtime Tool Discovery
+
Tool Invocation
The LLM is not MCP.
The tools are not MCP.
MCP is simply the protocol that allows clients to discover and invoke capabilities exposed by a remote service.
The Biggest Surprise
The biggest surprise wasn't that MCP exists.
It was how ordinary it is.
The SDK wasn't doing anything magical.
The protocol wasn't doing anything magical.
Everything ultimately boiled down to a few JSON-RPC messages sent over HTTPS.
Sometimes the fastest way to understand a protocol isn't another tutorial.
It's opening curl -v.
What's Next?
There are still a few things I'd like to explore:
- How stateful MCP sessions work.
- Resources vs Tools vs Prompts.
- Authentication.
- Building a minimal MCP server from scratch.
- How different transports (stdio vs HTTP) compare.
Those will probably become future posts.