Covers: Why the change was made; Capabilities and what to do without them; Form mode; URL mode, and the person who is not the person.
Why the change was made
The earlier mechanism kept the call open and the server thread parked waiting. That has costs that only appear at scale, and the example makes three of them visible.
Situation
Earlier era (server waits)
Modern (state travels)
Server threads parked while the person thinks
1
0
The answer reaches a different instance (round-robin balancer)
timed out, 0 entries
completed, 1 entry
The server instance dies while waiting
call lost, 0 entries
finished on a new instance with the same key; 1 entry
New instance has a different key
n/a
refused: bad signature
The earlier mechanism is not wrong for a single local process. It is hard to scale and hard to survive, which is the same lesson as module 3's stateless HTTP.
The example runs the same tool body in both eras: ctx.need(key, request) returns the answer in the earlier era by waiting, and in the modern era by stopping and asking. That is how the table above was measured on identical code, and it is a handy pattern for a server that must talk to old clients for a while.
Capabilities and what to do without them
A client declares what it can do when it connects: elicitation (an empty object means form only; url must be named), sampling (with tools and context sub-capabilities), roots. A server must not send what the client did not declare. The example gates every request in one function and gives the model a message instead.
Measured with a client that declared no elicitation: the server sent 0 requests, the tool returned an error that said "Amounts above 100 need the user's confirmation and this client cannot ask. Ask the user in the conversation; if they agree, call submit_expense again with confirmed=true", and 0 entries were submitted. When the model did exactly that, 1 entry was submitted. The same gate refused URL mode to a forms-only client, sampling to a client with none, and sampling with tools to a client that declared sampling without tools.
Warning: a fallback flag is a weaker control
A confirmed=true argument lets the model say "the user agreed". It is only as honest as the model's belief, which is a reason to keep the high-risk actions behind a host-side approval (section 10) and to use this fallback only where a mistake is cheap.
Form mode
A form is a flat object with primitive properties: strings (with length limits and the formats email, uri, date, date-time), numbers and integers (with minimum and maximum), booleans, and single or multiple choice from a fixed list. Nested objects and arbitrary arrays are not allowed, and the example host refuses them (a: nested objects are not allowed). This keeps the client's job small: render a handful of fields and validate them.
The host should check the answer against the schema before sending it on, and ask again on a bad one. In the example, a person who typed middle for a field allowing window or aisle was re-asked (3 prompts in all) and the server received a valid answer. Remember that the server must check too: the client's check is a convenience, and a malicious client can send anything.
Never ask for secrets in a form. The specification says servers must not request passwords, API keys, tokens or payment credentials through form mode; those need URL mode. The reason is structural: form data passes through the client, which means through the model's host, its logs, and possibly the model.
The example host has a screening heuristic that refused a form titled "Paste your cloud API key": 0 prompts reached the person. The same request with the field renamed value_2 and the message "Paste the value we emailed you" was shown to the person. That is the honest limit of any such filter: it catches the careless author, not the determined one, and the rule is for the server author to follow, with the host's check as a seat belt.
URL mode, and the person who is not the person
URL mode sends the person to a page outside the client, where the secret never touches the client or the model. The server returns a message and a link. The client must show the full address, get consent before opening it, never prefetch it, open it in a way that the client and the model cannot read, highlight the domain, and warn about punycode (encoded international names that imitate familiar ones). The server must not put sensitive data or a pre-authenticated URL in the address.
Accepting means the person agreed to open the page, not that the work is done. The tool must check for completion itself. In the example, calling the tool again before the sign-in finished returned "The sign-in is not finished yet", and after the web page recorded the grant, the next call returned connected.
The example's address inspection, applied to seven addresses:
blocked: user-name trick, and the real host is evil.example
https://xn--mcp-example-9ub.com/connect
allowed with warnings: punycode, not the server's host
javascript:alert(1)
blocked: scheme
https://other.example/login
allowed with a warning: not the server's host
https://mcp.example.com/connect?token=abc123
allowed with a warning: a secret in the address
The attack on the person, not the connection
Here is the one that is not obvious. Alice triggers a tool that needs her to connect her calendar. The server issues a link. Bob, tricked by a message, opens Alice's link and signs in with his calendar. If the server only records "this link was completed by someone", Alice's account is now connected to Bob's calendar, and her future calls act on it.
The specification's answer is that the server must verify that the user who completes the web flow is the user who started the elicitation, for example by checking the signed-in user of the web session against the request's user, and must never trust an identity supplied by the client. Measured: without the check, Bob's link was accepted and Alice ended up connected to bob@calendar; with it, Bob was refused ("this link was issued to someone else") and Alice stayed unconnected.
The two attacks that matter, and where the real control sits
Two related rules from the page: a third party's tokens never pass through the client (the server holds them), and token passthrough is forbidden. URL mode is for third-party sign-in and secrets; it is not how a client authenticates to the MCP server. That is module 5.