# MCP protocol revisions

> The MCP wire generations and dated revisions Fixter can speak, what Newest supported and a pinned revision mean, how detection works, and how to watch several revisions with pinned tests.

This page covers the three generations of the MCP protocol and their five revisions, how an MCP
test picks the revision it speaks, how Fixter detects what your server speaks, and how to watch
that your server keeps accepting the revisions your clients use.

:::note[Note]
Protocol revision handling is under active development. This page reflects current behaviour
and may be revised as that work progresses.
:::

## Generations

MCP has had five dated protocol revisions, and they do not all open a conversation the same way.
Fixter groups them into three **generations** by that opening, because the opening is what a
test has to get right before it can check anything else, and the app shows the generation
beside every revision:

- **Stateless** (`2026-07-28`, the current revision): the test sends one `server/discover`
  request. There is no `initialize`, no event stream and no session.
- **Sessions** (`2025-03-26`, `2025-06-18`, `2025-11-25`): the test sends `initialize`
  proposing the revision, then `notifications/initialized`, and deletes the session afterwards
  if the server minted one. It never opens an event stream.
- **HTTP+SSE transport** (`2024-11-05`, deprecated): the test opens a GET event stream, waits up
  to two seconds for its first event, `endpoint`, then sends `initialize` and
  `notifications/initialized` to that endpoint.

