MCP, Explained for Data People: It Standardizes the Plumbing, Not the Thinking
The Model Context Protocol is the closest thing AI agents have to a shared standard for reaching data and tools. I built a working server in 39 lines to see what the protocol actually guarantees, and where the real work still sits with you.

Every agent demo eventually hits the same wall. The model is fine. The prompt is fine. The problem is the last mile: getting the agent to your Postgres, your files, your internal API, without writing a bespoke connector for each pairing. Anthropic introduced the Model Context Protocol on 25 November 2024 to standardize exactly that last mile, shipping a specification, SDKs, local server support in the Claude desktop apps, and reference servers for Google Drive, Slack, GitHub, Git, Postgres, and Puppeteer. Block and Apollo were early adopters; OpenAI and Google DeepMind followed. In December 2025, Anthropic donated the protocol to the Agentic AI Foundation, a fund under the Linux Foundation, making it vendor-neutral.
That is the resume. What follows is the part that matters for people who build: what the protocol actually guarantees, what I found when I ran it, and the security fine print most tutorials skip.
The problem MCP was built to kill
Without a shared protocol, every AI application needs a custom connector for every tool it touches. Two apps and three tools means six integrations, each with its own auth, error handling, and schema translation. MCP collapses that into each side implementing the protocol once: every app speaks MCP on the way out, every tool speaks MCP on the way in. The integration math goes from M times N to M plus N.
This only pays off at scale, which is worth stating plainly. If you have one agent and one database, a direct function call is simpler and you should keep it. MCP earns its keep when the same tools must serve several agents, or when you want your tool to work in Claude Desktop, Cursor, ChatGPT, and your own agent without writing the glue four times.
Three roles, and the one everyone gets wrong
MCP has three roles, and the split between the first two is the part people mangle:
- Host. The AI application the user talks to: Claude Desktop, Cursor, a custom agent you wrote. The host owns the conversation, the model calls, the consent UI, and the security policy.
- Client. Lives inside the host. Each client holds a one-to-one connection with exactly one server. Three servers means three clients, each isolated. Servers never see the whole conversation and cannot see each other; the host aggregates context and enforces boundaries.
- Server. A separate process, local or remote, that exposes capabilities. A Postgres server. A filesystem server. A wrapper around your internal API.
The isolation is deliberate and it is the most underrated design decision in the protocol. When a server is compromised or merely badly written, the blast radius is bounded by what that one server can see, not by everything the agent knows.
Three primitives, three different bosses
A server can expose three kinds of capabilities, and the useful mental model is who controls each one:
- Tools are model-controlled. The spec says it outright: the language model discovers tools and invokes them on its own, based on the task and the user's prompt. A tool is a function with a JSON Schema for inputs:
run_query,create_ticket,total_revenue. Tools are the write side of MCP, the part where the agent acts. - Resources are application-controlled. Read-only data the host decides to surface, addressed by URI:
schema://sales,docs://onboarding. The model reads them; it does not choose when they appear. - Prompts are user-controlled. Reusable templates the user picks explicitly, like slash commands:
/monthly-review,/triage-bug.
Getting this split right changes how you design a server. Reads belong on resources, where the host controls exposure. Writes belong on tools, where the model decides but the host can demand confirmation. Workflows the user triggers belong on prompts. Most of the MCP servers I have seen that feel wrong are wrong because they put everything behind tools.
The wire format is boring on purpose
Messages are JSON-RPC 2.0, a format older than most of the engineers implementing it. Two transports: stdio for local servers (the host spawns the server as a subprocess) and Streamable HTTP for remote ones. Boring is a feature here. A protocol that needs a clever transport is a protocol nobody adopts.
I built one: 39 lines, one SQLite table
Reading specs is one thing. I wanted to see what actually crosses the wire, so I built a minimal server with the current Python SDK (2.x, using the MCPServer class): one tool, one resource, one prompt, over a tiny in-memory sales table.
from mcp.server.mcpserver import MCPServer
import sqlite3
mcp = MCPServer("sales-demo")
DB = sqlite3.connect(":memory:", check_same_thread=False)
DB.execute("CREATE TABLE sales (region TEXT, month TEXT, revenue REAL, units INTEGER)")
DB.executemany(
"INSERT INTO sales VALUES (?,?,?,?)",
[
("West", "2026-07", 41200.0, 310),
("West", "2026-08", 47850.0, 352),
("East", "2026-07", 38900.0, 295),
("East", "2026-08", 35100.0, 260),
],
)
@mcp.tool()
def total_revenue(region: str) -> float:
"""Sum all recorded revenue for a region. Read-only, no side effects."""
cur = DB.execute("SELECT SUM(revenue) FROM sales WHERE region = ?", (region,))
return float(cur.fetchone()[0])
@mcp.resource("sales://schema")
def schema() -> str:
"""The sales table schema, for the model to reference before querying."""
return "TABLE sales(region TEXT, month TEXT, revenue REAL, units INTEGER)"
@mcp.prompt()
def monthly_review(region: str) -> str:
"""Draft a three-bullet monthly review outline for a region."""
return (
f"Write a 3-bullet monthly review for the {region} region: "
"revenue trend, best month, and one risk to watch."
)
if __name__ == "__main__":
mcp.run()
Then a client over stdio that lists tools, calls the tool, reads the resource, and fetches the prompt. Real output, unedited:
TOOLS ADVERTISED: [('total_revenue', 'Sum all recorded revenue for a region. Read-only, no side effects.')]
CALL total_revenue(region='West') -> 89050.0
RESOURCE sales://schema -> TABLE sales(region TEXT, month TEXT, revenue REAL, units INTEGER)
PROMPT monthly_review(region='East') -> Write a 3-bullet monthly review for the East region: revenue trend, best month, and one risk to watch.
Two honest notes from the build. First, the SDK runs tool functions in a worker thread, so the in-memory SQLite connection needed check_same_thread=False; the first run failed on exactly that, which is the kind of thing you only learn by running it. Second, notice what the client saw at session start: the tool's description string. That string is the model's entire understanding of what the tool does. Keep that in mind for the next section.
What the spec actually requires
The specification is refreshingly blunt about security, and it splits obligations by role. Servers MUST validate all tool inputs, implement access controls, rate-limit invocations, and sanitize outputs. Clients SHOULD prompt for user confirmation on sensitive operations, show the tool inputs to the user before calling (explicitly to avoid malicious or accidental data exfiltration), validate tool results before passing them to the model, and log tool usage for audit.
Read that list again. The protocol standardizes the messages. Everything that makes a deployment safe, the validation, the confirmation UI, the audit log, is an obligation the spec assigns to you. MCP gives you the hooks; it does not do the work.
What MCP does not do
This is the section most explainers skip, and it is the one that matters in production.
It does not vet the servers you install. MCP is a contract format, not a reviewed app store. Anyone can publish a server, and installing one is installing code that your agent will then act on. The MCPInspect academic study (arXiv 2510.16558, presented at DSN 2026) scanned public servers and found 833 vulnerable ones, 18 with suspicious or deliberately misleading tool descriptions. One industry analysis put OAuth adoption among public servers at about 8.5 percent. The ecosystem's trust model is currently "read the code yourself."
Tool descriptions are an attack surface. Remember the demo: the model learns what a tool does from a plain string. Researchers at Invariant Labs showed that a malicious server can embed instructions inside tool descriptions or parameter schemas, a technique called tool poisoning. The agent reads the description at session start as context, and a poisoned one can steer it to exfiltrate data or call other tools. The spec tells clients to treat tool annotations as untrusted unless they come from trusted servers. In practice, "trusted" is doing a lot of work in that sentence.
Every tool costs context. Each tool's name, description, and JSON Schema ships to the model with every request. Ten well-described tools are fine. Two hundred tools, each with a paragraph of description, is a tax on every call and a larger surface for the model to misread. Curate the tool list like you curate a prompt: nothing in it that is not earning its place.
It versions the contract, not the behavior. Capability negotiation handles protocol versions cleanly. Nothing in the protocol tells you that a server update changed what run_query actually does. Pin versions, read changelogs, and treat a server upgrade like a dependency upgrade, because that is what it is.
Should you reach for it?
Reach for MCP when the same data or tools must serve more than one agent, or when you want your capability to work across every MCP-compatible host without bespoke glue. A Postgres server you write once and use from Claude Desktop, your IDE agent, and your nightly pipeline is the canonical win.
Skip it, or at least defer it, when you have one app and one tool. Direct function calling is less moving machinery. Also skip the fantasy that adopting MCP upgrades your agent's judgment. It upgrades your plumbing. The thinking is still yours to build, and the eval harness to prove it.
Key takeaways
- MCP standardizes the last mile between agents and tools: host, client, server; tools, resources, prompts; JSON-RPC 2.0 over stdio or Streamable HTTP.
- The role split is the design insight: one client per server, servers isolated from each other and from the full conversation.
- Assign capabilities by who should control them: reads as resources, writes as tools, user-triggered workflows as prompts.
- A working server is genuinely small. Mine was 39 lines, and the client saw exactly what I advertised, nothing more.
- The spec assigns security obligations to implementers: validate inputs, confirm sensitive calls, show inputs before calling, log everything.
- The gaps are real: unvetted servers, tool-description poisoning (833 vulnerable servers in one study), context cost per tool, and behavior changes that no version handshake will catch.
What is the first tool you would expose to an agent through MCP, and would you let it run without a confirmation step?
Related on Everyday Data Science: Your agent needs an eval harness before it needs more tools · Pruning agent context cuts cost and raises accuracy
Sources
- Model Context Protocol: Tools specification (official spec; tools are model-controlled; security obligations for servers and clients)
- MCP Architecture Explained: Tools, Resources and Prompts (Knit; the who-controls-what framing for the three primitives)
- Anthropic's new standard raises AI privacy, other concerns (TechTarget, 27 Nov 2024; launch date, launch components, early adopters)
- Model Context Protocol (Wikipedia; adoption by OpenAI and Google DeepMind; December 2025 donation to the Agentic AI Foundation under the Linux Foundation)
- MCP Security: Attack Surface, CVEs, and Mitigations (Forkast Learn; MCPInspect findings: 833 vulnerable servers, 18 with misleading descriptions)
- OWASP MCP Security Cheat Sheet (OWASP; tool descriptions and schemas as injection surfaces)
About the writer
Data Scientist & AI Researcher
Data scientist and AI researcher at Pace University. I coined Artificial Frictional Unemployment, and built the first machine learning model for crop yield prediction in Sierra Leone. Author of Understanding Agentic AI. I write about agentic systems and applied ML, with a bias toward what actually works, and who gets left out when it doesn't.
Found this useful? Passing it on to someone who builds is the best way to help the publication grow.
Built something worth sharing? Write it up for us →