LearnThatStack Ace your next interview

Model Context Protocol (MCP)

MCP spec 2026-07-28, with older revisions marked Checked Sep 2026 50 code snippets

The model

  • MCP is one open protocol between AI apps and outside systems. Wrap a system once as a server, and every MCP host can use it.
  • The host runs one client per server. A server sees only what its own client sends.
  • Servers offer three things. The model calls tools, the app attaches resources, and the user picks prompts.
  • Every message is a JSON-RPC 2.0 request, response or notification. Both transports, stdio and HTTP, carry the same messages.
  • Since 2026-07-28 the core is stateless. Each request carries its own version and capabilities, so any replica can answer.
Hostmodel, UI, consentClientone per serverMCP servertools, resourcesOutside systemAPI, files, DBLISTtools/listtool definitionsCALLapproved calltools/callAPI callresult or isErrorinto context
LIST shows the model which tools exist. CALL runs each time the model picks a tool, usually after the host asks the user.

Hosts, clients & servers

what MCP is and how the parts fit

What MCP is and how it's built

HostclientclientclientfilesGitHubdatabase
one hostThe host makes one client for each server, and each client talks to exactly one server.
Say what MCP isAnthropic's 2024 open standard for connecting AI apps to tools and data.
Say what problem it solvesIntegration cost. M apps and N systems need M + N connectors, not M times N.
Say what the host doesThe AI app the user sees. It holds the chat and asks for permission.
Say why each client gets one serverIsolation. A server never sees other servers or the whole chat.

TipThe official analogy is a USB-C port for AI apps. One connector fits many devices.

MCP next to REST, RAG and A2A

Explain how MCP differs from RESTAn MCP server often wraps a REST API. Clients discover its tools at runtime.
Explain how MCP differs from RAGRAG ranks what to retrieve, and MCP reaches the system. They don't compete.
Explain how MCP differs from A2AMCP connects an agent to its tools. A2A connects agents to each other.
Choose MCP or plain function callingUse function calling inside one app, and MCP when tools cross a process or team boundary.

TrapMCP doesn't replace function calling. The host still turns MCP tools into the model's own tool format.

Tools, resources & prompts

the three things a server offers

Who decides when each is used

A toolLet the model take an action
A resourceAttach read-only data as context
A promptGive the user a ready-made workflow
decides when it's useddoesn't decide

TipPut anything with side effects in a tool, so the host can ask the user first.

TrapSampling, roots and elicitation are client features. Servers don't offer them.

Resources

Read data by its URIresources/readA URI like file:///app/schema.sql. The host decides what to attach.
Fill in a URI templateresources/templates/listcompletion/completeRFC 6570 templates, like repo://{owner}. Completion suggests values.
Watch a resource for changessubscriptions/listenUpdates carry only the URI. Before 2026-07-28, use resources/subscribe.

TipThink of a resource as GET and a tool as POST.

TrapSome hosts ignore resources. If the model must fetch the data itself, ship a tool too.

Prompts

Fetch a prompt with its argumentsprompts/listprompts/getIt returns messages, each with a role and content.
Show prompts to usersAs slash commands in Claude Code, or menu items in Claude Desktop.

TipA prompt can embed resources, so one pick pulls in files and instructions.

Messages & versions

JSON-RPC, first contact and notifications

Connect and agree on a version

Start talking to a serverSend your first real request. Its _meta carries your version and capabilities.
Learn what a server supports firstserver/discover
Handle a version the server rejectsUnsupportedProtocolVersionErrorThe error lists the versions the server supports. Retry with one.
Connect to a server on 2025-11-25initializenotifications/initializedSend initialize, get the reply, then send initialized.

TipA version is a date like 2026-07-28. It changes only for breaking changes.

Notifications and long calls

Tell clients the tool list changednotifications/tools/list_changedThe client calls tools/list again.
Report progress on a long callnotifications/progressKeyed to the client's progressToken. The value must only go up.
Cancel a requestnotifications/cancelledOn stdio, send it with the requestId. Over HTTP since 2026-07-28, close the stream instead.
Run a job that outlives the connectiontasks/getUse the Tasks extension. You get a task id back and poll it.

TipSince 2026-07-28, change notices reach only clients with an open subscriptions/listen stream.

TrapNothing acknowledges a notification. A missed one is lost, and a cancelled request may still reply.

stdio & HTTP transports

how the messages travel

Pick a transport

Run a server on the user's machinestdio. The host starts it as a child process, off the network.
Serve many users from one deploymentStreamable HTTP. You take on OAuth, uptime and tenant isolation.

TipA local server runs as your OS user. A remote one checks OAuth 2.1 tokens.

TrapHTTP+SSE is deprecated. To reach an old server, POST first. Fall back to GET on a 400, 404 or 405 with no modern error.

stdio rules

