Introducing MCP 2.0 Support in Mastra

Upgraded support for MCP 2026-07-28.

Paul ScanlonPaul Scanlon·

Oct 21, 2026

·

6 min read

Mastra now supports the MCP 2026-07-28 specification with @mastra/mcp ^2.0.0. The new spec removes server sessions, so every request is self-contained. If you build MCP servers with Mastra, you can now scale them like any other HTTP service, across load-balanced instances, or on serverless platforms that scale to zero.

How MCP 1.0 worked

In the previous MCP spec, a client opened every connection with an initialize request. The server created a session, sent back a session ID, and every following request had to include it.

This pinned the client to whichever server instance issued the session. If you ran multiple servers behind a load balancer, a request could be routed to an instance that didn't recognize the session ID, resulting in 400 session not found errors.

The workarounds were sticky sessions or a shared session store like Redis. Both solutions worked, but they added complexity, latency, and potentially cost.

What changed in MCP 2.0

There are two key changes to the 2026-07-28 spec:

  • SEP-2575 removes initialize handshake.
  • SEP-2567 removes Mcp-Session-Id header and protocol-level sessions.

Every request is now independent and behaves like a regular HTTP request. Any instance can respond to any request, so a traditional round-robin load balancer works without sticky sessions or a session store. If an instance crashes, the load balancer routes the next request to another instance and the client continues as normal.

The new spec also opens up more deployment options. With no session to keep alive, a server doesn't have to stay up between requests. On serverless platforms it can scale to zero when it's not in use, saving you money.

HTTP headers

Every HTTP request now includes an Mcp-Method header, plus an Mcp-Name header for tool calls, prompts, and resources, letting gateways, rate limiters, and firewalls make decisions about the request without needing to parse the request body:

HTTP request
POST /api/mcp/mastra-blog/mcp HTTP/1.1
Content-Type: application/json
Accept: application/json, text/event-stream
Mcp-Protocol-Version: 2026-07-28
Mcp-Method: tools/call
Mcp-Name: emailPost
 
{
  "jsonrpc": "2.0",
  "id": 3,
  "method": "tools/call",
  "params": {
    "name": "emailPost",
    "arguments": { "slug": "introducing-code-mode" }
  }
}

Cache hints

List results such as tools/list and resources/list now include two hints borrowed from HTTP caching:

  • ttlMs: How long the result stays fresh.
  • cacheScope: Whether the result can be shared across users (public) or only reused for the caller (private).
TypeScriptsrc/mastra/mcp/blog-server.ts
export const blogServer = new MCPServer({
  // ...
  cacheHints: {
    "tools/list": { ttlMs: 60_000, cacheScope: "public" },
    "resources/list": { ttlMs: 30_000, cacheScope: "private" }
  }
});

server/discover

The server/discover method helps you understand an MCP server's capabilities. In Mastra, use getServerInfo() to see the underlying details:

TypeScriptsrc/mastra/mcp/blog-client.ts
import { MCPClient } from "@mastra/mcp";
 
export const blogClient = new MCPClient({
  servers: {
    blog: { url: new URL("http://localhost:4111/api/mcp/mastra-blog/mcp") }
  }
});
 
await blogClient.listTools(); // Connects to the server and runs server/discover
 
blogClient.getServerInfo(); // Returns the server's name, version, and description

A response includes the supported MCP spec versions, its capabilities, and a name and description:

JSONresponse
{
  "jsonrpc": "2.0",
  "id": 1,
  "result": {
    "supportedVersions": ["2026-07-28"],
    "capabilities": { "tools": { "listChanged": true } },
    "_meta": {
      "io.modelcontextprotocol/serverInfo": {
        "name": "Mastra Blog",
        "version": "1.0.0",
        "description": "Search and read posts from the Mastra blog."
      }
    }
  }
}

requestState

The requestState lets a server ask the user a question mid-call without holding the connection open. In Mastra, use suspend() from a tool's execute function to ask the question and resumeData to read the answer:

TypeScriptsrc/mastra/tools/email-post.ts
import { createTool } from "@mastra/core/tools";
import { z } from "zod";
 
