跳到正文
Mastra Blog·· 3 小時前AI 評分50

Mastra 加入 MCP 2.0 支援

Introducing MCP 2.0 Support in Mastra

AI 導讀

Mastra 透過 @mastra/mcp ^2.0.0 支援 MCP 2026-07-28 規格,該規格移除伺服器工作階段,令每個請求均可獨立處理。任何伺服器實例都能回應請求,毋須使用黏性工作階段或共用工作階段儲存。伺服器亦可在無請求時於無伺服器平台縮減至零。

正文

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.

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:

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).
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:

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:

{
  "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:

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:

{
  "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.

{
  "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().

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:

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:

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:

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:

來源:Mastra Blog · mastra.ai