The Model Context Protocol (MCP) is an open, JSON-RPC based standard that lets a host application expose structured context, tools, and prompts to a language model through a uniform client-server interface. In short, what is model context protocol but a USB-C port for AI integrations—instead of writing custom glue code for every data source, you implement one server and any MCP-aware client can connect.
How MCP works
Core architecture
MCP separates three roles: the host (the LLM-powered app, e.g., an agent loop), the client (the in-process connector that speaks MCP), and the server (the external process or service that exposes capabilities). The host owns the model call; the client handles protocol framing; the server owns the integration with a database, filesystem, API, or internal tool.
This separation means a server author never imports your agent framework, and your agent framework never links against the server’s native dependencies. Communication happens over a transport—typically stdio for local subprocesses or HTTP+SSE for remote servers.
Primitives
MCP defines four capability types:
- Resources: read-only data blobs or text the server can surface (e.g., a file, a DB row, a config snippet). They are addressed by URI.
- Prompts: parameterized message templates the server recommends to the model.
- Tools: functions the model can call, with JSON Schema–defined inputs and structured outputs.
- Sampling: a server-initiated request for the host to run a model inference (used for recursive summarization or decision-making inside the server).
A server advertises these via initialize and list_* RPC methods. The host decides what to present to the model based on policy.
Transport and lifecycle
The handshake is explicit:
- Client sends
initializewith protocol version and capabilities. - Server responds with its own version and supported primitives.
- Client sends
initializednotification. - Normal requests flow.
Over stdio, messages are newline-delimited JSON. Over HTTP, the client posts JSON-RPC to a URL and receives server pushes via SSE. Both sides can cancel in-flight requests with notifications/cancelled.
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "query_orders",
"arguments": { "customer_id": "c_123", "limit": 10 }
}
}
Why MCP matters
Decoupling integrations
Before MCP, every agent framework shipped its own tool abstraction. A Postgres tool written for LangChain didn’t plug into a custom Go agent. MCP makes the server the unit of reuse: write the Postgres MCP server once, and any host—Python, TypeScript, Rust—can consume it.
Security and consent
The host retains control. A server may advertise a tool, but the client can refuse to expose it to the model, require human approval per call, or sandbox the subprocess. Because servers run as separate processes (or behind authenticated HTTP), a compromised tool doesn’t inherently leak the model key.
Ecosystem effects
A common protocol lets tool authors target one spec instead of N frameworks. It also lets enterprises audit context exposure: the set of MCP servers a host mounts is a concrete inventory of what the model can touch.
If you route model calls through a single OpenAI-compatible endpoint such as n4n.ai, which fronts 240+ models and handles fallback, your MCP host can stay model-agnostic while the gateway deals with provider quirks. The protocol doesn’t care which backend produced the tokens.
A concrete example
Suppose you want your agent to read repo files and run a lint check. Instead of embedding fs logic in your agent, you mount two MCP servers.
# terminal 1: filesystem server
npx -y @modelcontextprotocol/server-filesystem ./src
# terminal 2: a hypothetical lint server (illustrative)
python -m mcp_lint_server --root ./src
A minimal Python host using the official SDK:
from mcp import ClientSession, StdioServerParameters
import asyncio
async def main():
fs_params = StdioServerParameters(
command="npx",
args=["-y", "@modelcontextprotocol/server-filesystem", "./src"]
)
async with ClientSession(fs_params) as fs:
await fs.initialize()
tools = await fs.list_tools()
# tools contains 'read_file', 'list_directory', etc.
res = await fs.call_tool("read_file", {"path": "main.py"})
print(res.content[:200])
asyncio.run(main())
The host now has a read_file tool it can pass to the model in the standard tool-calling format. When the model emits a read_file invocation, the host forwards it to the MCP client, which serializes the JSON-RPC call to the subprocess. The file contents come back as a resource the model can reason over. No custom parsing, no framework-specific decorator.
Common misconceptions
MCP is not a model training protocol
It carries context at inference time. It does not define fine-tuning, LoRA adapters, or weight updates. If you see “context protocol” and think embeddings training, that’s RAG pipeline territory, not MCP.
MCP is not tied to a single vendor
Although early implementations and the spec emerged from Anthropic, the protocol is open and has community servers for GitHub, Slack, SQL, and more. A TypeScript server runs against a Python host without permission from any model provider.
MCP does not replace RAG
Retrieval-augmented generation is a strategy; MCP is a transport. You can absolutely back an MCP resource with a vector search over your knowledge base. The protocol standardizes how the retrieved chunk reaches the model, not how you embed or index.
MCP is not a query language
It is not SQL, not GraphQL, not Cypher. It’s a remote procedure call surface with typed tools and URI-addressed resources. You won’t write “SELECT * FROM mcp”, you call tools/call with a schema-validated argument object.
MCP is not only for local subprocesses
The stdio transport is common for dev, but the HTTP+SSE binding lets you run a managed MCP server in your VPC and have multiple hosts connect. Authentication is up to the deployment—the spec defines OAuth 2.0 as a recommended flow for remote servers.
Building a server: what you actually implement
A server is a loop that reads JSON-RPC, dispatches to handlers, and writes responses. Using the Python SDK:
from mcp.server import Server
import mcp.types as types
app = Server("demo")
@app.list_tools()
async def list_tools() -> list[types.Tool]:
return [types.Tool(
name="ping",
description="Returns pong",
inputSchema={"type": "object", "properties": {}}
)]
@app.call_tool()
async def call_tool(name: str, args: dict) -> list[types.Content]:
if name == "ping":
return [types.TextContent(type="text", text="pong")]
raise ValueError("unknown tool")
if __name__ == "__main__":
app.run(transport="stdio")
That’s the entire surface for a trivial tool. The client side handles serialization, lifecycle, and capability negotiation. You focus on the integration logic.
When to adopt it
If you have one agent and one data source, a direct function call is simpler. MCP earns its keep when any of these hold:
- You maintain tools consumed by multiple host processes or languages.
- You want to isolate untrusted integrations in a separate process.
- You need an auditable boundary of what context the model can access.
- You plan to swap model providers without rewriting tool adapters.
What is model context protocol in practice? A boring, well-specified RPC layer that removes the N×M integration tax between models and tools. That boredom is the feature.
Key takeaways
- MCP is a client-server JSON-RPC protocol with resources, prompts, tools, and sampling.
- The host keeps model and approval control; servers are interchangeable.
- Use stdio for local, HTTP+SSE for remote; both speak the same messages.
- It complements RAG and inference gateways; it does not replace them.
- Adopt it when integration breadth or isolation matters more than a ten-line inline function.