DEV Community

Christo
Christo

Posted on

MCP goes stateless on July 28 — catch every breaking change in your server before the spec locks

On July 28, 2026 the Model Context Protocol locks its 2026-07-28 specification — the biggest breaking change the protocol has shipped. The handshake and sessions are gone; the protocol goes stateless. If you run an MCP server, several things you rely on today stop working on that date.

I built mcp-vet, a zero-config CLI that scans your MCP server source (TypeScript, JavaScript, Python) for the exact patterns that break — and tells you what to change.

npx @booyaka/mcp-vet .
Enter fullscreen mode Exit fullscreen mode

mcp-vet output

No account, no API key, no network calls. It parses your code locally (ts-morph for TS/JS, a bundled Python ast script for .py) and exits non-zero on anything BREAKING, so it drops straight into CI.

What changes on 2026-07-28

Breaking — these stop working:

  • Mcp-Session-Id header and protocol-level sessions are removed. Client info and capabilities now travel in per-request _meta.
  • initialize / notifications/initialized handshake is removed. Protocol version, client info, and capabilities now arrive on every request.
  • Error code -32002 (missing resource) becomes the JSON-RPC standard -32602 Invalid Params.
  • Legacy Tasks methodstasks/get · tasks/update · tasks/cancel argument shapes change, and tasks/list and tasks/result are removed entirely (poll with tasks/get).

Deprecated — 12-month grace period: the roots, sampling, and logging capabilities.

It matches how servers are actually written

The trick to a useful migration linter is catching real code, not just raw method strings. mcp-vet matches:

  • literal method strings'tasks/list', 'sampling/createMessage', 'logging/setLevel', …
  • SDK schema-constant registrationserver.setRequestHandler(InitializeRequestSchema, …) (how the official SDKs register handlers) maps InitializeRequestSchema, ListRootsRequestSchema, CreateMessageRequestSchema, ListTasksRequestSchema, … to the right rule.
  • SDK capability constructors — the Python SDK's ClientCapabilities(roots=RootsCapability()) is recognized structurally.
  • sessionIdGenerator — flagged only when it's a real generator, not the migrated sessionIdGenerator: undefined.

Every finding carries a confidence (high / medium / low) so you can tune the signal with --min-confidence.

Validated on real servers — 0 false positives

Pointed at the official MCP TypeScript SDK's own example servers, it finds 6 real issues — including the sessionIdGenerator session usage that servers set but rarely spell out as a literal header — and stays quiet on the Mcp-Session-Id and initialize mentions that live only in comments:

legacy-routing.ts:36  BREAKING    MCP_SESSION_ID   req.headers['mcp-session-id']
legacy-routing.ts:41  BREAKING    MCP_SESSION_ID   sessionIdGenerator: () => randomUUID()
sse-polling.ts:34     DEPRECATED  LOGGING_CAP      capabilities: { logging: {} }

6 finding(s): 5 BREAKING, 1 DEPRECATED
Enter fullscreen mode Exit fullscreen mode

Run across a broad corpus of the official reference servers (TypeScript and Python), it produced 0 false positives — precision from structural AST checks, not text matching.

Fix the mechanical one automatically

The -32002 → -32602 swap is purely mechanical, so it's auto-fixable — with a preview first:

npx @booyaka/mcp-vet . --fix --dry-run   # show the rewrites, change nothing
npx @booyaka/mcp-vet . --fix             # apply them
Enter fullscreen mode Exit fullscreen mode

Drop it into CI

- uses: actions/setup-node@v4
  with: { node-version: '20' }
- run: npx @booyaka/mcp-vet . --github-annotations
Enter fullscreen mode Exit fullscreen mode

It also emits SARIF 2.1.0 for GitHub code scanning, plus Markdown/JSON reports, --json for piping, and inline suppression comments. And it's a typed library, not just a CLI:

import { scan, applyFixes } from '@booyaka/mcp-vet';
const { findings } = scan(['./src'], { /* ... */ });
Enter fullscreen mode Exit fullscreen mode

What it can't tell you (on purpose)

A few 2026-07-28 changes have no reliable static signal — the removed long-lived SSE push channel, the new required Mcp-Method / Mcp-Name headers, auth hardening, and JSON-Schema-2020-12 tool schemas. mcp-vet documents these and prints a reminder after every scan, so a clean result never pretends to mean "fully migrated."

The spec locks in days — better to find out now than when a client stops talking to your server.

Repo: https://github.com/Booyaka101/mcp-vet
Install: npx @booyaka/mcp-vet .

Top comments (1)

Collapse
 
raju_dandigam profile image
Raju Dandigam

Stateless MCP is the kind of change that sounds simple in the spec and turns messy in the glue code around auth, retries, and session assumptions. I like that this checks source patterns locally and fails CI, because most breakage will sit in wrappers and samples people forgot they copied six months ago. The part I'd add to migration plans is exercising client-side assumptions too: a lot of tool reliability bugs only show up when the server is stateless but the client still behaves as if it owns a session.