Vendor-neutral guide to the open protocol that connects AI applications to tools and context: host/client/server roles, when to use tools vs. resources vs. prompts, resource catalogs, tool descriptions as the selection signal, JSON-RPC error codes vs. isError tool execution errors, stdio vs. Streamable HTTP, project vs. user server scoping, OAuth 2.1 and the MCP threat model. Spec baseline: revision 2026-07-28.
The Model Context Protocol (MCP) is an open, vendor-neutral protocol that standardizes how AI applications discover and use external tools, data (resources) and prompt templates. Messages are JSON-RPC 2.0 over a pluggable transport (stdio or Streamable HTTP). Analogy: a "USB-C port for AI". It defines the connector, not what the device does with it.
MCP standardizes how AI applications connect to tools and context. It doesn't determine how your agent reasons or orchestrates those tools. Started at Anthropic (Nov 2024), it's now governed by the Agentic AI Foundation under the Linux Foundation and implemented across hosts, frameworks and clouds.
| Problem before MCP | What MCP changes |
|---|---|
| Every data source needed its own custom integration per AI app | Write a server once; every MCP host can use it |
| M × N integration matrix (M apps × N tools) | M + N: each app implements a client once, each tool a server once |
| Tool schemas, auth and transport reinvented per vendor | Shared discovery (*/list), invocation (tools/call), transports and OAuth profile |
| User context (files, tickets, DBs) hard to bring to the model safely | Standard resources and consent hooks owned by the host |
| When | Milestone (know the shape) |
|---|---|
| Nov 2024 | Open-sourced with spec, SDKs and reference servers |
| 2025 | Streamable HTTP replaces HTTP+SSE (2025-03-26); OAuth 2.1 profile matures (2025-06-18); tasks, server identity, extensions (2025-11-25) |
| Late 2025 | Donated to the Agentic AI Foundation (Linux Foundation) |
| 2026-07-28 | Stateless revision: no initialize handshake, per-request _meta, server/discover; roots, sampling and logging deprecated |
No. It's an open spec under neutral governance. Any compliant host (chat apps, IDEs, coding CLIs, agent frameworks) can use any compliant server, with any model.
| Role | What it is | Responsibilities | Examples |
|---|---|---|---|
| Host | The AI application the user interacts with | Runs the model and agent loop; creates clients; owns consent, policy and what the model sees; aggregates context across servers | Desktop chat apps, coding CLIs, IDEs, a framework-built agent |
| Client | Connector inside the host, one per server | Speaks the protocol to exactly one server: transport, version/capability metadata, request IDs, routing results back | ADK McpToolset connection; an IDE's per-server connection |
| Server | Program exposing capabilities over MCP | Advertises and executes tools, serves resources, provides prompts; validates input; enforces access control | Filesystem, Git hosting, Postgres, ticketing SaaS, managed cloud data |
"Who decides whether this tool call runs?" → the host (often via a human-in-the-loop prompt), not the server and not the model.
"The client is the end-user app." In MCP vocabulary the host is the app; the client is the per-server connector inside it.
Servers expose three primitives. The key distinction is who controls each one.
| Primitive | Control model | Purpose | Discovery / use | Typical UI |
|---|---|---|---|---|
| Tools | Model-controlled (host-gated) | Executable actions with side effects or computation | tools/list → tools/call | Model decides; host may ask the user to approve |
| Resources | Application-controlled | Context/data identified by URI (files, schemas, records, catalogs) | resources/list, resources/templates/list → resources/read | App attaches them (file picker, @-mention, auto-include) |
| Prompts | User-controlled | Reusable, parameterized message templates and workflows | prompts/list → prompts/get | Slash commands, menu items |
| You want to… | Use | Why |
|---|---|---|
| Let the model take an action or run a parameterized query | Tool | Model decides when; side effects gated by the host |
| Give the model read-only context chosen by the app or user | Resource | URI-addressable, no side effects, app decides when to attach |
| Show the agent what content exists before it explores | Resource catalog | One read replaces many exploratory list/search tool calls |
| Offer a tested, repeatable workflow a human triggers | Prompt | Server-owned template, invoked explicitly (e.g. /review-pr 42) |
name, description, inputSchema (JSON Schema); optional title, outputSchema, annotations, icons.content (text, image, audio, resource links) and/or structuredContent (matches outputSchema), plus isError.readOnlyHint, destructiveHint, idempotentHint, openWorldHint) are hints only. Never base tool-use decisions on annotations from untrusted servers.Move prompt engineering into the server: the domain owner ships a tested template (e.g. /review-pr {pr_number}) and fills context server-side. prompts/get returns messages ready to inject.
Client-side features (2026-07-28): Elicitation is active (§6). Roots and Sampling are deprecated.
Without a catalog, an agent burns several turns calling list_*/search_* tools just to learn what exists. Expose a compact index as a resource and the agent gets the map up front, then calls the one tool it needs with a precise argument.
| Catalog resource | Replaces |
|---|---|
docs://index (doc hierarchy with titles and URIs) | Repeated search_docs calls to discover page names |
db://warehouse/schema (tables, columns, descriptions) | Trial-and-error run_query calls against unknown tables |
tickets://open/summary (open issues, one-line summaries) | Paginating list_tickets to find a relevant issue |
Mechanics: resources/list (paginated), resource templates (RFC 6570, e.g. tickets://{project}/{id}), resources/read (text or base64 binary + MIME type), listChanged and subscribe capabilities, and annotations (audience, priority 0–1, lastModified).
Tools for actions, resources for context, prompts for human-triggered workflows.
They're addressable data: they can be templated, generated on read and subscribable. What makes them resources is that the application decides when to include them, not the model.
Servers MUST implement server/discover: supported versions, capabilities, identity, optional instructions.
tools/list (+ resources, prompts) is paginated (cursor → nextCursor) with caching hints (ttlMs, cacheScope).
The host filters, namespaces and converts tools into the model's function-calling format.
Model emits a call → host (optionally after approval) sends tools/call → result is complete or input_required.
Per-request metadata, no handshake (2026-07-28): every request carries _meta with io.modelcontextprotocol/protocolVersion, client capabilities and client info; results carry server info. A version mismatch returns UnsupportedProtocolVersionError. Servers that declare tools.listChanged notify clients when the tool set changes.
{ "jsonrpc": "2.0", "id": 42, "method": "tools/call",
"params": { "name": "get_order_status",
"arguments": { "order_id": "A-1001" },
"_meta": { "io.modelcontextprotocol/protocolVersion": "2026-07-28" } } }
{ "jsonrpc": "2.0", "id": 42,
"result": { "resultType": "complete",
"content": [{ "type": "text", "text": "Order A-1001 shipped on 2026-09-30." }],
"structuredContent": { "order_id": "A-1001", "status": "SHIPPED" },
"isError": false } }
From 2024-11-05 through 2025-11-25 a connection started with a three-step handshake: client initialize → server capabilities response → client notifications/initialized. The session stayed stateful until shutdown. The 2026-07-28 revision removed it.
Whatever the host exposes still enters the model's context. Savings come from host-side choices: filtering, lazy loading / tool search and prompt caching, not from the protocol itself.
The model picks a tool almost entirely from its name, description and input schema; it never sees your implementation. A one-line description ("Gets data") gives it nothing to separate two similar tools, so selection becomes a coin flip.
| A good description states… | Example |
|---|---|
| What it does and returns | "Returns current stock level, reserved quantity and warehouse for one SKU." |
| When to use it | "Use when the user asks whether an item is available or how many are left." |
| When NOT to use it (boundary) | "For historical sales volumes, use get_sales_history instead." |
| Input formats | "sku: 8-character code like AB-12345. Not the product name." |
| Example queries | "'Do we still have the blue kettle?' → resolve SKU first, then call this." |
| Edge cases | "Returns quantity: 0 with isError: false when the SKU exists but is out of stock." |
| Symptom | Fix |
|---|---|
| Two tools with near-identical descriptions get confused | Rewrite both: purpose, inputs, outputs and "use X instead when…" boundaries |
A generic tool does several unrelated jobs (process_document) | Split into purpose-specific tools (extract_invoice_fields, summarize_contract, check_signature_present) |
Names don't convey intent (tool_a, query2) | Rename with verb + object; namespace by domain (inventory_get_stock) |
| Several tools overlap heavily | Consolidate into one tool with a constrained parameter (report_type: "daily" | "weekly") |
| Agent prefers a built-in generic tool (shell search, file read) over a more capable MCP tool | Expand the MCP tool's description: what it can do that the generic tool can't, and what it returns |
| A system-prompt rule ("always search first") overrides good descriptions | Review the system prompt for keyword-sensitive instructions and align them with the tool descriptions |
Spec naming guidance: tool names SHOULD be 1–128 characters, case-sensitive, using only A–Z a–z 0–9 _ - ., and unique within a server. When a host aggregates servers and two expose search, the host SHOULD disambiguate (e.g. prefix a server identifier).
| Interface rule (agent-first, not API-first) | Why |
|---|---|
Don't mirror REST 1:1. Design task-level tools (find_customer_orders) | Fewer calls, less reasoning burden, fewer errors |
| Typed, constrained inputs (enums, formats, required fields) | Fewer malformed calls; server still validates |
| Paginate and filter server-side | Unbounded results blow the context; some hosts cap or offload large outputs, silently losing detail |
| Truncate intelligently: summary + how to refine | Keeps the model on track |
Enrich results (semantic layer): resolve IDs to names, return structuredContent | Model reasons over meaning, not raw keys |
| Structured, actionable errors (§5) | Lets the model pick the right recovery |
| Annotate read-only vs. destructive; make writes idempotent | Enables safe auto-approval and retries |
| Small, role-scoped toolset per agent | Selection accuracy drops as the toolset grows |
| Version tool contracts; description changes = prompt changes | Avoids silent behavior shifts |
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("inventory")
@mcp.tool()
def inventory_get_stock(sku: str) -> dict:
"""Return current stock level, reserved quantity and warehouse for ONE SKU.
Use when the user asks if an item is available. For past sales, use
inventory_get_sales_history instead. sku format: 'AB-12345' (not a name)."""
return {"sku": sku, "quantity": 12, "reserved": 3, "warehouse": "LYS-1"}
@mcp.resource("inventory://catalog")
def catalog() -> str:
"""Compact index of product families and SKU ranges (content catalog)."""
return '{"kettles": "AB-10000..AB-19999", "toasters": "AB-20000..AB-29999"}'
@mcp.tool, @mcp.resource, @mcp.prompt) generate JSON Schemas from type hints and docstrings; low-level handlers for full control.McpToolset, LangChain MultiServerMCPClient) and some can expose their tools as servers.Output schemas must resolve to a JSON object. Untyped *args/**kwargs or non-serializable return types break schema generation at startup.
| Channel (spec) | Signal | Meaning | Who reacts |
|---|---|---|---|
| Protocol error | JSON-RPC error object with a numeric code and a message (unknown tool, malformed request, unsupported method, server fault) | The request could not be processed as a valid MCP call | Host/client code; the model usually never sees it |
| Tool execution error | CallToolResult with isError: true and an explanation in content | Tool ran but failed (upstream API failure, invalid argument values, business rule) | The model, which reads the message and adjusts |
The spec says errors originating inside the tool SHOULD be returned in the result with isError: true, not as a protocol error, otherwise the model can't see what went wrong. Clients SHOULD pass tool execution errors to the model. Recent revisions also classify input validation failures (for example a date in the past) as tool execution errors, so the model gets a chance to fix its arguments.
MCP reuses the standard JSON-RPC 2.0 codes. Don't invent extra fields to carry meaning: use the code, a clear message and, if needed, the optional data member.
| Code | Name | Typical MCP trigger |
|---|---|---|
-32700 | Parse error | The body is not valid JSON |
-32600 | Invalid request | Valid JSON, but not a valid JSON-RPC request (e.g. missing jsonrpc or method) |
-32601 | Method not found | The server does not implement the method, e.g. a capability it never advertised |
-32602 | Invalid params | Wrong parameter structure, or tools/call names a tool that does not exist |
-32603 | Internal error | Unexpected failure inside the server while handling the request |
-32000 … -32099 | Server error | Reserved for implementation-defined server errors; document any code you use |
{ "jsonrpc": "2.0", "id": 3,
"error": {
"code": -32602,
"message": "Unknown tool: ship_parcel_v1" } }
{ "jsonrpc": "2.0", "id": 7,
"result": { "resultType": "complete",
"isError": true,
"content": [{ "type": "text",
"text": "Pickup date 2026-10-02 is in the past.
Use a date on or after 2026-11-02 (YYYY-MM-DD)." }] } }
If the tool declares an outputSchema, structuredContent must conform to it; keep error explanations in content rather than bending the schema.
isError absent or false) with an empty list, e.g. "0 shipments for customer C-88".isError: true with a message saying the lookup could not be performed.The protocol does not define retry semantics. Retry decisions live in the host or agent framework.
| Signal | How it informs retries |
|---|---|
idempotentHint: true | Repeating the call with the same arguments has no extra effect, so an automatic retry after a timeout is low risk |
idempotentHint: false / destructiveHint: true | Don't auto-retry blindly. Prefer a server-supported idempotency key argument, or confirm with the user first |
readOnlyHint: true | No side effects; retries only cost latency and quota |
Annotations are hints: treat them as untrusted unless the server itself is trusted. Bound every retry loop (attempts, backoff, overall timeout).
The model can't tell a typo it could fix from a condition only a human can resolve, so it loops on hopeless calls or gives up on recoverable ones.
except: return []Swallowing an exception into an empty list makes the model state "no records exist" with confidence when the truth is "I couldn't check".
Client sends tools/call.
Server returns resultType: "input_required" with inputRequests (e.g. elicitation/create) and an opaque requestState.
Host renders the request and collects the answer (accept, decline or cancel).
Client retries with inputResponses + the same requestState, using a new JSON-RPC id. The server stays stateless.
| Mode | What happens | Use for |
|---|---|---|
| Form | Server sends a requestedSchema; host renders a form. Data is exposed to the client | Missing parameters, confirmations, choices |
| URL | User completes the flow out-of-band in a browser. Data (other than the URL) is not exposed to the client | Third-party OAuth, payments, entering API keys |
Sensitive credentials should use URL mode so they never transit the MCP client. The server stores the resulting third-party tokens itself and must not reuse the client's MCP access token upstream.
| Transport | How it works | When | Status |
|---|---|---|---|
| stdio | Host spawns the server as a subprocess; newline-delimited JSON-RPC on stdin/stdout; stderr is for logs only | Local tools, dev machines, IDE plugins | Current |
| Streamable HTTP | Single endpoint (e.g. /mcp); each message is an HTTP POST; server replies with JSON or an SSE stream | Remote, shared, multi-tenant servers | Current (standard for remote) |
| HTTP+SSE (two endpoints) | Separate SSE GET stream plus POST endpoint | Legacy clients only | Deprecated since 2025-03-26 |
| Streamable HTTP aspect | 2025-03-26 → 2025-11-25 | 2026-07-28 |
|---|---|---|
| Session | Mcp-Session-Id assigned at initialize | No sessions; each request is self-describing via _meta |
| Headers | Mcp-Session-Id, MCP-Protocol-Version | MCP-Protocol-Version, Mcp-Method, Mcp-Name so gateways route and authorize without parsing the body |
| Server → client notifications | GET SSE stream | subscriptions/listen long-lived stream |
stdio = "one user, one machine, the host owns the process." Streamable HTTP = "a network service." The stateless 2026 design lets remote servers scale horizontally behind ordinary load balancers, no sticky sessions.
Standalone HTTP+SSE is deprecated. Streamable HTTP may use SSE to stream a response, but it's a different transport.
| Dimension | Local (stdio subprocess) | Remote (Streamable HTTP) |
|---|---|---|
| Where it runs | User's machine, spawned by the host | Cloud/service endpoint |
| Trust boundary | Runs with the user's OS privileges | Network service: treat as an external API |
| Credentials | Environment variables / local config; stdio should not use the HTTP OAuth flow | OAuth 2.1 bearer tokens (or API keys/IAM in managed offerings) |
| Tenancy & updates | Single user; user updates the package | Multi-tenant; provider updates centrally |
| Main risks | Supply chain, local file/command access | Token misuse, confused deputy, SSRF, tenant leakage |
| Mitigations | Pin versions, containerize, non-root, restrict paths | Audience validation, narrow scopes, gateway policy, audit |
| Config scope | Typical location | Shared? | Use for |
|---|---|---|---|
| Project | File committed at the repo root (e.g. .mcp.json) | Yes, via version control | Team tooling everyone on the project needs (issue tracker, internal docs, staging DB) |
| User | File in the user's home directory | No | Personal or experimental servers used across projects |
| Local / private per-project | User config keyed by project path | No | Personal overrides for one project |
Project scope = .mcp.json at the project root; user and local scopes live in ~/.claude.json; on name clashes local > project > user; project-scoped servers trigger an approval prompt the first time, so a cloned repo can't launch processes without consent. Servers from every scope are merged: the host connects to all configured servers and their tools are offered to the model together in the same session (Claude Code defers full schemas behind tool search by default; alwaysLoad: true preloads one server's tools). Full details in the Claude Code guide § MCP and the Claude Agent SDK guide.
{
"mcpServers": {
"tracker": {
"type": "http",
"url": "${TRACKER_URL:-https://tracker.example.com}/mcp",
"headers": { "Authorization": "Bearer ${TRACKER_TOKEN}" }
},
"inventory": {
"type": "stdio",
"command": "uvx",
"args": ["inventory-mcp-server==1.4.2"],
"env": { "INVENTORY_API_KEY": "${INVENTORY_API_KEY}" }
}
}
}
${VAR} expands from each developer's environment; ${VAR:-default} adds a fallback (Claude Code supports both in command, args, env, url, headers).url, httpUrl, type: "http"). The principles don't: pin versions, inject secrets from the environment or a secret manager.(1) Hard-coding a token in a committed config "because the repo is private". (2) Putting a team-wide server in one person's user config, so nobody else gets it. (3) Building a custom server for a mainstream SaaS integration "for control" instead of starting with a vetted existing one.
| Layer | Question | Mechanism |
|---|---|---|
| Authentication | Who is the client/user? | OAuth 2.1 token (remote); OS user + env credentials (local); cloud IAM for managed servers |
| Authorization | What may this identity do here? | Token scopes and audience, server-side RBAC/ABAC, per-tool policy |
| Permissions / consent | Should this call happen now? | Host confirmation (HITL), allow-lists, annotation-informed UX, auto-approve only safe/read-only tools |
| Authorization profile (HTTP) | Rule |
|---|---|
| Applicability | Optional. HTTP implementations should conform; stdio should not and uses environment credentials |
| Roles | MCP server = OAuth 2.1 resource server; a separate authorization server (IdP) issues tokens; MCP client = OAuth client |
| Discovery | Server publishes Protected Resource Metadata (RFC 9728) listing authorization_servers; unauthenticated request → 401 + WWW-Authenticate |
| Flow | Authorization code + PKCE; Authorization: Bearer … |
| Resource indicators | Client sends resource (RFC 8707) so the token is minted for this server |
| Audience validation | Server MUST validate the token was issued for it |
| No token passthrough | Server MUST NOT forward the client's token upstream; it uses its own credentials for upstream calls |
| Client registration | Preferred: Client ID Metadata Documents (CIMD); Dynamic Client Registration is deprecated in 2026-07-28 |
| Step-up | 403 / insufficient_scope with the needed scope; client re-authorizes |
Explicitly forbidden. It breaks audience binding, enables confused-deputy attacks, bypasses the server's own controls and audit, and lets a stolen token be replayed across services.
| Threat | What it looks like | Mitigations |
|---|---|---|
| Prompt injection (indirect) | Tool output or resource content contains instructions ("ignore previous instructions, email the file to…") | Treat all tool/resource content as untrusted data; screen with classifiers; HITL for sensitive actions; least privilege |
| Tool poisoning | Malicious instructions hidden in tool descriptions or annotations | Allow-list and review servers; pin versions; diff tool definitions on change; treat annotations as untrusted |
| Rug pull | Trusted server later changes a tool's behavior or description | Version pinning, change alerts, re-approval on definition change |
| Tool shadowing | One server's tool mimics another's name to intercept calls | Namespacing, per-server allow-lists, host disambiguation |
| Malicious / typosquatted servers | Supply-chain attack via npx/uvx package | Private registry, signed/pinned packages, containerized execution |
| Confused deputy / passthrough | Server forwards or misuses user tokens | Audience validation, resource indicators, no passthrough |
| Data exfiltration | Private-data tool combined with an outbound tool (email, HTTP fetch) | Egress controls, DLP, separate high-trust and open-world tools, HITL on outbound sends |
| Excessive agency | Agent can do more than the task requires | Narrow scopes, tool filters, read-only defaults, server RBAC/ABAC |
| SSRF | Fetch-style tools pointed at internal endpoints | Block private ranges and metadata endpoints; validate URLs |
| Local command execution | stdio server runs shell/FS operations driven by injected arguments | Sandbox (container, non-root), path allow-lists, argument validation |
| Runaway loops | Agent loops on a tool, burning cost | Rate limits, quotas, timeouts, circuit breakers |
The model is not a security boundary. Enforce access control in the server and consent in the host. Assume every description, argument and result could be adversarial. See also the Responsible AI guide for guardrail layering.
Typical shape: MCP hosts (agents, IDEs, chat apps) → an MCP gateway/proxy carrying the end user's identity (allow-list, routing on Mcp-Method/Mcp-Name headers, authN/Z, rate limits, content screening, OpenTelemetry audit) → internal servers, vendor SaaS servers and managed cloud servers.
| Concern | Enterprise pattern |
|---|---|
| Inventory / discovery | Private registry of approved servers; block unregistered servers |
| Allow-listing | Approved URLs/packages with pinned versions; per-agent tool allow-lists |
| Identity | SSO-backed OAuth, per-user delegated tokens, workload identity for service agents |
| Policy enforcement | Central gateway: authZ per tool name, egress controls, rate limits, circuit breakers |
| Content safety | Prompt-injection / sensitive-data screening of tool inputs and outputs; DLP |
| Observability | Log every tools/call (user, agent, tool, redacted args, latency, outcome); OpenTelemetry traces (replaces deprecated protocol logging) |
| Isolation | Containerized servers, network policies, tenant separation, sandboxed untrusted connectors |
| Lifecycle | Versioned servers, schema change review (descriptions are effectively prompts), staged rollout |
from google.adk.agents import LlmAgent
from google.adk.tools.mcp_tool import McpToolset, StreamableHTTPConnectionParams
inventory = McpToolset(
connection_params=StreamableHTTPConnectionParams(url="https://inventory.internal/mcp"),
tool_filter=["inventory_get_stock", "inventory_list_warehouses"], # read-only subset
)
agent = LlmAgent(model="gemini-2.5-flash", name="stock_agent",
instruction="Answer stock questions using the tools.",
tools=[inventory])
Cloud providers also offer managed remote MCP servers for their own services (IAM-controlled, with optional content screening). See the vendor guides on the hub and the ADK guide.
An enterprise answer mentions four things: registry/allow-list, per-user OAuth with audience-bound tokens, a gateway for policy and audit, and host-side HITL for destructive tools.
| REST/gRPC API | MCP server | |
|---|---|---|
| Audience | Deterministic software written by developers | AI applications and models |
| Discovery | Docs / OpenAPI read by humans at build time | Runtime, self-describing with descriptions aimed at the model |
| Shape | Many fine-grained endpoints | Task-oriented tools; context as resources; templates as prompts |
| Relationship | — | MCP usually wraps existing APIs; it doesn't replace them |
| Function (tool) calling | MCP | |
|---|---|---|
| What it is | A model API feature: the model returns "call X with args Y" | A protocol between host and external servers for discovering and executing tools (plus resources/prompts) |
| Where it lives | Between your code and the LLM | Between your host and tool providers |
| Who executes | Your application code | The MCP server (host sends tools/call) |
| Portability | Declaration format differs per provider | Same server works with any host and model |
Frameworks (ADK, LangGraph, Claude Agent SDK…) own reasoning and orchestration: agent loop, planning, which tool and when, state, memory, evaluation. MCP owns connectivity. Frameworks act as MCP hosts/clients and can expose tools as MCP servers. See Agentic Design Patterns.
MCP = agent-to-tool (vertical; capability provider without its own goals; tools/list; tool call). A2A = agent-to-agent (horizontal; opaque autonomous agent; Agent Card; Task lifecycle). Real systems use both. See the A2A guide.
The model still uses function calling to choose a tool. MCP standardizes where tool definitions come from and how the call is executed. They're complementary.
MCP defines messages, methods, transports, capability metadata and an auth profile. It does not define planning, tool ordering/retries, memory, multi-agent routing or guardrail policy. That narrow contract is what enables the M + N ecosystem.
| Revision | Headline changes |
|---|---|
| 2024-11-05 | Initial release: stdio + HTTP+SSE; tools, resources, prompts, sampling, roots, logging |
| 2025-03-26 | Streamable HTTP replaces HTTP+SSE; first OAuth 2.1 framework; tool annotations |
| 2025-06-18 | Server = OAuth resource server with PRM (RFC 9728); RFC 8707; elicitation; structured tool output; MCP-Protocol-Version header |
| 2025-11-25 | Tasks (long-running), server identity, official extensions, URL-mode elicitation, CIMD |
| 2026-07-28 | Stateless: no initialize, per-request _meta, server/discover, input_required round-trips, session-free Streamable HTTP (Mcp-Method/Mcp-Name, subscriptions/listen) |
| Deprecated in 2026-07-28 (removable on/after 2027-07-28) | Replacement |
|---|---|
| Roots | Pass paths via tool parameters, resource URIs or server config |
| Sampling (server borrows the host's LLM) | Server calls an LLM provider API directly |
| Protocol logging | stderr (stdio) or OpenTelemetry |
| Dynamic Client Registration | Client ID Metadata Documents |
| HTTP+SSE transport (deprecated since 2025-03-26) | Streamable HTTP |
The initialize → initialized handshake and Mcp-Session-Id are correct for 2025-11-25 and earlier but were removed in 2026-07-28. If a question names a revision, answer for that revision.
| If the question says… | Think… |
|---|---|
| "Who approves a destructive tool call?" | Host (HITL), not the server or model |
| "Model decides" / "App attaches context" / "User picks a slash command" | Tool / Resource / Prompt |
| "Agent wastes turns listing things to find what exists" | Expose a resource catalog |
| "Agent keeps picking the wrong one of two similar tools" | Improve descriptions; rename/split overlapping tools |
| "Tool failed; how does the model find out?" | isError: true in the result, not a JSON-RPC error |
| "Should the agent retry?" | Host policy: retry idempotent/read-only tools with bounded backoff; confirm before repeating destructive calls |
"tools/call names a tool that doesn't exist" | Protocol error, JSON-RPC -32602 |
| "Query matched nothing" | Valid empty result, isError: false |
| "Team needs the same server; secrets stay out of git" | Project-scoped config + ${VAR} expansion |
| "Personal experimental server" | User-scoped config |
| "Standard SaaS integration" | Vetted community/vendor server first; custom for team-specific workflows |
| "Local, user's machine" / "Remote, multi-tenant" | stdio + env credentials / Streamable HTTP + OAuth 2.1 |
| "Server forwards user's token upstream" | Token passthrough: forbidden |
| "Malicious instructions in a tool description" | Tool poisoning: allow-list, pin, diff, treat annotations as untrusted |
| "Third-party OAuth / API key during a tool call" | URL-mode elicitation |
readOnlyHint, destructiveHint, idempotentHint, openWorldHint. Hints only; untrusted unless the server is trusted.client_id is an HTTPS URL to the client's metadata; replaces Dynamic Client Registration.input_requiredrequestState.isError / JSON-RPC error codecode of a protocol error (-32700, -32600, -32601, -32602, -32603, or -32000…-32099 for implementation-defined server errors)..mcp.json) vs. personal config across all of a user's projects.server/discoverclaude mcp add, scopes, managed MCP policy, tool search.McpToolset, tool_filter, exposing tools as MCP.Drill scenario questions on primitives and resource catalogs, tool description design, structured errors and retryability, transports, project vs. user scoping, OAuth and the MCP threat model.
Q1. What are the three server primitives and who controls each?
Q2. Why expose a documentation index as a resource instead of only a search tool?
Q3. Two tools are often confused. What do you change first?
Q4. A tool's backend times out. How should the result look?
CallToolResult with isError: true and a message saying the upstream system timed out. Whether to retry is host policy, safest for tools annotated idempotentHint: true.Q5. How do you distinguish "no matches" from "couldn't check"?
isError: true with a message explaining the lookup could not be performed.Q6. Where do shared team servers and personal servers go?
${VAR}. Personal/experimental: user-scoped config.Q7. A remote server receives a token minted for another API. What should it do?
Q8. What changed in the 2026-07-28 revision?
initialize handshake, per-request _meta, server/discover, session-free Streamable HTTP and input_required round-trips. Roots, sampling, protocol logging and DCR are deprecated.