Model Context Protocol
Every framework had its own way to describe a tool, so every integration was written N times. MCP is the interface that makes it N plus M — a server exposes tools once, and any client can use them.
Standing
Commonly usedEstablished and frequently the right choice, but competing with live alternatives rather than having settled the question.
Adopted quickly and broadly for an interface specification. It solves an integration problem cleanly and leaves the security problem entirely open.
judged as of 2026-09 · what the labels mean
Theory
Giving a model access to a database, a filesystem or an API means describing those capabilities in whatever format the framework expects. Do it for three frameworks and you have written the integration three times.
MCP is a JSON-RPC protocol that puts a standard boundary in the middle. A server exposes capabilities; a client — a model host — consumes them. The integration is written once.
Three kinds of thing a server exposes
Tools. Functions the model may call, with a JSON Schema for their arguments. Model-controlled: the model decides when to invoke one.
Resources. Data the client may read — files, records, documents. Application- controlled: the host decides what to include, and the model does not fetch them on its own.
Prompts. Named templates a user can invoke, typically surfaced as slash commands.
The distinction between the first two is the interesting design choice. Tools are actions with effects and need the model’s judgement; resources are context and should not be. Collapsing them — which most pre-MCP tool interfaces did — is what makes “read this file” and “delete this file” look like the same kind of operation to a model.
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("grimoire")
@mcp.tool()
def search_entries(query: str, limit: int = 5) -> list[dict]:
"""Search the reference entries. The docstring is what the model reads
to decide whether to call this — it is prompt text, not documentation."""
return db.search(query, limit)
@mcp.resource("entry://{slug}")
def read_entry(slug: str) -> str:
"""Fetched by the host, not chosen by the model."""
return db.get(slug).body
mcp.run(transport="stdio")Two transports: stdio for a local subprocess, and HTTP with server-sent events
for a remote one. Local servers are the common case and get their credentials
from the environment they are launched in.
What it is for
- Writing an integration once. Any MCP-speaking client can use it.
- Keeping tools out of the application. A server is a separate process with its own dependencies and its own credentials.
- Local-first access. A filesystem or database server runs on your machine and the data does not leave it.
What it does not do
Authorisation. The protocol describes capabilities; deciding who may invoke which is the host’s problem, and hosts vary in how seriously they take it.
Sandboxing. A server runs with the permissions it was started with. There is no isolation in the specification.
Injection defence. This is the significant one. Tool results are untrusted text that arrives in the model’s context alongside instructions, and so are tool descriptions. A compromised server can rewrite a description to include directives, and the model has no way to distinguish those from the operator’s.