diff --git a/mcp/assets/release-notes-search-repositories.png b/mcp/assets/release-notes-search-repositories.png new file mode 100644 index 0000000..cf1ae8d Binary files /dev/null and b/mcp/assets/release-notes-search-repositories.png differ diff --git a/mcp/release-notes.mdx b/mcp/release-notes.mdx index 281c541..ca8c581 100644 --- a/mcp/release-notes.mdx +++ b/mcp/release-notes.mdx @@ -3,7 +3,7 @@ title: MCP release notes description: A comparative guide to the major changes in the Model Context Protocol (MCP) across versions. --- -import { Callout, Table } from "@/mdx/components"; +import { Callout, CodeWithTabs } from "@/mdx/components"; # MCP release notes @@ -110,21 +110,73 @@ One of the most significant changes is the **removal of JSON-RPC batching** supp 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. -```python -@app.tool("get_user_info", description="Get structured user information") -async def get_user_info(user_id: str): - return { - "content": [{ - "type": "structured", - "data": { - "user": { - "id": user_id, - "name": "John Doe", - "email": "john@example.com", - "status": "active" - } +**What you need to change:** Update your tool definitions to specify `Tool.outputSchema` and return `CallToolResult.structuredContent` in your tool implementations. + + + +```python !!tabs Python +# 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"] }, - "schema": { + # New: Add outputSchema to specify return format + outputSchema={ "type": "object", "properties": { "user": { @@ -138,41 +190,323 @@ async def get_user_info(user_id: str): } } } - }] - } + ) + ] ``` +```typescript !!tabs TypeScript +// Using official TypeScript SDK - @modelcontextprotocol/sdk package +import { Server } from "@modelcontextprotocol/sdk/server/index.js"; +import { + CallToolRequestSchema, + ListToolsRequestSchema, + Tool, + CallToolResult, +} 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): +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. + + + +```python !!tabs Python Server +# 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 +``` + +```python !!tabs Python Client +# 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 +``` + + -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 -3. **Enhanced Security Guidelines:** New [security best practices](https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices) documentation +### 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, making conversations more dynamic and context-aware. +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. + + + +```python !!tabs Python +@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}" + )] +``` -```python -async def handle_complex_query(query: str, context): - if missing_context(query): - # Request additional information from user - additional_info = await context.elicit_user_input( - prompt="I need more details about your requirements. What specific format would you like the output in?", - options=["JSON", "CSV", "Plain text"] - ) - # Process with additional context - return process_with_context(query, additional_info) +```typescript !!tabs TypeScript +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): +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 @@ -187,12 +521,38 @@ MCP-Protocol-Version: 2025-06-18 } ``` -### Other Notable Changes in MCP 2025-06-18 +### 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 +# Python SDK example +@mcp.tool(title="Search repositories") +def search_repositories(query: str, language: str = None) -> str: + """Search for GitHub repositories""" + # name remains "search_repositories" for programmatic access + # title shows "Search repositories" in UIs + return f"Searching repositories for: {query}" + +@mcp.resource("search://{query}", title="Repository Search Results") +def get_search_results(query: str) -> str: + """Get repository search results""" + return f"Search results for {query}" +``` + +Here's how it looks in VS Code Copilot Chat: + +![Search repositories in VS Code](./assets/release-notes-search-repositories.png) + +### Other Notable Changes - **Lifecycle Operations:** Changed from **SHOULD** to **MUST** for certain [lifecycle operations](https://modelcontextprotocol.io/specification/2025-06-18/basic/lifecycle#operation) - **Meta Fields:** Added `_meta` field to additional interface types for better extensibility - **Completion Context:** Added `context` field to `CompletionRequest` for LLM-based autocompletion support -- **Title Fields:** Added `title` field for human-friendly display names while keeping `name` as programmatic identifier ## MCP 2025-03-26 vs MCP 2024-11-05