export const emailPost = createTool({
  // ...
  resumeSchema: z.object({ email: z.string() }), // The answer you need, sent to the client as a form
  execute: async ({ slug }, context) => {
    if (!context.resumeData) {
      await context.suspend?.({ message: "What email address should I send the post to?" }); // Ends the call with the question and a requestState
      return;
    }
    return { sentTo: context.resumeData.email }; // resumeData holds the user's answer
  }
});

A response includes the question and a requestState, a serialized string of the paused call:

JSONresponse
{
  "jsonrpc": "2.0",
  "id": 4,
  "result": {
    "resultType": "input_required",
    "inputRequests": {
      "input": {
        "method": "elicitation/create",
        "params": {
          "mode": "form",
          "message": "What email address should I send the post to?",
          "requestedSchema": { "type": "object", "properties": { "email": { "type": "string" } } }
        }
      }
    },
    "requestState": "v1.eyJwIjp7Im1ldGhvZCI6InRvb2xzL2NhbGwi…"
  }
}

Deserialized, the requestState is Mastra's record of the paused call: the tool, a fingerprint of its arguments, the question, and who asked.

JSONrequestState (deserialized)
{
  "method": "tools/call",
  "name": "emailPost",
  "argsHash": "I4GGwEU5Xtn4_l7aKiUxlKOEvzksi6Soa9j9xJB6TZU",
  "round": 1,
  "suspendPayload": { "message": "What email address should I send the post to?" },
  "principal": "anonymous",
  "iat": 1792419520
}

Tasks

Tasks, previously an experimental feature, are now an official extension. A long-running tool call can respond straight away with a task handle and keep working in the background, while the client checks on it with tasks/get until the result is ready.

We're investigating options for supporting tasks in @mastra/mcp. In the meantime, a long-running tool can report progress with context.mcp.progress().

TypeScriptsrc/mastra/tools/summarize-posts.ts
import { createTool } from "@mastra/core/tools";
 
export const summarizePosts = createTool({
  // ...
  execute: async ({ slugs }, context) => {
    for (const [index, slug] of slugs.entries()) {
      // ...
      await context.mcp?.progress?.({
        progress: index + 1,
        total: slugs.length,
        message: `Summarized ${slug}`
      });
    }
    return { done: true };
  }
});

Deprecations

The spec deprecates the HTTP+SSE transport, roots, sampling, logging, and OAuth Dynamic Client Registration. The deprecated features registry lists the migration path and earliest removal date for each. @mastra/mcp 2.0 has already removed the SSE transport, roots, sampling, and Dynamic Client Registration, and it only accepts clients on the 2026-07-28 spec.

Upgrading from MCP 1.0

The migration guide explains how to upgrade in more detail.

Get started

Install the latest @mastra/core and @mastra/mcp:

GNU BashTerminal
npm install @mastra/core@latest @mastra/mcp@latest
note
Requires @mastra/mcp@2.0.0 and @mastra/core@1.76.0 or later, added in PR #23876.

Create an MCPServer and pass it your tools:

TypeScriptsrc/mastra/mcp/blog-server.ts
import { MCPServer } from "@mastra/mcp";
 
export const blogServer = new MCPServer({
  id: "mastra-blog",
  name: "Mastra Blog",
  version: "1.0.0",
  description: "Search and read posts from the Mastra blog.",
  tools: {
    /* ... */
  }
});

Register the server on your Mastra instance:

TypeScriptsrc/mastra/index.ts
import { Mastra } from "@mastra/core";
import { blogServer } from "./mcp/blog-server";
 
export const mastra = new Mastra({
  // ...
  mcpServers: { "mastra-blog": blogServer }
});

For more information and full configuration options, see:

Share on X or LinkedIn
Paul Scanlon
Paul ScanlonTechnical Product Marketing Manager

Paul Scanlon sits between Developer Education and Product Marketing at Mastra. Previously, he was a Technical Product Marketing Manager at Neon and worked in Developer Relations at Gatsby, where he created educational content and developer experiences.

All articles by Paul Scanlon →