Why MCP exists
Before MCP, every AI application needed a custom integration for every tool it wanted to use. Ten apps × ten tools meant up to a hundred one-off integrations. MCP replaces that with one standard interface: a tool exposes itself once, and any MCP-capable client can use it.
Every connection is bespoke code: separate auth, separate formats, separate maintenance.
A tool exposes one MCP interface. Every MCP-capable app can use it — no custom glue code per pairing.
Core MCP building blocks
Three architecture roles (indigo) carry the conversation, and three capability types (teal) are what servers offer.
The AI application the user actually interacts with — an AI chat app, an IDE, or an agent product.
The component inside the host that speaks the MCP protocol. A host runs one client per server it connects to.
tools/call messages.The service that exposes tools, resources, and prompts. It can wrap a database, an API, a filesystem — anything.
Actions the AI can call: “search files”, “create ticket”, “query database”. Each tool has a name, description, and input schema.
create_ticket(title, priority)Readable context and data: files, documents, records, or API-backed content the AI can read but not change.
file:///reports/q2.mdReusable prompt templates the server exposes, so common workflows are one click away in the host UI.
What methods does MCP use?
MCP messages are JSON-RPC style: a method name, params, and a matching result. Methods come in groups — discovery, tools, resources, prompts — plus notifications for events like “the tool list changed”.
Purpose: ask the server which tools exist. Called by: the client. Returns: tool names, descriptions, and input schemas.
Purpose: execute one tool with arguments. Called by: the client (when the model decides to act). Returns: content blocks plus an isError flag.
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "search_files",
"arguments": { "query": "quarterly report" }
}
}
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"content": [
{ "type": "text", "text": "Found 3 matching files: …" }
],
"isError": false
}
}
Purpose: discover readable data. Called by: the client. Returns: a list of resources with URIs, names, and MIME types.
Purpose: read one resource by URI. Called by: the client. Returns: the content (text or binary) with its MIME type.
{
"jsonrpc": "2.0",
"id": 3,
"method": "resources/read",
"params": {
"uri": "file:///reports/q2-summary.md"
}
}
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"contents": [
{
"uri": "file:///reports/q2-summary.md",
"mimeType": "text/markdown",
"text": "# Q2 Summary\n…"
}
]
}
}
Purpose: discover available prompt templates. Called by: the client. Returns: template names, descriptions, and accepted arguments.
Purpose: fetch one template, filled in with arguments. Called by: the client. Returns: ready-to-use chat messages.
{
"jsonrpc": "2.0",
"id": 4,
"method": "prompts/get",
"params": {
"name": "summarize_ticket",
"arguments": { "ticketId": "T-1042" }
}
}
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "Summarize ticket T-1042 for a customer update."
}
}
]
}
}
Purpose: learn the server’s identity, capabilities, and instructions — replacing the old initialize handshake. Called by: the client, any time. Returns: server info and capabilities, with cache metadata so clients and gateways don’t have to re-ask constantly.
Servers can emit events such as notifications/tools/list_changed so clients know to refresh their cached lists.
{
"jsonrpc": "2.0",
"id": 1,
"method": "server/discover",
"params": {
"_meta": {
"io.modelcontextprotocol/protocolVersion": "2026-07-28",
"io.modelcontextprotocol/clientInfo": {
"name": "example-client",
"version": "1.0.0"
}
}
}
}
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"serverInfo": { "name": "docs-server", "version": "2.1.0" },
"capabilities": {
"tools": { "listChanged": true },
"resources": { "listChanged": false },
"prompts": { "listChanged": false }
},
"_meta": {
"io.modelcontextprotocol/cache": {
"ttlMs": 300000,
"cacheScope": "server"
}
}
}
}
Transport mechanisms
MCP defines the message format — but messages still need a way to travel between client and server. That carrier is the transport.
The server runs locally as a child process and talks over standard input/output. Great for developer tools, desktop apps, and IDEs. Simple and private by default — but not suitable for public internet APIs.
The server is remote or web-hosted. Requests go over HTTP POST, and responses can stream. The right choice for hosted services, gateways, cloud deployments, and enterprise systems. Earlier versions layered sessions on top; the 2026-07-28 direction makes it stateless.
Earlier MCP setups used HTTP + Server-Sent Events with a separate endpoint per direction. Streamable HTTP became the primary HTTP transport direction; SSE-style setups remain mostly for backwards compatibility.
Old MCP vs new MCP 2026-07-28
The 2026-07-28 release candidate introduces a stateless-first protocol core and several production-oriented changes. This is the most important shift since MCP launched — it changes how clients connect, how requests are routed, and where session state lives.
Details below reflect the release candidate and may change before final release. Support will vary by SDK, client, and server.
initialize + notifications/initialized handshake before any workMcp-Session-Id carried on every request_metaserver/discover method NewMcp-Method and Mcp-Name headers for routing and rate limiting Gateway friendlyttlMs, cacheScope Gateway friendlyio.modelcontextprotocol/tasks New// 1. Client must open a session first POST /mcp { "jsonrpc": "2.0", "id": 1, "method": "initialize", "params": { "protocolVersion": "2025-06-18", "clientInfo": { "name": "example-client", "version": "1.0.0" }, "capabilities": { } } } // 2. Server responds and assigns a session HTTP/1.1 200 OK Mcp-Session-Id: 8f3c-a1d2-77b9 // 3. Client confirms, then must echo the session ID forever after POST /mcp Mcp-Session-Id: 8f3c-a1d2-77b9 { "jsonrpc": "2.0", "id": 2, "method": "tools/call", "params": { "name": "search_files", "arguments": { "query": "roadmap" } } }
// No handshake. No session. Headers tell gateways everything. POST /mcp MCP-Protocol-Version: 2026-07-28 Mcp-Method: tools/call Mcp-Name: search_files { "jsonrpc": "2.0", "id": 1, "method": "tools/call", "params": { "name": "search_files", "arguments": { "query": "roadmap" }, "_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28", "io.modelcontextprotocol/clientInfo": { "name": "example-client", "version": "1.0.0" }, "io.modelcontextprotocol/clientCapabilities": { "elicitation": { } } } } }
Filesystem-boundary hints move out of the core; clients pass relevant context with requests instead.
Server-initiated model calls leave the core; the spec points to replacement patterns outside the stateless flow.
Protocol-level logging is deprecated in favor of standard observability on the server and gateway side.
Why stateless MCP matters
When every request is self-contained, the server doesn’t need to remember who you are between requests. That one property simplifies almost everything about running MCP in production.
No session pinning — any request can go to any server instance.
Load balancers stop tracking sticky sessions and just distribute traffic.
Gateways route on headers like Mcp-Method without opening the body.
Add or remove instances freely — there is no session state to migrate.
No expired or lost sessions; a restarted server doesn’t break clients.
Auth, rate limits, and allow/deny rules can be applied to each request on its own.
Any request → any instance. Nothing to remember in between.
The MCP gateway view
In larger deployments, a gateway sits between AI clients and many MCP servers. The stateless core and routing headers in 2026-07-28 are designed to make this layer practical.
One place to verify identity before anything reaches a server.
Throttle per client, per method, or per tool using the new headers.
Block dangerous tools centrally via Mcp-Name — no body parsing.
Fan requests out to many MCP servers behind one endpoint.
Metrics, traces, and logs for every tool call passing through.
Cache tool and resource lists using ttlMs / cacheScope.
Record who called which sensitive tool, when, and with what result.
Security and authorization
MCP can expose powerful actions — deleting files, writing to databases, sending messages. Remote servers should use strong authentication; OAuth/OIDC enables delegated access with explicit scopes; and the 2026-07-28 spec hardens authorization behavior. Sensitive tools may require stricter scopes or step-up approval.
Never expose dangerous tools without authentication, input validation, logging, and user consent.
Tools can read data and take real actions. Treat every exposed tool as production attack surface with clear permissions.
Local and stdio-based servers assume a trusted machine. Putting one behind a public URL without auth is an open door.
Treat tool arguments as untrusted input — and treat tool results as untrusted before they flow back into the model’s context.
Narrow OAuth scopes, read-only credentials where possible, and step-up approval for destructive operations.
Practical examples
Pick a use case to see what an MCP server for it might expose — and what to watch out for.
search_files · read_file · list_directory{
"result": {
"tools": [
{
"name": "search_files",
"description": "Search names and contents in allowed folders",
"inputSchema": {
"type": "object",
"properties": {
"query": { "type": "string" }
},
"required": ["query"]
}
},
{
"name": "read_file",
"description": "Read one file by path",
"inputSchema": {
"type": "object",
"properties": {
"path": { "type": "string" }
},
"required": ["path"]
}
}
]
}
}
run_query · list_tables · describe_table{
"result": {
"tools": [
{
"name": "run_query",
"description": "Run a read-only SQL query (SELECT only)",
"inputSchema": {
"type": "object",
"properties": {
"sql": { "type": "string" },
"limit": { "type": "number" }
},
"required": ["sql"]
}
}
]
}
}
create_issue · search_issues · get_pull_request{
"result": {
"tools": [
{
"name": "create_issue",
"description": "Create an issue in a project",
"inputSchema": {
"type": "object",
"properties": {
"title": { "type": "string" },
"body": { "type": "string" },
"labels": {
"type": "array",
"items": { "type": "string" }
}
},
"required": ["title"]
}
}
]
}
}
search_tickets · create_ticket · add_note{
"result": {
"tools": [
{
"name": "search_tickets",
"description": "Search support tickets by text and status",
"inputSchema": {
"type": "object",
"properties": {
"query": { "type": "string" },
"status": {
"type": "string",
"enum": ["open", "pending", "solved"]
}
},
"required": ["query"]
}
}
]
}
}
list_services · get_logs · restart_service{
"result": {
"tools": [
{
"name": "restart_service",
"description": "Restart a service (requires approval)",
"inputSchema": {
"type": "object",
"properties": {
"serviceId": { "type": "string" },
"reason": { "type": "string" }
},
"required": ["serviceId", "reason"]
}
}
]
}
}
Quick glossary
Migration checklist: older MCP → 2026-07-28
If you maintain an MCP client, server, or gateway, this is the rough order of operations. Tick items off as you plan.