When Anthropic shipped MCP in November 2024, it was a working draft — solid enough to build on, but everyone knew changes were coming. Four months later, on March 26, 2025, the first major spec revision landed. It wasn't just a patch. It changed how servers connect to clients, how they authenticate, and how they declare what their tools actually do. If you built anything on the 2024-11-05 spec, here's exactly what shifted and whether you need to act.

What the 2024-11-05 Release Actually Was

The original MCP spec, published November 5, 2024, established the core protocol architecture: a client-server model where AI hosts (like Claude Desktop) connect to MCP servers that expose tools, resources, and prompts. All communication used JSON-RPC 2.0 as the message format.

For transport — the mechanism that actually moves those messages — the spec defined two options:

  • stdio: The client spawns the server as a subprocess and communicates over standard input/output. Simple, reliable, works great for local tools.
  • HTTP + SSE: The client sends HTTP POST requests to the server, while the server pushes responses back over a persistent Server-Sent Events stream. This was the intended path for remote servers.

The spec was intentionally lean. No authentication standard. No way for tools to declare their own behavior. No request batching. These weren't oversights — they were deliberate choices to ship something usable while the community figured out what was actually needed. Four months of real-world usage gave Anthropic that feedback.

The Four Big Changes in 2025-03-26

The March 2025 update touched four distinct areas. Each one addresses a real limitation that developers hit when trying to deploy MCP in production:

  1. Streamable HTTP transport replaced SSE as the standard for HTTP-based servers
  2. OAuth 2.0 authentication was formalized for remote connections
  3. Tool annotations were introduced for declaring tool behavior
  4. JSON-RPC batching was added for sending multiple requests at once

We'll walk through each one. The first two are the most consequential for anyone running or planning to run remote servers.

Streamable HTTP: What It Is and Why SSE Had to Go

The original HTTP + SSE transport had a structural problem. It required a persistent SSE connection to be open for the entire session — the server would send all responses over this long-lived stream. This works fine in a simple setup, but it falls apart in production environments.

Load balancers time out long-lived connections. Serverless functions can't maintain persistent state between invocations. API gateways often strip or block SSE headers. The old transport made MCP servers hostile to exactly the infrastructure most production services run on.

Streamable HTTP solves this by making streaming optional rather than mandatory. Here's how it works:

  • The client sends a standard HTTP POST to a single endpoint (typically /mcp)
  • For simple request-response pairs, the server replies with a plain JSON response — no streaming, no persistent connection
  • For operations that need streaming (long-running tasks, progress updates), the server can respond with an SSE stream for that specific request
  • The client signals it can accept streaming by including Accept: text/event-stream in its request headers

The result is a transport that works with any HTTP infrastructure. A Cloudflare Worker, an AWS Lambda, an Express.js server on a standard VPS — all of them can serve a Streamable HTTP MCP endpoint without special configuration.

# Old SSE transport — two endpoints required
GET  /sse          ← persistent connection, server pushes all responses here
POST /messages     ← client sends all requests here

# New Streamable HTTP — single endpoint
POST /mcp          ← client sends requests here
                    ← server replies inline (JSON) OR streams (SSE) per-request
Streamable HTTP collapses two endpoints into one and makes the persistent connection optional.

SSE-only transport is still in the 2025-03-26 spec but is explicitly marked as deprecated. The expectation is clear: new servers should implement Streamable HTTP. For a deeper comparison of the two transports, see our dedicated guide on MCP Streamable HTTP vs SSE.

OAuth 2.0: Secure Remote Connections Are Now Standardized

The 2024 spec said nothing about authentication. If you wanted to secure a remote MCP server, you were on your own — implement an API key check, roll your own token validation, or just leave it open. None of those options are great when you're connecting an AI agent to production systems.

The March 2025 update added a formal OAuth 2.0 authorization framework to the spec. Specifically, it adopts OAuth 2.0 with PKCE (Proof Key for Code Exchange), which is the current best practice for interactive applications.

Here's what this enables in practice:

  • A user can authorize an MCP client to access a remote server on their behalf, without giving the client their password
  • Access tokens are scoped — a server can grant read-only access to one client while granting full access to another
  • Tokens expire, and the spec includes the refresh token flow for getting new ones
  • The authorization server can be the MCP server itself, or a separate identity provider

For developers building local tools for personal use, this changes nothing — stdio transport doesn't need authentication. But for anyone shipping a hosted MCP server that users will connect to remotely, OAuth 2.0 is now the specified path rather than an improvised one.

// Authorization flow for remote MCP connection (simplified)
// 1. Client discovers the auth endpoint
GET /.well-known/oauth-authorization-server

// 2. Client redirects user to authorize
GET /authorize?response_type=code
               &client_id=my-mcp-client
               &code_challenge=...      // PKCE
               &scope=tools:read

// 3. User approves — server redirects back with auth code
// 4. Client exchanges code for access token
POST /token
  { "grant_type": "authorization_code", "code": "...", "code_verifier": "..." }

// 5. Client includes token in all MCP requests
POST /mcp
  Authorization: Bearer <access_token>
The OAuth 2.0 PKCE flow as applied to remote MCP server connections.

Tool Annotations: Telling Clients What a Tool Actually Does

Before March 2025, every MCP tool looked the same from the client's perspective: a name, a description, and an input schema. The client had no structured way to know whether calling that tool would read a file, delete a database row, or send an email.

