# MCP release notes
Source: https://speakeasy.com/mcp/release-notes

import { Callout, CodeWithTabs, Table } from "@/mdx/components";



Each release of the Model Context Protocol (MCP) introduces changes to how LLM applications integrate external tools and context. These updates can improve developer experience, interoperability, and safety, but they may also introduce breaking changes or require adjustments in your implementation.

<Callout title="Note" type="info">
  This page is not an official MCP release notes page. It exists to help
  developers understand what changed, why it matters, and how to adopt the
  latest MCP revisions. For the full changelog, refer to the [official
  changelog](https://github.com/modelcontextprotocol/modelcontextprotocol/tags).
</Callout>

## MCP version history

This section summarizes the significant differences between MCP versions. The following table summarizes major changes between MCP versions.

<Table
  data={[
    {
      fieldName:
        "**[2025-06-18](https://github.com/modelcontextprotocol/modelcontextprotocol/releases/tag/2025-06-18)**",
      type: "`Jun 18, 2025`",
      required: "`Yes`",
      description:
        "- Removes [JSON-RPC batching](https://www.jsonrpc.org/specification#batch) (reversal from 2025-03-26)\n" +
        "- Adds [structured tool output](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content)\n" +
        "- Classifies MCP servers as [OAuth Resource Servers](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization#authorization-server-discovery)\n" +
        "- Requires [Resource Indicators](https://www.rfc-editor.org/rfc/rfc8707.html) for enhanced security\n" +
        "- Adds [elicitation](https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation) for server-initiated user requests\n" +
        "- Adds [resource links](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#resource-links) in tool results\n" +
        "- Requires MCP-Protocol-Version header for HTTP requests",
    },
    {
      fieldName:
        "**[2025-03-26](https://github.com/modelcontextprotocol/modelcontextprotocol/releases/tag/2025-03-26)**",
      type: "`Mar 26, 2025`",
      required: "`Yes`",
      description:
        "- Adds structured [OAuth 2.1-style authorization](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization)\n" +
        "- Replaces SSE with [Streamable HTTP](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports#streamable-http)\n" +
        "- Adds [JSON-RPC batching](https://www.jsonrpc.org/specification#batch) (later removed in 2025-06-18)\n" +
        "- Introduces tool annotations (for example, read-only, destructive)\n" +
        "- Adds support for audio content, progress messages, and completions capability",
    },
    {
      fieldName:
        "**[2024-11-05](https://github.com/modelcontextprotocol/modelcontextprotocol/releases/tag/2024-11-05)**",
      type: "`Jan 17, 2025`",
      required: "`—`",
      description:
        "- Initial schema stabilization\n" +
        "- Defines core architecture and message format\n" +
        "- Introduces tool, resource, and prompt concepts\n" +
        "- Adds ProgressNotification and sampling features\n" +
        "- Uses HTTP with SSE for streaming transport",
    },
  ]}
  columns={[
    { key: "fieldName", header: "Version" },
    { key: "type", header: "Release date" },
    { key: "description", header: "Changes" },
    { key: "required", header: "Breaking changes" },
  ]}
/>

## MCP 2025-06-18 vs MCP 2025-03-26

The **2025-06-18** MCP release represents a significant refinement of the Model Context Protocol, introducing structured tool outputs, enhanced OAuth security, and server-initiated user interactions. However, it also removes JSON-RPC batching from the previous version, simplifying protocol implementation while adding new capabilities for better security and data handling.

Key highlights include:

- **Removal of JSON-RPC batching:** The batching feature introduced in 2025-03-26 has been removed, simplifying the protocol implementation
- **Structured tool output:** Tools can now return structured, predictable data formats for better integration
- **Enhanced OAuth security:** MCP servers are now classified as OAuth Resource Servers with additional security requirements
- **Elicitation capability:** Servers can now request additional information from users during interactions
- **Resource links:** Tool results can now reference MCP resources, creating better integration between tools and data

You may choose to remain on the **2025-03-26** version if you need:

- **JSON-RPC batching support:** If your implementation relies on batching multiple requests
- **Simpler OAuth implementation:** The earlier version has less stringent OAuth requirements

## What's new in MCP 2025-06-18?

MCP **2025-06-18** introduces structured tool outputs, removes JSON-RPC batching, enhances OAuth security with Resource Server requirements, and adds elicitation for server-initiated user requests. Here's a detailed breakdown of the major updates:

### Removal of JSON-RPC Batching

One of the most significant changes is the **removal of JSON-RPC batching** support, which was introduced in 2025-03-26. This decision simplifies the protocol implementation and reduces complexity for both clients and servers. The maintainers mentioned that there was ['no compelling use case for batching'](https://github.com/modelcontextprotocol/modelcontextprotocol/pull/416), but not all users agreed.

<Callout title="Note" type="info">
  If you were using batching in your 2025-03-26 implementation, you'll need to
  refactor to send individual requests instead of batched ones.
</Callout>

### Structured Tool Output

The new [structured tool output](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#structured-content) capability allows tools to return data in predictable, structured formats rather than just plain text responses.

**What you need to change:** Update your tool definitions to specify `Tool.outputSchema` and return `CallToolResult.structuredContent` in your tool implementations.

<CodeWithTabs
  tabs={[
    {
      label: "Python",
      language: "python",
      code: `# Using official Python SDK - mcp package
  from mcp.server import Server
  from mcp.types import (
      CallToolRequestSchema,
      ListToolsRequestSchema,
      Tool,
      CallToolResult,
  )

  server = Server("example-server")

  # Define tool with outputSchema
  @mcp.tool()
  def get_user_info(user_id: str) -> CallToolResult:
      """Get structured user information"""
      user_data = {
          "user": {
              "id": user_id,
              "name": "John Doe",
              "email": "john@example.com",
              "status": "active"
          }
      }

      return CallToolResult(
          content=[{"type": "text", "text": f"Retrieved user {user_id}"}],
          # New: Use structuredContent for predictable data format
          structuredContent=user_data
      )

  # Set the tool's outputSchema property
  get_user_info.outputSchema = {
      "type": "object",
      "properties": {
          "user": {
              "type": "object",
              "properties": {
                  "id": {"type": "string"},
                  "name": {"type": "string"},
                  "email": {"type": "string"},
                  "status": {"type": "string"}
              }
          }
      }
  }

  # Register tool with outputSchema
  @server.list_tools()
  async def handle_list_tools() -> list[Tool]:
      return [
          Tool(
              name="get_user_info",
              description="Get structured user information",
              inputSchema={
                  "type": "object",
                  "properties": {
                      "user_id": {"type": "string"}
                  },
                  "required": ["user_id"]
              },
              # New: Add outputSchema to specify return format
              outputSchema={
                  "type": "object",
                  "properties": {
                      "user": {
                          "type": "object",
                          "properties": {
                              "id": {"type": "string"},
                              "name": {"type": "string"},
                              "email": {"type": "string"},
                              "status": {"type": "string"}
                          }
                      }
                  }
              }
          )
      ]`,
    },
    {
      label: "TypeScript",
      language: "typescript",
      code: `// Using official TypeScript SDK - @modelcontextprotocol/sdk package
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
  import {
    CallToolRequestSchema,
    CallToolResult,
    ListToolsRequestSchema,
    Tool,
  } from "@modelcontextprotocol/sdk/types.js";

  const server = new Server(
    { name: "example-server", version: "1.0.0" },
    { capabilities: { tools: {} } },
  );

  // Handle tool calls with structured output
  server.setRequestHandler(CallToolRequestSchema, async (request) => {
    const { name, arguments: args } = request.params;

    if (name === "get_user_info") {
      const userData = {
        user: {
          id: args.user_id,
          name: "John Doe",
          email: "john@example.com",
          status: "active",
        },
      };

      return {
        content: [{ type: "text", text: \`Retrieved user \${args.user_id}\` }],
        // New: Use structuredContent for predictable data format
        structuredContent: userData,
      } as CallToolResult;
    }
  });

  // Register tool with outputSchema
  server.setRequestHandler(ListToolsRequestSchema, async () => {
    return {
      tools: [
        {
          name: "get_user_info",
          description: "Get structured user information",
          inputSchema: {
            type: "object",
            properties: {
              user_id: { type: "string" },
            },
            required: ["user_id"],
          },
          // New: Add outputSchema to specify return format
          outputSchema: {
            type: "object",
            properties: {
              user: {
                type: "object",
                properties: {
                  id: { type: "string" },
                  name: { type: "string" },
                  email: { type: "string" },
                  status: { type: "string" },
                },
              },
            },
          },
        } as Tool,
      ],
    };
  });`,
}
]}
/>

### Enhanced OAuth Security

MCP servers are now classified as [OAuth Resource Servers](https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization#authorization-server-discovery), introducing two **MUST** requirements for both servers and clients:

1. **Resource Server Discovery:** Servers **MUST** provide metadata to help clients discover the corresponding Authorization Server
2. **Resource Indicators:** Clients **MUST** implement [RFC 8707](https://www.rfc-editor.org/rfc/rfc8707.html) Resource Indicators to prevent malicious servers from obtaining inappropriate access tokens

**What you need to change:** Update your server to expose authorization metadata and modify your client to include resource indicators in token requests.

<CodeWithTabs
  tabs={[
    {
      label: "Python Server",
      language: "python",
      code: `# Using official Python SDK - mcp package
  from mcp.server.fastmcp import FastMCP
  from mcp.server.auth.provider import TokenVerifier, TokenInfo
  from mcp.server.auth.settings import AuthSettings

  class MyTokenVerifier(TokenVerifier):
      async def verify_token(self, token: str) -> TokenInfo:
          # MUST: Validate token is scoped to this resource
          # Verify with your authorization server via token introspection
          response = await introspect_token(token)
          if not response.get("active"):
              raise ValueError("Token is not active")

          # Verify resource scope
          if "https://api.example.com/" not in response.get("aud", []):
              raise ValueError("Token not valid for this resource")

          return TokenInfo(
              sub=response["sub"],
              scopes=response["scope"].split(),
              expires_at=response["exp"]
          )

  # Create server with OAuth Resource Server configuration
  mcp = FastMCP(
      "example-server",
      token_verifier=MyTokenVerifier(),
      auth=AuthSettings(
          # MUST: Expose authorization server discovery metadata
          issuer_url="https://auth.example.com",
          resource_server_url="https://api.example.com/",
          required_scopes=["read", "write"],
      ),
  )

  async def introspect_token(token: str) -> dict:
      # Your token introspection implementation
      # Typically calls your authorization server's introspection endpoint
      pass`,
    },
    {
      label: "Python Client",
      language: "python",
      code: `# Using official Python SDK - mcp package
  from mcp.client.auth import OAuthClientProvider, TokenStorage
  from mcp.client.session import ClientSession
  from mcp.client.streamable_http import streamablehttp_client
  from mcp.shared.auth import OAuthClientInformationFull, OAuthClientMetadata, OAuthToken

  class SecureTokenStorage(TokenStorage):
      async def get_tokens(self) -> OAuthToken | None:
          # Load tokens from secure storage
          pass

      async def set_tokens(self, tokens: OAuthToken) -> None:
          # Save tokens to secure storage
          pass

      async def get_client_info(self) -> OAuthClientInformationFull | None:
          # Load client registration info
          pass

      async def set_client_info(self, client_info: OAuthClientInformationFull) -> None:
          # Save client registration info
          pass

  async def connect_to_protected_server():
      # Set up OAuth authentication with Resource Indicators
      oauth_auth = OAuthClientProvider(
          server_url="https://api.example.com/",  # MUST: Specify target resource
          client_metadata=OAuthClientMetadata(
              client_name="My MCP Client",
              redirect_uris=["http://localhost:3000/callback"],
              grant_types=["authorization_code", "refresh_token"],
              response_types=["code"],
          ),
          storage=SecureTokenStorage(),
          redirect_handler=lambda url: print(f"Visit: {url}"),
          callback_handler=lambda: ("auth_code", None),
      )

      # Connect with resource-scoped authentication
      async with streamablehttp_client(
          "https://api.example.com/mcp", auth=oauth_auth
      ) as (read, write, _):
          async with ClientSession(read, write) as session:
              await session.initialize()
              # Authenticated session ready with resource-scoped tokens`,

}
]}
/>

### Security Best Practices

The 2025-06-18 release introduces comprehensive [security best practices](https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices) documentation. This covers token validation, resource isolation, and threat mitigation to help prevent common security vulnerabilities in MCP implementations.

### MCP Elicitation Support

The new [elicitation](https://modelcontextprotocol.io/specification/2025-06-18/client/elicitation) capability enables servers to request additional information from users during interactions.

Elicitation allows servers to pause tool execution and ask users for specific information using structured JSON schemas. For example, a booking tool might request travel dates, or a file processor might ask for output format preferences.

**What you need to change:** Enable the `elicitation` capability and use `context.elicit()` to request user input when your tools need additional information.

<CodeWithTabs
  tabs={[
    {
      label: "Python",
      language: "python",
      code: `@server.call_tool()
  async def handle_call_tool(
      name: str, arguments: dict[str, Any] | None, context: RequestContext
  ) -> list[types.TextContent | types.ImageContent | types.EmbeddedResource]:
      if name == "book_flight":
          departure = arguments.get("departure")
          destination = arguments.get("destination")

          # If departure city is missing, ask the user
          if not departure:
              result = await context.elicit(
                  message="What city are you departing from?",
                  schema={
                      "type": "object",
                      "properties": {
                          "departure": {"type": "string", "description": "Departure city"}
                      },
                      "required": ["departure"]
                  }
              )

              if result.action == "accept":
                  departure = result.content["departure"]
              else:
                  return [types.TextContent(type="text", text="Flight booking cancelled")]

          # Continue with booking...
          return [types.TextContent(
              type="text",
              text=f"Booking flight from {departure} to {destination}"
          )]`,
      },
    {
      label: "TypeScript",
      language: "typescript",
        code: `server.setRequestHandler(CallToolRequestSchema, async (request, context) => {
    const { name, arguments: args } = request.params;

    if (name === "book_restaurant") {
      const restaurant = args.restaurant;
      const date = args.date;
      const partySize = args.partySize;

      // Check availability
      const isAvailable = await checkAvailability(restaurant, date, partySize);

      if (!isAvailable) {
        // Ask user if they want to check alternatives
        const result = await context.elicit({
          message: \`No availability at \${restaurant} on \${date}. Would you like to check alternative dates?\`,
          requestedSchema: {
            type: "object",
            properties: {
              checkAlternatives: {
                type: "boolean",
                title: "Check alternative dates",
                description: "Would you like me to check other dates?",
              },
              flexibleDates: {
                type: "string",
                title: "Date flexibility",
                description: "How flexible are your dates?",
                enum: ["next_day", "same_week", "next_week"],
                enumNames: ["Next day", "Same week", "Next week"],
              },
            },
            required: ["checkAlternatives"],
          },
        });

        if (result.action === "accept" && result.content?.checkAlternatives) {
          const alternatives = await findAlternatives(
            restaurant,
            date,
            partySize,
            result.content.flexibleDates as string,
          );
          return {
            content: [
              {
                type: "text",
                text: \`Found these alternatives: \${alternatives.join(", ")}\`,
              },
            ],
          };
        }

        return {
          content: [
            {
              type: "text",
              text: "No booking made. Original date not available.",
            },
          ],
        };
      }

      // Book the table
      await makeBooking(restaurant, date, partySize);
      return {
        content: [
          {
            type: "text",
            text: \`Booked table for \${partySize} at \${restaurant} on \${date}\`,
          },
        ],
      };
    }
  });`,
}
]}
/>

Key features:

- **Structured requests:** Use JSON schemas to define exactly what information you need
- **User control:** Users can accept, reject, or cancel elicitation requests
- **Simple schemas:** Only primitive types (string, number, boolean) are supported for easier client implementation
- **Security:** Servers must not request sensitive information through elicitation

### Resource Links in Tool Results

Tools can now return [resource links](https://modelcontextprotocol.io/specification/2025-06-18/server/tools#resource-links) that reference MCP resources, creating better integration between tool outputs and available data sources.

### MCP Protocol Version Headers

HTTP requests now require the `MCP-Protocol-Version` header to enable [version negotiation](https://modelcontextprotocol.io/specification/2025-06-18/basic/transports#protocol-version-header). **Clients are responsible** for setting this header in all HTTP requests after the initial version negotiation is completed during the handshake.

**What you need to change:** Update your HTTP client to include the `MCP-Protocol-Version` header in all requests after connecting.

```http
POST /mcp HTTP/1.1
Host: example.com
Content-Type: application/json
MCP-Protocol-Version: 2025-06-18

{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": { ... }
}
```

### Title Fields for Better UX

The `title` field has been added to tools, resources, and prompts to provide human-friendly display names while keeping `name` as the programmatic identifier. This improves how MCP items appear in client interfaces.

**What you need to change:** Add `title` fields to your tools, resources, and prompts for better user experience in clients like VS Code Copilot Chat.

In practice, this means users see "Search repositories" instead of "search_repositories" in their IDE:

```python