The Model Context Protocol is the standard that AI agents use to talk to tools. If you've used Claude Code, Cursor, ChatGPT Desktop, or any modern coding assistant in 2026, you've used MCP whether you noticed or not. Hundreds of MCP servers are public; thousands more are private inside teams.
This is the primer I wished existed when I started building with it. Not a copy of the spec page. The mental model first, then the wire format, then the integration questions that actually matter (security, auth, multi-tenant, where credentials live).
Sections:
- The one-line version
- The architecture
- The three primitives: tools, resources, prompts
- Transport: stdio vs HTTP
- Building your first MCP server
- Security and authentication
- Where credentials actually live
- Common patterns and anti-patterns
- FAQ
The one-line version
MCP is a JSON-RPC protocol that lets an AI host (Claude Desktop, Claude Code, Cursor, ChatGPT) connect to one or more external programs that expose tools (actions the model can take), resources (data the model can read), and prompts (reusable templates).
The everyone-uses-the-same-metaphor analogy: USB-C for AI. Any compliant host plugs into any compliant server, discovers what the server can do, and the model can immediately use it.
In numbers: MCP was announced by Anthropic in November 2024. By March 2026 it had crossed 97 million monthly SDK downloads, up from 100K at launch. In December 2025 Anthropic donated it to the Agentic AI Foundation under the Linux Foundation, making MCP genuinely vendor-neutral · same governance model as Kubernetes.
So when you read "the model can access your database", what's actually happening is: the model decides it needs to query the database, the host translates that into a tool call, the call goes over JSON-RPC to an MCP server that wraps the database, the server runs the query and returns rows, the host feeds the rows back into the model's context.
The architecture
Three participants.
The host is the AI application the user interacts with. It owns the model's context window, decides when to call tools, routes user messages to the model, and feeds tool outputs back. Examples: Claude Desktop, Claude Code, Cursor, custom agent runtimes you build yourself.
Clients are managed by the host. The host creates one MCP client per connected server. Each client maintains a dedicated JSON-RPC connection. You as a developer usually don't write client code; the host does.
Servers are the lightweight programs that expose capabilities. A server wraps something useful: a database, a filesystem, a SaaS API, an internal microservice. Servers can run locally (on the same machine as the host, communicating over stdio) or remotely (cloud-hosted, communicating over HTTP).
The decoupling matters. The same Postgres MCP server you wrote on your laptop can be reused unmodified by anyone running any compliant host. The same Claude Code instance can talk to your team's custom MCP server alongside the public GitHub MCP server alongside a third-party Stripe MCP server. They don't know about each other.
The three primitives: tools, resources, prompts
This is the part most explainers get wrong by lumping everything as "tools". MCP has exactly three primitives, and the distinction matters.
Tools
Tools are executable operations with side effects. When the model calls a tool, something happens in the external world: a row gets inserted, an email gets sent, a PR gets opened, a Stripe charge gets created.
A tool definition looks like:
{
"name": "create_issue",
"description": "Create a new issue in a GitHub repository.",
"inputSchema": {
"type": "object",
"properties": {
"owner": {"type": "string"},
"repo": {"type": "string"},
"title": {"type": "string"},
"body": {"type": "string"}
},
"required": ["owner", "repo", "title"]
}
}
The model sees this and knows: there's a create_issue function I can call with these parameters. When the user asks "create an issue tracking this bug", the model emits a tool call with the appropriate JSON.
Resources
Resources are read-only context data. The model can pull them in, but invoking a resource doesn't change anything in the world.
A resource might be the contents of a specific file, the schema of a database table, the latest commits on a branch, the current state of a Linear project. They're addressed by URIs (file:///path/to/file.md, postgres://db/schema/users, github://repo/owner/file.md).
The split matters because the host can make different security decisions per primitive. Reading a resource is usually safe by default. Calling a tool with side effects usually requires user approval. Conflating them loses that distinction.
Prompts
Prompts are reusable templates the host can surface to the user. They're parameterized text snippets that show up in the host's UI (Claude Desktop's slash menu, Cursor's command palette) and, when invoked, get filled in with arguments and sent to the model.
A summarize_pr prompt might take an owner, repo, and pr_number, fetch the PR via the GitHub MCP server's resources, and ask the model to summarize.
In practice, prompts are the least-used primitive. Most MCP servers ship a dozen tools, half a dozen resources, and zero prompts. Hosts have been slow to expose the prompt UX.
Transport: stdio vs HTTP
MCP supports two transports.
stdio is the default for local servers. The host spawns the MCP server as a subprocess and they communicate over stdin/stdout. Newline-delimited JSON-RPC messages. Fast, secure-by-default (no network), and the right choice for anything that runs on the same machine as the host.
HTTP with Server-Sent Events (SSE) is the right choice for remote servers. The MCP server lives somewhere cloud-hosted; clients connect to it over the network. The 2025 spec evolution moved this toward HTTP/1.1 with SSE for the server-to-client direction, plus regular POSTs for client-to-server.
Practically:
- Local tool that wraps a CLI or a local file system? stdio.
- Wrapping a SaaS API your team owns? HTTP, hosted internally.
- Public SaaS-style MCP server? HTTP, with OAuth.
- Anything in CI? Usually stdio because the runner spawns the server alongside the host.
The Anthropic team has been signaling that HTTP is where the future is, especially for any MCP server that wants to be shared across teams or made publicly accessible. If you're choosing between transports today for a new server, default to HTTP unless you have a specific reason to use stdio.
Building your first MCP server
The easiest path is the official SDK. There's a Python and a TypeScript version. Here's the Python "hello world":
# server.py
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("hello-world")
@mcp.tool()
def add(a: int, b: int) -> int:
"""Add two numbers."""
return a + b
@mcp.resource("greeting://{name}")
def greeting(name: str) -> str:
"""Get a personalized greeting."""
return f"Hello, {name}!"
if __name__ == "__main__":
mcp.run()
Register it with Claude Code by adding to ~/.claude.json:
{
"mcpServers": {
"hello-world": {
"command": "python",
"args": ["/path/to/server.py"]
}
}
}
Restart Claude Code, ask it to use the add tool, watch it work. That's the basic loop. From here it scales to wrapping your own APIs, your databases, your internal services.
The TypeScript SDK is the equivalent if you prefer Node:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
const server = new McpServer({ name: "hello-world", version: "1.0.0" });
server.tool("add", { a: "number", b: "number" }, async ({ a, b }) => ({
content: [{ type: "text", text: String(a + b) }],
}));
await server.connect(new StdioServerTransport());
Security and authentication
The MCP spec is intentionally agnostic about auth at the protocol level, but in practice every real-world MCP server has to authenticate to something. There are three common patterns.
Pattern A: env-var pass-through
The host config passes credentials to the MCP server via environment variables.
{
"mcpServers": {
"github": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-github"],
"env": {
"GITHUB_PERSONAL_ACCESS_TOKEN": "ghp_..."
}
}
}
}
Simple, works everywhere, and has all the problems env vars always have: the token is in the config file (which gets committed), it doesn't rotate, multi-account is impossible, and a leak of the file is a leak of the token.
GitGuardian's 2026 State of Secrets Sprawl found 24,008 unique secrets exposed in public MCP configuration files specifically. That's the failure mode of pattern A at scale.
Pattern B: OAuth at the MCP server
For remote (HTTP) MCP servers, OAuth is the right model. The MCP server itself runs the OAuth flow against the underlying service. The MCP host (Claude Code, Cursor) authenticates to the MCP server, not to the underlying API.
The 2025 MCP spec added support for OAuth 2.1 with Dynamic Client Registration (DCR), which means the first time a host connects, it can register itself as a client without the user manually creating an OAuth app. This is the recommended pattern for any MCP server that wants real-world adoption beyond "paste a token in a config file".
Notion's MCP endpoint (mcp.notion.com) implements DCR and is a good reference example. The user runs one command, a browser opens to Notion's OAuth consent screen, and they're authenticated. No client_id, no client_secret to manage.
Pattern C: credential broker between the host and the MCP server
This is the pattern I use for my own setup. Instead of giving the MCP server raw credentials, the host (Claude Code) is launched under a credential broker like authsome. The MCP server makes whatever API call it normally makes; the broker intercepts the outbound HTTPS, substitutes the real Authorization header for the placeholder, forwards the request.
{
"mcpServers": {
"github": {
"command": "authsome",
"args": ["run", "--", "npx", "-y", "@modelcontextprotocol/server-github"]
}
}
}
The MCP server runs under the broker. The credential never appears in the config file. Refresh, multi-account, audit all become broker-side concerns.
For why this matters for prompt-injection threats specifically, see How prompt injection becomes credential exfiltration.
Where credentials actually live
A typical Claude Code + MCP setup has credentials at three layers:
- The Anthropic API key for talking to Claude itself. Has to be readable by the host.
- MCP server credentials for whatever the server wraps (GitHub PAT, Postgres password, Stripe key, etc.). Has to be readable by the server.
- Per-call credentials if the server proxies requests on behalf of different users (a multi-tenant MCP server).
The cleanest mental model: each layer has its own credential namespace, and you want to minimize how many layers hold raw secrets at any given time. The broker pattern collapses (2) into a single "broker holds it, neither the host nor the server sees it" arrangement. (1) and (3) are still your problem, but they have well-understood patterns.
Common patterns and anti-patterns
What good MCP setups do:
- Pin the server version. Don't track
latest. An MCP server can run arbitrary code in your shell context; the supply-chain risk is real. - One server per concern. A "do everything" MCP server is fragile and hard to scope. Multiple narrow servers are easier to update and easier to permission.
- Prefer HTTP for remote servers. OAuth-based auth, no token in config, multi-tenant clean.
- Read the server's source before installing. Especially for community servers. Five minutes upfront beats a supply-chain CVE.
What good MCP setups don't do:
- Don't commit
~/.claude.jsonwith tokens in it. This is the single most common leak vector. - Don't run unaudited MCP servers as root or in your shell with all your env vars exposed. Sandbox them, or run them under a broker.
- Don't write your own JSON-RPC parser when SDKs exist. The official SDKs handle the spec edge cases.
FAQ
Is MCP the same as function calling?
Function calling (in OpenAI's GPT-4 sense) is a model-side feature: the model emits structured outputs that look like function calls, and the application decides what to do with them. MCP is an application-layer protocol: a standard way to expose tools so any host can discover and call them. The model's tool-use capability is what enables MCP, but MCP is the connector between models and the outside world. They're complementary.
Do I need MCP to use Claude / GPT / Gemini?
No. You can write custom tool definitions per-application without MCP. MCP just standardizes the connector so the same tool works across hosts.
Does every AI host support MCP?
The major ones do or are adding it: Claude Desktop, Claude Code, Cursor, ChatGPT Desktop, VS Code Copilot, Continue, OpenCode. Some older agent frameworks have community MCP integrations.
Is there an MCP server registry?
There's no single official one. The main discovery surfaces are the @modelcontextprotocol GitHub org for first-party servers, a few community lists, and various MCP marketplace sites. Treat the marketplaces with caution; install only from sources you'd trust to run code.
Can MCP servers call other MCP servers?
In principle yes (a server can be a client of another server). In practice this is rare and not what the spec is optimized for. Most setups have a single host orchestrating multiple servers.
What's the difference between an MCP server and a tool plugin?
Functionally similar. MCP standardizes the protocol; most tool-plugin systems are vendor-specific. If you're choosing what to build today, build an MCP server. It works with more hosts and the ecosystem momentum is here.
How do I auth a hosted MCP server in a CI environment?
The patterns are similar to any other CI-auth problem: short-lived tokens via OIDC, GitHub App installation tokens, or service-account credentials stored in your CI's secrets manager. Don't use long-lived PATs in CI for MCP servers any more than you would for any other API.
What about MCP for non-AI use cases?
The protocol is general (it's just JSON-RPC over stdio or HTTP), but the ecosystem is AI-focused. If you're not building for an AI host, a regular API or a regular CLI is probably the right shape.
Summary
MCP is the connector between AI agents and the outside world. Three primitives (tools, resources, prompts), two transports (stdio and HTTP), client-server architecture, JSON-RPC under the hood. Hosts (Claude Code, Cursor, etc.) talk to servers (wrappers around APIs, databases, internal services). The model decides what to use; the protocol carries the calls.
The auth story is the part most teams underestimate. Env-var pass-through works on day one and leaks on day 200. OAuth at the server is the right pattern for remote servers. Credential brokering at the host is the right pattern for local-first workflows.
If you're building with AI agents in 2026 and you're not already using MCP for tool integration, you're either rolling something custom that overlaps 90% with what MCP already does, or you're missing connectivity that your competitors have. The protocol is good, the ecosystem is rich, and the LF governance means it'll outlast any single vendor.
Next steps
Further reading
Top agent proxy tools in 2026: what each one does and what to know before picking one
Eight tools that sit between AI agents and the services they call. Not a comparison post. A walking tour of the category so you know what each is for, what it's good at, and where it'll bite you.
Read postMay 15, 2026AI agent security in 2026: the four threat models you actually need to think about
Prompt injection, credential exfiltration, runaway autonomy, supply chain. What each one looks like in practice, how attacks actually unfold, and which defenses work.
Read postMay 1, 2026Headless agent OAuth: the device code flow explained
How OAuth2 device authorization works, why it's the right pattern for SSH sessions and CI runners, and what the RFC doesn't quite tell you.
Read post