In every generation the test identifies itself as `fixter-monitoring`. A server that answers
none of these openings has not identified itself, and the run fails `mcp.initialize`; see
[What a failed run says](/api-testing/mcp-tests/#what-a-failed-run-says).

Servers move from one revision to the next at their own pace and often accept several at once,
so every MCP test speaks one revision: the newest the server accepts, or one you pin. A set of
pinned tests watches several revisions at once.

## Revisions

| Revision | Generation | Note | Sends `MCP-Protocol-Version` |
|---|---|---|---|
| `2024-11-05` | `HTTP+SSE transport` | `deprecated, eligible for removal` | Never |
| `2025-03-26` | `sessions` | `not the current revision` | Never |
| `2025-06-18` | `sessions` | `not the current revision` | On every request after `initialize` |
| `2025-11-25` | `sessions` | `not the current revision` | On every request after `initialize` |
| `2026-07-28` | `stateless` | `current` | On every request, `server/discover` included |

The generation and note columns are the labels the app shows. The `Protocol revision` picker
in the test editor lists each revision in the same form, for example
`2026-07-28 · stateless · current`, after `Newest supported`.

## Newest supported

`Newest supported` (`NEWEST_SUPPORTED`) is the default. The editor's help text says:
`Newest supported runs against the newest protocol revision the server speaks and moves with it
when the server changes. A dated revision pins the test and it fails if the server stops
accepting it.` Over MCP, `create_api_test` and `update_api_test` take the revision as
`mcpProtocolRevision`; leaving it out means `NEWEST_SUPPORTED`.

The revision the test currently speaks is kept on the test as `currentRevision`, and the test
page shows `Protocol: Newest supported (currently <revision>)`, or
`Protocol: Newest supported (not run yet)` before the first run. Fixter looks for a newer or
older revision only when a run meets one of the era signals described under
[When a test looks up the revision again](#when-a-test-looks-up-the-revision-again).

When a run speaks a different revision from the one before, the run is judged normally: it
passes when its assertions pass. It records the change in `protocolRevision` and
`previousRevision`, and its row in the run history reads `now <protocolRevision>, was
<previousRevision>`, for example `now 2026-07-28, was 2025-11-25`. A move is not an alert: no
notification is sent for it.

### When a test looks up the revision again

The lookup a run does is shorter than a detection you ask for: it tries the newest generation
first, stops at the first that answers, and sends at most four requests. A `Newest supported`
test with no current revision runs it before its first run. After that, any test runs it again
only when a run meets an era signal, a reply that says the server has moved generation:

- On a stateless revision: a 400, 404 or 405 reply to `server/discover` whose body is not one of
  the JSON-RPC errors `-32020`, `-32021`, `-32022` or `-32601`, or a `-32601` reply at any status.
- On a sessions revision: a 4xx other than 401 and 403 in reply to `initialize` whose body is one
  of those errors.
- On the HTTP+SSE transport: a GET that does not open a stream whose first event is `endpoint`.

Timeouts, connection errors, 5xx, 401, 403, 429 and failed assertions never trigger it. The
lookup sends `server/discover`, then `initialize` proposing `2025-11-25`, then the HTTP+SSE GET;
a 401, 403 or 5xx ends it with no server found, and the run fails `mcp.initialize` with
`the target did not identify itself as an MCP server`. A run keeps at most one lookup across its
retry attempts, and the test's timeout covers it. After the lookup, a `Newest supported` test
repeats the attempt at the revision it found, and a pinned test fails `mcp.protocol.revision`.

## A pinned revision

Pick a dated revision in `Protocol revision` to pin the test to it. The test page then shows
`Protocol: <revision> (pinned)`. When the server stops accepting the pinned revision, the run
fails assertion `mcp.protocol.revision` with the sentence:

```text
Server no longer speaks protocol <pin>
```

The assertion's expected value is the pinned revision, and its actual value is the revision
the server offers instead, or `unsupported`. A sessions server that answers `initialize` with
a different sessions revision from the one proposed fails a pin the same way; a
`Newest supported` test adopts the revision it answered with.

Pin when an agent or client you depend on speaks that revision and must keep working. Use
`Newest supported` when you only care that the server stays healthy on whatever it speaks.

## Detection

A detection asks a server which of the five revisions it accepts. It probes each revision on its
own (it does not stop at the first one that answers), in at most eight requests and within 20
seconds. A detection is not a run: it needs no saved test, is never written to a test's run
history and does not change any test's state or uptime.

### With your agent

`detect_mcp_protocol` is read-only and takes `mcpUrl` and, optionally, `mcpCredentialId`. See
[the API tests tools](/reference/mcp/tools/#api-tests-and-uptime). The answer carries:

- `outcome`: `DETECTED` when at least one revision is supported, `NONE` when none is,
  `RATE_LIMITED` when the server answered with a rate limit, with its `Retry-After` value in
  `retryAfterHeader`.
- `newestSupported` and the server's `serverInfo`.
- `revisions`, one entry per revision with `revision`, `generation`, `supported` and `note`,
  plus `serverInfo` for a supported revision or `evidence` for an unsupported one.

`NONE` is a normal answer, not an error, and every entry then carries its evidence: `status`,
`contentType`, `bodyPreview`, `jsonRpcErrorCode`, `jsonRpcErrorMessage`, `millis`,
`reachabilityFailure` and `message`. The tool also gives one sentence per revision, for
example `2026-07-28 (stateless): supported. This is the current protocol revision.` or
`2025-06-18 (sessions): not supported (HTTP 400, application/json).`

### In the app

The test editor detects automatically once the URL looks
complete (it starts with `http://` or `https://`, has no spaces, and its host is `localhost`,
an IP address or a domain whose last label has two or more letters). It detects after a 1.5
second pause in typing, at once when you paste the URL or leave the field, and again when you
change the credential. Editing an existing test does not detect while its URL is untouched.
While it works the editor shows `Detecting protocol…`. On `DETECTED` it sets
`Protocol revision` to `Newest supported`, shows `currently <revision>` beside it, and lists
every revision in a table with the columns `Revision`, `Generation`, `Result` (`Supported` or
`Not supported`) and `Note`. For each revision the server does not support, the result also
shows the reply's status, content type and first bytes. The other results read:

- `This URL did not identify itself as an MCP server on any revision.` (outcome `NONE`)
- `The server rate-limited the detection (HTTP 429); try again later.`
- `Detection failed.`

Once a result or an error is on screen, the `Detect again` link runs the detection again; it
is the only manual control.

## Watching several revisions

An MCP test speaks one revision. To check that your server keeps accepting several, for example
every revision the agents and clients you depend on speak, create one MCP test per revision:

- `Check depth`: `Handshake`.
- `Protocol revision`: the dated revision this test watches.

An agent does the same with `create_api_test`: type `MCP`, `mcpTier` `HANDSHAKE` and
`mcpProtocolRevision` set to the revision. Each test runs, fails and notifies on its own. When the
server drops a revision, only the test pinned to it fails, with
`Server no longer speaks protocol <pin>`, and its notification names the test.

For example, to keep three revisions working on an orders MCP server, create three tests:

| `Name` | `Check depth` | `Protocol revision` |
|---|---|---|
| `Orders MCP 2025-03-26` | `Handshake` | `2025-03-26 · sessions · not the current revision` |
| `Orders MCP 2025-06-18` | `Handshake` | `2025-06-18 · sessions · not the current revision` |
| `Orders MCP 2026-07-28` | `Handshake` | `2026-07-28 · stateless · current` |

If the server's next release drops both sessions revisions, the first two tests fail, one with
`Server no longer speaks protocol 2025-03-26` and the other with
`Server no longer speaks protocol 2025-06-18`, and the third keeps passing.

Saving each test runs it once first and saves it only if it passes, so a revision the server
does not accept today cannot be saved as a pinned test. To see which revisions the server
accepts right now, run a [detection](#detection): it lists every revision, supported or not,
without creating a test.