About this article: AI-assisted writing, reviewed against the cited sources. This is source research, not a lab validation of the procedures. Check the stated versions and your own recovery requirements before making changes.
The Model Context Protocol, revision 2025-11-25, is a JSON-RPC session between a client and a server. Two transports are standard: the client launches a subprocess and speaks newline-delimited JSON on its standard streams, or both sides speak HTTP to a single endpoint that can answer with one JSON object or with a server-sent event stream. Before any tool, prompt, or resource is used, the client and server exchange the protocol version and a capability list, and each side may use only what the other side agreed to. Authorisation is optional. When it is used, it is an OAuth 2.1 flow for HTTP, and that revision says stdio servers should not follow that flow. The security model is a set of duties placed on the implementor. The specification says the protocol itself cannot enforce consent.
An AI writing system produced this article. No MCP server was run.
Read the revision before the protocol
Revision 2025-11-25 is superseded. The current specification endpoint checked during this review serves revision 2026-07-28. The detailed sections below intentionally explain older clients and servers; they must not be used as a specification for a new implementation. Retaining the old detail is useful when identifying why an existing integration differs from a new peer, but migration requires the current specification and conformance checks for both endpoints.
The 2026-07-28 changelog removes the initialize/initialized handshake and protocol sessions, including Mcp-Session-Id. Protocol version and client capabilities instead travel in each request's _meta; server/discover provides discovery. The HTTP GET stream and resource subscription mechanism are replaced by subscriptions/listen. SSE resumption and Last-Event-ID are removed. This is a protocol change, not a reason to omit authentication from an old deployment.
Tasks move into the io.modelcontextprotocol/tasks extension. Roots, Sampling and Logging are deprecated; ping and logging/setLevel are removed. New Mcp-Method and Mcp-Name header requirements and the authorisation changes, including the RFC 9207 issuer check when present and deprecation of dynamic client registration, need to be assessed against the current normative pages. This summary is a migration checklist, not a replacement for them.
Record each endpoint's protocol revision and SDK. Supporting several revisions requires implementing each contract correctly. Removing protocol sessions does not remove application authorisation or resource ownership.
Why it matters
A tool in this protocol is a function the server says it can run. The specification's own security section says tools are arbitrary code execution, and that a description of a tool is untrusted unless it came from a server you already trust. A local server is not a sandbox the protocol creates. The client starts it as a subprocess. The security best-practices page says that process runs with the client's privileges, and that a one-click installer which hides the command is the attack.
The HTTP transport has a different boundary. A server on a laptop that accepts connections from anywhere, and does not check the Origin header, is the DNS-rebinding case the transport page names: a page in a browser can reach a process that was only meant to be local. Treating the session identifier as a login is the impersonation case the same security page names. The identifier is a correlation value. Where authorisation is implemented, authentication is a separate check on every request.
How it works
The session, before any feature
JSON-RPC messages are UTF-8. The lifecycle page says the first interaction is an initialize request from the client, carrying a protocol version, the client's capabilities, and implementation info. The server answers with a version, its capabilities, and its own implementation info. If it supports the client's version it must answer with that version. If it does not, it answers with a version it does support, and the client should disconnect when it does not support that answer. An example error in the specification uses JSON-RPC code -32602 and lists the versions the server can speak.
After the response, the client must send an initialized notification. Until the response arrives, the client should send nothing but pings. Until the notification arrives, the server should send nothing but pings and logging. During operation both sides must stay on the negotiated version and must use only negotiated capabilities. There is no shutdown method. The transport closes. For stdio the client should close the child's input, wait, send SIGTERM, and send SIGKILL if the process is still there. The server may instead close its output and exit. For HTTP, shutdown is closing the connection. Implementations should time out requests, send a cancellation notification when a request times out, and keep a maximum timeout even if progress notifications reset the clock.
Capabilities are the feature list
The lifecycle page tabulates what each side can offer. These are the names in that revision, not a promise that a given server implements them.
| Side | Capability | What the specification says it means |
|---|---|---|
| Client | roots |
The client can provide filesystem roots |
| Client | sampling |
The client accepts sampling requests |
| Client | elicitation |
The client accepts elicitation requests |
| Client | tasks |
The client supports task-augmented requests |
| Server | prompts |
Prompt templates |
| Server | resources |
Readable resources. subscribe is a sub-capability for individual items |
| Server | tools |
Callable tools |
| Server | logging |
Structured log messages |
| Server | completions |
Argument autocompletion |
| Server | tasks |
Task-augmented server requests |
Tasks are experimental in revision 2025-11-25. Either side may also advertise experimental features. listChanged is the sub-capability for being told that a list of prompts, resources, or tools has changed. A server that did not advertise tools is not a server the client may call tools on. A client that did not advertise sampling is not a client the server may ask to sample. The specification overview adds the consent rules around the two that move data and run code: the host must obtain explicit user consent before exposing user data to a server and must not transmit resource data elsewhere without consent, and the host must obtain explicit consent before invoking a tool. Sampling is narrower still. The user must approve a sampling request, and should control whether sampling happens, the prompt that is sent, and what results the server can see. The specification says it intentionally limits how much of the prompt a server can see. It also says it cannot enforce any of these principles at the protocol level. Consent is an application behaviour, not a message the server can require.
stdio
The client launches the server as a subprocess. The server reads JSON-RPC from standard input and writes JSON-RPC to standard output. Messages are delimited by newlines and must not contain a newline inside the JSON. Standard error is for logs. The client may capture it, forward it, or ignore it, and should not treat it as an error indicator. The server must not write anything to standard output that is not a valid MCP message. The client must not write anything to the server's input that is not one. One stray log line on standard output is a broken stream, not a harmless diagnostic.
The authorisation specification says an implementation on stdio should not follow the OAuth flow. Credentials come from the environment. A token dance in a process that has no browser and no HTTP listener is not what that revision specifies.
Streamable HTTP
This is the transport that revision 2025-11-25 defines, and the page says it replaces the HTTP+SSE transport from protocol version 2024-11-05. The server is its own process and can serve more than one client. It must expose a single path that accepts POST and GET. Every client message is a new POST. The client must send an Accept header listing both application/json and text/event-stream. A notification or a response, if accepted, gets HTTP 202 and an empty body. A request gets either one JSON object or an SSE stream, and the client must handle both. The server may send requests and notifications on that stream before the response. A dropped connection is not cancellation. Cancellation is a CancelledNotification. The server may attach event ids and, after a disconnect, the client resumes with an HTTP GET carrying Last-Event-ID. The server must not replay events that belonged to a different stream. A server may reject GET with 405; where supported, a separate GET can open a stream for server-initiated messages that are not the answer to a POST. The server must not put the same message on two streams.
A session identifier is optional. If the server returns MCP-Session-Id on the initialise response, the client must send it on every later request. The value should be globally unique and cryptographically secure, and it may contain only visible ASCII, from 0x21 to 0x7E. A request that should have carried it and did not should get HTTP 400. When the server has ended the session it must answer that id with HTTP 404, and the client must start again with a new initialize and no session id. The client should send HTTP DELETE when it is finished. The server may answer 405 if it does not allow the client to end sessions.
On HTTP, every request after initialisation must carry MCP-Protocol-Version. The value should be the negotiated one. If the header is absent and the server has no other way to know the version, it should assume 2025-03-26. An unsupported value is HTTP 400.
The transport page's security warning is three requirements. Servers must validate Origin on every incoming connection, and a present but invalid Origin is HTTP 403. A local server should bind to 127.0.0.1, not to every interface. Servers should authenticate connections. The page states the consequence of skipping these: a remote site can use DNS rebinding to talk to a local server.
Clients that still need the old transport try a POST of initialize first. A 400, 404, or 405 is the signal to fall back to a GET and wait for the old endpoint event. That fallback is compatibility, not a second current transport.
Authorisation, when HTTP is protected
Authorisation is optional. When an HTTP implementation supports it, the client is an OAuth 2.1 client, the MCP server is the resource server, and an authorisation server, which may be the same host or another one, issues the access token. The MCP server must publish OAuth 2.0 Protected Resource Metadata, including at least one authorisation server. Clients must use that metadata to find it. The authorisation server must offer OAuth 2.0 Authorisation Server Metadata or OpenID Connect Discovery, and clients must support both. Clients must use PKCE with the S256 challenge when they are able, and they must refuse an authorisation server whose metadata does not advertise PKCE. Clients must send a resource parameter so the token is bound to an audience, and servers must check that audience. The token travels in the Authorization header on every HTTP request, including later requests in the same session. It must not be placed in the query string. Invalid or expired tokens are HTTP 401. Insufficient scope is HTTP 403.
Two sentences in the legacy authorisation page set an important boundary; the page also covers token storage, audience validation, lifetime and refresh-token handling. Clients must not send a token that was not issued by this MCP server's authorisation server. Servers must not accept or pass on any other token. The security page explains why a "pure proxy" that forwards an upstream token is the wrong shape: the MCP server cannot tell clients apart, the upstream log shows the wrong caller, and a stolen token becomes a way through the MCP server into the upstream API. If the MCP server calls an upstream API, that call uses a different token, issued for that API.
Selected duties in the legacy security page
This is a selection, not a complete security checklist. The source also covers OAuth state validation, cookie signing and client binding, URL-scheme validation, avoiding shell invocation for URLs, stdio proxy escalation and scope minimisation.
A proxy that uses one static client id at a third-party authorisation server, while registering MCP clients dynamically, is the confused-deputy case. A consent cookie set for the static client can cause the third party to skip its consent screen, and a later request with an attacker's redirect URI collects an authorisation code. The mitigation the page requires is consent stored per MCP client_id, checked before the third-party flow starts. The consent page must name the client, show the third-party scopes, show the registered redirect URI, use a CSRF control, and refuse to be framed. Redirect URIs match by exact string. If consent is a cookie, the name uses the __Host- prefix and the cookie is Secure, HttpOnly, and SameSite=Lax.
Session hijacking, on that page, is what happens when a session id is treated as proof of who is calling. One variant injects an event into a shared queue so that a resume delivers it to the real client, including a tool-list change the client did not enable. The other variant simply presents the stolen id and is treated as the user. Servers that implement authorisation must verify every inbound request. They must not use the session as authentication. Session ids must be non-deterministic. Binding the stored key to the user as well as the session, in the form the page suggests (user_id:session_id), means a guessed id is not enough.
A local server is the remaining case, and it is the one stdio makes ordinary. The page's examples of a malicious startup command are a shell line that posts a private key, and a shell line that deletes files. A client that offers one-click local configuration must show a consent dialog first. The dialog must show the exact command, without truncation, say that it executes code, and require an explicit approval. The page also says the client should warn on patterns such as sudo, rm -rf, network access, and reads of home directories or SSH keys, and should say that the server runs as the client. Sandboxing is a recommendation on that page, not a property of the protocol.
OAuth discovery is also a request the client makes to a URL the server supplied. The security page treats a metadata URL of http://169.254.169.254/, a private address, or localhost as server-side request forgery: the client fetches an internal target because the server told it to look there for an authorisation server. Clients deployed as servers must consider that risk. The page lists it because the discovery fields are attacker-controlled if the MCP server is.
What to choose and why
| You are choosing | The revision's rule | Why it is that way |
|---|---|---|
| A server on the same machine, started by the client | stdio. Credentials from the environment. Do not implement the HTTP OAuth flow for it | The authorisation page says stdio should not follow that specification |
| A server other processes and users connect to | Streamable HTTP, with Origin checks, authentication, and a bind address that is not the whole machine when the server is local |
The transport page names DNS rebinding as the failure mode |
| Whether a session id is enough to know the caller | No. Every authorised request still carries a bearer token, and the session is not authentication | The authorisation page requires the header on every request. The security page forbids using the session as authentication |
| A token the user already holds for another API | Do not send it to the MCP server, and do not have the MCP server forward it | Audience binding and the ban on token passthrough |
| An old client that speaks HTTP+SSE | Keep the old endpoints if you must, and have new clients try Streamable HTTP first | The transport page's compatibility procedure. The old transport is the one this revision replaced |
| A new tool | Advertise tools, and treat the description as untrusted unless the server is trusted. The host asks the user before the call |
The overview's tool-safety principle, which the protocol does not enforce |
A custom transport is allowed. It must still carry JSON-RPC and obey the lifecycle. It does not get to skip the capability negotiation because the bytes moved over a different socket.
Risks and limits
- Consent is not a protocol message. The overview lists user consent, data privacy, tool safety, and sampling controls, and then says the protocol cannot enforce them. A server that never asks is still speaking MCP.
- Tool descriptions and tool-list notifications are a trust boundary. The security page's session-injection case is a
notifications/tools/list_changedthe client did not expect. A changed list is a changed set of code the model may be offered. - stdio logging on standard output breaks the stream. The transport page forbids any non-message byte there. Standard error is the log channel, and it is not an error indicator.
- A missing
MCP-Protocol-Versionheader is not "latest". With no other signal, the server should assume2025-03-26. - PKCE is mandatory for the client, and an authorisation server that does not advertise it is one the client must refuse. A deployment that skips the check is outside the specification.
- The confused-deputy mitigations apply to proxy servers that hold a static client id for a third party. A server that is only its own resource server does not thereby satisfy a proxy's consent-storage rules, and a proxy that ignores them is the attack the page describes.
- No server was started for this article. The detailed contract is the superseded 2025-11-25 revision; the 2026-07-28 migration differences are summarised above and require current implementation review.
- No NetLeverage product behaviour is claimed.
Sources
- Model Context Protocol specification, revision 2025-11-25 — https://modelcontextprotocol.io/specification/2025-11-25 (opened 10 November 2026).
- Transports — https://modelcontextprotocol.io/specification/2025-11-25/basic/transports (opened 10 November 2026).
- Lifecycle — https://modelcontextprotocol.io/specification/2025-11-25/basic/lifecycle (opened 10 November 2026).
- Authorization — https://modelcontextprotocol.io/specification/2025-11-25/basic/authorization (opened 10 November 2026).
-
Security best practices — https://modelcontextprotocol.io/specification/2025-11-25/basic/security_best_practices (opened 10 November 2026).
-
Current MCP specification — https://modelcontextprotocol.io/specification/latest. Served revision 2026-07-28 during review.
- MCP 2026-07-28 changelog — https://modelcontextprotocol.io/specification/2026-07-28/changelog. Migration differences, not a publication-date claim.
This article was originally published on .