Log from a stdio serverprint("msg", file=sys.stderr, flush=True)console.error("msg")
Launch it reliably from a hostUse absolute paths, and pass secrets as env vars.

TipEach message is one line of JSON-RPC. Flush every write, or the client seems to hang.

TrapOne stray print() on stdout corrupts the stream, and the client drops the connection.

Streamable HTTP

Send a messagePOST /mcpMCP-Protocol-Version: 2026-07-28One endpoint. The server answers with JSON or an SSE stream.
Let gateways route without parsing JSONMcp-Method: tools/callMcp-Name: get_weather
Keep a sessionMcp-Session-IdRemoved in 2026-07-28. Pass state as handles in tool arguments.
Scale out behind a plain load balancerAny replica answers any POST, since 2026-07-28 has no sessions.
Say what broke when 2025 servers went statelessSubscriptions, list changes and server requests. Progress still worked.

TrapA proxy that strips Mcp-Method or Mcp-Name gets every request rejected with HTTP 400.

Build & connect a server

SDKs, tests, hosts and the registry

Python server

from mcp.server import MCPServermcp = MCPServer("demo")@mcp.tool()def add(a: int, b: int) -> int:hints become the schema """Add two integers."""the description return a + bmcp.run()or transport="streamable-http"
Install the SDKpip install "mcp[cli]"

TrapSDK v2 renamed FastMCP to MCPServer. Old mcp.server.fastmcp imports fail, so pin mcp<2 until you migrate.

TypeScript server

Install the SDKnpm install @modelcontextprotocol/server zod
Register a toolserver.registerTool(name, config, handler)
Serve over stdioawait server.connect(new StdioServerTransport())

TipThe official codemod rewrites v1's @modelcontextprotocol/sdk imports.

Trapv2 needs Zod 4. A Zod 3 range installs cleanly, then fails at runtime.

Test a server

Try a server by handnpx @modelcontextprotocol/inspector node build/index.jsNo model in the loop. Add --cli for scripted checks.
Test the protocol in one processInMemoryTransport.createLinkedPair()A real client and server, with no process or socket.
Test end to endSpawn the built server over stdio and drive it with an SDK client.

TipIf a tool fails in the Inspector, the bug is in your server, not in the host or the model.

TrapcreateLinkedPair connects only 2025-era instances. To test 2026-07-28 in one process, use createMcpHandler.

Add a server to Claude

Add a local server to Claude DesktopAdd it to mcpServers in Settings, Developer, Edit Config. Then quit the app fully.
Add a remote server to Claude DesktopPaste its URL on the Connectors page in Settings.
Add one to Claude Code, just for youclaude mcp add <name> -- <command>The default scope. It wins over project and user.
Share one with the teamclaude mcp add -s project <name> -- <command>It goes in .mcp.json. Use ${VAR} for keys.
Use one in all your projectsclaude mcp add -s user -t http <name> <url>

TipIf a server doesn't show up, read the mcp*.log files in ~/Library/Logs/Claude on a Mac.

TrapClaude Desktop doesn't inherit your shell PATH. Give command an absolute path.

Tool schemas & errors

tools the model can pick and use

Define a tool

Name and describe a toolName the action, like search_issues. Say when to use it, and when not.
Shape its parametersFew and flat. Use enum and ranges instead of prose.
Size what it returnsThe small useful answer, not the raw API payload.
Hint how a tool behavesreadOnlyHintdestructiveHintidempotentHintPlus openWorldHint. Destructive and open-world default to true.

TrapNothing verifies a hint. A hostile server can mark a destructive tool read-only to slip past auto-approval.

Tool errors

Report a failed API call"isError": trueReturn it in a normal result, so the model can read it and retry.
Reject arguments that fail the schemaAlso isError, since 2025-11-25, so the model can fix them.
Reject an unknown tool or method-32602-32601JSON-RPC errors. The host handles them, and the model rarely sees them.

TrapError text reaches the model. Leave out stack traces, secrets and internal paths.

Server-to-client requests

when a server needs input, a model or folders

Multi round-trip requests

Ask the client for input mid-callInputRequiredResultThe client answers by re-sending the call with inputResponses.
Keep progress between tripsrequestStateAn opaque string the client echoes back unchanged.

TipMRTR replaced every server-to-client request, including elicitation, sampling and roots/list.

TrapThe client can change requestState. When it affects access, protect it, for example with an HMAC.

Elicitation

Ask the user for missing inputelicitation/createA message and a flat schema of simple fields. Added in 2025-06-18.
Handle the user's replyIt's accept, decline or cancel. A decline isn't a cancel.
Ask for a password or tokenUse URL mode, from 2025-11-25. Form mode must never ask for secrets.

TrapThe call fails unless the client declared the elicitation capability.

Sampling and roots

