If you're asked "walk me through what happens when an agent calls an MCP tool", the strong answer has two parts: a short description of the messages, and a statement of which spec revision you mean. That second part matters more than it used to. The latest revision I could read on 2026-10-09, 2026-07-28, removed the initialize handshake and protocol-level sessions that a lot of tutorials still describe. This post gives you MCP interview questions on tool servers, transports and security, checked against that revision, plus a script that lints a tool definition against the rules I could find.
These are practice questions I wrote. They are not from any company's interview. For installing and auditing servers day to day, the MCP server security checklist is the practical companion, and what is MCP is the primer.
What changed in the latest MCP spec?
On 2026-10-09 the specification page at modelcontextprotocol.io was labelled "Version 2026-07-28 (latest)". I read its "Key Changes" page, which compares it with 2025-11-25. In short:
| Topic | Up to 2025-11-25 | 2026-07-28 |
|---|---|---|
| Handshake | initialize / initialized | Removed; every request carries _meta with protocol version and client capabilities |
| Sessions | Mcp-Session-Id header on Streamable HTTP | Removed; state goes through explicit handles in tool arguments |
| Discovery | Capabilities returned at initialize | server/discover (servers must implement it) |
| Server-to-client requests (sampling, elicitation, roots) | Servers sent their own requests | Returned as an input_required result; the client retries with inputResponses |
| Change notifications | HTTP GET stream, resources/subscribe | subscriptions/listen, a long-lived POST response stream |
| Stream resume | Last-Event-ID | Removed; a broken stream loses the request and the client re-issues it |
| Long-running work | Experimental tasks in core | Moved to an official extension |
| Deprecated | Roots, Sampling, Logging; HTTP+SSE transport; Dynamic Client Registration |
I don't know which SDK versions implement 2026-07-28. Check your SDK's release notes before you tell an interviewer how its client behaves. The spec also says clients that need to talk to older servers can fall back to the initialize flow, which is why you will meet both in the wild.
Q1. What are the host, client and server, and what can a server expose?
Strong answer outline
- The host is the application (an IDE, a chat app) that creates and manages client instances and enforces consent. Each client talks to exactly one server. Servers provide context and capabilities.
- Messages are JSON-RPC 2.0. Per the spec's architecture page, MCP is stateless: every request is self-contained.
- Servers can offer resources (context and data, for the user or the model), prompts (templated messages for users) and tools (functions the model executes). Tools are model-controlled; the spec says there should always be a human in the loop who can deny tool invocations.
- Elicitation is a client feature: the server asks the user for more information.
Follow-up: Why one client per server? Isolation. The spec's design principles say servers should not be able to read the whole conversation or see into other servers, and the host controls cross-server interactions.
Common wrong answer: "The server calls the LLM." The server doesn't hold the conversation; the host does. (Sampling lets a server request a completion through the client, but the revision I read deprecates it.)
Q2. Compare stdio and Streamable HTTP. When do you use each?
Strong answer outline
- stdio: the client launches the server as a subprocess. Messages are newline-delimited JSON-RPC on stdin/stdout, nothing else may be written to stdout, and logs go to stderr. Right for local tools that act on the user's machine.
- Streamable HTTP: one MCP endpoint that accepts POST. Each client message is its own POST with an
Acceptheader listingapplication/jsonandtext/event-stream. The server replies with either a JSON object or an SSE stream scoped to that request. Right for remote, multi-user servers. - Security rules for HTTP: servers must validate the
Originheader and answer 403 if it is invalid, should bind to 127.0.0.1 when running locally, and should authenticate connections. Without that, DNS rebinding lets a website talk to a local server. - Cancellation differs: on HTTP, closing the response stream cancels the request; on stdio, the client sends
notifications/cancelled.
Follow-up: What happened to the old HTTP+SSE transport? The spec reclassifies it as deprecated in favour of Streamable HTTP.
Common wrong answer: "Streamable HTTP keeps a session open." In this revision there are no protocol-level sessions, and GET or DELETE on the endpoint should get 405 from a server that only supports this revision.
Q3. How does a server keep state without sessions?
Strong answer outline
- The tools page says MCP has no protocol-level session, so servers should return an explicit handle from a creation tool (for example
create_basketreturningbsk_a1b2c3) and accept it as an argument in later calls. The model carries the handle forward. - Handles must not be treated as authentication. The security best-practices page names state handle hijacking: an attacker guesses or obtains a handle and calls tools with it. Mitigations it lists: verify every request, use non-deterministic handles from a secure random generator, expire them, and bind them server-side to the authenticated user (for example keying state as user id plus handle, with the user id taken from the verified token).
- State the retention in the creation tool's description so the model knows what it's creating.
Follow-up: What should happen when a handle has expired? Return a tool execution error that says so, so the model can create a new one.
Q4. What does a tools/call look like on the wire?
Strong answer outline
tools/listreturns tool definitions (name, description,inputSchema, optionaloutputSchemaand annotations) with pagination and, in this revision,ttlMsandcacheScopefor caching. Servers should return tools in a deterministic order, which also helps LLM prompt-cache hit rates.tools/callcarriesnameandarguments, plus_metawithio.modelcontextprotocol/protocolVersionand capabilities. Results includeresultType: "complete",contentand optionallystructuredContent.- On Streamable HTTP, each POST must also carry
MCP-Protocol-Version,Mcp-Methodand, fortools/call,Mcp-Name. A tool parameter markedx-mcp-headeris mirrored into anMcp-Param-{Name}header so load balancers and gateways can route without parsing the body. Servers must reject a request whose headers disagree with the body, with HTTP 400 and error code -32020 (HeaderMismatch). - Why the check exists: if a gateway enforces policy on the header while the server executes the body, a mismatch is a bypass.
Common wrong answer: "The headers are just for debugging." They are required, and mismatches must be rejected.
Q5. Protocol error or tool error?
Strong answer outline
- Protocol errors (unknown tool, malformed request, server error) are JSON-RPC errors, for example code -32602 for an unknown tool.
- Tool execution errors (API failure, bad date, business rule) are normal results with
isError: trueand a message the model can act on. Clients should feed these back to the model. - Practical point: write execution errors as instructions ("departure date must be in the future"), because the model reads them.
Q6. How does authorization work for a remote MCP server?
Strong answer outline (all from the authorization pages)
- Optional overall. HTTP transports should follow the spec; stdio should not, and should read credentials from the environment.
- The MCP server acts as an OAuth 2.1 resource server; the client is an OAuth 2.1 client; a separate authorization server issues tokens.
- Flow: the client sends a request without a token, gets a 401 with a
WWW-Authenticateheader pointing at the Protected Resource Metadata (RFC 9728, which servers must implement), discovers the authorization server, registers, then runs an authorization-code flow with PKCE (S256 required, and the client must refuse to proceed if the server's metadata lackscode_challenge_methods_supported). The client sends aresourceparameter (RFC 8707) in both the authorization and token requests so the token is bound to this MCP server. - Every request carries
Authorization: Bearer <token>; tokens must not go in the query string. - The server must validate that the token was issued for it. It must not accept or pass through other tokens. If it calls an upstream API, it uses a separate token from that API's authorization server.
- 401 for missing or invalid tokens. 403 with
error="insufficient_scope"and the neededscopefor step-up; the client re-authorizes with the union of previous and challenged scopes and limits retries. - Registration: Client ID Metadata Documents are what the spec recommends supporting. Dynamic Client Registration is deprecated and kept for backwards compatibility.
Follow-up: Why is token passthrough dangerous? The best-practices page lists security-control circumvention, broken audit trails and trust-boundary problems. The mitigation is a one-liner: servers must not accept tokens not issued for them.
Common wrong answer: "Use an API key in the URL." The spec forbids tokens in the query string.
Q7. Name four attacks from the MCP security guidance and how to stop them
| Attack | What happens | Mitigation named in the spec pages |
|---|---|---|
| Confused deputy | An MCP proxy with a static client ID and dynamic registration lets an attacker reuse a consent cookie to get a code sent to their redirect URI | Per-client consent stored server-side, exact redirect URI matching, state validation |
| SSRF in OAuth discovery | A malicious server returns metadata URLs pointing at internal addresses or cloud metadata | HTTPS only, block private and link-local ranges, validate redirects, use an egress proxy; do not hand-roll IP parsing |
| Local server compromise | A one-click config runs a malicious startup command | Show the exact command, require consent, sandbox with minimal privileges |
| DNS rebinding | A website talks to a local HTTP server | Validate Origin, bind to localhost, require auth |
Others on the pages: OAuth authorization-URL validation (reject javascript: and other schemes), mix-up attacks (validate iss), and scope minimisation.
Follow-up: Which of these apply to stdio? Local compromise mainly. The best-practices page says stdio itself isn't vulnerable, but a proxy that spawns stdio servers can become an escalation path.
Q8. How does prompt injection reach you through MCP, and what can the protocol do?
Strong answer outline
- A tool result is text that goes into the model's context. If it contains instructions, the model may follow them. The same goes for tool descriptions.
- The spec asks for: annotations treated as untrusted unless from a trusted server; servers that validate inputs, apply access controls, rate limit and sanitize outputs; clients that show inputs before calling, validate results, set timeouts and log usage.
- What it can't do: the spec says it cannot enforce these principles at the protocol level. Least privilege on tools, human confirmation on writes, and separating untrusted content from instructions are host decisions. See prompt injection in tool-using agents.
Common wrong answer: "Signing tool descriptions fixes it." Descriptions from a trusted server can still return hostile data from the web.
Q9. How would you design a good tool?
Strong answer outline
- Names: the spec says 1 to 128 characters from letters, digits, underscore, hyphen and dot, unique within a server. Aggregating clients may see collisions and should prefix with a server identifier; the server's self-reported name isn't guaranteed unique.
inputSchemamust be a valid JSON Schema object. For no-parameter tools,{"type": "object", "additionalProperties": false}is the recommended form.- Add
outputSchemawhen results are structured, and return the serialized JSON in a text block too for compatibility. - Keep sensitive values out of
x-mcp-headerparameters, because intermediaries can see headers. - One responsibility per tool, readable errors, idempotent where possible. More in how to design tools for AI agents and build an MCP server in Python. For how this differs from plain function calling, see MCP vs function calling.
Run it: a tool-definition and header linter
This script encodes the rules above that I could state precisely: name format, a non-null inputSchema, x-mcp-header constraints (valid token, primitive type, statically reachable, no obviously sensitive field) and the header-versus-body check for tools/call. It's my reading of the 2026-07-28 text, not an official validator. Run with Python 3 on 2026-10-09:
import base64, re
TOKEN = re.compile(r"^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$") # RFC 9110 tchar
NAME_OK = re.compile(r"^[A-Za-z0-9_.\-]{1,128}$")
PRIMITIVE = {"string", "integer", "boolean"}
SENSITIVE = re.compile(r"pass|token|secret|key|ssn|email", re.I)
def walk(schema, path=()):
for name, sub in (schema.get("properties") or {}).items():
if "x-mcp-header" in sub:
yield path + (name,), sub, True
yield from walk(sub, path + (name,))
for kw in ("items", "anyOf", "oneOf", "allOf", "not", "if", "then", "else", "$defs"):
sub = schema.get(kw)
for s in (sub if isinstance(sub, list) else [sub] if isinstance(sub, dict) else []):
for p, ps, _ in walk(s, path + (kw,)):
yield p, ps, False
def lint_tool(tool):
out, seen = [], set()
if not NAME_OK.match(tool.get("name", "")):
out.append("name should be 1-128 chars of A-Z a-z 0-9 _ - .")
schema = tool.get("inputSchema")
if not isinstance(schema, dict):
return out + ["inputSchema MUST be a JSON Schema object, not null/missing"]
for path, prop, reachable in walk(schema):
h, where = prop["x-mcp-header"], ".".join(path)
if not reachable:
out.append(f"{where}: x-mcp-header not statically reachable via properties")
if not h or not TOKEN.match(h):
out.append(f"{where}: x-mcp-header {h!r} is not a valid header token")
if prop.get("type") not in PRIMITIVE:
out.append(f"{where}: type {prop.get('type')!r} not allowed (string/integer/boolean)")
if (h or "").lower() in seen:
out.append(f"{where}: duplicate x-mcp-header {h!r}")
seen.add((h or "").lower())
if SENSITIVE.search(path[-1]):
out.append(f"{where}: looks sensitive; headers are visible to intermediaries")
return out
def decode(v):
m = re.match(r"^=\?base64\?(.*)\?=$", v)
return base64.b64decode(m.group(1)).decode() if m else v
def check_headers(headers, body):
h = {k.lower(): v for k, v in headers.items()}
p, errs = body["params"], []
if h.get("mcp-protocol-version") != p["_meta"].get("io.modelcontextprotocol/protocolVersion"):
errs.append("MCP-Protocol-Version != _meta protocolVersion")
if h.get("mcp-method") != body["method"]:
errs.append("Mcp-Method != body method")
if body["method"] == "tools/call" and decode(h.get("mcp-name", "")) != p["name"]:
errs.append("Mcp-Name != params.name")
return errs
I fed it four tool definitions (a clean execute_sql with a region header, a tool named bad name!, a send tool with a sensitive field, a number-typed header and a header nested inside an array, and one with a null schema) and three requests. Real output:
execute_sql -> ok
bad name! -> ['name should be 1-128 chars of A-Z a-z 0-9 _ - .']
send -> ['api_key: looks sensitive; headers are visible to intermediaries', "ratio: type 'number' not allowed (string/integer/boolean)", 'tags.items.t: x-mcp-header not statically reachable via properties']
broken -> ['inputSchema MUST be a JSON Schema object, not null/missing']
good request -> ok
spoofed Mcp-Name -> ['Mcp-Name != params.name']
base64 name -> ok
The spoofed request sends Mcp-Name: delete_everything while the body calls get_weather, which is the gateway-bypass case from Q4. The last line checks the Base64 sentinel form (=?base64?...?=) that the transport page defines for header values that are not plain ASCII.
How to prepare in one evening
- Read the spec's "Key Changes" page and write the table above from memory.
- Draw one remote tool call with auth: 401, metadata, PKCE, token with
resource, call. - Memorise four attacks and one mitigation each.
- Say out loud which revision you are describing before any answer. If the interviewer's mental model is the older one, name the difference without making it a correction contest.
What I could not verify
- I read these pages on 2026-10-09 by downloading their Markdown versions from modelcontextprotocol.io: specification index, Key Changes, architecture, tools, Streamable HTTP, stdio (first part), authorization, authorization security considerations,
server/discover, and the Security Best Practices page. I did not read the client registration, authorization-server discovery, resources, prompts, multi round-trip, subscriptions or versioning pages, so my statements about Client ID Metadata Documents rest on the authorization overview and changelog only. - I did not run an MCP server or client, so nothing here describes how any SDK or product behaves with the new revision.
- Spec text changes. Treat this as a dated reading, and check the live page for exact normative wording.



