You stop a tool call mid-flight — close the chat, hit stop, move on to something else — and the natural assumption is that whatever the tool was doing stops too. Sometimes it does. Often it doesn’t. MCP cancellation is the protocol’s mechanism for asking a server to abandon an in-flight request, and like several of MCP’s other signals, it’s a request to stop, not a guarantee that anything actually did.

How it actually works

Every MCP request carries an ID. To cancel one, either side — client or server — sends a notifications/cancelled notification naming that ID, with an optional free-text reason. It’s a notification, not a request: it doesn’t get a response, and the side that sent it doesn’t wait to find out whether it worked. The receiver should stop processing the named request and should not send a result for it, but the spec is explicit that this can race: the request may already have finished by the time the cancellation arrives, in which case the notification just shows up too late and nothing happens. There’s one exception carved out directly in the spec — the initial initialize request can’t be cancelled, since cancelling the handshake itself would leave the connection in an undefined state.

Notice what’s missing from that description: nothing about what the server was actually doing when the notification arrived. Cancellation tells a server to stop sending work back over the MCP connection. It says nothing about whatever that server had already set in motion on the other side of its own tool call.

What it doesn’t give you

If a tool call is a pure read — a search, a lookup, a database query — cancelling it is close to free. The server drops the in-flight work, nothing was changed, and the only cost was the compute already spent. The problem is a tool call that isn’t a pure read: send an email, create a ticket, charge a card, kick off a deployment. By the time a cancellation notification reaches the server, the external side effect may already be underway or finished, and nothing in the MCP spec requires — or even describes — a way to undo it. The notification can stop the server from telling you the email sent. It can’t un-send the email.

This is the same gap we described for tool idempotency: a cancelled call that actually completed looks, from the model’s side, identical to one that failed outright — no result came back either way. If the model (or the person driving it) reacts by simply retrying, a cancelled-but-completed action can fire twice. Whether that matters depends entirely on whether the specific tool is safe to run more than once for the same input, which is a property of the tool, not of cancellation itself.

There’s also no guarantee the cancellation message is sent at all. Stopping generation in a chat client and stopping the underlying MCP request are two different things, and a client can do the first without ever doing the second. Whether “stop” in whatever AI client you’re using actually reaches the server as a notifications/cancelled message, or just stops the client from waiting on a response it still lets the server finish computing, is an implementation detail of that specific client — not something the protocol standardizes end to end.

Where this matters more than usual

Cancellation is most worth understanding for exactly the tool calls people are most tempted to interrupt: the slow ones. A crawl that’s taking too long, an export that looks stuck, a search against a huge index. These overlap heavily with the use case for MCP Tasks, the newer mechanism for long-running work that returns a reference instead of blocking the connection. The two aren’t the same feature, but they share a failure mode: a task you abandon by simply no longer polling it isn’t the same as a task you’ve told the server to stop, and a server has no obligation to notice the difference on its own.

The practical rule is narrow but useful: before you treat “stop” as undo, check whether the specific tool you just interrupted does anything besides read. For a lookup, interrupting it is harmless. For anything that writes, sends, charges, or triggers, assume the action may have already happened until you can confirm otherwise — the same caution we’d apply to any best-effort protocol signal that tells you about server-side state without being able to enforce anything about it.

If you build a server

Handle notifications/cancelled where it’s cheap to handle: stop internal loops, close connections you’re holding open, free resources tied to the request. Don’t promise more than that in a tool’s description — if a tool kicks off something irreversible as one of its first steps, say so plainly, so a client (or a human watching the call) knows that cancelling after that point is cosmetic. If a tool’s action genuinely can be undone — a draft that can be discarded, a job that can be killed cleanly — that undo path has to be built deliberately; nothing about implementing cancellation gives it to you for free.

Where a gateway fits: a per-tool call log is the only reliable way to know whether “cancelled” and “completed” actually diverged for a given call, since neither the client UI nor the cancellation notification itself tells you. A gateway sitting in front of your MCP servers can record what a tool call actually did, not just whether the client gave up waiting on it — and whether a server’s tools are the kind where that distinction is low-stakes or high-stakes is exactly what gate’s free MCP security scanner is built to surface before you connect one.

The bottom line

MCP cancellation lets either side ask the other to stop working on a specific request, and in the common case of read-only tools, that’s exactly as simple as it sounds. For anything that writes, it’s advisory, not transactional: the notification can arrive too late, can be ignored, and can’t reach back into an external system to reverse what a tool already did. Treat “I stopped it” as a question to verify, not an answer, for any tool whose job is more than reading something back.