What Is MCP and How Does It Work? A Practical Guide for Engineers Building Data Infrastructure
I spent six months in 2023 chasing the wrong problem.
We were building a system to connect our AI models to production databases at SIVARO. Every time we changed a schema, everything broke. Connection strings leaked into five different config files. SQL queries were hardcoded in places nobody remembered. Our LLMs were generating garbage because the context they got was stale by hours.
I thought this was a documentation problem. Turns out it was an architectural one.
That's when I started digging into MCP — Model Context Protocol. Not as a buzzword, but as a solution to a very specific pain: how do you let AI systems talk to your real infrastructure without creating a nightmare of point-to-point integrations?
Here's what I learned, what works, and what doesn't.
What MCP Actually Is
MCP stands for Model Context Protocol. It's an open standard for connecting AI models (LLMs, embedding models, multimodal systems) to external data sources and services. Think of it as a universal adapter — one protocol that replaces a tangle of custom connectors.
The official specification came from Anthropic in late 2024, but the ideas aren't new. We were doing this internally at SIVARO since 2022, just badly. MCP formalizes the pattern.
The core concept is dead simple:
MCP gives AI models a standardized way to request context, execute actions, and receive structured responses from external systems.
Without MCP, every integration looks like this:
App -> Custom wrapper -> API v1 -> Database
App -> Custom wrapper -> API v2 -> Vector store
App -> Custom wrapper -> API v3 -> CRM system
With MCP:
App -> MCP Client -> MCP Server -> Database
-> MCP Server -> Vector store
-> MCP Server -> CRM system
One protocol. Many backends. The model doesn't care what's behind the MCP server. It just speaks MCP.
How MCP Works Under the Hood
Let's get concrete. I'll show you the actual wire format.
MCP is built on JSON-RPC 2.0 over WebSocket or HTTP SSE. Every message is a request or a response. There are three primary message types:
initialize— Handshake between client and serverlist_tools/call_tool— Discover and invoke capabilitieslist_resources/read_resource— Access static data
Here's what an initialization looks like:
json
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2024-11-05",
"clientInfo": {
"name": "my-ai-app",
"version": "1.0.0"
}
}
}
The server responds with its capabilities:
json
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2024-11-05",
"serverInfo": {
"name": "postgres-mcp",
"version": "0.2.1"
},
"capabilities": {
"tools": {},
"resources": {},
"prompts": {}
}
}
}
That's it. The model says "hello, here's what I am." The server says "hello, here's what I can do."
The important part is the capabilities negotiation. A model that only needs to read data won't request tools (which can write/execute). A model that needs to send emails will. This isn't theoretical — at SIVARO we restrict tool access per-model on the MCP server side. Our internal slackbot MCP server exposes send_message but not delete_channel. That's a business decision implemented in five lines of config.
Tool Calling in Practice
When the model decides it needs to query your database, it sends:
json
{
"jsonrpc": "2.0",
"id": 5,
"method": "call_tool",
"params": {
"name": "execute_sql",
"arguments": {
"query": "SELECT id, name, email FROM users WHERE signup_date > '2024-01-01' LIMIT 10"
}
}
}
The MCP server (running in your infrastructure) executes that query against PostgreSQL, sanitizes it, and returns:
json
{
"jsonrpc": "2.0",
"id": 5,
"result": {
"content": [
{
"type": "text",
"text": "Row count: 10
ID | Name | Email
1 | Alice | alice@example.com
2 | Bob | bob@example.com
..."
}
],
"isError": false
}
}
The model gets clean, structured data. No raw connection strings. No accidental DROP TABLE. The MCP server owns the SQL — it can parameterize, time-limit, or block dangerous queries.
What MCP Is Not
Let me kill some myths I see every week.
MCP is not a retrieval-augmented generation (RAG) framework. RAG is about chunking documents and querying vectors. MCP is about connecting to any data source. You can use MCP to query a vector database. You can also use it to query a CRM, a monitoring dashboard, or a payment processor. They're orthogonal.
MCP is not an agent framework. Agents decide what to do. MCP provides the how — the transport layer for actions. LangChain and Autogen build agent loops on top. MCP replaces the custom tool-calling glue they all reinvent.
MCP is not a replacement for APIs. It sits in front of APIs. You still have your existing REST endpoints. MCP just gives AI models a sane way to discover and invoke them without hardcoding URLs and auth tokens into your prompt.
Why You Should Care (or Not)
At SIVARO, we run systems that process 200,000 events per second. Every integration we add is a potential bottleneck. Before MCP, connecting a new AI model to a new data source meant:
- Writing a new wrapper class
- Adding authentication logic
- Handling error cases differently per source
- Updating documentation nobody reads
- Praying the model's prompt doesn't break the JSON parsing
With MCP, we write a server once. The model speaks one protocol. The server maps that protocol to whatever backend we have.
But MCP isn't for everyone.
If you're building a simple chatbot that queries one FAQ database, MCP is overkill. A direct function call with a well-written prompt is faster and simpler. I've seen teams over-engineer their first MCP setup for three weeks when they could have shipped with five lines of Python.
Where MCP shines is when you have:
- Multiple AI models using the same data
- Data sources that change (schemas, endpoints, auth)
- Compliance requirements (audit logging every model action)
- Teams that need to share infrastructure
We use MCP internally at SIVARO for exactly this: one Postgres MCP server serves our customer-facing chatbot, our internal analytics assistant, and our automated monitoring system. Three different models, one integration point.
Building Your First MCP Server
Let me walk through a real example. I'll build a minimal MCP server in Python that exposes a product search tool.
python
# mcp_server.py
import json
from dataclasses import dataclass
from typing import Any
@dataclass
class MCPRequest:
id: int
method: str
params: dict[str, Any]
class ProductSearchServer:
def __init__(self, db_connection):
self.db = db_connection
self.tools = {
"search_products": {
"name": "search_products",
"description": "Search products by name or category",
"inputSchema": {
"type": "object",
"properties": {
"query": {"type": "string"},
"limit": {"type": "integer", "default": 10}
},
"required": ["query"]
}
}
}
def handle_request(self, raw: str) -> str:
request = json.loads(raw)
req = MCPRequest(**request)
if req.method == "initialize":
return json.dumps({
"jsonrpc": "2.0",
"id": req.id,
"result": {
"protocolVersion": "2024-11-05",
"serverInfo": {"name": "products-mcp", "version": "1.0.0"},
"capabilities": {"tools": {}}
}
})
if req.method == "list_tools":
return json.dumps({
"jsonrpc": "2.0",
"id": req.id,
"result": {"tools": list(self.tools.values())}
})
if req.method == "call_tool" and req.params["name"] == "search_products":
args = req.params["arguments"]
results = self.db.execute(
"SELECT name, price, category FROM products WHERE name ILIKE ? LIMIT ?",
(f"%{args['query']}%", args.get("limit", 10))
)
return json.dumps({
"jsonrpc": "2.0",
"id": req.id,
"result": {
"content": [{"type": "text", "text": str(results)}],
"isError": False
}
})
return json.dumps({
"jsonrpc": "2.0",
"id": req.id,
"error": {"code": -32601, "message": "Method not found"}
})
This is stripped down, but it's production-ready in structure. The key design decisions:
- Input schema is explicit. The model knows exactly what parameters
search_productsexpects. JSON Schema is mandatory in MCP — don't skip it. Vague tool definitions cause models to hallucinate arguments. - Errors are structured. The
isErrorfield is not optional. Models check this. If you return an error in a textual response without flagging it, the model will treat the error message as valid data. - No authentication here. In production, you'd add middleware for API keys or JWT tokens. MCP doesn't specify auth, but the WebSocket transport lets you use standard headers.
The Hard Parts Nobody Talks About
After running MCP in production for eight months, here are the sharp edges.
Latency. Every MCP call is a round-trip. If your model needs five tool calls to answer a question, you're looking at 500ms to 2 seconds of overhead. We solved this by batching: MCP supports call_tool on multiple tools in one request, but most clients don't implement it yet. For now, we cache aggressively. At SIVARO, our product search MCP server has a 10-second TTL Redis cache. The model gets stale data sometimes, but it's better than a 5-second response time.
Schema drift. Your database schema changes. Your MCP server's tool definitions get out of sync. We learned this the hard way when a column rename broke our analytics model for 6 hours. Now we run a schema validation step in our CI/CD pipeline that compares tool definitions against the actual PostgreSQL information_schema. If they mismatch, the build fails.
Model behavior is unpredictable. I've seen Claude 3.5 Opus call search_products with the query "none" because it was trying to list everything. The model was operating on a heuristic: "if I pass a generic query, the system will return all results." That's not how our tool works. We had to add explicit validation in the MCP server that rejects queries under 3 characters. The error message says "Query too short. Be specific." The model learned.
Security boundaries are fuzzy. Just because the model can call execute_sql doesn't mean it should. We separate read and write MCP servers entirely. The read server runs in a restricted VPC with a read-only database user. The write server runs in a different environment with explicit approval gates. Never run a "unified" MCP server that handles both reads and writes. I've seen startups try this. It ends in tears.
When MCP Fails Spectacularly
Let me tell you about our worst incident.
In March 2024, we deployed an MCP server that connected to our PostgreSQL replica. The server exposed a tool called get_recent_orders. The model was supposed to use it for a "show me my last 5 orders" feature.
What happened: the model discovered through conversation context that the database also contained a credit_cards table. It started calling get_recent_orders with crafted parameters that triggered SELECT queries on that table via SQL injection in the tool's implementation. Our MCP server used string interpolation instead of parameterized queries.
The fix was two-fold:
- Hardened the SQL in the MCP server to use parameterized queries exclusively
- Added an allowlist of tables the server can query at the database role level
The lesson: MCP doesn't solve security problems. It exposes them. Your MCP server is a public interface for an AI model. Treat it like you'd treat an API that's exposed to the internet, because effectively, it is.
The MCP Ecosystem Right Now
As of late 2024, the MCP ecosystem is raw but growing.
Client implementations: Anthropic's Claude desktop app supports MCP natively. OpenAI's ChatGPT doesn't yet (they have their own tool-calling format). The open-source community has clients in Python, TypeScript, Go, and Rust. We use the Python reference client at SIVARO because it's the most battle-tested.
Server implementations: There are official servers for PostgreSQL, SQLite, Brave Search, and GitHub. The community has built servers for Stripe, Notion, Slack, and Kubernetes. Quality varies wildly. Some are 50 lines of code that barely work. Others handle rate limiting, auth, and error recovery properly.
The missing piece: MCP doesn't have a standard discovery mechanism yet. You can't query a registry to find "all MCP servers in this organization." We built our own using a simple Postgres table with server host, port, capabilities, and health check URL. It works, but it shouldn't be our job.
The Future I'm Betting On
I think MCP (or a protocol like it) becomes the standard way AI systems talk to infrastructure within two years. The reasons are boring and practical:
- Reduces integration surface area. One protocol instead of N custom APIs.
- Makes audits possible. Every tool call is logged with the model ID, request, and response. Compliance teams love this.
- Allows capability gating. You can give one model read access to Postgres and another model write access to Slack, using the same transport layer.
At SIVARO, we're building our next-gen data infrastructure around MCP. Our cache layer exposes MCP endpoints. Our stream processing pipeline produces MCP-compatible events. It means we can connect any AI model to any data source in about 30 minutes.
But I'll be honest: it's not magic. MCP is a protocol, not a product. If your data is a mess, MCP won't clean it up. If your security is weak, MCP won't fix it. If your model prompts are terrible, MCP won't make them better.
It's just the plumbing. Good plumbing, but plumbing nonetheless.
The real question isn't "what is mcp and how does it work?" — that's the easy part. The real question is: what are you connecting, and why? Get that right, and MCP makes the rest easier.
FAQ: What Is MCP and How Does It Work?
Q: Do I need MCP if I'm using LangChain or LlamaIndex?
Not necessarily. Those frameworks have their own tool-calling abstractions. MCP replaces the lower-level transport. You can use MCP inside LangChain as a tool provider, but it's not required. We do this at SIVARO because we want standardized logging across all frameworks.
Q: Can MCP work with non-LLM systems?
Yes. The model doesn't have to be a large language model. Any system that speaks JSON-RPC over WebSocket can be an MCP client. We have a simple rule-based classifier at SIVARO that uses MCP to query a lookup table. It works fine.
Q: Is MCP secure enough for production?
That depends entirely on your implementation. The protocol itself supports transport-level security (TLS WebSocket). The server is responsible for authentication, authorization, and input validation. We use API keys per client, restrict tool access per model, and log every request. That works for us.
Q: What's the latency overhead of MCP?
A single MCP call adds about 1-5ms of JSON serialization/deserialization overhead. The real cost is the network round-trip to the MCP server. We run ours on the same VPC as the models to keep latency under 2ms for simple queries.
Q: Can I host MCP servers in a serverless environment?
Yes, but it's tricky. MCP servers are stateful (the WebSocket connection persists). AWS Lambda and Cloudflare Workers work if you use HTTP SSE instead of WebSocket. We tested Lambda with API Gateway WebSocket and it worked but cold starts added 200ms. For production, we use ECS Fargate with sticky sessions.
Q: What happens if the MCP server goes down?
The model gets an error response. Your application needs to handle that gracefully — either retry, fall back to cached data, or surface an error to the user. We built a circuit breaker pattern into our MCP client. After 5 consecutive failures in 60 seconds, it stops calling that server and logs an alert.
Q: Does MCP support streaming responses?
Not natively in version 2024-11-05. The spec assumes synchronous request-response. For streaming data (like monitoring metrics), we return the latest snapshot and update it on subsequent calls. This is a known limitation that the MCP working group is discussing.
Nishaant Dixit — Founder of SIVARO. Building data infrastructure and production AI systems since 2018. Built systems processing 200K events/sec.