Borrow the host's modelsampling/createMessageDeprecated in 2026-07-28. New servers call a model API directly.
Explain why a person approves itIt spends the user's tokens, and a hostile prompt can steal context.
Support sampling safely in a hostAllow-list models, treat includeContext as none and clamp maxTokens.
Tell a server which folders to useroots/listDeprecated. New servers take paths as tool parameters or config.

TipRoots don't enforce anything. The server must check paths, and real isolation needs a sandbox.

Auth & security

OAuth, tokens and attacks on agents

The OAuth 2.1 flow

Find the authorization server/.well-known/oauth-protected-resourceThe 401 response points here. It lists authorization_servers.
Get a client IDPre-registered, then a Client ID Metadata Document, then dynamic registration.
Get the user's consentAuthorization code with PKCE, plus resource to bind the token to this server.
Send the tokenAuthorization: Bearer <token>On every request, over HTTPS. Never in a query string.

TipThe MCP server is only a resource server. It checks tokens and never issues them.

TrapDon't rely on dynamic client registration. 2026-07-28 deprecated it, and many enterprise identity providers disable it.

Token audience and confused deputies

Bind a token to one serverSend RFC 8707 resource. The server rejects tokens whose aud isn't itself.
Call a downstream APIUse the server's own credentials, for example through token exchange.
Stop a confused deputy in an OAuth proxyGet consent per dynamically registered client, and match redirect_uri exactly.

TrapNever forward the client's token downstream. It skips the audience check, so one leak opens every service.

Prompt injection and poisoned tools

Explain indirect prompt injectionTool output lands in context, and the model can't tell data from instructions.
Spot the riskiest sessionPrivate data, untrusted content and a way to send data out, together.
Limit what an injection can doPer-session server allow-lists, approval for writes, and an egress allow-list.
Spot tool poisoningCommands hidden in a tool's description or schema, behind a friendly name.
Catch a rug pullHash each approved definition. If it changes later, ask the user again.

TipShow users each tool's full description, and treat it as untrusted data.

TrapTelling the model to ignore instructions in tool results is not a control. Enforce limits in code.

Secure a remote server

Block DNS rebindingCheck Origin and answer 403 if it's bad. Bind local servers to 127.0.0.1.
Check every tokenVerify signature, expiry and audience. Answer 401 when one fails.
Hand out a state handleMake it random and bound to one user. Holding one isn't authentication.

TrapA valid token says who called, not what they may touch. Check permissions inside each tool.

Run MCP at scale

tenants, gateways and production debugging

Tenants, gateways and proxies

Find the tenantFrom the verified token's claims, never from a tool argument.
Give each tenant its own toolsVary tools/list by each request's auth, and key caches by tenant.
Give agents tools from 50 serversOne gateway in front, with a search tool that returns full schemas on demand.
Stop tool names clashinggithub__create_issuePrefix each tool with its server's name.
Chain servers through a proxyForward sampling and elicitation up to a person. Add your own depth limit.

TipTrack how often the model picks the right tool. Latency alone won't show bad picks.

TrapOne query that misses its tenant filter leaks data across customers.

Debug in production

Trace one failing callLog the JSON-RPC id, method, HTTP status and instance on both sides.
Find the failing layerA 5xx is transport, an error is protocol, and isError is the tool.
Send log lines to the clientnotifications/messageDeprecated in 2026-07-28. Use stderr and OpenTelemetry instead.

TipCommon causes are proxy idle timeouts, expired tokens and, on 2025-era servers, lost sticky routing.

Spec versions & migration

what each revision changed

Revisions, extensions and deprecation

Say what 2025-03-26 addedStreamable HTTP, OAuth 2.1, tool annotations and batching.
Say what 2025-06-18 changedIt added elicitation and structured output, and removed batching.
Say what 2026-07-28 changedA stateless core with no handshake or sessions, plus MRTR and extensions.
Ship an optional featureA vendor-prefixed extension, like io.modelcontextprotocol/tasks.
Retire a core featureWrite a SEP, a spec change proposal. The feature stays at least 12 months, or 90 days for a security risk.

Migrate to 2026-07-28

Serve old and new clientsRequests with modern _meta go stateless. An initialize gets the old path.
Drop sessionsRemove Mcp-Session-Id. Read identity from the token, and state from a shared store.
Move what the server pushedChange notices move to subscriptions/listen, and server requests to MRTR.

TrapHandlers that aren't idempotent break. Any retry can land on a different replica.

Found this useful? Pass it on.
Pro · $10/mo

This sheet stays free. With Pro, you can:

  • Every answer on this sheet. Read the full answer to all 56 questions it links, not just the 7 free ones.
  • My sheet. The cards you star and the ones you miss in Quiz me, from every sheet, on one page to quiz and print.
  • PDFs. Download this sheet and My sheet, laid out for A4, with every question a link to its answer.
  • The rest of Pro. Every answer in the question library, and 1,000 AI credits a month.