Tool annotations fix this by adding optional behavioral hints to tool definitions. There are three:

  • readOnlyHint: Set to true if the tool never modifies any state. The client can safely call it without asking the user first.
  • destructiveHint: Set to true if the tool performs changes that can't be undone. Clients should require explicit user confirmation before running it.
  • idempotentHint: Set to true if calling the tool multiple times with the same inputs produces the same result. Useful for retries and caching logic.

These are hints, not enforcement. The spec is explicit: clients should use them to improve UX, but they cannot trust them as security boundaries. A malicious server could lie. The annotations are for making good UI decisions — like whether to show a confirmation dialog — not for access control.

// Tool definition with annotations (2025-03-26 format)
{
  "name": "delete_file",
  "description": "Permanently deletes a file from the filesystem.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "path": { "type": "string", "description": "Absolute path to the file" }
    },
    "required": ["path"]
  },
  "annotations": {
    "readOnlyHint": false,
    "destructiveHint": true,
    "idempotentHint": false
  }
}

// vs. a safe read tool
{
  "name": "read_file",
  "description": "Returns the contents of a file.",
  "inputSchema": { ... },
  "annotations": {
    "readOnlyHint": true,
    "destructiveHint": false,
    "idempotentHint": true
  }
}
Tool annotations give clients structured information about what a tool does before it's called.

JSON-RPC Batching: Fewer Round-Trips

The March 2025 spec added support for JSON-RPC batch requests. Instead of sending ten separate requests and waiting for ten separate responses, a client can bundle them into a single array and get all the responses back at once.

This is particularly useful for initialization sequences — fetching the tool list, resource list, and server capabilities simultaneously rather than one after another. In high-latency environments (remote servers, slower connections), the savings can be significant.

// Batch request — one HTTP round-trip instead of three
[
  { "jsonrpc": "2.0", "id": 1, "method": "tools/list",     "params": {} },
  { "jsonrpc": "2.0", "id": 2, "method": "resources/list", "params": {} },
  { "jsonrpc": "2.0", "id": 3, "method": "prompts/list",   "params": {} }
]

// Batch response — all three results in one reply
[
  { "jsonrpc": "2.0", "id": 1, "result": { "tools": [...] } },
  { "jsonrpc": "2.0", "id": 2, "result": { "resources": [...] } },
  { "jsonrpc": "2.0", "id": 3, "result": { "prompts": [...] } }
]
Batching lets clients fetch multiple data types in a single network round-trip.

Note: JSON-RPC batching had a short life in the spec. It was added in this March update but removed again in the June 2025 revision. If you implemented batching against the March spec, you'll need to read about the March vs June 2025 changes before making any assumptions about your current setup.

What the 2025-03-26 Spec Did Not Change

It's worth being clear about what stayed the same, because some developers assumed the March update was a more sweeping rewrite:

  • The core JSON-RPC 2.0 message format is unchanged
  • The three capability types — tools, resources, prompts — are unchanged
  • stdio transport is unchanged and remains the recommended approach for local servers
  • The capability negotiation handshake at connection start is unchanged
  • Sampling (letting servers request LLM completions from the client) is unchanged

If you have a working local stdio server built on the 2024-11-05 spec, it almost certainly still works. The breaking changes are concentrated in the HTTP transport layer.

Migration: What You Actually Need to Update

Here's a practical breakdown by server type:

Local stdio servers (Claude Desktop plugins, personal tools)

You don't need to change anything for compatibility. However, adding tool annotations is a low-effort improvement worth doing — it gives Claude better context about what your tools can and can't do, which leads to fewer unnecessary confirmation prompts.

Remote HTTP servers built on the old SSE transport

This is where real work is required. You need to:

  1. Implement the Streamable HTTP transport on a single /mcp endpoint
  2. Handle both plain JSON responses (for simple operations) and SSE streams (for long-running ones)
  3. Implement OAuth 2.0 if your server is accessed by multiple users
  4. Keep the old SSE endpoint running temporarily for any clients that haven't updated yet

SDK users

If you're using the official Anthropic Python or TypeScript MCP SDKs, check the changelog. Both SDKs updated to support 2025-03-26 features within weeks of the spec release. In most cases, transport migration is handled by changing a configuration option, not rewriting server logic.

Frequently Asked Questions

Not immediately — the 2024-11-05 spec still works for local stdio-based servers. But if you plan to run remote servers over HTTP, you'll need to move to Streamable HTTP transport and implement OAuth 2.0 authentication. The old SSE-only transport is deprecated and may stop being supported by new clients in the future.

Streamable HTTP is a transport mechanism that uses standard HTTP POST for requests and optionally upgrades to Server-Sent Events (SSE) for streaming responses. SSE alone required a persistent connection for every session; Streamable HTTP makes streaming optional, which is far more compatible with standard web infrastructure like load balancers and API gateways. A serverless function can serve Streamable HTTP; it can't maintain a persistent SSE stream.

OAuth 2.0 is required only for remote MCP servers accessed over HTTP. Local servers running over stdio — the most common setup for Claude Desktop tools — are not affected. For remote servers, the spec mandates OAuth 2.0 with PKCE as the authorization mechanism.

Tool annotations are optional fields you add to a tool's definition to describe its behavior: readOnlyHint (true if the tool never changes data), destructiveHint (true if changes can't be undone), and idempotentHint (true if calling the tool multiple times produces the same result). Clients use these hints to decide whether to ask the user for confirmation before running a tool. Add them to the annotations field of your tool definition — they're optional, so existing tools that omit them keep working.